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



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

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)


49
50
51
# File 'lib/tempest/repl/screen.rb', line 49

def enabled?
  @enabled
end

#flushObject



90
91
92
# File 'lib/tempest/repl/screen.rb', line 90

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.



58
59
60
# File 'lib/tempest/repl/screen.rb', line 58

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


82
83
84
# File 'lib/tempest/repl/screen.rb', line 82

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

#puts(*lines) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/tempest/repl/screen.rb', line 62

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)


98
99
100
# File 'lib/tempest/repl/screen.rb', line 98

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

#tty?Boolean

Returns:

  • (Boolean)


94
95
96
# File 'lib/tempest/repl/screen.rb', line 94

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

#write(*args) ⇒ Object



86
87
88
# File 'lib/tempest/repl/screen.rb', line 86

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