require "rooibos"
module CustomShellOutput
Chunk = Data.define(:stream, :text)
ProcessOutput = Data.define(:command, :chunks, :running, :exit_status, :dismissed)
Init = -> do
ProcessOutput.new(command: "", chunks: [].freeze, running: false, exit_status: nil, dismissed: false)
end
View = -> (model, tui) do
spans = if model.chunks.empty? && model.running
[tui.text_span(content: "Running...", style: tui.style(fg: :dark_gray))]
else
model.chunks.map do |chunk|
style = (chunk.stream == :stderr) ? tui.style(fg: :yellow) : nil
tui.text_span(content: chunk.text, style:)
end
end
border_style = case model.exit_status
when nil then nil
when 0 then tui.style(fg: :green)
else tui.style(fg: :red)
end
left_title = model.running ? "ESC: Cancel" : "ESC: Dismiss"
display_cmd = (model.command.length > 60) ? "#{model.command[0..57]}..." : model.command
tui.center(
width_percent: 80,
height_percent: 80,
child: tui.overlay(
layers: [
tui.clear,
tui.block(
title: display_cmd,
titles: [
{ content: left_title, position: :bottom, alignment: :left },
{ content: "ENTER: Dismiss", position: :bottom, alignment: :right },
],
borders: [:all],
border_style:,
children: [tui.paragraph(text: spans)]
),
]
)
)
end
Update = -> (message, model) do
case message
in { type: :system_stream, stream: :stdout, content: }
new_chunks = [*model.chunks, Chunk.new(stream: :stdout, text: content)].freeze
[model.with(chunks: new_chunks), nil]
in { type: :system_stream, stream: :stderr, content: }
new_chunks = [*model.chunks, Chunk.new(stream: :stderr, text: content)].freeze
[model.with(chunks: new_chunks), nil]
in { type: :system_stream, stream: :complete, status: }
[model.with(running: false, exit_status: status), nil]
in { type: :system_stream, stream: :error, content: error_msg }
new_chunks = [*model.chunks, Chunk.new(stream: :stderr, text: "Error: #{error_msg}\n")].freeze
[model.with(chunks: new_chunks, running: false, exit_status: 1), nil]
in _ if message.respond_to?(:esc?) && message.esc?
[model.with(dismissed: true), nil]
in _ if message.respond_to?(:enter?) && message.enter?
[model.with(dismissed: true), nil]
else
[model, nil]
end
end
end