diff options
Diffstat (limited to 'packages/cli/src/ui/components/shared')
| -rw-r--r-- | packages/cli/src/ui/components/shared/text-buffer.test.ts | 130 | ||||
| -rw-r--r-- | packages/cli/src/ui/components/shared/text-buffer.ts | 75 |
2 files changed, 158 insertions, 47 deletions
diff --git a/packages/cli/src/ui/components/shared/text-buffer.test.ts b/packages/cli/src/ui/components/shared/text-buffer.test.ts index 218ed1c3..5ea52ba4 100644 --- a/packages/cli/src/ui/components/shared/text-buffer.test.ts +++ b/packages/cli/src/ui/components/shared/text-buffer.test.ts @@ -574,8 +574,24 @@ describe('useTextBuffer', () => { const { result } = renderHook(() => useTextBuffer({ viewport, isValidPath: () => false }), ); - act(() => result.current.handleInput('h', {})); - act(() => result.current.handleInput('i', {})); + act(() => + result.current.handleInput({ + name: 'h', + ctrl: false, + meta: false, + shift: false, + sequence: 'h', + }), + ); + act(() => + result.current.handleInput({ + name: 'i', + ctrl: false, + meta: false, + shift: false, + sequence: 'i', + }), + ); expect(getBufferState(result).text).toBe('hi'); }); @@ -583,7 +599,15 @@ describe('useTextBuffer', () => { const { result } = renderHook(() => useTextBuffer({ viewport, isValidPath: () => false }), ); - act(() => result.current.handleInput(undefined, { return: true })); + act(() => + result.current.handleInput({ + name: 'return', + ctrl: false, + meta: false, + shift: false, + sequence: '\r', + }), + ); expect(getBufferState(result).lines).toEqual(['', '']); }); @@ -596,7 +620,15 @@ describe('useTextBuffer', () => { }), ); act(() => result.current.move('end')); - act(() => result.current.handleInput(undefined, { backspace: true })); + act(() => + result.current.handleInput({ + name: 'backspace', + ctrl: false, + meta: false, + shift: false, + sequence: '\x7f', + }), + ); expect(getBufferState(result).text).toBe(''); }); @@ -671,9 +703,25 @@ describe('useTextBuffer', () => { }), ); act(() => result.current.move('end')); // cursor [0,2] - act(() => result.current.handleInput(undefined, { leftArrow: true })); // cursor [0,1] + act(() => + result.current.handleInput({ + name: 'left', + ctrl: false, + meta: false, + shift: false, + sequence: '\x1b[D', + }), + ); // cursor [0,1] expect(getBufferState(result).cursor).toEqual([0, 1]); - act(() => result.current.handleInput(undefined, { rightArrow: true })); // cursor [0,2] + act(() => + result.current.handleInput({ + name: 'right', + ctrl: false, + meta: false, + shift: false, + sequence: '\x1b[C', + }), + ); // cursor [0,2] expect(getBufferState(result).cursor).toEqual([0, 2]); }); @@ -683,7 +731,15 @@ describe('useTextBuffer', () => { ); const textWithAnsi = '\x1B[31mHello\x1B[0m \x1B[32mWorld\x1B[0m'; // Simulate pasting by calling handleInput with a string longer than 1 char - act(() => result.current.handleInput(textWithAnsi, {})); + act(() => + result.current.handleInput({ + name: undefined, + ctrl: false, + meta: false, + shift: false, + sequence: textWithAnsi, + }), + ); expect(getBufferState(result).text).toBe('Hello World'); }); @@ -691,7 +747,15 @@ describe('useTextBuffer', () => { const { result } = renderHook(() => useTextBuffer({ viewport, isValidPath: () => false }), ); - act(() => result.current.handleInput('\r', {})); // Simulates Shift+Enter in VSCode terminal + act(() => + result.current.handleInput({ + name: 'return', + ctrl: false, + meta: false, + shift: true, + sequence: '\r', + }), + ); // Simulates Shift+Enter in VSCode terminal expect(getBufferState(result).lines).toEqual(['', '']); }); @@ -880,7 +944,15 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots useTextBuffer({ viewport, isValidPath: () => false }), ); const textWithAnsi = '\x1B[31mHello\x1B[0m'; - act(() => result.current.handleInput(textWithAnsi, {})); + act(() => + result.current.handleInput({ + name: undefined, + ctrl: false, + meta: false, + shift: false, + sequence: textWithAnsi, + }), + ); expect(getBufferState(result).text).toBe('Hello'); }); @@ -889,7 +961,15 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots useTextBuffer({ viewport, isValidPath: () => false }), ); const textWithControlChars = 'H\x07e\x08l\x0Bl\x0Co'; // BELL, BACKSPACE, VT, FF - act(() => result.current.handleInput(textWithControlChars, {})); + act(() => + result.current.handleInput({ + name: undefined, + ctrl: false, + meta: false, + shift: false, + sequence: textWithControlChars, + }), + ); expect(getBufferState(result).text).toBe('Hello'); }); @@ -898,7 +978,15 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots useTextBuffer({ viewport, isValidPath: () => false }), ); const textWithMixed = '\u001B[4mH\u001B[0mello'; - act(() => result.current.handleInput(textWithMixed, {})); + act(() => + result.current.handleInput({ + name: undefined, + ctrl: false, + meta: false, + shift: false, + sequence: textWithMixed, + }), + ); expect(getBufferState(result).text).toBe('Hello'); }); @@ -907,7 +995,15 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots useTextBuffer({ viewport, isValidPath: () => false }), ); const validText = 'Hello World\nThis is a test.'; - act(() => result.current.handleInput(validText, {})); + act(() => + result.current.handleInput({ + name: undefined, + ctrl: false, + meta: false, + shift: false, + sequence: validText, + }), + ); expect(getBufferState(result).text).toBe(validText); }); @@ -916,7 +1012,15 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots useTextBuffer({ viewport, isValidPath: () => false }), ); const pastedText = '\u001B[4mPasted\u001B[4m Text'; - act(() => result.current.handleInput(pastedText, {})); + act(() => + result.current.handleInput({ + name: undefined, + ctrl: false, + meta: false, + shift: false, + sequence: pastedText, + }), + ); expect(getBufferState(result).text).toBe('Pasted Text'); }); }); diff --git a/packages/cli/src/ui/components/shared/text-buffer.ts b/packages/cli/src/ui/components/shared/text-buffer.ts index 82f10fa1..15fc6d3c 100644 --- a/packages/cli/src/ui/components/shared/text-buffer.ts +++ b/packages/cli/src/ui/components/shared/text-buffer.ts @@ -1220,9 +1220,16 @@ export function useTextBuffer({ ); const handleInput = useCallback( - (input: string | undefined, key: Record<string, boolean>): boolean => { + (key: { + name: string; + ctrl: boolean; + meta: boolean; + shift: boolean; + paste: boolean; + sequence: string; + }): boolean => { + const { sequence: input } = key; dbg('handleInput', { - input, key, cursor: [cursorRow, cursorCol], visualCursor, @@ -1231,50 +1238,46 @@ export function useTextBuffer({ const beforeLogicalCursor = [cursorRow, cursorCol]; const beforeVisualCursor = [...visualCursor]; - if (key['escape']) return false; + if (key.name === 'escape') return false; if ( - key['return'] || + key.name === 'return' || input === '\r' || input === '\n' || input === '\\\r' // VSCode terminal represents shift + enter this way ) newline(); - else if (key['leftArrow'] && !key['meta'] && !key['ctrl'] && !key['alt']) - move('left'); - else if (key['ctrl'] && input === 'b') move('left'); - else if (key['rightArrow'] && !key['meta'] && !key['ctrl'] && !key['alt']) - move('right'); - else if (key['ctrl'] && input === 'f') move('right'); - else if (key['upArrow']) move('up'); - else if (key['downArrow']) move('down'); - else if ((key['ctrl'] || key['alt']) && key['leftArrow']) - move('wordLeft'); - else if (key['meta'] && input === 'b') move('wordLeft'); - else if ((key['ctrl'] || key['alt']) && key['rightArrow']) + else if (key.name === 'left' && !key.meta && !key.ctrl) move('left'); + else if (key.ctrl && key.name === 'b') move('left'); + else if (key.name === 'right' && !key.meta && !key.ctrl) move('right'); + else if (key.ctrl && key.name === 'f') move('right'); + else if (key.name === 'up') move('up'); + else if (key.name === 'down') move('down'); + else if ((key.ctrl || key.meta) && key.name === 'left') move('wordLeft'); + else if (key.meta && key.name === 'b') move('wordLeft'); + else if ((key.ctrl || key.meta) && key.name === 'right') move('wordRight'); - else if (key['meta'] && input === 'f') move('wordRight'); - else if (key['home']) move('home'); - else if (key['ctrl'] && input === 'a') move('home'); - else if (key['end']) move('end'); - else if (key['ctrl'] && input === 'e') move('end'); - else if (key['ctrl'] && input === 'w') deleteWordLeft(); + else if (key.meta && key.name === 'f') move('wordRight'); + else if (key.name === 'home') move('home'); + else if (key.ctrl && key.name === 'a') move('home'); + else if (key.name === 'end') move('end'); + else if (key.ctrl && key.name === 'e') move('end'); + else if (key.ctrl && key.name === 'w') deleteWordLeft(); else if ( - (key['meta'] || key['ctrl'] || key['alt']) && - (key['backspace'] || input === '\x7f') + (key.meta || key.ctrl) && + (key.name === 'backspace' || input === '\x7f') ) deleteWordLeft(); - else if ((key['meta'] || key['ctrl'] || key['alt']) && key['delete']) + else if ((key.meta || key.ctrl) && key.name === 'delete') deleteWordRight(); else if ( - key['backspace'] || + key.name === 'backspace' || input === '\x7f' || - (key['ctrl'] && input === 'h') || - (key['delete'] && !key['shift']) + (key.ctrl && key.name === 'h') ) backspace(); - else if (key['delete'] || (key['ctrl'] && input === 'd')) del(); - else if (input && !key['ctrl'] && !key['meta']) { + else if (key.name === 'delete' || (key.ctrl && key.name === 'd')) del(); + else if (input && !key.ctrl && !key.meta) { insert(input); } @@ -1483,10 +1486,14 @@ export interface TextBuffer { /** * High level "handleInput" – receives what Ink gives us. */ - handleInput: ( - input: string | undefined, - key: Record<string, boolean>, - ) => boolean; + handleInput: (key: { + name: string; + ctrl: boolean; + meta: boolean; + shift: boolean; + paste: boolean; + sequence: string; + }) => boolean; /** * Opens the current buffer contents in the user's preferred terminal text * editor ($VISUAL or $EDITOR, falling back to "vi"). The method blocks |
