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
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require_relative "../../scratchpad_runner"
|
|
3
|
+
|
|
1
4
|
# Namespace for the Kward CLI agent runtime.
|
|
2
5
|
module Kward
|
|
3
6
|
# Interactive terminal UI used by the CLI frontend.
|
|
@@ -43,6 +46,31 @@ module Kward
|
|
|
43
46
|
"$#{path}"
|
|
44
47
|
end
|
|
45
48
|
|
|
49
|
+
def open_scratchpad(language = :text, content: "")
|
|
50
|
+
language = normalize_scratchpad_language(language)
|
|
51
|
+
@editor_state = EditorState.new(
|
|
52
|
+
path: scratchpad_display_path(language),
|
|
53
|
+
display_path: scratchpad_display_path(language),
|
|
54
|
+
content: content.to_s,
|
|
55
|
+
new_file: true,
|
|
56
|
+
editor_mode: current_editor_mode,
|
|
57
|
+
virtual: true,
|
|
58
|
+
language: language
|
|
59
|
+
)
|
|
60
|
+
@editor_state.status = scratchpad_status_text(language)
|
|
61
|
+
@prompt_label = "Edit>"
|
|
62
|
+
self.composer_input = ""
|
|
63
|
+
self.composer_cursor = 0
|
|
64
|
+
@composer.clear_attachments
|
|
65
|
+
@pending_keys.clear
|
|
66
|
+
@file_overlay_dismissed_token = nil
|
|
67
|
+
@file_open_dismissed_token = nil
|
|
68
|
+
@asking = true
|
|
69
|
+
set_editor_bar_cursor_locked if current_editor_bar_cursor?
|
|
70
|
+
enable_editor_mouse_reporting
|
|
71
|
+
true
|
|
72
|
+
end
|
|
73
|
+
|
|
46
74
|
def open_editor(path, allow_new: false, base_dir: Dir.pwd, restrict_to_workspace: true)
|
|
47
75
|
full_path = File.expand_path(path.to_s, base_dir)
|
|
48
76
|
root = File.expand_path(Dir.pwd)
|
|
@@ -131,6 +159,8 @@ module Kward
|
|
|
131
159
|
disable_editor_mouse_reporting(force: true)
|
|
132
160
|
restore_editor_cursor_shape_locked
|
|
133
161
|
@editor_text_width = nil
|
|
162
|
+
@editor_save_as_active = false
|
|
163
|
+
@editor_save_as_buffer = ""
|
|
134
164
|
@editor_state = nil
|
|
135
165
|
@prompt_label = "You>"
|
|
136
166
|
self.composer_input = ""
|
|
@@ -143,6 +173,7 @@ module Kward
|
|
|
143
173
|
return if key.nil?
|
|
144
174
|
mouse_result = handle_editor_mouse_key(key)
|
|
145
175
|
return mouse_result unless mouse_result == false
|
|
176
|
+
return handle_editor_save_as_key(key) if @editor_save_as_active
|
|
146
177
|
return handle_readonly_editor_key(key) if @editor_state&.readonly?
|
|
147
178
|
return handle_vibe_key(key) if @editor_state&.vibe?
|
|
148
179
|
return handle_emacs_key(key) if @editor_state&.emacs?
|
|
@@ -175,14 +206,14 @@ module Kward
|
|
|
175
206
|
editor_insert_tab unless editor_search_active?
|
|
176
207
|
when "\b", "\x7F"
|
|
177
208
|
editor_search_active? ? editor_search_delete_character : delete_editor_selection || editor_delete_before_cursor
|
|
178
|
-
when
|
|
209
|
+
when TerminalKeys::CTRL_C
|
|
179
210
|
return editor_search_cancel if editor_search_active?
|
|
180
211
|
when "\e"
|
|
181
212
|
return editor_search_cancel if editor_search_active?
|
|
182
213
|
return @editor_state.clear_selection if @editor_state.selection_active?
|
|
183
|
-
when
|
|
214
|
+
when TerminalKeys::CTRL_Q
|
|
184
215
|
quit_editor
|
|
185
|
-
when
|
|
216
|
+
when TerminalKeys::CTRL_S
|
|
186
217
|
save_editor
|
|
187
218
|
when "/"
|
|
188
219
|
clear_editor_selection_before_edit unless editor_search_active?
|
|
@@ -270,20 +301,7 @@ module Kward
|
|
|
270
301
|
end
|
|
271
302
|
|
|
272
303
|
def parse_editor_mouse_key(key)
|
|
273
|
-
|
|
274
|
-
return nil unless match
|
|
275
|
-
|
|
276
|
-
code = match[1].to_i
|
|
277
|
-
{
|
|
278
|
-
code: code,
|
|
279
|
-
button: code & 3,
|
|
280
|
-
column: match[2].to_i,
|
|
281
|
-
row: match[3].to_i,
|
|
282
|
-
action: match[4],
|
|
283
|
-
release: match[4] == "m",
|
|
284
|
-
drag: (code & 32).positive?,
|
|
285
|
-
remaining: key.to_s[match[0].length..].to_s
|
|
286
|
-
}
|
|
304
|
+
parse_sgr_mouse_event(key)
|
|
287
305
|
end
|
|
288
306
|
|
|
289
307
|
def handle_editor_mouse_press(event)
|
|
@@ -451,13 +469,13 @@ module Kward
|
|
|
451
469
|
return false if editor_search_active?
|
|
452
470
|
|
|
453
471
|
case key
|
|
454
|
-
when
|
|
472
|
+
when *TerminalKeys::SHIFT_LEFT
|
|
455
473
|
editor_extending_selection { @editor_state.move_left }
|
|
456
|
-
when
|
|
474
|
+
when *TerminalKeys::SHIFT_RIGHT
|
|
457
475
|
editor_extending_selection { @editor_state.move_right }
|
|
458
|
-
when
|
|
476
|
+
when *TerminalKeys::SHIFT_UP
|
|
459
477
|
editor_extending_selection { editor_move_up }
|
|
460
|
-
when
|
|
478
|
+
when *TerminalKeys::SHIFT_DOWN
|
|
461
479
|
editor_extending_selection { editor_move_down }
|
|
462
480
|
else
|
|
463
481
|
false
|
|
@@ -466,39 +484,39 @@ module Kward
|
|
|
466
484
|
|
|
467
485
|
def handle_editor_key_binding(key)
|
|
468
486
|
case key
|
|
469
|
-
when
|
|
487
|
+
when TerminalKeys::CTRL_A
|
|
470
488
|
@editor_state.move_line_start unless editor_search_active?
|
|
471
|
-
when
|
|
489
|
+
when TerminalKeys::CTRL_B
|
|
472
490
|
@editor_state.move_left unless editor_search_active?
|
|
473
|
-
when
|
|
491
|
+
when TerminalKeys::CTRL_D
|
|
474
492
|
@editor_state.delete_at_cursor unless editor_search_active?
|
|
475
|
-
when
|
|
493
|
+
when TerminalKeys::CTRL_E
|
|
476
494
|
@editor_state.move_line_end unless editor_search_active?
|
|
477
|
-
when
|
|
495
|
+
when TerminalKeys::CTRL_F
|
|
478
496
|
@editor_state.move_right unless editor_search_active?
|
|
479
|
-
when
|
|
497
|
+
when TerminalKeys::CTRL_SPACE
|
|
480
498
|
@editor_state.begin_selection unless editor_search_active?
|
|
481
|
-
when
|
|
499
|
+
when TerminalKeys::CTRL_K
|
|
482
500
|
@editor_state.kill_line_after_cursor unless editor_search_active?
|
|
483
|
-
when
|
|
501
|
+
when TerminalKeys::CTRL_N
|
|
484
502
|
editor_move_down unless editor_search_active?
|
|
485
|
-
when
|
|
503
|
+
when TerminalKeys::CTRL_P
|
|
486
504
|
editor_move_up unless editor_search_active?
|
|
487
|
-
when
|
|
505
|
+
when TerminalKeys::CTRL_U
|
|
488
506
|
@editor_state.kill_line_before_cursor unless editor_search_active?
|
|
489
|
-
when
|
|
507
|
+
when TerminalKeys::CTRL_W
|
|
490
508
|
@editor_state.delete_word_before_cursor unless editor_search_active?
|
|
491
|
-
when
|
|
509
|
+
when TerminalKeys::CTRL_Y
|
|
492
510
|
editor_selection_active? ? copy_editor_selection : @editor_state.yank_kill_buffer unless editor_search_active?
|
|
493
|
-
when
|
|
511
|
+
when *TerminalKeys::LEFT
|
|
494
512
|
@editor_state.move_left unless editor_search_active?
|
|
495
|
-
when
|
|
513
|
+
when *TerminalKeys::RIGHT
|
|
496
514
|
@editor_state.move_right unless editor_search_active?
|
|
497
|
-
when
|
|
515
|
+
when *TerminalKeys::HOME
|
|
498
516
|
@editor_state.move_line_start unless editor_search_active?
|
|
499
|
-
when
|
|
517
|
+
when *TerminalKeys::END_KEY
|
|
500
518
|
@editor_state.move_line_end unless editor_search_active?
|
|
501
|
-
when
|
|
519
|
+
when *TerminalKeys::DELETE
|
|
502
520
|
delete_editor_selection || @editor_state.delete_at_cursor unless editor_search_active?
|
|
503
521
|
when "\eb", "\eB"
|
|
504
522
|
@editor_state.move_to_previous_word unless editor_search_active?
|
|
@@ -600,12 +618,9 @@ module Kward
|
|
|
600
618
|
end
|
|
601
619
|
|
|
602
620
|
def handle_editor_bracketed_paste_key(key)
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
@editor_state.insert(normalize_paste(paste[:content])) unless editor_search_active?
|
|
607
|
-
queue_pending_keys(paste[:remaining]) if paste[:remaining] && !paste[:remaining].empty?
|
|
608
|
-
true
|
|
621
|
+
handle_bracketed_paste(key) do |content|
|
|
622
|
+
@editor_state.insert(content) unless editor_search_active?
|
|
623
|
+
end
|
|
609
624
|
end
|
|
610
625
|
|
|
611
626
|
def ctrl_code(code)
|
|
@@ -669,12 +684,12 @@ module Kward
|
|
|
669
684
|
return named_result unless named_result == false || named_result.nil?
|
|
670
685
|
|
|
671
686
|
case key
|
|
672
|
-
when
|
|
687
|
+
when TerminalKeys::CTRL_Q
|
|
673
688
|
close_editor
|
|
674
|
-
when
|
|
689
|
+
when TerminalKeys::CTRL_F
|
|
675
690
|
editor_search_active? ? editor_search_append(key) : editor_search_begin
|
|
676
|
-
when
|
|
677
|
-
editor_search_cancel
|
|
691
|
+
when TerminalKeys::CTRL_C
|
|
692
|
+
editor_search_active? ? editor_search_cancel : copy_editor_selection
|
|
678
693
|
when "/"
|
|
679
694
|
editor_search_active? ? editor_search_append(key) : editor_search_begin
|
|
680
695
|
when "\b", "\x7F"
|
|
@@ -707,6 +722,10 @@ module Kward
|
|
|
707
722
|
return editor_search_active? ? editor_search_append(key) : editor_search_begin
|
|
708
723
|
end
|
|
709
724
|
|
|
725
|
+
if (ctrl_modifier?(modifier) || super_modifier?(modifier)) && ctrl_code(code) == 99
|
|
726
|
+
return editor_search_active? ? editor_search_cancel : copy_editor_selection
|
|
727
|
+
end
|
|
728
|
+
|
|
710
729
|
case code
|
|
711
730
|
when 13
|
|
712
731
|
editor_search_confirm if editor_search_active?
|
|
@@ -726,12 +745,9 @@ module Kward
|
|
|
726
745
|
end
|
|
727
746
|
|
|
728
747
|
def handle_readonly_bracketed_paste_key(key)
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
queue_pending_keys(paste[:remaining]) if paste[:remaining] && !paste[:remaining].empty?
|
|
733
|
-
@editor_state.status = "Read-only diff" unless editor_search_active?
|
|
734
|
-
true
|
|
748
|
+
handle_bracketed_paste(key) do |_content|
|
|
749
|
+
@editor_state.status = "Read-only diff" unless editor_search_active?
|
|
750
|
+
end
|
|
735
751
|
end
|
|
736
752
|
|
|
737
753
|
def handle_readonly_named_key(key_name)
|
|
@@ -786,12 +802,18 @@ module Kward
|
|
|
786
802
|
line, column = @editor_state.cursor_line_and_column
|
|
787
803
|
text_width = current_editor_text_width
|
|
788
804
|
row_start = editor_visual_row_start_column(line, column, text_width)
|
|
805
|
+
visual_column = column - row_start
|
|
789
806
|
if row_start.positive?
|
|
790
|
-
target_column = row_start - text_width +
|
|
807
|
+
target_column = row_start - text_width + visual_column
|
|
791
808
|
return @editor_state.set_cursor_line_and_column(line, target_column)
|
|
792
809
|
end
|
|
793
810
|
|
|
794
|
-
@editor_state.move_up
|
|
811
|
+
return @editor_state.move_up if line.zero?
|
|
812
|
+
|
|
813
|
+
previous_line = @editor_state.lines[line - 1].to_s
|
|
814
|
+
previous_row_start = editor_last_visual_row_start_column(previous_line, text_width)
|
|
815
|
+
target_column = [previous_row_start + visual_column, previous_line.length].min
|
|
816
|
+
@editor_state.set_cursor_line_and_column(line - 1, target_column)
|
|
795
817
|
end
|
|
796
818
|
|
|
797
819
|
def editor_move_down
|
|
@@ -800,14 +822,26 @@ module Kward
|
|
|
800
822
|
line, column = @editor_state.cursor_line_and_column
|
|
801
823
|
text_width = current_editor_text_width
|
|
802
824
|
row_start = editor_visual_row_start_column(line, column, text_width)
|
|
825
|
+
visual_column = column - row_start
|
|
803
826
|
next_start = row_start + text_width
|
|
804
827
|
current_line = @editor_state.lines[line].to_s
|
|
805
828
|
if next_start < current_line.length
|
|
806
|
-
target_column = [next_start +
|
|
829
|
+
target_column = [next_start + visual_column, current_line.length].min
|
|
807
830
|
return @editor_state.set_cursor_line_and_column(line, target_column)
|
|
808
831
|
end
|
|
809
832
|
|
|
810
|
-
@editor_state.move_down
|
|
833
|
+
return @editor_state.move_down if line >= @editor_state.lines.length - 1
|
|
834
|
+
|
|
835
|
+
next_line = @editor_state.lines[line + 1].to_s
|
|
836
|
+
target_column = [visual_column, next_line.length].min
|
|
837
|
+
@editor_state.set_cursor_line_and_column(line + 1, target_column)
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
def editor_last_visual_row_start_column(line, text_width)
|
|
841
|
+
length = line.to_s.length
|
|
842
|
+
return 0 if length.zero?
|
|
843
|
+
|
|
844
|
+
((length - 1) / text_width) * text_width
|
|
811
845
|
end
|
|
812
846
|
|
|
813
847
|
def current_editor_text_width
|
|
@@ -845,7 +879,7 @@ module Kward
|
|
|
845
879
|
return false if text.empty?
|
|
846
880
|
|
|
847
881
|
@editor_state.push_kill(text)
|
|
848
|
-
@output_io.print(
|
|
882
|
+
@output_io.print(TerminalSequences.osc52(text))
|
|
849
883
|
@output_io.flush if @output_io.respond_to?(:flush)
|
|
850
884
|
@editor_state.clear_selection
|
|
851
885
|
@editor_state.status = "Copied selection"
|
|
@@ -923,7 +957,7 @@ module Kward
|
|
|
923
957
|
def enable_editor_mouse_reporting
|
|
924
958
|
return if @editor_mouse_reporting_enabled
|
|
925
959
|
|
|
926
|
-
@output_io.print(
|
|
960
|
+
@output_io.print(TerminalSequences::MOUSE_REPORTING_ENABLE)
|
|
927
961
|
@output_io.flush if @output_io.respond_to?(:flush)
|
|
928
962
|
@editor_mouse_reporting_enabled = true
|
|
929
963
|
end
|
|
@@ -931,7 +965,7 @@ module Kward
|
|
|
931
965
|
def disable_editor_mouse_reporting(force: false)
|
|
932
966
|
return unless force || @editor_mouse_reporting_enabled
|
|
933
967
|
|
|
934
|
-
@output_io.print(
|
|
968
|
+
@output_io.print(TerminalSequences::MOUSE_REPORTING_DISABLE)
|
|
935
969
|
@output_io.flush if @output_io.respond_to?(:flush)
|
|
936
970
|
@editor_mouse_reporting_enabled = false
|
|
937
971
|
end
|
|
@@ -989,19 +1023,33 @@ module Kward
|
|
|
989
1023
|
true
|
|
990
1024
|
end
|
|
991
1025
|
|
|
992
|
-
def save_editor
|
|
1026
|
+
def save_editor(path = nil, prompt_for_path: true)
|
|
993
1027
|
return false unless @editor_state
|
|
994
1028
|
if @editor_state.readonly?
|
|
995
1029
|
@editor_state.status = "Read-only diff"
|
|
996
1030
|
return true
|
|
997
1031
|
end
|
|
998
1032
|
|
|
1033
|
+
if path.to_s.strip.empty? && @editor_state.path.to_s.empty?
|
|
1034
|
+
if prompt_for_path
|
|
1035
|
+
begin_editor_save_as
|
|
1036
|
+
return true
|
|
1037
|
+
end
|
|
1038
|
+
|
|
1039
|
+
@editor_state.status = "Use :w filename"
|
|
1040
|
+
return false
|
|
1041
|
+
end
|
|
1042
|
+
|
|
1043
|
+
return false if !path.to_s.strip.empty? && !bind_editor_save_path(path)
|
|
1044
|
+
|
|
999
1045
|
if @editor_state.file_changed_on_disk? && !@editor_state.overwrite_confirmed
|
|
1000
1046
|
@editor_state.overwrite_confirmed = true
|
|
1001
1047
|
@editor_state.status = "File changed on disk. Press Ctrl+S again to overwrite."
|
|
1002
1048
|
return true
|
|
1003
1049
|
end
|
|
1004
1050
|
|
|
1051
|
+
parent = File.dirname(@editor_state.path)
|
|
1052
|
+
FileUtils.mkdir_p(parent) unless Dir.exist?(parent)
|
|
1005
1053
|
File.write(@editor_state.path, @editor_state.buffer)
|
|
1006
1054
|
@editor_state.refresh_after_save(@editor_state.buffer)
|
|
1007
1055
|
true
|
|
@@ -1010,6 +1058,158 @@ module Kward
|
|
|
1010
1058
|
false
|
|
1011
1059
|
end
|
|
1012
1060
|
|
|
1061
|
+
def begin_editor_save_as
|
|
1062
|
+
@editor_save_as_active = true
|
|
1063
|
+
@editor_save_as_buffer = ""
|
|
1064
|
+
@editor_state.status = "Save as: "
|
|
1065
|
+
true
|
|
1066
|
+
end
|
|
1067
|
+
|
|
1068
|
+
def handle_editor_save_as_key(key)
|
|
1069
|
+
return true if handle_bundled_key(key) { |token| handle_editor_save_as_key(token) }
|
|
1070
|
+
|
|
1071
|
+
csi_result = handle_editor_save_as_csi_u_key(key)
|
|
1072
|
+
return csi_result unless csi_result == false
|
|
1073
|
+
|
|
1074
|
+
case key
|
|
1075
|
+
when "\n", "\r"
|
|
1076
|
+
finish_editor_save_as
|
|
1077
|
+
when "\e", TerminalKeys::CTRL_C
|
|
1078
|
+
cancel_editor_save_as
|
|
1079
|
+
when "\b", "\x7F"
|
|
1080
|
+
@editor_save_as_buffer = @editor_save_as_buffer.to_s[0...-1]
|
|
1081
|
+
update_editor_save_as_status
|
|
1082
|
+
else
|
|
1083
|
+
key_name = key_name_for(key)
|
|
1084
|
+
case key_name
|
|
1085
|
+
when :return, :enter
|
|
1086
|
+
finish_editor_save_as
|
|
1087
|
+
when :backspace
|
|
1088
|
+
@editor_save_as_buffer = @editor_save_as_buffer.to_s[0...-1]
|
|
1089
|
+
update_editor_save_as_status
|
|
1090
|
+
else
|
|
1091
|
+
if printable_key?(key)
|
|
1092
|
+
@editor_save_as_buffer = @editor_save_as_buffer.to_s + key
|
|
1093
|
+
update_editor_save_as_status
|
|
1094
|
+
end
|
|
1095
|
+
end
|
|
1096
|
+
end
|
|
1097
|
+
true
|
|
1098
|
+
end
|
|
1099
|
+
|
|
1100
|
+
def handle_editor_save_as_csi_u_key(key)
|
|
1101
|
+
sequence = parse_csi_u_key(key)
|
|
1102
|
+
return false unless sequence
|
|
1103
|
+
|
|
1104
|
+
queue_pending_keys(sequence[:remaining]) if sequence[:remaining] && !sequence[:remaining].empty?
|
|
1105
|
+
code = sequence[:code]
|
|
1106
|
+
modifier = sequence[:modifier]
|
|
1107
|
+
normalized_code = ctrl_code(code)
|
|
1108
|
+
if code == 13
|
|
1109
|
+
return finish_editor_save_as
|
|
1110
|
+
elsif [8, 127].include?(code)
|
|
1111
|
+
@editor_save_as_buffer = @editor_save_as_buffer.to_s[0...-1]
|
|
1112
|
+
return update_editor_save_as_status
|
|
1113
|
+
elsif code == 27 || (ctrl_modifier?(modifier) && normalized_code == 99)
|
|
1114
|
+
return cancel_editor_save_as
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
text = csi_u_printable_text(sequence)
|
|
1118
|
+
if text
|
|
1119
|
+
@editor_save_as_buffer = @editor_save_as_buffer.to_s + text
|
|
1120
|
+
return update_editor_save_as_status
|
|
1121
|
+
end
|
|
1122
|
+
|
|
1123
|
+
true
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1126
|
+
def finish_editor_save_as
|
|
1127
|
+
path = @editor_save_as_buffer.to_s.strip
|
|
1128
|
+
@editor_save_as_active = false
|
|
1129
|
+
@editor_save_as_buffer = ""
|
|
1130
|
+
if path.empty?
|
|
1131
|
+
@editor_state.status = "Save canceled"
|
|
1132
|
+
return true
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
save_editor(path, prompt_for_path: false)
|
|
1136
|
+
end
|
|
1137
|
+
|
|
1138
|
+
def cancel_editor_save_as
|
|
1139
|
+
@editor_save_as_active = false
|
|
1140
|
+
@editor_save_as_buffer = ""
|
|
1141
|
+
@editor_state.status = "Save canceled"
|
|
1142
|
+
true
|
|
1143
|
+
end
|
|
1144
|
+
|
|
1145
|
+
def update_editor_save_as_status
|
|
1146
|
+
@editor_state.status = "Save as: #{@editor_save_as_buffer}"
|
|
1147
|
+
true
|
|
1148
|
+
end
|
|
1149
|
+
|
|
1150
|
+
def bind_editor_save_path(path)
|
|
1151
|
+
full_path = File.expand_path(path.to_s.strip, Dir.pwd)
|
|
1152
|
+
root = File.expand_path(Dir.pwd)
|
|
1153
|
+
unless full_path == root || full_path.start_with?("#{root}/")
|
|
1154
|
+
@editor_state.status = "Cannot save outside workspace"
|
|
1155
|
+
return false
|
|
1156
|
+
end
|
|
1157
|
+
|
|
1158
|
+
@editor_state.bind_path(full_path)
|
|
1159
|
+
@editor_syntax_language_path = nil
|
|
1160
|
+
@editor_indent_unit_path = nil
|
|
1161
|
+
true
|
|
1162
|
+
end
|
|
1163
|
+
|
|
1164
|
+
def run_editor_buffer
|
|
1165
|
+
return false unless @editor_state
|
|
1166
|
+
|
|
1167
|
+
language = @editor_state.language || editor_syntax_language
|
|
1168
|
+
result = ScratchpadRunner.run(language, @editor_state.buffer)
|
|
1169
|
+
@editor_state.replace_range(0, @editor_state.buffer.length, result.buffer)
|
|
1170
|
+
@editor_state.status = "Ran #{language} (exit #{result.exit_status})"
|
|
1171
|
+
true
|
|
1172
|
+
rescue StandardError => e
|
|
1173
|
+
@editor_state.status = "Run failed: #{e.message}" if @editor_state
|
|
1174
|
+
false
|
|
1175
|
+
end
|
|
1176
|
+
|
|
1177
|
+
def normalize_scratchpad_language(language)
|
|
1178
|
+
case language.to_s.strip.downcase
|
|
1179
|
+
when "", "text", "txt"
|
|
1180
|
+
:text
|
|
1181
|
+
when "markdown", "md"
|
|
1182
|
+
:markdown
|
|
1183
|
+
when "ruby", "rb"
|
|
1184
|
+
:ruby
|
|
1185
|
+
else
|
|
1186
|
+
:text
|
|
1187
|
+
end
|
|
1188
|
+
end
|
|
1189
|
+
|
|
1190
|
+
def scratchpad_display_path(language)
|
|
1191
|
+
case language.to_sym
|
|
1192
|
+
when :markdown
|
|
1193
|
+
"scratchpad.md"
|
|
1194
|
+
when :ruby
|
|
1195
|
+
"scratchpad.rb"
|
|
1196
|
+
else
|
|
1197
|
+
"scratchpad.txt"
|
|
1198
|
+
end
|
|
1199
|
+
end
|
|
1200
|
+
|
|
1201
|
+
def scratchpad_status_text(language)
|
|
1202
|
+
runnable = language.to_sym == :ruby
|
|
1203
|
+
case current_editor_mode
|
|
1204
|
+
when "vibe"
|
|
1205
|
+
runnable ? "NORMAL · i insert · :w filename save · :q quit · :run run" : "NORMAL · i insert · :w filename save · :q quit"
|
|
1206
|
+
when "emacs"
|
|
1207
|
+
runnable ? "C-x C-s save as · C-x C-c quit · C-r run" : "C-x C-s save as · C-x C-c quit"
|
|
1208
|
+
else
|
|
1209
|
+
runnable ? "Ctrl+S save as · Ctrl+Q quit · Ctrl+R run" : "Ctrl+S save as · Ctrl+Q quit"
|
|
1210
|
+
end
|
|
1211
|
+
end
|
|
1212
|
+
|
|
1013
1213
|
def printable_key?(key)
|
|
1014
1214
|
key.is_a?(String) && key.length == 1 && key.match?(/[[:print:]]/)
|
|
1015
1215
|
end
|
|
@@ -37,44 +37,44 @@ module Kward
|
|
|
37
37
|
when "\b", "\x7F"
|
|
38
38
|
clear_editor_selection_before_edit unless editor_search_active?
|
|
39
39
|
editor_search_active? ? editor_search_delete_character : editor_delete_before_cursor
|
|
40
|
-
when
|
|
40
|
+
when TerminalKeys::CTRL_SPACE
|
|
41
41
|
@editor_state.begin_selection unless editor_search_active?
|
|
42
|
-
when
|
|
42
|
+
when TerminalKeys::CTRL_A
|
|
43
43
|
@editor_state.move_line_start unless editor_search_active?
|
|
44
|
-
when
|
|
44
|
+
when TerminalKeys::CTRL_B
|
|
45
45
|
@editor_state.move_left unless editor_search_active?
|
|
46
|
-
when
|
|
46
|
+
when TerminalKeys::CTRL_D
|
|
47
47
|
@editor_state.delete_at_cursor unless editor_search_active?
|
|
48
|
-
when
|
|
48
|
+
when TerminalKeys::CTRL_E
|
|
49
49
|
@editor_state.move_line_end unless editor_search_active?
|
|
50
|
-
when
|
|
50
|
+
when TerminalKeys::CTRL_F
|
|
51
51
|
@editor_state.move_right unless editor_search_active?
|
|
52
52
|
when "\x07"
|
|
53
53
|
emacs_cancel
|
|
54
|
-
when
|
|
54
|
+
when TerminalKeys::CTRL_K
|
|
55
55
|
if editor_selection_active?
|
|
56
56
|
emacs_kill_selection
|
|
57
57
|
else
|
|
58
58
|
@editor_state.kill_line_after_cursor unless editor_search_active?
|
|
59
59
|
end
|
|
60
|
-
when
|
|
60
|
+
when TerminalKeys::CTRL_N
|
|
61
61
|
editor_move_down unless editor_search_active?
|
|
62
|
-
when
|
|
62
|
+
when TerminalKeys::CTRL_P
|
|
63
63
|
editor_move_up unless editor_search_active?
|
|
64
|
-
when
|
|
64
|
+
when TerminalKeys::CTRL_R
|
|
65
65
|
editor_search_active? ? editor_search_append(key) : editor_search_begin(:backward)
|
|
66
|
-
when
|
|
66
|
+
when TerminalKeys::CTRL_S
|
|
67
67
|
editor_search_active? ? editor_search_append(key) : editor_search_begin(:forward)
|
|
68
|
-
when
|
|
68
|
+
when TerminalKeys::CTRL_U
|
|
69
69
|
@editor_state.kill_line_before_cursor unless editor_search_active?
|
|
70
|
-
when
|
|
70
|
+
when TerminalKeys::CTRL_V
|
|
71
71
|
@editor_state.page_down(editor_page_rows) unless editor_search_active?
|
|
72
|
-
when
|
|
72
|
+
when TerminalKeys::CTRL_W
|
|
73
73
|
editor_selection_active? ? emacs_kill_selection : @editor_state.delete_word_before_cursor unless editor_search_active?
|
|
74
|
-
when
|
|
74
|
+
when TerminalKeys::CTRL_X
|
|
75
75
|
@editor_state.emacs_pending = "C-x"
|
|
76
76
|
@editor_state.status = "C-x"
|
|
77
|
-
when
|
|
77
|
+
when TerminalKeys::CTRL_Y
|
|
78
78
|
@editor_state.yank_from_kill_ring unless editor_search_active?
|
|
79
79
|
when "\e"
|
|
80
80
|
return editor_search_cancel if editor_search_active?
|
|
@@ -192,9 +192,9 @@ module Kward
|
|
|
192
192
|
@editor_state.emacs_pending = nil
|
|
193
193
|
key = emacs_ctrl_x_csi_u_key(key)
|
|
194
194
|
case key
|
|
195
|
-
when
|
|
195
|
+
when TerminalKeys::CTRL_S
|
|
196
196
|
save_editor
|
|
197
|
-
when
|
|
197
|
+
when TerminalKeys::CTRL_C
|
|
198
198
|
quit_editor("Unsaved changes. Press C-x C-c again to discard.")
|
|
199
199
|
else
|
|
200
200
|
@editor_state.status = "Unknown C-x command"
|
|
@@ -209,9 +209,9 @@ module Kward
|
|
|
209
209
|
queue_pending_keys(sequence[:remaining]) if sequence[:remaining] && !sequence[:remaining].empty?
|
|
210
210
|
case ctrl_code(sequence[:code])
|
|
211
211
|
when 99
|
|
212
|
-
|
|
212
|
+
TerminalKeys::CTRL_C
|
|
213
213
|
when 115
|
|
214
|
-
|
|
214
|
+
TerminalKeys::CTRL_S
|
|
215
215
|
else
|
|
216
216
|
key
|
|
217
217
|
end
|
|
@@ -236,7 +236,7 @@ module Kward
|
|
|
236
236
|
end
|
|
237
237
|
|
|
238
238
|
@editor_state.copy_for_kill_ring(range[0], range[1])
|
|
239
|
-
@output_io.print(
|
|
239
|
+
@output_io.print(TerminalSequences.osc52(@editor_state.kill_buffer))
|
|
240
240
|
@output_io.flush if @output_io.respond_to?(:flush)
|
|
241
241
|
@editor_state.clear_selection
|
|
242
242
|
@editor_state.status = "Copied region"
|