ruby_rich 0.4.4 → 0.4.5
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 +4 -4
- data/lib/ruby_rich/agent_shell.rb +1 -1
- data/lib/ruby_rich/app_shell.rb +1 -1
- data/lib/ruby_rich/composer.rb +22 -1
- data/lib/ruby_rich/focus_manager.rb +13 -2
- data/lib/ruby_rich/layout.rb +11 -0
- data/lib/ruby_rich/line_editor.rb +43 -12
- data/lib/ruby_rich/live.rb +19 -0
- data/lib/ruby_rich/terminal.rb +1 -1
- data/lib/ruby_rich/version.rb +1 -1
- data/lib/ruby_rich/viewport.rb +32 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa1b6881cd40609ff69b7e2b4c766c5fa3551bf49cd5558ad79681d89abc4e1a
|
|
4
|
+
data.tar.gz: e596c29715037aca70508b3dc564b009bc20bfa8f56a6286fb173819f9b2750a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 425b8e8584af060a2e9eb1ffe7666e0f2ddcec84c4a6a8005ffaf0f44c976b3e9c4c121a839a7c2b617f26ccabac20db8ef93ec1b3bc163761bd538a689e8f93
|
|
7
|
+
data.tar.gz: f17b46fb04ef923a0803163b06be590f985187ac50a81e9d76a47329ce2503c353c98f7d90295a94a534ecefe201907d0e6aa2585764c8a76df99f2ce436bf62
|
|
@@ -42,7 +42,7 @@ module RubyRich
|
|
|
42
42
|
self
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
def start(refresh_rate: 24, mouse: true, alt_screen:
|
|
45
|
+
def start(refresh_rate: 24, mouse: true, alt_screen: true)
|
|
46
46
|
@state_mutex.synchronize { @state = :starting }
|
|
47
47
|
Live.start(@layout, refresh_rate: refresh_rate, mouse: mouse, alt_screen: alt_screen, autowrap: false) do |live|
|
|
48
48
|
@state_mutex.synchronize do
|
data/lib/ruby_rich/app_shell.rb
CHANGED
|
@@ -147,7 +147,7 @@ module RubyRich
|
|
|
147
147
|
false
|
|
148
148
|
end
|
|
149
149
|
|
|
150
|
-
def start(refresh_rate: 24, mouse: true, alt_screen:
|
|
150
|
+
def start(refresh_rate: 24, mouse: true, alt_screen: true)
|
|
151
151
|
Live.start(@layout, refresh_rate: refresh_rate, mouse: mouse, alt_screen: alt_screen, autowrap: false) do |live|
|
|
152
152
|
@live = live
|
|
153
153
|
live.listening = true
|
data/lib/ruby_rich/composer.rb
CHANGED
|
@@ -73,6 +73,27 @@ module RubyRich
|
|
|
73
73
|
@menu_open
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
+
def native_cursor_position
|
|
77
|
+
return nil unless focused?
|
|
78
|
+
|
|
79
|
+
input_width = [inner_width - 2, 1].max
|
|
80
|
+
editor_row, editor_col = @editor.cursor_visual_position(width: input_width)
|
|
81
|
+
raw_row = [@attachments.length, 3].min + editor_row
|
|
82
|
+
raw_col = 2 + editor_col
|
|
83
|
+
|
|
84
|
+
raw_height = [@height.to_i, 1].max
|
|
85
|
+
raw_lines = []
|
|
86
|
+
raw_lines.concat(render_attachments)
|
|
87
|
+
raw_lines.concat(render_input_lines)
|
|
88
|
+
raw_lines.concat(render_menu_lines) if menu_open?
|
|
89
|
+
|
|
90
|
+
clipped_rows = [raw_lines.length - raw_height, 0].max
|
|
91
|
+
visible_row = raw_row - clipped_rows
|
|
92
|
+
return nil if visible_row.negative? || visible_row >= raw_height
|
|
93
|
+
|
|
94
|
+
[visible_row, raw_col]
|
|
95
|
+
end
|
|
96
|
+
|
|
76
97
|
def wants_tab?
|
|
77
98
|
focused? && (menu_open? || @editor.value.include?("/"))
|
|
78
99
|
end
|
|
@@ -229,7 +250,7 @@ module RubyRich
|
|
|
229
250
|
elsif !@editor.empty?
|
|
230
251
|
@editor.clear
|
|
231
252
|
else
|
|
232
|
-
|
|
253
|
+
focus
|
|
233
254
|
end
|
|
234
255
|
end
|
|
235
256
|
|
|
@@ -29,8 +29,8 @@ module RubyRich
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
root_layout.key(:
|
|
33
|
-
entry = entry_at(event_data[:x], event_data[:y])
|
|
32
|
+
root_layout.key(:mouse_target, priority) do |event_data, _live|
|
|
33
|
+
entry = entry_for_layout(event_data[:target_layout]) || entry_at(event_data[:x], event_data[:y])
|
|
34
34
|
if entry
|
|
35
35
|
focus(entry[:name])
|
|
36
36
|
false
|
|
@@ -73,5 +73,16 @@ module RubyRich
|
|
|
73
73
|
def entry_at(x, y)
|
|
74
74
|
@entries.reverse.find { |entry| entry[:layout].contains?(x, y) }
|
|
75
75
|
end
|
|
76
|
+
|
|
77
|
+
def entry_for_layout(layout)
|
|
78
|
+
current = layout
|
|
79
|
+
while current
|
|
80
|
+
entry = @entries.reverse.find { |item| item[:layout] == current }
|
|
81
|
+
return entry if entry
|
|
82
|
+
|
|
83
|
+
current = current.parent
|
|
84
|
+
end
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
76
87
|
end
|
|
77
88
|
end
|
data/lib/ruby_rich/layout.rb
CHANGED
|
@@ -334,6 +334,7 @@ module RubyRich
|
|
|
334
334
|
|
|
335
335
|
capture = root.instance_variable_get(:@mouse_capture)
|
|
336
336
|
target = capture && [:mouse_drag, :mouse_up].include?(event_data[:name]) ? capture : (hit_test(event_data[:x], event_data[:y]) || self)
|
|
337
|
+
dispatch_mouse_target_event(event_data, target) if event_data[:name] == :mouse_down
|
|
337
338
|
handled = target.bubble_mouse_event(event_data)
|
|
338
339
|
|
|
339
340
|
root.instance_variable_set(:@mouse_capture, target) if event_data[:name] == :mouse_down && handled
|
|
@@ -341,6 +342,16 @@ module RubyRich
|
|
|
341
342
|
handled
|
|
342
343
|
end
|
|
343
344
|
|
|
345
|
+
def dispatch_mouse_target_event(event_data, target)
|
|
346
|
+
target.bubble_mouse_event(
|
|
347
|
+
event_data.merge(
|
|
348
|
+
name: :mouse_target,
|
|
349
|
+
target_layout: target,
|
|
350
|
+
original_name: event_data[:name]
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
end
|
|
354
|
+
|
|
344
355
|
protected
|
|
345
356
|
|
|
346
357
|
def bubble_mouse_event(event_data)
|
|
@@ -193,18 +193,33 @@ module RubyRich
|
|
|
193
193
|
end
|
|
194
194
|
|
|
195
195
|
def render_lines(width:, placeholder: nil, focused: true)
|
|
196
|
+
_ = focused
|
|
196
197
|
content = value
|
|
197
198
|
return [placeholder.to_s] if content.empty? && placeholder
|
|
198
199
|
|
|
199
200
|
rendered = []
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
marker_col = focused && index == line_index ? col : nil
|
|
203
|
-
rendered.concat(wrap_line_with_cursor(line, width, marker_col))
|
|
201
|
+
lines.each do |line|
|
|
202
|
+
rendered.concat(wrap_line_without_cursor(line, width))
|
|
204
203
|
end
|
|
205
204
|
rendered.empty? ? [""] : rendered
|
|
206
205
|
end
|
|
207
206
|
|
|
207
|
+
def cursor_visual_position(width:)
|
|
208
|
+
line_index, cursor_col = cursor_line_col
|
|
209
|
+
row = 0
|
|
210
|
+
|
|
211
|
+
lines.each_with_index do |line, index|
|
|
212
|
+
if index == line_index
|
|
213
|
+
cursor_row, display_col = cursor_position_in_wrapped_line(line, width, cursor_col)
|
|
214
|
+
return [row + cursor_row, display_col]
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
row += wrap_line_without_cursor(line, width).length
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
[row, 0]
|
|
221
|
+
end
|
|
222
|
+
|
|
208
223
|
private
|
|
209
224
|
|
|
210
225
|
def invalidate_content_cache
|
|
@@ -332,30 +347,46 @@ module RubyRich
|
|
|
332
347
|
nil
|
|
333
348
|
end
|
|
334
349
|
|
|
335
|
-
def
|
|
350
|
+
def wrap_line_without_cursor(line, width)
|
|
336
351
|
width = [width, 1].max
|
|
337
352
|
segments = []
|
|
338
353
|
current = +""
|
|
339
354
|
current_width = 0
|
|
340
|
-
|
|
341
|
-
chars.
|
|
355
|
+
|
|
356
|
+
line.chars.each do |char|
|
|
342
357
|
char_width = display_width(char)
|
|
343
|
-
if current_width + char_width > width
|
|
358
|
+
if current_width + char_width > width && !current.empty?
|
|
344
359
|
segments << current
|
|
345
360
|
current = +""
|
|
346
361
|
current_width = 0
|
|
347
362
|
end
|
|
348
|
-
|
|
363
|
+
|
|
364
|
+
current << char
|
|
349
365
|
current_width += char_width
|
|
350
366
|
end
|
|
351
367
|
|
|
352
|
-
current << cursor_marker(" ") if marker_col == chars.length
|
|
353
368
|
segments << current
|
|
354
369
|
segments
|
|
355
370
|
end
|
|
356
371
|
|
|
357
|
-
def
|
|
358
|
-
|
|
372
|
+
def cursor_position_in_wrapped_line(line, width, cursor_col)
|
|
373
|
+
width = [width, 1].max
|
|
374
|
+
row = 0
|
|
375
|
+
display_col = 0
|
|
376
|
+
|
|
377
|
+
line.chars.each_with_index do |char, index|
|
|
378
|
+
char_width = display_width(char)
|
|
379
|
+
if display_col + char_width > width && display_col.positive?
|
|
380
|
+
row += 1
|
|
381
|
+
display_col = 0
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
return [row, display_col] if index == cursor_col
|
|
385
|
+
|
|
386
|
+
display_col += char_width
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
[row, display_col]
|
|
359
390
|
end
|
|
360
391
|
|
|
361
392
|
def display_width(char)
|
data/lib/ruby_rich/live.rb
CHANGED
|
@@ -236,6 +236,25 @@ module RubyRich
|
|
|
236
236
|
@last_terminal_size = [width, height]
|
|
237
237
|
@layout.calculate_dimensions(width, height)
|
|
238
238
|
@render.draw(@layout.render_to_buffer)
|
|
239
|
+
position_native_cursor
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def position_native_cursor
|
|
243
|
+
composer_layout = @layout[:composer]
|
|
244
|
+
return unless composer_layout
|
|
245
|
+
|
|
246
|
+
frame = composer_layout.content
|
|
247
|
+
component = frame.instance_variable_get(:@component) if frame
|
|
248
|
+
return unless component&.respond_to?(:native_cursor_position)
|
|
249
|
+
|
|
250
|
+
cursor = component.native_cursor_position
|
|
251
|
+
return unless cursor
|
|
252
|
+
|
|
253
|
+
row, col = cursor
|
|
254
|
+
terminal_row = composer_layout.y_offset.to_i + 1 + row.to_i
|
|
255
|
+
terminal_col = composer_layout.x_offset.to_i + 1 + col.to_i
|
|
256
|
+
print "\e[#{terminal_row + 1};#{terminal_col + 1}H"
|
|
257
|
+
$stdout.flush
|
|
239
258
|
end
|
|
240
259
|
|
|
241
260
|
def wait_for_activity
|
data/lib/ruby_rich/terminal.rb
CHANGED
|
@@ -42,7 +42,7 @@ module RubyRich
|
|
|
42
42
|
class << self
|
|
43
43
|
attr_accessor :debug_input
|
|
44
44
|
|
|
45
|
-
def setup(mouse: false, hide_cursor:
|
|
45
|
+
def setup(mouse: false, hide_cursor: false, autowrap: true, alt_screen: false)
|
|
46
46
|
capture_state
|
|
47
47
|
enable_virtual_terminal_on_windows
|
|
48
48
|
system('stty -echo') unless windows?
|
data/lib/ruby_rich/version.rb
CHANGED
data/lib/ruby_rich/viewport.rb
CHANGED
|
@@ -14,6 +14,7 @@ module RubyRich
|
|
|
14
14
|
@height = 0
|
|
15
15
|
@scroll_top = 0
|
|
16
16
|
@dragging_scrollbar = false
|
|
17
|
+
@dragging_viewport = false
|
|
17
18
|
@drag_start_y = nil
|
|
18
19
|
@drag_start_scroll_top = nil
|
|
19
20
|
@selecting = false
|
|
@@ -62,7 +63,6 @@ module RubyRich
|
|
|
62
63
|
|
|
63
64
|
def handle_event(event_data, layout = nil)
|
|
64
65
|
return false if keyboard_event?(event_data) && !@focused
|
|
65
|
-
return false if mouse_event?(event_data) && !@focused
|
|
66
66
|
|
|
67
67
|
case event_data[:name]
|
|
68
68
|
when :page_up
|
|
@@ -89,11 +89,11 @@ module RubyRich
|
|
|
89
89
|
when :mouse_down
|
|
90
90
|
return copy_selection if event_data[:button] == :right
|
|
91
91
|
|
|
92
|
-
start_scrollbar_drag(event_data, layout) ||
|
|
92
|
+
start_scrollbar_drag(event_data, layout) || start_viewport_drag(event_data, layout)
|
|
93
93
|
when :mouse_drag
|
|
94
|
-
drag_scrollbar(event_data, layout) || drag_selection(event_data, layout)
|
|
94
|
+
drag_scrollbar(event_data, layout) || drag_viewport(event_data, layout) || drag_selection(event_data, layout)
|
|
95
95
|
when :mouse_up
|
|
96
|
-
stop_scrollbar_drag || stop_selection
|
|
96
|
+
stop_scrollbar_drag || stop_viewport_drag || stop_selection
|
|
97
97
|
else
|
|
98
98
|
false
|
|
99
99
|
end
|
|
@@ -352,6 +352,18 @@ module RubyRich
|
|
|
352
352
|
true
|
|
353
353
|
end
|
|
354
354
|
|
|
355
|
+
def start_viewport_drag(event_data, layout)
|
|
356
|
+
return false unless layout
|
|
357
|
+
return false unless event_data[:button] == :left
|
|
358
|
+
return false if event_data[:x] >= layout.x_offset + content_width
|
|
359
|
+
return false unless event_data[:y].between?(layout.y_offset, layout.y_offset + @height - 1)
|
|
360
|
+
|
|
361
|
+
@dragging_viewport = true
|
|
362
|
+
@drag_start_y = event_data[:y]
|
|
363
|
+
@drag_start_scroll_top = @scroll_top
|
|
364
|
+
true
|
|
365
|
+
end
|
|
366
|
+
|
|
355
367
|
def drag_scrollbar(event_data, layout)
|
|
356
368
|
return false unless @dragging_scrollbar && layout
|
|
357
369
|
|
|
@@ -367,6 +379,14 @@ module RubyRich
|
|
|
367
379
|
true
|
|
368
380
|
end
|
|
369
381
|
|
|
382
|
+
def drag_viewport(event_data, layout)
|
|
383
|
+
return false unless @dragging_viewport && layout
|
|
384
|
+
|
|
385
|
+
delta_y = event_data[:y] - @drag_start_y
|
|
386
|
+
scroll_to(@drag_start_scroll_top - delta_y)
|
|
387
|
+
true
|
|
388
|
+
end
|
|
389
|
+
|
|
370
390
|
def drag_selection(event_data, layout)
|
|
371
391
|
return false unless @selecting && layout
|
|
372
392
|
|
|
@@ -382,6 +402,14 @@ module RubyRich
|
|
|
382
402
|
was_dragging
|
|
383
403
|
end
|
|
384
404
|
|
|
405
|
+
def stop_viewport_drag
|
|
406
|
+
was_dragging = @dragging_viewport
|
|
407
|
+
@dragging_viewport = false
|
|
408
|
+
@drag_start_y = nil
|
|
409
|
+
@drag_start_scroll_top = nil
|
|
410
|
+
was_dragging
|
|
411
|
+
end
|
|
412
|
+
|
|
385
413
|
def stop_selection
|
|
386
414
|
return false unless @selecting
|
|
387
415
|
|