Class: SwarmCLI::V3::Display

Inherits:
Object
  • Object
show all
Defined in:
lib/swarm_cli/v3/display.rb

Overview

Mutex-protected coordinator for terminal output.

Owns a TextInput and an ActivityIndicator, and ensures all screen writes are serialized so agent events and user keystrokes never visually collide. The input line (prompt + buffer) is always visible at the bottom; agent output “slides in” above it.

The footer is composed of (top to bottom):

1. Optional working indicator (blank line + "Working..." line)
2. Horizontal separator
3. Prompt line (may wrap and span multiple lines)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io: $stdout, width: nil) ⇒ Display

Returns a new instance of Display.

Parameters:

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

    output stream (defaults to $stdout, use StringIO for tests)

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

    terminal width override (nil = auto-detect)



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/swarm_cli/v3/display.rb', line 22

def initialize(io: $stdout, width: nil)
  @io = io
  @width_override = width
  @mutex = Mutex.new
  @active = false
  @text_input = TextInput.new
  @indicator = ActivityIndicator.new(on_tick: -> { tick })
  # Tracks which row of the prompt area the terminal cursor is on
  # (0 = first line of prompt, relative to prompt start).
  # Used by reposition_cursor to navigate from current position.
  @terminal_cursor_row = 0
  @hint_message = nil
  @hint_timer = nil
  @status_message = nil # Persistent status message
  @dropdown = nil # Dropdown instance when autocomplete is active
end

Instance Attribute Details

#text_inputTextInput (readonly)

Returns the text input model (read-only access for reader).

Returns:

  • (TextInput)

    the text input model (read-only access for reader)



18
19
20
# File 'lib/swarm_cli/v3/display.rb', line 18

def text_input
  @text_input
end

Instance Method Details

#activatevoid

This method returns an undefined value.

Start showing the input footer.



42
43
44
45
46
47
# File 'lib/swarm_cli/v3/display.rb', line 42

def activate
  @mutex.synchronize do
    @active = true
    redraw_footer
  end
end

#agent_print(text) ⇒ void

This method returns an undefined value.

Print agent output above the always-visible footer.

Parameters:

  • text (String)

    output text to display



416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/swarm_cli/v3/display.rb', line 416

def agent_print(text)
  @mutex.synchronize do
    if @active
      clear_footer
      write(text)
      write("\n") unless text.end_with?("\n")
      redraw_footer
    else
      write(text)
      write("\n") unless text.end_with?("\n")
    end
    flush
  end
end

#backspacevoid

This method returns an undefined value.

Handle a backspace keypress.



118
119
120
121
122
123
124
125
126
# File 'lib/swarm_cli/v3/display.rb', line 118

def backspace
  @mutex.synchronize do
    old_lines = @text_input.wrapped_lines(terminal_width)
    @text_input.backspace

    # Always redraw to ensure bottom separator is present
    redraw_prompt_with_separator_from(old_lines)
  end
end

#clear_hintvoid

This method returns an undefined value.

Clear the hint message immediately.



246
247
248
249
250
251
252
253
254
255
256
# File 'lib/swarm_cli/v3/display.rb', line 246

def clear_hint
  return unless @hint_message

  clear_hint_timer
  @hint_message = nil

  if @active
    old_lines = @text_input.wrapped_lines(terminal_width)
    redraw_prompt_with_separator_from(old_lines)
  end
end

#clear_inputvoid

This method returns an undefined value.

Clear the input buffer (for double-ESC).



203
204
205
206
207
208
209
210
211
# File 'lib/swarm_cli/v3/display.rb', line 203

def clear_input
  @mutex.synchronize do
    old_lines = @text_input.wrapped_lines(terminal_width)
    @text_input.replace("")
    @hint_message = nil # Clear hint directly
    clear_hint_timer
    redraw_prompt_with_separator_from(old_lines) if @active
  end
end

#clear_statusvoid

This method returns an undefined value.

Clear the persistent status message.



277
278
279
280
281
282
283
284
285
286
# File 'lib/swarm_cli/v3/display.rb', line 277

def clear_status
  return unless @status_message

  @status_message = nil

  if @active
    old_lines = @text_input.wrapped_lines(terminal_width)
    redraw_prompt_with_separator_from(old_lines)
  end
end

#current_bufferString

Return a copy of the current input buffer (thread-safe).

Returns:

  • (String)


408
409
410
# File 'lib/swarm_cli/v3/display.rb', line 408

def current_buffer
  @mutex.synchronize { @text_input.buffer.dup }
end

#deactivatevoid

This method returns an undefined value.

Stop showing the input footer and clear it from the screen.



52
53
54
55
56
57
58
59
# File 'lib/swarm_cli/v3/display.rb', line 52

def deactivate
  @mutex.synchronize do
    return unless @active

    clear_footer
    @active = false
  end
end

Get selected item and close dropdown.

Returns:

  • (String, nil)

    the selected item or nil if no dropdown active



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/swarm_cli/v3/display.rb', line 340

def dropdown_accept
  @mutex.synchronize do
    return unless @dropdown

    selected = @dropdown.selected_item
    @dropdown = nil

    if @active
      old_lines = @text_input.wrapped_lines(terminal_width)
      redraw_prompt_with_separator_from(old_lines)
    end

    selected
  end
end

Get first item and close dropdown (for Tab behavior).

Returns:

  • (String, nil)

    the first item or nil if no dropdown active



359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/swarm_cli/v3/display.rb', line 359

def dropdown_accept_first
  @mutex.synchronize do
    return unless @dropdown

    first = @dropdown.first_item
    @dropdown = nil

    if @active
      old_lines = @text_input.wrapped_lines(terminal_width)
      redraw_prompt_with_separator_from(old_lines)
    end

    first
  end
end

Check whether dropdown is active.

Returns:

  • (Boolean)

    true if dropdown is shown



394
395
396
# File 'lib/swarm_cli/v3/display.rb', line 394

def dropdown_active?
  @mutex.synchronize { !@dropdown.nil? }
end

This method returns an undefined value.

Close dropdown without selection.



378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/swarm_cli/v3/display.rb', line 378

def dropdown_close
  @mutex.synchronize do
    return unless @dropdown

    @dropdown = nil

    if @active
      old_lines = @text_input.wrapped_lines(terminal_width)
      redraw_prompt_with_separator_from(old_lines)
    end
  end
end

Get dropdown items if dropdown is active.

Returns:

  • (Array<String>, nil)

    dropdown items or nil if no dropdown



401
402
403
# File 'lib/swarm_cli/v3/display.rb', line 401

def dropdown_items
  @mutex.synchronize { @dropdown&.items }
end

This method returns an undefined value.

Navigate dropdown selection to next item.



308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/swarm_cli/v3/display.rb', line 308

def dropdown_select_next
  @mutex.synchronize do
    return unless @dropdown

    @dropdown.select_next

    if @active
      old_lines = @text_input.wrapped_lines(terminal_width)
      redraw_prompt_with_separator_from(old_lines)
    end
  end
end

This method returns an undefined value.

Navigate dropdown selection to previous item.



324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/swarm_cli/v3/display.rb', line 324

def dropdown_select_previous
  @mutex.synchronize do
    return unless @dropdown

    @dropdown.select_previous

    if @active
      old_lines = @text_input.wrapped_lines(terminal_width)
      redraw_prompt_with_separator_from(old_lines)
    end
  end
end

#enterString

Finalize the current input line and return the buffer text.

Returns:

  • (String)

    the submitted text



177
178
179
180
181
182
183
184
185
186
# File 'lib/swarm_cli/v3/display.rb', line 177

def enter
  @mutex.synchronize do
    line = @text_input.submit
    clear_footer if @active
    write("#{@text_input.render}#{line}\n")
    redraw_footer if @active
    flush
    line
  end
end

#move_downBoolean

Move cursor down one line within multiline buffer.

Returns:

  • (Boolean)

    true if cursor moved, false if already on last line



164
165
166
167
168
169
170
171
172
# File 'lib/swarm_cli/v3/display.rb', line 164

def move_down
  @mutex.synchronize do
    old = @text_input.cursor
    @text_input.move_down
    moved = @text_input.cursor != old
    reposition_cursor if moved
    moved
  end
end

#move_leftvoid

This method returns an undefined value.

Move cursor left.



131
132
133
134
135
136
# File 'lib/swarm_cli/v3/display.rb', line 131

def move_left
  @mutex.synchronize do
    @text_input.move_left
    reposition_cursor
  end
end

#move_rightvoid

This method returns an undefined value.

Move cursor right.



141
142
143
144
145
146
# File 'lib/swarm_cli/v3/display.rb', line 141

def move_right
  @mutex.synchronize do
    @text_input.move_right
    reposition_cursor
  end
end

#move_upBoolean

Move cursor up one line within multiline buffer.

Returns:

  • (Boolean)

    true if cursor moved, false if already on first line



151
152
153
154
155
156
157
158
159
# File 'lib/swarm_cli/v3/display.rb', line 151

def move_up
  @mutex.synchronize do
    old = @text_input.cursor
    @text_input.move_up
    moved = @text_input.cursor != old
    reposition_cursor if moved
    moved
  end
end

#replace_buffer(text) ⇒ void

This method returns an undefined value.

Replace the buffer contents (for history navigation).

Parameters:

  • text (String, nil)

    replacement text



192
193
194
195
196
197
198
# File 'lib/swarm_cli/v3/display.rb', line 192

def replace_buffer(text)
  @mutex.synchronize do
    old_lines = @text_input.wrapped_lines(terminal_width)
    @text_input.replace(text)
    redraw_prompt_with_separator_from(old_lines) if @active
  end
end

#show_dropdown(dropdown) ⇒ void

This method returns an undefined value.

Show dropdown in status area.

Parameters:

  • dropdown (Dropdown)

    the dropdown to display



292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/swarm_cli/v3/display.rb', line 292

def show_dropdown(dropdown)
  @mutex.synchronize do
    @dropdown = dropdown
    @hint_message = nil    # Clear hints when dropdown shows
    @status_message = nil  # Clear status when dropdown shows

    if @active
      old_lines = @text_input.wrapped_lines(terminal_width)
      redraw_prompt_with_separator_from(old_lines)
    end
  end
end

#show_hint(message, duration: 1.5) ⇒ void

This method returns an undefined value.

Show a temporary hint message below the input area.

Parameters:

  • message (String)

    the hint text to display

  • duration (Float) (defaults to: 1.5)

    seconds to show the hint (default 1.5)



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/swarm_cli/v3/display.rb', line 218

def show_hint(message, duration: 1.5)
  @mutex.synchronize do
    clear_hint_timer
    @hint_message = message

    if @active
      old_lines = @text_input.wrapped_lines(terminal_width)
      redraw_prompt_with_separator_from(old_lines)
    end

    @hint_timer = Thread.new do
      sleep(duration)
      @mutex.synchronize do
        @hint_message = nil
        @hint_timer = nil

        if @active
          old_lines = @text_input.wrapped_lines(terminal_width)
          redraw_prompt_with_separator_from(old_lines)
        end
      end
    end
  end
end

#show_status(message) ⇒ void

This method returns an undefined value.

Show a persistent status message in the status area. Unlike hints, status messages don’t auto-clear.

Parameters:

  • message (String)

    the status text to display



263
264
265
266
267
268
269
270
271
272
# File 'lib/swarm_cli/v3/display.rb', line 263

def show_status(message)
  @mutex.synchronize do
    @status_message = message

    if @active
      old_lines = @text_input.wrapped_lines(terminal_width)
      redraw_prompt_with_separator_from(old_lines)
    end
  end
end

#start_workingvoid

This method returns an undefined value.

Signal that the agent is working.



64
65
66
67
68
69
70
71
72
73
74
# File 'lib/swarm_cli/v3/display.rb', line 64

def start_working
  @mutex.synchronize do
    if @active
      clear_footer
      @indicator.start
      redraw_footer
    else
      @indicator.start
    end
  end
end

#stop_workingvoid

This method returns an undefined value.

Signal that the agent has finished working.



91
92
93
94
95
96
97
98
99
# File 'lib/swarm_cli/v3/display.rb', line 91

def stop_working
  @mutex.synchronize do
    return unless @indicator.working?

    clear_footer if @active
    @indicator.stop
    redraw_footer if @active
  end
end

#type_char(char) ⇒ void

This method returns an undefined value.

Echo a typed character at the cursor position.

Parameters:

  • char (String)

    single character



105
106
107
108
109
110
111
112
113
# File 'lib/swarm_cli/v3/display.rb', line 105

def type_char(char)
  @mutex.synchronize do
    old_lines = @text_input.wrapped_lines(terminal_width)
    @text_input.type_char(char)

    # Always redraw to ensure bottom separator is present
    redraw_prompt_with_separator_from(old_lines)
  end
end

#update_activity(text) ⇒ void

This method returns an undefined value.

Update the activity indicator status suffix.

Parameters:

  • text (String, nil)

    status text to show



80
81
82
83
84
85
86
# File 'lib/swarm_cli/v3/display.rb', line 80

def update_activity(text)
  @mutex.synchronize do
    return unless @indicator.working?

    @indicator.status = text
  end
end