Class: TuiTui::TextInput
- Inherits:
-
Object
- Object
- TuiTui::TextInput
- Defined in:
- lib/tui_tui/text_input.rb
Overview
A single-line, grapheme-aware text buffer with a cursor: the editing model shared by the prompt, search bars, command-palette queries, and editable cells. It is Canvas-agnostic — it knows only graphemes, cursor position, and display columns — so the same model drives any chrome around it.
Editing happens by grapheme cluster, so the cursor never lands inside an emoji modifier or a base+combining-mark pair, and a combining mark typed after its base merges into one grapheme.
The buffer owns only the editing keys (text, Backspace/Delete, arrows, Home/End) via #handle_editing; semantic keys like Enter and Escape belong to the host, which decides what they mean.
Defined Under Namespace
Classes: Viewport
Instance Attribute Summary collapse
-
#cursor ⇒ Object
The cursor as a grapheme index in 0..length.
Instance Method Summary collapse
- #delete_back ⇒ Object
- #delete_forward ⇒ Object
- #empty? ⇒ Boolean
-
#handle_editing(key) ⇒ Object
Handle an editing key, returning true if it was consumed.
-
#index_at(rel, from: 0) ⇒ Object
The grapheme index whose left edge is closest to
relcolumns past the graphemefrom— for mapping a mouse click onto the cursor, wherefromis the left grapheme of the visible viewport (see #viewport). -
#initialize(value: "") ⇒ TextInput
constructor
A new instance of TextInput.
-
#insert(string) ⇒ Object
Insert a string at the cursor, re-clustering across both boundaries so a combining mark merges into its neighbour.
- #left ⇒ Object
- #length ⇒ Object
- #right ⇒ Object
- #to_end ⇒ Object
- #to_home ⇒ Object
-
#type(key) ⇒ Object
Insert
keyas literal text when it is printable (no control bytes), returning whether it was accepted. - #value ⇒ Object
- #viewport(cols) ⇒ Object
Constructor Details
#initialize(value: "") ⇒ TextInput
Returns a new instance of TextInput.
23 24 25 26 |
# File 'lib/tui_tui/text_input.rb', line 23 def initialize(value: "") @graphemes = value.grapheme_clusters @cursor = @graphemes.length end |
Instance Attribute Details
#cursor ⇒ Object
The cursor as a grapheme index in 0..length.
21 22 23 |
# File 'lib/tui_tui/text_input.rb', line 21 def cursor @cursor end |
Instance Method Details
#delete_back ⇒ Object
72 73 74 75 76 77 78 |
# File 'lib/tui_tui/text_input.rb', line 72 def delete_back return false if @cursor.zero? @graphemes.delete_at(@cursor - 1) @cursor -= 1 true end |
#delete_forward ⇒ Object
80 81 82 83 84 85 |
# File 'lib/tui_tui/text_input.rb', line 80 def delete_forward return false if @cursor >= @graphemes.length @graphemes.delete_at(@cursor) true end |
#empty? ⇒ Boolean
29 |
# File 'lib/tui_tui/text_input.rb', line 29 def empty? = @graphemes.empty? |
#handle_editing(key) ⇒ Object
Handle an editing key, returning true if it was consumed. Enter, Escape and any other non-editing key return false so the host can act on them.
39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/tui_tui/text_input.rb', line 39 def handle_editing(key) case key when KeyCode::BACKSPACE, :backspace then delete_back when :delete then delete_forward when :left then left when :right then right when :home then to_home when :end then to_end when String then return type(key) else return false end true end |
#index_at(rel, from: 0) ⇒ Object
The grapheme index whose left edge is closest to rel columns past the
grapheme from — for mapping a mouse click onto the cursor, where from
is the left grapheme of the visible viewport (see #viewport).
95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/tui_tui/text_input.rb', line 95 def index_at(rel, from: 0) return from if rel <= 0 width = 0 @graphemes[from..].each_with_index do |grapheme, i| w = DisplayText.new(grapheme).width return from + i if rel < width + ((w + 1) / 2) width += w end @graphemes.length end |
#insert(string) ⇒ Object
Insert a string at the cursor, re-clustering across both boundaries so a combining mark merges into its neighbour. The cursor lands after the text.
66 67 68 69 70 |
# File 'lib/tui_tui/text_input.rb', line 66 def insert(string) head = @graphemes[0...@cursor].join @graphemes = (head + string + @graphemes[@cursor..].join).grapheme_clusters @cursor = (head + string).grapheme_clusters.length end |
#left ⇒ Object
87 |
# File 'lib/tui_tui/text_input.rb', line 87 def left = (@cursor = [@cursor - 1, 0].max) |
#length ⇒ Object
30 |
# File 'lib/tui_tui/text_input.rb', line 30 def length = @graphemes.length |
#right ⇒ Object
88 |
# File 'lib/tui_tui/text_input.rb', line 88 def right = (@cursor = [@cursor + 1, @graphemes.length].min) |
#to_end ⇒ Object
90 |
# File 'lib/tui_tui/text_input.rb', line 90 def to_end = (@cursor = @graphemes.length) |
#to_home ⇒ Object
89 |
# File 'lib/tui_tui/text_input.rb', line 89 def to_home = (@cursor = 0) |
#type(key) ⇒ Object
Insert key as literal text when it is printable (no control bytes),
returning whether it was accepted. The entry point for typed input — hosts
that bypass #handle_editing (e.g. append-only queries) call this directly so
the printable rule lives in one place rather than at every call site.
57 58 59 60 61 62 |
# File 'lib/tui_tui/text_input.rb', line 57 def type(key) return false unless printable?(key) insert(key) true end |
#value ⇒ Object
28 |
# File 'lib/tui_tui/text_input.rb', line 28 def value = @graphemes.join |
#viewport(cols) ⇒ Object
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/tui_tui/text_input.rb', line 119 def (cols) cols = [cols, 1].max # Reserve room for the block cursor: a real grapheme's width, else one # column for the trailing space drawn when the cursor sits at the end. cell = grapheme_at_cursor ? DisplayText.new(grapheme_at_cursor).width : 1 # Scroll right one grapheme at a time until the cursor cell fits, shrinking # the running leading width instead of recomputing it from scratch. start = 0 lead = width_before_cursor while start < @cursor && lead + cell > cols lead -= DisplayText.new(@graphemes[start]).width start += 1 end Viewport.new( text: visible_slice(start, cols), cursor_col: lead, cursor_grapheme: grapheme_at_cursor, start: start ) end |