From 06c398a0150d2d1e06f0cb1d2145f4cb6882ae6c Mon Sep 17 00:00:00 2001 From: Alexander Farber Date: Tue, 9 Dec 2025 16:37:32 +0100 Subject: [PATCH 1/2] Remove vertical borders from input prompt for easier copy/paste --- .../src/ui/components/InputPrompt.test.tsx | 11 +++--- .../cli/src/ui/components/InputPrompt.tsx | 17 +++++---- .../__snapshots__/InputPrompt.test.tsx.snap | 36 +++++++++---------- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/packages/cli/src/ui/components/InputPrompt.test.tsx b/packages/cli/src/ui/components/InputPrompt.test.tsx index 5449db5e..7c523620 100644 --- a/packages/cli/src/ui/components/InputPrompt.test.tsx +++ b/packages/cli/src/ui/components/InputPrompt.test.tsx @@ -1252,7 +1252,8 @@ describe('InputPrompt', () => { unmount(); }); - it('should display cursor at the end of the line as an inverted space', async () => { + // Skip: trailing cursor (inverted space at EOL) is trimmed without right border + it.skip('should display cursor at the end of the line as an inverted space', async () => { mockBuffer.text = 'hello'; mockBuffer.lines = ['hello']; mockBuffer.viewportVisualLines = ['hello']; @@ -1302,7 +1303,8 @@ describe('InputPrompt', () => { unmount(); }); - it('should display cursor at the end of a line with unicode characters', async () => { + // Skip: trailing cursor (inverted space at EOL) is trimmed without right border + it.skip('should display cursor at the end of a line with unicode characters', async () => { const text = 'hello 👍'; mockBuffer.text = text; mockBuffer.lines = [text]; @@ -1394,7 +1396,8 @@ describe('InputPrompt', () => { unmount(); }); - it('should display cursor at the end of a line in a multiline block', async () => { + // Skip: trailing cursor (inverted space at EOL) is trimmed without right border + it.skip('should display cursor at the end of a line in a multiline block', async () => { const text = 'first line\nsecond line'; mockBuffer.text = text; mockBuffer.lines = text.split('\n'); @@ -1466,7 +1469,7 @@ describe('InputPrompt', () => { // Check that all lines, including the empty one, are rendered. // This implicitly tests that the Box wrapper provides height for the empty line. expect(frame).toContain('hello'); - expect(frame).toContain(`world${chalk.inverse(' ')}`); + expect(frame).toContain('world'); const outputLines = frame!.split('\n'); // The number of lines should be 2 for the border plus 3 for the content. diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index 8af77059..4eaf535f 100644 --- a/packages/cli/src/ui/components/InputPrompt.tsx +++ b/packages/cli/src/ui/components/InputPrompt.tsx @@ -707,15 +707,20 @@ export const InputPrompt: React.FC = ({ statusText = t('Accepting edits'); } + const borderColor = + isShellFocused && !isEmbeddedShellFocused + ? (statusColor ?? theme.border.focused) + : theme.border.default; + return ( <> command search (Ctrl+R when not in shell) > expands and c `; exports[`InputPrompt > command search (Ctrl+R when not in shell) > renders match window and expanded view (snapshots) > command-search-collapsed-match 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ (r:) commit │ -╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ +"──────────────────────────────────────────────────────────────────────────────────────────────────── + (r:) commit +──────────────────────────────────────────────────────────────────────────────────────────────────── git commit -m "feat: add search" in src/app" `; exports[`InputPrompt > command search (Ctrl+R when not in shell) > renders match window and expanded view (snapshots) > command-search-expanded-match 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ (r:) commit │ -╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ +"──────────────────────────────────────────────────────────────────────────────────────────────────── + (r:) commit +──────────────────────────────────────────────────────────────────────────────────────────────────── git commit -m "feat: add search" in src/app" `; exports[`InputPrompt > snapshots > should not show inverted cursor when shell is focused 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ > Type your message or @path/to/file │ -╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" +"──────────────────────────────────────────────────────────────────────────────────────────────────── + > Type your message or @path/to/file +────────────────────────────────────────────────────────────────────────────────────────────────────" `; exports[`InputPrompt > snapshots > should render correctly in shell mode 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ ! Type your message or @path/to/file │ -╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" +"──────────────────────────────────────────────────────────────────────────────────────────────────── + ! Type your message or @path/to/file +────────────────────────────────────────────────────────────────────────────────────────────────────" `; exports[`InputPrompt > snapshots > should render correctly in yolo mode 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ * Type your message or @path/to/file │ -╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" +"──────────────────────────────────────────────────────────────────────────────────────────────────── + * Type your message or @path/to/file +────────────────────────────────────────────────────────────────────────────────────────────────────" `; exports[`InputPrompt > snapshots > should render correctly when accepting edits 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ > Type your message or @path/to/file │ -╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" +"──────────────────────────────────────────────────────────────────────────────────────────────────── + > Type your message or @path/to/file +────────────────────────────────────────────────────────────────────────────────────────────────────" `; From 5b74422be63d6d5ced68b9b8eea3ac22b6e35b49 Mon Sep 17 00:00:00 2001 From: Alexander Farber Date: Tue, 9 Dec 2025 16:46:18 +0100 Subject: [PATCH 2/2] Do not skip the tests --- packages/cli/src/ui/components/InputPrompt.test.tsx | 13 +++++-------- packages/cli/src/ui/components/InputPrompt.tsx | 3 ++- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/ui/components/InputPrompt.test.tsx b/packages/cli/src/ui/components/InputPrompt.test.tsx index 7c523620..cf8b9685 100644 --- a/packages/cli/src/ui/components/InputPrompt.test.tsx +++ b/packages/cli/src/ui/components/InputPrompt.test.tsx @@ -1252,8 +1252,7 @@ describe('InputPrompt', () => { unmount(); }); - // Skip: trailing cursor (inverted space at EOL) is trimmed without right border - it.skip('should display cursor at the end of the line as an inverted space', async () => { + it('should display cursor at the end of the line as an inverted space', async () => { mockBuffer.text = 'hello'; mockBuffer.lines = ['hello']; mockBuffer.viewportVisualLines = ['hello']; @@ -1303,13 +1302,12 @@ describe('InputPrompt', () => { unmount(); }); - // Skip: trailing cursor (inverted space at EOL) is trimmed without right border - it.skip('should display cursor at the end of a line with unicode characters', async () => { + it('should display cursor at the end of a line with unicode characters', async () => { const text = 'hello 👍'; mockBuffer.text = text; mockBuffer.lines = [text]; mockBuffer.viewportVisualLines = [text]; - mockBuffer.visualCursor = [0, 8]; // cursor after '👍' (length is 6 + 2 for emoji) + mockBuffer.visualCursor = [0, 7]; // cursor after '👍' (emoji is 1 code point, so total is 7) const { stdout, unmount } = renderWithProviders( , @@ -1396,8 +1394,7 @@ describe('InputPrompt', () => { unmount(); }); - // Skip: trailing cursor (inverted space at EOL) is trimmed without right border - it.skip('should display cursor at the end of a line in a multiline block', async () => { + it('should display cursor at the end of a line in a multiline block', async () => { const text = 'first line\nsecond line'; mockBuffer.text = text; mockBuffer.lines = text.split('\n'); @@ -1469,7 +1466,7 @@ describe('InputPrompt', () => { // Check that all lines, including the empty one, are rendered. // This implicitly tests that the Box wrapper provides height for the empty line. expect(frame).toContain('hello'); - expect(frame).toContain('world'); + expect(frame).toContain(`world${chalk.inverse(' ')}`); const outputLines = frame!.split('\n'); // The number of lines should be 2 for the border plus 3 for the content. diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index 4eaf535f..7d174250 100644 --- a/packages/cli/src/ui/components/InputPrompt.tsx +++ b/packages/cli/src/ui/components/InputPrompt.tsx @@ -834,9 +834,10 @@ export const InputPrompt: React.FC = ({ isOnCursorLine && cursorVisualColAbsolute === cpLen(lineText) ) { + // Add zero-width space after cursor to prevent Ink from trimming trailing whitespace renderedLine.push( - {showCursor ? chalk.inverse(' ') : ' '} + {showCursor ? chalk.inverse(' ') + '\u200B' : ' \u200B'} , ); }