Class: Charming::Internal::Terminal::TTYBackend
- Inherits:
-
Object
- Object
- Charming::Internal::Terminal::TTYBackend
- Includes:
- Adapter
- Defined in:
- lib/charming/internal/terminal/tty_backend.rb
Overview
TTYBackend is the production terminal backend. It reads key and mouse events from a TTY::Reader, normalizes them via KeyNormalizer and MouseParser, and writes output frames using TTY::Cursor and TTY::Screen. It also installs SIGWINCH and SIGINFO handlers so the runtime can react to terminal resize and focus changes.
Constant Summary collapse
- ALT_SCREEN_ON =
Escape sequences for entering/leaving the alternate screen buffer.
"\e[?1049h"- ALT_SCREEN_OFF =
"\e[?1049l"- AUTO_WRAP_OFF =
Escape sequences for disabling/enabling automatic line wrapping during frame writes.
"\e[?7l"- AUTO_WRAP_ON =
"\e[?7h"
Instance Method Summary collapse
-
#clear ⇒ Object
Clears the terminal screen and moves the cursor to (1, 1).
-
#disable_mouse_tracking ⇒ Object
Emits the ANSI sequences that disable terminal mouse reporting.
-
#enable_mouse_tracking ⇒ Object
Emits the ANSI sequences that enable terminal mouse reporting (press, motion, SGR).
-
#enter_alt_screen ⇒ Object
Enters the alternate screen buffer.
-
#hide_cursor ⇒ Object
Hides the terminal cursor.
-
#initialize(input: $stdin, output: $stdout, reader: nil, cursor: TTY::Cursor) ⇒ TTYBackend
constructor
input and output default to ‘$stdin`/`$stdout` for normal terminal use; tests can inject IO objects.
-
#install_focus_handler ⇒ Object
Installs a SIGINFO handler that marks the terminal as having received focus.
-
#install_resize_handler ⇒ Object
Installs a SIGWINCH handler that sets the internal ‘@resized` flag, returning the previous handler so it can be restored on teardown.
-
#leave_alt_screen ⇒ Object
Leaves the alternate screen buffer.
-
#mouse_enabled? ⇒ Boolean
Returns whether mouse tracking is currently enabled on this backend.
-
#move_cursor(row, column) ⇒ Object
Moves the terminal cursor to the given 1-based (row, column).
-
#notify_resize ⇒ Object
Manually flags the backend as resized (used by tests or external integrations).
-
#read_event(timeout: nil) ⇒ Object
Reads the next event.
-
#restore_focus_handler ⇒ Object
Restores the previous SIGINFO handler.
-
#restore_resize_handler ⇒ Object
Restores the previous SIGWINCH handler captured by ‘install_resize_handler`.
-
#show_cursor ⇒ Object
Shows the terminal cursor.
-
#size ⇒ Object
Returns the current terminal dimensions as [width, height] via TTY::Screen.
-
#write_frame(frame) ⇒ Object
Writes a full multi-line frame to the terminal, disabling auto-wrap during the write so overlong lines don’t disturb the screen layout.
-
#write_lines(line_changes) ⇒ Object
Writes a partial frame composed of [row, line] tuples (1-based rows).
Constructor Details
#initialize(input: $stdin, output: $stdout, reader: nil, cursor: TTY::Cursor) ⇒ TTYBackend
input and output default to ‘$stdin`/`$stdout` for normal terminal use; tests can inject IO objects. reader is a TTY::Reader instance (created from input/output when nil). cursor is the TTY::Cursor class used for cursor control.
28 29 30 31 32 33 34 35 36 37 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 28 def initialize(input: $stdin, output: $stdout, reader: nil, cursor: TTY::Cursor) @input = input @output = output @reader = reader || TTY::Reader.new(input: input, output: output) @cursor = cursor @key_normalizer = KeyNormalizer.new(@reader) @resized = false @previous_winch_handler = nil @mouse_enabled = false end |
Instance Method Details
#clear ⇒ Object
Clears the terminal screen and moves the cursor to (1, 1).
148 149 150 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 148 def clear write_control(@cursor.clear_screen) end |
#disable_mouse_tracking ⇒ Object
Emits the ANSI sequences that disable terminal mouse reporting. Idempotent.
92 93 94 95 96 97 98 99 100 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 92 def disable_mouse_tracking return unless @mouse_enabled write_control("\e[?1000l") write_control("\e[?1002l") write_control("\e[?1003l") write_control("\e[?1006l") @mouse_enabled = false end |
#enable_mouse_tracking ⇒ Object
Emits the ANSI sequences that enable terminal mouse reporting (press, motion, SGR). Idempotent: skipped when mouse tracking is already enabled.
82 83 84 85 86 87 88 89 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 82 def enable_mouse_tracking return if @mouse_enabled write_control("\e[?1000h") write_control("\e[?1002h") write_control("\e[?1006h") @mouse_enabled = true end |
#enter_alt_screen ⇒ Object
Enters the alternate screen buffer.
128 129 130 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 128 def enter_alt_screen write_control(ALT_SCREEN_ON) end |
#hide_cursor ⇒ Object
Hides the terminal cursor.
143 144 145 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 143 def hide_cursor write_control(@cursor.hide) end |
#install_focus_handler ⇒ Object
Installs a SIGINFO handler that marks the terminal as having received focus. SIGINFO is sent by some terminals (notably macOS Terminal.app) on focus changes.
62 63 64 65 66 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 62 def install_focus_handler # Terminal focus change: some terminals send a special sequence # when focus changes. We use this to throttle rendering. @previous_focus_handler = Signal.trap("INFO") { @focused = true } end |
#install_resize_handler ⇒ Object
Installs a SIGWINCH handler that sets the internal ‘@resized` flag, returning the previous handler so it can be restored on teardown.
56 57 58 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 56 def install_resize_handler @previous_winch_handler = Signal.trap("WINCH") { @resized = true } end |
#leave_alt_screen ⇒ Object
Leaves the alternate screen buffer.
133 134 135 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 133 def leave_alt_screen write_control(ALT_SCREEN_OFF) end |
#mouse_enabled? ⇒ Boolean
Returns whether mouse tracking is currently enabled on this backend.
103 104 105 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 103 def mouse_enabled? @mouse_enabled end |
#move_cursor(row, column) ⇒ Object
Moves the terminal cursor to the given 1-based (row, column).
153 154 155 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 153 def move_cursor(row, column) write_control(@cursor.move_to(column - 1, row - 1)) end |
#notify_resize ⇒ Object
Manually flags the backend as resized (used by tests or external integrations).
108 109 110 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 108 def notify_resize @resized = true end |
#read_event(timeout: nil) ⇒ Object
Reads the next event. If a SIGWINCH was received, returns a ResizeEvent with the current terminal dimensions. Mouse escape sequences are parsed by MouseParser; other input is normalized via KeyNormalizer. Returns nil on timeout.
42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 42 def read_event(timeout: nil) return resize_event if resized? raw = @reader.read_keypress(echo: false, raw: true, nonblock: timeout) return nil unless raw return MouseParser.parse(raw) if MouseParser.sequence?(raw) @key_normalizer.normalize(raw) rescue Errno::EAGAIN, IO::WaitReadable nil end |
#restore_focus_handler ⇒ Object
Restores the previous SIGINFO handler.
69 70 71 72 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 69 def restore_focus_handler Signal.trap("INFO", @previous_focus_handler) if @previous_focus_handler @previous_focus_handler = nil end |
#restore_resize_handler ⇒ Object
Restores the previous SIGWINCH handler captured by ‘install_resize_handler`.
75 76 77 78 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 75 def restore_resize_handler Signal.trap("WINCH", @previous_winch_handler) if @previous_winch_handler @previous_winch_handler = nil end |
#show_cursor ⇒ Object
Shows the terminal cursor.
138 139 140 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 138 def show_cursor write_control(@cursor.show) end |
#size ⇒ Object
Returns the current terminal dimensions as [width, height] via TTY::Screen.
158 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 158 def size = [TTY::Screen.width, TTY::Screen.height] |
#write_frame(frame) ⇒ Object
Writes a full multi-line frame to the terminal, disabling auto-wrap during the write so overlong lines don’t disturb the screen layout.
114 115 116 117 118 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 114 def write_frame(frame) without_auto_wrap do write_positioned_lines(frame.to_s.lines(chomp: true)) end end |
#write_lines(line_changes) ⇒ Object
Writes a partial frame composed of [row, line] tuples (1-based rows).
121 122 123 124 125 |
# File 'lib/charming/internal/terminal/tty_backend.rb', line 121 def write_lines(line_changes, **) without_auto_wrap do write_control(line_changes.map { |row, line| "\e[#{row};1H\e[2K#{line}" }.join) end end |