legion-tty 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c79bfc84ae3c3bc48ae055db72296b3aec2ab24c9a86dba5d7604e5732a3503e
4
- data.tar.gz: 96cb9a58135228887222a4fcc6064209ae1e825fad949c0cbd143c40c5249ed7
3
+ metadata.gz: 7e484905e47a82bedca53b308ff66880c8b64bcb320d9af6a1130fe4ad50a056
4
+ data.tar.gz: f5c49491e1227c5984e99f161662eef56a5d5924903666aa2ea9dcaecccf098f
5
5
  SHA512:
6
- metadata.gz: ea7ed87c3baffa35d8e02852ce301c532e0aef8deab17a40892db373abd7f82ac49ae60f22a88e4ea66e90f3d56996db0850cef8e8e09ffd743f8e0a924ff0e3
7
- data.tar.gz: dd2e68abefc811eface7a9db9c7c2014bccf433d7b859b42068746fc952afdbcc4470ec41e761a0d86df40236bb2aebeb334afddcbe278be06fe9acb78f6715f
6
+ metadata.gz: f2eb92406a0f61f109213eec5858122038e270052cdca8c9c4f3bfcfba7d64d0ead54a1f40b53dd356aa08dc172f920a09f47c46dd108c15a708e2a680ffe474
7
+ data.tar.gz: e107d25bbb2ce75ce886329f8ef02f2fb5991278c5a4d140cc809d940adf34aba9f14a9c4269309be847ae78c78d48ee4d33540d03bfc71712d4b4261ae7a080
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.2] - 2026-04-20
4
+
5
+ ### Fixed
6
+ - **Bracketed paste support** — `App#run_loop` now enables bracketed paste mode (`\e[?2004h`) on startup and disables (`\e[?2004l`) on shutdown; `read_csi_sequence` detects `\e[200~` / `\e[201~` paste markers and buffers the full pasted text into a `{ paste: text }` event (closes #22)
7
+ - **InputBar paste handling** — `handle_key` recognizes paste Hash events and inserts the full pasted text at the cursor position, replacing newlines with spaces, without triggering submit (closes #22)
8
+ - **Non-symbol key guard** — `dispatch_key` checks `key.is_a?(Symbol)` before the scroll-event include check, preventing `NoMethodError` on Hash paste events (closes #22)
9
+ - **`normalize_key` paste passthrough** — Hash events (paste) pass through without KEY_MAP lookup (closes #22)
10
+
3
11
  ## [0.5.1] - 2026-04-18
4
12
 
5
13
  ### Fixed
@@ -40,6 +40,8 @@ module Legion
40
40
  DISABLE_ALT_SCREEN = "\e[?1049l"
41
41
  ENABLE_MOUSE = "\e[?1000h\e[?1006h"
42
42
  DISABLE_MOUSE = "\e[?1000h\e[?1006l"
43
+ ENABLE_BRACKETED_PASTE = "\e[?2004h"
44
+ DISABLE_BRACKETED_PASTE = "\e[?2004l"
43
45
  SGR_MOUSE_RE = /\A\e\[<(\d+);(\d+);(\d+)([Mm])\z/
44
46
 
45
47
  attr_reader :config, :credentials, :screen_manager, :hotkeys, :llm_chat, :input_bar
@@ -158,7 +160,7 @@ module Legion
158
160
 
159
161
  # --- Event Loop ---
160
162
 
161
- # rubocop:disable Metrics/AbcSize
163
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
162
164
  def run_loop
163
165
  require 'io/console'
164
166
 
@@ -166,6 +168,7 @@ module Legion
166
168
  @raw_mode = true
167
169
  $stdout.print ENABLE_ALT_SCREEN
168
170
  $stdout.print ENABLE_MOUSE
171
+ $stdout.print ENABLE_BRACKETED_PASTE
169
172
  $stdout.print cursor.hide
170
173
  $stdout.print cursor.clear_screen
171
174
 
@@ -178,25 +181,38 @@ module Legion
178
181
 
179
182
  key = normalize_key(raw_key)
180
183
  dispatch_key(key)
184
+ drain_burst_keys(raw_in)
181
185
  end
182
186
  end
183
187
  rescue Interrupt
184
188
  nil
185
189
  ensure
186
190
  @raw_mode = false
191
+ $stdout.print DISABLE_BRACKETED_PASTE
187
192
  $stdout.print DISABLE_MOUSE
188
193
  $stdout.print cursor.show
189
194
  $stdout.print DISABLE_ALT_SCREEN
190
195
  shutdown
191
196
  end
192
- # rubocop:enable Metrics/AbcSize
197
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
193
198
 
194
199
  def needs_refresh?
195
200
  active = @screen_manager.active_screen
196
201
  active.respond_to?(:streaming?) && active.streaming?
197
202
  end
198
203
 
204
+ def drain_burst_keys(raw_in)
205
+ while @running && raw_in.wait_readable(0)
206
+ burst_key = read_raw_key(raw_in, timeout: 0)
207
+ break unless burst_key
208
+
209
+ dispatch_key(normalize_key(burst_key))
210
+ end
211
+ end
212
+
199
213
  def normalize_key(raw)
214
+ return raw if raw.is_a?(Hash)
215
+
200
216
  mouse = parse_sgr_mouse(raw)
201
217
  return mouse if mouse
202
218
 
@@ -205,6 +221,7 @@ module Legion
205
221
 
206
222
  # --- Key Dispatch ---
207
223
 
224
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
208
225
  def dispatch_key(key)
209
226
  if key == :ctrl_c
210
227
  @running = false
@@ -225,7 +242,7 @@ module Legion
225
242
  active = @screen_manager.active_screen
226
243
  return unless active
227
244
 
228
- if %i[scroll_up scroll_down].include?(key)
245
+ if key.is_a?(Symbol) && %i[scroll_up scroll_down].include?(key)
229
246
  dispatch_to_screen(active, key)
230
247
  return
231
248
  end
@@ -236,6 +253,7 @@ module Legion
236
253
  dispatch_to_screen(active, key)
237
254
  end
238
255
  end
256
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
239
257
 
240
258
  def dispatch_to_input_screen(screen, key)
241
259
  result = @input_bar.handle_key(key)
@@ -311,9 +329,28 @@ module Legion
311
329
  seq << c
312
330
  break if c.ord.between?(0x40, 0x7E)
313
331
  end
332
+
333
+ return read_paste_content(io) if seq == "\e[200~"
334
+
314
335
  seq
315
336
  end
316
337
 
338
+ def read_paste_content(io)
339
+ buf = +''
340
+ end_marker = "\e[201~"
341
+ loop do
342
+ break unless io.wait_readable(1)
343
+
344
+ c = io.getc
345
+ break unless c
346
+
347
+ buf << c
348
+ break if buf.end_with?(end_marker)
349
+ end
350
+ buf.delete_suffix!(end_marker)
351
+ { paste: buf }
352
+ end
353
+
317
354
  def parse_sgr_mouse(raw)
318
355
  match = SGR_MOUSE_RE.match(raw)
319
356
  return nil unless match
@@ -32,6 +32,8 @@ module Legion
32
32
 
33
33
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
34
34
  def handle_key(key)
35
+ return handle_paste(key[:paste]) if key.is_a?(Hash) && key.key?(:paste)
36
+
35
37
  case key
36
38
  when :enter
37
39
  submit_line
@@ -202,6 +204,14 @@ module Legion
202
204
  :handled
203
205
  end
204
206
 
207
+ def handle_paste(text)
208
+ sanitized = text.to_s.gsub(/\r\n?/, "\n").gsub("\n", ' ')
209
+ @buffer.insert(@cursor_pos, sanitized)
210
+ @cursor_pos += sanitized.length
211
+ @tab_matches = []
212
+ :handled
213
+ end
214
+
205
215
  def insert_char(key)
206
216
  return :pass unless key.is_a?(String) && key.length == 1 && key.ord >= 32
207
217
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module TTY
5
- VERSION = '0.5.1'
5
+ VERSION = '0.5.2'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legion-tty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity