Class: TTY::Command::Window::Emulator
- Inherits:
-
Object
- Object
- TTY::Command::Window::Emulator
- Defined in:
- lib/tty/command/window/emulator.rb
Overview
A VT100-subset terminal emulator with a fixed-size screen.
The emulator maintains a rows x cols grid of cells (character +
SGR attributes), interprets the escape sequences commonly emitted by
CLI tools (cursor movement, erase, scroll regions, insert/delete,
colors, alternate screen) and keeps a plain-text scrollback of lines
that scroll off the top of the main screen.
It is not thread-safe on its own; callers synchronize access.
Constant Summary collapse
- BLANK =
[" ", nil].freeze
- WIDE_PLACEHOLDER =
["", nil].freeze
- MAX_SEQUENCE =
512- TAB_STOP =
8- ASCII_CHARS =
Pre-allocated frozen single-char strings for every printable ASCII codepoint. The ASCII fast path in
scanreuses these instead of allocating a new String per printable byte (see P0-3). (0x20..0x7E).map { |b| b.chr(Encoding::UTF_8).freeze }.freeze
Instance Attribute Summary collapse
-
#cols ⇒ Object
readonly
Returns the value of attribute cols.
-
#cursor_col ⇒ Object
readonly
Returns the value of attribute cursor_col.
-
#cursor_row ⇒ Object
readonly
Returns the value of attribute cursor_row.
-
#rows ⇒ Object
readonly
Returns the value of attribute rows.
-
#scrollback ⇒ Object
readonly
Returns the value of attribute scrollback.
Instance Method Summary collapse
-
#cursor_visible? ⇒ Boolean
Whether the child requested a visible cursor.
-
#feed(data) ⇒ Object
Feed a chunk of raw child output into the emulator.
-
#full_text ⇒ String
Scrollback plus visible screen as plain text.
-
#initialize(rows:, cols:, scrollback_limit: DEFAULT_SCROLLBACK, responder: nil) ⇒ Emulator
constructor
A new instance of Emulator.
-
#render_line(index) ⇒ String
Render one screen row as an ANSI string (no trailing newline, no clear-to-eol; trailing unstyled blanks are stripped).
-
#render_lines ⇒ Array<String>
Every screen row rendered with ANSI colors.
-
#resize(cols:) ⇒ Object
Resize the screen width.
-
#screen_text ⇒ Array<String>
Plain-text content of the visible screen.
Constructor Details
#initialize(rows:, cols:, scrollback_limit: DEFAULT_SCROLLBACK, responder: nil) ⇒ Emulator
Returns a new instance of Emulator.
35 36 37 38 39 40 41 42 43 |
# File 'lib/tty/command/window/emulator.rb', line 35 def initialize(rows:, cols:, scrollback_limit: DEFAULT_SCROLLBACK, responder: nil) @rows = rows @cols = [cols, 1].max @scrollback_limit = scrollback_limit @responder = responder @scrollback = [] @partial_input = (+"").force_encoding(Encoding::BINARY) full_reset end |
Instance Attribute Details
#cols ⇒ Object (readonly)
Returns the value of attribute cols.
28 29 30 |
# File 'lib/tty/command/window/emulator.rb', line 28 def cols @cols end |
#cursor_col ⇒ Object (readonly)
Returns the value of attribute cursor_col.
28 29 30 |
# File 'lib/tty/command/window/emulator.rb', line 28 def cursor_col @cursor_col end |
#cursor_row ⇒ Object (readonly)
Returns the value of attribute cursor_row.
28 29 30 |
# File 'lib/tty/command/window/emulator.rb', line 28 def cursor_row @cursor_row end |
#rows ⇒ Object (readonly)
Returns the value of attribute rows.
28 29 30 |
# File 'lib/tty/command/window/emulator.rb', line 28 def rows @rows end |
#scrollback ⇒ Object (readonly)
Returns the value of attribute scrollback.
28 29 30 |
# File 'lib/tty/command/window/emulator.rb', line 28 def scrollback @scrollback end |
Instance Method Details
#cursor_visible? ⇒ Boolean
Returns whether the child requested a visible cursor.
83 84 85 |
# File 'lib/tty/command/window/emulator.rb', line 83 def cursor_visible? @cursor_visible end |
#feed(data) ⇒ Object
Feed a chunk of raw child output into the emulator. Handles UTF-8 characters and escape sequences split across chunk boundaries.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/tty/command/window/emulator.rb', line 49 def feed(data) buffer = @partial_input + data.dup.force_encoding(Encoding::BINARY) @partial_input = (+"").force_encoding(Encoding::BINARY) tail = incomplete_utf8_tail(buffer) if tail.positive? @partial_input = buffer.byteslice(-tail, tail) buffer = buffer.byteslice(0, buffer.bytesize - tail) end buffer.force_encoding(Encoding::UTF_8) buffer = buffer.scrub("\u{FFFD}") unless buffer.valid_encoding? scan(buffer) invalidate_render_cache nil end |
#full_text ⇒ String
Returns scrollback plus visible screen as plain text.
120 121 122 |
# File 'lib/tty/command/window/emulator.rb', line 120 def full_text (@scrollback + screen_text).join("\n").rstrip end |
#render_line(index) ⇒ String
Render one screen row as an ANSI string (no trailing newline, no clear-to-eol; trailing unstyled blanks are stripped).
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/tty/command/window/emulator.rb', line 92 def render_line(index) row = @grid[index] or return "" last = row.rindex { |cell| cell[0] != " " || cell[1] } or return "" out = +"" current = false row[0..last].each do |(char, sgr)| if sgr != current out << (sgr ? "\e[0;#{sgr}m" : "\e[0m") current = sgr end out << char end out << "\e[0m" if current out end |
#render_lines ⇒ Array<String>
Returns every screen row rendered with ANSI colors.
110 111 112 |
# File 'lib/tty/command/window/emulator.rb', line 110 def render_lines @render_lines ||= Array.new(@rows) { |i| render_line(i) } end |
#resize(cols:) ⇒ Object
Resize the screen width. Rows are fixed for the window's lifetime.
69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/tty/command/window/emulator.rb', line 69 def resize(cols:) cols = [cols, 1].max return if cols == @cols @cols = cols [@main_grid, @alt_grid].each do |grid| grid.each { |row| resize_row(row, cols) } end @cursor_col = [@cursor_col, cols - 1].min @pending_wrap = false invalidate_render_cache end |
#screen_text ⇒ Array<String>
Returns plain-text content of the visible screen.
115 116 117 |
# File 'lib/tty/command/window/emulator.rb', line 115 def screen_text @grid.map { |row| plain_row(row) } end |