Class: AnsiBackend
- Inherits:
-
Object
- Object
- AnsiBackend
- Defined in:
- lib/ansibackend.rb
Overview
A rendering backend that emits ANSI escape sequences instead of painting pixels: a drop-in for WindowAdapter (it satisfies the same draw / scroll / clear / clear_line / insert_lines / delete_lines interface the run-batcher in TrackChanges drives, plus the cell-metric and scrollback hooks Term queries). It turns the terminal’s damage stream - changed runs of cells, plus scroll/clear ops - into the minimal escape sequences that reproduce the screen on a real terminal. This is the “render economically to a terminal, like Emacs” backend, and the basis for letting a TUI app render to a terminal OR an X11 window from the same code.
Colours arrive already resolved to 24-bit RGB (Term#fg/#bg), so they are emitted as truecolor SGR. The run-batcher only hands us cells that changed, so only changed runs are emitted (CUP + minimal SGR + text). The cursor overlay (a cell drawn with the CURSOR background) is recognised and turned into a real cursor position rather than a coloured cell.
Constant Summary collapse
- CURSOR_BG =
must match Term::CURSOR
0xff00ff- SGR_FLAGS =
flag bit -> SGR set-code
{ BOLD => 1, FAINT => 2, ITALICS => 3, UNDERLINE => 4, BLINK => 5, RAPID_BLINK => 6, INVERSE => 7, INVISIBLE => 8, CROSSED_OUT => 9, DBL_UNDERLINE => 21, OVERLINE => 53, }.freeze
Instance Method Summary collapse
- #char_h ⇒ Object
-
#char_w ⇒ Object
# cell metrics: a text cell is one character.
- #clear ⇒ Object
- #clear_line(y, from_x, to_x = nil) ⇒ Object
- #delete_lines(y, num, maxy) ⇒ Object
- #draw(x, y, str, fg, bg, flags, _lineattrs = nil) ⇒ Object
-
#initialize(cols, rows, origin_row: 0, origin_col: 0) ⇒ AnsiBackend
constructor
origin_row/origin_col place the rendered screen at an offset on the real terminal, so the same Term core can be drawn into a sub-window (a terminal-in-a-terminal / multiplexer pane).
- #insert_lines(y, num, maxy) ⇒ Object
-
#output ⇒ Object
The escape sequence produced so far, with a trailing reposition to the cursor overlay if one was seen.
- #reset_state ⇒ Object
- #scroll_up(scroll_start, scroll_end) ⇒ Object
- #scrollback_anchor ⇒ Object
- #scrollback_mode ⇒ Object
- #set_columns(_) ⇒ Object
-
#take ⇒ Object
Take the output and reset the buffer for the next frame.
Constructor Details
#initialize(cols, rows, origin_row: 0, origin_col: 0) ⇒ AnsiBackend
origin_row/origin_col place the rendered screen at an offset on the real terminal, so the same Term core can be drawn into a sub-window (a terminal-in-a-terminal / multiplexer pane). With a non-zero origin, full-screen erase becomes a per-row erase of just the sub-window.
32 33 34 35 36 37 |
# File 'lib/ansibackend.rb', line 32 def initialize(cols, rows, origin_row: 0, origin_col: 0) @cols, @rows = cols, rows @origin_row, @origin_col = origin_row, origin_col @out = +"".b reset_state end |
Instance Method Details
#char_h ⇒ Object
48 |
# File 'lib/ansibackend.rb', line 48 def char_h = 1 |
#char_w ⇒ Object
# cell metrics: a text cell is one character
47 |
# File 'lib/ansibackend.rb', line 47 def char_w = 1 |
#clear ⇒ Object
83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/ansibackend.rb', line 83 def clear reset_state if @origin_row.zero? && @origin_col.zero? @out << "\e[H\e[2J" @cx = @cy = 0 # \e[H homes the cursor else # Erase only this sub-window's rows, not the whole real screen. This # leaves the cursor at the last erased row, so @cx/@cy stay nil # (reset_state) and the next draw re-issues a CUP. @rows.times { |y| @out << cup(y, 0) << "\e[2K" } end end |
#clear_line(y, from_x, to_x = nil) ⇒ Object
96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/ansibackend.rb', line 96 def clear_line(y, from_x, to_x = nil) if to_x # Erase to start (from_x is 0 in practice): emit EL-1 rather than # synthesising spaces, so the replay's clear_to_start reproduces the # exact same cells (raw default attributes, not a resolved \e[0m). move(y, to_x) @out << "\e[1K" else move(y, from_x) @out << "\e[0K" # erase to end of line (buffer truncates the row) end end |
#delete_lines(y, num, maxy) ⇒ Object
120 121 122 123 124 |
# File 'lib/ansibackend.rb', line 120 def delete_lines(y, num, maxy) set_region(@region ? @region[0] : 0, maxy) move(y, 0) @out << "\e[#{num}M" end |
#draw(x, y, str, fg, bg, flags, _lineattrs = nil) ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/ansibackend.rb', line 71 def draw(x, y, str, fg, bg, flags, _lineattrs = nil) if bg == CURSOR_BG @cursor_pos = [x, y] # cursor overlay - the real cursor shows position return end move(y, x) @out << sgr(fg, bg, flags) @out << str @cx = x + str.length @cy = y end |
#insert_lines(y, num, maxy) ⇒ Object
114 115 116 117 118 |
# File 'lib/ansibackend.rb', line 114 def insert_lines(y, num, maxy) set_region(@region ? @region[0] : 0, maxy) move(y, 0) @out << "\e[#{num}L" end |
#output ⇒ Object
The escape sequence produced so far, with a trailing reposition to the cursor overlay if one was seen. Non-destructive.
55 56 57 |
# File 'lib/ansibackend.rb', line 55 def output @cursor_pos ? @out + cup(@cursor_pos[1], @cursor_pos[0]) : @out.dup end |
#reset_state ⇒ Object
39 40 41 42 43 44 |
# File 'lib/ansibackend.rb', line 39 def reset_state @cx = @cy = nil # tracked cursor (nil = unknown -> force CUP) @fg = @bg = @flags = nil # tracked SGR (nil = unknown) @region = nil @cursor_pos = nil end |
#scroll_up(scroll_start, scroll_end) ⇒ Object
109 110 111 112 |
# File 'lib/ansibackend.rb', line 109 def scroll_up(scroll_start, scroll_end) set_region(scroll_start, scroll_end) @out << "\e[S" # scroll the region up one line end |
#scrollback_anchor ⇒ Object
50 |
# File 'lib/ansibackend.rb', line 50 def scrollback_anchor; end |
#scrollback_mode ⇒ Object
49 |
# File 'lib/ansibackend.rb', line 49 def scrollback_mode = false |
#set_columns(_) ⇒ Object
51 |
# File 'lib/ansibackend.rb', line 51 def set_columns(_); end |
#take ⇒ Object
Take the output and reset the buffer for the next frame. The trailing cursor reposition moved the real terminal cursor, so forget the tracked position (the next frame’s first draw must re-issue a CUP). SGR/region state persists - the real terminal keeps it between frames.
63 64 65 66 67 68 69 |
# File 'lib/ansibackend.rb', line 63 def take s = output @out = +"".b @cursor_pos = nil @cx = @cy = nil s end |