kward 0.71.0 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +30 -0
- data/CHANGELOG.md +93 -0
- data/Gemfile.lock +2 -2
- data/README.md +4 -0
- data/doc/agent-tools.md +15 -6
- data/doc/authentication.md +22 -1
- data/doc/code-search.md +42 -2
- data/doc/configuration.md +106 -3
- data/doc/context-budgeting.md +136 -0
- data/doc/context-tools.md +16 -3
- data/doc/editor.md +415 -0
- data/doc/extensibility.md +16 -7
- data/doc/files.md +100 -0
- data/doc/getting-started.md +25 -18
- data/doc/git.md +123 -0
- data/doc/memory.md +24 -4
- data/doc/personas.md +34 -5
- data/doc/plugins.md +72 -1
- data/doc/releasing.md +37 -9
- data/doc/rpc.md +75 -5
- data/doc/session-management.md +35 -1
- data/doc/shell.md +332 -0
- data/doc/tabs.md +122 -0
- data/doc/troubleshooting.md +77 -1
- data/doc/usage.md +79 -7
- data/doc/web-search.md +12 -4
- data/doc/workspace-tools.md +51 -12
- data/examples/plugins/space_invaders.rb +377 -0
- data/lib/kward/agent.rb +1 -1
- data/lib/kward/ansi.rb +62 -23
- data/lib/kward/cli/commands.rb +33 -2
- data/lib/kward/cli/git.rb +150 -0
- data/lib/kward/cli/interactive_turn.rb +73 -9
- data/lib/kward/cli/plugins.rb +54 -4
- data/lib/kward/cli/prompt_interface.rb +32 -1
- data/lib/kward/cli/rendering.rb +4 -1
- data/lib/kward/cli/runtime_helpers.rb +268 -4
- data/lib/kward/cli/sessions.rb +2 -2
- data/lib/kward/cli/settings.rb +217 -9
- data/lib/kward/cli/slash_commands.rb +628 -2
- data/lib/kward/cli/tabs.rb +725 -0
- data/lib/kward/cli/tool_summaries.rb +6 -0
- data/lib/kward/cli.rb +150 -26
- data/lib/kward/clipboard.rb +2 -3
- data/lib/kward/compactor.rb +7 -19
- data/lib/kward/config_files.rb +145 -1
- data/lib/kward/context_budget_meter.rb +44 -0
- data/lib/kward/conversation.rb +12 -4
- data/lib/kward/editor_mode.rb +25 -0
- data/lib/kward/ekwsh.rb +559 -0
- data/lib/kward/image_attachments.rb +3 -1
- data/lib/kward/interactive_pty_runner.rb +151 -0
- data/lib/kward/local_command_runner.rb +155 -0
- data/lib/kward/local_pty_command_runner.rb +171 -0
- data/lib/kward/model/context_usage.rb +2 -2
- data/lib/kward/model/payloads.rb +2 -5
- data/lib/kward/plugin_registry.rb +61 -0
- data/lib/kward/project_files.rb +52 -0
- data/lib/kward/prompt_history.rb +84 -0
- data/lib/kward/prompt_interface/composer_controller.rb +69 -1
- data/lib/kward/prompt_interface/composer_renderer.rb +109 -13
- data/lib/kward/prompt_interface/composer_state.rb +96 -27
- data/lib/kward/prompt_interface/editor/auto_close_pairs.rb +123 -0
- data/lib/kward/prompt_interface/editor/auto_indent.rb +510 -0
- data/lib/kward/prompt_interface/editor/buffer.rb +109 -0
- data/lib/kward/prompt_interface/editor/controller.rb +1218 -0
- data/lib/kward/prompt_interface/editor/endwise.rb +321 -0
- data/lib/kward/prompt_interface/editor/file_marker.rb +40 -0
- data/lib/kward/prompt_interface/editor/indent_navigation.rb +61 -0
- data/lib/kward/prompt_interface/editor/kill_ring.rb +78 -0
- data/lib/kward/prompt_interface/editor/modes/emacs.rb +259 -0
- data/lib/kward/prompt_interface/editor/modes/modern.rb +354 -0
- data/lib/kward/prompt_interface/editor/modes/vibe.rb +1812 -0
- data/lib/kward/prompt_interface/editor/modes/vibe_insert_readline.rb +166 -0
- data/lib/kward/prompt_interface/editor/renderer.rb +244 -0
- data/lib/kward/prompt_interface/editor/search.rb +76 -0
- data/lib/kward/prompt_interface/editor/selections.rb +120 -0
- data/lib/kward/prompt_interface/editor/state.rb +1271 -0
- data/lib/kward/prompt_interface/editor/status_text.rb +23 -0
- data/lib/kward/prompt_interface/editor/syntax_highlighter.rb +422 -0
- data/lib/kward/prompt_interface/editor/undo_history.rb +46 -0
- data/lib/kward/prompt_interface/editor/vibe_state.rb +44 -0
- data/lib/kward/prompt_interface/file_overlay.rb +211 -0
- data/lib/kward/prompt_interface/git_prompt.rb +288 -0
- data/lib/kward/prompt_interface/interactive/controller.rb +186 -0
- data/lib/kward/prompt_interface/interactive/renderer.rb +71 -0
- data/lib/kward/prompt_interface/interactive/state.rb +62 -0
- data/lib/kward/prompt_interface/key_handler.rb +451 -57
- data/lib/kward/prompt_interface/overlay_renderer.rb +21 -2
- data/lib/kward/prompt_interface/project_browser.rb +524 -0
- data/lib/kward/prompt_interface/question_prompt.rb +99 -56
- data/lib/kward/prompt_interface/runtime_state.rb +43 -0
- data/lib/kward/prompt_interface/screen.rb +19 -3
- data/lib/kward/prompt_interface/selection_prompt.rb +10 -19
- data/lib/kward/prompt_interface/slash_overlay.rb +2 -0
- data/lib/kward/prompt_interface/stream_state.rb +7 -0
- data/lib/kward/prompt_interface/transcript_buffer.rb +6 -0
- data/lib/kward/prompt_interface.rb +366 -222
- data/lib/kward/prompts/commands.rb +9 -0
- data/lib/kward/prompts.rb +2 -0
- data/lib/kward/rpc/memory_methods.rb +83 -0
- data/lib/kward/rpc/server.rb +169 -83
- data/lib/kward/rpc/session_manager.rb +45 -121
- data/lib/kward/rpc/session_tree_rows.rb +9 -115
- data/lib/kward/rpc/tool_event_normalizer.rb +1 -1
- data/lib/kward/rpc/tool_metadata.rb +11 -0
- data/lib/kward/rpc/transcript_normalizer.rb +4 -39
- data/lib/kward/scratchpad_runner.rb +56 -0
- data/lib/kward/session_diff.rb +20 -3
- data/lib/kward/session_naming.rb +11 -0
- data/lib/kward/session_store.rb +44 -0
- data/lib/kward/session_tree_nodes.rb +136 -0
- data/lib/kward/session_tree_renderer.rb +9 -131
- data/lib/kward/tab_store.rb +47 -0
- data/lib/kward/terminal_keys.rb +84 -0
- data/lib/kward/terminal_sequences.rb +42 -0
- data/lib/kward/text_boundary.rb +25 -0
- data/lib/kward/tools/context_budget_stats.rb +54 -0
- data/lib/kward/tools/context_for_task.rb +204 -0
- data/lib/kward/tools/read_file.rb +8 -4
- data/lib/kward/tools/registry.rb +62 -16
- data/lib/kward/tools/tool_call.rb +10 -0
- data/lib/kward/version.rb +1 -1
- data/lib/kward/workers/git_guard.rb +93 -0
- data/lib/kward/workers/job.rb +99 -0
- data/lib/kward/workers/live_view.rb +49 -0
- data/lib/kward/workers/manager.rb +288 -0
- data/lib/kward/workers/queue_runner.rb +166 -0
- data/lib/kward/workers/queue_store.rb +112 -0
- data/lib/kward/workers/store.rb +72 -0
- data/lib/kward/workers/tool_policy.rb +23 -0
- data/lib/kward/workers/worker.rb +82 -0
- data/lib/kward/workers/write_lock.rb +38 -0
- data/lib/kward/workers.rb +10 -0
- data/lib/kward/workspace.rb +125 -87
- data/templates/default/fulldoc/html/css/kward.css +140 -36
- data/templates/default/fulldoc/html/images/kward_screen_1.png +0 -0
- data/templates/default/fulldoc/html/setup.rb +1 -0
- data/templates/default/kward_navigation.rb +12 -1
- data/templates/default/layout/html/layout.erb +23 -34
- data/templates/default/layout/html/setup.rb +6 -0
- metadata +67 -1
|
@@ -1,21 +1,46 @@
|
|
|
1
|
+
require "find"
|
|
1
2
|
require "io/console"
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "rbconfig"
|
|
2
5
|
require "thread"
|
|
6
|
+
require_relative "project_files"
|
|
7
|
+
require_relative "prompt_history"
|
|
3
8
|
require "tty-cursor"
|
|
4
9
|
require "tty-reader"
|
|
5
10
|
require "tty-screen"
|
|
6
11
|
require_relative "ansi"
|
|
12
|
+
require_relative "terminal_sequences"
|
|
13
|
+
require_relative "terminal_keys"
|
|
14
|
+
require_relative "editor_mode"
|
|
7
15
|
require_relative "prompt_interface/banner"
|
|
8
16
|
require_relative "prompt_interface/composer_state"
|
|
17
|
+
require_relative "prompt_interface/editor/state"
|
|
9
18
|
require_relative "prompt_interface/transcript_buffer"
|
|
10
19
|
require_relative "prompt_interface/transcript_renderer"
|
|
11
20
|
require_relative "prompt_interface/prompt_renderer"
|
|
12
21
|
require_relative "prompt_interface/stream_state"
|
|
13
22
|
require_relative "prompt_interface/slash_overlay"
|
|
23
|
+
require_relative "prompt_interface/file_overlay"
|
|
24
|
+
require_relative "prompt_interface/project_browser"
|
|
14
25
|
require_relative "prompt_interface/selection_prompt"
|
|
15
26
|
require_relative "prompt_interface/question_prompt"
|
|
27
|
+
require_relative "prompt_interface/git_prompt"
|
|
16
28
|
require_relative "prompt_interface/overlay_renderer"
|
|
29
|
+
require_relative "prompt_interface/editor/renderer"
|
|
30
|
+
require_relative "prompt_interface/editor/syntax_highlighter"
|
|
31
|
+
require_relative "prompt_interface/editor/auto_close_pairs"
|
|
32
|
+
require_relative "prompt_interface/editor/endwise"
|
|
33
|
+
require_relative "prompt_interface/editor/auto_indent"
|
|
17
34
|
require_relative "prompt_interface/composer_renderer"
|
|
18
35
|
require_relative "prompt_interface/composer_controller"
|
|
36
|
+
require_relative "prompt_interface/editor/modes/modern"
|
|
37
|
+
require_relative "prompt_interface/editor/modes/emacs"
|
|
38
|
+
require_relative "prompt_interface/editor/modes/vibe_insert_readline"
|
|
39
|
+
require_relative "prompt_interface/editor/modes/vibe"
|
|
40
|
+
require_relative "prompt_interface/editor/controller"
|
|
41
|
+
require_relative "prompt_interface/interactive/controller"
|
|
42
|
+
require_relative "prompt_interface/interactive/renderer"
|
|
43
|
+
require_relative "prompt_interface/interactive/state"
|
|
19
44
|
require_relative "prompt_interface/layout"
|
|
20
45
|
require_relative "prompt_interface/screen"
|
|
21
46
|
require_relative "prompt_interface/key_handler"
|
|
@@ -39,33 +64,51 @@ module Kward
|
|
|
39
64
|
SPINNER_FRAMES = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].freeze
|
|
40
65
|
SPINNER_INTERVAL = 0.1
|
|
41
66
|
FOOTER_REFRESH_INTERVAL = 1.0
|
|
67
|
+
COMPOSER_STATUS_REFRESH_INTERVAL = 1.0
|
|
42
68
|
COMPOSER_MAX_INPUT_ROWS = 6
|
|
43
69
|
TRANSCRIPT_BUFFER_LIMIT = 200_000
|
|
44
70
|
BANNER_MESSAGE = Banner::MESSAGE
|
|
45
71
|
|
|
46
72
|
include SlashOverlay
|
|
73
|
+
include FileOverlay
|
|
74
|
+
include ProjectBrowser
|
|
47
75
|
include SelectionPrompt
|
|
48
76
|
include QuestionPrompt
|
|
77
|
+
include GitPrompt
|
|
49
78
|
include OverlayRenderer
|
|
79
|
+
include EditorRenderer
|
|
80
|
+
include EditorSyntaxHighlighter
|
|
81
|
+
include EditorAutoClosePairs
|
|
82
|
+
include EditorEndwise
|
|
83
|
+
include EditorAutoIndent
|
|
50
84
|
include ComposerRenderer
|
|
51
85
|
include ComposerController
|
|
86
|
+
include ModernEditorMode
|
|
87
|
+
include EmacsEditorMode
|
|
88
|
+
include VibeInsertReadline
|
|
89
|
+
include VibeEditorMode
|
|
90
|
+
include EditorController
|
|
91
|
+
include InteractiveRenderer
|
|
92
|
+
include InteractiveState
|
|
52
93
|
include Layout
|
|
53
94
|
include Screen
|
|
54
95
|
include KeyHandler
|
|
55
96
|
include RuntimeState
|
|
56
97
|
include TranscriptRenderer
|
|
57
98
|
include PromptRenderer
|
|
58
|
-
KEYBOARD_PROTOCOL_ENABLE =
|
|
59
|
-
KEYBOARD_PROTOCOL_RESTORE =
|
|
60
|
-
BRACKETED_PASTE_ENABLE =
|
|
61
|
-
BRACKETED_PASTE_RESTORE =
|
|
62
|
-
BRACKETED_PASTE_START =
|
|
63
|
-
BRACKETED_PASTE_END =
|
|
64
|
-
SYNCHRONIZED_OUTPUT_ENABLE =
|
|
65
|
-
SYNCHRONIZED_OUTPUT_DISABLE =
|
|
66
|
-
CURSOR_SHOW =
|
|
67
|
-
CURSOR_HIDE =
|
|
68
|
-
|
|
99
|
+
KEYBOARD_PROTOCOL_ENABLE = TerminalSequences::KEYBOARD_PROTOCOL_ENABLE
|
|
100
|
+
KEYBOARD_PROTOCOL_RESTORE = TerminalSequences::KEYBOARD_PROTOCOL_RESTORE
|
|
101
|
+
BRACKETED_PASTE_ENABLE = TerminalSequences::BRACKETED_PASTE_ENABLE
|
|
102
|
+
BRACKETED_PASTE_RESTORE = TerminalSequences::BRACKETED_PASTE_RESTORE
|
|
103
|
+
BRACKETED_PASTE_START = TerminalSequences::BRACKETED_PASTE_START
|
|
104
|
+
BRACKETED_PASTE_END = TerminalSequences::BRACKETED_PASTE_END
|
|
105
|
+
SYNCHRONIZED_OUTPUT_ENABLE = TerminalSequences::SYNCHRONIZED_OUTPUT_ENABLE
|
|
106
|
+
SYNCHRONIZED_OUTPUT_DISABLE = TerminalSequences::SYNCHRONIZED_OUTPUT_DISABLE
|
|
107
|
+
CURSOR_SHOW = TerminalSequences::CURSOR_SHOW
|
|
108
|
+
CURSOR_HIDE = TerminalSequences::CURSOR_HIDE
|
|
109
|
+
CURSOR_SHAPE_DEFAULT = TerminalSequences::CURSOR_SHAPE_DEFAULT
|
|
110
|
+
CURSOR_SHAPE_BAR = TerminalSequences::CURSOR_SHAPE_BAR
|
|
111
|
+
SHIFT_ENTER_SEQUENCES = TerminalKeys::SHIFT_ENTER
|
|
69
112
|
EXIT_INPUT = :exit_input
|
|
70
113
|
CANCEL_INPUT = :cancel_input
|
|
71
114
|
SELECT_CANCEL = :select_cancel
|
|
@@ -82,12 +125,14 @@ module Kward
|
|
|
82
125
|
end
|
|
83
126
|
end
|
|
84
127
|
|
|
85
|
-
def initialize(input: $stdin, output: $stdout, slash_commands: [], overlay_settings: nil, footer: nil, composer_status: nil, busy_help: true, attachment_badges: nil, attachment_parser: nil, banner_message: nil)
|
|
128
|
+
def initialize(input: $stdin, output: $stdout, slash_commands: [], overlay_settings: nil, footer: nil, composer_status: nil, busy_help: true, attachment_badges: nil, attachment_parser: nil, banner_message: nil, tab_keybindings: nil, prompt_history: nil, editor_mode: nil, editor_mode_source: nil, editor_auto_indent: true, editor_auto_indent_source: nil, editor_auto_close_pairs: true, editor_auto_close_pairs_source: nil, editor_soft_wrap: true, editor_soft_wrap_source: nil, editor_bar_cursor: true, editor_bar_cursor_source: nil, editor_line_numbers: "absolute", editor_line_numbers_source: nil)
|
|
86
129
|
@input_io = input
|
|
87
130
|
@output_io = output
|
|
88
131
|
@reader = TTY::Reader.new(input: input, output: output, interrupt: :error)
|
|
89
132
|
@mutex = Mutex.new
|
|
133
|
+
@prompt_history = prompt_history
|
|
90
134
|
@composer = ComposerState.new
|
|
135
|
+
load_history(@prompt_history.values) if @prompt_history
|
|
91
136
|
self.composer_input = @composer.input
|
|
92
137
|
self.composer_cursor = @composer.cursor
|
|
93
138
|
@started = false
|
|
@@ -99,6 +144,8 @@ module Kward
|
|
|
99
144
|
@spinner_frame_index = 0
|
|
100
145
|
@last_spinner_tick = monotonic_now
|
|
101
146
|
@last_footer_refresh = monotonic_now
|
|
147
|
+
@last_composer_status_refresh = 0.0
|
|
148
|
+
@cached_composer_status_text = nil
|
|
102
149
|
@prompt_label = "You>"
|
|
103
150
|
@assistant_label = "Assistant"
|
|
104
151
|
@stream_state = StreamState.new
|
|
@@ -109,18 +156,33 @@ module Kward
|
|
|
109
156
|
@transcript_viewport_rows = 0
|
|
110
157
|
@restoring_transcript = false
|
|
111
158
|
@pending_keys = []
|
|
159
|
+
@completion_provider = nil
|
|
112
160
|
@original_console_mode = nil
|
|
113
161
|
@raw_mode_active = false
|
|
114
162
|
@slash_commands = normalize_slash_commands(slash_commands)
|
|
115
163
|
@slash_selection_index = 0
|
|
116
164
|
@slash_overlay_dismissed_input = nil
|
|
165
|
+
@slash_overlay_disabled = false
|
|
166
|
+
@file_selection_index = 0
|
|
167
|
+
@file_overlay_dismissed_token = nil
|
|
168
|
+
@file_open_dismissed_token = nil
|
|
169
|
+
@file_editor_open_status = nil
|
|
170
|
+
@file_mention_paths = nil
|
|
171
|
+
@project_browser_state = nil
|
|
172
|
+
@project_browser_restore_after_editor = false
|
|
173
|
+
@editor_state = nil
|
|
174
|
+
@interactive_state = nil
|
|
175
|
+
@last_interactive_tick = monotonic_now
|
|
117
176
|
@select_state = nil
|
|
118
177
|
@question_state = nil
|
|
178
|
+
@question_prompt_active = false
|
|
179
|
+
@git_state = nil
|
|
119
180
|
@last_width = screen_width
|
|
120
181
|
@last_height = screen_height
|
|
121
182
|
@reserved_rows = 0
|
|
122
183
|
@color_enabled = ANSI.enabled?(output)
|
|
123
184
|
@cursor_visible = true
|
|
185
|
+
@editor_bar_cursor_active = false
|
|
124
186
|
@synchronized_output_depth = 0
|
|
125
187
|
@overlay_settings = normalize_overlay_settings(overlay_settings)
|
|
126
188
|
@footer = footer
|
|
@@ -129,6 +191,21 @@ module Kward
|
|
|
129
191
|
@attachment_badges = attachment_badges
|
|
130
192
|
@attachment_parser = attachment_parser
|
|
131
193
|
@banner = Banner.new(message: banner_message, screen_height: method(:screen_height))
|
|
194
|
+
@tabs = []
|
|
195
|
+
@active_tab_index = 0
|
|
196
|
+
@tab_keybindings = normalize_tab_keybindings(tab_keybindings)
|
|
197
|
+
@editor_mode = normalize_editor_mode(editor_mode)
|
|
198
|
+
@editor_mode_source = editor_mode_source
|
|
199
|
+
@editor_auto_indent = editor_auto_indent != false
|
|
200
|
+
@editor_auto_indent_source = editor_auto_indent_source
|
|
201
|
+
@editor_auto_close_pairs = editor_auto_close_pairs != false
|
|
202
|
+
@editor_auto_close_pairs_source = editor_auto_close_pairs_source
|
|
203
|
+
@editor_soft_wrap = editor_soft_wrap != false
|
|
204
|
+
@editor_soft_wrap_source = editor_soft_wrap_source
|
|
205
|
+
@editor_bar_cursor = editor_bar_cursor != false
|
|
206
|
+
@editor_bar_cursor_source = editor_bar_cursor_source
|
|
207
|
+
@editor_line_numbers = normalize_editor_line_numbers(editor_line_numbers)
|
|
208
|
+
@editor_line_numbers_source = editor_line_numbers_source
|
|
132
209
|
end
|
|
133
210
|
|
|
134
211
|
def start(render: true)
|
|
@@ -138,6 +215,7 @@ module Kward
|
|
|
138
215
|
enter_raw_mode_locked
|
|
139
216
|
@started = true
|
|
140
217
|
@asking = true
|
|
218
|
+
disable_editor_mouse_reporting(force: true) unless editor_active?
|
|
141
219
|
@output_io.print(KEYBOARD_PROTOCOL_ENABLE)
|
|
142
220
|
@output_io.print(BRACKETED_PASTE_ENABLE)
|
|
143
221
|
render_prompt_locked if render
|
|
@@ -150,8 +228,10 @@ module Kward
|
|
|
150
228
|
|
|
151
229
|
clear_prompt_for_output_locked
|
|
152
230
|
restore_scroll_region_locked
|
|
231
|
+
disable_editor_mouse_reporting(force: true)
|
|
153
232
|
@output_io.print(BRACKETED_PASTE_RESTORE)
|
|
154
233
|
@output_io.print(KEYBOARD_PROTOCOL_RESTORE)
|
|
234
|
+
restore_editor_cursor_shape_locked
|
|
155
235
|
set_cursor_visible_locked(true, force: true)
|
|
156
236
|
@output_io.puts
|
|
157
237
|
@output_io.flush
|
|
@@ -220,6 +300,79 @@ module Kward
|
|
|
220
300
|
end
|
|
221
301
|
end
|
|
222
302
|
|
|
303
|
+
def with_completion_provider(provider, slash_overlay: true)
|
|
304
|
+
previous_provider = @completion_provider
|
|
305
|
+
previous_slash_overlay_disabled = @slash_overlay_disabled
|
|
306
|
+
@completion_provider = provider
|
|
307
|
+
@slash_overlay_disabled = !slash_overlay
|
|
308
|
+
yield
|
|
309
|
+
ensure
|
|
310
|
+
@completion_provider = previous_provider
|
|
311
|
+
@slash_overlay_disabled = previous_slash_overlay_disabled
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def with_prompt_history(history)
|
|
315
|
+
previous_history = @prompt_history
|
|
316
|
+
@prompt_history = history
|
|
317
|
+
load_history(@prompt_history.values) if @prompt_history
|
|
318
|
+
yield
|
|
319
|
+
ensure
|
|
320
|
+
@prompt_history = previous_history
|
|
321
|
+
load_history(@prompt_history.values) if @prompt_history
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def editing_file?
|
|
325
|
+
@mutex.synchronize { editor_active? }
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def edit_file(path, base_dir: Dir.pwd, allow_new: true)
|
|
329
|
+
start(render: false)
|
|
330
|
+
opened = @mutex.synchronize do
|
|
331
|
+
open_editor(path, allow_new: allow_new, base_dir: base_dir, restrict_to_workspace: false).tap do
|
|
332
|
+
render_prompt_locked
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
return false unless opened
|
|
336
|
+
|
|
337
|
+
run_editor
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def scratchpad(language = :text)
|
|
341
|
+
start(render: false)
|
|
342
|
+
opened = @mutex.synchronize do
|
|
343
|
+
open_scratchpad(language).tap do
|
|
344
|
+
render_prompt_locked
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
return false unless opened
|
|
348
|
+
|
|
349
|
+
run_editor
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def run_editor
|
|
353
|
+
loop do
|
|
354
|
+
key = read_key(nonblock: true)
|
|
355
|
+
action = nil
|
|
356
|
+
editor_open = @mutex.synchronize do
|
|
357
|
+
if key.nil?
|
|
358
|
+
resized = handle_resize_locked
|
|
359
|
+
footer_refreshed = tick_footer_locked
|
|
360
|
+
render_prompt_locked if resized || footer_refreshed
|
|
361
|
+
else
|
|
362
|
+
result = handle_key(key)
|
|
363
|
+
action = result if prompt_action_result?(result)
|
|
364
|
+
render_prompt_locked unless result.is_a?(String) || result == EXIT_INPUT || prompt_action_result?(result)
|
|
365
|
+
end
|
|
366
|
+
editor_active?
|
|
367
|
+
end
|
|
368
|
+
return action if action
|
|
369
|
+
break unless editor_open
|
|
370
|
+
|
|
371
|
+
sleep 0.02 if key.nil?
|
|
372
|
+
end
|
|
373
|
+
true
|
|
374
|
+
end
|
|
375
|
+
|
|
223
376
|
def ask(message = "You>")
|
|
224
377
|
was_composing = @started && @asking
|
|
225
378
|
start
|
|
@@ -250,10 +403,10 @@ module Kward
|
|
|
250
403
|
render_prompt_locked if resized || footer_refreshed
|
|
251
404
|
else
|
|
252
405
|
result = handle_key(key)
|
|
253
|
-
render_prompt_locked unless result.is_a?(String) || result == EXIT_INPUT
|
|
406
|
+
render_prompt_locked unless result.is_a?(String) || result == EXIT_INPUT || prompt_action_result?(result)
|
|
254
407
|
end
|
|
255
408
|
end
|
|
256
|
-
return result if result.is_a?(String)
|
|
409
|
+
return result if result.is_a?(String) || prompt_action_result?(result)
|
|
257
410
|
return nil if result == EXIT_INPUT
|
|
258
411
|
|
|
259
412
|
sleep 0.02 if key.nil?
|
|
@@ -323,7 +476,10 @@ module Kward
|
|
|
323
476
|
start
|
|
324
477
|
saved_state = nil
|
|
325
478
|
answers = []
|
|
326
|
-
@mutex.synchronize
|
|
479
|
+
@mutex.synchronize do
|
|
480
|
+
@question_prompt_active = true
|
|
481
|
+
saved_state = begin_question_prompt_state
|
|
482
|
+
end
|
|
327
483
|
|
|
328
484
|
questions.each_with_index do |question, index|
|
|
329
485
|
answer = ask_single_user_question(question, index + 1, questions.length)
|
|
@@ -342,7 +498,157 @@ module Kward
|
|
|
342
498
|
end
|
|
343
499
|
|
|
344
500
|
def modal_active?
|
|
345
|
-
@mutex.synchronize {
|
|
501
|
+
@mutex.synchronize { modal_active_locked? }
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def interactive_active?
|
|
505
|
+
@mutex.synchronize { interactive_active_locked? }
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def interactive_exited?
|
|
509
|
+
@mutex.synchronize do
|
|
510
|
+
return false unless @interactive_state
|
|
511
|
+
|
|
512
|
+
@interactive_state[:controller].exited?
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def finish_interactive
|
|
517
|
+
@mutex.synchronize do
|
|
518
|
+
return unless @interactive_state
|
|
519
|
+
|
|
520
|
+
snapshot = @interactive_state[:snapshot]
|
|
521
|
+
@interactive_state = nil
|
|
522
|
+
restore_composer_snapshot_locked(snapshot)
|
|
523
|
+
redraw_screen_locked if @started
|
|
524
|
+
@output_io.flush
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def with_terminal_handoff
|
|
529
|
+
start
|
|
530
|
+
input = nil
|
|
531
|
+
output = nil
|
|
532
|
+
@mutex.synchronize do
|
|
533
|
+
clear_prompt_for_output_locked
|
|
534
|
+
restore_scroll_region_locked
|
|
535
|
+
disable_editor_mouse_reporting(force: true)
|
|
536
|
+
@output_io.print(BRACKETED_PASTE_RESTORE)
|
|
537
|
+
@output_io.print(KEYBOARD_PROTOCOL_RESTORE)
|
|
538
|
+
restore_editor_cursor_shape_locked
|
|
539
|
+
set_cursor_visible_locked(true, force: true)
|
|
540
|
+
@output_io.flush
|
|
541
|
+
restore_console_mode_locked
|
|
542
|
+
input = @input_io
|
|
543
|
+
output = @output_io
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
yield(input, output)
|
|
547
|
+
ensure
|
|
548
|
+
@mutex.synchronize do
|
|
549
|
+
enter_raw_mode_locked
|
|
550
|
+
@output_io.print(KEYBOARD_PROTOCOL_ENABLE)
|
|
551
|
+
@output_io.print(BRACKETED_PASTE_ENABLE)
|
|
552
|
+
@last_composer_rows = []
|
|
553
|
+
render_prompt_locked if @started && @asking
|
|
554
|
+
@output_io.flush
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def start_interactive(title:, rows:, fps:)
|
|
559
|
+
snapshot = composer_snapshot
|
|
560
|
+
controller = InteractiveController.new(width: interactive_canvas_width, height: rows, fps: fps)
|
|
561
|
+
start
|
|
562
|
+
@mutex.synchronize do
|
|
563
|
+
@interactive_state = {
|
|
564
|
+
title: title.to_s,
|
|
565
|
+
rows: rows,
|
|
566
|
+
controller: controller,
|
|
567
|
+
snapshot: snapshot
|
|
568
|
+
}
|
|
569
|
+
@last_interactive_tick = monotonic_now
|
|
570
|
+
@asking = true
|
|
571
|
+
@busy = false
|
|
572
|
+
@last_composer_rows = []
|
|
573
|
+
render_prompt_locked
|
|
574
|
+
end
|
|
575
|
+
controller
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
def update_tabs(labels:, active_index: 0)
|
|
579
|
+
@mutex.synchronize do
|
|
580
|
+
@tabs = Array(labels).map { |label| normalize_tab_label(label) }
|
|
581
|
+
@active_tab_index = active_index.to_i
|
|
582
|
+
render_prompt_locked if @started && @asking
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
def composer_snapshot
|
|
587
|
+
@mutex.synchronize do
|
|
588
|
+
{
|
|
589
|
+
composer: @composer,
|
|
590
|
+
prompt_label: @prompt_label
|
|
591
|
+
}
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
def tab_view_snapshot
|
|
596
|
+
@mutex.synchronize do
|
|
597
|
+
{
|
|
598
|
+
composer: @composer.dup,
|
|
599
|
+
prompt_label: @prompt_label.dup,
|
|
600
|
+
editor_state: @editor_state&.dup,
|
|
601
|
+
transcript_buffer: @transcript_buffer.dup,
|
|
602
|
+
transcript_viewport_rows: @transcript_viewport_rows,
|
|
603
|
+
stream_state: @stream_state.dup
|
|
604
|
+
}
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
def restore_composer_snapshot(snapshot)
|
|
609
|
+
@mutex.synchronize do
|
|
610
|
+
restore_composer_snapshot_locked(snapshot)
|
|
611
|
+
restore_editor_snapshot_locked(snapshot)
|
|
612
|
+
redraw_screen_locked if @started
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def restore_tab_view_snapshot(snapshot)
|
|
617
|
+
@mutex.synchronize do
|
|
618
|
+
restore_composer_snapshot_locked(snapshot)
|
|
619
|
+
restore_editor_snapshot_locked(snapshot)
|
|
620
|
+
@transcript_buffer = snapshot[:transcript_buffer] || TranscriptBuffer.new(limit: TRANSCRIPT_BUFFER_LIMIT)
|
|
621
|
+
@transcript_viewport_rows = snapshot[:transcript_viewport_rows].to_i
|
|
622
|
+
@stream_state = snapshot[:stream_state] || StreamState.new
|
|
623
|
+
@last_composer_rows = []
|
|
624
|
+
redraw_screen_locked if @started
|
|
625
|
+
end
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def restore_composer_snapshot_locked(snapshot)
|
|
629
|
+
@composer = snapshot[:composer] || new_composer_state_with_history
|
|
630
|
+
@prompt_label = snapshot[:prompt_label].to_s.empty? ? "You>" : snapshot[:prompt_label].to_s
|
|
631
|
+
self.composer_input = @composer.input
|
|
632
|
+
self.composer_cursor = @composer.cursor
|
|
633
|
+
@last_composer_rows = []
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
def new_composer_state_with_history
|
|
637
|
+
composer = ComposerState.new
|
|
638
|
+
composer.load_history(@prompt_history.values) if @prompt_history
|
|
639
|
+
composer
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
def restore_editor_snapshot_locked(snapshot)
|
|
643
|
+
editor_was_active = editor_active?
|
|
644
|
+
@editor_state = snapshot[:editor_state]&.dup
|
|
645
|
+
editor_is_active = editor_active?
|
|
646
|
+
|
|
647
|
+
if editor_is_active
|
|
648
|
+
enable_editor_mouse_reporting unless editor_was_active
|
|
649
|
+
else
|
|
650
|
+
disable_editor_mouse_reporting(force: true)
|
|
651
|
+
end
|
|
346
652
|
end
|
|
347
653
|
|
|
348
654
|
def update_overlay_settings(settings)
|
|
@@ -409,6 +715,22 @@ module Kward
|
|
|
409
715
|
def poll_input
|
|
410
716
|
key = read_key(nonblock: true)
|
|
411
717
|
@mutex.synchronize do
|
|
718
|
+
if interactive_active_locked?
|
|
719
|
+
if key.nil?
|
|
720
|
+
resized = handle_resize_locked
|
|
721
|
+
ticked = tick_interactive_locked
|
|
722
|
+
render_prompt_locked if resized || ticked
|
|
723
|
+
return :interactive_exited if @interactive_state[:controller].exited?
|
|
724
|
+
return nil
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
route_interactive_key(key)
|
|
728
|
+
ticked = tick_interactive_locked
|
|
729
|
+
render_prompt_locked if ticked
|
|
730
|
+
return :interactive_exited if @interactive_state[:controller].exited?
|
|
731
|
+
return nil
|
|
732
|
+
end
|
|
733
|
+
|
|
412
734
|
if key.nil?
|
|
413
735
|
resized = handle_resize_locked
|
|
414
736
|
spun = tick_spinner_locked
|
|
@@ -417,8 +739,13 @@ module Kward
|
|
|
417
739
|
return nil
|
|
418
740
|
end
|
|
419
741
|
|
|
742
|
+
if modal_active_locked?
|
|
743
|
+
queue_pending_keys(key)
|
|
744
|
+
return nil
|
|
745
|
+
end
|
|
746
|
+
|
|
420
747
|
result = handle_key(key)
|
|
421
|
-
render_prompt_locked unless [EXIT_INPUT, CANCEL_INPUT].include?(result)
|
|
748
|
+
render_prompt_locked unless [EXIT_INPUT, CANCEL_INPUT].include?(result) || prompt_action_result?(result)
|
|
422
749
|
[EXIT_INPUT, CANCEL_INPUT].include?(result) ? result : result
|
|
423
750
|
end
|
|
424
751
|
end
|
|
@@ -465,6 +792,17 @@ module Kward
|
|
|
465
792
|
end
|
|
466
793
|
end
|
|
467
794
|
|
|
795
|
+
def write_transcript_delta(delta)
|
|
796
|
+
@mutex.synchronize do
|
|
797
|
+
with_synchronized_output_locked do
|
|
798
|
+
prepare_transcript_output_locked unless @restoring_transcript
|
|
799
|
+
write_transcript_text_locked(delta.to_s)
|
|
800
|
+
restore_composer_cursor_locked unless @restoring_transcript
|
|
801
|
+
end
|
|
802
|
+
@output_io.flush unless @restoring_transcript
|
|
803
|
+
end
|
|
804
|
+
end
|
|
805
|
+
|
|
468
806
|
def write_stream_block(label, delta, finish: false)
|
|
469
807
|
@mutex.synchronize do
|
|
470
808
|
write_stream_block_locked(label, delta.to_s, finish: finish)
|
|
@@ -479,6 +817,14 @@ module Kward
|
|
|
479
817
|
end
|
|
480
818
|
end
|
|
481
819
|
|
|
820
|
+
def refresh_composer_status
|
|
821
|
+
@mutex.synchronize do
|
|
822
|
+
@cached_composer_status_text = nil
|
|
823
|
+
@last_composer_status_refresh = 0.0
|
|
824
|
+
render_prompt_locked if @started && @asking
|
|
825
|
+
end
|
|
826
|
+
end
|
|
827
|
+
|
|
482
828
|
def clear_transcript
|
|
483
829
|
@mutex.synchronize do
|
|
484
830
|
@transcript_buffer.clear
|
|
@@ -493,210 +839,8 @@ module Kward
|
|
|
493
839
|
|
|
494
840
|
private
|
|
495
841
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
842
|
+
def modal_active_locked?
|
|
843
|
+
@question_prompt_active || !@question_state.nil? || !@select_state.nil? || !@git_state.nil?
|
|
844
|
+
end
|
|
701
845
|
end
|
|
702
846
|
end
|