feat: remove prompt completion feature (#1076)

This commit is contained in:
tanzhenxin
2025-11-20 14:36:51 +08:00
committed by GitHub
parent e1e7a0d606
commit 07069f00d1
11 changed files with 99 additions and 665 deletions

View File

@@ -164,11 +164,6 @@ describe('InputPrompt', () => {
setActiveSuggestionIndex: vi.fn(),
setShowSuggestions: vi.fn(),
handleAutocomplete: vi.fn(),
promptCompletion: {
text: '',
accept: vi.fn(),
clear: vi.fn(),
},
};
mockedUseCommandCompletion.mockReturnValue(mockCommandCompletion);

View File

@@ -12,9 +12,8 @@ import { theme } from '../semantic-colors.js';
import { useInputHistory } from '../hooks/useInputHistory.js';
import type { TextBuffer } from './shared/text-buffer.js';
import { logicalPosToOffset } from './shared/text-buffer.js';
import { cpSlice, cpLen, toCodePoints } from '../utils/textUtils.js';
import { cpSlice, cpLen } from '../utils/textUtils.js';
import chalk from 'chalk';
import stringWidth from 'string-width';
import { useShellHistory } from '../hooks/useShellHistory.js';
import { useReverseSearchCompletion } from '../hooks/useReverseSearchCompletion.js';
import { useCommandCompletion } from '../hooks/useCommandCompletion.js';
@@ -91,7 +90,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
commandContext,
placeholder = ' Type your message or @path/to/file',
focus = true,
inputWidth,
suggestionsWidth,
shellModeActive,
setShellModeActive,
@@ -526,16 +524,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
}
}
// Handle Tab key for ghost text acceptance
if (
key.name === 'tab' &&
!completion.showSuggestions &&
completion.promptCompletion.text
) {
completion.promptCompletion.accept();
return;
}
if (!shellModeActive) {
if (keyMatchers[Command.REVERSE_SEARCH](key)) {
setCommandSearchActive(true);
@@ -657,18 +645,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
// Fall back to the text buffer's default input handling for all other keys
buffer.handleInput(key);
// Clear ghost text when user types regular characters (not navigation/control keys)
if (
completion.promptCompletion.text &&
key.sequence &&
key.sequence.length === 1 &&
!key.ctrl &&
!key.meta
) {
completion.promptCompletion.clear();
setExpandedSuggestionIndex(-1);
}
},
[
focus,
@@ -703,118 +679,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
buffer.visualCursor;
const scrollVisualRow = buffer.visualScrollRow;
const getGhostTextLines = useCallback(() => {
if (
!completion.promptCompletion.text ||
!buffer.text ||
!completion.promptCompletion.text.startsWith(buffer.text)
) {
return { inlineGhost: '', additionalLines: [] };
}
const ghostSuffix = completion.promptCompletion.text.slice(
buffer.text.length,
);
if (!ghostSuffix) {
return { inlineGhost: '', additionalLines: [] };
}
const currentLogicalLine = buffer.lines[buffer.cursor[0]] || '';
const cursorCol = buffer.cursor[1];
const textBeforeCursor = cpSlice(currentLogicalLine, 0, cursorCol);
const usedWidth = stringWidth(textBeforeCursor);
const remainingWidth = Math.max(0, inputWidth - usedWidth);
const ghostTextLinesRaw = ghostSuffix.split('\n');
const firstLineRaw = ghostTextLinesRaw.shift() || '';
let inlineGhost = '';
let remainingFirstLine = '';
if (stringWidth(firstLineRaw) <= remainingWidth) {
inlineGhost = firstLineRaw;
} else {
const words = firstLineRaw.split(' ');
let currentLine = '';
let wordIdx = 0;
for (const word of words) {
const prospectiveLine = currentLine ? `${currentLine} ${word}` : word;
if (stringWidth(prospectiveLine) > remainingWidth) {
break;
}
currentLine = prospectiveLine;
wordIdx++;
}
inlineGhost = currentLine;
if (words.length > wordIdx) {
remainingFirstLine = words.slice(wordIdx).join(' ');
}
}
const linesToWrap = [];
if (remainingFirstLine) {
linesToWrap.push(remainingFirstLine);
}
linesToWrap.push(...ghostTextLinesRaw);
const remainingGhostText = linesToWrap.join('\n');
const additionalLines: string[] = [];
if (remainingGhostText) {
const textLines = remainingGhostText.split('\n');
for (const textLine of textLines) {
const words = textLine.split(' ');
let currentLine = '';
for (const word of words) {
const prospectiveLine = currentLine ? `${currentLine} ${word}` : word;
const prospectiveWidth = stringWidth(prospectiveLine);
if (prospectiveWidth > inputWidth) {
if (currentLine) {
additionalLines.push(currentLine);
}
let wordToProcess = word;
while (stringWidth(wordToProcess) > inputWidth) {
let part = '';
const wordCP = toCodePoints(wordToProcess);
let partWidth = 0;
let splitIndex = 0;
for (let i = 0; i < wordCP.length; i++) {
const char = wordCP[i];
const charWidth = stringWidth(char);
if (partWidth + charWidth > inputWidth) {
break;
}
part += char;
partWidth += charWidth;
splitIndex = i + 1;
}
additionalLines.push(part);
wordToProcess = cpSlice(wordToProcess, splitIndex);
}
currentLine = wordToProcess;
} else {
currentLine = prospectiveLine;
}
}
if (currentLine) {
additionalLines.push(currentLine);
}
}
}
return { inlineGhost, additionalLines };
}, [
completion.promptCompletion.text,
buffer.text,
buffer.lines,
buffer.cursor,
inputWidth,
]);
const { inlineGhost, additionalLines } = getGhostTextLines();
const getActiveCompletion = () => {
if (commandSearchActive) return commandSearchCompletion;
if (reverseSearchActive) return reverseSearchCompletion;
@@ -887,134 +751,96 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
<Text color={theme.text.secondary}>{placeholder}</Text>
)
) : (
linesToRender
.map((lineText, visualIdxInRenderedSet) => {
const absoluteVisualIdx =
scrollVisualRow + visualIdxInRenderedSet;
const mapEntry = buffer.visualToLogicalMap[absoluteVisualIdx];
const cursorVisualRow =
cursorVisualRowAbsolute - scrollVisualRow;
const isOnCursorLine =
focus && visualIdxInRenderedSet === cursorVisualRow;
linesToRender.map((lineText, visualIdxInRenderedSet) => {
const absoluteVisualIdx =
scrollVisualRow + visualIdxInRenderedSet;
const mapEntry = buffer.visualToLogicalMap[absoluteVisualIdx];
const cursorVisualRow = cursorVisualRowAbsolute - scrollVisualRow;
const isOnCursorLine =
focus && visualIdxInRenderedSet === cursorVisualRow;
const renderedLine: React.ReactNode[] = [];
const renderedLine: React.ReactNode[] = [];
const [logicalLineIdx, logicalStartCol] = mapEntry;
const logicalLine = buffer.lines[logicalLineIdx] || '';
const tokens = parseInputForHighlighting(
logicalLine,
logicalLineIdx,
);
const [logicalLineIdx, logicalStartCol] = mapEntry;
const logicalLine = buffer.lines[logicalLineIdx] || '';
const tokens = parseInputForHighlighting(
logicalLine,
logicalLineIdx,
);
const visualStart = logicalStartCol;
const visualEnd = logicalStartCol + cpLen(lineText);
const segments = buildSegmentsForVisualSlice(
tokens,
visualStart,
visualEnd,
);
const visualStart = logicalStartCol;
const visualEnd = logicalStartCol + cpLen(lineText);
const segments = buildSegmentsForVisualSlice(
tokens,
visualStart,
visualEnd,
);
let charCount = 0;
segments.forEach((seg, segIdx) => {
const segLen = cpLen(seg.text);
let display = seg.text;
let charCount = 0;
segments.forEach((seg, segIdx) => {
const segLen = cpLen(seg.text);
let display = seg.text;
if (isOnCursorLine) {
const relativeVisualColForHighlight =
cursorVisualColAbsolute;
const segStart = charCount;
const segEnd = segStart + segLen;
if (
relativeVisualColForHighlight >= segStart &&
relativeVisualColForHighlight < segEnd
) {
const charToHighlight = cpSlice(
if (isOnCursorLine) {
const relativeVisualColForHighlight = cursorVisualColAbsolute;
const segStart = charCount;
const segEnd = segStart + segLen;
if (
relativeVisualColForHighlight >= segStart &&
relativeVisualColForHighlight < segEnd
) {
const charToHighlight = cpSlice(
seg.text,
relativeVisualColForHighlight - segStart,
relativeVisualColForHighlight - segStart + 1,
);
const highlighted = showCursor
? chalk.inverse(charToHighlight)
: charToHighlight;
display =
cpSlice(
seg.text,
0,
relativeVisualColForHighlight - segStart,
) +
highlighted +
cpSlice(
seg.text,
relativeVisualColForHighlight - segStart + 1,
);
const highlighted = showCursor
? chalk.inverse(charToHighlight)
: charToHighlight;
display =
cpSlice(
seg.text,
0,
relativeVisualColForHighlight - segStart,
) +
highlighted +
cpSlice(
seg.text,
relativeVisualColForHighlight - segStart + 1,
);
}
charCount = segEnd;
}
const color =
seg.type === 'command' || seg.type === 'file'
? theme.text.accent
: theme.text.primary;
renderedLine.push(
<Text key={`token-${segIdx}`} color={color}>
{display}
</Text>,
);
});
const currentLineGhost = isOnCursorLine ? inlineGhost : '';
if (
isOnCursorLine &&
cursorVisualColAbsolute === cpLen(lineText)
) {
if (!currentLineGhost) {
renderedLine.push(
<Text key={`cursor-end-${cursorVisualColAbsolute}`}>
{showCursor ? chalk.inverse(' ') : ' '}
</Text>,
);
}
charCount = segEnd;
}
const showCursorBeforeGhost =
focus &&
isOnCursorLine &&
cursorVisualColAbsolute === cpLen(lineText) &&
currentLineGhost;
const color =
seg.type === 'command' || seg.type === 'file'
? theme.text.accent
: theme.text.primary;
return (
<Box key={`line-${visualIdxInRenderedSet}`} height={1}>
<Text>
{renderedLine}
{showCursorBeforeGhost &&
(showCursor ? chalk.inverse(' ') : ' ')}
{currentLineGhost && (
<Text color={theme.text.secondary}>
{currentLineGhost}
</Text>
)}
</Text>
</Box>
renderedLine.push(
<Text key={`token-${segIdx}`} color={color}>
{display}
</Text>,
);
})
.concat(
additionalLines.map((ghostLine, index) => {
const padding = Math.max(
0,
inputWidth - stringWidth(ghostLine),
);
return (
<Text
key={`ghost-line-${index}`}
color={theme.text.secondary}
>
{ghostLine}
{' '.repeat(padding)}
</Text>
);
}),
)
});
if (
isOnCursorLine &&
cursorVisualColAbsolute === cpLen(lineText)
) {
renderedLine.push(
<Text key={`cursor-end-${cursorVisualColAbsolute}`}>
{showCursor ? chalk.inverse(' ') : ' '}
</Text>,
);
}
return (
<Box key={`line-${visualIdxInRenderedSet}`} height={1}>
<Text>{renderedLine}</Text>
</Box>
);
})
)}
</Box>
</Box>

View File

@@ -1271,7 +1271,6 @@ describe('SettingsDialog', () => {
vimMode: true,
disableAutoUpdate: true,
debugKeystrokeLogging: true,
enablePromptCompletion: true,
},
ui: {
hideWindowTitle: true,
@@ -1517,7 +1516,6 @@ describe('SettingsDialog', () => {
vimMode: false,
disableAutoUpdate: false,
debugKeystrokeLogging: false,
enablePromptCompletion: false,
},
ui: {
hideWindowTitle: false,

View File

@@ -10,8 +10,6 @@ exports[`SettingsDialog > Snapshot Tests > should render default state correctly
│ │
│ Disable Auto Update false │
│ │
│ Enable Prompt Completion false │
│ │
│ Debug Keystroke Logging false │
│ │
│ Output Format Text │
@@ -22,6 +20,8 @@ exports[`SettingsDialog > Snapshot Tests > should render default state correctly
│ │
│ Hide Tips false │
│ │
│ Hide Banner false │
│ │
│ ▼ │
│ │
│ │
@@ -44,8 +44,6 @@ exports[`SettingsDialog > Snapshot Tests > should render focused on scope select
│ │
│ Disable Auto Update false │
│ │
│ Enable Prompt Completion false │
│ │
│ Debug Keystroke Logging false │
│ │
│ Output Format Text │
@@ -56,6 +54,8 @@ exports[`SettingsDialog > Snapshot Tests > should render focused on scope select
│ │
│ Hide Tips false │
│ │
│ Hide Banner false │
│ │
│ ▼ │
│ │
│ │
@@ -78,8 +78,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with accessibility sett
│ │
│ Disable Auto Update false │
│ │
│ Enable Prompt Completion false │
│ │
│ Debug Keystroke Logging false │
│ │
│ Output Format Text │
@@ -90,6 +88,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with accessibility sett
│ │
│ Hide Tips false │
│ │
│ Hide Banner false │
│ │
│ ▼ │
│ │
│ │
@@ -112,8 +112,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with all boolean settin
│ │
│ Disable Auto Update false* │
│ │
│ Enable Prompt Completion false* │
│ │
│ Debug Keystroke Logging false* │
│ │
│ Output Format Text │
@@ -124,6 +122,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with all boolean settin
│ │
│ Hide Tips false* │
│ │
│ Hide Banner false │
│ │
│ ▼ │
│ │
│ │
@@ -146,8 +146,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with different scope se
│ │
│ Disable Auto Update (Modified in System) false │
│ │
│ Enable Prompt Completion false │
│ │
│ Debug Keystroke Logging false │
│ │
│ Output Format Text │
@@ -158,6 +156,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with different scope se
│ │
│ Hide Tips false │
│ │
│ Hide Banner false │
│ │
│ ▼ │
│ │
│ │
@@ -180,8 +180,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with different scope se
│ │
│ Disable Auto Update false │
│ │
│ Enable Prompt Completion false │
│ │
│ Debug Keystroke Logging (Modified in Workspace) false │
│ │
│ Output Format Text │
@@ -192,6 +190,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with different scope se
│ │
│ Hide Tips false │
│ │
│ Hide Banner false │
│ │
│ ▼ │
│ │
│ │
@@ -214,8 +214,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with file filtering set
│ │
│ Disable Auto Update false │
│ │
│ Enable Prompt Completion false │
│ │
│ Debug Keystroke Logging false │
│ │
│ Output Format Text │
@@ -226,6 +224,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with file filtering set
│ │
│ Hide Tips false │
│ │
│ Hide Banner false │
│ │
│ ▼ │
│ │
│ │
@@ -248,8 +248,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with mixed boolean and
│ │
│ Disable Auto Update true* │
│ │
│ Enable Prompt Completion false │
│ │
│ Debug Keystroke Logging false │
│ │
│ Output Format Text │
@@ -260,6 +258,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with mixed boolean and
│ │
│ Hide Tips false │
│ │
│ Hide Banner false │
│ │
│ ▼ │
│ │
│ │
@@ -282,8 +282,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with tools and security
│ │
│ Disable Auto Update false │
│ │
│ Enable Prompt Completion false │
│ │
│ Debug Keystroke Logging false │
│ │
│ Output Format Text │
@@ -294,6 +292,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with tools and security
│ │
│ Hide Tips false │
│ │
│ Hide Banner false │
│ │
│ ▼ │
│ │
│ │
@@ -316,8 +316,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with various boolean se
│ │
│ Disable Auto Update true* │
│ │
│ Enable Prompt Completion true* │
│ │
│ Debug Keystroke Logging true* │
│ │
│ Output Format Text │
@@ -328,6 +326,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with various boolean se
│ │
│ Hide Tips true* │
│ │
│ Hide Banner false │
│ │
│ ▼ │
│ │
│ │