Class: Tempest::REPL::Screen
- Inherits:
-
Object
- Object
- Tempest::REPL::Screen
show all
- Defined in:
- lib/tempest/repl/screen.rb
Overview
Implements the earthquake-style split layout: the bottom row holds the tempest> prompt, while the rest of the terminal scrolls timeline lines in from below. Built on the DECSTBM (top/bottom margin) escape sequence so we don’t need a full curses screen.
Sequences used:
CSI top;bottom r set scrolling region
CSI r reset scrolling region (full screen)
CSI row;col H move cursor
ESC 7 / ESC 8 save/restore cursor (DECSC/DECRC)
Constant Summary
collapse
- PROMPT_ROWS =
Number of bottom rows reserved for the prompt. Reline may wrap a long input across multiple display rows; without reserving those rows below the DECSTBM region the wrap would emit n past the bottom margin and scroll the timeline off-screen. Two rows is the common case for a single-wrap post.
2
Instance Method Summary
collapse
Constructor Details
#initialize(io:, rows: nil, cols: nil) ⇒ Screen
Returns a new instance of Screen.
24
25
26
27
28
29
30
31
32
|
# File 'lib/tempest/repl/screen.rb', line 24
def initialize(io:, rows: nil, cols: nil)
@io = io
@rows = rows
@cols = cols
@enabled = false
@suspended = false
@mutex = Mutex.new
@pending_resize = nil
end
|
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args, **kwargs, &block) ⇒ Object
153
154
155
|
# File 'lib/tempest/repl/screen.rb', line 153
def method_missing(name, *args, **kwargs, &block)
@io.send(name, *args, **kwargs, &block)
end
|
Instance Method Details
#disable ⇒ Object
48
49
50
51
52
53
54
55
|
# File 'lib/tempest/repl/screen.rb', line 48
def disable
return unless @enabled
uninstall_resize_trap
@io.print "\e_Ga=d,q=2\e\\"
@io.print "\e[r"
@io.flush if @io.respond_to?(:flush)
@enabled = false
end
|
#enable ⇒ Object
34
35
36
37
38
39
40
41
42
43
44
45
46
|
# File 'lib/tempest/repl/screen.rb', line 34
def enable
return unless @io.respond_to?(:tty?) && @io.tty?
rows = @rows || detect_rows
return unless rows && rows >= PROMPT_ROWS + 3
@rows = rows
@cols ||= detect_cols
@io.print "\e[1;#{rows - PROMPT_ROWS}r" @io.print "\e[#{prompt_row};1H" @io.flush if @io.respond_to?(:flush)
@enabled = true
install_resize_trap
end
|
#enabled? ⇒ Boolean
79
80
81
|
# File 'lib/tempest/repl/screen.rb', line 79
def enabled?
@enabled
end
|
#flush ⇒ Object
141
142
143
|
# File 'lib/tempest/repl/screen.rb', line 141
def flush
@io.flush if @io.respond_to?(:flush)
end
|
#notify_resize(rows: nil, cols: nil) ⇒ Object
SIGWINCH hook. Trap handlers in Ruby are restricted (can’t reliably acquire mutexes or drive Reline), so we only stash the new dimensions here and apply them on the next mutex-protected write. If rows/cols are omitted (the production path), they’re read from IO.console at apply time so coalesced WINCHes still pick up the latest size.
104
105
106
|
# File 'lib/tempest/repl/screen.rb', line 104
def notify_resize(rows: nil, cols: nil)
@pending_resize = { rows: rows, cols: cols }
end
|
#prepare_prompt ⇒ Object
Clear the rows reserved for the prompt and re-park the cursor on the first prompt row. Called by the REPL right before each readline so a previous wrapped input doesn’t leave residue on the lower prompt rows.
86
87
88
89
90
91
92
93
94
95
96
97
|
# File 'lib/tempest/repl/screen.rb', line 86
def prepare_prompt
return unless @enabled
@mutex.synchronize do
apply_pending_resize
PROMPT_ROWS.times do |i|
@io.print "\e[#{prompt_row + i};1H"
@io.print "\r\e[2K"
end
@io.print "\e[#{prompt_row};1H"
@io.flush if @io.respond_to?(:flush)
end
end
|
#print(*args) ⇒ Object
133
134
135
|
# File 'lib/tempest/repl/screen.rb', line 133
def print(*args)
@io.print(*args)
end
|
#puts(*lines) ⇒ Object
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
# File 'lib/tempest/repl/screen.rb', line 108
def puts(*lines)
return if @suspended
@mutex.synchronize do
apply_pending_resize
if @enabled
flat = lines.empty? ? [""] : lines.flat_map { |l| l.to_s.split("\n") }
flat.each { |line| insert_above_prompt(line) }
else
(lines.empty? ? [""] : lines).each do |line|
@io.print "\r\e[2K"
@io.puts line
end
@io.flush if @io.respond_to?(:flush)
end
end
rerender_prompt
end
|
#respond_to_missing?(name, include_private = false) ⇒ Boolean
149
150
151
|
# File 'lib/tempest/repl/screen.rb', line 149
def respond_to_missing?(name, include_private = false)
@io.respond_to?(name, include_private)
end
|
#resume ⇒ Object
73
74
75
76
77
|
# File 'lib/tempest/repl/screen.rb', line 73
def resume
return if @enabled
@suspended = false
enable
end
|
#suspend ⇒ Object
Transient teardown for handing the terminal off to a subprocess (e.g. $EDITOR via ‘:compose`). Unlike `disable`, this does NOT issue the Kitty graphics delete sequence — terminals that support the Kitty protocol keep image placements in the main screen buffer even while the editor draws on the alternate buffer, so suspending without deleting lets the avatars re-appear automatically when the editor exits. Pair with `resume` to re-establish the scrolling region.
64
65
66
67
68
69
70
71
|
# File 'lib/tempest/repl/screen.rb', line 64
def suspend
return unless @enabled
uninstall_resize_trap
@io.print "\e[r"
@io.flush if @io.respond_to?(:flush)
@enabled = false
@suspended = true
end
|
#tty? ⇒ Boolean
145
146
147
|
# File 'lib/tempest/repl/screen.rb', line 145
def tty?
@io.respond_to?(:tty?) ? @io.tty? : false
end
|
#write(*args) ⇒ Object
137
138
139
|
# File 'lib/tempest/repl/screen.rb', line 137
def write(*args)
@io.write(*args)
end
|