diff options
| author | Jacob Richman <[email protected]> | 2025-05-16 11:58:37 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-05-16 11:58:37 -0700 |
| commit | c692a0c583f343225c4435ad14fb1b5a8d0e1b01 (patch) | |
| tree | 33ac89c1e67939f55f6da59bc7d8776db1b17448 /packages/cli/src/ui/components/shared/multiline-editor.tsx | |
| parent | 968e09f0b50d17f7c591baa977666b991a1e59b7 (diff) | |
Support auto wrapping of in the multiline editor. (#383)
Diffstat (limited to 'packages/cli/src/ui/components/shared/multiline-editor.tsx')
| -rw-r--r-- | packages/cli/src/ui/components/shared/multiline-editor.tsx | 76 |
1 files changed, 51 insertions, 25 deletions
diff --git a/packages/cli/src/ui/components/shared/multiline-editor.tsx b/packages/cli/src/ui/components/shared/multiline-editor.tsx index e1e21fff..a89acfd1 100644 --- a/packages/cli/src/ui/components/shared/multiline-editor.tsx +++ b/packages/cli/src/ui/components/shared/multiline-editor.tsx @@ -4,12 +4,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { useTextBuffer } from './text-buffer.js'; +import { useTextBuffer, cpSlice, cpLen } from './text-buffer.js'; import chalk from 'chalk'; import { Box, Text, useInput, useStdin, Key } from 'ink'; import React from 'react'; import { useTerminalSize } from '../../hooks/useTerminalSize.js'; import { Colors } from '../../colors.js'; +import stringWidth from 'string-width'; export interface MultilineTextEditorProps { // Initial contents. @@ -184,14 +185,21 @@ export const MultilineTextEditor = ({ } if (key.upArrow) { - if (buffer.cursor[0] === 0 && navigateUp) { + if ( + buffer.visualCursor[0] === 0 && + buffer.visualScrollRow === 0 && + navigateUp + ) { navigateUp(); return; } } if (key.downArrow) { - if (buffer.cursor[0] === buffer.lines.length - 1 && navigateDown) { + if ( + buffer.visualCursor[0] === buffer.allVisualLines.length - 1 && + navigateDown + ) { navigateDown(); return; } @@ -202,39 +210,57 @@ export const MultilineTextEditor = ({ { isActive: focus }, ); - const visibleLines = buffer.visibleLines; - const [cursorRow, cursorCol] = buffer.cursor; - const [scrollRow, scrollCol] = buffer.scroll; + const linesToRender = buffer.viewportVisualLines; // This is the subset of visual lines for display + const [cursorVisualRowAbsolute, cursorVisualColAbsolute] = + buffer.visualCursor; // This is relative to *all* visual lines + const scrollVisualRow = buffer.visualScrollRow; + // scrollHorizontalCol removed as it's always 0 due to word wrap return ( <Box flexDirection="column"> {buffer.text.length === 0 && placeholder ? ( <Text color={Colors.SubtleComment}>{placeholder}</Text> ) : ( - visibleLines.map((lineText, idx) => { - const absoluteRow = scrollRow + idx; - let display = lineText.slice(scrollCol, scrollCol + effectiveWidth); - if (display.length < effectiveWidth) { - display = display.padEnd(effectiveWidth, ' '); + linesToRender.map((lineText, visualIdxInRenderedSet) => { + // cursorVisualRow is the cursor's row index within the currently *rendered* set of visual lines + const cursorVisualRow = cursorVisualRowAbsolute - scrollVisualRow; + + let display = cpSlice( + lineText, + 0, // Start from 0 as horizontal scroll is disabled + effectiveWidth, // This is still code point based for slicing + ); + // Pad based on visual width + const currentVisualWidth = stringWidth(display); + if (currentVisualWidth < effectiveWidth) { + display = display + ' '.repeat(effectiveWidth - currentVisualWidth); } - if (absoluteRow === cursorRow) { - const relativeCol = cursorCol - scrollCol; - const highlightCol = relativeCol; + if (visualIdxInRenderedSet === cursorVisualRow) { + const relativeVisualColForHighlight = cursorVisualColAbsolute; // Directly use absolute as horizontal scroll is 0 - if (highlightCol >= 0 && highlightCol < effectiveWidth) { - const charToHighlight = display[highlightCol] || ' '; - const highlighted = chalk.inverse(charToHighlight); - display = - display.slice(0, highlightCol) + - highlighted + - display.slice(highlightCol + 1); - } else if (relativeCol === effectiveWidth) { - display = - display.slice(0, effectiveWidth - 1) + chalk.inverse(' '); + if (relativeVisualColForHighlight >= 0) { + if (relativeVisualColForHighlight < cpLen(display)) { + const charToHighlight = + cpSlice( + display, + relativeVisualColForHighlight, + relativeVisualColForHighlight + 1, + ) || ' '; + const highlighted = chalk.inverse(charToHighlight); + display = + cpSlice(display, 0, relativeVisualColForHighlight) + + highlighted + + cpSlice(display, relativeVisualColForHighlight + 1); + } else if ( + relativeVisualColForHighlight === cpLen(display) && + cpLen(display) === effectiveWidth + ) { + display = display + chalk.inverse(' '); + } } } - return <Text key={idx}>{display}</Text>; + return <Text key={visualIdxInRenderedSet}>{display}</Text>; }) )} </Box> |
