Class: Hawktui::StreamingTable

Inherits:
Object
  • Object
show all
Defined in:
lib/hawktui/streaming_table.rb,
lib/hawktui/streaming_table/cell.rb,
lib/hawktui/streaming_table/column.rb,
lib/hawktui/streaming_table/layout.rb

Overview

Public: Streams a dynamic table to the terminal, handling user input such as pausing/unpausing and quitting. Continually appends new rows at the top of the table and enforces a maximum row limit.

Examples

columns = [
  { name: :timestamp, width: 20 },
  { name: :message,   width: 50 },
]
table = Hawktui::StreamingTable.new(columns: columns, max_rows: 1000)
table.start

# In a separate thread or async process:
table.add_row(timestamp: Time.now.to_s, message: "Hello, world!")

WARNING: The StreamingTable must run in the main thread and table.add_row should be called from a separate thread or async process. This is because the table uses curses, which is not thread-safe and does not respond to user input in a separate thread.

Defined Under Namespace

Classes: Cell, Column, Layout

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(columns: {}, max_rows: 100_000) ⇒ StreamingTable

Public: Create a new StreamingTable.

columns - An Array of Hashes or Hawktui::StreamingTable::Column objects that define the table’s

columns. Each element should at least contain `:name` and `:width`.

max_rows - The maximum number of rows to keep in the table. Defaults to 100000.

Examples

table = Hawktui::StreamingTable.new(columns: [{ name: :time, width: 10 }], max_rows: 500)

Returns a new StreamingTable instance.



43
44
45
46
47
48
49
50
# File 'lib/hawktui/streaming_table.rb', line 43

def initialize(columns: {}, max_rows: 100_000)
  @layout = Layout.new(columns: columns)
  @max_rows = max_rows
  @rows = [] # Store rows newest-first
  @paused = false
  @input_thread = nil
  @should_exit = false
end

Instance Attribute Details

#layoutObject

Public accessors



53
54
55
# File 'lib/hawktui/streaming_table.rb', line 53

def layout
  @layout
end

#max_rowsObject (readonly)

Public accessors



53
54
55
# File 'lib/hawktui/streaming_table.rb', line 53

def max_rows
  @max_rows
end

#pausedObject (readonly)

Public accessors



53
54
55
# File 'lib/hawktui/streaming_table.rb', line 53

def paused
  @paused
end

#rowsObject (readonly)

Public accessors



53
54
55
# File 'lib/hawktui/streaming_table.rb', line 53

def rows
  @rows
end

#should_exitObject (readonly)

Public accessors



53
54
55
# File 'lib/hawktui/streaming_table.rb', line 53

def should_exit
  @should_exit
end

#winObject

Returns the value of attribute win.



54
55
56
# File 'lib/hawktui/streaming_table.rb', line 54

def win
  @win
end

Instance Method Details

#add_row(row_data) ⇒ Object

Public: Add a new row of data to the top of the table. If the table has reached its maximum row limit, the oldest row is dropped.

row_data - A Hash of row data where keys match column names. Values can be

raw (String, Integer, etc.) or a Hash with :value and :color.

Examples

table.add_row(timestamp: "2025-01-01 12:00", message: { value: "New Year!", color: :red })

Returns nothing.



111
112
113
114
115
116
117
# File 'lib/hawktui/streaming_table.rb', line 111

def add_row(row_data)
  return unless win

  rows.unshift(row_data)
  rows.pop if rows.size > max_rows
  draw unless paused
end

#drawObject

Internal: Draw the entire table (header, rows, status line).

Returns nothing.



178
179
180
181
182
183
184
185
186
# File 'lib/hawktui/streaming_table.rb', line 178

def draw
  return unless win

  win.clear
  draw_header
  draw_body
  draw_footer
  win.refresh
end

#draw_bodyObject

Internal: Draw the body of the table (rows of data).

Returns nothing.



202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/hawktui/streaming_table.rb', line 202

def draw_body
  max_display_rows = Curses.lines - 2
  display_rows = rows.first(max_display_rows)

  display_rows.each_with_index do |row_data, idx|
    cells = layout.build_cells_for_row(row_data)
    formatted_cells = cells.zip(layout.columns).map do |cell, column|
      column.format_cell(cell)
    end
    draw_row(idx + 1, formatted_cells)
  end
end

Internal: Draw the status line at the bottom of the screen.

Returns nothing.



218
219
220
221
222
223
224
225
226
# File 'lib/hawktui/streaming_table.rb', line 218

def draw_footer
  return unless win

  win.setpos(Curses.lines - 1, 0)
  status = paused ? "PAUSED" : "RUNNING"
  help_text = " | Press 'p' to pause/unpause, 'q' to quit"
  win.addstr("Status: #{status}#{help_text}".ljust(Curses.cols))
  win.refresh
end

#draw_headerObject

Internal: Draw the header row of the table.

Returns nothing.



191
192
193
194
195
196
197
# File 'lib/hawktui/streaming_table.rb', line 191

def draw_header
  header_cells = layout.build_header_row
  formatted_header_cells = header_cells.zip(layout.columns).map do |cell, column|
    column.format_cell(cell)
  end
  draw_row(0, formatted_header_cells)
end

#draw_row(y_pos, formatted_cells) ⇒ Object

Internal: Draw a single row of the table, given already-formatted cells.

y_pos - The Integer row position (0-based) on the screen to draw. formatted_cells - An Array of [string_value, color], as returned by column formatting.

Returns nothing.



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/hawktui/streaming_table.rb', line 234

def draw_row(y_pos, formatted_cells)
  x_pos = 0
  formatted_cells.each do |str_value, color|
    win.setpos(y_pos, x_pos)

    if y_pos == 0
      # Bold the header row
      win.attron(Curses::A_BOLD) { draw_with_color(str_value, color) }
    else
      draw_with_color(str_value, color)
    end

    x_pos += str_value.length + 1
  end
end

#draw_with_color(str_value, color) ⇒ Object

Internal: Add a string to the screen, optionally with a color attribute.

str_value - The String text to draw. color - An Integer color index or Symbol referencing a base color in Colors.

Returns nothing.



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/hawktui/streaming_table.rb', line 256

def draw_with_color(str_value, color)
  if color
    color_index =
      case color
      when Integer
        color
      when Symbol, String
        Utils::Colors::BASE_COLORS[color.to_sym]&.first
      end

    if color_index
      win.attron(Curses.color_pair(color_index + 1)) { win.addstr(str_value) }
    else
      win.addstr(str_value)
    end
  else
    win.addstr(str_value)
  end
end

#handle_inputObject

Internal: Handle a single character of user input, toggling pause or stopping the table as appropriate.

Returns nothing.



156
157
158
159
160
161
162
163
164
# File 'lib/hawktui/streaming_table.rb', line 156

def handle_input
  case Curses.getch
  when "p"
    toggle_pause
  when "q"
    @should_exit = true
    stop
  end
end

#setupObject

Internal: Set up curses, initialize colors, etc. Called by #start.

Returns nothing.



122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/hawktui/streaming_table.rb', line 122

def setup
  Curses.init_screen
  Utils::Colors.setup_colors
  Curses.start_color
  Curses.noecho
  Curses.curs_set(0)
  Curses.stdscr.keypad(true)
  Curses.timeout = 0

  self.win = Curses.stdscr
  win.clear
end

#startObject

Public: Start the table UI. Initializes curses, sets up input handling, and draws the initial screen.

Examples

table.start

Returns nothing.



79
80
81
82
83
# File 'lib/hawktui/streaming_table.rb', line 79

def start
  setup
  start_input_handling
  draw
end

#start_input_handlingObject

Internal: Start a separate thread to handle user input (non-blocking).

Returns nothing.



138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/hawktui/streaming_table.rb', line 138

def start_input_handling
  @input_thread = Thread.new do
    loop do
      handle_input
      sleep 0.1
      break if should_exit
    end
  rescue => e
    win.setpos(0, 0)
    win.addstr("Error in input thread: #{e.message}")
    win.refresh
  end
end

#stopObject

Public: Stop the table UI. Stops the input thread, closes the curses screen, and exits the process.

Examples

table.stop

Returns nothing. Exits the process.



93
94
95
96
97
98
# File 'lib/hawktui/streaming_table.rb', line 93

def stop
  @input_thread&.exit
  Curses.close_screen if win
  self.win = nil
  Process.exit(0)
end

#toggle_pauseObject

Internal: Toggle whether the table is paused. When paused, new rows are still collected but not rendered until unpaused.

Returns nothing.



170
171
172
173
# File 'lib/hawktui/streaming_table.rb', line 170

def toggle_pause
  @paused = !paused
  draw_footer
end