Class: Typr::Text

Inherits:
Stack show all
Defined in:
lib/text.rb

Overview

) text.show

Constant Summary

Constants included from Typr

COLORS, COLOR_MAP, ERASE_LINE, INPUT, KEY_ESCAPE, KEY_PAGEDOWN, KEY_PAGEUP, KEY_RETURN, KEY_TAB, MIMETYPES, MODES

Instance Attribute Summary collapse

Attributes inherited from Stack

#header, #hints, #hints_start, #keymap, #selected, #separator, #start

Attributes inherited from Space

#borders, #bottom, #colors, #interval, #left, #margin, #right, #top

Instance Method Summary collapse

Methods inherited from Stack

#cycle, #down, #draw_hints, #headspace, #height, #page, #page_down, #page_up, #pick, #send, #to_bottom, #to_top, #up

Methods inherited from Space

#border=, #draw_border, #height, #start, #stop, #symbolize, #width

Methods included from Typr

#background, clear, #clip, #coerce_type, #color, #color_code, column, #draw, exit, #fade, #foreground, #get_background, #get_foreground, height, init, #mode, #mode_code, #move, #move_code, on_resize, position, #prepare, #printables, read_key, read_line, #real_size, row, size, text_width, width, word_next, word_prev

Constructor Details

#initialize(args = {}) ⇒ Text

Creates a new Text widget.

text = Typr::Text.new(input: "hello", header: "Title")
text = Typr::Text.new(input: $stdin, tabspace: 4)


183
184
185
186
187
188
189
190
191
192
# File 'lib/text.rb', line 183

def initialize args={}
  @tabspace = 2
  @data, @lines, @tail = "", [0], ""
  @search = { pattern: nil, dir: :forward, last: nil, matches: [] }
  super args
  @colors = { header: :yellow, search: :grey20 }.merge( @colors || {} )
  @keymap = { search: ?/, search_next: ?n, search_prev: ?p }.merge @keymap
  @lastwidth = width
  self << @input
end

Instance Attribute Details

#tabspaceObject

Tabstop width in spaces. Default: 2.



26
27
28
# File 'lib/text.rb', line 26

def tabspace
  @tabspace
end

Instance Method Details

#build(input = nil) ⇒ Object Also known as: <<

Rebuilds the internal line buffer from input. Splits text at newlines and word boundaries to fit the current width. Pass nil to re-parse from the existing buffer (triggered on resize).

text.build("one\ntwo three")  # => #<Typr::Text ...>
text << "appended line"       # alias for build


35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/text.rb', line 35

def build input=nil
  ( @lines = [0]; input = "" ) if rebuild = !input
  return self if rebuild && input == ""
  input = StringIO.new( input.to_s.dup ) unless input.respond_to?(:read)
  @tail = ""
  until input.closed? or input.eof?
    capacity = width - @margin.size - @tail.length
    chunk = (capacity < 1) ? "x" : capacity
    line = @tail + input.read(chunk)
    if line.empty?
      break
    else
      wrap_idx = line.index("\n") || line.rindex(" ") || line.length
      wrap = [wrap_idx, line.length].min + 1
      @data += line[0...wrap]
      if wrap < line.length
        @tail = line[wrap..-1]
        @lines << @data.length
      else
        @tail = ""
      end
    end
    break if rebuild && line[-1] == "\n"
  end
  until @tail.empty?
    wrap_idx = @tail.index("\n") || @tail.rindex(" ") || @tail.length
    wrap = [wrap_idx, @tail.length].min + 1
    @data += @tail[0...wrap]
    if wrap < @tail.length
      @lines << @data.length
      @tail = @tail[wrap..-1]
    else
      @tail = ""
    end
  end
  @lines << @data.length unless @lines.last == @data.length
  highlight if @re
end

#highlightObject

Recompute the set of lines containing the current pattern.



162
163
164
165
166
167
# File 'lib/text.rb', line 162

def highlight
  @search[:matches] = @re ?
    @lines[0...-1].each_index.select{ |i|
      @data[ @lines[i] ... @lines[i+1] ][ @re ] } : []
  return
end

#highlight_clearObject

Clear the search pattern and all match highlights.



171
172
173
174
175
176
# File 'lib/text.rb', line 171

def highlight_clear
  @re = @search[:pattern] = nil
  @search[:matches] = []
  @search[:last] = nil
  return
end

#positions(row) ⇒ Object

Returns word-boundary column offsets for a given page row. Used internally to place hint markers between words.

text.positions(2)  # => [0, 6, 10, ...]


99
100
101
# File 'lib/text.rb', line 99

def positions row
  [0] + @page[row-1].chars.map.with_index{|c,i| i+1 if c == @separator}.compact
end

Renders a single display row. Pass an Integer for a content row or :header for the header bar.

text.print(:header)  # renders the header bar
text.print(3)        # renders row 3 of the current page


81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/text.rb', line 81

def print row=nil
  header = ( row == :header )
  return super unless row and @lines[ row + 1 ] unless header
  text = ( header ? @header : @page[ row-@start ] )
  if row.is_a?(Integer) and @search[:matches].include? row
    background @colors[:search]
    draw prepare( text, width )
    background
  else
    draw prepare( text, width )
  end
end

#reset(type = :all) ⇒ Object



194
195
196
197
198
199
200
# File 'lib/text.rb', line 194

def reset type=:all
  case type
    when :search; highlight_clear
    when :all; reset [:position, :selection, :search]; return
  end
  super
end

#rowsObject

Total number of text lines in the buffer.

text.rows  # => 42


120
# File 'lib/text.rb', line 120

def rows; @lines.count end

#search(query = nil, dir = :forward) ⇒ Object

Pager-style search over the buffer, modeled after less/vim.

With a query (String, matched literally, or Regexp) it stores the pattern, highlights every matching line, and scrolls to the first match from the current position. dir is :forward or :backward. Returns the matched line id, or nil when nothing matches.

With no argument it prompts interactively for a pattern at the bottom of the screen (pressing Return searches, Escape aborts).

Matching is smart-case: lowercase queries are case-insensitive; a query containing an uppercase letter is case-sensitive.

text.search "needle"    # => 12 (or nil)
text.search /^TODO/     # => 3
text.search "needle", :backward   # search upward


140
141
142
143
144
145
146
147
148
149
150
# File 'lib/text.rb', line 140

def search query=nil, dir=:forward
  return search_prompt if query.nil?
  @search[:pattern] = query
  @search[:dir] = dir
  @search[:last] = nil
  @re = query.is_a?(Regexp) ? query :
    Regexp.new( Regexp.escape( query ),
      ( query[/[A-Z]/] ? 0 : Regexp::IGNORECASE ) )
  highlight
  step
end

#search_nextObject

Jump to the next match of the last search (wrap-around). Returns its line id or nil.



154
# File 'lib/text.rb', line 154

def search_next; @search[:dir] = :forward; step end

#search_prevObject

Jump to the previous match of the last search (wrap-around). Returns its line id or nil.



158
# File 'lib/text.rb', line 158

def search_prev; @search[:dir] = :backward; step end

#showObject

Full draw cycle: rebuilds on resize, computes visible lines, and renders the viewport with borders and hints.

text.show


108
109
110
111
112
113
114
# File 'lib/text.rb', line 108

def show
  (build; @lastwidth = width) if width != @lastwidth
  range = @lines[@start..@start+height]
  page = StringIO.new @data[range.first..range.last]
  @page=range[1..-1].map{|pos| page.read(pos-range.first-page.pos).rstrip}
  super
end