@abacus-ai/cli 2.0.0-canary.1 → 2.0.0-canary.3

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 (197) hide show
  1. package/dist/index.mjs +450 -422
  2. package/package.json +4 -1
  3. package/.oxlintrc.json +0 -8
  4. package/resources/abacus.ico +0 -0
  5. package/resources/entitlements.plist +0 -9
  6. package/src/__e2e__/README.md +0 -196
  7. package/src/__e2e__/agent-interactions.e2e.test.tsx +0 -61
  8. package/src/__e2e__/cli-commands.e2e.test.tsx +0 -77
  9. package/src/__e2e__/conversation-throttle.e2e.test.ts +0 -453
  10. package/src/__e2e__/conversation.e2e.test.tsx +0 -56
  11. package/src/__e2e__/diff-preview.e2e.test.tsx +0 -3399
  12. package/src/__e2e__/file-creation.e2e.test.tsx +0 -149
  13. package/src/__e2e__/helpers/test-helpers.ts +0 -449
  14. package/src/__e2e__/keyboard-navigation.e2e.test.tsx +0 -34
  15. package/src/__e2e__/llm-models.e2e.test.ts +0 -402
  16. package/src/__e2e__/mcp/mcp-callback-flow.e2e.test.tsx +0 -71
  17. package/src/__e2e__/mcp/mcp-full-app-ui.e2e.test.tsx +0 -167
  18. package/src/__e2e__/mcp/mcp-ui-rendering.e2e.test.tsx +0 -185
  19. package/src/__e2e__/repl.e2e.test.tsx +0 -78
  20. package/src/__e2e__/shell-compatibility.e2e.test.tsx +0 -76
  21. package/src/__e2e__/theme-mcp.e2e.test.tsx +0 -98
  22. package/src/__e2e__/tool-permissions.e2e.test.tsx +0 -66
  23. package/src/args.ts +0 -22
  24. package/src/components/__tests__/react-compiler.test.tsx +0 -78
  25. package/src/components/__tests__/status-indicator.test.tsx +0 -403
  26. package/src/components/composer/__tests__/bash-runner.test.tsx +0 -263
  27. package/src/components/composer/agent-mode-indicator.tsx +0 -63
  28. package/src/components/composer/bash-runner.tsx +0 -54
  29. package/src/components/composer/commands/default-commands.tsx +0 -615
  30. package/src/components/composer/commands/handler.tsx +0 -59
  31. package/src/components/composer/commands/picker.tsx +0 -273
  32. package/src/components/composer/commands/registry.ts +0 -233
  33. package/src/components/composer/commands/types.ts +0 -33
  34. package/src/components/composer/context.tsx +0 -88
  35. package/src/components/composer/file-mention-picker.tsx +0 -83
  36. package/src/components/composer/help.tsx +0 -44
  37. package/src/components/composer/index.tsx +0 -1007
  38. package/src/components/composer/mentions.ts +0 -57
  39. package/src/components/composer/message-queue.tsx +0 -70
  40. package/src/components/composer/mode-panel.tsx +0 -35
  41. package/src/components/composer/modes/__tests__/bash-handler.test.tsx +0 -755
  42. package/src/components/composer/modes/__tests__/bash-renderer.test.tsx +0 -1108
  43. package/src/components/composer/modes/bash-handler.tsx +0 -132
  44. package/src/components/composer/modes/bash-renderer.tsx +0 -175
  45. package/src/components/composer/modes/default-handlers.tsx +0 -33
  46. package/src/components/composer/modes/index.ts +0 -41
  47. package/src/components/composer/modes/types.ts +0 -21
  48. package/src/components/composer/persistent-shell.ts +0 -283
  49. package/src/components/composer/process.ts +0 -65
  50. package/src/components/composer/types.ts +0 -9
  51. package/src/components/composer/use-mention-search.ts +0 -68
  52. package/src/components/error-boundry.tsx +0 -60
  53. package/src/components/exit-message.tsx +0 -29
  54. package/src/components/expanded-view.tsx +0 -74
  55. package/src/components/file-completion.tsx +0 -127
  56. package/src/components/header.tsx +0 -47
  57. package/src/components/logo.tsx +0 -37
  58. package/src/components/segments.tsx +0 -356
  59. package/src/components/status-indicator.tsx +0 -306
  60. package/src/components/tool-group-summary.tsx +0 -263
  61. package/src/components/tool-permissions/ask-user-question-permission-ui.tsx +0 -319
  62. package/src/components/tool-permissions/diff-preview.tsx +0 -359
  63. package/src/components/tool-permissions/index.ts +0 -5
  64. package/src/components/tool-permissions/permission-options.tsx +0 -401
  65. package/src/components/tool-permissions/permission-preview-header.tsx +0 -57
  66. package/src/components/tool-permissions/tool-permission-ui.tsx +0 -420
  67. package/src/components/tools/agent/ask-user-question.tsx +0 -107
  68. package/src/components/tools/agent/enter-plan-mode.tsx +0 -55
  69. package/src/components/tools/agent/exit-plan-mode.tsx +0 -83
  70. package/src/components/tools/agent/handoff-to-main.tsx +0 -27
  71. package/src/components/tools/agent/subagent.tsx +0 -37
  72. package/src/components/tools/agent/todo-write.tsx +0 -104
  73. package/src/components/tools/browser/close-tab.tsx +0 -58
  74. package/src/components/tools/browser/computer.tsx +0 -70
  75. package/src/components/tools/browser/get-interactive-elements.tsx +0 -54
  76. package/src/components/tools/browser/get-tab-content.tsx +0 -51
  77. package/src/components/tools/browser/navigate-to.tsx +0 -59
  78. package/src/components/tools/browser/new-tab.tsx +0 -60
  79. package/src/components/tools/browser/perform-action.tsx +0 -63
  80. package/src/components/tools/browser/refresh-tab.tsx +0 -43
  81. package/src/components/tools/browser/switch-tab.tsx +0 -58
  82. package/src/components/tools/filesystem/delete-file.tsx +0 -104
  83. package/src/components/tools/filesystem/edit.tsx +0 -220
  84. package/src/components/tools/filesystem/list-dir.tsx +0 -78
  85. package/src/components/tools/filesystem/read-file.tsx +0 -180
  86. package/src/components/tools/filesystem/upload-image.tsx +0 -76
  87. package/src/components/tools/ide/ide-diagnostics.tsx +0 -62
  88. package/src/components/tools/index.ts +0 -91
  89. package/src/components/tools/mcp/mcp-tool.tsx +0 -158
  90. package/src/components/tools/search/fetch-url.tsx +0 -73
  91. package/src/components/tools/search/file-search.tsx +0 -78
  92. package/src/components/tools/search/grep.tsx +0 -90
  93. package/src/components/tools/search/semantic-search.tsx +0 -66
  94. package/src/components/tools/search/web-search.tsx +0 -71
  95. package/src/components/tools/shared/index.tsx +0 -48
  96. package/src/components/tools/shared/zod-coercion.ts +0 -35
  97. package/src/components/tools/terminal/bash-tool-output.tsx +0 -188
  98. package/src/components/tools/terminal/get-terminal-output.tsx +0 -91
  99. package/src/components/tools/terminal/run-in-terminal.tsx +0 -131
  100. package/src/components/tools/types.ts +0 -16
  101. package/src/components/tools.tsx +0 -68
  102. package/src/components/ui/__tests__/divider.test.tsx +0 -61
  103. package/src/components/ui/__tests__/gradient.test.tsx +0 -125
  104. package/src/components/ui/__tests__/input.test.tsx +0 -166
  105. package/src/components/ui/__tests__/select.test.tsx +0 -273
  106. package/src/components/ui/__tests__/shimmer.test.tsx +0 -99
  107. package/src/components/ui/blinking-indicator.tsx +0 -27
  108. package/src/components/ui/divider.tsx +0 -162
  109. package/src/components/ui/gradient.tsx +0 -56
  110. package/src/components/ui/input.tsx +0 -228
  111. package/src/components/ui/select.tsx +0 -151
  112. package/src/components/ui/shimmer.tsx +0 -76
  113. package/src/context/agent-mode.tsx +0 -95
  114. package/src/context/extension-file.tsx +0 -136
  115. package/src/context/network-activity.tsx +0 -45
  116. package/src/context/notification.tsx +0 -62
  117. package/src/context/shell-size.tsx +0 -49
  118. package/src/context/shell-title.tsx +0 -38
  119. package/src/entrypoints/print-mode.ts +0 -312
  120. package/src/entrypoints/repl.tsx +0 -389
  121. package/src/hooks/use-agent.ts +0 -15
  122. package/src/hooks/use-api-client.ts +0 -1
  123. package/src/hooks/use-available-height.ts +0 -8
  124. package/src/hooks/use-cleanup.ts +0 -29
  125. package/src/hooks/use-interrupt-manager.ts +0 -242
  126. package/src/hooks/use-models.ts +0 -22
  127. package/src/index.ts +0 -217
  128. package/src/lib/__tests__/ansi.test.ts +0 -255
  129. package/src/lib/__tests__/cli.test.ts +0 -122
  130. package/src/lib/__tests__/commands.test.ts +0 -325
  131. package/src/lib/__tests__/constants.test.ts +0 -15
  132. package/src/lib/__tests__/focusables.test.ts +0 -25
  133. package/src/lib/__tests__/fs.test.ts +0 -231
  134. package/src/lib/__tests__/markdown.test.tsx +0 -348
  135. package/src/lib/__tests__/mcpCommandHandler.test.ts +0 -173
  136. package/src/lib/__tests__/mcpManagement.test.ts +0 -38
  137. package/src/lib/__tests__/path-paste.test.ts +0 -144
  138. package/src/lib/__tests__/path.test.ts +0 -300
  139. package/src/lib/__tests__/queries.test.ts +0 -39
  140. package/src/lib/__tests__/standaloneMcpService.test.ts +0 -71
  141. package/src/lib/__tests__/text-buffer.test.ts +0 -328
  142. package/src/lib/__tests__/text-utils.test.ts +0 -32
  143. package/src/lib/__tests__/timing.test.ts +0 -78
  144. package/src/lib/__tests__/utils.test.ts +0 -238
  145. package/src/lib/__tests__/vim-buffer-actions.test.ts +0 -154
  146. package/src/lib/ansi.ts +0 -150
  147. package/src/lib/cli-push-server.ts +0 -112
  148. package/src/lib/cli.ts +0 -44
  149. package/src/lib/clipboard.ts +0 -226
  150. package/src/lib/command-utils.ts +0 -93
  151. package/src/lib/commands.ts +0 -270
  152. package/src/lib/constants.ts +0 -3
  153. package/src/lib/extension-connection.ts +0 -181
  154. package/src/lib/focusables.ts +0 -7
  155. package/src/lib/fs.ts +0 -533
  156. package/src/lib/markdown/code-block.tsx +0 -63
  157. package/src/lib/markdown/index.ts +0 -4
  158. package/src/lib/markdown/link.tsx +0 -19
  159. package/src/lib/markdown/markdown.tsx +0 -372
  160. package/src/lib/markdown/types.ts +0 -15
  161. package/src/lib/mcpCommandHandler.ts +0 -121
  162. package/src/lib/mcpManagement.ts +0 -44
  163. package/src/lib/path-paste.ts +0 -185
  164. package/src/lib/path.ts +0 -179
  165. package/src/lib/queries.ts +0 -15
  166. package/src/lib/standaloneMcpService.ts +0 -688
  167. package/src/lib/status-utils.ts +0 -237
  168. package/src/lib/test-utils.tsx +0 -72
  169. package/src/lib/text-buffer.ts +0 -2415
  170. package/src/lib/text-utils.ts +0 -272
  171. package/src/lib/timing.ts +0 -63
  172. package/src/lib/types.ts +0 -295
  173. package/src/lib/utils.ts +0 -182
  174. package/src/lib/vim-buffer-actions.ts +0 -732
  175. package/src/providers/agent.tsx +0 -1063
  176. package/src/providers/api-client.tsx +0 -43
  177. package/src/services/logger.ts +0 -85
  178. package/src/terminal/detection.ts +0 -187
  179. package/src/terminal/exit.ts +0 -279
  180. package/src/terminal/notification.ts +0 -83
  181. package/src/terminal/progress.ts +0 -201
  182. package/src/terminal/setup.ts +0 -797
  183. package/src/terminal/types.ts +0 -51
  184. package/src/theme/context.tsx +0 -57
  185. package/src/theme/index.ts +0 -4
  186. package/src/theme/themed.tsx +0 -35
  187. package/src/theme/themes.json +0 -546
  188. package/src/theme/types.ts +0 -110
  189. package/src/tools/types.ts +0 -59
  190. package/src/tools/utils/__tests__/zod-coercion.test.ts +0 -33
  191. package/src/tools/utils/tool-ui-components.tsx +0 -649
  192. package/src/tools/utils/zod-coercion.ts +0 -35
  193. package/tsconfig.json +0 -16
  194. package/tsconfig.node.json +0 -29
  195. package/tsconfig.test.json +0 -27
  196. package/tsdown.config.ts +0 -17
  197. package/vitest.config.ts +0 -76
@@ -1,732 +0,0 @@
1
- import {
2
- findNextWordAcrossLines,
3
- findPrevWordAcrossLines,
4
- findWordEndInLine,
5
- getLineRangeOffsets,
6
- getPositionFromOffsets,
7
- isCombiningMark,
8
- isWordCharStrict,
9
- isWordCharWithCombining,
10
- pushUndo,
11
- replaceRangeInternal,
12
- TextBufferAction,
13
- TextBufferState,
14
- } from "./text-buffer.js";
15
- import { cpLen, toCodePoints } from "./text-utils.js";
16
-
17
- /* Fail to compile on unexpected values. */
18
- export function assumeExhaustive(_value: never): void {}
19
-
20
- // Check if we're at the end of a base word (on the last base character)
21
- // Returns true if current position has a base character followed only by combining marks until non-word
22
- function isAtEndOfBaseWord(lineCodePoints: string[], col: number): boolean {
23
- if (!isWordCharStrict(lineCodePoints[col])) {
24
- return false;
25
- }
26
-
27
- // Look ahead to see if we have only combining marks followed by non-word
28
- let i = col + 1;
29
-
30
- // Skip any combining marks
31
- while (i < lineCodePoints.length && isCombiningMark(lineCodePoints[i])) {
32
- i++;
33
- }
34
-
35
- // If we hit end of line or non-word character, we were at end of base word
36
- return i >= lineCodePoints.length || !isWordCharStrict(lineCodePoints[i]);
37
- }
38
-
39
- export type VimAction = Extract<
40
- TextBufferAction,
41
- | { type: "vim_delete_word_forward" }
42
- | { type: "vim_delete_word_backward" }
43
- | { type: "vim_delete_word_end" }
44
- | { type: "vim_change_word_forward" }
45
- | { type: "vim_change_word_backward" }
46
- | { type: "vim_change_word_end" }
47
- | { type: "vim_delete_line" }
48
- | { type: "vim_change_line" }
49
- | { type: "vim_delete_to_end_of_line" }
50
- | { type: "vim_change_to_end_of_line" }
51
- | { type: "vim_change_movement" }
52
- | { type: "vim_move_left" }
53
- | { type: "vim_move_right" }
54
- | { type: "vim_move_up" }
55
- | { type: "vim_move_down" }
56
- | { type: "vim_move_word_forward" }
57
- | { type: "vim_move_word_backward" }
58
- | { type: "vim_move_word_end" }
59
- | { type: "vim_delete_char" }
60
- | { type: "vim_insert_at_cursor" }
61
- | { type: "vim_append_at_cursor" }
62
- | { type: "vim_open_line_below" }
63
- | { type: "vim_open_line_above" }
64
- | { type: "vim_append_at_line_end" }
65
- | { type: "vim_insert_at_line_start" }
66
- | { type: "vim_move_to_line_start" }
67
- | { type: "vim_move_to_line_end" }
68
- | { type: "vim_move_to_first_nonwhitespace" }
69
- | { type: "vim_move_to_first_line" }
70
- | { type: "vim_move_to_last_line" }
71
- | { type: "vim_move_to_line" }
72
- | { type: "vim_escape_insert_mode" }
73
- >;
74
-
75
- export function handleVimAction(state: TextBufferState, action: VimAction): TextBufferState {
76
- const { lines, cursorRow, cursorCol } = state;
77
-
78
- switch (action.type) {
79
- case "vim_delete_word_forward":
80
- case "vim_change_word_forward": {
81
- const { count } = action.payload;
82
- let endRow = cursorRow;
83
- let endCol = cursorCol;
84
-
85
- for (let i = 0; i < count; i++) {
86
- const nextWord = findNextWordAcrossLines(lines, endRow, endCol, true);
87
- if (nextWord) {
88
- endRow = nextWord.row;
89
- endCol = nextWord.col;
90
- } else {
91
- // No more words, delete/change to end of current word or line
92
- const currentLine = lines[endRow] || "";
93
- const wordEnd = findWordEndInLine(currentLine, endCol);
94
- if (wordEnd !== null) {
95
- endCol = wordEnd + 1; // Include the character at word end
96
- } else {
97
- endCol = cpLen(currentLine);
98
- }
99
- break;
100
- }
101
- }
102
-
103
- if (endRow !== cursorRow || endCol !== cursorCol) {
104
- const nextState = pushUndo(state);
105
- return replaceRangeInternal(nextState, cursorRow, cursorCol, endRow, endCol, "");
106
- }
107
- return state;
108
- }
109
-
110
- case "vim_delete_word_backward":
111
- case "vim_change_word_backward": {
112
- const { count } = action.payload;
113
- let startRow = cursorRow;
114
- let startCol = cursorCol;
115
-
116
- for (let i = 0; i < count; i++) {
117
- const prevWord = findPrevWordAcrossLines(lines, startRow, startCol);
118
- if (prevWord) {
119
- startRow = prevWord.row;
120
- startCol = prevWord.col;
121
- } else {
122
- break;
123
- }
124
- }
125
-
126
- if (startRow !== cursorRow || startCol !== cursorCol) {
127
- const nextState = pushUndo(state);
128
- return replaceRangeInternal(nextState, startRow, startCol, cursorRow, cursorCol, "");
129
- }
130
- return state;
131
- }
132
-
133
- case "vim_delete_word_end":
134
- case "vim_change_word_end": {
135
- const { count } = action.payload;
136
- let row = cursorRow;
137
- let col = cursorCol;
138
- let endRow = cursorRow;
139
- let endCol = cursorCol;
140
-
141
- for (let i = 0; i < count; i++) {
142
- const wordEnd = findNextWordAcrossLines(lines, row, col, false);
143
- if (wordEnd) {
144
- endRow = wordEnd.row;
145
- endCol = wordEnd.col + 1; // Include the character at word end
146
- // For next iteration, move to start of next word
147
- if (i < count - 1) {
148
- const nextWord = findNextWordAcrossLines(lines, wordEnd.row, wordEnd.col + 1, true);
149
- if (nextWord) {
150
- row = nextWord.row;
151
- col = nextWord.col;
152
- } else {
153
- break; // No more words
154
- }
155
- }
156
- } else {
157
- break;
158
- }
159
- }
160
-
161
- // Ensure we don't go past the end of the last line
162
- if (endRow < lines.length) {
163
- const lineLen = cpLen(lines[endRow] || "");
164
- endCol = Math.min(endCol, lineLen);
165
- }
166
-
167
- if (endRow !== cursorRow || endCol !== cursorCol) {
168
- const nextState = pushUndo(state);
169
- return replaceRangeInternal(nextState, cursorRow, cursorCol, endRow, endCol, "");
170
- }
171
- return state;
172
- }
173
-
174
- case "vim_delete_line": {
175
- const { count } = action.payload;
176
- if (lines.length === 0) {
177
- return state;
178
- }
179
-
180
- const linesToDelete = Math.min(count, lines.length - cursorRow);
181
- const totalLines = lines.length;
182
-
183
- if (totalLines === 1 || linesToDelete >= totalLines) {
184
- // If there's only one line, or we're deleting all remaining lines,
185
- // clear the content but keep one empty line (text editors should never be completely empty)
186
- const nextState = pushUndo(state);
187
- return {
188
- ...nextState,
189
- lines: [""],
190
- cursorRow: 0,
191
- cursorCol: 0,
192
- preferredCol: null,
193
- };
194
- }
195
-
196
- const nextState = pushUndo(state);
197
- const newLines = [...nextState.lines];
198
- newLines.splice(cursorRow, linesToDelete);
199
-
200
- // Adjust cursor position
201
- const newCursorRow = Math.min(cursorRow, newLines.length - 1);
202
- const newCursorCol = 0; // Vim places cursor at beginning of line after dd
203
-
204
- return {
205
- ...nextState,
206
- lines: newLines,
207
- cursorRow: newCursorRow,
208
- cursorCol: newCursorCol,
209
- preferredCol: null,
210
- };
211
- }
212
-
213
- case "vim_change_line": {
214
- const { count } = action.payload;
215
- if (lines.length === 0) {
216
- return state;
217
- }
218
-
219
- const linesToChange = Math.min(count, lines.length - cursorRow);
220
- const nextState = pushUndo(state);
221
-
222
- const { startOffset, endOffset } = getLineRangeOffsets(
223
- cursorRow,
224
- linesToChange,
225
- nextState.lines,
226
- );
227
- const { startRow, startCol, endRow, endCol } = getPositionFromOffsets(
228
- startOffset,
229
- endOffset,
230
- nextState.lines,
231
- );
232
- return replaceRangeInternal(nextState, startRow, startCol, endRow, endCol, "");
233
- }
234
-
235
- case "vim_delete_to_end_of_line":
236
- case "vim_change_to_end_of_line": {
237
- const currentLine = lines[cursorRow] || "";
238
- if (cursorCol < cpLen(currentLine)) {
239
- const nextState = pushUndo(state);
240
- return replaceRangeInternal(
241
- nextState,
242
- cursorRow,
243
- cursorCol,
244
- cursorRow,
245
- cpLen(currentLine),
246
- "",
247
- );
248
- }
249
- return state;
250
- }
251
-
252
- case "vim_change_movement": {
253
- const { movement, count } = action.payload;
254
- const totalLines = lines.length;
255
-
256
- switch (movement) {
257
- case "h": {
258
- // Left
259
- // Change N characters to the left
260
- const startCol = Math.max(0, cursorCol - count);
261
- return replaceRangeInternal(
262
- pushUndo(state),
263
- cursorRow,
264
- startCol,
265
- cursorRow,
266
- cursorCol,
267
- "",
268
- );
269
- }
270
-
271
- case "j": {
272
- // Down
273
- const linesToChange = Math.min(count, totalLines - cursorRow);
274
- if (linesToChange > 0) {
275
- if (totalLines === 1) {
276
- const currentLine = state.lines[0] || "";
277
- return replaceRangeInternal(pushUndo(state), 0, 0, 0, cpLen(currentLine), "");
278
- } else {
279
- const nextState = pushUndo(state);
280
- const { startOffset, endOffset } = getLineRangeOffsets(
281
- cursorRow,
282
- linesToChange,
283
- nextState.lines,
284
- );
285
- const { startRow, startCol, endRow, endCol } = getPositionFromOffsets(
286
- startOffset,
287
- endOffset,
288
- nextState.lines,
289
- );
290
- return replaceRangeInternal(nextState, startRow, startCol, endRow, endCol, "");
291
- }
292
- }
293
- return state;
294
- }
295
-
296
- case "k": {
297
- // Up
298
- const upLines = Math.min(count, cursorRow + 1);
299
- if (upLines > 0) {
300
- if (state.lines.length === 1) {
301
- const currentLine = state.lines[0] || "";
302
- return replaceRangeInternal(pushUndo(state), 0, 0, 0, cpLen(currentLine), "");
303
- } else {
304
- const startRow = Math.max(0, cursorRow - count + 1);
305
- const linesToChange = cursorRow - startRow + 1;
306
- const nextState = pushUndo(state);
307
- const { startOffset, endOffset } = getLineRangeOffsets(
308
- startRow,
309
- linesToChange,
310
- nextState.lines,
311
- );
312
- const {
313
- startRow: newStartRow,
314
- startCol,
315
- endRow,
316
- endCol,
317
- } = getPositionFromOffsets(startOffset, endOffset, nextState.lines);
318
- const resultState = replaceRangeInternal(
319
- nextState,
320
- newStartRow,
321
- startCol,
322
- endRow,
323
- endCol,
324
- "",
325
- );
326
- return {
327
- ...resultState,
328
- cursorRow: startRow,
329
- cursorCol: 0,
330
- };
331
- }
332
- }
333
- return state;
334
- }
335
-
336
- case "l": {
337
- // Right
338
- // Change N characters to the right
339
- return replaceRangeInternal(
340
- pushUndo(state),
341
- cursorRow,
342
- cursorCol,
343
- cursorRow,
344
- Math.min(cpLen(lines[cursorRow] || ""), cursorCol + count),
345
- "",
346
- );
347
- }
348
-
349
- default:
350
- return state;
351
- }
352
- }
353
-
354
- case "vim_move_left": {
355
- const { count } = action.payload;
356
- const { cursorRow, cursorCol, lines } = state;
357
- let newRow = cursorRow;
358
- let newCol = cursorCol;
359
-
360
- for (let i = 0; i < count; i++) {
361
- if (newCol > 0) {
362
- newCol--;
363
- } else if (newRow > 0) {
364
- // Move to end of previous line
365
- newRow--;
366
- const prevLine = lines[newRow] || "";
367
- const prevLineLength = cpLen(prevLine);
368
- // Position on last character, or column 0 for empty lines
369
- newCol = prevLineLength === 0 ? 0 : prevLineLength - 1;
370
- }
371
- }
372
-
373
- return {
374
- ...state,
375
- cursorRow: newRow,
376
- cursorCol: newCol,
377
- preferredCol: null,
378
- };
379
- }
380
-
381
- case "vim_move_right": {
382
- const { count } = action.payload;
383
- const { cursorRow, cursorCol, lines } = state;
384
- let newRow = cursorRow;
385
- let newCol = cursorCol;
386
-
387
- for (let i = 0; i < count; i++) {
388
- const currentLine = lines[newRow] || "";
389
- const lineLength = cpLen(currentLine);
390
- // Don't move past the last character of the line
391
- // For empty lines, stay at column 0; for non-empty lines, don't go past last character
392
- if (lineLength === 0) {
393
- // Empty line - try to move to next line
394
- if (newRow < lines.length - 1) {
395
- newRow++;
396
- newCol = 0;
397
- }
398
- } else if (newCol < lineLength - 1) {
399
- newCol++;
400
-
401
- // Skip over combining marks - don't let cursor land on them
402
- const currentLinePoints = toCodePoints(currentLine);
403
- while (
404
- newCol < currentLinePoints.length &&
405
- isCombiningMark(currentLinePoints[newCol]) &&
406
- newCol < lineLength - 1
407
- ) {
408
- newCol++;
409
- }
410
- } else if (newRow < lines.length - 1) {
411
- // At end of line - move to beginning of next line
412
- newRow++;
413
- newCol = 0;
414
- }
415
- }
416
-
417
- return {
418
- ...state,
419
- cursorRow: newRow,
420
- cursorCol: newCol,
421
- preferredCol: null,
422
- };
423
- }
424
-
425
- case "vim_move_up": {
426
- const { count } = action.payload;
427
- const { cursorRow, cursorCol, lines } = state;
428
- const newRow = Math.max(0, cursorRow - count);
429
- const targetLine = lines[newRow] || "";
430
- const targetLineLength = cpLen(targetLine);
431
- const newCol = Math.min(cursorCol, targetLineLength > 0 ? targetLineLength - 1 : 0);
432
-
433
- return {
434
- ...state,
435
- cursorRow: newRow,
436
- cursorCol: newCol,
437
- preferredCol: null,
438
- };
439
- }
440
-
441
- case "vim_move_down": {
442
- const { count } = action.payload;
443
- const { cursorRow, cursorCol, lines } = state;
444
- const newRow = Math.min(lines.length - 1, cursorRow + count);
445
- const targetLine = lines[newRow] || "";
446
- const targetLineLength = cpLen(targetLine);
447
- const newCol = Math.min(cursorCol, targetLineLength > 0 ? targetLineLength - 1 : 0);
448
-
449
- return {
450
- ...state,
451
- cursorRow: newRow,
452
- cursorCol: newCol,
453
- preferredCol: null,
454
- };
455
- }
456
-
457
- case "vim_move_word_forward": {
458
- const { count } = action.payload;
459
- let row = cursorRow;
460
- let col = cursorCol;
461
-
462
- for (let i = 0; i < count; i++) {
463
- const nextWord = findNextWordAcrossLines(lines, row, col, true);
464
- if (nextWord) {
465
- row = nextWord.row;
466
- col = nextWord.col;
467
- } else {
468
- // No more words to move to
469
- break;
470
- }
471
- }
472
-
473
- return {
474
- ...state,
475
- cursorRow: row,
476
- cursorCol: col,
477
- preferredCol: null,
478
- };
479
- }
480
-
481
- case "vim_move_word_backward": {
482
- const { count } = action.payload;
483
- let row = cursorRow;
484
- let col = cursorCol;
485
-
486
- for (let i = 0; i < count; i++) {
487
- const prevWord = findPrevWordAcrossLines(lines, row, col);
488
- if (prevWord) {
489
- row = prevWord.row;
490
- col = prevWord.col;
491
- } else {
492
- break;
493
- }
494
- }
495
-
496
- return {
497
- ...state,
498
- cursorRow: row,
499
- cursorCol: col,
500
- preferredCol: null,
501
- };
502
- }
503
-
504
- case "vim_move_word_end": {
505
- const { count } = action.payload;
506
- let row = cursorRow;
507
- let col = cursorCol;
508
-
509
- for (let i = 0; i < count; i++) {
510
- // Special handling for the first iteration when we're at end of word
511
- if (i === 0) {
512
- const currentLine = lines[row] || "";
513
- const lineCodePoints = toCodePoints(currentLine);
514
-
515
- // Check if we're at the end of a word (on the last base character)
516
- const atEndOfWord =
517
- col < lineCodePoints.length &&
518
- isWordCharStrict(lineCodePoints[col]) &&
519
- (col + 1 >= lineCodePoints.length ||
520
- !isWordCharWithCombining(lineCodePoints[col + 1]) ||
521
- // Or if we're on a base char followed only by combining marks until non-word
522
- (isWordCharStrict(lineCodePoints[col]) && isAtEndOfBaseWord(lineCodePoints, col)));
523
-
524
- if (atEndOfWord) {
525
- // We're already at end of word, find next word end
526
- const nextWord = findNextWordAcrossLines(lines, row, col + 1, false);
527
- if (nextWord) {
528
- row = nextWord.row;
529
- col = nextWord.col;
530
- continue;
531
- }
532
- }
533
- }
534
-
535
- const wordEnd = findNextWordAcrossLines(lines, row, col, false);
536
- if (wordEnd) {
537
- row = wordEnd.row;
538
- col = wordEnd.col;
539
- } else {
540
- break;
541
- }
542
- }
543
-
544
- return {
545
- ...state,
546
- cursorRow: row,
547
- cursorCol: col,
548
- preferredCol: null,
549
- };
550
- }
551
-
552
- case "vim_delete_char": {
553
- const { count } = action.payload;
554
- const { cursorRow, cursorCol, lines } = state;
555
- const currentLine = lines[cursorRow] || "";
556
- const lineLength = cpLen(currentLine);
557
-
558
- if (cursorCol < lineLength) {
559
- const deleteCount = Math.min(count, lineLength - cursorCol);
560
- const nextState = pushUndo(state);
561
- return replaceRangeInternal(
562
- nextState,
563
- cursorRow,
564
- cursorCol,
565
- cursorRow,
566
- cursorCol + deleteCount,
567
- "",
568
- );
569
- }
570
- return state;
571
- }
572
-
573
- case "vim_insert_at_cursor": {
574
- // Just return state - mode change is handled elsewhere
575
- return state;
576
- }
577
-
578
- case "vim_append_at_cursor": {
579
- const { cursorRow, cursorCol, lines } = state;
580
- const currentLine = lines[cursorRow] || "";
581
- const newCol = cursorCol < cpLen(currentLine) ? cursorCol + 1 : cursorCol;
582
-
583
- return {
584
- ...state,
585
- cursorCol: newCol,
586
- preferredCol: null,
587
- };
588
- }
589
-
590
- case "vim_open_line_below": {
591
- const { cursorRow, lines } = state;
592
- const nextState = pushUndo(state);
593
-
594
- // Insert newline at end of current line
595
- const endOfLine = cpLen(lines[cursorRow] || "");
596
- return replaceRangeInternal(nextState, cursorRow, endOfLine, cursorRow, endOfLine, "\n");
597
- }
598
-
599
- case "vim_open_line_above": {
600
- const { cursorRow } = state;
601
- const nextState = pushUndo(state);
602
-
603
- // Insert newline at beginning of current line
604
- const resultState = replaceRangeInternal(nextState, cursorRow, 0, cursorRow, 0, "\n");
605
-
606
- // Move cursor to the new line above
607
- return {
608
- ...resultState,
609
- cursorRow,
610
- cursorCol: 0,
611
- };
612
- }
613
-
614
- case "vim_append_at_line_end": {
615
- const { cursorRow, lines } = state;
616
- const lineLength = cpLen(lines[cursorRow] || "");
617
-
618
- return {
619
- ...state,
620
- cursorCol: lineLength,
621
- preferredCol: null,
622
- };
623
- }
624
-
625
- case "vim_insert_at_line_start": {
626
- const { cursorRow, lines } = state;
627
- const currentLine = lines[cursorRow] || "";
628
- let col = 0;
629
-
630
- // Find first non-whitespace character using proper Unicode handling
631
- const lineCodePoints = toCodePoints(currentLine);
632
- while (col < lineCodePoints.length && /\s/.test(lineCodePoints[col])) {
633
- col++;
634
- }
635
-
636
- return {
637
- ...state,
638
- cursorCol: col,
639
- preferredCol: null,
640
- };
641
- }
642
-
643
- case "vim_move_to_line_start": {
644
- return {
645
- ...state,
646
- cursorCol: 0,
647
- preferredCol: null,
648
- };
649
- }
650
-
651
- case "vim_move_to_line_end": {
652
- const { cursorRow, lines } = state;
653
- const lineLength = cpLen(lines[cursorRow] || "");
654
-
655
- return {
656
- ...state,
657
- cursorCol: lineLength > 0 ? lineLength - 1 : 0,
658
- preferredCol: null,
659
- };
660
- }
661
-
662
- case "vim_move_to_first_nonwhitespace": {
663
- const { cursorRow, lines } = state;
664
- const currentLine = lines[cursorRow] || "";
665
- let col = 0;
666
-
667
- // Find first non-whitespace character using proper Unicode handling
668
- const lineCodePoints = toCodePoints(currentLine);
669
- while (col < lineCodePoints.length && /\s/.test(lineCodePoints[col])) {
670
- col++;
671
- }
672
-
673
- return {
674
- ...state,
675
- cursorCol: col,
676
- preferredCol: null,
677
- };
678
- }
679
-
680
- case "vim_move_to_first_line": {
681
- return {
682
- ...state,
683
- cursorRow: 0,
684
- cursorCol: 0,
685
- preferredCol: null,
686
- };
687
- }
688
-
689
- case "vim_move_to_last_line": {
690
- const { lines } = state;
691
- const lastRow = lines.length - 1;
692
-
693
- return {
694
- ...state,
695
- cursorRow: lastRow,
696
- cursorCol: 0,
697
- preferredCol: null,
698
- };
699
- }
700
-
701
- case "vim_move_to_line": {
702
- const { lineNumber } = action.payload;
703
- const { lines } = state;
704
- const targetRow = Math.min(Math.max(0, lineNumber - 1), lines.length - 1);
705
-
706
- return {
707
- ...state,
708
- cursorRow: targetRow,
709
- cursorCol: 0,
710
- preferredCol: null,
711
- };
712
- }
713
-
714
- case "vim_escape_insert_mode": {
715
- // Move cursor left if not at beginning of line (vim behavior when exiting insert mode)
716
- const { cursorCol } = state;
717
- const newCol = cursorCol > 0 ? cursorCol - 1 : 0;
718
-
719
- return {
720
- ...state,
721
- cursorCol: newCol,
722
- preferredCol: null,
723
- };
724
- }
725
-
726
- default: {
727
- // This should never happen if TypeScript is working correctly
728
- assumeExhaustive(action);
729
- return state;
730
- }
731
- }
732
- }