Class: TTY::Command::Window::Step

Inherits:
Object
  • Object
show all
Defined in:
lib/tty/command/window/step.rb

Overview

A caller-managed window whose lifecycle spans multiple commands.

Where Integration#run_windowed opens a window for exactly one command, a step is opened explicitly, fed any number of commands (via cmd.run_windowed(..., window: step) or #run / #run!), interleaved with plain #log lines, and closed with #finish. The title bar doubles as a progress line: spinner while open, ✔/✖ title • elapsed once finished.

When the environment cannot render windows (no TTY, Windows, no PTY), Step.open returns a PlainStep with the same interface: start and summary lines are printed as plain text and commands stream their full output, so calling code never needs a branch.

Examples:

A build step with two commands and a note

step = TTY::Command::Window::Step.open(title: "building", lines: 8)
cmd = TTY::Command.new(printer: :null)
cmd.run_windowed("make deps", window: step)
step.log "deps done, compiling"
result = cmd.run_windowed!("make -j8", window: step)
step.finish(success: result.success?)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(title:, lines:, output:, width:, on_exit:, scrollback:, dump_lines:) ⇒ Step

Returns a new instance of Step.



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/tty/command/window/step.rb', line 97

def initialize(title:, lines:, output:, width:, on_exit:, scrollback:, dump_lines:)
  @lines = lines
  @coordinator = Coordinator.for(output, width: width)
  @session = nil
  @session_mutex = Mutex.new
  @emulator = Emulator.new(
    rows: lines, cols: @coordinator.width,
    scrollback_limit: scrollback,
    responder: ->(reply) { write_to_child(reply) }
  )
  @block = Block.new(
    emulator: @emulator, title: title, lines: lines,
    on_exit: on_exit, dump_lines: dump_lines
  )
  @active = true
  @started_at = Block.clock
  @coordinator.register(@block)
end

Instance Attribute Details

#blockObject (readonly)



184
185
186
# File 'lib/tty/command/window/step.rb', line 184

def block
  @block
end

#coordinatorObject (readonly)



187
188
189
# File 'lib/tty/command/window/step.rb', line 187

def coordinator
  @coordinator
end

#linesObject (readonly)

Returns the value of attribute lines.



30
31
32
# File 'lib/tty/command/window/step.rb', line 30

def lines
  @lines
end

Class Method Details

.open(title:, lines: DEFAULT_LINES, output: $stdout, width: nil, on_exit: :collapse_or_dump, scrollback: DEFAULT_SCROLLBACK, dump_lines: nil, window: nil) {|step| ... } ⇒ Step, PlainStep

Open a step window (or a PlainStep fallback).

Parameters:

  • title (String)

    title-bar text

  • lines (Integer) (defaults to: DEFAULT_LINES)

    window height (also the PTY row count commands see)

  • output (IO) (defaults to: $stdout)

    render target

  • width (Integer, nil) (defaults to: nil)

    fixed render width override

  • on_exit (Symbol) (defaults to: :collapse_or_dump)

    :collapse_or_dump (default), :freeze, :dump_on_failure or :collapse

  • scrollback (Integer) (defaults to: DEFAULT_SCROLLBACK)

    plain-text history limit

  • dump_lines (Integer, nil) (defaults to: nil)

    cap on history lines printed by a failure dump; nil dumps everything

  • window (Boolean, nil) (defaults to: nil)

    force (+true+) or forbid (+false+) windowed rendering, overriding TTY / PTY detection

Yields:

  • (step)

    optional block form; the step is finished with success: true on normal return and success: false when the block raises (the exception propagates)

Returns:



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/tty/command/window/step.rb', line 51

def self.open(title:, lines: DEFAULT_LINES, output: $stdout, width: nil,
              on_exit: :collapse_or_dump, scrollback: DEFAULT_SCROLLBACK,
              dump_lines: nil, window: nil)
  validate!(lines, on_exit, dump_lines)

  step =
    if renderable?(window, output)
      new(title: title, lines: lines, output: output, width: width,
          on_exit: on_exit, scrollback: scrollback, dump_lines: dump_lines)
    else
      PlainStep.new(title: title, output: output)
    end
  return step unless block_given?

  begin
    result = yield step
    step.finish(success: true)
    result
  rescue Exception # rubocop:disable Lint/RescueException
    step.finish(success: false)
    raise
  end
end

Instance Method Details

#active?Boolean

Returns false once #finish has run.

Returns:

  • (Boolean)

    false once #finish has run



117
118
119
# File 'lib/tty/command/window/step.rb', line 117

def active?
  @active
end

#adopt_session(session) ⇒ Object



190
191
192
193
# File 'lib/tty/command/window/step.rb', line 190

def adopt_session(session)
  @session_mutex.synchronize { @session = session }
  @coordinator.attach_session(@block, session)
end

#finish(success: true) ⇒ self

Close the step: the title bar shows ✔/✖ and the window ends according to its on_exit mode (for the default :collapse_or_dump — success leaves a one-line summary in the scrollback, failure dumps the output history).

Parameters:

  • success (Boolean) (defaults to: true)

Returns:

  • (self)


174
175
176
177
178
179
180
181
# File 'lib/tty/command/window/step.rb', line 174

def finish(success: true)
  return self unless @active

  @active = false
  @block.finish(success ? true : false, Block.clock - @started_at)
  @coordinator.finalize(@block)
  self
end

#log(text) ⇒ self

Append plain text lines to the window, as if the child had printed them. Embedded ANSI colors are interpreted normally.

Parameters:

  • text (String)

Returns:

  • (self)


126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/tty/command/window/step.rb', line 126

def log(text)
  return self unless @active

  body = +""
  # Start on a fresh line when a child left the cursor mid-line.
  body << "\r\n" if @emulator.cursor_col.positive?
  body << text.to_s.gsub("\n", "\r\n")
  body << "\r\n" unless body.end_with?("\r\n")
  @block.feed(body)
  @coordinator.mark_dirty
  self
end

#release_sessionObject



196
197
198
199
200
# File 'lib/tty/command/window/step.rb', line 196

def release_session
  @session_mutex.synchronize { @session = nil }
  @coordinator.detach_session(@block)
  @coordinator.mark_dirty
end

#retitle(title) ⇒ self

Replace the title-bar text.

Parameters:

  • title (String)

Returns:

  • (self)


143
144
145
146
147
# File 'lib/tty/command/window/step.rb', line 143

def retitle(title)
  @block.retitle(title)
  @coordinator.mark_dirty
  self
end

#run(cmd, *args) ⇒ TTY::Command::Result

Run a command inside this step's window; raises TTY::Command::ExitError on failure like #run.

Sugar for cmd.run_windowed(*args, window: self).

Parameters:

Returns:

  • (TTY::Command::Result)


156
157
158
# File 'lib/tty/command/window/step.rb', line 156

def run(cmd, *args, &)
  cmd.run_windowed(*args_with_window(args), &)
end

#run!(cmd, *args) ⇒ TTY::Command::Result

Same as #run but never raises on non-zero exit.

Returns:

  • (TTY::Command::Result)


163
164
165
# File 'lib/tty/command/window/step.rb', line 163

def run!(cmd, *args, &)
  cmd.run_windowed!(*args_with_window(args), &)
end