Class: Echoes::Screen
- Inherits:
-
Object
- Object
- Echoes::Screen
- Defined in:
- lib/echoes/screen.rb
Constant Summary collapse
- DEC_SPECIAL =
{ '`' => "\u{25C6}", 'a' => "\u{2592}", 'b' => "\u{2409}", 'c' => "\u{240C}", 'd' => "\u{240D}", 'e' => "\u{240A}", 'f' => "\u{00B0}", 'g' => "\u{00B1}", 'h' => "\u{2424}", 'i' => "\u{240B}", 'j' => "\u{2518}", 'k' => "\u{2510}", 'l' => "\u{250C}", 'm' => "\u{2514}", 'n' => "\u{253C}", 'o' => "\u{23BA}", 'p' => "\u{23BB}", 'q' => "\u{2500}", 'r' => "\u{23BC}", 's' => "\u{23BD}", 't' => "\u{251C}", 'u' => "\u{2524}", 'v' => "\u{2534}", 'w' => "\u{252C}", 'x' => "\u{2502}", 'y' => "\u{2264}", 'z' => "\u{2265}", '{' => "\u{03C0}", '|' => "\u{2260}", '}' => "\u{00A3}", '~' => "\u{00B7}", }.freeze
Instance Attribute Summary collapse
-
#active_charset ⇒ Object
Returns the value of attribute active_charset.
-
#application_keypad ⇒ Object
Returns the value of attribute application_keypad.
-
#background ⇒ Object
Returns the value of attribute background.
-
#bell ⇒ Object
Returns the value of attribute bell.
-
#bg_fills ⇒ Object
Returns the value of attribute bg_fills.
-
#cell_pixel_height ⇒ Object
Returns the value of attribute cell_pixel_height.
-
#cell_pixel_width ⇒ Object
Returns the value of attribute cell_pixel_width.
-
#clipboard_handler ⇒ Object
Returns the value of attribute clipboard_handler.
-
#cols ⇒ Object
readonly
Returns the value of attribute cols.
-
#command_marks ⇒ Object
readonly
Returns the value of attribute command_marks.
-
#current_directory ⇒ Object
Returns the value of attribute current_directory.
-
#cursor ⇒ Object
readonly
Returns the value of attribute cursor.
-
#cursor_style ⇒ Object
Returns the value of attribute cursor_style.
-
#dirty_rows ⇒ Object
readonly
Returns the value of attribute dirty_rows.
-
#glyph_measurer ⇒ Object
Returns the value of attribute glyph_measurer.
-
#grid ⇒ Object
readonly
Returns the value of attribute grid.
-
#insert_mode ⇒ Object
Returns the value of attribute insert_mode.
-
#mouse_encoding ⇒ Object
Returns the value of attribute mouse_encoding.
-
#mouse_tracking ⇒ Object
Returns the value of attribute mouse_tracking.
-
#palette_handler ⇒ Object
Returns the value of attribute palette_handler.
-
#pending_wrap ⇒ Object
Returns the value of attribute pending_wrap.
-
#rows ⇒ Object
readonly
Returns the value of attribute rows.
-
#scrollback ⇒ Object
readonly
Returns the value of attribute scrollback.
-
#single_shift ⇒ Object
Returns the value of attribute single_shift.
-
#title ⇒ Object
Returns the value of attribute title.
Class Method Summary collapse
Instance Method Summary collapse
-
#adjust_command_marks(delta) ⇒ Object
When scrollback shifts (oldest row dropped), every row index in now point before the scrollback floor are dropped — their content is no longer reachable.
- #application_cursor_keys=(val) ⇒ Object
- #application_cursor_keys? ⇒ Boolean
- #apply_sgr_subparams(sub) ⇒ Object
- #auto_wrap=(val) ⇒ Object
- #auto_wrap? ⇒ Boolean
- #backspace ⇒ Object
- #backward_tab(n = 1) ⇒ Object
- #bracketed_paste_mode=(val) ⇒ Object
- #bracketed_paste_mode? ⇒ Boolean
- #carriage_return ⇒ Object
- #clear_dirty ⇒ Object
- #clear_tab_stop(mode = 0) ⇒ Object
- #clipboard_content ⇒ Object
- #decaln ⇒ Object
- #delete_chars(n = 1) ⇒ Object
- #delete_lines(n = 1) ⇒ Object
- #designate_charset(g, charset) ⇒ Object
- #erase_chars(n = 1) ⇒ Object
- #erase_in_display(mode = 0) ⇒ Object
- #erase_in_line(mode = 0) ⇒ Object
-
#find_command_mark_at_row(abs_row) ⇒ Object
Find the command mark whose prompt+input region covers ‘abs_row`, or nil if no mark covers that row.
- #focus_reporting=(val) ⇒ Object
- #focus_reporting? ⇒ Boolean
- #hide_cursor ⇒ Object
-
#initialize(rows: 24, cols: 80) ⇒ Screen
constructor
A new instance of Screen.
- #insert_chars(n = 1) ⇒ Object
- #insert_lines(n = 1) ⇒ Object
-
#last_completed_command_mark ⇒ Object
Most recently completed command mark (D was emitted).
- #line_feed ⇒ Object
- #mark_all_dirty ⇒ Object
- #mark_dirty(row) ⇒ Object
- #move_cursor(row, col) ⇒ Object
- #move_cursor_backward(n = 1) ⇒ Object
- #move_cursor_down(n = 1) ⇒ Object
- #move_cursor_forward(n = 1) ⇒ Object
- #move_cursor_next_line(n = 1) ⇒ Object
- #move_cursor_prev_line(n = 1) ⇒ Object
- #move_cursor_up(n = 1) ⇒ Object
- #origin_mode=(val) ⇒ Object
- #origin_mode? ⇒ Boolean
-
#osc133_mark(kind, exit_code: nil) ⇒ Object
OSC 133 prompt boundary marker.
-
#output_region_for_row(abs_row) ⇒ Object
Find the command mark whose output region covers ‘abs_row` and return [start_row, end_row] (both inclusive) so callers like the GUI’s triple-click can highlight or copy that whole region.
- #pop_title ⇒ Object
- #push_title ⇒ Object
- #put_char(c) ⇒ Object
- #put_multicell(text, scale:, width:, frac_n:, frac_d:, valign:, halign:, family: nil) ⇒ Object
- #put_sixel(data, params) ⇒ Object
-
#put_styled_segments(segments) ⇒ Object
Write a sequence of styled prompt segments directly into the cell grid, bypassing the ANSI SGR parser.
- #repeat_char(n = 1) ⇒ Object
- #reset ⇒ Object
- #resize(new_rows, new_cols) ⇒ Object
- #restore_cursor ⇒ Object
- #reverse_index ⇒ Object
- #save_cursor ⇒ Object
- #scroll_down(n = 1) ⇒ Object
- #scroll_up(n = 1) ⇒ Object
- #selected_text(sr, sc, er, ec) ⇒ Object
- #set_clipboard(text) ⇒ Object
-
#set_current_command_text(text) ⇒ Object
Attach the literal command text to the most recently opened mark.
- #set_graphics(params) ⇒ Object
- #set_hyperlink(uri) ⇒ Object
- #set_scroll_region(top, bottom) ⇒ Object
- #set_tab_stop ⇒ Object
- #show_cursor ⇒ Object
- #soft_reset ⇒ Object
- #switch_to_alt_screen ⇒ Object
- #switch_to_main_screen ⇒ Object
- #tab ⇒ Object
-
#text_for_command_output(mark) ⇒ Object
Extract the visible text of a command’s output region.
- #to_text ⇒ Object
- #using_alt_screen? ⇒ Boolean
- #word_boundaries_at(row, col) ⇒ Object
Constructor Details
#initialize(rows: 24, cols: 80) ⇒ Screen
Returns a new instance of Screen.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/echoes/screen.rb', line 16 def initialize(rows: 24, cols: 80) @rows = rows @cols = cols @cursor = Cursor.new @attrs = Cell.new @grid = Array.new(rows) { Array.new(cols) { Cell.new } } @line_wrapped = Array.new(rows, false) @scroll_top = 0 @scroll_bottom = rows - 1 @saved_cursor = nil @scrollback = [] @scrollback_wrapped = [] @cell_pixel_width = 8.0 @cell_pixel_height = 16.0 @application_cursor_keys = false @bracketed_paste_mode = false @focus_reporting = false @auto_wrap = true @mouse_tracking = :off # :off, :x10, :normal, :button_event, :any_event @mouse_encoding = :default # :default, :sgr @origin_mode = false @insert_mode = false @application_keypad = false @cursor_style = 0 # 0=default, 1=blinking block, 2=steady block, 3=blinking underline, 4=steady underline, 5=blinking bar, 6=steady bar @using_alt_screen = false @charset_g0 = :ascii # :ascii or :dec_special @charset_g1 = :ascii @charset_g2 = :ascii @charset_g3 = :ascii @active_charset = 0 # 0 = G0, 1 = G1 @single_shift = nil # nil, 2, or 3 (for SS2/SS3) @tab_stops = default_tab_stops @main_grid = nil @main_cursor = nil @main_scroll_top = nil @main_scroll_bottom = nil @main_saved_cursor = nil @main_scrollback = nil @pending_wrap = false @last_char = nil @title_stack = [] @dirty_rows = Set.new((0...rows).to_a) @bg_fills = [] # OSC 7772 ;bg-fill regions; each: {rect:[r1,c1,r2,c2], color:[r,g,b,a]} # OSC 133 prompt-boundary markers: each entry is a Hash with # :prompt_start / :input_start / :output_start / :output_end / # :exit_code keys, where the row values are *visual* row indices — # `scrollback_size + grid_row` at the moment the marker was seen. # When scrollback shifts off the front, mark rows decrement so # they keep pointing at the same content; marks that fall before # the scrollback floor are dropped. @command_marks = [] @current_command_mark = nil end |
Instance Attribute Details
#active_charset ⇒ Object
Returns the value of attribute active_charset.
682 683 684 |
# File 'lib/echoes/screen.rb', line 682 def active_charset @active_charset end |
#application_keypad ⇒ Object
Returns the value of attribute application_keypad.
682 683 684 |
# File 'lib/echoes/screen.rb', line 682 def application_keypad @application_keypad end |
#background ⇒ Object
Returns the value of attribute background.
9 10 11 |
# File 'lib/echoes/screen.rb', line 9 def background @background end |
#bell ⇒ Object
Returns the value of attribute bell.
682 683 684 |
# File 'lib/echoes/screen.rb', line 682 def bell @bell end |
#bg_fills ⇒ Object
Returns the value of attribute bg_fills.
9 10 11 |
# File 'lib/echoes/screen.rb', line 9 def bg_fills @bg_fills end |
#cell_pixel_height ⇒ Object
Returns the value of attribute cell_pixel_height.
9 10 11 |
# File 'lib/echoes/screen.rb', line 9 def cell_pixel_height @cell_pixel_height end |
#cell_pixel_width ⇒ Object
Returns the value of attribute cell_pixel_width.
9 10 11 |
# File 'lib/echoes/screen.rb', line 9 def cell_pixel_width @cell_pixel_width end |
#clipboard_handler ⇒ Object
Returns the value of attribute clipboard_handler.
871 872 873 |
# File 'lib/echoes/screen.rb', line 871 def clipboard_handler @clipboard_handler end |
#cols ⇒ Object (readonly)
Returns the value of attribute cols.
7 8 9 |
# File 'lib/echoes/screen.rb', line 7 def cols @cols end |
#command_marks ⇒ Object (readonly)
Returns the value of attribute command_marks.
7 8 9 |
# File 'lib/echoes/screen.rb', line 7 def command_marks @command_marks end |
#current_directory ⇒ Object
Returns the value of attribute current_directory.
9 10 11 |
# File 'lib/echoes/screen.rb', line 9 def current_directory @current_directory end |
#cursor ⇒ Object (readonly)
Returns the value of attribute cursor.
7 8 9 |
# File 'lib/echoes/screen.rb', line 7 def cursor @cursor end |
#cursor_style ⇒ Object
Returns the value of attribute cursor_style.
682 683 684 |
# File 'lib/echoes/screen.rb', line 682 def cursor_style @cursor_style end |
#dirty_rows ⇒ Object (readonly)
Returns the value of attribute dirty_rows.
7 8 9 |
# File 'lib/echoes/screen.rb', line 7 def dirty_rows @dirty_rows end |
#glyph_measurer ⇒ Object
Returns the value of attribute glyph_measurer.
871 872 873 |
# File 'lib/echoes/screen.rb', line 871 def glyph_measurer @glyph_measurer end |
#grid ⇒ Object (readonly)
Returns the value of attribute grid.
7 8 9 |
# File 'lib/echoes/screen.rb', line 7 def grid @grid end |
#insert_mode ⇒ Object
Returns the value of attribute insert_mode.
682 683 684 |
# File 'lib/echoes/screen.rb', line 682 def insert_mode @insert_mode end |
#mouse_encoding ⇒ Object
Returns the value of attribute mouse_encoding.
682 683 684 |
# File 'lib/echoes/screen.rb', line 682 def mouse_encoding @mouse_encoding end |
#mouse_tracking ⇒ Object
Returns the value of attribute mouse_tracking.
682 683 684 |
# File 'lib/echoes/screen.rb', line 682 def mouse_tracking @mouse_tracking end |
#palette_handler ⇒ Object
Returns the value of attribute palette_handler.
871 872 873 |
# File 'lib/echoes/screen.rb', line 871 def palette_handler @palette_handler end |
#pending_wrap ⇒ Object
Returns the value of attribute pending_wrap.
9 10 11 |
# File 'lib/echoes/screen.rb', line 9 def pending_wrap @pending_wrap end |
#rows ⇒ Object (readonly)
Returns the value of attribute rows.
7 8 9 |
# File 'lib/echoes/screen.rb', line 7 def rows @rows end |
#scrollback ⇒ Object (readonly)
Returns the value of attribute scrollback.
7 8 9 |
# File 'lib/echoes/screen.rb', line 7 def scrollback @scrollback end |
#single_shift ⇒ Object
Returns the value of attribute single_shift.
682 683 684 |
# File 'lib/echoes/screen.rb', line 682 def single_shift @single_shift end |
#title ⇒ Object
Returns the value of attribute title.
9 10 11 |
# File 'lib/echoes/screen.rb', line 9 def title @title end |
Class Method Details
Instance Method Details
#adjust_command_marks(delta) ⇒ Object
When scrollback shifts (oldest row dropped), every row index in now point before the scrollback floor are dropped — their content is no longer reachable.
856 857 858 859 860 861 862 863 864 865 866 867 868 869 |
# File 'lib/echoes/screen.rb', line 856 def adjust_command_marks(delta) return if @command_marks.empty? @command_marks.each do |m| m.each_key do |k| next if k == :exit_code v = m[k] m[k] = v + delta if v end end @command_marks.reject! { |m| m[:prompt_start] && m[:prompt_start] < 0 } if @current_command_mark && (@current_command_mark[:prompt_start] || 0) < 0 @current_command_mark = nil end end |
#application_cursor_keys=(val) ⇒ Object
653 654 655 |
# File 'lib/echoes/screen.rb', line 653 def application_cursor_keys=(val) @application_cursor_keys = val end |
#application_cursor_keys? ⇒ Boolean
649 650 651 |
# File 'lib/echoes/screen.rb', line 649 def application_cursor_keys? @application_cursor_keys end |
#apply_sgr_subparams(sub) ⇒ Object
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 |
# File 'lib/echoes/screen.rb', line 580 def apply_sgr_subparams(sub) case sub[0] when 4 # Underline style: 4:0=off, 4:1=single, 4:2=double, 4:3=curly, 4:4=dotted, 4:5=dashed style = sub[1] || 1 if style == 0 @attrs.underline = false else @attrs.underline = style end when 38 # Foreground color with sub-parameters if sub[1] == 2 # 38:2:cs:R:G:B or 38:2:R:G:B (cs = color space, often empty/omitted) r, g, b = extract_rgb_subparams(sub, 2) @attrs.fg = [r, g, b] if r && g && b elsif sub[1] == 5 && sub[2] @attrs.fg = sub[2] end when 48 # Background color with sub-parameters if sub[1] == 2 r, g, b = extract_rgb_subparams(sub, 2) @attrs.bg = [r, g, b] if r && g && b elsif sub[1] == 5 && sub[2] @attrs.bg = sub[2] end when 58 # Underline color if sub[1] == 2 r, g, b = extract_rgb_subparams(sub, 2) @attrs.underline_color = [r, g, b] if r && g && b elsif sub[1] == 5 && sub[2] @attrs.underline_color = sub[2] end when 59 @attrs.underline_color = nil end end |
#auto_wrap=(val) ⇒ Object
677 678 679 680 |
# File 'lib/echoes/screen.rb', line 677 def auto_wrap=(val) @auto_wrap = val @pending_wrap = false end |
#auto_wrap? ⇒ Boolean
673 674 675 |
# File 'lib/echoes/screen.rb', line 673 def auto_wrap? @auto_wrap end |
#backspace ⇒ Object
368 369 370 371 |
# File 'lib/echoes/screen.rb', line 368 def backspace @pending_wrap = false @cursor.col = [0, @cursor.col - 1].max end |
#backward_tab(n = 1) ⇒ Object
346 347 348 349 350 351 352 |
# File 'lib/echoes/screen.rb', line 346 def backward_tab(n = 1) @pending_wrap = false n.times do prev_stop = @tab_stops.reverse.find { |s| s < @cursor.col } @cursor.col = prev_stop || 0 end end |
#bracketed_paste_mode=(val) ⇒ Object
661 662 663 |
# File 'lib/echoes/screen.rb', line 661 def bracketed_paste_mode=(val) @bracketed_paste_mode = val end |
#bracketed_paste_mode? ⇒ Boolean
657 658 659 |
# File 'lib/echoes/screen.rb', line 657 def bracketed_paste_mode? @bracketed_paste_mode end |
#carriage_return ⇒ Object
317 318 319 320 |
# File 'lib/echoes/screen.rb', line 317 def carriage_return @pending_wrap = false @cursor.col = 0 end |
#clear_dirty ⇒ Object
700 701 702 |
# File 'lib/echoes/screen.rb', line 700 def clear_dirty @dirty_rows = Set.new end |
#clear_tab_stop(mode = 0) ⇒ Object
359 360 361 362 363 364 365 366 |
# File 'lib/echoes/screen.rb', line 359 def clear_tab_stop(mode = 0) case mode when 0 @tab_stops.delete(@cursor.col) when 3 @tab_stops.clear end end |
#clipboard_content ⇒ Object
877 878 879 |
# File 'lib/echoes/screen.rb', line 877 def clipboard_content @clipboard_handler&.call(:get, nil) end |
#decaln ⇒ Object
1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 |
# File 'lib/echoes/screen.rb', line 1035 def decaln @grid.each do |row| row.each do |cell| cell.reset! cell.char = 'E' end end @cursor.row = 0 @cursor.col = 0 @pending_wrap = false mark_all_dirty end |
#delete_chars(n = 1) ⇒ Object
431 432 433 434 435 436 437 438 439 |
# File 'lib/echoes/screen.rb', line 431 def delete_chars(n = 1) @pending_wrap = false row = @grid[@cursor.row] n.times do row.delete_at(@cursor.col) row.push(Cell.new) end mark_dirty(@cursor.row) end |
#delete_lines(n = 1) ⇒ Object
418 419 420 421 422 423 424 425 426 427 428 429 |
# File 'lib/echoes/screen.rb', line 418 def delete_lines(n = 1) @pending_wrap = false return unless @cursor.row >= @scroll_top && @cursor.row <= @scroll_bottom n.times do @grid.delete_at(@cursor.row) @line_wrapped.delete_at(@cursor.row) @grid.insert(@scroll_bottom, Array.new(@cols) { Cell.new }) @line_wrapped.insert(@scroll_bottom, false) end (@cursor.row..@scroll_bottom).each { |r| mark_dirty(r) } end |
#designate_charset(g, charset) ⇒ Object
881 882 883 884 885 886 887 888 |
# File 'lib/echoes/screen.rb', line 881 def designate_charset(g, charset) case g when 0 then @charset_g0 = charset when 1 then @charset_g1 = charset when 2 then @charset_g2 = charset when 3 then @charset_g3 = charset end end |
#erase_chars(n = 1) ⇒ Object
451 452 453 454 455 456 457 458 |
# File 'lib/echoes/screen.rb', line 451 def erase_chars(n = 1) n.times do |i| col = @cursor.col + i break if col >= @cols @grid[@cursor.row][col].reset! end mark_dirty(@cursor.row) end |
#erase_in_display(mode = 0) ⇒ Object
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
# File 'lib/echoes/screen.rb', line 373 def erase_in_display(mode = 0) @pending_wrap = false case mode when 0 @line_wrapped[@cursor.row] = false erase_in_line(0) ((@cursor.row + 1)...@rows).each { |r| clear_row(r); @line_wrapped[r] = false; mark_dirty(r) } when 1 erase_in_line(1) (0...@cursor.row).each { |r| clear_row(r); @line_wrapped[r] = false; mark_dirty(r) } when 2 (0...@rows).each { |r| clear_row(r); @line_wrapped[r] = false } mark_all_dirty when 3 @scrollback.clear @scrollback_wrapped.clear end end |
#erase_in_line(mode = 0) ⇒ Object
392 393 394 395 396 397 398 399 400 401 402 403 |
# File 'lib/echoes/screen.rb', line 392 def erase_in_line(mode = 0) @pending_wrap = false mark_dirty(@cursor.row) case mode when 0 (@cursor.col...@cols).each { |c| @grid[@cursor.row][c].reset! } when 1 (0..@cursor.col).each { |c| @grid[@cursor.row][c].reset! } when 2 clear_row(@cursor.row) end end |
#find_command_mark_at_row(abs_row) ⇒ Object
Find the command mark whose prompt+input region covers ‘abs_row`, or nil if no mark covers that row. The region runs from :prompt_start (inclusive) up to :output_start (exclusive). If a command is still running and no :output_start has been recorded yet, the region is taken as just :prompt_start itself (one row).
831 832 833 834 835 836 837 |
# File 'lib/echoes/screen.rb', line 831 def find_command_mark_at_row(abs_row) @command_marks.reverse_each.find do |m| next false unless m[:prompt_start] upper = m[:output_start] || (m[:prompt_start] + 1) abs_row >= m[:prompt_start] && abs_row < upper end end |
#focus_reporting=(val) ⇒ Object
669 670 671 |
# File 'lib/echoes/screen.rb', line 669 def focus_reporting=(val) @focus_reporting = val end |
#focus_reporting? ⇒ Boolean
665 666 667 |
# File 'lib/echoes/screen.rb', line 665 def focus_reporting? @focus_reporting end |
#hide_cursor ⇒ Object
979 980 981 |
# File 'lib/echoes/screen.rb', line 979 def hide_cursor @cursor.visible = false end |
#insert_chars(n = 1) ⇒ Object
441 442 443 444 445 446 447 448 449 |
# File 'lib/echoes/screen.rb', line 441 def insert_chars(n = 1) @pending_wrap = false row = @grid[@cursor.row] n.times do row.pop row.insert(@cursor.col, Cell.new) end mark_dirty(@cursor.row) end |
#insert_lines(n = 1) ⇒ Object
405 406 407 408 409 410 411 412 413 414 415 416 |
# File 'lib/echoes/screen.rb', line 405 def insert_lines(n = 1) @pending_wrap = false return unless @cursor.row >= @scroll_top && @cursor.row <= @scroll_bottom n.times do @grid.insert(@cursor.row, Array.new(@cols) { Cell.new }) @line_wrapped.insert(@cursor.row, false) @grid.delete_at(@scroll_bottom + 1) @line_wrapped.delete_at(@scroll_bottom + 1) end (@cursor.row..@scroll_bottom).each { |r| mark_dirty(r) } end |
#last_completed_command_mark ⇒ Object
Most recently completed command mark (D was emitted). nil if no command has finished yet on this pane.
813 814 815 |
# File 'lib/echoes/screen.rb', line 813 def last_completed_command_mark @command_marks.reverse_each.find { |m| m[:output_end] } end |
#line_feed ⇒ Object
322 323 324 325 326 327 328 329 |
# File 'lib/echoes/screen.rb', line 322 def line_feed @pending_wrap = false if @cursor.row == @scroll_bottom scroll_up(1) else @cursor.row = [@cursor.row + 1, @rows - 1].min end end |
#mark_all_dirty ⇒ Object
696 697 698 |
# File 'lib/echoes/screen.rb', line 696 def mark_all_dirty @dirty_rows = Set.new((0...@rows).to_a) end |
#mark_dirty(row) ⇒ Object
692 693 694 |
# File 'lib/echoes/screen.rb', line 692 def mark_dirty(row) @dirty_rows << row end |
#move_cursor(row, col) ⇒ Object
271 272 273 274 275 276 277 278 279 |
# File 'lib/echoes/screen.rb', line 271 def move_cursor(row, col) @pending_wrap = false if @origin_mode @cursor.row = (row + @scroll_top).clamp(@scroll_top, @scroll_bottom) else @cursor.row = clamp_row(row) end @cursor.col = clamp_col(col) end |
#move_cursor_backward(n = 1) ⇒ Object
312 313 314 315 |
# File 'lib/echoes/screen.rb', line 312 def move_cursor_backward(n = 1) @pending_wrap = false @cursor.col = [0, @cursor.col - n].max end |
#move_cursor_down(n = 1) ⇒ Object
287 288 289 290 291 |
# File 'lib/echoes/screen.rb', line 287 def move_cursor_down(n = 1) @pending_wrap = false bottom = @cursor.row <= @scroll_bottom ? @scroll_bottom : @rows - 1 @cursor.row = [bottom, @cursor.row + n].min end |
#move_cursor_forward(n = 1) ⇒ Object
307 308 309 310 |
# File 'lib/echoes/screen.rb', line 307 def move_cursor_forward(n = 1) @pending_wrap = false @cursor.col = [@cols - 1, @cursor.col + n].min end |
#move_cursor_next_line(n = 1) ⇒ Object
293 294 295 296 297 298 |
# File 'lib/echoes/screen.rb', line 293 def move_cursor_next_line(n = 1) @pending_wrap = false bottom = @cursor.row <= @scroll_bottom ? @scroll_bottom : @rows - 1 @cursor.row = [bottom, @cursor.row + n].min @cursor.col = 0 end |
#move_cursor_prev_line(n = 1) ⇒ Object
300 301 302 303 304 305 |
# File 'lib/echoes/screen.rb', line 300 def move_cursor_prev_line(n = 1) @pending_wrap = false top = @cursor.row >= @scroll_top ? @scroll_top : 0 @cursor.row = [top, @cursor.row - n].max @cursor.col = 0 end |
#move_cursor_up(n = 1) ⇒ Object
281 282 283 284 285 |
# File 'lib/echoes/screen.rb', line 281 def move_cursor_up(n = 1) @pending_wrap = false top = @cursor.row >= @scroll_top ? @scroll_top : 0 @cursor.row = [top, @cursor.row - n].max end |
#origin_mode=(val) ⇒ Object
894 895 896 897 898 899 900 901 |
# File 'lib/echoes/screen.rb', line 894 def origin_mode=(val) @origin_mode = val @pending_wrap = false if val @cursor.row = @scroll_top @cursor.col = 0 end end |
#origin_mode? ⇒ Boolean
890 891 892 |
# File 'lib/echoes/screen.rb', line 890 def origin_mode? @origin_mode end |
#osc133_mark(kind, exit_code: nil) ⇒ Object
OSC 133 prompt boundary marker. ‘kind` is one of:
:prompt_start — OSC 133 ; A — beginning of a fresh prompt block
:prompt_end — OSC 133 ; B — end of prompt / start of input
:command_start — OSC 133 ; C — start of command output
:command_end — OSC 133 ; D — end of command output (with optional exit code)
Marks are stored as visual rows (scrollback rows + grid rows from 0). ‘:prompt_start` opens a new mark; subsequent kinds populate it.
765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 |
# File 'lib/echoes/screen.rb', line 765 def osc133_mark(kind, exit_code: nil) row = @scrollback.size + @cursor.row case kind when :prompt_start @current_command_mark = { prompt_start: row, input_start: nil, output_start: nil, output_end: nil, exit_code: nil, } @command_marks << @current_command_mark when :prompt_end @current_command_mark ||= {prompt_start: row, input_start: nil, output_start: nil, output_end: nil, exit_code: nil} @command_marks << @current_command_mark unless @command_marks.last.equal?(@current_command_mark) @current_command_mark[:input_start] = row when :command_start return unless @current_command_mark @current_command_mark[:output_start] = row when :command_end return unless @current_command_mark @current_command_mark[:output_end] = row @current_command_mark[:exit_code] = exit_code end end |
#output_region_for_row(abs_row) ⇒ Object
Find the command mark whose output region covers ‘abs_row` and return [start_row, end_row] (both inclusive) so callers like the GUI’s triple-click can highlight or copy that whole region. nil if no completed mark covers the row.
843 844 845 846 847 848 849 850 |
# File 'lib/echoes/screen.rb', line 843 def output_region_for_row(abs_row) mark = @command_marks.reverse_each.find do |m| next false unless m[:output_start] && m[:output_end] abs_row >= m[:output_start] && abs_row < m[:output_end] end return nil unless mark [mark[:output_start], mark[:output_end] - 1] end |
#pop_title ⇒ Object
688 689 690 |
# File 'lib/echoes/screen.rb', line 688 def pop_title @title = @title_stack.pop if @title_stack.any? end |
#push_title ⇒ Object
684 685 686 |
# File 'lib/echoes/screen.rb', line 684 def push_title @title_stack.push(@title) end |
#put_char(c) ⇒ Object
81 82 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 |
# File 'lib/echoes/screen.rb', line 81 def put_char(c) if c.bytesize == 1 if @single_shift cs = @single_shift == 2 ? @charset_g2 : @charset_g3 @single_shift = nil else cs = @active_charset == 0 ? @charset_g0 : @charset_g1 end if cs == :dec_special c = DEC_SPECIAL.fetch(c, c) end end # Combining characters: append to previous cell if combining?(c) col = @pending_wrap ? @cursor.col : [0, @cursor.col - 1].max col -= 1 if col > 0 && @grid[@cursor.row][col].width == 0 @grid[@cursor.row][col].char += c @last_char = @grid[@cursor.row][col].char return end w = char_width(c) if @auto_wrap # Deferred wrap: if the previous character set the flag, wrap now if @pending_wrap @pending_wrap = false @line_wrapped[@cursor.row] = true @cursor.col = 0 line_feed end # Wide char at last column: doesn't fit, wrap first if w == 2 && @cursor.col == @cols - 1 @grid[@cursor.row][@cursor.col].reset! @line_wrapped[@cursor.row] = true @cursor.col = 0 line_feed end else # No wrap: clamp cursor to last column if w == 2 && @cursor.col >= @cols - 1 @cursor.col = @cols - 2 elsif @cursor.col >= @cols @cursor.col = @cols - 1 end end erase_multicell_at(@cursor.row, @cursor.col) if @insert_mode row = @grid[@cursor.row] w.times { row.pop; row.insert(@cursor.col, Cell.new) } end cell = @grid[@cursor.row][@cursor.col] cell.copy_from(@attrs) cell.char = c cell.width = w if w == 2 && @cursor.col + 1 < @cols # Mark the next cell as a continuation (width 0) next_cell = @grid[@cursor.row][@cursor.col + 1] next_cell.reset! next_cell.width = 0 end mark_dirty(@cursor.row) @cursor.col += w if @cursor.col >= @cols @cursor.col = @cols - 1 @pending_wrap = true if @auto_wrap end @last_char = c end |
#put_multicell(text, scale:, width:, frac_n:, frac_d:, valign:, halign:, family: nil) ⇒ Object
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/echoes/screen.rb', line 166 def put_multicell(text, scale:, width:, frac_n:, frac_d:, valign:, halign:, family: nil) mc_rows = scale if width > 0 # Explicit width: entire text in one block of scale*width cols × scale rows place_multicell_block(text, scale * width, mc_rows, scale, frac_n, frac_d, valign, halign, family) elsif halign != 0 # h= is set: render the whole string as one block of # `scale × source_chars` cells, so the renderer's halign math # has room to center / right-align the glyphs. The spec only # mandates h= when the glyphs are smaller than the block # (fractional n<d), but extending it to non-fractional / # proportional text is a natural superset — other terminals # just ignore the attribute. With a proportional family we # widen the block to `max(scale × source_chars, measured)` so # the text never overflows but still gets visible side # margins for centering. source_chars = text.each_grapheme_cluster.sum { |g| char_width(g) } mc_cols = scale * source_chars if family && @glyph_measurer && @cell_pixel_width && @cell_pixel_width > 0 measured_px = @glyph_measurer.call(text, family, scale, frac_n, frac_d).to_f measured_cells = (measured_px / @cell_pixel_width).ceil mc_cols = [mc_cols, measured_cells].max end mc_cols = [mc_cols, 1].max place_multicell_block(text, mc_cols, mc_rows, scale, frac_n, frac_d, valign, halign, family) elsif family && @glyph_measurer && @cell_pixel_width && @cell_pixel_width > 0 # Proportional fonts have variable glyph widths, so reserving # `char_width(grapheme) * scale` cells per grapheme leaves big # letters (Noto Serif "H" at 2×) overflowing into the next # cell and small letters ("l") under-filling theirs. Ask the # host to measure the whole text in the requested font and # reserve enough cells for the entire block, drawn as one # unit by the renderer's existing string-draw path. measured_px = @glyph_measurer.call(text, family, scale, frac_n, frac_d).to_f mc_cols = (measured_px / @cell_pixel_width).ceil mc_cols = [mc_cols, 1].max place_multicell_block(text, mc_cols, mc_rows, scale, frac_n, frac_d, valign, halign, family) else # Auto width: each grapheme gets its own block (monospace # assumption — fine for the configured terminal font). text.each_grapheme_cluster do |grapheme| cw = char_width(grapheme) mc_cols = scale * cw place_multicell_block(grapheme, mc_cols, mc_rows, scale, frac_n, frac_d, valign, halign, family) end end end |
#put_sixel(data, params) ⇒ Object
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/echoes/screen.rb', line 215 def put_sixel(data, params) decoder = SixelDecoder.new(params).decode(data) return if decoder.width == 0 || decoder.height == 0 mc_cols = (decoder.width / @cell_pixel_width).ceil mc_rows = (decoder.height / @cell_pixel_height).ceil return if mc_cols > @cols || mc_rows > @rows # Wrap if it doesn't fit on current line if @cursor.col + mc_cols > @cols @cursor.col = 0 line_feed end # Scroll if block doesn't fit vertically while @cursor.row + mc_rows > @rows scroll_up(1) @cursor.row = [@cursor.row - 1, 0].max end anchor_row = @cursor.row anchor_col = @cursor.col # Erase existing cells in the block area mc_rows.times do |dr| mc_cols.times do |dc| erase_multicell_at(anchor_row + dr, anchor_col + dc) end end # Set anchor cell with sixel data anchor = @grid[anchor_row][anchor_col] anchor.reset! anchor.char = " " anchor.width = 1 anchor.multicell = { cols: mc_cols, rows: mc_rows, scale: 1, frac_n: 0, frac_d: 0, valign: 0, halign: 0, sixel: { width: decoder.width, height: decoder.height, rgba: decoder.to_rgba } } # Mark continuation cells mc_rows.times do |dr| mc_cols.times do |dc| next if dr == 0 && dc == 0 cont = @grid[anchor_row + dr][anchor_col + dc] cont.reset! cont.multicell = :cont end end @cursor.col = 0 @cursor.row = [anchor_row + mc_rows, @rows - 1].min end |
#put_styled_segments(segments) ⇒ Object
Write a sequence of styled prompt segments directly into the cell grid, bypassing the ANSI SGR parser. Each segment is a ‘fg:, bg:, bold:, italic:, underline:, inverse:` Hash (see `Rubish::REPL#prompt_segments`). Color values follow rubish’s encoding: nil = default, 0..255 = palette index, ‘[:rgb, r, g, b]` = true color (translated to `[r, g, b]` for this Screen’s storage convention).
Existing ‘@attrs` is snapshotted and restored so any in-flight SGR state from prior parser-driven rendering is preserved.
718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 |
# File 'lib/echoes/screen.rb', line 718 def put_styled_segments(segments) saved_fg = @attrs.fg saved_bg = @attrs.bg saved_bold = @attrs.bold saved_italic = @attrs.italic saved_underline = @attrs.underline saved_inverse = @attrs.inverse begin segments.each do |seg| @attrs.fg = translate_segment_color(seg[:fg]) @attrs.bg = translate_segment_color(seg[:bg]) @attrs.bold = !!seg[:bold] @attrs.italic = !!seg[:italic] @attrs.underline = !!seg[:underline] @attrs.inverse = !!seg[:inverse] (seg[:text] || '').each_char { |c| put_char(c) } end ensure @attrs.fg = saved_fg @attrs.bg = saved_bg @attrs.bold = saved_bold @attrs.italic = saved_italic @attrs.underline = saved_underline @attrs.inverse = saved_inverse end end |
#repeat_char(n = 1) ⇒ Object
160 161 162 163 164 |
# File 'lib/echoes/screen.rb', line 160 def repeat_char(n = 1) return unless @last_char n.times { put_char(@last_char) } end |
#reset ⇒ Object
1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 |
# File 'lib/echoes/screen.rb', line 1048 def reset @cursor = Cursor.new @attrs = Cell.new @grid = Array.new(@rows) { Array.new(@cols) { Cell.new } } @line_wrapped = Array.new(@rows, false) @scroll_top = 0 @scroll_bottom = @rows - 1 @saved_cursor = nil @scrollback = [] @scrollback_wrapped = [] @tab_stops = default_tab_stops @pending_wrap = false mark_all_dirty end |
#resize(new_rows, new_cols) ⇒ Object
1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 |
# File 'lib/echoes/screen.rb', line 1063 def resize(new_rows, new_cols) old_cols = @cols @rows = new_rows @cols = new_cols if new_cols != old_cols reflow(new_rows, new_cols, old_cols) else # Only row count changed — simple add/remove if new_rows > @grid.size (new_rows - @grid.size).times do @grid.push(Array.new(new_cols) { Cell.new }) @line_wrapped.push(false) end elsif new_rows < @grid.size @grid.slice!(new_rows..) @line_wrapped.slice!(new_rows..) end end @scroll_top = 0 @scroll_bottom = new_rows - 1 @cursor.row = clamp_row(@cursor.row) @cursor.col = clamp_col(@cursor.col) @pending_wrap = false end |
#restore_cursor ⇒ Object
635 636 637 638 639 640 641 642 643 644 645 646 647 |
# File 'lib/echoes/screen.rb', line 635 def restore_cursor if @saved_cursor @cursor.row = @saved_cursor[:row] @cursor.col = @saved_cursor[:col] @attrs.copy_from(@saved_cursor[:attrs]) @origin_mode = @saved_cursor[:origin_mode] @auto_wrap = @saved_cursor[:auto_wrap] @charset_g0 = @saved_cursor[:charset_g0] @charset_g1 = @saved_cursor[:charset_g1] @active_charset = @saved_cursor[:active_charset] @pending_wrap = @saved_cursor[:pending_wrap] || false end end |
#reverse_index ⇒ Object
331 332 333 334 335 336 337 338 |
# File 'lib/echoes/screen.rb', line 331 def reverse_index @pending_wrap = false if @cursor.row == @scroll_top scroll_down(1) else @cursor.row = [0, @cursor.row - 1].max end end |
#save_cursor ⇒ Object
620 621 622 623 624 625 626 627 628 629 630 631 632 633 |
# File 'lib/echoes/screen.rb', line 620 def save_cursor saved_attrs = Cell.new saved_attrs.copy_from(@attrs) @saved_cursor = { row: @cursor.row, col: @cursor.col, attrs: saved_attrs, origin_mode: @origin_mode, auto_wrap: @auto_wrap, charset_g0: @charset_g0, charset_g1: @charset_g1, active_charset: @active_charset, pending_wrap: @pending_wrap, } end |
#scroll_down(n = 1) ⇒ Object
481 482 483 484 485 486 487 488 489 490 |
# File 'lib/echoes/screen.rb', line 481 def scroll_down(n = 1) @pending_wrap = false n.times do @grid.delete_at(@scroll_bottom) @line_wrapped.delete_at(@scroll_bottom) @grid.insert(@scroll_top, Array.new(@cols) { Cell.new }) @line_wrapped.insert(@scroll_top, false) end (@scroll_top..@scroll_bottom).each { |r| mark_dirty(r) } end |
#scroll_up(n = 1) ⇒ Object
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 |
# File 'lib/echoes/screen.rb', line 460 def scroll_up(n = 1) @pending_wrap = false n.times do if @scroll_top == 0 row = @grid[@scroll_top] @scrollback << row.map { |cell| c = Cell.new; c.copy_from(cell); c.width = cell.width; c.multicell = cell.multicell; c } @scrollback_wrapped << @line_wrapped[@scroll_top] if @scrollback.size > self.class.scrollback_limit @scrollback.shift adjust_command_marks(-1) end @scrollback_wrapped.shift if @scrollback_wrapped.size > self.class.scrollback_limit end @grid.delete_at(@scroll_top) @line_wrapped.delete_at(@scroll_top) @grid.insert(@scroll_bottom, Array.new(@cols) { Cell.new }) @line_wrapped.insert(@scroll_bottom, false) end (@scroll_top..@scroll_bottom).each { |r| mark_dirty(r) } end |
#selected_text(sr, sc, er, ec) ⇒ Object
987 988 989 990 991 992 993 994 995 |
# File 'lib/echoes/screen.rb', line 987 def selected_text(sr, sc, er, ec) lines = [] (sr..er).each do |r| from = (r == sr) ? sc : 0 to = (r == er) ? ec : @cols - 1 lines << @grid[r][from..to].map { |cell| cell.char }.join.rstrip end lines.join("\n") end |
#set_clipboard(text) ⇒ Object
873 874 875 |
# File 'lib/echoes/screen.rb', line 873 def set_clipboard(text) @clipboard_handler&.call(:set, text) end |
#set_current_command_text(text) ⇒ Object
Attach the literal command text to the most recently opened mark. The host calls this at submit time (between OSC 133 ;B and ;C) so click-to-rerun can recover the command text from a clicked prompt row long after submission.
821 822 823 824 |
# File 'lib/echoes/screen.rb', line 821 def set_current_command_text(text) return unless @current_command_mark @current_command_mark[:command_text] = text end |
#set_graphics(params) ⇒ Object
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 |
# File 'lib/echoes/screen.rb', line 500 def set_graphics(params) params = [0] if params.empty? i = 0 while i < params.length p = params[i] # Handle colon sub-parameter arrays (e.g. [38, 2, nil, R, G, B]) if p.is_a?(Array) apply_sgr_subparams(p) i += 1 next end case p when 0, nil @attrs.reset! when 1 @attrs.bold = true when 2 @attrs.faint = true when 3 @attrs.italic = true when 4 @attrs.underline = true when 7 @attrs.inverse = true when 5, 6 @attrs.blink = true when 8 @attrs.concealed = true when 9 @attrs.strikethrough = true when 22 @attrs.bold = false @attrs.faint = false when 23 @attrs.italic = false when 24 @attrs.underline = false when 27 @attrs.inverse = false when 25 @attrs.blink = false when 28 @attrs.concealed = false when 29 @attrs.strikethrough = false when 30..37 @attrs.fg = p - 30 when 38 if params[i + 1] == 2 && params[i + 2] && params[i + 3] && params[i + 4] @attrs.fg = [params[i + 2], params[i + 3], params[i + 4]] i += 4 elsif params[i + 1] == 5 && params[i + 2] @attrs.fg = params[i + 2] i += 2 end when 39 @attrs.fg = nil when 40..47 @attrs.bg = p - 40 when 48 if params[i + 1] == 2 && params[i + 2] && params[i + 3] && params[i + 4] @attrs.bg = [params[i + 2], params[i + 3], params[i + 4]] i += 4 elsif params[i + 1] == 5 && params[i + 2] @attrs.bg = params[i + 2] i += 2 end when 49 @attrs.bg = nil when 90..97 @attrs.fg = p - 90 + 8 when 100..107 @attrs.bg = p - 100 + 8 end i += 1 end end |
#set_hyperlink(uri) ⇒ Object
704 705 706 |
# File 'lib/echoes/screen.rb', line 704 def set_hyperlink(uri) @attrs.hyperlink = uri end |
#set_scroll_region(top, bottom) ⇒ Object
492 493 494 495 496 497 498 |
# File 'lib/echoes/screen.rb', line 492 def set_scroll_region(top, bottom) @pending_wrap = false @scroll_top = clamp_row(top) @scroll_bottom = clamp_row(bottom) @cursor.row = 0 @cursor.col = 0 end |
#set_tab_stop ⇒ Object
354 355 356 357 |
# File 'lib/echoes/screen.rb', line 354 def set_tab_stop @tab_stops << @cursor.col unless @tab_stops.include?(@cursor.col) @tab_stops.sort! end |
#show_cursor ⇒ Object
975 976 977 |
# File 'lib/echoes/screen.rb', line 975 def show_cursor @cursor.visible = true end |
#soft_reset ⇒ Object
1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 |
# File 'lib/echoes/screen.rb', line 1012 def soft_reset @attrs = Cell.new @cursor.visible = true @saved_cursor = nil @origin_mode = false @auto_wrap = true @insert_mode = false @application_cursor_keys = false @bracketed_paste_mode = false @focus_reporting = false @charset_g0 = :ascii @charset_g1 = :ascii @charset_g2 = :ascii @charset_g3 = :ascii @active_charset = 0 @single_shift = nil @cursor_style = 0 @tab_stops = default_tab_stops @scroll_top = 0 @scroll_bottom = @rows - 1 @pending_wrap = false end |
#switch_to_alt_screen ⇒ Object
907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 |
# File 'lib/echoes/screen.rb', line 907 def switch_to_alt_screen return if @using_alt_screen @main_grid = @grid @main_line_wrapped = @line_wrapped @main_cursor = [@cursor.row, @cursor.col, @cursor.visible] @main_scroll_top = @scroll_top @main_scroll_bottom = @scroll_bottom @main_saved_cursor = @saved_cursor @main_scrollback = @scrollback @main_scrollback_wrapped = @scrollback_wrapped @main_rows = @rows @main_cols = @cols @grid = Array.new(@rows) { Array.new(@cols) { Cell.new } } @line_wrapped = Array.new(@rows, false) @cursor = Cursor.new @attrs = Cell.new @scroll_top = 0 @scroll_bottom = @rows - 1 @saved_cursor = nil @scrollback = [] @scrollback_wrapped = [] @pending_wrap = false @using_alt_screen = true mark_all_dirty end |
#switch_to_main_screen ⇒ Object
935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 |
# File 'lib/echoes/screen.rb', line 935 def switch_to_main_screen return unless @using_alt_screen current_rows = @rows current_cols = @cols @grid = @main_grid @line_wrapped = @main_line_wrapped @cursor = Cursor.new @cursor.row, @cursor.col, @cursor.visible = @main_cursor @scroll_top = @main_scroll_top @scroll_bottom = @main_scroll_bottom @saved_cursor = @main_saved_cursor @scrollback = @main_scrollback @scrollback_wrapped = @main_scrollback_wrapped @rows = @main_rows @cols = @main_cols @attrs = Cell.new @main_grid = nil @main_line_wrapped = nil @main_cursor = nil @main_scroll_top = nil @main_scroll_bottom = nil @main_saved_cursor = nil @main_scrollback = nil @main_scrollback_wrapped = nil @main_rows = nil @main_cols = nil @pending_wrap = false @using_alt_screen = false # If terminal was resized while in alt screen, adjust the restored main grid if current_rows != @rows || current_cols != @cols resize(current_rows, current_cols) end mark_all_dirty end |
#tab ⇒ Object
340 341 342 343 344 |
# File 'lib/echoes/screen.rb', line 340 def tab @pending_wrap = false next_stop = @tab_stops.find { |s| s > @cursor.col } @cursor.col = next_stop ? [next_stop, @cols - 1].min : @cols - 1 end |
#text_for_command_output(mark) ⇒ Object
Extract the visible text of a command’s output region. ‘mark` is one of the entries from `@command_marks`. Rows that have scrolled off the front of the scrollback are silently skipped — the text is no longer recoverable. Returns “” when the mark is incomplete (no :output_start or :output_end yet).
794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 |
# File 'lib/echoes/screen.rb', line 794 def text_for_command_output(mark) return '' unless mark && mark[:output_start] && mark[:output_end] from = mark[:output_start] to = mark[:output_end] return '' if to <= from sb_size = @scrollback.size lines = [] (from...to).each do |abs_row| row = abs_row < 0 ? nil : abs_row < sb_size ? @scrollback[abs_row] : @grid[abs_row - sb_size] next unless row lines << row.map { |c| c.char || ' ' }.join.rstrip end lines.join("\n") end |
#to_text ⇒ Object
983 984 985 |
# File 'lib/echoes/screen.rb', line 983 def to_text @grid.map { |row| row.map { |cell| cell.char }.join.rstrip }.join("\n").rstrip end |
#using_alt_screen? ⇒ Boolean
903 904 905 |
# File 'lib/echoes/screen.rb', line 903 def using_alt_screen? @using_alt_screen end |
#word_boundaries_at(row, col) ⇒ Object
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 |
# File 'lib/echoes/screen.rb', line 997 def word_boundaries_at(row, col) return nil if row < 0 || row >= @rows || col < 0 || col >= @cols line = @grid[row] cls = char_class(line[col].char) start_col = col start_col -= 1 while start_col > 0 && char_class(line[start_col - 1].char) == cls end_col = col end_col += 1 while end_col < @cols - 1 && char_class(line[end_col + 1].char) == cls [start_col, end_col] end |