class Termisu::Terminal

Overview

High-level terminal interface combining I/O backend, Terminfo, and cell buffer.

Provides a complete terminal UI API including:

Example:

terminal = Termisu::Terminal.new
terminal.enable_raw_mode
terminal.enter_alternate_screen

terminal.set_cell(10, 5, 'H', fg: Color.red)
terminal.set_cell(11, 5, 'i', fg: Color.green)
terminal.set_cursor(12, 5)
terminal.render

terminal.exit_alternate_screen
terminal.close

Defined in:

termisu/terminal.cr

Constant Summary

BSU = "\e[?2026h"

Synchronized update escape sequences. Prevents screen tearing by buffering output between BSU and ESU. Supported by: Windows Terminal, Kitty, iTerm2, Wezterm, Alacritty 0.13+, foot, mintty, Ghostty. Unsupported terminals simply ignore these sequences.

ESU = "\e[?2026l"
KITTY_KEYBOARD_DISABLE = "\e[<u"
KITTY_KEYBOARD_ENABLE = "\e[>1u"

Enhanced keyboard protocol escape sequences. These protocols disambiguate keys that normally send the same bytes (e.g., Tab vs Ctrl+I, Enter vs Ctrl+M).

Kitty keyboard protocol (most comprehensive): https://sw.kovidgoyal.net/kitty/keyboard-protocol/ Flags: 1=disambiguate, 2=report_event_types, 4=report_alternate_keys 8=report_all_keys, 16=report_text

KITTY_KEYBOARD_QUERY = "\e[?u"
Log = Termisu::Logs::Terminal
MODIFY_OTHER_KEYS_DISABLE = "\e[>4;0m"
MODIFY_OTHER_KEYS_ENABLE = "\e[>4;2m"

modifyOtherKeys (xterm, widely supported): Mode 2 reports modified keys as CSI 27 ; modifier ; keycode ~

MOUSE_DISABLE_NORMAL = "\e[?1000l"
MOUSE_DISABLE_SGR = "\e[?1006l"
MOUSE_ENABLE_NORMAL = "\e[?1000h"

Mouse protocol escape sequences. Using CSI ? sequences for xterm-compatible mouse tracking.

MOUSE_ENABLE_SGR = "\e[?1006h"

Constructors

Instance Method Summary

Instance methods inherited from class Termisu::Renderer

background=(color : Color) background=, close close, enable_blink enable_blink, enable_bold enable_bold, enable_cursive enable_cursive, enable_dim enable_dim, enable_hidden enable_hidden, enable_reverse enable_reverse, enable_strikethrough enable_strikethrough, enable_underline enable_underline, flush flush, foreground=(color : Color) foreground=, move_cursor(x : Int32, y : Int32) move_cursor, reset_attributes reset_attributes, size : Tuple(Int32, Int32) size, write(data : String) write, write_hide_cursor write_hide_cursor, write_show_cursor write_show_cursor

Constructor Detail

def self.new(backend : Terminal::Backend = Terminal::Backend.new, terminfo : Terminfo = Terminfo.new, *, sync_updates : Bool = true) #

Creates a new terminal.

Parameters:

  • backend - Terminal::Backend instance for I/O operations (default: Terminal::Backend.new)
  • terminfo - Terminfo instance for capability strings (default: Terminfo.new)
  • sync_updates - Enable DEC mode 2026 synchronized updates (default: true)

[View source]

Instance Method Detail

def alternate_screen? : Bool #

Returns whether alternate screen mode is active.


[View source]
def background=(color : Color) #

Sets the background color with full ANSI-8, ANSI-256, and RGB support.

Caches the color to avoid redundant escape sequences when called repeatedly with the same color.


[View source]
def clear_cells #

Clears the cell buffer (fills with default cells).

Call render() to display changes on screen.


[View source]
def clear_screen #

Clears the screen.

Writes the clear screen escape sequence immediately and flushes. Also resets cached render state since screen content is cleared.


[View source]
def close #

Closes the terminal and underlying backend.


[View source]
def current_mode : Terminal::Mode | Nil #

Returns the current terminal mode, or nil if not yet set.

Delegates to underlying Backend instance.


[View source]
def disable_enhanced_keyboard #

Disables enhanced keyboard protocol.

Returns to legacy keyboard mode where Tab/Ctrl+I, Enter/Ctrl+M, etc. are indistinguishable.


[View source]
def disable_mouse #

Disables mouse input tracking.

Disables both SGR and normal mouse protocols.


[View source]
def disable_raw_mode #

Disables raw mode on the terminal.


[View source]
def enable_blink #

Enables blink.

Caches attribute state to avoid redundant escape sequences.


[View source]
def enable_bold #

Enables bold text.

Caches attribute state to avoid redundant escape sequences.


[View source]
def enable_cursive #

Enables italic/cursive text.

Caches attribute state to avoid redundant escape sequences.


[View source]
def enable_dim #

Enables dim/faint text.

Caches attribute state to avoid redundant escape sequences.


[View source]
def enable_enhanced_keyboard #

Enables enhanced keyboard protocol for disambiguated key reporting.

This enables the Kitty keyboard protocol (if supported) and falls back to modifyOtherKeys. Enhanced mode allows distinguishing between keys that normally send the same bytes:

  • Tab vs Ctrl+I
  • Enter vs Ctrl+M
  • Backspace vs Ctrl+H

Not all terminals support these protocols. Unsupported terminals will simply ignore the escape sequences and continue with legacy behavior.

Example:

terminal.enable_enhanced_keyboard
# Now Ctrl+I and Tab are distinguishable
terminal.disable_enhanced_keyboard # When done

[View source]
def enable_hidden #

Enables hidden/invisible text.

Caches attribute state to avoid redundant escape sequences.


[View source]
def enable_mouse #

Enables mouse input tracking.

Enables SGR extended mouse protocol (mode 1006) for better coordinate support and unambiguous button detection. Falls back to normal mode (1000) on older terminals that don't support SGR.

Example:

terminal.enable_mouse
# Now mouse events will be reported via poll_event
terminal.disable_mouse # When done

[View source]
def enable_raw_mode #

Enables raw mode on the terminal.


[View source]
def enable_reverse #

Enables reverse video.

Caches attribute state to avoid redundant escape sequences.


[View source]
def enable_strikethrough #

Enables strikethrough text.

Caches attribute state to avoid redundant escape sequences.


[View source]
def enable_underline #

Enables underline.

Caches attribute state to avoid redundant escape sequences.


[View source]
def enhanced_keyboard? : Bool #

Returns whether enhanced keyboard protocol is enabled.


[View source]
def enter_alternate_screen #

Enters alternate screen mode.

Switches to alternate screen buffer, clears the screen, enters keypad mode, and hides cursor. Also resets cached render state since we're entering a fresh screen.


[View source]
def exit_alternate_screen #

Exits alternate screen mode.

Shows cursor, exits keypad mode, and returns to main screen buffer. Also resets cached render state since we're returning to the main screen which may have different state.


[View source]
def flush #

Delegates flush to backend.


[View source]
def foreground=(color : Color) #

Sets the foreground color with full ANSI-8, ANSI-256, and RGB support.

Caches the color to avoid redundant escape sequences when called repeatedly with the same color.


[View source]
def get_cell(x : Int32, y : Int32) : Cell | Nil #

Gets a cell at the specified position from the buffer.

Returns nil if coordinates are out of bounds.


[View source]
def hide_cursor #

Hides the cursor (rendered on next render()).


[View source]
def infd : Int32 #

Returns the input file descriptor for Reader.


[View source]
def invalidate_buffer #

Invalidates the buffer, forcing a full re-render on next render().

Call this after the terminal screen has been cleared externally. Unlike sync(), this doesn't render immediately - it marks the buffer so the next render() call will redraw everything.


[View source]
def mouse_enabled? : Bool #

Returns whether mouse tracking is currently enabled.


[View source]
def move_cursor(x : Int32, y : Int32) #

Moves cursor to the specified position.

Uses the terminfo cup capability with tparm processing for proper terminal-specific cursor addressing. The cup capability handles the 0-to-1 based coordinate conversion via the %i operation.

Parameters:

  • x: Column position (0-based)
  • y: Row position (0-based)

[View source]
def outfd : Int32 #

Returns the output file descriptor.


[View source]
def raw_mode? : Bool #

Returns whether raw mode is currently enabled.


[View source]
def render #

Renders cell buffer changes to the screen.

Only cells that have changed since the last render are redrawn (diff-based). This is more efficient than full redraws for partial updates.

When sync_updates is enabled, wraps the render in DEC mode 2026 sequences (BSU/ESU) to prevent screen tearing during rapid updates.


[View source]
def reset_attributes #

Resets all attributes to default.

Also clears cached color/attribute state since reset affects all styling.


[View source]
def reset_render_state #

Resets the cached render state.

Call this when the terminal state becomes unknown (e.g., after external programs have modified the terminal, or after errors). This forces the next color/attribute calls to emit escape sequences even if the cached values match.

The following operations automatically reset render state:

  • enter_alternate_screen
  • exit_alternate_screen
  • clear_screen
  • reset_attributes

[View source]
def resize_buffer(width : Int32, height : Int32) #

Resizes the buffer to new dimensions.

Preserves existing content where possible.


[View source]
def set_cell(x : Int32, y : Int32, ch : Char, fg : Color = Color.white, bg : Color = Color.default, attr : Attribute = Attribute::None) : Bool #

Sets a cell at the specified position in the buffer.

Parameters:

  • x: Column position (0-based)
  • y: Row position (0-based)
  • ch: Character to display
  • fg: Foreground color (default: white)
  • bg: Background color (default: default terminal color)
  • attr: Text attributes (default: None)

Returns false if coordinates are out of bounds. Call render() to display changes on screen.


[View source]
def set_cursor(x : Int32, y : Int32) #

Sets cursor position in the buffer and makes it visible.

Coordinates are clamped to buffer bounds. Call render() to display the cursor on screen.


[View source]
def set_mode(mode : Terminal::Mode) #

Sets terminal to specific mode using Terminal::Mode flags.

Updates raw_mode_enabled tracking based on whether mode is raw. Does not handle screen or cursor transitions - use with_mode for that.

Parameters:

  • mode: Terminal::Mode flags specifying desired behavior

Example:

terminal.set_mode(Terminal::Mode.cooked)
terminal.set_mode(Terminal::Mode.raw)

ameba:disable Naming/AccessorMethodName


[View source]
def show_cursor #

Shows the cursor at current position (rendered on next render()).


[View source]
def size : Tuple(Int32, Int32) #

Delegates size to backend.


[View source]
def sync #

Forces a full redraw of all cells.

Useful after terminal resize or screen corruption.

When sync_updates is enabled, wraps the sync in DEC mode 2026 sequences (BSU/ESU) to prevent screen tearing during the full redraw.


[View source]
def sync_updates=(sync_updates : Bool) #

Sets whether synchronized updates are enabled.

Can be toggled at runtime. Set to false for debugging or compatibility with terminals that misbehave with sync sequences.


[View source]
def sync_updates? : Bool #

Returns whether synchronized updates are enabled.

When enabled, render operations are wrapped in BSU/ESU sequences to prevent screen tearing. Enabled by default.


[View source]
def with_cbreak_mode(preserve_screen : Bool = true, &) #

Executes a block with cbreak mode.

Cbreak mode provides character-by-character input with echo and signal handling. Useful for interactive prompts where you want immediate response but still show typed characters.

By default, preserves alternate screen since cbreak is typically used within a TUI context.

Example:

terminal.with_cbreak_mode do
  print "Press any key: "
  key = STDIN.read_char
end

[View source]
def with_cooked_mode(preserve_screen : Bool = false, &) #

Executes a block with cooked (shell-like) mode.

Cooked mode enables canonical input, echo, and signal handling - ideal for shell-out operations where the subprocess needs full terminal control.

By default, exits alternate screen to show the normal terminal, then re-enters alternate screen after the block.

Example:

terminal.with_cooked_mode do
  system("vim file.txt")
end

[View source]
def with_mode(mode : Terminal::Mode, preserve_screen : Bool = false, &) #

Executes a block with specific terminal mode, restoring previous mode after.

This is the recommended way to temporarily switch modes for operations like shell-out or password input. Handles:

  • Mode switching via Backend
  • Alternate screen exit/entry based on preserve_screen parameter
  • Cursor visibility (shown for user-interactive modes)

Parameters:

  • mode: Terminal::Mode to use within the block
  • preserve_screen: If false (default) and mode is canonical, exits alternate screen during block. If true, stays in alternate screen.

Example:

terminal.with_mode(Terminal::Mode.cooked) do
  system("vim file.txt")
end
# Previous mode and screen state restored

[View source]
def with_password_mode(preserve_screen : Bool = true, &) #

Executes a block with password input mode.

Password mode enables canonical (line-buffered) input with signal handling but disables echo. Perfect for secure password entry.

By default, preserves alternate screen since password prompts often appear within a TUI context.

Example:

terminal.with_password_mode do
  print "Password: "
  password = gets
end

[View source]
def with_raw_mode(&) #

Executes a block with raw mode enabled, ensuring cleanup.


[View source]
def write(data : String) #

Delegates write to backend.


[View source]
def write_hide_cursor #

Writes hide cursor escape sequence immediately.

Caches visibility state to avoid redundant escape sequences. Note: This is part of the Renderer interface, called by Buffer. For buffer-based cursor control, use hide_cursor instead.


[View source]
def write_show_cursor #

Writes show cursor escape sequence immediately.

Caches visibility state to avoid redundant escape sequences. Note: This is part of the Renderer interface, called by Buffer. For buffer-based cursor control, use show_cursor instead.


[View source]