Class: LLM::Repl::Input Private

Inherits:
Object
  • Object
show all
Defined in:
lib/llm/repl/input.rb,
lib/llm/repl/input/row.rb,
lib/llm/repl/input/char.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

The LLM::Repl::Input class manages the editable input line shown at the bottom of the REPL.

Defined Under Namespace

Classes: Char, Row

Constant Summary collapse

CTRL =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

{
  A: Curses::KEY_CTRL_A,
  E: Curses::KEY_CTRL_E,
  F: Curses::KEY_CTRL_F,
  K: Curses::KEY_CTRL_K,
  Y: Curses::KEY_CTRL_Y,
  D: Curses::KEY_CTRL_D,
  P: Curses::KEY_CTRL_P,
  N: Curses::KEY_CTRL_N
}
REPEATS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

This hash tracks how many times a given key was pressed repeatedly without being interrupted by another key. The previous key is reset to 0 when a different key is pressed.

{}
UP =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Curses::Key::UP
DOWN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Curses::Key::DOWN
LEFT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Curses::Key::LEFT
RIGHT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Curses::Key::RIGHT
PGUP =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Curses::KEY_PPAGE
PGDOWN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Curses::KEY_NPAGE
TAB =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

9
ESC =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

27
ENTER =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

10
BACKSPACE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

127
PASTE_THRESHOLD =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Threshold in seconds. If characters arrive faster than this, we assume the user is pasting multi-line text. Human typing is ~150–300ms per key, so 50ms reliably distinguishes a paste from manual typing.

0.05

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(repl, options = {}) ⇒ LLM::Repl::Input

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parameters:



58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/llm/repl/input.rb', line 58

def initialize(repl, options = {})
  @name = repl.name
  @agent = repl.agent
  @provider = @agent.llm.name
  @rows = [Row.new]
  @cursor = [0, 0]
  @scroll = 0
  @height = options.fetch(:height, 3)
  @last_char_at = nil
  @memory = @agent.messages.select(&:user?).map(&:content)
  @walker = Walker.new(@memory)
  @paste = false
end

Instance Attribute Details

#paste=(value) ⇒ void (writeonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Parameters:

  • value (Boolean)


53
54
55
# File 'lib/llm/repl/input.rb', line 53

def paste=(value)
  @paste = value
end

Instance Method Details

#autocompletevoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/llm/repl/input.rb', line 250

def autocomplete
  return unless text[0] == "/"
  ##
  # This method implements a simple autocomplete
  # that supports cycling through all known
  # commands. When given tab in quick succession,
  # we cycle to the nearest neighbour for the last
  # full match. However, it's not based on similarity,
  # it's just the next element in the array.
  keys = LLM::Command.registry.keys
  candidates = LLM::Command.complete(text)
  candidate =
    if REPEATS[TAB] >= 1
      keys[keys.index(candidates[0]) + 1] || keys[0]
    else
      candidates[0]
    end
  set(text: "/#{candidate}")
end

#bufferString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns The input buffer as an ordinary string.

Returns:

  • (String)

    The input buffer as an ordinary string.



75
76
77
# File 'lib/llm/repl/input.rb', line 75

def buffer
  text
end

#cursor_posArray(Integer, Integer)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the cursor position as [line, column] within the visible viewport.

Returns:

  • (Array(Integer, Integer))


201
202
203
204
205
206
# File 'lib/llm/repl/input.rb', line 201

def cursor_pos
  scroll!
  row, col = @cursor
  col += prompt.size if row.zero?
  [row - @scroll, col]
end

#deletevoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Deletes the character at the cursor, or consumes the break and pulls up the next row at the end of a row.



287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/llm/repl/input.rb', line 287

def delete
  row, col = @cursor
  if col < @rows[row].chars.size
    @rows[row].chars.delete_at(col)
  elsif row < @rows.size - 1
    nxt = @rows[row + 1]
    ##
    # A wrapped row was split at a space; restore it so
    # the merged words don't run together.
    @rows[row].chars << Char.new(" ") if nxt.break_type == :space
    @rows[row].chars.concat(nxt.chars)
    @rows.delete_at(row + 1)
  end
end

#heightInteger

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Integer)


181
182
183
# File 'lib/llm/repl/input.rb', line 181

def height
  @height
end

#killvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.



272
273
274
275
276
277
278
279
280
# File 'lib/llm/repl/input.rb', line 272

def kill
  row, col = @cursor
  tail = @rows[row].chars[col..].map(&:to_s).join
  tail += @rows[(row + 1)..].map(&:to_s).join("\n")
  @copy = tail
  @rows[row].chars.slice!(col..)
  @rows = @rows[0..row]
  @cursor = [row, col]
end

#linesArray<String>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the visible lines of the input buffer. The viewport follows the cursor so the cursor line is always visible.

Returns:

  • (Array<String>)


189
190
191
192
193
194
195
# File 'lib/llm/repl/input.rb', line 189

def lines
  scroll!
  visible = @rows[@scroll, height] || []
  visible.each_with_index.map do |row, i|
    (i.zero? && @scroll.zero? ? prompt : "") + row.to_s
  end
end

#move_endvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.



216
217
218
# File 'lib/llm/repl/input.rb', line 216

def move_end
  @cursor = [@rows.size - 1, @rows.last.chars.size]
end

#move_forwardvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.



244
245
246
# File 'lib/llm/repl/input.rb', line 244

def move_forward
  move_right
end

#move_leftvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.



222
223
224
225
226
227
228
229
# File 'lib/llm/repl/input.rb', line 222

def move_left
  row, col = @cursor
  @cursor =
    if col > 0 then [row, col - 1]
    elsif row > 0 then [row - 1, @rows[row - 1].chars.size]
    else [0, 0]
    end
end

#move_rightvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.



233
234
235
236
237
238
239
240
# File 'lib/llm/repl/input.rb', line 233

def move_right
  row, col = @cursor
  @cursor =
    if col < @rows[row].chars.size then [row, col + 1]
    elsif row < @rows.size - 1 then [row + 1, 0]
    else [row, col]
    end
end

#move_startvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.



210
211
212
# File 'lib/llm/repl/input.rb', line 210

def move_start
  @cursor = [0, 0]
end

#on_char(window, char, now) ⇒ Symbol?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parameters:

Returns:

  • (Symbol, nil)


83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/llm/repl/input.rb', line 83

def on_char(window, char, now)
  is_paste = lambda { @last_char_at and (now - @last_char_at) < PASTE_THRESHOLD }
  if char and @char != char
    REPEATS[@char] = 0
  end
  if PGUP == char
    (window.rows - 3).times { window.scroll_up }
    :pageup
  elsif PGDOWN == char
    (window.rows - 3).times { window.scroll_down }
    :pagedown
  elsif TAB == char
    autocomplete
    :tab
  elsif ESC == char
    @agent.cancel!
  elsif CTRL[:P] == char
    set(text: @walker.prev.dup)
    :ctrl_p
  elsif CTRL[:N] == char
    set(text: @walker.next.dup)
    :ctrl_n
  elsif CTRL[:D] == char
    delete
    :ctrl_d
  elsif CTRL[:A] == char
    move_start
    :ctrl_a
  elsif CTRL[:E] == char
    move_end
    :ctrl_e
  elsif CTRL[:F] == char
    move_forward
    :ctrl_f
  elsif CTRL[:Y] == char
    restore
    :ctrl_y
  elsif CTRL[:K] == char
    kill
    :ctrl_k
  elsif char == LEFT
    move_left
    :left
  elsif char == RIGHT
    move_right
    :right
  elsif BACKSPACE == char
    backspace
    :backspace
  elsif ENTER == char
    if @paste = is_paste.()
      insert("\n")
      :char
    else
      @memory.push(text)
      @walker.cursor = @memory.size
      :submit
    end
  elsif char == UP
    window.scroll_up
    :up
  elsif char == DOWN
    window.scroll_down
    :down
  elsif String === char
    ##
    # A paste arrives as a burst of fast characters. Flag it so the
    # next loop iteration drains the rest through read_paste in a
    # single shot, instead of inserting one character at a time and
    # redrawing after each. A string that is already the product of
    # read_paste (multi-character) is inserted as-is, one char at a
    # time, so every newline creates a row and every Char stays a
    # single character.
    @paste = true if char.length == 1 && is_paste.()
    char.each_char { |c| insert(c) }
    :char
  else
    nil
  end
ensure
  if char
    REPEATS[char] += 1
    @last_char_at = now
    @char = char
  end
end

#paste?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


332
333
334
# File 'lib/llm/repl/input.rb', line 332

def paste?
  @paste
end

#restorevoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/llm/repl/input.rb', line 304

def restore
  return unless @copy
  row, col = @cursor
  @copy.each_char do |char|
    if char == "\n"
      @rows.insert(row + 1, Row.new(:newline))
      row += 1
      col = 0
    else
      @rows[row].chars.insert(col, Char.new(char))
      col += 1
    end
  end
  @cursor = [row, col]
end

#takeString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (String)


322
323
324
325
326
327
328
# File 'lib/llm/repl/input.rb', line 322

def take
  text.tap do
    @rows = [Row.new]
    @cursor = [0, 0]
    @scroll = 0
  end
end

#to_sString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (String)


172
173
174
175
176
177
# File 'lib/llm/repl/input.rb', line 172

def to_s
  @rows
    .each_with_index
    .map { |row, i| (i.zero? ? prompt : "") + row.to_s }
    .join("\n")
end