kward 0.72.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 +53 -0
- data/Gemfile.lock +2 -2
- data/doc/configuration.md +1 -1
- data/doc/editor.md +23 -2
- data/doc/git.md +1 -0
- data/doc/rpc.md +2 -2
- data/doc/shell.md +56 -10
- data/doc/usage.md +27 -1
- data/lib/kward/ansi.rb +62 -23
- data/lib/kward/cli/plugins.rb +1 -1
- data/lib/kward/cli/rendering.rb +4 -1
- data/lib/kward/cli/runtime_helpers.rb +141 -7
- data/lib/kward/cli/settings.rb +0 -1
- data/lib/kward/cli/slash_commands.rb +213 -0
- data/lib/kward/cli/tabs.rb +34 -4
- data/lib/kward/cli/tool_summaries.rb +6 -0
- data/lib/kward/cli.rb +4 -12
- data/lib/kward/clipboard.rb +2 -3
- data/lib/kward/compactor.rb +7 -19
- data/lib/kward/config_files.rb +26 -4
- data/lib/kward/ekwsh.rb +239 -42
- 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/prompt_history.rb +5 -3
- data/lib/kward/prompt_interface/editor/auto_indent.rb +5 -4
- data/lib/kward/prompt_interface/editor/controller.rb +262 -62
- data/lib/kward/prompt_interface/editor/modes/emacs.rb +21 -21
- data/lib/kward/prompt_interface/editor/modes/modern.rb +38 -37
- data/lib/kward/prompt_interface/editor/modes/vibe.rb +23 -173
- data/lib/kward/prompt_interface/editor/modes/vibe_insert_readline.rb +166 -0
- data/lib/kward/prompt_interface/editor/renderer.rb +6 -5
- data/lib/kward/prompt_interface/editor/state.rb +28 -6
- data/lib/kward/prompt_interface/editor/syntax_highlighter.rb +5 -3
- data/lib/kward/prompt_interface/git_prompt.rb +12 -23
- data/lib/kward/prompt_interface/interactive/controller.rb +1 -1
- data/lib/kward/prompt_interface/key_handler.rb +93 -51
- data/lib/kward/prompt_interface/question_prompt.rb +1 -6
- data/lib/kward/prompt_interface/screen.rb +3 -3
- data/lib/kward/prompt_interface/selection_prompt.rb +3 -6
- data/lib/kward/prompt_interface/slash_overlay.rb +2 -0
- data/lib/kward/prompt_interface.rb +87 -221
- data/lib/kward/prompts/commands.rb +4 -0
- data/lib/kward/rpc/memory_methods.rb +83 -0
- data/lib/kward/rpc/server.rb +130 -83
- data/lib/kward/rpc/session_manager.rb +10 -74
- 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/terminal_keys.rb +84 -0
- data/lib/kward/terminal_sequences.rb +42 -0
- data/lib/kward/tools/context_for_task.rb +2 -0
- data/lib/kward/version.rb +1 -1
- data/lib/kward/workers/git_guard.rb +25 -0
- data/lib/kward/workers/job.rb +99 -0
- data/lib/kward/workers/queue_runner.rb +166 -0
- data/lib/kward/workers/queue_store.rb +112 -0
- data/lib/kward/workers.rb +3 -0
- data/lib/kward/workspace.rb +15 -63
- data/templates/default/fulldoc/html/css/kward.css +33 -0
- data/templates/default/fulldoc/html/images/kward_screen_1.png +0 -0
- data/templates/default/fulldoc/html/setup.rb +1 -0
- data/templates/default/layout/html/layout.erb +19 -32
- metadata +15 -1
|
@@ -43,7 +43,7 @@ module Kward
|
|
|
43
43
|
modern_record_undo { editor_insert_tab unless editor_search_active? }
|
|
44
44
|
when "\b", "\x7F"
|
|
45
45
|
editor_search_active? ? editor_search_delete_character : modern_record_undo { modern_delete_before_cursor }
|
|
46
|
-
when
|
|
46
|
+
when TerminalKeys::CTRL_C
|
|
47
47
|
return editor_search_cancel if editor_search_active?
|
|
48
48
|
when "\e"
|
|
49
49
|
return editor_search_cancel if editor_search_active?
|
|
@@ -55,11 +55,13 @@ module Kward
|
|
|
55
55
|
when "?"
|
|
56
56
|
clear_editor_selection_before_edit unless editor_search_active?
|
|
57
57
|
editor_search_active? ? editor_search_append(key) : editor_search_begin(:backward)
|
|
58
|
-
when
|
|
58
|
+
when TerminalKeys::CTRL_Q
|
|
59
59
|
quit_editor
|
|
60
|
-
when
|
|
60
|
+
when TerminalKeys::CTRL_S
|
|
61
61
|
save_editor
|
|
62
|
-
when
|
|
62
|
+
when TerminalKeys::CTRL_R
|
|
63
|
+
modern_record_undo { run_editor_buffer } unless editor_search_active?
|
|
64
|
+
when TerminalKeys::CTRL_Z
|
|
63
65
|
@editor_state.undo unless editor_search_active?
|
|
64
66
|
else
|
|
65
67
|
key_name = key_name_for(key)
|
|
@@ -94,11 +96,11 @@ module Kward
|
|
|
94
96
|
return false if editor_search_active?
|
|
95
97
|
|
|
96
98
|
case key
|
|
97
|
-
when
|
|
99
|
+
when TerminalKeys::CTRL_D
|
|
98
100
|
@editor_state.add_next_occurrence_selection
|
|
99
|
-
when
|
|
101
|
+
when *TerminalKeys::ALT_SHIFT_UP
|
|
100
102
|
@editor_state.add_vertical_cursor(:up)
|
|
101
|
-
when
|
|
103
|
+
when *TerminalKeys::ALT_SHIFT_DOWN
|
|
102
104
|
@editor_state.add_vertical_cursor(:down)
|
|
103
105
|
else
|
|
104
106
|
false
|
|
@@ -135,29 +137,29 @@ module Kward
|
|
|
135
137
|
def modern_indentation_key_sequences(action)
|
|
136
138
|
case [modern_indentation_modifier, action]
|
|
137
139
|
when [:alt, :up]
|
|
138
|
-
|
|
140
|
+
TerminalKeys::ALT_UP
|
|
139
141
|
when [:alt, :down]
|
|
140
|
-
|
|
142
|
+
TerminalKeys::ALT_DOWN
|
|
141
143
|
when [:alt, :right]
|
|
142
|
-
|
|
144
|
+
TerminalKeys::ALT_RIGHT
|
|
143
145
|
when [:alt, :select_up]
|
|
144
|
-
|
|
146
|
+
TerminalKeys::ALT_SHIFT_UP
|
|
145
147
|
when [:alt, :select_down]
|
|
146
|
-
|
|
148
|
+
TerminalKeys::ALT_SHIFT_DOWN
|
|
147
149
|
when [:alt, :select_right]
|
|
148
|
-
|
|
150
|
+
TerminalKeys::ALT_SHIFT_RIGHT
|
|
149
151
|
when [:ctrl, :up]
|
|
150
|
-
|
|
152
|
+
TerminalKeys::CTRL_UP
|
|
151
153
|
when [:ctrl, :down]
|
|
152
|
-
|
|
154
|
+
TerminalKeys::CTRL_DOWN
|
|
153
155
|
when [:ctrl, :right]
|
|
154
|
-
|
|
156
|
+
TerminalKeys::CTRL_RIGHT
|
|
155
157
|
when [:ctrl, :select_up]
|
|
156
|
-
|
|
158
|
+
TerminalKeys::CTRL_SHIFT_UP
|
|
157
159
|
when [:ctrl, :select_down]
|
|
158
|
-
|
|
160
|
+
TerminalKeys::CTRL_SHIFT_DOWN
|
|
159
161
|
when [:ctrl, :select_right]
|
|
160
|
-
|
|
162
|
+
TerminalKeys::CTRL_SHIFT_RIGHT
|
|
161
163
|
else
|
|
162
164
|
[]
|
|
163
165
|
end
|
|
@@ -171,17 +173,17 @@ module Kward
|
|
|
171
173
|
return false if editor_search_active?
|
|
172
174
|
|
|
173
175
|
case key
|
|
174
|
-
when
|
|
176
|
+
when *TerminalKeys::CTRL_RIGHT
|
|
175
177
|
@editor_state.move_line_end
|
|
176
|
-
when
|
|
178
|
+
when *TerminalKeys::CTRL_LEFT
|
|
177
179
|
@editor_state.move_line_start
|
|
178
|
-
when
|
|
180
|
+
when *TerminalKeys::CTRL_UP
|
|
179
181
|
@editor_state.move_file_start
|
|
180
|
-
when
|
|
182
|
+
when *TerminalKeys::CTRL_DOWN
|
|
181
183
|
@editor_state.move_file_end
|
|
182
|
-
when
|
|
184
|
+
when *TerminalKeys::ALT_SHIFT_RIGHT
|
|
183
185
|
editor_extending_selection { @editor_state.move_to_next_word }
|
|
184
|
-
when
|
|
186
|
+
when *TerminalKeys::ALT_SHIFT_LEFT
|
|
185
187
|
editor_extending_selection { @editor_state.move_to_previous_word }
|
|
186
188
|
else
|
|
187
189
|
false
|
|
@@ -190,15 +192,15 @@ module Kward
|
|
|
190
192
|
|
|
191
193
|
def handle_modern_key_binding(key)
|
|
192
194
|
case key
|
|
193
|
-
when
|
|
195
|
+
when TerminalKeys::CTRL_SPACE
|
|
194
196
|
true
|
|
195
|
-
when
|
|
197
|
+
when TerminalKeys::CTRL_C
|
|
196
198
|
editor_search_active? ? editor_search_cancel : copy_editor_selection
|
|
197
|
-
when
|
|
199
|
+
when TerminalKeys::CTRL_F
|
|
198
200
|
@editor_state.move_right unless editor_search_active?
|
|
199
|
-
when
|
|
201
|
+
when TerminalKeys::CTRL_V
|
|
200
202
|
modern_record_undo { @editor_state.yank_kill_buffer } unless editor_search_active?
|
|
201
|
-
when
|
|
203
|
+
when TerminalKeys::CTRL_X
|
|
202
204
|
modern_record_undo { cut_editor_selection } unless editor_search_active?
|
|
203
205
|
else
|
|
204
206
|
handle_modern_shared_key_binding(key)
|
|
@@ -237,6 +239,8 @@ module Kward
|
|
|
237
239
|
return false unless modern_ctrl_shift_key?(code, modifier)
|
|
238
240
|
|
|
239
241
|
@editor_state.selection_to_line_start_cursors
|
|
242
|
+
when 114
|
|
243
|
+
modern_record_undo { run_editor_buffer } unless editor_search_active?
|
|
240
244
|
when 122
|
|
241
245
|
return if editor_search_active?
|
|
242
246
|
|
|
@@ -250,7 +254,7 @@ module Kward
|
|
|
250
254
|
|
|
251
255
|
def handle_modern_shared_key_binding(key)
|
|
252
256
|
case key
|
|
253
|
-
when
|
|
257
|
+
when TerminalKeys::CTRL_D, TerminalKeys::CTRL_K, TerminalKeys::CTRL_U, TerminalKeys::CTRL_W, TerminalKeys::CTRL_Y, *TerminalKeys::DELETE, "\ed", "\eD", "\e\b", "\e\x7F"
|
|
254
258
|
modern_record_undo { handle_editor_key_binding(key) }
|
|
255
259
|
else
|
|
256
260
|
handle_editor_key_binding(key)
|
|
@@ -303,12 +307,9 @@ module Kward
|
|
|
303
307
|
end
|
|
304
308
|
|
|
305
309
|
def handle_modern_bracketed_paste_key(key)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
modern_record_undo { @editor_state.insert(normalize_paste(paste[:content])) } unless editor_search_active?
|
|
310
|
-
queue_pending_keys(paste[:remaining]) if paste[:remaining] && !paste[:remaining].empty?
|
|
311
|
-
true
|
|
310
|
+
handle_bracketed_paste(key) do |content|
|
|
311
|
+
modern_record_undo { @editor_state.insert(content) } unless editor_search_active?
|
|
312
|
+
end
|
|
312
313
|
end
|
|
313
314
|
|
|
314
315
|
def modern_insert_printable(text)
|
|
@@ -43,7 +43,7 @@ module Kward
|
|
|
43
43
|
|
|
44
44
|
return vibe_stop_macro_recording if key == "q" && @editor_state.vibe_recording_macro && !%w[insert replace command].include?(@editor_state.vibe_mode)
|
|
45
45
|
vibe_record_macro_key(key)
|
|
46
|
-
return vibe_begin_visual_mode("visual_block") if key ==
|
|
46
|
+
return vibe_begin_visual_mode("visual_block") if key == TerminalKeys::CTRL_V && @editor_state.vibe_mode == "normal"
|
|
47
47
|
return handle_vibe_repeat_change if key == "." && @editor_state.vibe_mode == "normal"
|
|
48
48
|
return handle_vibe_search_key(key) if editor_search_active?
|
|
49
49
|
return handle_vibe_command_key(key) if @editor_state.vibe_mode == "command"
|
|
@@ -119,7 +119,7 @@ module Kward
|
|
|
119
119
|
editor_search_confirm
|
|
120
120
|
when "\b", "\x7F"
|
|
121
121
|
editor_search_delete_character
|
|
122
|
-
when "\e",
|
|
122
|
+
when "\e", TerminalKeys::CTRL_C
|
|
123
123
|
editor_search_cancel
|
|
124
124
|
else
|
|
125
125
|
editor_search_append(key) if printable_key?(key)
|
|
@@ -135,7 +135,7 @@ module Kward
|
|
|
135
135
|
return tab_result unless tab_result == false
|
|
136
136
|
|
|
137
137
|
case key
|
|
138
|
-
when "\e",
|
|
138
|
+
when "\e", TerminalKeys::CTRL_C, :escape
|
|
139
139
|
vibe_return_to_normal
|
|
140
140
|
when "\b", "\x7F"
|
|
141
141
|
vibe_record_undo { editor_delete_before_cursor }
|
|
@@ -153,162 +153,6 @@ module Kward
|
|
|
153
153
|
end
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
-
def handle_vibe_insert_readline_key(key)
|
|
157
|
-
csi_result = handle_vibe_insert_readline_csi_u_key(key)
|
|
158
|
-
return csi_result unless csi_result == false
|
|
159
|
-
|
|
160
|
-
handle_vibe_insert_readline_ansi_key(key)
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def handle_vibe_insert_readline_csi_u_key(key)
|
|
164
|
-
sequence = parse_csi_u_key(key)
|
|
165
|
-
return false unless sequence
|
|
166
|
-
|
|
167
|
-
queue_pending_keys(sequence[:remaining]) if sequence[:remaining] && !sequence[:remaining].empty?
|
|
168
|
-
modifier = sequence[:modifier]
|
|
169
|
-
normalized_code = sequence[:code].to_i.chr.downcase.ord rescue sequence[:code]
|
|
170
|
-
if ctrl_modifier?(modifier)
|
|
171
|
-
return handle_vibe_insert_readline_ctrl_key(normalized_code)
|
|
172
|
-
elsif alt_modifier?(modifier)
|
|
173
|
-
return handle_vibe_insert_readline_alt_key(normalized_code)
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
false
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
def handle_vibe_insert_readline_ansi_key(key)
|
|
180
|
-
case key
|
|
181
|
-
when "\x01"
|
|
182
|
-
@editor_state.move_line_start
|
|
183
|
-
when "\x02"
|
|
184
|
-
@editor_state.move_left
|
|
185
|
-
when "\x04"
|
|
186
|
-
vibe_record_undo { @editor_state.delete_at_cursor }
|
|
187
|
-
when "\x05"
|
|
188
|
-
@editor_state.move_line_end
|
|
189
|
-
when "\x06"
|
|
190
|
-
@editor_state.move_right
|
|
191
|
-
when "\x0B"
|
|
192
|
-
vibe_record_undo { @editor_state.kill_line_after_cursor }
|
|
193
|
-
when "\x15"
|
|
194
|
-
vibe_record_undo { @editor_state.kill_line_before_cursor }
|
|
195
|
-
when "\x17"
|
|
196
|
-
vibe_record_undo { @editor_state.delete_word_before_cursor }
|
|
197
|
-
when "\x19"
|
|
198
|
-
vibe_record_undo { @editor_state.yank_kill_buffer }
|
|
199
|
-
when "\e[D", "\eOD"
|
|
200
|
-
@editor_state.move_left
|
|
201
|
-
when "\e[C", "\eOC"
|
|
202
|
-
@editor_state.move_right
|
|
203
|
-
when "\e[H", "\eOH", "\e[1~", "\e[7~"
|
|
204
|
-
@editor_state.move_line_start
|
|
205
|
-
when "\e[F", "\eOF", "\e[4~", "\e[8~"
|
|
206
|
-
@editor_state.move_line_end
|
|
207
|
-
when "\e[3~"
|
|
208
|
-
vibe_record_undo { @editor_state.delete_at_cursor }
|
|
209
|
-
when "\eb", "\eB"
|
|
210
|
-
@editor_state.move_to_previous_word
|
|
211
|
-
when "\ef", "\eF"
|
|
212
|
-
@editor_state.move_to_next_word
|
|
213
|
-
when "\ed", "\eD"
|
|
214
|
-
vibe_record_undo { @editor_state.delete_word_after_cursor }
|
|
215
|
-
when "\e\b", "\e\x7F"
|
|
216
|
-
vibe_record_undo { @editor_state.delete_word_before_cursor }
|
|
217
|
-
else
|
|
218
|
-
handle_vibe_insert_modified_ansi_key(key)
|
|
219
|
-
end
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
def handle_vibe_insert_readline_ctrl_key(normalized_code)
|
|
223
|
-
case normalized_code
|
|
224
|
-
when 97
|
|
225
|
-
@editor_state.move_line_start
|
|
226
|
-
when 98
|
|
227
|
-
@editor_state.move_left
|
|
228
|
-
when 100
|
|
229
|
-
vibe_record_undo { @editor_state.delete_at_cursor }
|
|
230
|
-
when 101
|
|
231
|
-
@editor_state.move_line_end
|
|
232
|
-
when 102
|
|
233
|
-
@editor_state.move_right
|
|
234
|
-
when 107
|
|
235
|
-
vibe_record_undo { @editor_state.kill_line_after_cursor }
|
|
236
|
-
when 117
|
|
237
|
-
vibe_record_undo { @editor_state.kill_line_before_cursor }
|
|
238
|
-
when 119
|
|
239
|
-
vibe_record_undo { @editor_state.delete_word_before_cursor }
|
|
240
|
-
when 121
|
|
241
|
-
vibe_record_undo { @editor_state.yank_kill_buffer }
|
|
242
|
-
else
|
|
243
|
-
false
|
|
244
|
-
end
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
def handle_vibe_insert_readline_alt_key(normalized_code)
|
|
248
|
-
case normalized_code
|
|
249
|
-
when 98
|
|
250
|
-
@editor_state.move_to_previous_word
|
|
251
|
-
when 100
|
|
252
|
-
vibe_record_undo { @editor_state.delete_word_after_cursor }
|
|
253
|
-
when 102
|
|
254
|
-
@editor_state.move_to_next_word
|
|
255
|
-
else
|
|
256
|
-
false
|
|
257
|
-
end
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
def handle_vibe_insert_modified_ansi_key(key)
|
|
261
|
-
sequence = parse_modified_ansi_key(key)
|
|
262
|
-
return false unless sequence
|
|
263
|
-
|
|
264
|
-
case sequence[:type]
|
|
265
|
-
when :cursor
|
|
266
|
-
return false unless alt_modifier?(sequence[:modifier])
|
|
267
|
-
|
|
268
|
-
case sequence[:final]
|
|
269
|
-
when "C"
|
|
270
|
-
@editor_state.move_to_next_word
|
|
271
|
-
when "D"
|
|
272
|
-
@editor_state.move_to_previous_word
|
|
273
|
-
when "F"
|
|
274
|
-
@editor_state.move_line_end
|
|
275
|
-
when "H"
|
|
276
|
-
@editor_state.move_line_start
|
|
277
|
-
else
|
|
278
|
-
false
|
|
279
|
-
end
|
|
280
|
-
when :delete
|
|
281
|
-
return false unless alt_modifier?(sequence[:modifier])
|
|
282
|
-
|
|
283
|
-
vibe_record_undo { @editor_state.delete_word_after_cursor }
|
|
284
|
-
else
|
|
285
|
-
false
|
|
286
|
-
end
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
def handle_vibe_insert_named_key(key_name)
|
|
290
|
-
case key_name
|
|
291
|
-
when :escape
|
|
292
|
-
vibe_return_to_normal
|
|
293
|
-
when :return, :enter
|
|
294
|
-
vibe_record_undo { editor_insert_newline }
|
|
295
|
-
when :backspace
|
|
296
|
-
vibe_record_undo { editor_delete_before_cursor }
|
|
297
|
-
when :delete
|
|
298
|
-
vibe_record_undo { @editor_state.delete_at_cursor }
|
|
299
|
-
when :left
|
|
300
|
-
@editor_state.move_left
|
|
301
|
-
when :right
|
|
302
|
-
@editor_state.move_right
|
|
303
|
-
when :up
|
|
304
|
-
editor_move_up
|
|
305
|
-
when :down
|
|
306
|
-
editor_move_down
|
|
307
|
-
else
|
|
308
|
-
false
|
|
309
|
-
end
|
|
310
|
-
end
|
|
311
|
-
|
|
312
156
|
def handle_vibe_replace_key(key)
|
|
313
157
|
return if handle_editor_bracketed_paste_key(key)
|
|
314
158
|
|
|
@@ -317,7 +161,7 @@ module Kward
|
|
|
317
161
|
return tab_result unless tab_result == false
|
|
318
162
|
|
|
319
163
|
case key
|
|
320
|
-
when "\e",
|
|
164
|
+
when "\e", TerminalKeys::CTRL_C, :escape
|
|
321
165
|
vibe_return_to_normal
|
|
322
166
|
when "\b", "\x7F"
|
|
323
167
|
vibe_record_undo { editor_delete_before_cursor }
|
|
@@ -339,7 +183,7 @@ module Kward
|
|
|
339
183
|
|
|
340
184
|
def handle_vibe_command_key(key)
|
|
341
185
|
case key
|
|
342
|
-
when "\e",
|
|
186
|
+
when "\e", TerminalKeys::CTRL_C, :escape
|
|
343
187
|
@editor_state.vibe_command = ""
|
|
344
188
|
vibe_return_to_normal
|
|
345
189
|
when "\b", "\x7F"
|
|
@@ -363,12 +207,18 @@ module Kward
|
|
|
363
207
|
case command
|
|
364
208
|
when "w"
|
|
365
209
|
save_editor
|
|
210
|
+
when /\Aw\s+(.+)\z/
|
|
211
|
+
save_editor(Regexp.last_match(1))
|
|
212
|
+
when "run"
|
|
213
|
+
vibe_record_undo { run_editor_buffer }
|
|
366
214
|
when "q"
|
|
367
215
|
vibe_quit_editor
|
|
368
216
|
when "q!"
|
|
369
217
|
close_editor
|
|
370
218
|
when "wq"
|
|
371
219
|
save_editor && close_editor
|
|
220
|
+
when /\Awq\s+(.+)\z/
|
|
221
|
+
save_editor(Regexp.last_match(1)) && close_editor
|
|
372
222
|
when "x"
|
|
373
223
|
save_editor if @editor_state&.dirty?
|
|
374
224
|
close_editor if @editor_state
|
|
@@ -414,7 +264,7 @@ module Kward
|
|
|
414
264
|
end
|
|
415
265
|
|
|
416
266
|
def handle_vibe_normal_key(key)
|
|
417
|
-
if key == "\e" || key ==
|
|
267
|
+
if key == "\e" || key == TerminalKeys::CTRL_C
|
|
418
268
|
@editor_state.vibe_pending = ""
|
|
419
269
|
vibe_return_to_normal
|
|
420
270
|
return true
|
|
@@ -477,7 +327,7 @@ module Kward
|
|
|
477
327
|
end
|
|
478
328
|
|
|
479
329
|
def vibe_normal_control_key?(key)
|
|
480
|
-
["\n", "\r", "\b", "\x7F",
|
|
330
|
+
["\n", "\r", "\b", "\x7F", TerminalKeys::CTRL_B, TerminalKeys::CTRL_D, TerminalKeys::CTRL_E, TerminalKeys::CTRL_F, TerminalKeys::CTRL_R, TerminalKeys::CTRL_U, TerminalKeys::CTRL_Y].include?(key)
|
|
481
331
|
end
|
|
482
332
|
|
|
483
333
|
def vibe_visual_mode?
|
|
@@ -577,19 +427,19 @@ module Kward
|
|
|
577
427
|
vibe_move_to_screen_line(editor_page_rows / 2)
|
|
578
428
|
when "L"
|
|
579
429
|
vibe_move_to_screen_line(editor_page_rows - count)
|
|
580
|
-
when
|
|
430
|
+
when TerminalKeys::CTRL_F
|
|
581
431
|
@editor_state.page_down(editor_page_rows)
|
|
582
|
-
when
|
|
432
|
+
when TerminalKeys::CTRL_B
|
|
583
433
|
@editor_state.page_up(editor_page_rows)
|
|
584
|
-
when
|
|
434
|
+
when TerminalKeys::CTRL_D
|
|
585
435
|
@editor_state.page_down(vibe_half_page_rows)
|
|
586
|
-
when
|
|
436
|
+
when TerminalKeys::CTRL_U
|
|
587
437
|
@editor_state.page_up(vibe_half_page_rows)
|
|
588
|
-
when
|
|
438
|
+
when TerminalKeys::CTRL_E
|
|
589
439
|
vibe_scroll_down
|
|
590
|
-
when
|
|
440
|
+
when TerminalKeys::CTRL_Y
|
|
591
441
|
vibe_scroll_up
|
|
592
|
-
when
|
|
442
|
+
when TerminalKeys::CTRL_R
|
|
593
443
|
@editor_state.redo
|
|
594
444
|
when "i"
|
|
595
445
|
vibe_enter_insert_mode(command)
|
|
@@ -691,7 +541,7 @@ module Kward
|
|
|
691
541
|
def handle_vibe_visual_key(key)
|
|
692
542
|
key_name = key_name_for(key)
|
|
693
543
|
return handle_vibe_visual_named_key(key_name) if key_name
|
|
694
|
-
if key == "\e" || key ==
|
|
544
|
+
if key == "\e" || key == TerminalKeys::CTRL_C
|
|
695
545
|
@editor_state.vibe_pending = ""
|
|
696
546
|
vibe_cancel_visual_mode
|
|
697
547
|
return true
|
|
@@ -1896,7 +1746,7 @@ module Kward
|
|
|
1896
1746
|
|
|
1897
1747
|
def vibe_copy_range(start_index, end_index, status)
|
|
1898
1748
|
@editor_state.copy_range(start_index, end_index)
|
|
1899
|
-
@output_io.print(
|
|
1749
|
+
@output_io.print(TerminalSequences.osc52(@editor_state.kill_buffer))
|
|
1900
1750
|
@output_io.flush if @output_io.respond_to?(:flush)
|
|
1901
1751
|
@editor_state.status = status
|
|
1902
1752
|
end
|
|
@@ -1928,7 +1778,7 @@ module Kward
|
|
|
1928
1778
|
|
|
1929
1779
|
def vibe_record_insert_change_key(key)
|
|
1930
1780
|
return unless @editor_state.vibe_last_change
|
|
1931
|
-
return if [
|
|
1781
|
+
return if [TerminalKeys::CTRL_C].include?(key)
|
|
1932
1782
|
|
|
1933
1783
|
@editor_state.vibe_last_change << key
|
|
1934
1784
|
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Namespace for the Kward CLI agent runtime.
|
|
2
|
+
module Kward
|
|
3
|
+
# Interactive terminal UI used by the CLI frontend.
|
|
4
|
+
class PromptInterface
|
|
5
|
+
# Readline-style insert-mode bindings for the Vibe editor mode.
|
|
6
|
+
module VibeInsertReadline
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def handle_vibe_insert_readline_key(key)
|
|
10
|
+
csi_result = handle_vibe_insert_readline_csi_u_key(key)
|
|
11
|
+
return csi_result unless csi_result == false
|
|
12
|
+
|
|
13
|
+
handle_vibe_insert_readline_ansi_key(key)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def handle_vibe_insert_readline_csi_u_key(key)
|
|
17
|
+
sequence = parse_csi_u_key(key)
|
|
18
|
+
return false unless sequence
|
|
19
|
+
|
|
20
|
+
queue_pending_keys(sequence[:remaining]) if sequence[:remaining] && !sequence[:remaining].empty?
|
|
21
|
+
modifier = sequence[:modifier]
|
|
22
|
+
normalized_code = sequence[:code].to_i.chr.downcase.ord rescue sequence[:code]
|
|
23
|
+
if ctrl_modifier?(modifier)
|
|
24
|
+
return handle_vibe_insert_readline_ctrl_key(normalized_code)
|
|
25
|
+
elsif alt_modifier?(modifier)
|
|
26
|
+
return handle_vibe_insert_readline_alt_key(normalized_code)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def handle_vibe_insert_readline_ansi_key(key)
|
|
33
|
+
case key
|
|
34
|
+
when TerminalKeys::CTRL_A
|
|
35
|
+
@editor_state.move_line_start
|
|
36
|
+
when TerminalKeys::CTRL_B
|
|
37
|
+
@editor_state.move_left
|
|
38
|
+
when TerminalKeys::CTRL_D
|
|
39
|
+
vibe_record_undo { @editor_state.delete_at_cursor }
|
|
40
|
+
when TerminalKeys::CTRL_E
|
|
41
|
+
@editor_state.move_line_end
|
|
42
|
+
when TerminalKeys::CTRL_F
|
|
43
|
+
@editor_state.move_right
|
|
44
|
+
when TerminalKeys::CTRL_K
|
|
45
|
+
vibe_record_undo { @editor_state.kill_line_after_cursor }
|
|
46
|
+
when TerminalKeys::CTRL_U
|
|
47
|
+
vibe_record_undo { @editor_state.kill_line_before_cursor }
|
|
48
|
+
when TerminalKeys::CTRL_W
|
|
49
|
+
vibe_record_undo { @editor_state.delete_word_before_cursor }
|
|
50
|
+
when TerminalKeys::CTRL_Y
|
|
51
|
+
vibe_record_undo { @editor_state.yank_kill_buffer }
|
|
52
|
+
when *TerminalKeys::LEFT
|
|
53
|
+
@editor_state.move_left
|
|
54
|
+
when *TerminalKeys::RIGHT
|
|
55
|
+
@editor_state.move_right
|
|
56
|
+
when *TerminalKeys::HOME
|
|
57
|
+
@editor_state.move_line_start
|
|
58
|
+
when *TerminalKeys::END_KEY
|
|
59
|
+
@editor_state.move_line_end
|
|
60
|
+
when *TerminalKeys::DELETE
|
|
61
|
+
vibe_record_undo { @editor_state.delete_at_cursor }
|
|
62
|
+
when "\eb", "\eB"
|
|
63
|
+
@editor_state.move_to_previous_word
|
|
64
|
+
when "\ef", "\eF"
|
|
65
|
+
@editor_state.move_to_next_word
|
|
66
|
+
when "\ed", "\eD"
|
|
67
|
+
vibe_record_undo { @editor_state.delete_word_after_cursor }
|
|
68
|
+
when "\e\b", "\e\x7F"
|
|
69
|
+
vibe_record_undo { @editor_state.delete_word_before_cursor }
|
|
70
|
+
else
|
|
71
|
+
handle_vibe_insert_modified_ansi_key(key)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def handle_vibe_insert_readline_ctrl_key(normalized_code)
|
|
76
|
+
case normalized_code
|
|
77
|
+
when 97
|
|
78
|
+
@editor_state.move_line_start
|
|
79
|
+
when 98
|
|
80
|
+
@editor_state.move_left
|
|
81
|
+
when 100
|
|
82
|
+
vibe_record_undo { @editor_state.delete_at_cursor }
|
|
83
|
+
when 101
|
|
84
|
+
@editor_state.move_line_end
|
|
85
|
+
when 102
|
|
86
|
+
@editor_state.move_right
|
|
87
|
+
when 107
|
|
88
|
+
vibe_record_undo { @editor_state.kill_line_after_cursor }
|
|
89
|
+
when 117
|
|
90
|
+
vibe_record_undo { @editor_state.kill_line_before_cursor }
|
|
91
|
+
when 119
|
|
92
|
+
vibe_record_undo { @editor_state.delete_word_before_cursor }
|
|
93
|
+
when 121
|
|
94
|
+
vibe_record_undo { @editor_state.yank_kill_buffer }
|
|
95
|
+
else
|
|
96
|
+
false
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def handle_vibe_insert_readline_alt_key(normalized_code)
|
|
101
|
+
case normalized_code
|
|
102
|
+
when 98
|
|
103
|
+
@editor_state.move_to_previous_word
|
|
104
|
+
when 100
|
|
105
|
+
vibe_record_undo { @editor_state.delete_word_after_cursor }
|
|
106
|
+
when 102
|
|
107
|
+
@editor_state.move_to_next_word
|
|
108
|
+
else
|
|
109
|
+
false
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def handle_vibe_insert_modified_ansi_key(key)
|
|
114
|
+
sequence = parse_modified_ansi_key(key)
|
|
115
|
+
return false unless sequence
|
|
116
|
+
|
|
117
|
+
case sequence[:type]
|
|
118
|
+
when :cursor
|
|
119
|
+
return false unless alt_modifier?(sequence[:modifier])
|
|
120
|
+
|
|
121
|
+
case sequence[:final]
|
|
122
|
+
when "C"
|
|
123
|
+
@editor_state.move_to_next_word
|
|
124
|
+
when "D"
|
|
125
|
+
@editor_state.move_to_previous_word
|
|
126
|
+
when "F"
|
|
127
|
+
@editor_state.move_line_end
|
|
128
|
+
when "H"
|
|
129
|
+
@editor_state.move_line_start
|
|
130
|
+
else
|
|
131
|
+
false
|
|
132
|
+
end
|
|
133
|
+
when :delete
|
|
134
|
+
return false unless alt_modifier?(sequence[:modifier])
|
|
135
|
+
|
|
136
|
+
vibe_record_undo { @editor_state.delete_word_after_cursor }
|
|
137
|
+
else
|
|
138
|
+
false
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def handle_vibe_insert_named_key(key_name)
|
|
143
|
+
case key_name
|
|
144
|
+
when :escape
|
|
145
|
+
vibe_return_to_normal
|
|
146
|
+
when :return, :enter
|
|
147
|
+
vibe_record_undo { editor_insert_newline }
|
|
148
|
+
when :backspace
|
|
149
|
+
vibe_record_undo { editor_delete_before_cursor }
|
|
150
|
+
when :delete
|
|
151
|
+
vibe_record_undo { @editor_state.delete_at_cursor }
|
|
152
|
+
when :left
|
|
153
|
+
@editor_state.move_left
|
|
154
|
+
when :right
|
|
155
|
+
@editor_state.move_right
|
|
156
|
+
when :up
|
|
157
|
+
editor_move_up
|
|
158
|
+
when :down
|
|
159
|
+
editor_move_down
|
|
160
|
+
else
|
|
161
|
+
false
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -132,21 +132,21 @@ module Kward
|
|
|
132
132
|
while index < rendered.length
|
|
133
133
|
if rendered[index] == "\e" && (match = rendered[index..].match(/\A\e\[[0-9;:]*m/))
|
|
134
134
|
output << match[0]
|
|
135
|
-
output <<
|
|
135
|
+
output << TerminalSequences::SGR_INVERSE if selected && match[0] == "\e[0m"
|
|
136
136
|
index += match[0].length
|
|
137
137
|
next
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
should_select = selection_ranges.any? { |range| visible_index >= range[0] && visible_index < range[1] }
|
|
141
141
|
if should_select != selected
|
|
142
|
-
output << (should_select ?
|
|
142
|
+
output << (should_select ? TerminalSequences::SGR_INVERSE : TerminalSequences::SGR_INVERSE_OFF)
|
|
143
143
|
selected = should_select
|
|
144
144
|
end
|
|
145
145
|
output << rendered[index]
|
|
146
146
|
visible_index += 1
|
|
147
147
|
index += 1
|
|
148
148
|
end
|
|
149
|
-
output <<
|
|
149
|
+
output << TerminalSequences::SGR_INVERSE_OFF if selected
|
|
150
150
|
output
|
|
151
151
|
end
|
|
152
152
|
|
|
@@ -229,9 +229,10 @@ module Kward
|
|
|
229
229
|
end
|
|
230
230
|
|
|
231
231
|
def editor_display_path
|
|
232
|
-
|
|
232
|
+
path = @editor_state.path || @editor_state.display_path
|
|
233
|
+
Pathname.new(path).relative_path_from(Pathname.new(Dir.pwd)).to_s
|
|
233
234
|
rescue StandardError
|
|
234
|
-
@editor_state.path
|
|
235
|
+
@editor_state.display_path || @editor_state.path
|
|
235
236
|
end
|
|
236
237
|
|
|
237
238
|
def editor_status_text
|