Class: Tempest::REPL::Screen

Inherits:
Object
  • Object
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)

Instance Method Summary collapse

Constructor Details

#initialize(io:, rows: nil, cols: nil) ⇒ Screen

Returns a new instance of Screen.



17
18
19
20
21
22
23
24
# File 'lib/tempest/repl/screen.rb', line 17

def initialize(io:, rows: nil, cols: nil)
  @io = io
  @rows = rows
  @cols = cols
  @enabled = 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



122
123
124
# File 'lib/tempest/repl/screen.rb', line 122

def method_missing(name, *args, **kwargs, &block)
  @io.send(name, *args, **kwargs, &block)
end

Instance Method Details

#disableObject



40
41
42
43
44
45
46
47
# File 'lib/tempest/repl/screen.rb', line 40

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

#enableObject



26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/tempest/repl/screen.rb', line 26

def enable
  return unless @io.respond_to?(:tty?) && @io.tty?
  rows = @rows || detect_rows
  return unless rows && rows >= 4

  @rows = rows
  @cols ||= detect_cols
  @io.print "\e[1;#{rows - 1}r"   # scrolling region: rows 1..rows-1
  @io.print "\e[#{rows};1H"        # park cursor on the final row (prompt)
  @io.flush if @io.respond_to?(:flush)
  @enabled = true
  install_resize_trap
end

#enabled?Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/tempest/repl/screen.rb', line 69

def enabled?
  @enabled
end

#flushObject



110
111
112
# File 'lib/tempest/repl/screen.rb', line 110

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.



78
79
80
# File 'lib/tempest/repl/screen.rb', line 78

def notify_resize(rows: nil, cols: nil)
  @pending_resize = { rows: rows, cols: cols }
end


102
103
104
# File 'lib/tempest/repl/screen.rb', line 102

def print(*args)
  @io.print(*args)
end

#puts(*lines) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/tempest/repl/screen.rb', line 82

def puts(*lines)
  @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
      # Best-effort write that doesn't shred the prompt when we don't have
      # a scrolling region in place. Reline rerender is invoked by
      # AsyncOutput; Screen itself stays neutral here.
      (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

Returns:

  • (Boolean)


118
119
120
# File 'lib/tempest/repl/screen.rb', line 118

def respond_to_missing?(name, include_private = false)
  @io.respond_to?(name, include_private)
end

#resumeObject



64
65
66
67
# File 'lib/tempest/repl/screen.rb', line 64

def resume
  return if @enabled
  enable
end

#suspendObject

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.



56
57
58
59
60
61
62
# File 'lib/tempest/repl/screen.rb', line 56

def suspend
  return unless @enabled
  uninstall_resize_trap
  @io.print "\e[r"
  @io.flush if @io.respond_to?(:flush)
  @enabled = false
end

#tty?Boolean

Returns:

  • (Boolean)


114
115
116
# File 'lib/tempest/repl/screen.rb', line 114

def tty?
  @io.respond_to?(:tty?) ? @io.tty? : false
end

#write(*args) ⇒ Object



106
107
108
# File 'lib/tempest/repl/screen.rb', line 106

def write(*args)
  @io.write(*args)
end