$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
require "bundler/setup"
require "ratatui_ruby"
PROCESSES = [
{ pid: 1234, name: "ruby", cpu: 15.2 },
{ pid: 5678, name: "postgres", cpu: 8.7 },
{ pid: 9012, name: "nginx", cpu: 3.1 },
{ pid: 3456, name: "redis", cpu: 12.4 },
{ pid: 7890, name: "sidekiq", cpu: 22.8 },
{ pid: 2345, name: "webpack", cpu: 45.3 },
{ pid: 6789, name: "node", cpu: 18.9 },
].freeze
class WidgetTable
attr_reader :selected_index, :selected_col, :current_style_index, :column_spacing, :highlight_spacing, :column_highlight_style, :cell_highlight_style
HIGHLIGHT_SPACINGS = [
{ name: "When Selected", spacing: :when_selected },
{ name: "Always", spacing: :always },
{ name: "Never", spacing: :never },
].freeze
OFFSET_MODES = [
{ name: "Auto (No Offset)", offset: nil, allow_selection: true },
{ name: "Offset Only (row 3)", offset: 3, allow_selection: false },
{ name: "Selection + Offset (Conflict)", offset: 0, allow_selection: true },
].freeze
FLEX_MODES = [
{ name: "Legacy (Default)", flex: :legacy },
{ name: "Start", flex: :start },
{ name: "Center", flex: :center },
{ name: "End", flex: :end },
{ name: "Space Between", flex: :space_between },
{ name: "Space Around", flex: :space_around },
{ name: "Space Evenly", flex: :space_evenly },
].freeze
def initialize
@selected_index = 1
@selected_col = 1
@current_style_index = 0
@column_spacing = 1
@highlight_spacing_index = 0
@show_column_highlight = true
@show_cell_highlight = true
@show_header = true
@offset_mode_index = 0
@flex_mode_index = 0
@strikethrough_pids = Set.new
end
def run
RatatuiRuby.run do |tui|
@tui = tui
setup_styles
loop do
@tui.draw do |frame|
render(frame)
end
break if handle_input == :quit
end
end
end
private def setup_styles
@styles = [
{ name: "Cyan", style: @tui.style(fg: :cyan) },
{ name: "Red", style: @tui.style(fg: :red) },
{ name: "Green", style: @tui.style(fg: :green) },
{ name: "Blue on White", style: @tui.style(fg: :blue, bg: :white) },
{ name: "Magenta", style: @tui.style(fg: :magenta, modifiers: [:bold]) },
]
@column_highlight_style = @tui.style(fg: :red)
@cell_highlight_style = @tui.style(fg: :white, bg: :red, modifiers: [:bold])
@hotkey_style = @tui.style(modifiers: [:bold, :underlined])
end
private def render(frame)
rows = PROCESSES.each_with_index.map do |p, i|
cpu_style = case p[:cpu]
when 0...10 then @tui.style(fg: :green)
when 10...30 then @tui.style(fg: :yellow)
else @tui.style(fg: :red, modifiers: [:bold])
end
row = @tui.table_row(
cells: [
p[:pid].to_s,
p[:name],
@tui.table_cell(content: "#{p[:cpu]}%", style: cpu_style),
],
style: i.even? ? @tui.style(bg: :white, fg: :black) : nil
)
if @strikethrough_pids.include?(p[:pid])
row.enable_strikethrough.with(style: (row.style || @tui.style).with(modifiers: ((row.style&.modifiers || []) + [:crossed_out, :dim]).uniq))
else
row
end
end
widths = [
@tui.constraint_length(8),
@tui.constraint_length(15),
@tui.constraint_length(10),
]
row_highlight_style = @tui.style(fg: :yellow)
current_style_entry = @styles[@current_style_index]
current_spacing_entry = HIGHLIGHT_SPACINGS[@highlight_spacing_index]
offset_mode_entry = OFFSET_MODES[@offset_mode_index]
flex_mode_entry = FLEX_MODES[@flex_mode_index]
effective_selection = offset_mode_entry[:allow_selection] ? @selected_index : nil
effective_offset = offset_mode_entry[:offset]
selection_label = effective_selection.nil? ? "none" : effective_selection.to_s
offset_label = effective_offset.nil? ? "auto" : effective_offset.to_s
header_label = @show_header ? "On" : "Off"
table = @tui.table(
header: @show_header ? ["PID", "Name", "CPU"] : nil,
rows:,
widths:,
selected_row: effective_selection,
selected_column: @selected_col,
offset: effective_offset,
row_highlight_style:,
highlight_symbol: "> ",
highlight_spacing: current_spacing_entry[:spacing],
column_highlight_style: @show_column_highlight ? @column_highlight_style : nil,
cell_highlight_style: @show_cell_highlight ? @cell_highlight_style : nil,
style: current_style_entry[:style],
column_spacing: @column_spacing,
flex: flex_mode_entry[:flex],
block: @tui.block(
title: "Processes | Sel: #{selection_label} | Offset: #{offset_label} | Flex: #{flex_mode_entry[:name]}",
borders: :all
),
footer: ["Total: #{PROCESSES.length}", "Total CPU: #{PROCESSES.sum { |p| p[:cpu] }}%", ""]
)
control_panel = @tui.block(
title: "Controls",
borders: [:all],
children: [
@tui.paragraph(
text: [
@tui.text_line(spans: [
@tui.text_span(content: "↑/↓", style: @hotkey_style),
@tui.text_span(content: ": Nav Row "),
@tui.text_span(content: "←/→", style: @hotkey_style),
@tui.text_span(content: ": Nav Col "),
@tui.text_span(content: "x", style: @hotkey_style),
@tui.text_span(content: ": Toggle Row (#{selection_label}) "),
@tui.text_span(content: "q", style: @hotkey_style),
@tui.text_span(content: ": Quit"),
]),
@tui.text_line(spans: [
@tui.text_span(content: "s", style: @hotkey_style),
@tui.text_span(content: ": Style (#{current_style_entry[:name]}) "),
@tui.text_span(content: "p", style: @hotkey_style),
@tui.text_span(content: ": Spacing (#{current_spacing_entry[:name]}) "),
@tui.text_span(content: "t", style: @hotkey_style),
@tui.text_span(content: ": Tamp Row"),
]),
@tui.text_line(spans: [
@tui.text_span(content: "+/-", style: @hotkey_style),
@tui.text_span(content: ": Col Space (#{@column_spacing}) "),
@tui.text_span(content: "c", style: @hotkey_style),
@tui.text_span(content: ": Col Highlight (#{@show_column_highlight ? 'On' : 'Off'}) "),
@tui.text_span(content: "f", style: @hotkey_style),
@tui.text_span(content: ": Flex Mode (#{flex_mode_entry[:name]})"),
]),
@tui.text_line(spans: [
@tui.text_span(content: "z", style: @hotkey_style),
@tui.text_span(content: ": Cell Highlight (#{@show_cell_highlight ? 'On' : 'Off'}) "),
@tui.text_span(content: "o", style: @hotkey_style),
@tui.text_span(content: ": Offset Mode (#{offset_mode_entry[:name]}) "),
@tui.text_span(content: "d", style: @hotkey_style),
@tui.text_span(content: ": Header (#{header_label})"),
]),
]
),
]
)
layout = @tui.layout_split(
frame.area,
direction: :vertical,
constraints: [
@tui.constraint_fill(1),
@tui.constraint_length(6),
]
)
frame.render_widget(table, layout[0])
frame.render_widget(control_panel, layout[1])
end
private def handle_input
event = @tui.poll_event
case event
in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
:quit
in type: :key, code: "down" | "j"
@selected_index = ((@selected_index || -1) + 1) % PROCESSES.length
in type: :key, code: "up" | "k"
@selected_index = (@selected_index || 0) - 1
@selected_index = PROCESSES.length - 1 if @selected_index.negative?
in type: :key, code: "right" | "l"
@selected_col = ((@selected_col || -1) + 1) % 3
in type: :key, code: "left" | "h"
@selected_col = (@selected_col || 0) - 1
@selected_col = 2 if @selected_col.negative?
in type: :key, code: "s"
@current_style_index = (@current_style_index + 1) % @styles.length
in type: :key, code: "+"
@column_spacing += 1
in type: :key, code: "-"
@column_spacing = [@column_spacing - 1, 0].max
in type: :key, code: "p"
@highlight_spacing_index = (@highlight_spacing_index + 1) % HIGHLIGHT_SPACINGS.length
in type: :key, code: "x"
@selected_index = @selected_index.nil? ? 0 : nil
in type: :key, code: "t"
if @selected_index
pid = PROCESSES[@selected_index][:pid]
if @strikethrough_pids.include?(pid)
@strikethrough_pids.delete(pid)
else
@strikethrough_pids.add(pid)
end
end
in type: :key, code: "c"
@show_column_highlight = !@show_column_highlight
in type: :key, code: "z"
@show_cell_highlight = !@show_cell_highlight
in type: :key, code: "o"
@offset_mode_index = (@offset_mode_index + 1) % OFFSET_MODES.length
in type: :key, code: "f"
@flex_mode_index = (@flex_mode_index + 1) % FLEX_MODES.length
in type: :key, code: "d"
@show_header = !@show_header
else
nil
end
end
end
if __FILE__ == $0
WidgetTable.new.run
end