legion-tty 0.5.0 → 0.5.1

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: 20065c242e16ca4262309393cdad733b473f139c1efd288512ffe9d704fb5b69
4
- data.tar.gz: 1159cc15d4545b6dbda4d9a69087a9f9a929c6579fd477c1ae0a27d12dbf95c5
3
+ metadata.gz: c79bfc84ae3c3bc48ae055db72296b3aec2ab24c9a86dba5d7604e5732a3503e
4
+ data.tar.gz: 96cb9a58135228887222a4fcc6064209ae1e825fad949c0cbd143c40c5249ed7
5
5
  SHA512:
6
- metadata.gz: 77d12618eb36e18a62e3c0fbfa973f645155a4611771982d596b03803a1c98e3e4b592f3b40699842cd23f977ecb86518c1cdd3a26a4e58db936f0f45ba6137c
7
- data.tar.gz: 4b8f2551be051f7140c851f9cac8a632430a102eda8f0ca78a5b1750fb0f2c266fc903fafd6e01606c5a2222986ab1cab3539003d41120bfac8a2762957de329
6
+ metadata.gz: ea7ed87c3baffa35d8e02852ce301c532e0aef8deab17a40892db373abd7f82ac49ae60f22a88e4ea66e90f3d56996db0850cef8e8e09ffd743f8e0a924ff0e3
7
+ data.tar.gz: dd2e68abefc811eface7a9db9c7c2014bccf433d7b859b42068746fc952afdbcc4470ec41e761a0d86df40236bb2aebeb334afddcbe278be06fe9acb78f6715f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.1] - 2026-04-18
4
+
5
+ ### Fixed
6
+ - **Alternate screen buffer** — `App#run_loop` now enables `\e[?1049h` on startup and `\e[?1049l` on shutdown so the TUI no longer pollutes shell scrollback and the prior shell session is restored on exit (closes #20)
7
+ - **Mouse wheel scroll** — enabled SGR mouse tracking (`\e[?1000h` + `\e[?1006h`) and routed wheel-up/wheel-down events to `MessageStream#scroll_up` / `#scroll_down` with 3-line increments (closes #20)
8
+ - **Vim-style scroll bindings** — added `Ctrl+B` (half-page up) and `Ctrl+F` (half-page down) on the Chat screen; wired `Home` / `End` to jump-to-top / jump-to-bottom when the input bar is empty (closes #20)
9
+ - **Scroll hint in status bar** — `scroll_segment` now shows directional arrows (`↑↓ scroll` or `↑ scroll`) alongside the position counter when content overflows the viewport (closes #20)
10
+ - **Help text updated** — added scroll binding reference line to the help overlay
11
+
3
12
  ## [0.5.0] - 2026-04-17
4
13
 
5
14
  ### Added
@@ -30,11 +30,18 @@ module Legion
30
30
  "\e[1~" => :home, "\e[4~" => :end,
31
31
  "\x7f" => :backspace, "\b" => :backspace, "\t" => :tab,
32
32
  "\x03" => :ctrl_c, "\x04" => :ctrl_d,
33
- "\x01" => :ctrl_a, "\x05" => :ctrl_e,
33
+ "\x01" => :ctrl_a, "\x02" => :ctrl_b,
34
+ "\x05" => :ctrl_e, "\x06" => :ctrl_f,
34
35
  "\x0B" => :ctrl_k, "\x0C" => :ctrl_l, "\x13" => :ctrl_s,
35
36
  "\x15" => :ctrl_u
36
37
  }.freeze
37
38
 
39
+ ENABLE_ALT_SCREEN = "\e[?1049h"
40
+ DISABLE_ALT_SCREEN = "\e[?1049l"
41
+ ENABLE_MOUSE = "\e[?1000h\e[?1006h"
42
+ DISABLE_MOUSE = "\e[?1000h\e[?1006l"
43
+ SGR_MOUSE_RE = /\A\e\[<(\d+);(\d+);(\d+)([Mm])\z/
44
+
38
45
  attr_reader :config, :credentials, :screen_manager, :hotkeys, :llm_chat, :input_bar
39
46
 
40
47
  def self.run(argv = [])
@@ -157,6 +164,8 @@ module Legion
157
164
 
158
165
  @running = true
159
166
  @raw_mode = true
167
+ $stdout.print ENABLE_ALT_SCREEN
168
+ $stdout.print ENABLE_MOUSE
160
169
  $stdout.print cursor.hide
161
170
  $stdout.print cursor.clear_screen
162
171
 
@@ -175,9 +184,9 @@ module Legion
175
184
  nil
176
185
  ensure
177
186
  @raw_mode = false
187
+ $stdout.print DISABLE_MOUSE
178
188
  $stdout.print cursor.show
179
- $stdout.print cursor.move_to(0, terminal_height - 1)
180
- $stdout.puts
189
+ $stdout.print DISABLE_ALT_SCREEN
181
190
  shutdown
182
191
  end
183
192
  # rubocop:enable Metrics/AbcSize
@@ -188,6 +197,9 @@ module Legion
188
197
  end
189
198
 
190
199
  def normalize_key(raw)
200
+ mouse = parse_sgr_mouse(raw)
201
+ return mouse if mouse
202
+
191
203
  KEY_MAP[raw] || raw
192
204
  end
193
205
 
@@ -213,6 +225,11 @@ module Legion
213
225
  active = @screen_manager.active_screen
214
226
  return unless active
215
227
 
228
+ if %i[scroll_up scroll_down].include?(key)
229
+ dispatch_to_screen(active, key)
230
+ return
231
+ end
232
+
216
233
  if active.respond_to?(:needs_input_bar?) && active.needs_input_bar? && @input_bar
217
234
  dispatch_to_input_screen(active, key)
218
235
  else
@@ -297,6 +314,17 @@ module Legion
297
314
  seq
298
315
  end
299
316
 
317
+ def parse_sgr_mouse(raw)
318
+ match = SGR_MOUSE_RE.match(raw)
319
+ return nil unless match
320
+
321
+ button = match[1].to_i
322
+ return :scroll_up if button == 64
323
+ return :scroll_down if button == 65
324
+
325
+ nil
326
+ end
327
+
300
328
  # --- Rendering ---
301
329
 
302
330
  # rubocop:disable Metrics/AbcSize
@@ -49,10 +49,20 @@ module Legion
49
49
  when :left
50
50
  @cursor_pos = [@cursor_pos - 1, 0].max
51
51
  :handled
52
- when :home, :ctrl_a
52
+ when :home
53
+ return :pass if @buffer.empty?
54
+
55
+ @cursor_pos = 0
56
+ :handled
57
+ when :ctrl_a
53
58
  @cursor_pos = 0
54
59
  :handled
55
- when :end, :ctrl_e
60
+ when :end
61
+ return :pass if @buffer.empty?
62
+
63
+ @cursor_pos = @buffer.length
64
+ :handled
65
+ when :ctrl_e
56
66
  @cursor_pos = @buffer.length
57
67
  :handled
58
68
  when :ctrl_u
@@ -122,7 +122,8 @@ module Legion
122
122
  scroll = @state[:scroll]
123
123
  return nil unless scroll.is_a?(Hash) && scroll[:total].to_i > scroll[:visible].to_i
124
124
 
125
- Theme.c(:muted, "#{scroll[:current]}/#{scroll[:total]}")
125
+ hint = scroll[:current].to_i.positive? ? "\u2191\u2193 scroll" : "\u2191 scroll"
126
+ Theme.c(:muted, "#{scroll[:current]}/#{scroll[:total]} #{hint}")
126
127
  end
127
128
 
128
129
  def level_to_priority(level)
@@ -36,7 +36,8 @@ module Legion
36
36
  'TOOLS : /tools /export /bookmark /pin /pins /alias /snippet /history',
37
37
  'UTILS : /calc /rand',
38
38
  '',
39
- 'Hotkeys: Ctrl+D=dashboard Ctrl+K=palette Ctrl+S=sessions Esc=back'
39
+ 'Hotkeys: Ctrl+D=dashboard Ctrl+K=palette Ctrl+S=sessions Esc=back',
40
+ 'Scroll : Mouse wheel PgUp/PgDn Ctrl+B/F (half-page) Home/End (top/bottom)'
40
41
  ].freeze
41
42
 
42
43
  CALC_SAFE_PATTERN = %r{\A[\d\s+\-*/.()%]*\z}
@@ -200,20 +200,33 @@ module Legion
200
200
  end
201
201
 
202
202
  def handle_input(key)
203
+ scroll = handle_scroll_key(key)
204
+ return scroll if scroll
205
+
206
+ :pass
207
+ end
208
+
209
+ def handle_scroll_key(key)
203
210
  case key
204
- when :page_up
205
- @message_stream.scroll_up(10)
206
- :handled
207
- when :page_down
208
- @message_stream.scroll_down(10)
209
- :handled
210
- else
211
- :pass
211
+ when :page_up then @message_stream.scroll_up(10)
212
+ when :page_down then @message_stream.scroll_down(10)
213
+ when :scroll_up then @message_stream.scroll_up(3)
214
+ when :scroll_down then @message_stream.scroll_down(3)
215
+ when :ctrl_b then @message_stream.scroll_up(half_page_lines)
216
+ when :ctrl_f then @message_stream.scroll_down(half_page_lines)
217
+ when :home then @message_stream.scroll_up(@message_stream.messages.size * 5)
218
+ when :end then @message_stream.scroll_down(@message_stream.scroll_offset)
219
+ else return nil
212
220
  end
221
+ :handled
213
222
  end
214
223
 
215
224
  private
216
225
 
226
+ def half_page_lines
227
+ [(terminal_height - 3) / 2, 1].max
228
+ end
229
+
217
230
  def render_focus(width, height)
218
231
  stream_lines = @message_stream.render(width: width, height: [height, 1].max)
219
232
  @status_bar.update(scroll: @message_stream.scroll_position)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module TTY
5
- VERSION = '0.5.0'
5
+ VERSION = '0.5.1'
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.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity