@google/gemini-cli 0.13.0-nightly.20251102.d7243fb8 → 0.13.0-preview.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.
- package/dist/google-gemini-cli-0.13.0-nightly.20251031.c89bc30d.tgz +0 -0
- package/dist/package.json +3 -3
- package/dist/src/commands/mcp/list.test.js +25 -21
- package/dist/src/commands/mcp/list.test.js.map +1 -1
- package/dist/src/config/config.js +11 -84
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +13 -30
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/extension-manager.d.ts +18 -6
- package/dist/src/config/extension-manager.js +25 -19
- package/dist/src/config/extension-manager.js.map +1 -1
- package/dist/src/config/extension.test.js +9 -9
- package/dist/src/config/extension.test.js.map +1 -1
- package/dist/src/config/extensions/extensionSettings.test.js +39 -43
- package/dist/src/config/extensions/extensionSettings.test.js.map +1 -1
- package/dist/src/config/extensions/github.test.js +133 -165
- package/dist/src/config/extensions/github.test.js.map +1 -1
- package/dist/src/config/keyBindings.d.ts +3 -0
- package/dist/src/config/keyBindings.js +29 -7
- package/dist/src/config/keyBindings.js.map +1 -1
- package/dist/src/config/keyBindings.test.js +17 -0
- package/dist/src/config/keyBindings.test.js.map +1 -1
- package/dist/src/config/policy.d.ts +0 -7
- package/dist/src/config/policy.js +10 -177
- package/dist/src/config/policy.js.map +1 -1
- package/dist/src/config/settings.js +1 -0
- package/dist/src/config/settings.js.map +1 -1
- package/dist/src/config/settingsSchema.d.ts +123 -22
- package/dist/src/config/settingsSchema.js +371 -21
- package/dist/src/config/settingsSchema.js.map +1 -1
- package/dist/src/config/settingsSchema.test.js +40 -1
- package/dist/src/config/settingsSchema.test.js.map +1 -1
- package/dist/src/gemini.js +15 -5
- package/dist/src/gemini.js.map +1 -1
- package/dist/src/gemini.test.js +2 -0
- package/dist/src/gemini.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/generated/git-commit.js.map +1 -1
- package/dist/src/nonInteractiveCli.js +68 -1
- package/dist/src/nonInteractiveCli.js.map +1 -1
- package/dist/src/services/BuiltinCommandLoader.js +4 -0
- package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
- package/dist/src/services/BuiltinCommandLoader.test.js +22 -0
- package/dist/src/services/BuiltinCommandLoader.test.js.map +1 -1
- package/dist/src/services/McpPromptLoader.js +2 -2
- package/dist/src/services/McpPromptLoader.js.map +1 -1
- package/dist/src/services/McpPromptLoader.test.js +4 -2
- package/dist/src/services/McpPromptLoader.test.js.map +1 -1
- package/dist/src/test-utils/render.d.ts +2 -1
- package/dist/src/test-utils/render.js +3 -2
- package/dist/src/test-utils/render.js.map +1 -1
- package/dist/src/ui/AppContainer.js +32 -15
- package/dist/src/ui/AppContainer.js.map +1 -1
- package/dist/src/ui/AppContainer.test.js +160 -0
- package/dist/src/ui/AppContainer.test.js.map +1 -1
- package/dist/src/ui/commands/mcpCommand.js +14 -14
- package/dist/src/ui/commands/mcpCommand.js.map +1 -1
- package/dist/src/ui/commands/mcpCommand.test.js +4 -0
- package/dist/src/ui/commands/mcpCommand.test.js.map +1 -1
- package/dist/src/ui/commands/policiesCommand.d.ts +7 -0
- package/dist/src/ui/commands/policiesCommand.js +59 -0
- package/dist/src/ui/commands/policiesCommand.js.map +1 -0
- package/dist/src/ui/commands/policiesCommand.test.js +83 -0
- package/dist/src/ui/commands/policiesCommand.test.js.map +1 -0
- package/dist/src/ui/components/Composer.js +1 -1
- package/dist/src/ui/components/Composer.js.map +1 -1
- package/dist/src/ui/components/Composer.test.js +4 -1
- package/dist/src/ui/components/Composer.test.js.map +1 -1
- package/dist/src/ui/components/ConfigInitDisplay.js +4 -6
- package/dist/src/ui/components/ConfigInitDisplay.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.js +22 -2
- package/dist/src/ui/components/InputPrompt.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.test.js +70 -5
- package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
- package/dist/src/ui/components/MainContent.js +15 -4
- package/dist/src/ui/components/MainContent.js.map +1 -1
- package/dist/src/ui/components/Notifications.js +38 -5
- package/dist/src/ui/components/Notifications.js.map +1 -1
- package/dist/src/ui/components/SettingsDialog.js +32 -25
- package/dist/src/ui/components/SettingsDialog.js.map +1 -1
- package/dist/src/ui/components/ShellConfirmationDialog.js +1 -1
- package/dist/src/ui/components/ShellConfirmationDialog.js.map +1 -1
- package/dist/src/ui/components/messages/InfoMessage.js +1 -1
- package/dist/src/ui/components/messages/InfoMessage.js.map +1 -1
- package/dist/src/ui/components/messages/ToolConfirmationMessage.js +1 -1
- package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
- package/dist/src/ui/components/messages/WarningMessage.js +2 -2
- package/dist/src/ui/components/messages/WarningMessage.js.map +1 -1
- package/dist/src/ui/components/shared/text-buffer.d.ts +1 -0
- package/dist/src/ui/components/shared/text-buffer.js +23 -0
- package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
- package/dist/src/ui/components/shared/text-buffer.test.js +246 -201
- package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.js +182 -132
- package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.test.js +144 -8
- package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
- package/dist/src/ui/contexts/MouseContext.d.ts +21 -0
- package/dist/src/ui/contexts/MouseContext.js +89 -0
- package/dist/src/ui/contexts/MouseContext.js.map +1 -0
- package/dist/src/ui/contexts/MouseContext.test.js +164 -0
- package/dist/src/ui/contexts/MouseContext.test.js.map +1 -0
- package/dist/src/ui/hooks/slashCommandProcessor.test.js +70 -73
- package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.test.js +135 -368
- package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
- package/dist/src/ui/hooks/useKeypress.test.js +17 -9
- package/dist/src/ui/hooks/useKeypress.test.js.map +1 -1
- package/dist/src/ui/hooks/useMouse.d.ts +17 -0
- package/dist/src/ui/hooks/useMouse.js +27 -0
- package/dist/src/ui/hooks/useMouse.js.map +1 -0
- package/dist/src/ui/hooks/useMouse.test.d.ts +6 -0
- package/dist/src/ui/hooks/useMouse.test.js +57 -0
- package/dist/src/ui/hooks/useMouse.test.js.map +1 -0
- package/dist/src/ui/hooks/useSelectionList.js +5 -4
- package/dist/src/ui/hooks/useSelectionList.js.map +1 -1
- package/dist/src/ui/hooks/useSelectionList.test.js +24 -3
- package/dist/src/ui/hooks/useSelectionList.test.js.map +1 -1
- package/dist/src/ui/hooks/useToolScheduler.test.js +109 -200
- package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
- package/dist/src/ui/keyMatchers.test.js +27 -0
- package/dist/src/ui/keyMatchers.test.js.map +1 -1
- package/dist/src/ui/themes/no-color.js +1 -0
- package/dist/src/ui/themes/no-color.js.map +1 -1
- package/dist/src/ui/themes/semantic-tokens.d.ts +1 -0
- package/dist/src/ui/themes/semantic-tokens.js +3 -0
- package/dist/src/ui/themes/semantic-tokens.js.map +1 -1
- package/dist/src/ui/themes/theme.d.ts +1 -0
- package/dist/src/ui/themes/theme.js +4 -0
- package/dist/src/ui/themes/theme.js.map +1 -1
- package/dist/src/ui/utils/InlineMarkdownRenderer.d.ts +1 -0
- package/dist/src/ui/utils/InlineMarkdownRenderer.js +11 -10
- package/dist/src/ui/utils/InlineMarkdownRenderer.js.map +1 -1
- package/dist/src/ui/utils/MarkdownDisplay.js +11 -9
- package/dist/src/ui/utils/MarkdownDisplay.js.map +1 -1
- package/dist/src/ui/utils/input.d.ts +17 -0
- package/dist/src/ui/utils/input.js +51 -0
- package/dist/src/ui/utils/input.js.map +1 -0
- package/dist/src/ui/utils/input.test.d.ts +6 -0
- package/dist/src/ui/utils/input.test.js +44 -0
- package/dist/src/ui/utils/input.test.js.map +1 -0
- package/dist/src/ui/utils/kittyProtocolDetector.js +13 -4
- package/dist/src/ui/utils/kittyProtocolDetector.js.map +1 -1
- package/dist/src/ui/utils/mouse.d.ts +31 -0
- package/dist/src/ui/utils/mouse.js +164 -0
- package/dist/src/ui/utils/mouse.js.map +1 -0
- package/dist/src/ui/utils/mouse.test.d.ts +6 -0
- package/dist/src/ui/utils/mouse.test.js +131 -0
- package/dist/src/ui/utils/mouse.test.js.map +1 -0
- package/dist/src/utils/events.d.ts +11 -2
- package/dist/src/utils/events.js +1 -0
- package/dist/src/utils/events.js.map +1 -1
- package/dist/src/utils/sandbox.js +16 -18
- package/dist/src/utils/sandbox.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/dist/src/config/policy-toml-loader.d.ts +0 -46
- package/dist/src/config/policy-toml-loader.js +0 -314
- package/dist/src/config/policy-toml-loader.js.map +0 -1
- package/dist/src/config/policy-toml-loader.test.js +0 -626
- package/dist/src/config/policy-toml-loader.test.js.map +0 -1
- package/dist/src/config/policy.test.js +0 -1058
- package/dist/src/config/policy.test.js.map +0 -1
- /package/dist/src/{config/policy-toml-loader.test.d.ts → ui/commands/policiesCommand.test.d.ts} +0 -0
- /package/dist/src/{config/policy.test.d.ts → ui/contexts/MouseContext.test.d.ts} +0 -0
|
@@ -188,41 +188,38 @@ describe('textBufferReducer', () => {
|
|
|
188
188
|
});
|
|
189
189
|
});
|
|
190
190
|
describe('delete_word_left action', () => {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
191
|
+
const createSingleLineState = (text, col) => ({
|
|
192
|
+
...initialState,
|
|
193
|
+
lines: [text],
|
|
194
|
+
cursorRow: 0,
|
|
195
|
+
cursorCol: col,
|
|
196
|
+
});
|
|
197
|
+
it.each([
|
|
198
|
+
{
|
|
199
|
+
input: 'hello world',
|
|
196
200
|
cursorCol: 11,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
it('should delete a path segment', () => {
|
|
204
|
-
const stateWithText = {
|
|
205
|
-
...initialState,
|
|
206
|
-
lines: ['path/to/file'],
|
|
207
|
-
cursorRow: 0,
|
|
201
|
+
expectedLines: ['hello '],
|
|
202
|
+
expectedCol: 6,
|
|
203
|
+
desc: 'simple word',
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
input: 'path/to/file',
|
|
208
207
|
cursorCol: 12,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
it('should delete variable_name parts', () => {
|
|
216
|
-
const stateWithText = {
|
|
217
|
-
...initialState,
|
|
218
|
-
lines: ['variable_name'],
|
|
219
|
-
cursorRow: 0,
|
|
208
|
+
expectedLines: ['path/to/'],
|
|
209
|
+
expectedCol: 8,
|
|
210
|
+
desc: 'path segment',
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
input: 'variable_name',
|
|
220
214
|
cursorCol: 13,
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
215
|
+
expectedLines: ['variable_'],
|
|
216
|
+
expectedCol: 9,
|
|
217
|
+
desc: 'variable_name parts',
|
|
218
|
+
},
|
|
219
|
+
])('should delete $desc', ({ input, cursorCol, expectedLines, expectedCol }) => {
|
|
220
|
+
const state = textBufferReducer(createSingleLineState(input, cursorCol), { type: 'delete_word_left' });
|
|
221
|
+
expect(state.lines).toEqual(expectedLines);
|
|
222
|
+
expect(state.cursorCol).toBe(expectedCol);
|
|
226
223
|
});
|
|
227
224
|
it('should act like backspace at the beginning of a line', () => {
|
|
228
225
|
const stateWithText = {
|
|
@@ -231,51 +228,55 @@ describe('textBufferReducer', () => {
|
|
|
231
228
|
cursorRow: 1,
|
|
232
229
|
cursorCol: 0,
|
|
233
230
|
};
|
|
234
|
-
const
|
|
235
|
-
|
|
231
|
+
const state = textBufferReducer(stateWithText, {
|
|
232
|
+
type: 'delete_word_left',
|
|
233
|
+
});
|
|
236
234
|
expect(state.lines).toEqual(['helloworld']);
|
|
237
235
|
expect(state.cursorRow).toBe(0);
|
|
238
236
|
expect(state.cursorCol).toBe(5);
|
|
239
237
|
});
|
|
240
238
|
});
|
|
241
239
|
describe('delete_word_right action', () => {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
240
|
+
const createSingleLineState = (text, col) => ({
|
|
241
|
+
...initialState,
|
|
242
|
+
lines: [text],
|
|
243
|
+
cursorRow: 0,
|
|
244
|
+
cursorCol: col,
|
|
245
|
+
});
|
|
246
|
+
it.each([
|
|
247
|
+
{
|
|
248
|
+
input: 'hello world',
|
|
247
249
|
cursorCol: 0,
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
250
|
+
expectedLines: ['world'],
|
|
251
|
+
expectedCol: 0,
|
|
252
|
+
desc: 'simple word',
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
input: 'variable_name',
|
|
256
|
+
cursorCol: 0,
|
|
257
|
+
expectedLines: ['_name'],
|
|
258
|
+
expectedCol: 0,
|
|
259
|
+
desc: 'variable_name parts',
|
|
260
|
+
},
|
|
261
|
+
])('should delete $desc', ({ input, cursorCol, expectedLines, expectedCol }) => {
|
|
262
|
+
const state = textBufferReducer(createSingleLineState(input, cursorCol), { type: 'delete_word_right' });
|
|
263
|
+
expect(state.lines).toEqual(expectedLines);
|
|
264
|
+
expect(state.cursorCol).toBe(expectedCol);
|
|
265
|
+
});
|
|
266
|
+
it('should delete path segments progressively', () => {
|
|
255
267
|
const stateWithText = {
|
|
256
268
|
...initialState,
|
|
257
269
|
lines: ['path/to/file'],
|
|
258
270
|
cursorRow: 0,
|
|
259
271
|
cursorCol: 0,
|
|
260
272
|
};
|
|
261
|
-
|
|
262
|
-
|
|
273
|
+
let state = textBufferReducer(stateWithText, {
|
|
274
|
+
type: 'delete_word_right',
|
|
275
|
+
});
|
|
263
276
|
expect(state.lines).toEqual(['/to/file']);
|
|
264
|
-
state = textBufferReducer(state,
|
|
277
|
+
state = textBufferReducer(state, { type: 'delete_word_right' });
|
|
265
278
|
expect(state.lines).toEqual(['to/file']);
|
|
266
279
|
});
|
|
267
|
-
it('should delete variable_name parts', () => {
|
|
268
|
-
const stateWithText = {
|
|
269
|
-
...initialState,
|
|
270
|
-
lines: ['variable_name'],
|
|
271
|
-
cursorRow: 0,
|
|
272
|
-
cursorCol: 0,
|
|
273
|
-
};
|
|
274
|
-
const action = { type: 'delete_word_right' };
|
|
275
|
-
const state = textBufferReducer(stateWithText, action);
|
|
276
|
-
expect(state.lines).toEqual(['_name']);
|
|
277
|
-
expect(state.cursorCol).toBe(0);
|
|
278
|
-
});
|
|
279
280
|
it('should act like delete at the end of a line', () => {
|
|
280
281
|
const stateWithText = {
|
|
281
282
|
...initialState,
|
|
@@ -283,15 +284,15 @@ describe('textBufferReducer', () => {
|
|
|
283
284
|
cursorRow: 0,
|
|
284
285
|
cursorCol: 5,
|
|
285
286
|
};
|
|
286
|
-
const
|
|
287
|
-
|
|
287
|
+
const state = textBufferReducer(stateWithText, {
|
|
288
|
+
type: 'delete_word_right',
|
|
289
|
+
});
|
|
288
290
|
expect(state.lines).toEqual(['helloworld']);
|
|
289
291
|
expect(state.cursorRow).toBe(0);
|
|
290
292
|
expect(state.cursorCol).toBe(5);
|
|
291
293
|
});
|
|
292
294
|
});
|
|
293
295
|
});
|
|
294
|
-
// Helper to get the state from the hook
|
|
295
296
|
const getBufferState = (result) => {
|
|
296
297
|
expect(result.current).toHaveOnlyValidCharacters();
|
|
297
298
|
return {
|
|
@@ -1129,71 +1130,46 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
|
|
1129
1130
|
});
|
|
1130
1131
|
});
|
|
1131
1132
|
describe('Input Sanitization', () => {
|
|
1132
|
-
|
|
1133
|
+
const createInput = (sequence) => ({
|
|
1134
|
+
name: '',
|
|
1135
|
+
ctrl: false,
|
|
1136
|
+
meta: false,
|
|
1137
|
+
shift: false,
|
|
1138
|
+
paste: false,
|
|
1139
|
+
sequence,
|
|
1140
|
+
});
|
|
1141
|
+
it.each([
|
|
1142
|
+
{
|
|
1143
|
+
input: '\x1B[31mHello\x1B[0m \x1B[32mWorld\x1B[0m',
|
|
1144
|
+
expected: 'Hello World',
|
|
1145
|
+
desc: 'ANSI escape codes',
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
input: 'H\x07e\x08l\x0Bl\x0Co',
|
|
1149
|
+
expected: 'Hello',
|
|
1150
|
+
desc: 'control characters',
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
input: '\u001B[4mH\u001B[0mello',
|
|
1154
|
+
expected: 'Hello',
|
|
1155
|
+
desc: 'mixed ANSI and control characters',
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
input: '\u001B[4mPasted\u001B[4m Text',
|
|
1159
|
+
expected: 'Pasted Text',
|
|
1160
|
+
desc: 'pasted text with ANSI',
|
|
1161
|
+
},
|
|
1162
|
+
])('should strip $desc from input', ({ input, expected }) => {
|
|
1133
1163
|
const { result } = renderHook(() => useTextBuffer({ viewport, isValidPath: () => false }));
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
name: '',
|
|
1137
|
-
ctrl: false,
|
|
1138
|
-
meta: false,
|
|
1139
|
-
shift: false,
|
|
1140
|
-
paste: false,
|
|
1141
|
-
sequence: textWithAnsi,
|
|
1142
|
-
}));
|
|
1143
|
-
expect(getBufferState(result).text).toBe('Hello World');
|
|
1144
|
-
});
|
|
1145
|
-
it('should strip control characters from input', () => {
|
|
1146
|
-
const { result } = renderHook(() => useTextBuffer({ viewport, isValidPath: () => false }));
|
|
1147
|
-
const textWithControlChars = 'H\x07e\x08l\x0Bl\x0Co'; // BELL, BACKSPACE, VT, FF
|
|
1148
|
-
act(() => result.current.handleInput({
|
|
1149
|
-
name: '',
|
|
1150
|
-
ctrl: false,
|
|
1151
|
-
meta: false,
|
|
1152
|
-
shift: false,
|
|
1153
|
-
paste: false,
|
|
1154
|
-
sequence: textWithControlChars,
|
|
1155
|
-
}));
|
|
1156
|
-
expect(getBufferState(result).text).toBe('Hello');
|
|
1157
|
-
});
|
|
1158
|
-
it('should strip mixed ANSI and control characters from input', () => {
|
|
1159
|
-
const { result } = renderHook(() => useTextBuffer({ viewport, isValidPath: () => false }));
|
|
1160
|
-
const textWithMixed = '\u001B[4mH\u001B[0mello';
|
|
1161
|
-
act(() => result.current.handleInput({
|
|
1162
|
-
name: '',
|
|
1163
|
-
ctrl: false,
|
|
1164
|
-
meta: false,
|
|
1165
|
-
shift: false,
|
|
1166
|
-
paste: false,
|
|
1167
|
-
sequence: textWithMixed,
|
|
1168
|
-
}));
|
|
1169
|
-
expect(getBufferState(result).text).toBe('Hello');
|
|
1164
|
+
act(() => result.current.handleInput(createInput(input)));
|
|
1165
|
+
expect(getBufferState(result).text).toBe(expected);
|
|
1170
1166
|
});
|
|
1171
1167
|
it('should not strip standard characters or newlines', () => {
|
|
1172
1168
|
const { result } = renderHook(() => useTextBuffer({ viewport, isValidPath: () => false }));
|
|
1173
1169
|
const validText = 'Hello World\nThis is a test.';
|
|
1174
|
-
act(() => result.current.handleInput(
|
|
1175
|
-
name: '',
|
|
1176
|
-
ctrl: false,
|
|
1177
|
-
meta: false,
|
|
1178
|
-
shift: false,
|
|
1179
|
-
paste: false,
|
|
1180
|
-
sequence: validText,
|
|
1181
|
-
}));
|
|
1170
|
+
act(() => result.current.handleInput(createInput(validText)));
|
|
1182
1171
|
expect(getBufferState(result).text).toBe(validText);
|
|
1183
1172
|
});
|
|
1184
|
-
it('should sanitize pasted text via handleInput', () => {
|
|
1185
|
-
const { result } = renderHook(() => useTextBuffer({ viewport, isValidPath: () => false }));
|
|
1186
|
-
const pastedText = '\u001B[4mPasted\u001B[4m Text';
|
|
1187
|
-
act(() => result.current.handleInput({
|
|
1188
|
-
name: '',
|
|
1189
|
-
ctrl: false,
|
|
1190
|
-
meta: false,
|
|
1191
|
-
shift: false,
|
|
1192
|
-
paste: false,
|
|
1193
|
-
sequence: pastedText,
|
|
1194
|
-
}));
|
|
1195
|
-
expect(getBufferState(result).text).toBe('Pasted Text');
|
|
1196
|
-
});
|
|
1197
1173
|
it('should sanitize large text (>5000 chars) and strip unsafe characters', () => {
|
|
1198
1174
|
const { result } = renderHook(() => useTextBuffer({ viewport, isValidPath: () => false }));
|
|
1199
1175
|
const unsafeChars = '\x07\x08\x0B\x0C';
|
|
@@ -1409,86 +1385,156 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
|
|
1409
1385
|
});
|
|
1410
1386
|
});
|
|
1411
1387
|
describe('offsetToLogicalPos', () => {
|
|
1412
|
-
it(
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1388
|
+
it.each([
|
|
1389
|
+
{ text: 'any text', offset: 0, expected: [0, 0], desc: 'offset 0' },
|
|
1390
|
+
{ text: 'hello', offset: 0, expected: [0, 0], desc: 'single line start' },
|
|
1391
|
+
{ text: 'hello', offset: 2, expected: [0, 2], desc: 'single line middle' },
|
|
1392
|
+
{ text: 'hello', offset: 5, expected: [0, 5], desc: 'single line end' },
|
|
1393
|
+
{ text: 'hello', offset: 10, expected: [0, 5], desc: 'beyond end clamps' },
|
|
1394
|
+
{
|
|
1395
|
+
text: 'a\n\nc',
|
|
1396
|
+
offset: 0,
|
|
1397
|
+
expected: [0, 0],
|
|
1398
|
+
desc: 'empty lines - first char',
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
text: 'a\n\nc',
|
|
1402
|
+
offset: 1,
|
|
1403
|
+
expected: [0, 1],
|
|
1404
|
+
desc: 'empty lines - end of first',
|
|
1405
|
+
},
|
|
1406
|
+
{
|
|
1407
|
+
text: 'a\n\nc',
|
|
1408
|
+
offset: 2,
|
|
1409
|
+
expected: [1, 0],
|
|
1410
|
+
desc: 'empty lines - empty line',
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
text: 'a\n\nc',
|
|
1414
|
+
offset: 3,
|
|
1415
|
+
expected: [2, 0],
|
|
1416
|
+
desc: 'empty lines - last line start',
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
text: 'a\n\nc',
|
|
1420
|
+
offset: 4,
|
|
1421
|
+
expected: [2, 1],
|
|
1422
|
+
desc: 'empty lines - last line end',
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
text: 'hello\n',
|
|
1426
|
+
offset: 5,
|
|
1427
|
+
expected: [0, 5],
|
|
1428
|
+
desc: 'newline end - before newline',
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
text: 'hello\n',
|
|
1432
|
+
offset: 6,
|
|
1433
|
+
expected: [1, 0],
|
|
1434
|
+
desc: 'newline end - after newline',
|
|
1435
|
+
},
|
|
1436
|
+
{
|
|
1437
|
+
text: 'hello\n',
|
|
1438
|
+
offset: 7,
|
|
1439
|
+
expected: [1, 0],
|
|
1440
|
+
desc: 'newline end - beyond',
|
|
1441
|
+
},
|
|
1442
|
+
{
|
|
1443
|
+
text: '\nhello',
|
|
1444
|
+
offset: 0,
|
|
1445
|
+
expected: [0, 0],
|
|
1446
|
+
desc: 'newline start - first line',
|
|
1447
|
+
},
|
|
1448
|
+
{
|
|
1449
|
+
text: '\nhello',
|
|
1450
|
+
offset: 1,
|
|
1451
|
+
expected: [1, 0],
|
|
1452
|
+
desc: 'newline start - second line',
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
text: '\nhello',
|
|
1456
|
+
offset: 3,
|
|
1457
|
+
expected: [1, 2],
|
|
1458
|
+
desc: 'newline start - middle of second',
|
|
1459
|
+
},
|
|
1460
|
+
{ text: '', offset: 0, expected: [0, 0], desc: 'empty string at 0' },
|
|
1461
|
+
{ text: '', offset: 5, expected: [0, 0], desc: 'empty string beyond' },
|
|
1462
|
+
{
|
|
1463
|
+
text: '你好\n世界',
|
|
1464
|
+
offset: 0,
|
|
1465
|
+
expected: [0, 0],
|
|
1466
|
+
desc: 'unicode - start',
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
text: '你好\n世界',
|
|
1470
|
+
offset: 1,
|
|
1471
|
+
expected: [0, 1],
|
|
1472
|
+
desc: 'unicode - after first char',
|
|
1473
|
+
},
|
|
1474
|
+
{
|
|
1475
|
+
text: '你好\n世界',
|
|
1476
|
+
offset: 2,
|
|
1477
|
+
expected: [0, 2],
|
|
1478
|
+
desc: 'unicode - end first line',
|
|
1479
|
+
},
|
|
1480
|
+
{
|
|
1481
|
+
text: '你好\n世界',
|
|
1482
|
+
offset: 3,
|
|
1483
|
+
expected: [1, 0],
|
|
1484
|
+
desc: 'unicode - second line start',
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
text: '你好\n世界',
|
|
1488
|
+
offset: 4,
|
|
1489
|
+
expected: [1, 1],
|
|
1490
|
+
desc: 'unicode - second line middle',
|
|
1491
|
+
},
|
|
1492
|
+
{
|
|
1493
|
+
text: '你好\n世界',
|
|
1494
|
+
offset: 5,
|
|
1495
|
+
expected: [1, 2],
|
|
1496
|
+
desc: 'unicode - second line end',
|
|
1497
|
+
},
|
|
1498
|
+
{
|
|
1499
|
+
text: '你好\n世界',
|
|
1500
|
+
offset: 6,
|
|
1501
|
+
expected: [1, 2],
|
|
1502
|
+
desc: 'unicode - beyond',
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
text: 'abc\ndef',
|
|
1506
|
+
offset: 3,
|
|
1507
|
+
expected: [0, 3],
|
|
1508
|
+
desc: 'at newline - end of line',
|
|
1509
|
+
},
|
|
1510
|
+
{
|
|
1511
|
+
text: 'abc\ndef',
|
|
1512
|
+
offset: 4,
|
|
1513
|
+
expected: [1, 0],
|
|
1514
|
+
desc: 'at newline - after newline',
|
|
1515
|
+
},
|
|
1516
|
+
{ text: '🐶🐱', offset: 0, expected: [0, 0], desc: 'emoji - start' },
|
|
1517
|
+
{ text: '🐶🐱', offset: 1, expected: [0, 1], desc: 'emoji - middle' },
|
|
1518
|
+
{ text: '🐶🐱', offset: 2, expected: [0, 2], desc: 'emoji - end' },
|
|
1519
|
+
])('should handle $desc', ({ text, offset, expected }) => {
|
|
1520
|
+
expect(offsetToLogicalPos(text, offset)).toEqual(expected);
|
|
1421
1521
|
});
|
|
1422
|
-
|
|
1522
|
+
describe('multi-line text', () => {
|
|
1423
1523
|
const text = 'hello\nworld\n123';
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
expect(offsetToLogicalPos(text, 15)).toEqual([2, 3]); // End of '123'
|
|
1439
|
-
expect(offsetToLogicalPos(text, 20)).toEqual([2, 3]); // Beyond end of text
|
|
1440
|
-
});
|
|
1441
|
-
it('should handle empty lines', () => {
|
|
1442
|
-
const text = 'a\n\nc'; // "a" (1) + \n (1) + "" (0) + \n (1) + "c" (1)
|
|
1443
|
-
expect(offsetToLogicalPos(text, 0)).toEqual([0, 0]); // 'a'
|
|
1444
|
-
expect(offsetToLogicalPos(text, 1)).toEqual([0, 1]); // End of 'a'
|
|
1445
|
-
expect(offsetToLogicalPos(text, 2)).toEqual([1, 0]); // Start of empty line
|
|
1446
|
-
expect(offsetToLogicalPos(text, 3)).toEqual([2, 0]); // Start of 'c'
|
|
1447
|
-
expect(offsetToLogicalPos(text, 4)).toEqual([2, 1]); // End of 'c'
|
|
1448
|
-
});
|
|
1449
|
-
it('should handle text ending with a newline', () => {
|
|
1450
|
-
const text = 'hello\n'; // "hello" (5) + \n (1)
|
|
1451
|
-
expect(offsetToLogicalPos(text, 5)).toEqual([0, 5]); // End of 'hello'
|
|
1452
|
-
expect(offsetToLogicalPos(text, 6)).toEqual([1, 0]); // Position on the new empty line after
|
|
1453
|
-
expect(offsetToLogicalPos(text, 7)).toEqual([1, 0]); // Still on the new empty line
|
|
1454
|
-
});
|
|
1455
|
-
it('should handle text starting with a newline', () => {
|
|
1456
|
-
const text = '\nhello'; // "" (0) + \n (1) + "hello" (5)
|
|
1457
|
-
expect(offsetToLogicalPos(text, 0)).toEqual([0, 0]); // Start of first empty line
|
|
1458
|
-
expect(offsetToLogicalPos(text, 1)).toEqual([1, 0]); // Start of 'hello'
|
|
1459
|
-
expect(offsetToLogicalPos(text, 3)).toEqual([1, 2]); // 'l' in 'hello'
|
|
1460
|
-
});
|
|
1461
|
-
it('should handle empty string input', () => {
|
|
1462
|
-
expect(offsetToLogicalPos('', 0)).toEqual([0, 0]);
|
|
1463
|
-
expect(offsetToLogicalPos('', 5)).toEqual([0, 0]);
|
|
1464
|
-
});
|
|
1465
|
-
it('should handle multi-byte unicode characters correctly', () => {
|
|
1466
|
-
const text = '你好\n世界'; // "你好" (2 chars) + \n (1) + "世界" (2 chars)
|
|
1467
|
-
// Total "code points" for offset calculation: 2 + 1 + 2 = 5
|
|
1468
|
-
expect(offsetToLogicalPos(text, 0)).toEqual([0, 0]); // Start of '你好'
|
|
1469
|
-
expect(offsetToLogicalPos(text, 1)).toEqual([0, 1]); // After '你', before '好'
|
|
1470
|
-
expect(offsetToLogicalPos(text, 2)).toEqual([0, 2]); // End of '你好'
|
|
1471
|
-
expect(offsetToLogicalPos(text, 3)).toEqual([1, 0]); // Start of '世界'
|
|
1472
|
-
expect(offsetToLogicalPos(text, 4)).toEqual([1, 1]); // After '世', before '界'
|
|
1473
|
-
expect(offsetToLogicalPos(text, 5)).toEqual([1, 2]); // End of '世界'
|
|
1474
|
-
expect(offsetToLogicalPos(text, 6)).toEqual([1, 2]); // Beyond end
|
|
1475
|
-
});
|
|
1476
|
-
it('should handle offset exactly at newline character', () => {
|
|
1477
|
-
const text = 'abc\ndef';
|
|
1478
|
-
// a b c \n d e f
|
|
1479
|
-
// 0 1 2 3 4 5 6
|
|
1480
|
-
expect(offsetToLogicalPos(text, 3)).toEqual([0, 3]); // End of 'abc'
|
|
1481
|
-
// The next character is the newline, so an offset of 4 means the start of the next line.
|
|
1482
|
-
expect(offsetToLogicalPos(text, 4)).toEqual([1, 0]); // Start of 'def'
|
|
1483
|
-
});
|
|
1484
|
-
it('should handle offset in the middle of a multi-byte character (should place at start of that char)', () => {
|
|
1485
|
-
// This scenario is tricky as "offset" is usually character-based.
|
|
1486
|
-
// Assuming cpLen and related logic handles this by treating multi-byte as one unit.
|
|
1487
|
-
// The current implementation of offsetToLogicalPos uses cpLen, so it should be code-point aware.
|
|
1488
|
-
const text = '🐶🐱'; // 2 code points
|
|
1489
|
-
expect(offsetToLogicalPos(text, 0)).toEqual([0, 0]);
|
|
1490
|
-
expect(offsetToLogicalPos(text, 1)).toEqual([0, 1]); // After 🐶
|
|
1491
|
-
expect(offsetToLogicalPos(text, 2)).toEqual([0, 2]); // After 🐱
|
|
1524
|
+
it.each([
|
|
1525
|
+
{ offset: 0, expected: [0, 0], desc: 'start of first line' },
|
|
1526
|
+
{ offset: 3, expected: [0, 3], desc: 'middle of first line' },
|
|
1527
|
+
{ offset: 5, expected: [0, 5], desc: 'end of first line' },
|
|
1528
|
+
{ offset: 6, expected: [1, 0], desc: 'start of second line' },
|
|
1529
|
+
{ offset: 8, expected: [1, 2], desc: 'middle of second line' },
|
|
1530
|
+
{ offset: 11, expected: [1, 5], desc: 'end of second line' },
|
|
1531
|
+
{ offset: 12, expected: [2, 0], desc: 'start of third line' },
|
|
1532
|
+
{ offset: 13, expected: [2, 1], desc: 'middle of third line' },
|
|
1533
|
+
{ offset: 15, expected: [2, 3], desc: 'end of third line' },
|
|
1534
|
+
{ offset: 20, expected: [2, 3], desc: 'beyond end' },
|
|
1535
|
+
])('should return $expected for $desc (offset $offset)', ({ offset, expected }) => {
|
|
1536
|
+
expect(offsetToLogicalPos(text, offset)).toEqual(expected);
|
|
1537
|
+
});
|
|
1492
1538
|
});
|
|
1493
1539
|
});
|
|
1494
1540
|
describe('logicalPosToOffset', () => {
|
|
@@ -1538,7 +1584,6 @@ describe('logicalPosToOffset', () => {
|
|
|
1538
1584
|
expect(logicalPosToOffset(lines, 5, 10)).toBe(5); // Clamps to end of last line
|
|
1539
1585
|
});
|
|
1540
1586
|
});
|
|
1541
|
-
// Helper to create state for reducer tests
|
|
1542
1587
|
const createTestState = (lines, cursorRow, cursorCol, viewportWidth = 80) => {
|
|
1543
1588
|
const text = lines.join('\n');
|
|
1544
1589
|
let state = textBufferReducer(initialState, {
|