Class: Legion::TTY::App

Inherits:
Object
  • Object
show all
Extended by:
Logging::Helper
Includes:
Logging::Helper
Defined in:
lib/legion/tty/app.rb

Overview

rubocop:disable Metrics/ClassLength

Constant Summary collapse

CONFIG_DIR =
File.expand_path('~/.legionio/settings')
KEY_MAP =

Key normalization: raw escape sequences and control chars to symbols

{
  "\e[A" => :up, "\e[B" => :down, "\e[C" => :right, "\e[D" => :left,
  "\r" => :enter, "\n" => :enter, "\e" => :escape,
  "\e[5~" => :page_up, "\e[6~" => :page_down,
  "\e[H" => :home, "\eOH" => :home, "\e[F" => :end, "\eOF" => :end,
  "\e[1~" => :home, "\e[4~" => :end,
  "\x7f" => :backspace, "\b" => :backspace, "\t" => :tab,
  "\x03" => :ctrl_c, "\x04" => :ctrl_d,
  "\x01" => :ctrl_a, "\x02" => :ctrl_b,
  "\x05" => :ctrl_e, "\x06" => :ctrl_f,
  "\x0B" => :ctrl_k, "\x0C" => :ctrl_l, "\x13" => :ctrl_s,
  "\x15" => :ctrl_u
}.freeze
ENABLE_ALT_SCREEN =
"\e[?1049h"
DISABLE_ALT_SCREEN =
"\e[?1049l"
ENABLE_MOUSE =
"\e[?1000h\e[?1006h"
DISABLE_MOUSE =
"\e[?1000h\e[?1006l"
ENABLE_BRACKETED_PASTE =
"\e[?2004h"
DISABLE_BRACKETED_PASTE =
"\e[?2004l"
SGR_MOUSE_RE =
/\A\e\[<(\d+);(\d+);(\d+)([Mm])\z/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_dir: CONFIG_DIR, skip_rain: false) ⇒ App

Returns a new instance of App.



68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/legion/tty/app.rb', line 68

def initialize(config_dir: CONFIG_DIR, skip_rain: false)
  @config_dir = config_dir
  @skip_rain = skip_rain
  @config = load_config
  @credentials = load_credentials
  @screen_manager = ScreenManager.new
  @hotkeys = Hotkeys.new
  @llm_chat = nil
  @input_bar = nil
  @running = false
  @prev_frame = []
  @raw_mode = false
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



47
48
49
# File 'lib/legion/tty/app.rb', line 47

def config
  @config
end

#credentialsObject (readonly)

Returns the value of attribute credentials.



47
48
49
# File 'lib/legion/tty/app.rb', line 47

def credentials
  @credentials
end

#hotkeysObject (readonly)

Returns the value of attribute hotkeys.



47
48
49
# File 'lib/legion/tty/app.rb', line 47

def hotkeys
  @hotkeys
end

#input_barObject (readonly)

Returns the value of attribute input_bar.



47
48
49
# File 'lib/legion/tty/app.rb', line 47

def input_bar
  @input_bar
end

#llm_chatObject (readonly)

Returns the value of attribute llm_chat.



47
48
49
# File 'lib/legion/tty/app.rb', line 47

def llm_chat
  @llm_chat
end

#screen_managerObject (readonly)

Returns the value of attribute screen_manager.



47
48
49
# File 'lib/legion/tty/app.rb', line 47

def screen_manager
  @screen_manager
end

Class Method Details

.first_run?(config_dir: CONFIG_DIR) ⇒ Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/legion/tty/app.rb', line 64

def self.first_run?(config_dir: CONFIG_DIR)
  !File.exist?(File.join(config_dir, 'identity.json'))
end

.parse_argv(argv) ⇒ Object



58
59
60
61
62
# File 'lib/legion/tty/app.rb', line 58

def self.parse_argv(argv)
  opts = {}
  opts[:skip_rain] = true if argv.include?('--skip-rain')
  opts
end

.run(argv = []) ⇒ Object



49
50
51
52
53
54
55
56
# File 'lib/legion/tty/app.rb', line 49

def self.run(argv = [])
  opts = parse_argv(argv)
  app = new(**opts)
  app.start
rescue Interrupt => e
  log.debug { "app interrupted: #{e.message}" }
  app&.shutdown
end

Instance Method Details

#render_frameObject

Public: called by screens (e.g., Chat during LLM streaming) to force a re-render rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/legion/tty/app.rb', line 91

def render_frame
  width = terminal_width
  height = terminal_height
  active = @screen_manager.active_screen
  return unless active

  has_input = active.respond_to?(:needs_input_bar?) && active.needs_input_bar?
  screen_height = has_input ? height - 1 : height

  lines = active.render(width, screen_height)
  lines << @input_bar.render_line(width: width) if has_input && @input_bar

  lines = lines[0, height] if lines.size > height
  lines += Array.new(height - lines.size, '') if lines.size < height

  lines = composite_overlay(lines, width, height) if @screen_manager.overlay

  write_differential(lines, width)

  if has_input && @input_bar
    col = [@input_bar.cursor_column, width - 1].min
    $stdout.print cursor.move_to(col, height - 1)
  end

  $stdout.flush
rescue StandardError => e
  log.warn { "render_frame failed: #{e.message}" }
end

#shutdownObject



142
143
144
145
# File 'lib/legion/tty/app.rb', line 142

def shutdown
  @running = false
  @screen_manager.teardown_all
end

#startObject



82
83
84
85
86
87
# File 'lib/legion/tty/app.rb', line 82

def start
  setup_hotkeys
  run_onboarding if self.class.first_run?(config_dir: @config_dir)
  setup_for_chat
  run_loop
end

#toggle_dashboardObject



131
132
133
134
135
136
137
138
139
140
# File 'lib/legion/tty/app.rb', line 131

def toggle_dashboard
  active = @screen_manager.active_screen
  if active.is_a?(Screens::Dashboard)
    @screen_manager.pop
  else
    require_relative 'screens/dashboard'
    dashboard = Screens::Dashboard.new(self)
    @screen_manager.push(dashboard)
  end
end

#with_cooked_modeObject

Temporarily exit raw mode for blocking prompts (TTY::Prompt, etc.)



122
123
124
125
126
127
128
129
# File 'lib/legion/tty/app.rb', line 122

def with_cooked_mode(&)
  return yield unless @raw_mode

  $stdout.print cursor.show
  $stdin.cooked(&)
  $stdout.print cursor.hide
  @prev_frame = []
end