Class: TTY::Command::Window::Coordinator
- Inherits:
-
Object
- Object
- TTY::Command::Window::Coordinator
- Defined in:
- lib/tty/command/window/coordinator.rb
Overview
Owns a region at the bottom of the terminal and paints the stack of active Blocks into it from a dedicated render thread.
All painted lines end with explicit "\r\n" so rendering stays correct even when the input router has put the terminal into raw mode (which disables output newline translation for the whole tty).
Constant Summary collapse
- FRAME_INTERVAL =
0.08- HIDE_CURSOR =
"\e[?25l"- SHOW_CURSOR =
"\e[?25h"
Instance Attribute Summary collapse
-
#output ⇒ Object
readonly
Returns the value of attribute output.
-
#pastel ⇒ Object
readonly
Returns the value of attribute pastel.
Class Method Summary collapse
-
.emergency_restore_all ⇒ Object
Trap-safe variant of Coordinator.restore_all: iterates a snapshot of the registry without @registry_mutex, which the main thread may hold when the signal arrives (see review P0-5).
-
.for(output, width: nil) ⇒ Coordinator
Coordinator bound to the given output IO (one per IO).
-
.restore_all ⇒ Object
Emergency cleanup for at_exit: restore the cursor everywhere.
Instance Method Summary collapse
-
#color? ⇒ Boolean
Whether colored output is enabled.
-
#emergency_restore ⇒ Object
at_exit safety net: show the cursor if we died mid-paint.
-
#finalize(block) ⇒ Object
Called by the runner when a block's command finished.
-
#initialize(output:, width_override: nil) ⇒ Coordinator
constructor
A new instance of Coordinator.
-
#mark_dirty ⇒ Object
Request a repaint on the next frame (cheap, lock-free).
-
#register(block, child_session: nil) ⇒ Object
Add a block to the stack and start rendering if needed.
-
#width ⇒ Integer
Current terminal width in columns.
-
#winch! ⇒ Object
Trap-context WINCH notification; actual work happens on the render thread.
Constructor Details
#initialize(output:, width_override: nil) ⇒ Coordinator
Returns a new instance of Coordinator.
55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/tty/command/window/coordinator.rb', line 55 def initialize(output:, width_override: nil) @output = output @width_override = width_override @blocks = [] @sessions = {} # block => ChildSession, for signal + PTY resize @painted_height = 0 @active = false @winch = false @dirty = false @mutex = Mutex.new @spinner = Spinner.new @pastel = Pastel.new(enabled: color?) end |
Instance Attribute Details
#output ⇒ Object (readonly)
Returns the value of attribute output.
53 54 55 |
# File 'lib/tty/command/window/coordinator.rb', line 53 def output @output end |
#pastel ⇒ Object (readonly)
Returns the value of attribute pastel.
53 54 55 |
# File 'lib/tty/command/window/coordinator.rb', line 53 def pastel @pastel end |
Class Method Details
.emergency_restore_all ⇒ Object
Trap-safe variant of restore_all: iterates a snapshot of the registry without @registry_mutex, which the main thread may hold when the signal arrives (see review P0-5).
44 45 46 47 48 49 50 |
# File 'lib/tty/command/window/coordinator.rb', line 44 def emergency_restore_all # .values (not each_value): iterating the live hash could raise # if the main thread mutates the registry mid-trap. @registry.values.each(&:emergency_restore) # rubocop:disable Style/HashEachMethods rescue StandardError nil end |
.for(output, width: nil) ⇒ Coordinator
Coordinator bound to the given output IO (one per IO).
28 29 30 31 32 |
# File 'lib/tty/command/window/coordinator.rb', line 28 def for(output, width: nil) @registry_mutex.synchronize do @registry[output] ||= new(output: output, width_override: width) end end |
.restore_all ⇒ Object
Emergency cleanup for at_exit: restore the cursor everywhere.
35 36 37 38 39 |
# File 'lib/tty/command/window/coordinator.rb', line 35 def restore_all @registry_mutex.synchronize do @registry.each_value(&:emergency_restore) end end |
Instance Method Details
#color? ⇒ Boolean
Returns whether colored output is enabled.
136 137 138 139 140 |
# File 'lib/tty/command/window/coordinator.rb', line 136 def color? return false if ENV.key?("NO_COLOR") Window.assume_tty || (@output.respond_to?(:tty?) && @output.tty?) end |
#emergency_restore ⇒ Object
at_exit safety net: show the cursor if we died mid-paint.
143 144 145 146 147 |
# File 'lib/tty/command/window/coordinator.rb', line 143 def emergency_restore @output.write(SHOW_CURSOR) if @active && @output.respond_to?(:write) && !@output.closed? rescue IOError, Errno::EBADF, Errno::EPIPE nil end |
#finalize(block) ⇒ Object
Called by the runner when a block's command finished. Repaints and, for dump-on-failure blocks, replaces the block with its full history as permanent scrolled output. Drains the region when every block is done.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/tty/command/window/coordinator.rb', line 100 def finalize(block) drained = false @mutex.synchronize do next unless @active if block.dump_on_finalize? wipe_region @blocks.delete(block) write_permanent(block.dump_text(@pastel)) end # Drop the session unconditionally: the child has been reaped by # Runner and its PID is no longer safe to signal. Leaving stale # entries in @sessions risks INT/TERM delivery to a recycled PID # (see follow-up review P0-2). @sessions.delete(block) paint if @blocks.all?(&:done?) drain drained = true end end join_render_thread if drained end |
#mark_dirty ⇒ Object
Request a repaint on the next frame (cheap, lock-free).
125 126 127 |
# File 'lib/tty/command/window/coordinator.rb', line 125 def mark_dirty @dirty = true end |
#register(block, child_session: nil) ⇒ Object
Add a block to the stack and start rendering if needed.
87 88 89 90 91 92 93 94 |
# File 'lib/tty/command/window/coordinator.rb', line 87 def register(block, child_session: nil) @mutex.synchronize do activate unless @active @blocks << block @sessions[block] = child_session if child_session paint end end |
#width ⇒ Integer
Returns current terminal width in columns.
70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/tty/command/window/coordinator.rb', line 70 def width return @width_override if @width_override if @output.respond_to?(:winsize) && @output.respond_to?(:tty?) && @output.tty? cols = @output.winsize[1] return cols if cols.positive? end (ENV["COLUMNS"] || "80").to_i.clamp(20, 1000) rescue Errno::ENOTTY, Errno::EBADF, IOError 80 end |
#winch! ⇒ Object
Trap-context WINCH notification; actual work happens on the render thread.
131 132 133 |
# File 'lib/tty/command/window/coordinator.rb', line 131 def winch! @winch = true end |