kward 0.72.0 → 0.73.1

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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +30 -0
  3. data/CHANGELOG.md +59 -0
  4. data/Gemfile.lock +2 -2
  5. data/doc/configuration.md +1 -1
  6. data/doc/editor.md +23 -2
  7. data/doc/git.md +1 -0
  8. data/doc/rpc.md +2 -2
  9. data/doc/shell.md +56 -10
  10. data/doc/usage.md +27 -1
  11. data/lib/kward/ansi.rb +62 -23
  12. data/lib/kward/cli/plugins.rb +1 -1
  13. data/lib/kward/cli/rendering.rb +4 -1
  14. data/lib/kward/cli/runtime_helpers.rb +141 -7
  15. data/lib/kward/cli/settings.rb +0 -1
  16. data/lib/kward/cli/slash_commands.rb +213 -0
  17. data/lib/kward/cli/tabs.rb +34 -4
  18. data/lib/kward/cli/tool_summaries.rb +6 -0
  19. data/lib/kward/cli.rb +4 -12
  20. data/lib/kward/clipboard.rb +2 -3
  21. data/lib/kward/compactor.rb +7 -19
  22. data/lib/kward/config_files.rb +26 -4
  23. data/lib/kward/ekwsh.rb +239 -42
  24. data/lib/kward/image_attachments.rb +3 -1
  25. data/lib/kward/interactive_pty_runner.rb +151 -0
  26. data/lib/kward/local_command_runner.rb +155 -0
  27. data/lib/kward/local_pty_command_runner.rb +171 -0
  28. data/lib/kward/model/context_usage.rb +2 -2
  29. data/lib/kward/model/payloads.rb +2 -5
  30. data/lib/kward/prompt_history.rb +5 -3
  31. data/lib/kward/prompt_interface/editor/auto_indent.rb +5 -4
  32. data/lib/kward/prompt_interface/editor/controller.rb +262 -62
  33. data/lib/kward/prompt_interface/editor/modes/emacs.rb +21 -21
  34. data/lib/kward/prompt_interface/editor/modes/modern.rb +38 -37
  35. data/lib/kward/prompt_interface/editor/modes/vibe.rb +23 -173
  36. data/lib/kward/prompt_interface/editor/modes/vibe_insert_readline.rb +166 -0
  37. data/lib/kward/prompt_interface/editor/renderer.rb +6 -5
  38. data/lib/kward/prompt_interface/editor/state.rb +28 -6
  39. data/lib/kward/prompt_interface/editor/syntax_highlighter.rb +5 -3
  40. data/lib/kward/prompt_interface/git_prompt.rb +12 -23
  41. data/lib/kward/prompt_interface/interactive/controller.rb +1 -1
  42. data/lib/kward/prompt_interface/key_handler.rb +93 -51
  43. data/lib/kward/prompt_interface/question_prompt.rb +1 -6
  44. data/lib/kward/prompt_interface/screen.rb +3 -3
  45. data/lib/kward/prompt_interface/selection_prompt.rb +12 -6
  46. data/lib/kward/prompt_interface/slash_overlay.rb +2 -0
  47. data/lib/kward/prompt_interface.rb +87 -221
  48. data/lib/kward/prompts/commands.rb +4 -0
  49. data/lib/kward/rpc/memory_methods.rb +83 -0
  50. data/lib/kward/rpc/server.rb +130 -83
  51. data/lib/kward/rpc/session_manager.rb +10 -74
  52. data/lib/kward/rpc/tool_metadata.rb +11 -0
  53. data/lib/kward/rpc/transcript_normalizer.rb +4 -39
  54. data/lib/kward/scratchpad_runner.rb +56 -0
  55. data/lib/kward/session_diff.rb +20 -3
  56. data/lib/kward/session_naming.rb +11 -0
  57. data/lib/kward/terminal_keys.rb +84 -0
  58. data/lib/kward/terminal_sequences.rb +42 -0
  59. data/lib/kward/tools/context_for_task.rb +2 -0
  60. data/lib/kward/version.rb +1 -1
  61. data/lib/kward/workers/git_guard.rb +25 -0
  62. data/lib/kward/workers/job.rb +99 -0
  63. data/lib/kward/workers/queue_runner.rb +166 -0
  64. data/lib/kward/workers/queue_store.rb +112 -0
  65. data/lib/kward/workers.rb +3 -0
  66. data/lib/kward/workspace.rb +15 -63
  67. data/templates/default/fulldoc/html/css/kward.css +33 -0
  68. data/templates/default/fulldoc/html/images/kward_screen_1.png +0 -0
  69. data/templates/default/fulldoc/html/setup.rb +1 -0
  70. data/templates/default/layout/html/layout.erb +19 -32
  71. 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 "\x03"
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 "\x11"
58
+ when TerminalKeys::CTRL_Q
59
59
  quit_editor
60
- when "\x13"
60
+ when TerminalKeys::CTRL_S
61
61
  save_editor
62
- when "\x1A"
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 "\x04"
99
+ when TerminalKeys::CTRL_D
98
100
  @editor_state.add_next_occurrence_selection
99
- when "\e[1;4A", "\e[4A"
101
+ when *TerminalKeys::ALT_SHIFT_UP
100
102
  @editor_state.add_vertical_cursor(:up)
101
- when "\e[1;4B", "\e[4B"
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
- ["\e[1;3A", "\e[3A"]
140
+ TerminalKeys::ALT_UP
139
141
  when [:alt, :down]
140
- ["\e[1;3B", "\e[3B"]
142
+ TerminalKeys::ALT_DOWN
141
143
  when [:alt, :right]
142
- ["\e[1;3C", "\e[3C"]
144
+ TerminalKeys::ALT_RIGHT
143
145
  when [:alt, :select_up]
144
- ["\e[1;4A", "\e[4A"]
146
+ TerminalKeys::ALT_SHIFT_UP
145
147
  when [:alt, :select_down]
146
- ["\e[1;4B", "\e[4B"]
148
+ TerminalKeys::ALT_SHIFT_DOWN
147
149
  when [:alt, :select_right]
148
- ["\e[1;4C", "\e[4C"]
150
+ TerminalKeys::ALT_SHIFT_RIGHT
149
151
  when [:ctrl, :up]
150
- ["\e[1;5A", "\e[5A"]
152
+ TerminalKeys::CTRL_UP
151
153
  when [:ctrl, :down]
152
- ["\e[1;5B", "\e[5B"]
154
+ TerminalKeys::CTRL_DOWN
153
155
  when [:ctrl, :right]
154
- ["\e[1;5C", "\e[5C"]
156
+ TerminalKeys::CTRL_RIGHT
155
157
  when [:ctrl, :select_up]
156
- ["\e[1;6A", "\e[6A"]
158
+ TerminalKeys::CTRL_SHIFT_UP
157
159
  when [:ctrl, :select_down]
158
- ["\e[1;6B", "\e[6B"]
160
+ TerminalKeys::CTRL_SHIFT_DOWN
159
161
  when [:ctrl, :select_right]
160
- ["\e[1;6C", "\e[6C"]
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 "\e[1;5C", "\e[5C"
176
+ when *TerminalKeys::CTRL_RIGHT
175
177
  @editor_state.move_line_end
176
- when "\e[1;5D", "\e[5D"
178
+ when *TerminalKeys::CTRL_LEFT
177
179
  @editor_state.move_line_start
178
- when "\e[1;5A", "\e[5A"
180
+ when *TerminalKeys::CTRL_UP
179
181
  @editor_state.move_file_start
180
- when "\e[1;5B", "\e[5B"
182
+ when *TerminalKeys::CTRL_DOWN
181
183
  @editor_state.move_file_end
182
- when "\e[1;4C", "\e[4C"
184
+ when *TerminalKeys::ALT_SHIFT_RIGHT
183
185
  editor_extending_selection { @editor_state.move_to_next_word }
184
- when "\e[1;4D", "\e[4D"
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 "\x00"
195
+ when TerminalKeys::CTRL_SPACE
194
196
  true
195
- when "\x03"
197
+ when TerminalKeys::CTRL_C
196
198
  editor_search_active? ? editor_search_cancel : copy_editor_selection
197
- when "\x06"
199
+ when TerminalKeys::CTRL_F
198
200
  @editor_state.move_right unless editor_search_active?
199
- when "\x16"
201
+ when TerminalKeys::CTRL_V
200
202
  modern_record_undo { @editor_state.yank_kill_buffer } unless editor_search_active?
201
- when "\x18"
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 "\x04", "\x0B", "\x15", "\x17", "\x19", "\e[3~", "\ed", "\eD", "\e\b", "\e\x7F"
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
- paste = read_bracketed_paste(key)
307
- return false unless paste
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 == "\x16" && @editor_state.vibe_mode == "normal"
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", "\x03"
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", "\x03", :escape
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", "\x03", :escape
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", "\x03", :escape
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 == "\x03"
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", "\x02", "\x04", "\x05", "\x06", "\x12", "\x15", "\x19"].include?(key)
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 "\x06"
430
+ when TerminalKeys::CTRL_F
581
431
  @editor_state.page_down(editor_page_rows)
582
- when "\x02"
432
+ when TerminalKeys::CTRL_B
583
433
  @editor_state.page_up(editor_page_rows)
584
- when "\x04"
434
+ when TerminalKeys::CTRL_D
585
435
  @editor_state.page_down(vibe_half_page_rows)
586
- when "\x15"
436
+ when TerminalKeys::CTRL_U
587
437
  @editor_state.page_up(vibe_half_page_rows)
588
- when "\x05"
438
+ when TerminalKeys::CTRL_E
589
439
  vibe_scroll_down
590
- when "\x19"
440
+ when TerminalKeys::CTRL_Y
591
441
  vibe_scroll_up
592
- when "\x12"
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 == "\x03"
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("\e]52;c;#{Base64.strict_encode64(@editor_state.kill_buffer)}\a")
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 ["\x03"].include?(key)
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 << "\e[7m" if selected && match[0] == "\e[0m"
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 ? "\e[7m" : "\e[27m")
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 << "\e[27m" if selected
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
- Pathname.new(@editor_state.path).relative_path_from(Pathname.new(Dir.pwd)).to_s
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