@vybestack/llxprt-code 0.1.14-nightly.250729.2076f7c6 → 0.1.15
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.
- package/README.md +53 -0
- package/dist/package.json +4 -5
- package/dist/src/config/extension.d.ts +1 -0
- package/dist/src/config/extension.js +4 -0
- package/dist/src/config/extension.js.map +1 -1
- package/dist/src/config/settings.d.ts +2 -1
- package/dist/src/config/settings.js.map +1 -1
- package/dist/src/gemini.d.ts +1 -0
- package/dist/src/gemini.js +35 -15
- package/dist/src/gemini.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +1 -1
- package/dist/src/generated/git-commit.js +1 -1
- package/dist/src/services/BuiltinCommandLoader.js +2 -0
- package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
- package/dist/src/services/CommandService.d.ts +8 -4
- package/dist/src/services/CommandService.js +24 -8
- package/dist/src/services/CommandService.js.map +1 -1
- package/dist/src/services/FileCommandLoader.d.ts +15 -3
- package/dist/src/services/FileCommandLoader.js +94 -42
- package/dist/src/services/FileCommandLoader.js.map +1 -1
- package/dist/src/services/McpPromptLoader.d.ts +25 -0
- package/dist/src/services/McpPromptLoader.js +192 -0
- package/dist/src/services/McpPromptLoader.js.map +1 -0
- package/dist/src/services/prompt-processors/shellProcessor.d.ts +32 -0
- package/dist/src/services/prompt-processors/shellProcessor.js +77 -0
- package/dist/src/services/prompt-processors/shellProcessor.js.map +1 -0
- package/dist/src/services/prompt-processors/types.d.ts +4 -0
- package/dist/src/services/prompt-processors/types.js +4 -0
- package/dist/src/services/prompt-processors/types.js.map +1 -1
- package/dist/src/ui/App.js +312 -195
- package/dist/src/ui/App.js.map +1 -1
- package/dist/src/ui/commands/chatCommand.js +39 -1
- package/dist/src/ui/commands/chatCommand.js.map +1 -1
- package/dist/src/ui/commands/initCommand.d.ts +7 -0
- package/dist/src/ui/commands/initCommand.js +76 -0
- package/dist/src/ui/commands/initCommand.js.map +1 -0
- package/dist/src/ui/commands/mcpCommand.js +53 -8
- package/dist/src/ui/commands/mcpCommand.js.map +1 -1
- package/dist/src/ui/commands/types.d.ts +22 -3
- package/dist/src/ui/commands/types.js +1 -0
- package/dist/src/ui/commands/types.js.map +1 -1
- package/dist/src/ui/commands/vimCommand.js +0 -7
- package/dist/src/ui/commands/vimCommand.js.map +1 -1
- package/dist/src/ui/components/ContextSummaryDisplay.d.ts +3 -3
- package/dist/src/ui/components/ContextSummaryDisplay.js +8 -8
- package/dist/src/ui/components/ContextSummaryDisplay.js.map +1 -1
- package/dist/src/ui/components/Footer.d.ts +1 -0
- package/dist/src/ui/components/Footer.js +2 -2
- package/dist/src/ui/components/Footer.js.map +1 -1
- package/dist/src/ui/components/Header.js +1 -1
- package/dist/src/ui/components/Header.js.map +1 -1
- package/dist/src/ui/components/Help.js +2 -2
- package/dist/src/ui/components/Help.js.map +1 -1
- package/dist/src/ui/components/IDEContextDetailDisplay.d.ts +4 -4
- package/dist/src/ui/components/IDEContextDetailDisplay.js +5 -7
- package/dist/src/ui/components/IDEContextDetailDisplay.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.d.ts +2 -0
- package/dist/src/ui/components/InputPrompt.js +5 -1
- package/dist/src/ui/components/InputPrompt.js.map +1 -1
- package/dist/src/ui/components/ShellConfirmationDialog.d.ts +15 -0
- package/dist/src/ui/components/ShellConfirmationDialog.js +45 -0
- package/dist/src/ui/components/ShellConfirmationDialog.js.map +1 -0
- package/dist/src/ui/components/Tips.js +1 -1
- package/dist/src/ui/components/Tips.js.map +1 -1
- package/dist/src/ui/components/shared/text-buffer.d.ts +270 -2
- package/dist/src/ui/components/shared/text-buffer.js +415 -70
- package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
- package/dist/src/ui/components/shared/vim-buffer-actions.d.ts +72 -0
- package/dist/src/ui/components/shared/vim-buffer-actions.js +565 -0
- package/dist/src/ui/components/shared/vim-buffer-actions.js.map +1 -0
- package/dist/src/ui/contexts/VimModeContext.js +2 -2
- package/dist/src/ui/contexts/VimModeContext.js.map +1 -1
- package/dist/src/ui/hooks/shellCommandProcessor.d.ts +1 -0
- package/dist/src/ui/hooks/shellCommandProcessor.js +139 -200
- package/dist/src/ui/hooks/shellCommandProcessor.js.map +1 -1
- package/dist/src/ui/hooks/slashCommandProcessor.d.ts +7 -3
- package/dist/src/ui/hooks/slashCommandProcessor.js +208 -129
- package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
- package/dist/src/ui/hooks/useCompletion.js +7 -2
- package/dist/src/ui/hooks/useCompletion.js.map +1 -1
- package/dist/src/ui/hooks/useConsoleMessages.js +53 -37
- package/dist/src/ui/hooks/useConsoleMessages.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.js +14 -2
- package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
- package/dist/src/ui/hooks/useKeypress.js +5 -2
- package/dist/src/ui/hooks/useKeypress.js.map +1 -1
- package/dist/src/ui/hooks/useReactToolScheduler.js +0 -1
- package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
- package/dist/src/ui/hooks/vim.d.ts +28 -0
- package/dist/src/ui/hooks/vim.js +630 -0
- package/dist/src/ui/hooks/vim.js.map +1 -0
- package/dist/src/ui/themes/theme-manager.js +10 -1
- package/dist/src/ui/themes/theme-manager.js.map +1 -1
- package/dist/src/ui/themes/theme.d.ts +1 -0
- package/dist/src/ui/themes/theme.js +19 -4
- package/dist/src/ui/themes/theme.js.map +1 -1
- package/dist/src/ui/utils/textUtils.d.ts +0 -8
- package/dist/src/ui/utils/textUtils.js +0 -22
- package/dist/src/ui/utils/textUtils.js.map +1 -1
- package/dist/src/ui/utils/updateCheck.d.ts +7 -1
- package/dist/src/ui/utils/updateCheck.js +17 -28
- package/dist/src/ui/utils/updateCheck.js.map +1 -1
- package/dist/src/utils/events.d.ts +11 -0
- package/dist/src/utils/events.js +13 -0
- package/dist/src/utils/events.js.map +1 -0
- package/dist/src/utils/handleAutoUpdate.d.ts +10 -0
- package/dist/src/utils/handleAutoUpdate.js +97 -0
- package/dist/src/utils/handleAutoUpdate.js.map +1 -0
- package/dist/src/utils/installationInfo.d.ts +23 -0
- package/dist/src/utils/installationInfo.js +154 -0
- package/dist/src/utils/installationInfo.js.map +1 -0
- package/dist/src/utils/updateEventEmitter.d.ts +11 -0
- package/dist/src/utils/updateEventEmitter.js +12 -0
- package/dist/src/utils/updateEventEmitter.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -5
@@ -12,6 +12,7 @@ import { useState, useCallback, useEffect, useMemo, useReducer } from 'react';
|
|
12
12
|
import stringWidth from 'string-width';
|
13
13
|
import { unescapePath } from '@vybestack/llxprt-code-core';
|
14
14
|
import { toCodePoints, cpLen, cpSlice } from '../../utils/textUtils.js';
|
15
|
+
import { handleVimAction } from './vim-buffer-actions.js';
|
15
16
|
// Simple helper for word‑wise ops.
|
16
17
|
function isWordChar(ch) {
|
17
18
|
if (ch === undefined) {
|
@@ -19,6 +20,209 @@ function isWordChar(ch) {
|
|
19
20
|
}
|
20
21
|
return !/[\s,.;!?]/.test(ch);
|
21
22
|
}
|
23
|
+
// Vim-specific word boundary functions
|
24
|
+
export const findNextWordStart = (text, currentOffset) => {
|
25
|
+
let i = currentOffset;
|
26
|
+
if (i >= text.length)
|
27
|
+
return i;
|
28
|
+
const currentChar = text[i];
|
29
|
+
// Skip current word/sequence based on character type
|
30
|
+
if (/\w/.test(currentChar)) {
|
31
|
+
// Skip current word characters
|
32
|
+
while (i < text.length && /\w/.test(text[i])) {
|
33
|
+
i++;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
else if (!/\s/.test(currentChar)) {
|
37
|
+
// Skip current non-word, non-whitespace characters (like "/", ".", etc.)
|
38
|
+
while (i < text.length && !/\w/.test(text[i]) && !/\s/.test(text[i])) {
|
39
|
+
i++;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
// Skip whitespace
|
43
|
+
while (i < text.length && /\s/.test(text[i])) {
|
44
|
+
i++;
|
45
|
+
}
|
46
|
+
// If we reached the end of text and there's no next word,
|
47
|
+
// vim behavior for dw is to delete to the end of the current word
|
48
|
+
if (i >= text.length) {
|
49
|
+
// Go back to find the end of the last word
|
50
|
+
let endOfLastWord = text.length - 1;
|
51
|
+
while (endOfLastWord >= 0 && /\s/.test(text[endOfLastWord])) {
|
52
|
+
endOfLastWord--;
|
53
|
+
}
|
54
|
+
// For dw on last word, return position AFTER the last character to delete entire word
|
55
|
+
return Math.max(currentOffset + 1, endOfLastWord + 1);
|
56
|
+
}
|
57
|
+
return i;
|
58
|
+
};
|
59
|
+
export const findPrevWordStart = (text, currentOffset) => {
|
60
|
+
let i = currentOffset;
|
61
|
+
// If at beginning of text, return current position
|
62
|
+
if (i <= 0) {
|
63
|
+
return currentOffset;
|
64
|
+
}
|
65
|
+
// Move back one character to start searching
|
66
|
+
i--;
|
67
|
+
// Skip whitespace moving backwards
|
68
|
+
while (i >= 0 && (text[i] === ' ' || text[i] === '\t' || text[i] === '\n')) {
|
69
|
+
i--;
|
70
|
+
}
|
71
|
+
if (i < 0) {
|
72
|
+
return 0; // Reached beginning of text
|
73
|
+
}
|
74
|
+
const charAtI = text[i];
|
75
|
+
if (/\w/.test(charAtI)) {
|
76
|
+
// We're in a word, move to its beginning
|
77
|
+
while (i >= 0 && /\w/.test(text[i])) {
|
78
|
+
i--;
|
79
|
+
}
|
80
|
+
return i + 1; // Return first character of word
|
81
|
+
}
|
82
|
+
else {
|
83
|
+
// We're in punctuation, move to its beginning
|
84
|
+
while (i >= 0 &&
|
85
|
+
!/\w/.test(text[i]) &&
|
86
|
+
text[i] !== ' ' &&
|
87
|
+
text[i] !== '\t' &&
|
88
|
+
text[i] !== '\n') {
|
89
|
+
i--;
|
90
|
+
}
|
91
|
+
return i + 1; // Return first character of punctuation sequence
|
92
|
+
}
|
93
|
+
};
|
94
|
+
export const findWordEnd = (text, currentOffset) => {
|
95
|
+
let i = currentOffset;
|
96
|
+
// If we're already at the end of a word, advance to next word
|
97
|
+
if (i < text.length &&
|
98
|
+
/\w/.test(text[i]) &&
|
99
|
+
(i + 1 >= text.length || !/\w/.test(text[i + 1]))) {
|
100
|
+
// We're at the end of a word, move forward to find next word
|
101
|
+
i++;
|
102
|
+
// Skip whitespace/punctuation to find next word
|
103
|
+
while (i < text.length && !/\w/.test(text[i])) {
|
104
|
+
i++;
|
105
|
+
}
|
106
|
+
}
|
107
|
+
// If we're not on a word character, find the next word
|
108
|
+
if (i < text.length && !/\w/.test(text[i])) {
|
109
|
+
while (i < text.length && !/\w/.test(text[i])) {
|
110
|
+
i++;
|
111
|
+
}
|
112
|
+
}
|
113
|
+
// Move to end of current word
|
114
|
+
while (i < text.length && /\w/.test(text[i])) {
|
115
|
+
i++;
|
116
|
+
}
|
117
|
+
// Move back one to be on the last character of the word
|
118
|
+
return Math.max(currentOffset, i - 1);
|
119
|
+
};
|
120
|
+
// Helper functions for vim operations
|
121
|
+
export const getOffsetFromPosition = (row, col, lines) => {
|
122
|
+
let offset = 0;
|
123
|
+
for (let i = 0; i < row; i++) {
|
124
|
+
offset += lines[i].length + 1; // +1 for newline
|
125
|
+
}
|
126
|
+
offset += col;
|
127
|
+
return offset;
|
128
|
+
};
|
129
|
+
export const getPositionFromOffsets = (startOffset, endOffset, lines) => {
|
130
|
+
let offset = 0;
|
131
|
+
let startRow = 0;
|
132
|
+
let startCol = 0;
|
133
|
+
let endRow = 0;
|
134
|
+
let endCol = 0;
|
135
|
+
// Find start position
|
136
|
+
for (let i = 0; i < lines.length; i++) {
|
137
|
+
const lineLength = lines[i].length + 1; // +1 for newline
|
138
|
+
if (offset + lineLength > startOffset) {
|
139
|
+
startRow = i;
|
140
|
+
startCol = startOffset - offset;
|
141
|
+
break;
|
142
|
+
}
|
143
|
+
offset += lineLength;
|
144
|
+
}
|
145
|
+
// Find end position
|
146
|
+
offset = 0;
|
147
|
+
for (let i = 0; i < lines.length; i++) {
|
148
|
+
const lineLength = lines[i].length + (i < lines.length - 1 ? 1 : 0); // +1 for newline except last line
|
149
|
+
if (offset + lineLength >= endOffset) {
|
150
|
+
endRow = i;
|
151
|
+
endCol = endOffset - offset;
|
152
|
+
break;
|
153
|
+
}
|
154
|
+
offset += lineLength;
|
155
|
+
}
|
156
|
+
return { startRow, startCol, endRow, endCol };
|
157
|
+
};
|
158
|
+
export const getLineRangeOffsets = (startRow, lineCount, lines) => {
|
159
|
+
let startOffset = 0;
|
160
|
+
// Calculate start offset
|
161
|
+
for (let i = 0; i < startRow; i++) {
|
162
|
+
startOffset += lines[i].length + 1; // +1 for newline
|
163
|
+
}
|
164
|
+
// Calculate end offset
|
165
|
+
let endOffset = startOffset;
|
166
|
+
for (let i = 0; i < lineCount; i++) {
|
167
|
+
const lineIndex = startRow + i;
|
168
|
+
if (lineIndex < lines.length) {
|
169
|
+
endOffset += lines[lineIndex].length;
|
170
|
+
if (lineIndex < lines.length - 1) {
|
171
|
+
endOffset += 1; // +1 for newline
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}
|
175
|
+
return { startOffset, endOffset };
|
176
|
+
};
|
177
|
+
export const replaceRangeInternal = (state, startRow, startCol, endRow, endCol, text) => {
|
178
|
+
const currentLine = (row) => state.lines[row] || '';
|
179
|
+
const currentLineLen = (row) => cpLen(currentLine(row));
|
180
|
+
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
181
|
+
if (startRow > endRow ||
|
182
|
+
(startRow === endRow && startCol > endCol) ||
|
183
|
+
startRow < 0 ||
|
184
|
+
startCol < 0 ||
|
185
|
+
endRow >= state.lines.length ||
|
186
|
+
(endRow < state.lines.length && endCol > currentLineLen(endRow))) {
|
187
|
+
return state; // Invalid range
|
188
|
+
}
|
189
|
+
const newLines = [...state.lines];
|
190
|
+
const sCol = clamp(startCol, 0, currentLineLen(startRow));
|
191
|
+
const eCol = clamp(endCol, 0, currentLineLen(endRow));
|
192
|
+
const prefix = cpSlice(currentLine(startRow), 0, sCol);
|
193
|
+
const suffix = cpSlice(currentLine(endRow), eCol);
|
194
|
+
const normalisedReplacement = text
|
195
|
+
.replace(/\r\n/g, '\n')
|
196
|
+
.replace(/\r/g, '\n');
|
197
|
+
const replacementParts = normalisedReplacement.split('\n');
|
198
|
+
// Replace the content
|
199
|
+
if (startRow === endRow) {
|
200
|
+
newLines[startRow] = prefix + normalisedReplacement + suffix;
|
201
|
+
}
|
202
|
+
else {
|
203
|
+
const firstLine = prefix + replacementParts[0];
|
204
|
+
if (replacementParts.length === 1) {
|
205
|
+
// Single line of replacement text, but spanning multiple original lines
|
206
|
+
newLines.splice(startRow, endRow - startRow + 1, firstLine + suffix);
|
207
|
+
}
|
208
|
+
else {
|
209
|
+
// Multi-line replacement text
|
210
|
+
const lastLine = replacementParts[replacementParts.length - 1] + suffix;
|
211
|
+
const middleLines = replacementParts.slice(1, -1);
|
212
|
+
newLines.splice(startRow, endRow - startRow + 1, firstLine, ...middleLines, lastLine);
|
213
|
+
}
|
214
|
+
}
|
215
|
+
const finalCursorRow = startRow + replacementParts.length - 1;
|
216
|
+
const finalCursorCol = (replacementParts.length > 1 ? 0 : sCol) +
|
217
|
+
cpLen(replacementParts[replacementParts.length - 1]);
|
218
|
+
return {
|
219
|
+
...state,
|
220
|
+
lines: newLines,
|
221
|
+
cursorRow: Math.min(Math.max(finalCursorRow, 0), newLines.length - 1),
|
222
|
+
cursorCol: Math.max(0, Math.min(finalCursorCol, cpLen(newLines[finalCursorRow] || ''))),
|
223
|
+
preferredCol: null,
|
224
|
+
};
|
225
|
+
};
|
22
226
|
/**
|
23
227
|
* Strip characters that can break terminal rendering.
|
24
228
|
*
|
@@ -106,6 +310,24 @@ export function offsetToLogicalPos(text, offset) {
|
|
106
310
|
}
|
107
311
|
return [row, col];
|
108
312
|
}
|
313
|
+
/**
|
314
|
+
* Converts logical row/col position to absolute text offset
|
315
|
+
* Inverse operation of offsetToLogicalPos
|
316
|
+
*/
|
317
|
+
export function logicalPosToOffset(lines, row, col) {
|
318
|
+
let offset = 0;
|
319
|
+
// Clamp row to valid range
|
320
|
+
const actualRow = Math.min(row, lines.length - 1);
|
321
|
+
// Add lengths of all lines before the target row
|
322
|
+
for (let i = 0; i < actualRow; i++) {
|
323
|
+
offset += cpLen(lines[i]) + 1; // +1 for newline
|
324
|
+
}
|
325
|
+
// Add column offset within the target row
|
326
|
+
if (actualRow >= 0 && actualRow < lines.length) {
|
327
|
+
offset += Math.min(col, cpLen(lines[actualRow]));
|
328
|
+
}
|
329
|
+
return offset;
|
330
|
+
}
|
109
331
|
// Helper to calculate visual lines and map cursor positions
|
110
332
|
function calculateVisualLayout(logicalLines, logicalCursor, viewportWidth) {
|
111
333
|
const visualLines = [];
|
@@ -279,26 +501,27 @@ function calculateVisualLayout(logicalLines, logicalCursor, viewportWidth) {
|
|
279
501
|
};
|
280
502
|
}
|
281
503
|
const historyLimit = 100;
|
282
|
-
export
|
283
|
-
const
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
cursorCol: currentState.cursorCol,
|
288
|
-
};
|
289
|
-
const newStack = [...currentState.undoStack, snapshot];
|
290
|
-
if (newStack.length > historyLimit) {
|
291
|
-
newStack.shift();
|
292
|
-
}
|
293
|
-
return { ...currentState, undoStack: newStack, redoStack: [] };
|
504
|
+
export const pushUndo = (currentState) => {
|
505
|
+
const snapshot = {
|
506
|
+
lines: [...currentState.lines],
|
507
|
+
cursorRow: currentState.cursorRow,
|
508
|
+
cursorCol: currentState.cursorCol,
|
294
509
|
};
|
510
|
+
const newStack = [...currentState.undoStack, snapshot];
|
511
|
+
if (newStack.length > historyLimit) {
|
512
|
+
newStack.shift();
|
513
|
+
}
|
514
|
+
return { ...currentState, undoStack: newStack, redoStack: [] };
|
515
|
+
};
|
516
|
+
export function textBufferReducer(state, action) {
|
517
|
+
const pushUndoLocal = pushUndo;
|
295
518
|
const currentLine = (r) => state.lines[r] ?? '';
|
296
519
|
const currentLineLen = (r) => cpLen(currentLine(r));
|
297
520
|
switch (action.type) {
|
298
521
|
case 'set_text': {
|
299
522
|
let nextState = state;
|
300
523
|
if (action.pushToUndo !== false) {
|
301
|
-
nextState =
|
524
|
+
nextState = pushUndoLocal(state);
|
302
525
|
}
|
303
526
|
const newContentLines = action.payload
|
304
527
|
.replace(/\r\n?/g, '\n')
|
@@ -314,7 +537,7 @@ export function textBufferReducer(state, action) {
|
|
314
537
|
};
|
315
538
|
}
|
316
539
|
case 'insert': {
|
317
|
-
const nextState =
|
540
|
+
const nextState = pushUndoLocal(state);
|
318
541
|
const newLines = [...nextState.lines];
|
319
542
|
let newCursorRow = nextState.cursorRow;
|
320
543
|
let newCursorCol = nextState.cursorCol;
|
@@ -351,7 +574,7 @@ export function textBufferReducer(state, action) {
|
|
351
574
|
};
|
352
575
|
}
|
353
576
|
case 'backspace': {
|
354
|
-
const nextState =
|
577
|
+
const nextState = pushUndoLocal(state);
|
355
578
|
const newLines = [...nextState.lines];
|
356
579
|
let newCursorRow = nextState.cursorRow;
|
357
580
|
let newCursorCol = nextState.cursorCol;
|
@@ -528,7 +751,7 @@ export function textBufferReducer(state, action) {
|
|
528
751
|
const { cursorRow, cursorCol, lines } = state;
|
529
752
|
const lineContent = currentLine(cursorRow);
|
530
753
|
if (cursorCol < currentLineLen(cursorRow)) {
|
531
|
-
const nextState =
|
754
|
+
const nextState = pushUndoLocal(state);
|
532
755
|
const newLines = [...nextState.lines];
|
533
756
|
newLines[cursorRow] =
|
534
757
|
cpSlice(lineContent, 0, cursorCol) +
|
@@ -536,7 +759,7 @@ export function textBufferReducer(state, action) {
|
|
536
759
|
return { ...nextState, lines: newLines, preferredCol: null };
|
537
760
|
}
|
538
761
|
else if (cursorRow < lines.length - 1) {
|
539
|
-
const nextState =
|
762
|
+
const nextState = pushUndoLocal(state);
|
540
763
|
const nextLineContent = currentLine(cursorRow + 1);
|
541
764
|
const newLines = [...nextState.lines];
|
542
765
|
newLines[cursorRow] = lineContent + nextLineContent;
|
@@ -551,7 +774,7 @@ export function textBufferReducer(state, action) {
|
|
551
774
|
return state;
|
552
775
|
if (cursorCol === 0) {
|
553
776
|
// Act as a backspace
|
554
|
-
const nextState =
|
777
|
+
const nextState = pushUndoLocal(state);
|
555
778
|
const prevLineContent = currentLine(cursorRow - 1);
|
556
779
|
const currentLineContentVal = currentLine(cursorRow);
|
557
780
|
const newCol = cpLen(prevLineContent);
|
@@ -566,7 +789,7 @@ export function textBufferReducer(state, action) {
|
|
566
789
|
preferredCol: null,
|
567
790
|
};
|
568
791
|
}
|
569
|
-
const nextState =
|
792
|
+
const nextState = pushUndoLocal(state);
|
570
793
|
const lineContent = currentLine(cursorRow);
|
571
794
|
const arr = toCodePoints(lineContent);
|
572
795
|
let start = cursorCol;
|
@@ -604,14 +827,14 @@ export function textBufferReducer(state, action) {
|
|
604
827
|
return state;
|
605
828
|
if (cursorCol >= arr.length) {
|
606
829
|
// Act as a delete
|
607
|
-
const nextState =
|
830
|
+
const nextState = pushUndoLocal(state);
|
608
831
|
const nextLineContent = currentLine(cursorRow + 1);
|
609
832
|
const newLines = [...nextState.lines];
|
610
833
|
newLines[cursorRow] = lineContent + nextLineContent;
|
611
834
|
newLines.splice(cursorRow + 1, 1);
|
612
835
|
return { ...nextState, lines: newLines, preferredCol: null };
|
613
836
|
}
|
614
|
-
const nextState =
|
837
|
+
const nextState = pushUndoLocal(state);
|
615
838
|
let end = cursorCol;
|
616
839
|
while (end < arr.length && !isWordChar(arr[end]))
|
617
840
|
end++;
|
@@ -626,14 +849,14 @@ export function textBufferReducer(state, action) {
|
|
626
849
|
const { cursorRow, cursorCol, lines } = state;
|
627
850
|
const lineContent = currentLine(cursorRow);
|
628
851
|
if (cursorCol < currentLineLen(cursorRow)) {
|
629
|
-
const nextState =
|
852
|
+
const nextState = pushUndoLocal(state);
|
630
853
|
const newLines = [...nextState.lines];
|
631
854
|
newLines[cursorRow] = cpSlice(lineContent, 0, cursorCol);
|
632
855
|
return { ...nextState, lines: newLines };
|
633
856
|
}
|
634
857
|
else if (cursorRow < lines.length - 1) {
|
635
858
|
// Act as a delete
|
636
|
-
const nextState =
|
859
|
+
const nextState = pushUndoLocal(state);
|
637
860
|
const nextLineContent = currentLine(cursorRow + 1);
|
638
861
|
const newLines = [...nextState.lines];
|
639
862
|
newLines[cursorRow] = lineContent + nextLineContent;
|
@@ -645,7 +868,7 @@ export function textBufferReducer(state, action) {
|
|
645
868
|
case 'kill_line_left': {
|
646
869
|
const { cursorRow, cursorCol } = state;
|
647
870
|
if (cursorCol > 0) {
|
648
|
-
const nextState =
|
871
|
+
const nextState = pushUndoLocal(state);
|
649
872
|
const lineContent = currentLine(cursorRow);
|
650
873
|
const newLines = [...nextState.lines];
|
651
874
|
newLines[cursorRow] = cpSlice(lineContent, cursorCol);
|
@@ -692,51 +915,8 @@ export function textBufferReducer(state, action) {
|
|
692
915
|
}
|
693
916
|
case 'replace_range': {
|
694
917
|
const { startRow, startCol, endRow, endCol, text } = action.payload;
|
695
|
-
|
696
|
-
|
697
|
-
startRow < 0 ||
|
698
|
-
startCol < 0 ||
|
699
|
-
endRow >= state.lines.length ||
|
700
|
-
(endRow < state.lines.length && endCol > currentLineLen(endRow))) {
|
701
|
-
return state; // Invalid range
|
702
|
-
}
|
703
|
-
const nextState = pushUndo(state);
|
704
|
-
const newLines = [...nextState.lines];
|
705
|
-
const sCol = clamp(startCol, 0, currentLineLen(startRow));
|
706
|
-
const eCol = clamp(endCol, 0, currentLineLen(endRow));
|
707
|
-
const prefix = cpSlice(currentLine(startRow), 0, sCol);
|
708
|
-
const suffix = cpSlice(currentLine(endRow), eCol);
|
709
|
-
const normalisedReplacement = text
|
710
|
-
.replace(/\r\n/g, '\n')
|
711
|
-
.replace(/\r/g, '\n');
|
712
|
-
const replacementParts = normalisedReplacement.split('\n');
|
713
|
-
// Replace the content
|
714
|
-
if (startRow === endRow) {
|
715
|
-
newLines[startRow] = prefix + normalisedReplacement + suffix;
|
716
|
-
}
|
717
|
-
else {
|
718
|
-
const firstLine = prefix + replacementParts[0];
|
719
|
-
if (replacementParts.length === 1) {
|
720
|
-
// Single line of replacement text, but spanning multiple original lines
|
721
|
-
newLines.splice(startRow, endRow - startRow + 1, firstLine + suffix);
|
722
|
-
}
|
723
|
-
else {
|
724
|
-
// Multi-line replacement text
|
725
|
-
const lastLine = replacementParts[replacementParts.length - 1] + suffix;
|
726
|
-
const middleLines = replacementParts.slice(1, -1);
|
727
|
-
newLines.splice(startRow, endRow - startRow + 1, firstLine, ...middleLines, lastLine);
|
728
|
-
}
|
729
|
-
}
|
730
|
-
const finalCursorRow = startRow + replacementParts.length - 1;
|
731
|
-
const finalCursorCol = (replacementParts.length > 1 ? 0 : sCol) +
|
732
|
-
cpLen(replacementParts[replacementParts.length - 1]);
|
733
|
-
return {
|
734
|
-
...nextState,
|
735
|
-
lines: newLines,
|
736
|
-
cursorRow: finalCursorRow,
|
737
|
-
cursorCol: finalCursorCol,
|
738
|
-
preferredCol: null,
|
739
|
-
};
|
918
|
+
const nextState = pushUndoLocal(state);
|
919
|
+
return replaceRangeInternal(nextState, startRow, startCol, endRow, endCol, text);
|
740
920
|
}
|
741
921
|
case 'move_to_offset': {
|
742
922
|
const { offset } = action.payload;
|
@@ -749,8 +929,42 @@ export function textBufferReducer(state, action) {
|
|
749
929
|
};
|
750
930
|
}
|
751
931
|
case 'create_undo_snapshot': {
|
752
|
-
return
|
932
|
+
return pushUndoLocal(state);
|
753
933
|
}
|
934
|
+
// Vim-specific operations
|
935
|
+
case 'vim_delete_word_forward':
|
936
|
+
case 'vim_delete_word_backward':
|
937
|
+
case 'vim_delete_word_end':
|
938
|
+
case 'vim_change_word_forward':
|
939
|
+
case 'vim_change_word_backward':
|
940
|
+
case 'vim_change_word_end':
|
941
|
+
case 'vim_delete_line':
|
942
|
+
case 'vim_change_line':
|
943
|
+
case 'vim_delete_to_end_of_line':
|
944
|
+
case 'vim_change_to_end_of_line':
|
945
|
+
case 'vim_change_movement':
|
946
|
+
case 'vim_move_left':
|
947
|
+
case 'vim_move_right':
|
948
|
+
case 'vim_move_up':
|
949
|
+
case 'vim_move_down':
|
950
|
+
case 'vim_move_word_forward':
|
951
|
+
case 'vim_move_word_backward':
|
952
|
+
case 'vim_move_word_end':
|
953
|
+
case 'vim_delete_char':
|
954
|
+
case 'vim_insert_at_cursor':
|
955
|
+
case 'vim_append_at_cursor':
|
956
|
+
case 'vim_open_line_below':
|
957
|
+
case 'vim_open_line_above':
|
958
|
+
case 'vim_append_at_line_end':
|
959
|
+
case 'vim_insert_at_line_start':
|
960
|
+
case 'vim_move_to_line_start':
|
961
|
+
case 'vim_move_to_line_end':
|
962
|
+
case 'vim_move_to_first_nonwhitespace':
|
963
|
+
case 'vim_move_to_first_line':
|
964
|
+
case 'vim_move_to_last_line':
|
965
|
+
case 'vim_move_to_line':
|
966
|
+
case 'vim_escape_insert_mode':
|
967
|
+
return handleVimAction(state, action);
|
754
968
|
default: {
|
755
969
|
const exhaustiveCheck = action;
|
756
970
|
console.error(`Unknown action encountered: ${exhaustiveCheck}`);
|
@@ -872,6 +1086,104 @@ export function useTextBuffer({ initialText = '', initialCursorOffset = 0, viewp
|
|
872
1086
|
const killLineLeft = useCallback(() => {
|
873
1087
|
dispatch({ type: 'kill_line_left' });
|
874
1088
|
}, []);
|
1089
|
+
// Vim-specific operations
|
1090
|
+
const vimDeleteWordForward = useCallback((count) => {
|
1091
|
+
dispatch({ type: 'vim_delete_word_forward', payload: { count } });
|
1092
|
+
}, []);
|
1093
|
+
const vimDeleteWordBackward = useCallback((count) => {
|
1094
|
+
dispatch({ type: 'vim_delete_word_backward', payload: { count } });
|
1095
|
+
}, []);
|
1096
|
+
const vimDeleteWordEnd = useCallback((count) => {
|
1097
|
+
dispatch({ type: 'vim_delete_word_end', payload: { count } });
|
1098
|
+
}, []);
|
1099
|
+
const vimChangeWordForward = useCallback((count) => {
|
1100
|
+
dispatch({ type: 'vim_change_word_forward', payload: { count } });
|
1101
|
+
}, []);
|
1102
|
+
const vimChangeWordBackward = useCallback((count) => {
|
1103
|
+
dispatch({ type: 'vim_change_word_backward', payload: { count } });
|
1104
|
+
}, []);
|
1105
|
+
const vimChangeWordEnd = useCallback((count) => {
|
1106
|
+
dispatch({ type: 'vim_change_word_end', payload: { count } });
|
1107
|
+
}, []);
|
1108
|
+
const vimDeleteLine = useCallback((count) => {
|
1109
|
+
dispatch({ type: 'vim_delete_line', payload: { count } });
|
1110
|
+
}, []);
|
1111
|
+
const vimChangeLine = useCallback((count) => {
|
1112
|
+
dispatch({ type: 'vim_change_line', payload: { count } });
|
1113
|
+
}, []);
|
1114
|
+
const vimDeleteToEndOfLine = useCallback(() => {
|
1115
|
+
dispatch({ type: 'vim_delete_to_end_of_line' });
|
1116
|
+
}, []);
|
1117
|
+
const vimChangeToEndOfLine = useCallback(() => {
|
1118
|
+
dispatch({ type: 'vim_change_to_end_of_line' });
|
1119
|
+
}, []);
|
1120
|
+
const vimChangeMovement = useCallback((movement, count) => {
|
1121
|
+
dispatch({ type: 'vim_change_movement', payload: { movement, count } });
|
1122
|
+
}, []);
|
1123
|
+
// New vim navigation and operation methods
|
1124
|
+
const vimMoveLeft = useCallback((count) => {
|
1125
|
+
dispatch({ type: 'vim_move_left', payload: { count } });
|
1126
|
+
}, []);
|
1127
|
+
const vimMoveRight = useCallback((count) => {
|
1128
|
+
dispatch({ type: 'vim_move_right', payload: { count } });
|
1129
|
+
}, []);
|
1130
|
+
const vimMoveUp = useCallback((count) => {
|
1131
|
+
dispatch({ type: 'vim_move_up', payload: { count } });
|
1132
|
+
}, []);
|
1133
|
+
const vimMoveDown = useCallback((count) => {
|
1134
|
+
dispatch({ type: 'vim_move_down', payload: { count } });
|
1135
|
+
}, []);
|
1136
|
+
const vimMoveWordForward = useCallback((count) => {
|
1137
|
+
dispatch({ type: 'vim_move_word_forward', payload: { count } });
|
1138
|
+
}, []);
|
1139
|
+
const vimMoveWordBackward = useCallback((count) => {
|
1140
|
+
dispatch({ type: 'vim_move_word_backward', payload: { count } });
|
1141
|
+
}, []);
|
1142
|
+
const vimMoveWordEnd = useCallback((count) => {
|
1143
|
+
dispatch({ type: 'vim_move_word_end', payload: { count } });
|
1144
|
+
}, []);
|
1145
|
+
const vimDeleteChar = useCallback((count) => {
|
1146
|
+
dispatch({ type: 'vim_delete_char', payload: { count } });
|
1147
|
+
}, []);
|
1148
|
+
const vimInsertAtCursor = useCallback(() => {
|
1149
|
+
dispatch({ type: 'vim_insert_at_cursor' });
|
1150
|
+
}, []);
|
1151
|
+
const vimAppendAtCursor = useCallback(() => {
|
1152
|
+
dispatch({ type: 'vim_append_at_cursor' });
|
1153
|
+
}, []);
|
1154
|
+
const vimOpenLineBelow = useCallback(() => {
|
1155
|
+
dispatch({ type: 'vim_open_line_below' });
|
1156
|
+
}, []);
|
1157
|
+
const vimOpenLineAbove = useCallback(() => {
|
1158
|
+
dispatch({ type: 'vim_open_line_above' });
|
1159
|
+
}, []);
|
1160
|
+
const vimAppendAtLineEnd = useCallback(() => {
|
1161
|
+
dispatch({ type: 'vim_append_at_line_end' });
|
1162
|
+
}, []);
|
1163
|
+
const vimInsertAtLineStart = useCallback(() => {
|
1164
|
+
dispatch({ type: 'vim_insert_at_line_start' });
|
1165
|
+
}, []);
|
1166
|
+
const vimMoveToLineStart = useCallback(() => {
|
1167
|
+
dispatch({ type: 'vim_move_to_line_start' });
|
1168
|
+
}, []);
|
1169
|
+
const vimMoveToLineEnd = useCallback(() => {
|
1170
|
+
dispatch({ type: 'vim_move_to_line_end' });
|
1171
|
+
}, []);
|
1172
|
+
const vimMoveToFirstNonWhitespace = useCallback(() => {
|
1173
|
+
dispatch({ type: 'vim_move_to_first_nonwhitespace' });
|
1174
|
+
}, []);
|
1175
|
+
const vimMoveToFirstLine = useCallback(() => {
|
1176
|
+
dispatch({ type: 'vim_move_to_first_line' });
|
1177
|
+
}, []);
|
1178
|
+
const vimMoveToLastLine = useCallback(() => {
|
1179
|
+
dispatch({ type: 'vim_move_to_last_line' });
|
1180
|
+
}, []);
|
1181
|
+
const vimMoveToLine = useCallback((lineNumber) => {
|
1182
|
+
dispatch({ type: 'vim_move_to_line', payload: { lineNumber } });
|
1183
|
+
}, []);
|
1184
|
+
const vimEscapeInsertMode = useCallback(() => {
|
1185
|
+
dispatch({ type: 'vim_escape_insert_mode' });
|
1186
|
+
}, []);
|
875
1187
|
const openInExternalEditor = useCallback(async (opts = {}) => {
|
876
1188
|
const editor = opts.editor ??
|
877
1189
|
process.env['VISUAL'] ??
|
@@ -1016,6 +1328,39 @@ export function useTextBuffer({ initialText = '', initialCursorOffset = 0, viewp
|
|
1016
1328
|
killLineLeft,
|
1017
1329
|
handleInput,
|
1018
1330
|
openInExternalEditor,
|
1331
|
+
// Vim-specific operations
|
1332
|
+
vimDeleteWordForward,
|
1333
|
+
vimDeleteWordBackward,
|
1334
|
+
vimDeleteWordEnd,
|
1335
|
+
vimChangeWordForward,
|
1336
|
+
vimChangeWordBackward,
|
1337
|
+
vimChangeWordEnd,
|
1338
|
+
vimDeleteLine,
|
1339
|
+
vimChangeLine,
|
1340
|
+
vimDeleteToEndOfLine,
|
1341
|
+
vimChangeToEndOfLine,
|
1342
|
+
vimChangeMovement,
|
1343
|
+
vimMoveLeft,
|
1344
|
+
vimMoveRight,
|
1345
|
+
vimMoveUp,
|
1346
|
+
vimMoveDown,
|
1347
|
+
vimMoveWordForward,
|
1348
|
+
vimMoveWordBackward,
|
1349
|
+
vimMoveWordEnd,
|
1350
|
+
vimDeleteChar,
|
1351
|
+
vimInsertAtCursor,
|
1352
|
+
vimAppendAtCursor,
|
1353
|
+
vimOpenLineBelow,
|
1354
|
+
vimOpenLineAbove,
|
1355
|
+
vimAppendAtLineEnd,
|
1356
|
+
vimInsertAtLineStart,
|
1357
|
+
vimMoveToLineStart,
|
1358
|
+
vimMoveToLineEnd,
|
1359
|
+
vimMoveToFirstNonWhitespace,
|
1360
|
+
vimMoveToFirstLine,
|
1361
|
+
vimMoveToLastLine,
|
1362
|
+
vimMoveToLine,
|
1363
|
+
vimEscapeInsertMode,
|
1019
1364
|
};
|
1020
1365
|
return returnValue;
|
1021
1366
|
}
|