kward 0.71.0 → 0.72.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/CHANGELOG.md +41 -1
- 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 +394 -0
- data/doc/extensibility.md +16 -7
- data/doc/files.md +100 -0
- data/doc/getting-started.md +25 -18
- data/doc/git.md +122 -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 +74 -4
- data/doc/session-management.md +35 -1
- data/doc/shell.md +286 -0
- data/doc/tabs.md +122 -0
- data/doc/troubleshooting.md +77 -1
- data/doc/usage.md +53 -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/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/runtime_helpers.rb +133 -3
- data/lib/kward/cli/sessions.rb +2 -2
- data/lib/kward/cli/settings.rb +218 -9
- data/lib/kward/cli/slash_commands.rb +415 -2
- data/lib/kward/cli/tabs.rb +695 -0
- data/lib/kward/cli.rb +158 -26
- data/lib/kward/config_files.rb +123 -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 +362 -0
- data/lib/kward/plugin_registry.rb +61 -0
- data/lib/kward/project_files.rb +52 -0
- data/lib/kward/prompt_history.rb +82 -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 +509 -0
- data/lib/kward/prompt_interface/editor/buffer.rb +109 -0
- data/lib/kward/prompt_interface/editor/controller.rb +1018 -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 +353 -0
- data/lib/kward/prompt_interface/editor/modes/vibe.rb +1962 -0
- data/lib/kward/prompt_interface/editor/renderer.rb +243 -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 +1249 -0
- data/lib/kward/prompt_interface/editor/status_text.rb +23 -0
- data/lib/kward/prompt_interface/editor/syntax_highlighter.rb +420 -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 +299 -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 +387 -35
- 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 +98 -50
- data/lib/kward/prompt_interface/runtime_state.rb +43 -0
- data/lib/kward/prompt_interface/screen.rb +16 -0
- data/lib/kward/prompt_interface/selection_prompt.rb +7 -13
- 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 +286 -8
- data/lib/kward/prompts/commands.rb +5 -0
- data/lib/kward/prompts.rb +2 -0
- data/lib/kward/rpc/server.rb +42 -3
- data/lib/kward/rpc/session_manager.rb +35 -47
- data/lib/kward/rpc/session_tree_rows.rb +9 -115
- data/lib/kward/rpc/tool_event_normalizer.rb +1 -1
- 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/text_boundary.rb +25 -0
- data/lib/kward/tools/context_budget_stats.rb +54 -0
- data/lib/kward/tools/context_for_task.rb +202 -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 +68 -0
- data/lib/kward/workers/live_view.rb +49 -0
- data/lib/kward/workers/manager.rb +288 -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 +7 -0
- data/lib/kward/workspace.rb +110 -24
- data/templates/default/fulldoc/html/css/kward.css +107 -36
- data/templates/default/kward_navigation.rb +12 -1
- data/templates/default/layout/html/layout.erb +4 -2
- data/templates/default/layout/html/setup.rb +6 -0
- metadata +53 -1
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# Namespace for the Kward CLI agent runtime.
|
|
2
|
+
module Kward
|
|
3
|
+
# Interactive terminal UI used by the CLI frontend.
|
|
4
|
+
class PromptInterface
|
|
5
|
+
# Emacs-style keymap for the built-in composer file editor.
|
|
6
|
+
module EmacsEditorMode
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def handle_emacs_key(key)
|
|
10
|
+
return if handle_editor_bracketed_paste_key(key)
|
|
11
|
+
|
|
12
|
+
if @editor_state.emacs_pending == "C-x"
|
|
13
|
+
return handle_emacs_ctrl_x_key(key)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
csi_result = handle_emacs_csi_u_key(key)
|
|
17
|
+
return csi_result unless csi_result == false
|
|
18
|
+
|
|
19
|
+
shift_result = handle_editor_shift_navigation_key(key)
|
|
20
|
+
return shift_result unless shift_result == false
|
|
21
|
+
|
|
22
|
+
editor_tab_result = handle_editor_tab_key(key)
|
|
23
|
+
return editor_tab_result unless editor_tab_result == false
|
|
24
|
+
|
|
25
|
+
tab_result = handle_tab_key_binding(key)
|
|
26
|
+
return tab_result unless tab_result == false
|
|
27
|
+
|
|
28
|
+
return true if handle_bundled_key(key) { |token| handle_emacs_key(token) }
|
|
29
|
+
|
|
30
|
+
case key
|
|
31
|
+
when "\n", "\r"
|
|
32
|
+
return editor_search_confirm if editor_search_active?
|
|
33
|
+
clear_editor_selection_before_edit
|
|
34
|
+
editor_insert_newline
|
|
35
|
+
when "\t"
|
|
36
|
+
editor_insert_tab unless editor_search_active?
|
|
37
|
+
when "\b", "\x7F"
|
|
38
|
+
clear_editor_selection_before_edit unless editor_search_active?
|
|
39
|
+
editor_search_active? ? editor_search_delete_character : editor_delete_before_cursor
|
|
40
|
+
when "\x00"
|
|
41
|
+
@editor_state.begin_selection unless editor_search_active?
|
|
42
|
+
when "\x01"
|
|
43
|
+
@editor_state.move_line_start unless editor_search_active?
|
|
44
|
+
when "\x02"
|
|
45
|
+
@editor_state.move_left unless editor_search_active?
|
|
46
|
+
when "\x04"
|
|
47
|
+
@editor_state.delete_at_cursor unless editor_search_active?
|
|
48
|
+
when "\x05"
|
|
49
|
+
@editor_state.move_line_end unless editor_search_active?
|
|
50
|
+
when "\x06"
|
|
51
|
+
@editor_state.move_right unless editor_search_active?
|
|
52
|
+
when "\x07"
|
|
53
|
+
emacs_cancel
|
|
54
|
+
when "\x0B"
|
|
55
|
+
if editor_selection_active?
|
|
56
|
+
emacs_kill_selection
|
|
57
|
+
else
|
|
58
|
+
@editor_state.kill_line_after_cursor unless editor_search_active?
|
|
59
|
+
end
|
|
60
|
+
when "\x0E"
|
|
61
|
+
editor_move_down unless editor_search_active?
|
|
62
|
+
when "\x10"
|
|
63
|
+
editor_move_up unless editor_search_active?
|
|
64
|
+
when "\x12"
|
|
65
|
+
editor_search_active? ? editor_search_append(key) : editor_search_begin(:backward)
|
|
66
|
+
when "\x13"
|
|
67
|
+
editor_search_active? ? editor_search_append(key) : editor_search_begin(:forward)
|
|
68
|
+
when "\x15"
|
|
69
|
+
@editor_state.kill_line_before_cursor unless editor_search_active?
|
|
70
|
+
when "\x16"
|
|
71
|
+
@editor_state.page_down(editor_page_rows) unless editor_search_active?
|
|
72
|
+
when "\x17"
|
|
73
|
+
editor_selection_active? ? emacs_kill_selection : @editor_state.delete_word_before_cursor unless editor_search_active?
|
|
74
|
+
when "\x18"
|
|
75
|
+
@editor_state.emacs_pending = "C-x"
|
|
76
|
+
@editor_state.status = "C-x"
|
|
77
|
+
when "\x19"
|
|
78
|
+
@editor_state.yank_from_kill_ring unless editor_search_active?
|
|
79
|
+
when "\e"
|
|
80
|
+
return editor_search_cancel if editor_search_active?
|
|
81
|
+
@editor_state.clear_selection
|
|
82
|
+
when "\eb", "\eB"
|
|
83
|
+
@editor_state.move_to_previous_word unless editor_search_active?
|
|
84
|
+
when "\ed", "\eD"
|
|
85
|
+
@editor_state.delete_word_after_cursor unless editor_search_active?
|
|
86
|
+
when "\ef", "\eF"
|
|
87
|
+
@editor_state.move_to_next_word unless editor_search_active?
|
|
88
|
+
when "\ev", "\eV"
|
|
89
|
+
@editor_state.page_up(editor_page_rows) unless editor_search_active?
|
|
90
|
+
when "\ew", "\eW"
|
|
91
|
+
emacs_copy_selection unless editor_search_active?
|
|
92
|
+
when "\ey", "\eY"
|
|
93
|
+
@editor_state.yank_pop unless editor_search_active?
|
|
94
|
+
when "\e", "\e\x7F"
|
|
95
|
+
@editor_state.delete_word_before_cursor unless editor_search_active?
|
|
96
|
+
else
|
|
97
|
+
ansi_result = handle_editor_modified_ansi_key(key)
|
|
98
|
+
return ansi_result unless ansi_result == false
|
|
99
|
+
|
|
100
|
+
key_name = key_name_for(key)
|
|
101
|
+
named_result = handle_editor_named_key(key_name) if key_name
|
|
102
|
+
return named_result unless named_result == false || named_result.nil?
|
|
103
|
+
|
|
104
|
+
if editor_search_active?
|
|
105
|
+
editor_search_append(key) if printable_key?(key)
|
|
106
|
+
elsif printable_key?(key)
|
|
107
|
+
editor_insert_printable(key)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def handle_emacs_csi_u_key(key)
|
|
113
|
+
sequence = parse_csi_u_key(key)
|
|
114
|
+
return false unless sequence
|
|
115
|
+
|
|
116
|
+
code = sequence[:code]
|
|
117
|
+
modifier = sequence[:modifier]
|
|
118
|
+
queue_pending_keys(sequence[:remaining]) if sequence[:remaining] && !sequence[:remaining].empty?
|
|
119
|
+
sequence = sequence.merge(remaining: "")
|
|
120
|
+
normalized_code = ctrl_code(code)
|
|
121
|
+
|
|
122
|
+
if ctrl_modifier?(modifier)
|
|
123
|
+
case normalized_code
|
|
124
|
+
when 13
|
|
125
|
+
return false if editor_search_active?
|
|
126
|
+
|
|
127
|
+
clear_editor_selection_before_edit
|
|
128
|
+
editor_insert_endwise_modifier_newline
|
|
129
|
+
when 32
|
|
130
|
+
@editor_state.begin_selection unless editor_search_active?
|
|
131
|
+
when 97
|
|
132
|
+
@editor_state.move_line_start unless editor_search_active?
|
|
133
|
+
when 98
|
|
134
|
+
@editor_state.move_left unless editor_search_active?
|
|
135
|
+
when 99, 103
|
|
136
|
+
emacs_cancel
|
|
137
|
+
when 100
|
|
138
|
+
@editor_state.delete_at_cursor unless editor_search_active?
|
|
139
|
+
when 101
|
|
140
|
+
@editor_state.move_line_end unless editor_search_active?
|
|
141
|
+
when 102
|
|
142
|
+
@editor_state.move_right unless editor_search_active?
|
|
143
|
+
when 107
|
|
144
|
+
@editor_state.kill_line_after_cursor unless editor_search_active?
|
|
145
|
+
when 110
|
|
146
|
+
editor_move_down unless editor_search_active?
|
|
147
|
+
when 112
|
|
148
|
+
editor_move_up unless editor_search_active?
|
|
149
|
+
when 114
|
|
150
|
+
editor_search_active? ? editor_search_append(key) : editor_search_begin(:backward)
|
|
151
|
+
when 115
|
|
152
|
+
editor_search_active? ? editor_search_append(key) : editor_search_begin(:forward)
|
|
153
|
+
when 118
|
|
154
|
+
@editor_state.page_down(editor_page_rows) unless editor_search_active?
|
|
155
|
+
when 119
|
|
156
|
+
editor_selection_active? ? emacs_kill_selection : @editor_state.delete_word_before_cursor unless editor_search_active?
|
|
157
|
+
when 120
|
|
158
|
+
@editor_state.emacs_pending = "C-x"
|
|
159
|
+
@editor_state.status = "C-x"
|
|
160
|
+
when 121
|
|
161
|
+
@editor_state.yank_from_kill_ring unless editor_search_active?
|
|
162
|
+
else
|
|
163
|
+
return false
|
|
164
|
+
end
|
|
165
|
+
elsif alt_modifier?(modifier)
|
|
166
|
+
case normalized_code
|
|
167
|
+
when 98
|
|
168
|
+
@editor_state.move_to_previous_word unless editor_search_active?
|
|
169
|
+
when 100
|
|
170
|
+
@editor_state.delete_word_after_cursor unless editor_search_active?
|
|
171
|
+
when 102
|
|
172
|
+
@editor_state.move_to_next_word unless editor_search_active?
|
|
173
|
+
when 118
|
|
174
|
+
@editor_state.page_up(editor_page_rows) unless editor_search_active?
|
|
175
|
+
when 119
|
|
176
|
+
emacs_copy_selection unless editor_search_active?
|
|
177
|
+
when 121
|
|
178
|
+
@editor_state.yank_pop unless editor_search_active?
|
|
179
|
+
else
|
|
180
|
+
return false
|
|
181
|
+
end
|
|
182
|
+
elsif code == 9 && !ctrl_modifier?(modifier) && !alt_modifier?(modifier) && !super_modifier?(modifier)
|
|
183
|
+
return false if editor_search_active?
|
|
184
|
+
|
|
185
|
+
shift_modifier?(modifier) ? editor_outdent_tab : editor_insert_tab
|
|
186
|
+
else
|
|
187
|
+
handle_parsed_editor_csi_u_key(sequence)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def handle_emacs_ctrl_x_key(key)
|
|
192
|
+
@editor_state.emacs_pending = nil
|
|
193
|
+
key = emacs_ctrl_x_csi_u_key(key)
|
|
194
|
+
case key
|
|
195
|
+
when "\x13"
|
|
196
|
+
save_editor
|
|
197
|
+
when "\x03"
|
|
198
|
+
quit_editor("Unsaved changes. Press C-x C-c again to discard.")
|
|
199
|
+
else
|
|
200
|
+
@editor_state.status = "Unknown C-x command"
|
|
201
|
+
true
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def emacs_ctrl_x_csi_u_key(key)
|
|
206
|
+
sequence = parse_csi_u_key(key)
|
|
207
|
+
return key unless sequence && ctrl_modifier?(sequence[:modifier])
|
|
208
|
+
|
|
209
|
+
queue_pending_keys(sequence[:remaining]) if sequence[:remaining] && !sequence[:remaining].empty?
|
|
210
|
+
case ctrl_code(sequence[:code])
|
|
211
|
+
when 99
|
|
212
|
+
"\x03"
|
|
213
|
+
when 115
|
|
214
|
+
"\x13"
|
|
215
|
+
else
|
|
216
|
+
key
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def emacs_kill_selection
|
|
221
|
+
return false unless editor_selection_active?
|
|
222
|
+
|
|
223
|
+
range = @editor_state.selection_range
|
|
224
|
+
@editor_state.cut_range(range[0], range[1])
|
|
225
|
+
@editor_state.status = "Killed region"
|
|
226
|
+
true
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def emacs_copy_selection
|
|
230
|
+
if editor_selection_active?
|
|
231
|
+
range = @editor_state.selection_range
|
|
232
|
+
elsif @editor_state.selection_anchor
|
|
233
|
+
range = [@editor_state.selection_anchor, @editor_state.cursor + 1].minmax
|
|
234
|
+
else
|
|
235
|
+
return false
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
@editor_state.copy_for_kill_ring(range[0], range[1])
|
|
239
|
+
@output_io.print("\e]52;c;#{Base64.strict_encode64(@editor_state.kill_buffer)}\a")
|
|
240
|
+
@output_io.flush if @output_io.respond_to?(:flush)
|
|
241
|
+
@editor_state.clear_selection
|
|
242
|
+
@editor_state.status = "Copied region"
|
|
243
|
+
true
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def emacs_cancel
|
|
247
|
+
if editor_search_active?
|
|
248
|
+
editor_search_cancel
|
|
249
|
+
else
|
|
250
|
+
@editor_state.emacs_pending = nil
|
|
251
|
+
@editor_state.clear_selection
|
|
252
|
+
@editor_state.status = "Cancelled"
|
|
253
|
+
end
|
|
254
|
+
true
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# Namespace for the Kward CLI agent runtime.
|
|
2
|
+
module Kward
|
|
3
|
+
# Interactive terminal UI used by the CLI frontend.
|
|
4
|
+
class PromptInterface
|
|
5
|
+
# Modern keymap for the built-in composer file editor.
|
|
6
|
+
module ModernEditorMode
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def handle_modern_key(key)
|
|
10
|
+
return if handle_modern_bracketed_paste_key(key)
|
|
11
|
+
|
|
12
|
+
csi_result = handle_modern_csi_u_key(key)
|
|
13
|
+
return csi_result unless csi_result == false
|
|
14
|
+
|
|
15
|
+
multi_cursor_result = handle_modern_multi_cursor_key(key)
|
|
16
|
+
return multi_cursor_result unless multi_cursor_result == false
|
|
17
|
+
|
|
18
|
+
indentation_navigation_result = handle_modern_indentation_navigation_key(key)
|
|
19
|
+
return indentation_navigation_result unless indentation_navigation_result == false
|
|
20
|
+
|
|
21
|
+
modified_navigation_result = handle_modern_modified_navigation_key(key)
|
|
22
|
+
return modified_navigation_result unless modified_navigation_result == false
|
|
23
|
+
|
|
24
|
+
shift_result = handle_editor_shift_navigation_key(key)
|
|
25
|
+
return shift_result unless shift_result == false
|
|
26
|
+
|
|
27
|
+
binding_result = handle_modern_key_binding(key)
|
|
28
|
+
return binding_result unless binding_result == false
|
|
29
|
+
|
|
30
|
+
editor_tab_result = handle_editor_tab_key(key) { |direction| modern_record_undo { direction == :forward ? editor_insert_tab : editor_outdent_tab } }
|
|
31
|
+
return editor_tab_result unless editor_tab_result == false
|
|
32
|
+
|
|
33
|
+
tab_result = handle_tab_key_binding(key)
|
|
34
|
+
return tab_result unless tab_result == false
|
|
35
|
+
|
|
36
|
+
return true if handle_bundled_key(key) { |token| handle_modern_key(token) }
|
|
37
|
+
|
|
38
|
+
case key
|
|
39
|
+
when "\n", "\r"
|
|
40
|
+
return editor_search_confirm if editor_search_active?
|
|
41
|
+
modern_record_undo { modern_insert_text("\n") }
|
|
42
|
+
when "\t"
|
|
43
|
+
modern_record_undo { editor_insert_tab unless editor_search_active? }
|
|
44
|
+
when "\b", "\x7F"
|
|
45
|
+
editor_search_active? ? editor_search_delete_character : modern_record_undo { modern_delete_before_cursor }
|
|
46
|
+
when "\x03"
|
|
47
|
+
return editor_search_cancel if editor_search_active?
|
|
48
|
+
when "\e"
|
|
49
|
+
return editor_search_cancel if editor_search_active?
|
|
50
|
+
return @editor_state.collapse_to_primary_selection if @editor_state.multi_cursor?
|
|
51
|
+
return @editor_state.clear_selection if @editor_state.selection_active?
|
|
52
|
+
when "/"
|
|
53
|
+
clear_editor_selection_before_edit unless editor_search_active?
|
|
54
|
+
editor_search_active? ? editor_search_append(key) : editor_search_begin(:forward)
|
|
55
|
+
when "?"
|
|
56
|
+
clear_editor_selection_before_edit unless editor_search_active?
|
|
57
|
+
editor_search_active? ? editor_search_append(key) : editor_search_begin(:backward)
|
|
58
|
+
when "\x11"
|
|
59
|
+
quit_editor
|
|
60
|
+
when "\x13"
|
|
61
|
+
save_editor
|
|
62
|
+
when "\x1A"
|
|
63
|
+
@editor_state.undo unless editor_search_active?
|
|
64
|
+
else
|
|
65
|
+
key_name = key_name_for(key)
|
|
66
|
+
named_result = handle_editor_named_key(key_name) if key_name
|
|
67
|
+
return named_result unless named_result == false || named_result.nil?
|
|
68
|
+
|
|
69
|
+
if editor_search_active?
|
|
70
|
+
editor_search_append(key) if printable_key?(key)
|
|
71
|
+
elsif printable_key?(key)
|
|
72
|
+
modern_record_undo { modern_insert_printable(key) }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def handle_modern_csi_u_key(key)
|
|
78
|
+
sequence = parse_csi_u_key(key)
|
|
79
|
+
return false unless sequence
|
|
80
|
+
|
|
81
|
+
code = sequence[:code]
|
|
82
|
+
modifier = sequence[:modifier]
|
|
83
|
+
queue_pending_keys(sequence[:remaining]) if sequence[:remaining] && !sequence[:remaining].empty?
|
|
84
|
+
sequence = sequence.merge(remaining: "")
|
|
85
|
+
|
|
86
|
+
if ctrl_modifier?(modifier) || super_modifier?(modifier)
|
|
87
|
+
handle_modern_modified_key(code, modifier, sequence)
|
|
88
|
+
else
|
|
89
|
+
handle_modern_editor_csi_u_key(sequence)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def handle_modern_multi_cursor_key(key)
|
|
94
|
+
return false if editor_search_active?
|
|
95
|
+
|
|
96
|
+
case key
|
|
97
|
+
when "\x04"
|
|
98
|
+
@editor_state.add_next_occurrence_selection
|
|
99
|
+
when "\e[1;4A", "\e[4A"
|
|
100
|
+
@editor_state.add_vertical_cursor(:up)
|
|
101
|
+
when "\e[1;4B", "\e[4B"
|
|
102
|
+
@editor_state.add_vertical_cursor(:down)
|
|
103
|
+
else
|
|
104
|
+
false
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def handle_modern_indentation_navigation_key(key)
|
|
109
|
+
return false if editor_search_active?
|
|
110
|
+
|
|
111
|
+
case key
|
|
112
|
+
when *modern_indentation_key_sequences(:up)
|
|
113
|
+
modern_move_indentation { @editor_state.move_indentation_up }
|
|
114
|
+
when *modern_indentation_key_sequences(:down)
|
|
115
|
+
modern_move_indentation { @editor_state.move_indentation_down }
|
|
116
|
+
when *modern_indentation_key_sequences(:right)
|
|
117
|
+
modern_move_indentation { @editor_state.move_indentation_right }
|
|
118
|
+
when *modern_indentation_key_sequences(:select_up)
|
|
119
|
+
editor_extending_selection { @editor_state.move_indentation_up }
|
|
120
|
+
when *modern_indentation_key_sequences(:select_down)
|
|
121
|
+
editor_extending_selection { @editor_state.move_indentation_down }
|
|
122
|
+
when *modern_indentation_key_sequences(:select_right)
|
|
123
|
+
editor_extending_selection { @editor_state.move_indentation_right }
|
|
124
|
+
else
|
|
125
|
+
false
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def modern_move_indentation
|
|
130
|
+
result = yield
|
|
131
|
+
@editor_state.clear_selection
|
|
132
|
+
result
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def modern_indentation_key_sequences(action)
|
|
136
|
+
case [modern_indentation_modifier, action]
|
|
137
|
+
when [:alt, :up]
|
|
138
|
+
["\e[1;3A", "\e[3A"]
|
|
139
|
+
when [:alt, :down]
|
|
140
|
+
["\e[1;3B", "\e[3B"]
|
|
141
|
+
when [:alt, :right]
|
|
142
|
+
["\e[1;3C", "\e[3C"]
|
|
143
|
+
when [:alt, :select_up]
|
|
144
|
+
["\e[1;4A", "\e[4A"]
|
|
145
|
+
when [:alt, :select_down]
|
|
146
|
+
["\e[1;4B", "\e[4B"]
|
|
147
|
+
when [:alt, :select_right]
|
|
148
|
+
["\e[1;4C", "\e[4C"]
|
|
149
|
+
when [:ctrl, :up]
|
|
150
|
+
["\e[1;5A", "\e[5A"]
|
|
151
|
+
when [:ctrl, :down]
|
|
152
|
+
["\e[1;5B", "\e[5B"]
|
|
153
|
+
when [:ctrl, :right]
|
|
154
|
+
["\e[1;5C", "\e[5C"]
|
|
155
|
+
when [:ctrl, :select_up]
|
|
156
|
+
["\e[1;6A", "\e[6A"]
|
|
157
|
+
when [:ctrl, :select_down]
|
|
158
|
+
["\e[1;6B", "\e[6B"]
|
|
159
|
+
when [:ctrl, :select_right]
|
|
160
|
+
["\e[1;6C", "\e[6C"]
|
|
161
|
+
else
|
|
162
|
+
[]
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def modern_indentation_modifier
|
|
167
|
+
RbConfig::CONFIG["host_os"].to_s.downcase.include?("darwin") ? :alt : :ctrl
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def handle_modern_modified_navigation_key(key)
|
|
171
|
+
return false if editor_search_active?
|
|
172
|
+
|
|
173
|
+
case key
|
|
174
|
+
when "\e[1;5C", "\e[5C"
|
|
175
|
+
@editor_state.move_line_end
|
|
176
|
+
when "\e[1;5D", "\e[5D"
|
|
177
|
+
@editor_state.move_line_start
|
|
178
|
+
when "\e[1;5A", "\e[5A"
|
|
179
|
+
@editor_state.move_file_start
|
|
180
|
+
when "\e[1;5B", "\e[5B"
|
|
181
|
+
@editor_state.move_file_end
|
|
182
|
+
when "\e[1;4C", "\e[4C"
|
|
183
|
+
editor_extending_selection { @editor_state.move_to_next_word }
|
|
184
|
+
when "\e[1;4D", "\e[4D"
|
|
185
|
+
editor_extending_selection { @editor_state.move_to_previous_word }
|
|
186
|
+
else
|
|
187
|
+
false
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def handle_modern_key_binding(key)
|
|
192
|
+
case key
|
|
193
|
+
when "\x00"
|
|
194
|
+
true
|
|
195
|
+
when "\x03"
|
|
196
|
+
editor_search_active? ? editor_search_cancel : copy_editor_selection
|
|
197
|
+
when "\x06"
|
|
198
|
+
@editor_state.move_right unless editor_search_active?
|
|
199
|
+
when "\x16"
|
|
200
|
+
modern_record_undo { @editor_state.yank_kill_buffer } unless editor_search_active?
|
|
201
|
+
when "\x18"
|
|
202
|
+
modern_record_undo { cut_editor_selection } unless editor_search_active?
|
|
203
|
+
else
|
|
204
|
+
handle_modern_shared_key_binding(key)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def handle_modern_modified_key(code, modifier, sequence)
|
|
209
|
+
normalized_code = ctrl_code(code)
|
|
210
|
+
if super_modifier?(modifier)
|
|
211
|
+
return editor_search_active? ? editor_search_cancel : copy_editor_selection if normalized_code == 99
|
|
212
|
+
return handle_modern_editor_csi_u_key(sequence) unless ctrl_modifier?(modifier)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
case normalized_code
|
|
216
|
+
when 13
|
|
217
|
+
return false if editor_search_active?
|
|
218
|
+
|
|
219
|
+
modern_record_undo do
|
|
220
|
+
clear_editor_selection_before_edit
|
|
221
|
+
editor_insert_endwise_modifier_newline
|
|
222
|
+
end
|
|
223
|
+
when 99
|
|
224
|
+
editor_search_active? ? editor_search_cancel : copy_editor_selection
|
|
225
|
+
when 100
|
|
226
|
+
return false if editor_search_active?
|
|
227
|
+
|
|
228
|
+
@editor_state.add_next_occurrence_selection
|
|
229
|
+
when 102
|
|
230
|
+
@editor_state.move_right unless editor_search_active?
|
|
231
|
+
when 118
|
|
232
|
+
modern_record_undo { @editor_state.yank_kill_buffer } unless editor_search_active?
|
|
233
|
+
when 120
|
|
234
|
+
modern_record_undo { cut_editor_selection } unless editor_search_active?
|
|
235
|
+
when 108
|
|
236
|
+
return false if editor_search_active?
|
|
237
|
+
return false unless modern_ctrl_shift_key?(code, modifier)
|
|
238
|
+
|
|
239
|
+
@editor_state.selection_to_line_start_cursors
|
|
240
|
+
when 122
|
|
241
|
+
return if editor_search_active?
|
|
242
|
+
|
|
243
|
+
modern_ctrl_shift_key?(code, modifier) ? @editor_state.redo : @editor_state.undo
|
|
244
|
+
else
|
|
245
|
+
return false if normalized_code == 32
|
|
246
|
+
|
|
247
|
+
handle_modern_editor_csi_u_key(sequence)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def handle_modern_shared_key_binding(key)
|
|
252
|
+
case key
|
|
253
|
+
when "\x04", "\x0B", "\x15", "\x17", "\x19", "\e[3~", "\ed", "\eD", "\e\b", "\e\x7F"
|
|
254
|
+
modern_record_undo { handle_editor_key_binding(key) }
|
|
255
|
+
else
|
|
256
|
+
handle_editor_key_binding(key)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def handle_modern_editor_csi_u_key(key_or_sequence)
|
|
261
|
+
sequence = key_or_sequence.is_a?(Hash) ? key_or_sequence : parse_csi_u_key(key_or_sequence)
|
|
262
|
+
return handle_editor_csi_u_key(key_or_sequence) unless sequence
|
|
263
|
+
|
|
264
|
+
code = sequence[:code]
|
|
265
|
+
modifier = sequence[:modifier]
|
|
266
|
+
if ctrl_modifier?(modifier)
|
|
267
|
+
normalized_code = ctrl_code(code)
|
|
268
|
+
return @editor_state.add_next_occurrence_selection if normalized_code == 100
|
|
269
|
+
if normalized_code == 108 && modern_ctrl_shift_key?(code, modifier)
|
|
270
|
+
return @editor_state.selection_to_line_start_cursors
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
case normalized_code
|
|
274
|
+
when 107, 117, 119, 121
|
|
275
|
+
return modern_record_undo { handle_parsed_editor_csi_u_key(sequence) }
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
case code
|
|
280
|
+
when 9
|
|
281
|
+
return false if editor_search_active?
|
|
282
|
+
return false if ctrl_modifier?(modifier) || alt_modifier?(modifier) || super_modifier?(modifier)
|
|
283
|
+
|
|
284
|
+
shift_modifier?(modifier) ? modern_record_undo { editor_outdent_tab } : modern_record_undo { editor_insert_tab }
|
|
285
|
+
when 13
|
|
286
|
+
return editor_search_confirm if editor_search_active?
|
|
287
|
+
|
|
288
|
+
modern_record_undo { modern_insert_text("\n") }
|
|
289
|
+
when 8, 127
|
|
290
|
+
editor_search_active? ? editor_search_delete_character : modern_record_undo { modern_delete_before_cursor }
|
|
291
|
+
when 4
|
|
292
|
+
modern_record_undo { delete_editor_selection || @editor_state.delete_at_cursor } unless editor_search_active?
|
|
293
|
+
else
|
|
294
|
+
text = csi_u_printable_text(sequence)
|
|
295
|
+
if text
|
|
296
|
+
editor_search_active? ? editor_search_append(text) : modern_record_undo { modern_insert_printable(text) }
|
|
297
|
+
elsif csi_u_text_field?(sequence)
|
|
298
|
+
true
|
|
299
|
+
else
|
|
300
|
+
handle_parsed_editor_csi_u_key(sequence)
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
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
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def modern_insert_printable(text)
|
|
315
|
+
return editor_insert_printable(text) unless @editor_state.multi_cursor?
|
|
316
|
+
|
|
317
|
+
@editor_state.replace_selections(text)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def modern_insert_text(text)
|
|
321
|
+
return @editor_state.replace_selections(text) if @editor_state.multi_cursor? || @editor_state.selection_ranges.any?
|
|
322
|
+
|
|
323
|
+
if text == "\n"
|
|
324
|
+
editor_insert_newline
|
|
325
|
+
else
|
|
326
|
+
@editor_state.insert(text)
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def modern_delete_before_cursor
|
|
331
|
+
return @editor_state.delete_before_selections if @editor_state.multi_cursor?
|
|
332
|
+
|
|
333
|
+
delete_editor_selection || editor_delete_before_cursor
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def modern_record_undo
|
|
337
|
+
before_buffer = @editor_state.buffer.dup
|
|
338
|
+
before_redo_stack = @editor_state.redo_stack.map { |entry| entry.merge(buffer: entry[:buffer].dup, selections: entry[:selections]&.map(&:dup)) }
|
|
339
|
+
@editor_state.push_undo
|
|
340
|
+
result = yield
|
|
341
|
+
if @editor_state.buffer == before_buffer
|
|
342
|
+
@editor_state.undo_stack.pop
|
|
343
|
+
@editor_state.redo_stack = before_redo_stack
|
|
344
|
+
end
|
|
345
|
+
result
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def modern_ctrl_shift_key?(code, modifier)
|
|
349
|
+
code.to_i.between?(65, 90) || ((modifier.to_i - 1) & 1).positive?
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|