Class: Potty::Surfaces::InlineSurface

Inherits:
Potty::Surface show all
Defined in:
lib/potty/surfaces/inline_surface.rb

Overview

Renders an N-line region in place under the cursor — like docker compose / npm / cargo progress — instead of taking over the screen. No init_screen, no alt-screen; the terminal stays in cooked mode, so Ctrl-C behaves normally and input is left alone (passive widgets only).

Model: a small cell grid (rows x cols). Widgets draw into it via the same setpos/addstr/attron calls they use on a curses surface; present repaints the region with ANSI (carriage-return + clear-line per row, then cursor back to the top). finalize freezes the last frame and drops the cursor to the line below so the next prompt lands cleanly.

Instance Method Summary collapse

Constructor Details

#initialize(theme:, lines: nil, tick_interval: 40, out: $stdout, listen: false, input: $stdin) ⇒ InlineSurface

Returns a new instance of InlineSurface.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/potty/surfaces/inline_surface.rb', line 21

def initialize(theme:, lines: nil, tick_interval: 40, out: $stdout, listen: false, input: $stdin)
  super()
  @theme = theme
  @rows = [lines || 1, 1].max
  @tick_interval = tick_interval
  @out = out
  @listen = listen
  @input = input
  @cols = detect_cols
  @cursor = [0, 0]
  @cur_style = nil
  @primed = false
  @raw = false
  @decoder = (Input::Decoder.new if listen)
  @queue = []
  erase
end

Instance Method Details

#addstr(str) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/potty/surfaces/inline_surface.rb', line 68

def addstr(str)
  row, col = @cursor
  return unless row.between?(0, @rows - 1)

  str.to_s.each_char do |ch|
    break if col >= @cols

    @cells[row][col] = [ch, @cur_style] if col >= 0
    col += 1
  end
  @cursor = [row, col]
end

#attron(style) ⇒ Object



81
82
83
84
85
86
87
# File 'lib/potty/surfaces/inline_surface.rb', line 81

def attron(style)
  prev = @cur_style
  @cur_style = style.is_a?(Potty::Style) ? style : nil
  yield if block_given?
ensure
  @cur_style = prev
end

#eraseObject



60
61
62
# File 'lib/potty/surfaces/inline_surface.rb', line 60

def erase
  @cells = Array.new(@rows) { Array.new(@cols) { [' ', nil] } }
end

#finalizeObject



49
50
51
52
53
54
55
56
57
58
# File 'lib/potty/surfaces/inline_surface.rb', line 49

def finalize
  present                 # freeze the final frame
  restore_cooked
  # Explicit CR+LF: present leaves the cursor at the end of the last
  # rendered row, and in raw mode \n alone is a bare line-feed (no
  # column reset), which would indent whatever the host prints next.
  @out.write("\r\n")      # drop below the region, at column 0
  @out.write("\e[?25h")   # restore cursor
  @out.flush
end

#presentObject



89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/potty/surfaces/inline_surface.rb', line 89

def present
  if @primed
    @out.write("\e[#{@rows - 1}A") if @rows > 1 # back to the top row
  else
    @primed = true
  end

  @rows.times do |i|
    @out.write("\r\e[2K") # carriage return + clear line
    @out.write(render_row(@cells[i]))
    @out.write("\n") unless i == @rows - 1
  end
  @out.flush
end

#read_keyObject

In listen mode: drain raw stdin (non-blocking, waiting up to one tick for the first byte), decode to Keys codes, and return them one per call (queueing the rest). Without listening (or off a real TTY): just pace the loop and return nil, leaving input alone. Ctrl-C arrives as a byte the decoder passes through as Keys::CTRL_C; the event loop raises.



109
110
111
112
113
114
115
116
117
118
119
# File 'lib/potty/surfaces/inline_surface.rb', line 109

def read_key
  return @queue.shift unless @queue.empty?

  if @raw
    fill_queue
    @queue.shift
  else
    sleep(@tick_interval / 1000.0) if @tick_interval
    nil
  end
end

#setpos(row, col) ⇒ Object



64
65
66
# File 'lib/potty/surfaces/inline_surface.rb', line 64

def setpos(row, col)
  @cursor = [row, col]
end

#sizeObject



39
40
41
# File 'lib/potty/surfaces/inline_surface.rb', line 39

def size
  [@rows, @cols]
end

#startObject



43
44
45
46
47
# File 'lib/potty/surfaces/inline_surface.rb', line 43

def start
  @out.write("\e[?25l") # hide cursor
  @out.flush
  enter_raw if @listen
end