mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
feat: remove prompt completion feature (#1076)
This commit is contained in:
@@ -789,7 +789,6 @@ export async function loadCliConfig(
|
|||||||
useBuiltinRipgrep: settings.tools?.useBuiltinRipgrep,
|
useBuiltinRipgrep: settings.tools?.useBuiltinRipgrep,
|
||||||
shouldUseNodePtyShell: settings.tools?.shell?.enableInteractiveShell,
|
shouldUseNodePtyShell: settings.tools?.shell?.enableInteractiveShell,
|
||||||
skipNextSpeakerCheck: settings.model?.skipNextSpeakerCheck,
|
skipNextSpeakerCheck: settings.model?.skipNextSpeakerCheck,
|
||||||
enablePromptCompletion: settings.general?.enablePromptCompletion ?? false,
|
|
||||||
skipLoopDetection: settings.model?.skipLoopDetection ?? false,
|
skipLoopDetection: settings.model?.skipLoopDetection ?? false,
|
||||||
skipStartupContext: settings.model?.skipStartupContext ?? false,
|
skipStartupContext: settings.model?.skipStartupContext ?? false,
|
||||||
vlmSwitchMode,
|
vlmSwitchMode,
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ const MIGRATION_MAP: Record<string, string> = {
|
|||||||
disableAutoUpdate: 'general.disableAutoUpdate',
|
disableAutoUpdate: 'general.disableAutoUpdate',
|
||||||
disableUpdateNag: 'general.disableUpdateNag',
|
disableUpdateNag: 'general.disableUpdateNag',
|
||||||
dnsResolutionOrder: 'advanced.dnsResolutionOrder',
|
dnsResolutionOrder: 'advanced.dnsResolutionOrder',
|
||||||
enablePromptCompletion: 'general.enablePromptCompletion',
|
|
||||||
enforcedAuthType: 'security.auth.enforcedType',
|
enforcedAuthType: 'security.auth.enforcedType',
|
||||||
excludeTools: 'tools.exclude',
|
excludeTools: 'tools.exclude',
|
||||||
excludeMCPServers: 'mcp.excluded',
|
excludeMCPServers: 'mcp.excluded',
|
||||||
|
|||||||
@@ -167,16 +167,6 @@ const SETTINGS_SCHEMA = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
enablePromptCompletion: {
|
|
||||||
type: 'boolean',
|
|
||||||
label: 'Enable Prompt Completion',
|
|
||||||
category: 'General',
|
|
||||||
requiresRestart: true,
|
|
||||||
default: false,
|
|
||||||
description:
|
|
||||||
'Enable AI-powered prompt completion suggestions while typing.',
|
|
||||||
showInDialog: true,
|
|
||||||
},
|
|
||||||
debugKeystrokeLogging: {
|
debugKeystrokeLogging: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
label: 'Debug Keystroke Logging',
|
label: 'Debug Keystroke Logging',
|
||||||
|
|||||||
@@ -164,11 +164,6 @@ describe('InputPrompt', () => {
|
|||||||
setActiveSuggestionIndex: vi.fn(),
|
setActiveSuggestionIndex: vi.fn(),
|
||||||
setShowSuggestions: vi.fn(),
|
setShowSuggestions: vi.fn(),
|
||||||
handleAutocomplete: vi.fn(),
|
handleAutocomplete: vi.fn(),
|
||||||
promptCompletion: {
|
|
||||||
text: '',
|
|
||||||
accept: vi.fn(),
|
|
||||||
clear: vi.fn(),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
mockedUseCommandCompletion.mockReturnValue(mockCommandCompletion);
|
mockedUseCommandCompletion.mockReturnValue(mockCommandCompletion);
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,8 @@ import { theme } from '../semantic-colors.js';
|
|||||||
import { useInputHistory } from '../hooks/useInputHistory.js';
|
import { useInputHistory } from '../hooks/useInputHistory.js';
|
||||||
import type { TextBuffer } from './shared/text-buffer.js';
|
import type { TextBuffer } from './shared/text-buffer.js';
|
||||||
import { logicalPosToOffset } 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 chalk from 'chalk';
|
||||||
import stringWidth from 'string-width';
|
|
||||||
import { useShellHistory } from '../hooks/useShellHistory.js';
|
import { useShellHistory } from '../hooks/useShellHistory.js';
|
||||||
import { useReverseSearchCompletion } from '../hooks/useReverseSearchCompletion.js';
|
import { useReverseSearchCompletion } from '../hooks/useReverseSearchCompletion.js';
|
||||||
import { useCommandCompletion } from '../hooks/useCommandCompletion.js';
|
import { useCommandCompletion } from '../hooks/useCommandCompletion.js';
|
||||||
@@ -91,7 +90,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
commandContext,
|
commandContext,
|
||||||
placeholder = ' Type your message or @path/to/file',
|
placeholder = ' Type your message or @path/to/file',
|
||||||
focus = true,
|
focus = true,
|
||||||
inputWidth,
|
|
||||||
suggestionsWidth,
|
suggestionsWidth,
|
||||||
shellModeActive,
|
shellModeActive,
|
||||||
setShellModeActive,
|
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 (!shellModeActive) {
|
||||||
if (keyMatchers[Command.REVERSE_SEARCH](key)) {
|
if (keyMatchers[Command.REVERSE_SEARCH](key)) {
|
||||||
setCommandSearchActive(true);
|
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
|
// Fall back to the text buffer's default input handling for all other keys
|
||||||
buffer.handleInput(key);
|
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,
|
focus,
|
||||||
@@ -703,118 +679,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
buffer.visualCursor;
|
buffer.visualCursor;
|
||||||
const scrollVisualRow = buffer.visualScrollRow;
|
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 = () => {
|
const getActiveCompletion = () => {
|
||||||
if (commandSearchActive) return commandSearchCompletion;
|
if (commandSearchActive) return commandSearchCompletion;
|
||||||
if (reverseSearchActive) return reverseSearchCompletion;
|
if (reverseSearchActive) return reverseSearchCompletion;
|
||||||
@@ -887,134 +751,96 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
<Text color={theme.text.secondary}>{placeholder}</Text>
|
<Text color={theme.text.secondary}>{placeholder}</Text>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
linesToRender
|
linesToRender.map((lineText, visualIdxInRenderedSet) => {
|
||||||
.map((lineText, visualIdxInRenderedSet) => {
|
const absoluteVisualIdx =
|
||||||
const absoluteVisualIdx =
|
scrollVisualRow + visualIdxInRenderedSet;
|
||||||
scrollVisualRow + visualIdxInRenderedSet;
|
const mapEntry = buffer.visualToLogicalMap[absoluteVisualIdx];
|
||||||
const mapEntry = buffer.visualToLogicalMap[absoluteVisualIdx];
|
const cursorVisualRow = cursorVisualRowAbsolute - scrollVisualRow;
|
||||||
const cursorVisualRow =
|
const isOnCursorLine =
|
||||||
cursorVisualRowAbsolute - scrollVisualRow;
|
focus && visualIdxInRenderedSet === cursorVisualRow;
|
||||||
const isOnCursorLine =
|
|
||||||
focus && visualIdxInRenderedSet === cursorVisualRow;
|
|
||||||
|
|
||||||
const renderedLine: React.ReactNode[] = [];
|
const renderedLine: React.ReactNode[] = [];
|
||||||
|
|
||||||
const [logicalLineIdx, logicalStartCol] = mapEntry;
|
const [logicalLineIdx, logicalStartCol] = mapEntry;
|
||||||
const logicalLine = buffer.lines[logicalLineIdx] || '';
|
const logicalLine = buffer.lines[logicalLineIdx] || '';
|
||||||
const tokens = parseInputForHighlighting(
|
const tokens = parseInputForHighlighting(
|
||||||
logicalLine,
|
logicalLine,
|
||||||
logicalLineIdx,
|
logicalLineIdx,
|
||||||
);
|
);
|
||||||
|
|
||||||
const visualStart = logicalStartCol;
|
const visualStart = logicalStartCol;
|
||||||
const visualEnd = logicalStartCol + cpLen(lineText);
|
const visualEnd = logicalStartCol + cpLen(lineText);
|
||||||
const segments = buildSegmentsForVisualSlice(
|
const segments = buildSegmentsForVisualSlice(
|
||||||
tokens,
|
tokens,
|
||||||
visualStart,
|
visualStart,
|
||||||
visualEnd,
|
visualEnd,
|
||||||
);
|
);
|
||||||
|
|
||||||
let charCount = 0;
|
let charCount = 0;
|
||||||
segments.forEach((seg, segIdx) => {
|
segments.forEach((seg, segIdx) => {
|
||||||
const segLen = cpLen(seg.text);
|
const segLen = cpLen(seg.text);
|
||||||
let display = seg.text;
|
let display = seg.text;
|
||||||
|
|
||||||
if (isOnCursorLine) {
|
if (isOnCursorLine) {
|
||||||
const relativeVisualColForHighlight =
|
const relativeVisualColForHighlight = cursorVisualColAbsolute;
|
||||||
cursorVisualColAbsolute;
|
const segStart = charCount;
|
||||||
const segStart = charCount;
|
const segEnd = segStart + segLen;
|
||||||
const segEnd = segStart + segLen;
|
if (
|
||||||
if (
|
relativeVisualColForHighlight >= segStart &&
|
||||||
relativeVisualColForHighlight >= segStart &&
|
relativeVisualColForHighlight < segEnd
|
||||||
relativeVisualColForHighlight < segEnd
|
) {
|
||||||
) {
|
const charToHighlight = cpSlice(
|
||||||
const charToHighlight = cpSlice(
|
seg.text,
|
||||||
|
relativeVisualColForHighlight - segStart,
|
||||||
|
relativeVisualColForHighlight - segStart + 1,
|
||||||
|
);
|
||||||
|
const highlighted = showCursor
|
||||||
|
? chalk.inverse(charToHighlight)
|
||||||
|
: charToHighlight;
|
||||||
|
display =
|
||||||
|
cpSlice(
|
||||||
seg.text,
|
seg.text,
|
||||||
|
0,
|
||||||
relativeVisualColForHighlight - segStart,
|
relativeVisualColForHighlight - segStart,
|
||||||
|
) +
|
||||||
|
highlighted +
|
||||||
|
cpSlice(
|
||||||
|
seg.text,
|
||||||
relativeVisualColForHighlight - segStart + 1,
|
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 =
|
const color =
|
||||||
focus &&
|
seg.type === 'command' || seg.type === 'file'
|
||||||
isOnCursorLine &&
|
? theme.text.accent
|
||||||
cursorVisualColAbsolute === cpLen(lineText) &&
|
: theme.text.primary;
|
||||||
currentLineGhost;
|
|
||||||
|
|
||||||
return (
|
renderedLine.push(
|
||||||
<Box key={`line-${visualIdxInRenderedSet}`} height={1}>
|
<Text key={`token-${segIdx}`} color={color}>
|
||||||
<Text>
|
{display}
|
||||||
{renderedLine}
|
</Text>,
|
||||||
{showCursorBeforeGhost &&
|
|
||||||
(showCursor ? chalk.inverse(' ') : ' ')}
|
|
||||||
{currentLineGhost && (
|
|
||||||
<Text color={theme.text.secondary}>
|
|
||||||
{currentLineGhost}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
.concat(
|
|
||||||
additionalLines.map((ghostLine, index) => {
|
if (
|
||||||
const padding = Math.max(
|
isOnCursorLine &&
|
||||||
0,
|
cursorVisualColAbsolute === cpLen(lineText)
|
||||||
inputWidth - stringWidth(ghostLine),
|
) {
|
||||||
);
|
renderedLine.push(
|
||||||
return (
|
<Text key={`cursor-end-${cursorVisualColAbsolute}`}>
|
||||||
<Text
|
{showCursor ? chalk.inverse(' ') : ' '}
|
||||||
key={`ghost-line-${index}`}
|
</Text>,
|
||||||
color={theme.text.secondary}
|
);
|
||||||
>
|
}
|
||||||
{ghostLine}
|
|
||||||
{' '.repeat(padding)}
|
return (
|
||||||
</Text>
|
<Box key={`line-${visualIdxInRenderedSet}`} height={1}>
|
||||||
);
|
<Text>{renderedLine}</Text>
|
||||||
}),
|
</Box>
|
||||||
)
|
);
|
||||||
|
})
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1271,7 +1271,6 @@ describe('SettingsDialog', () => {
|
|||||||
vimMode: true,
|
vimMode: true,
|
||||||
disableAutoUpdate: true,
|
disableAutoUpdate: true,
|
||||||
debugKeystrokeLogging: true,
|
debugKeystrokeLogging: true,
|
||||||
enablePromptCompletion: true,
|
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
hideWindowTitle: true,
|
hideWindowTitle: true,
|
||||||
@@ -1517,7 +1516,6 @@ describe('SettingsDialog', () => {
|
|||||||
vimMode: false,
|
vimMode: false,
|
||||||
disableAutoUpdate: false,
|
disableAutoUpdate: false,
|
||||||
debugKeystrokeLogging: false,
|
debugKeystrokeLogging: false,
|
||||||
enablePromptCompletion: false,
|
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
hideWindowTitle: false,
|
hideWindowTitle: false,
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ exports[`SettingsDialog > Snapshot Tests > should render default state correctly
|
|||||||
│ │
|
│ │
|
||||||
│ Disable Auto Update false │
|
│ Disable Auto Update false │
|
||||||
│ │
|
│ │
|
||||||
│ Enable Prompt Completion false │
|
|
||||||
│ │
|
|
||||||
│ Debug Keystroke Logging false │
|
│ Debug Keystroke Logging false │
|
||||||
│ │
|
│ │
|
||||||
│ Output Format Text │
|
│ Output Format Text │
|
||||||
@@ -22,6 +20,8 @@ exports[`SettingsDialog > Snapshot Tests > should render default state correctly
|
|||||||
│ │
|
│ │
|
||||||
│ Hide Tips false │
|
│ Hide Tips false │
|
||||||
│ │
|
│ │
|
||||||
|
│ Hide Banner false │
|
||||||
|
│ │
|
||||||
│ ▼ │
|
│ ▼ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
@@ -44,8 +44,6 @@ exports[`SettingsDialog > Snapshot Tests > should render focused on scope select
|
|||||||
│ │
|
│ │
|
||||||
│ Disable Auto Update false │
|
│ Disable Auto Update false │
|
||||||
│ │
|
│ │
|
||||||
│ Enable Prompt Completion false │
|
|
||||||
│ │
|
|
||||||
│ Debug Keystroke Logging false │
|
│ Debug Keystroke Logging false │
|
||||||
│ │
|
│ │
|
||||||
│ Output Format Text │
|
│ Output Format Text │
|
||||||
@@ -56,6 +54,8 @@ exports[`SettingsDialog > Snapshot Tests > should render focused on scope select
|
|||||||
│ │
|
│ │
|
||||||
│ Hide Tips false │
|
│ Hide Tips false │
|
||||||
│ │
|
│ │
|
||||||
|
│ Hide Banner false │
|
||||||
|
│ │
|
||||||
│ ▼ │
|
│ ▼ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
@@ -78,8 +78,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with accessibility sett
|
|||||||
│ │
|
│ │
|
||||||
│ Disable Auto Update false │
|
│ Disable Auto Update false │
|
||||||
│ │
|
│ │
|
||||||
│ Enable Prompt Completion false │
|
|
||||||
│ │
|
|
||||||
│ Debug Keystroke Logging false │
|
│ Debug Keystroke Logging false │
|
||||||
│ │
|
│ │
|
||||||
│ Output Format Text │
|
│ Output Format Text │
|
||||||
@@ -90,6 +88,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with accessibility sett
|
|||||||
│ │
|
│ │
|
||||||
│ Hide Tips false │
|
│ Hide Tips false │
|
||||||
│ │
|
│ │
|
||||||
|
│ Hide Banner false │
|
||||||
|
│ │
|
||||||
│ ▼ │
|
│ ▼ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
@@ -112,8 +112,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with all boolean settin
|
|||||||
│ │
|
│ │
|
||||||
│ Disable Auto Update false* │
|
│ Disable Auto Update false* │
|
||||||
│ │
|
│ │
|
||||||
│ Enable Prompt Completion false* │
|
|
||||||
│ │
|
|
||||||
│ Debug Keystroke Logging false* │
|
│ Debug Keystroke Logging false* │
|
||||||
│ │
|
│ │
|
||||||
│ Output Format Text │
|
│ Output Format Text │
|
||||||
@@ -124,6 +122,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with all boolean settin
|
|||||||
│ │
|
│ │
|
||||||
│ Hide Tips false* │
|
│ 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 │
|
│ Disable Auto Update (Modified in System) false │
|
||||||
│ │
|
│ │
|
||||||
│ Enable Prompt Completion false │
|
|
||||||
│ │
|
|
||||||
│ Debug Keystroke Logging false │
|
│ Debug Keystroke Logging false │
|
||||||
│ │
|
│ │
|
||||||
│ Output Format Text │
|
│ Output Format Text │
|
||||||
@@ -158,6 +156,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with different scope se
|
|||||||
│ │
|
│ │
|
||||||
│ Hide Tips false │
|
│ Hide Tips false │
|
||||||
│ │
|
│ │
|
||||||
|
│ Hide Banner false │
|
||||||
|
│ │
|
||||||
│ ▼ │
|
│ ▼ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
@@ -180,8 +180,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with different scope se
|
|||||||
│ │
|
│ │
|
||||||
│ Disable Auto Update false │
|
│ Disable Auto Update false │
|
||||||
│ │
|
│ │
|
||||||
│ Enable Prompt Completion false │
|
|
||||||
│ │
|
|
||||||
│ Debug Keystroke Logging (Modified in Workspace) false │
|
│ Debug Keystroke Logging (Modified in Workspace) false │
|
||||||
│ │
|
│ │
|
||||||
│ Output Format Text │
|
│ Output Format Text │
|
||||||
@@ -192,6 +190,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with different scope se
|
|||||||
│ │
|
│ │
|
||||||
│ Hide Tips false │
|
│ Hide Tips false │
|
||||||
│ │
|
│ │
|
||||||
|
│ Hide Banner false │
|
||||||
|
│ │
|
||||||
│ ▼ │
|
│ ▼ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
@@ -214,8 +214,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with file filtering set
|
|||||||
│ │
|
│ │
|
||||||
│ Disable Auto Update false │
|
│ Disable Auto Update false │
|
||||||
│ │
|
│ │
|
||||||
│ Enable Prompt Completion false │
|
|
||||||
│ │
|
|
||||||
│ Debug Keystroke Logging false │
|
│ Debug Keystroke Logging false │
|
||||||
│ │
|
│ │
|
||||||
│ Output Format Text │
|
│ Output Format Text │
|
||||||
@@ -226,6 +224,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with file filtering set
|
|||||||
│ │
|
│ │
|
||||||
│ Hide Tips false │
|
│ Hide Tips false │
|
||||||
│ │
|
│ │
|
||||||
|
│ Hide Banner false │
|
||||||
|
│ │
|
||||||
│ ▼ │
|
│ ▼ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
@@ -248,8 +248,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with mixed boolean and
|
|||||||
│ │
|
│ │
|
||||||
│ Disable Auto Update true* │
|
│ Disable Auto Update true* │
|
||||||
│ │
|
│ │
|
||||||
│ Enable Prompt Completion false │
|
|
||||||
│ │
|
|
||||||
│ Debug Keystroke Logging false │
|
│ Debug Keystroke Logging false │
|
||||||
│ │
|
│ │
|
||||||
│ Output Format Text │
|
│ Output Format Text │
|
||||||
@@ -260,6 +258,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with mixed boolean and
|
|||||||
│ │
|
│ │
|
||||||
│ Hide Tips false │
|
│ Hide Tips false │
|
||||||
│ │
|
│ │
|
||||||
|
│ Hide Banner false │
|
||||||
|
│ │
|
||||||
│ ▼ │
|
│ ▼ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
@@ -282,8 +282,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with tools and security
|
|||||||
│ │
|
│ │
|
||||||
│ Disable Auto Update false │
|
│ Disable Auto Update false │
|
||||||
│ │
|
│ │
|
||||||
│ Enable Prompt Completion false │
|
|
||||||
│ │
|
|
||||||
│ Debug Keystroke Logging false │
|
│ Debug Keystroke Logging false │
|
||||||
│ │
|
│ │
|
||||||
│ Output Format Text │
|
│ Output Format Text │
|
||||||
@@ -294,6 +292,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with tools and security
|
|||||||
│ │
|
│ │
|
||||||
│ Hide Tips false │
|
│ Hide Tips false │
|
||||||
│ │
|
│ │
|
||||||
|
│ Hide Banner false │
|
||||||
|
│ │
|
||||||
│ ▼ │
|
│ ▼ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
@@ -316,8 +316,6 @@ exports[`SettingsDialog > Snapshot Tests > should render with various boolean se
|
|||||||
│ │
|
│ │
|
||||||
│ Disable Auto Update true* │
|
│ Disable Auto Update true* │
|
||||||
│ │
|
│ │
|
||||||
│ Enable Prompt Completion true* │
|
|
||||||
│ │
|
|
||||||
│ Debug Keystroke Logging true* │
|
│ Debug Keystroke Logging true* │
|
||||||
│ │
|
│ │
|
||||||
│ Output Format Text │
|
│ Output Format Text │
|
||||||
@@ -328,6 +326,8 @@ exports[`SettingsDialog > Snapshot Tests > should render with various boolean se
|
|||||||
│ │
|
│ │
|
||||||
│ Hide Tips true* │
|
│ Hide Tips true* │
|
||||||
│ │
|
│ │
|
||||||
|
│ Hide Banner false │
|
||||||
|
│ │
|
||||||
│ ▼ │
|
│ ▼ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -83,9 +83,7 @@ const setupMocks = ({
|
|||||||
|
|
||||||
describe('useCommandCompletion', () => {
|
describe('useCommandCompletion', () => {
|
||||||
const mockCommandContext = {} as CommandContext;
|
const mockCommandContext = {} as CommandContext;
|
||||||
const mockConfig = {
|
const mockConfig = {} as Config;
|
||||||
getEnablePromptCompletion: () => false,
|
|
||||||
} as Config;
|
|
||||||
const testDirs: string[] = [];
|
const testDirs: string[] = [];
|
||||||
const testRootDir = '/';
|
const testRootDir = '/';
|
||||||
|
|
||||||
@@ -516,81 +514,4 @@ describe('useCommandCompletion', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('prompt completion filtering', () => {
|
|
||||||
it('should not trigger prompt completion for line comments', async () => {
|
|
||||||
const mockConfig = {
|
|
||||||
getEnablePromptCompletion: () => true,
|
|
||||||
} as Config;
|
|
||||||
|
|
||||||
const { result } = renderHook(() => {
|
|
||||||
const textBuffer = useTextBufferForTest('// This is a line comment');
|
|
||||||
const completion = useCommandCompletion(
|
|
||||||
textBuffer,
|
|
||||||
testDirs,
|
|
||||||
testRootDir,
|
|
||||||
[],
|
|
||||||
mockCommandContext,
|
|
||||||
false,
|
|
||||||
mockConfig,
|
|
||||||
);
|
|
||||||
return { ...completion, textBuffer };
|
|
||||||
});
|
|
||||||
|
|
||||||
// Should not trigger prompt completion for comments
|
|
||||||
expect(result.current.suggestions.length).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not trigger prompt completion for block comments', async () => {
|
|
||||||
const mockConfig = {
|
|
||||||
getEnablePromptCompletion: () => true,
|
|
||||||
} as Config;
|
|
||||||
|
|
||||||
const { result } = renderHook(() => {
|
|
||||||
const textBuffer = useTextBufferForTest(
|
|
||||||
'/* This is a block comment */',
|
|
||||||
);
|
|
||||||
const completion = useCommandCompletion(
|
|
||||||
textBuffer,
|
|
||||||
testDirs,
|
|
||||||
testRootDir,
|
|
||||||
[],
|
|
||||||
mockCommandContext,
|
|
||||||
false,
|
|
||||||
mockConfig,
|
|
||||||
);
|
|
||||||
return { ...completion, textBuffer };
|
|
||||||
});
|
|
||||||
|
|
||||||
// Should not trigger prompt completion for comments
|
|
||||||
expect(result.current.suggestions.length).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should trigger prompt completion for regular text when enabled', async () => {
|
|
||||||
const mockConfig = {
|
|
||||||
getEnablePromptCompletion: () => true,
|
|
||||||
} as Config;
|
|
||||||
|
|
||||||
const { result } = renderHook(() => {
|
|
||||||
const textBuffer = useTextBufferForTest(
|
|
||||||
'This is regular text that should trigger completion',
|
|
||||||
);
|
|
||||||
const completion = useCommandCompletion(
|
|
||||||
textBuffer,
|
|
||||||
testDirs,
|
|
||||||
testRootDir,
|
|
||||||
[],
|
|
||||||
mockCommandContext,
|
|
||||||
false,
|
|
||||||
mockConfig,
|
|
||||||
);
|
|
||||||
return { ...completion, textBuffer };
|
|
||||||
});
|
|
||||||
|
|
||||||
// This test verifies that comments are filtered out while regular text is not
|
|
||||||
expect(result.current.textBuffer.text).toBe(
|
|
||||||
'This is regular text that should trigger completion',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,11 +13,6 @@ import { isSlashCommand } from '../utils/commandUtils.js';
|
|||||||
import { toCodePoints } from '../utils/textUtils.js';
|
import { toCodePoints } from '../utils/textUtils.js';
|
||||||
import { useAtCompletion } from './useAtCompletion.js';
|
import { useAtCompletion } from './useAtCompletion.js';
|
||||||
import { useSlashCompletion } from './useSlashCompletion.js';
|
import { useSlashCompletion } from './useSlashCompletion.js';
|
||||||
import type { PromptCompletion } from './usePromptCompletion.js';
|
|
||||||
import {
|
|
||||||
usePromptCompletion,
|
|
||||||
PROMPT_COMPLETION_MIN_LENGTH,
|
|
||||||
} from './usePromptCompletion.js';
|
|
||||||
import type { Config } from '@qwen-code/qwen-code-core';
|
import type { Config } from '@qwen-code/qwen-code-core';
|
||||||
import { useCompletion } from './useCompletion.js';
|
import { useCompletion } from './useCompletion.js';
|
||||||
|
|
||||||
@@ -25,7 +20,6 @@ export enum CompletionMode {
|
|||||||
IDLE = 'IDLE',
|
IDLE = 'IDLE',
|
||||||
AT = 'AT',
|
AT = 'AT',
|
||||||
SLASH = 'SLASH',
|
SLASH = 'SLASH',
|
||||||
PROMPT = 'PROMPT',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UseCommandCompletionReturn {
|
export interface UseCommandCompletionReturn {
|
||||||
@@ -41,7 +35,6 @@ export interface UseCommandCompletionReturn {
|
|||||||
navigateUp: () => void;
|
navigateUp: () => void;
|
||||||
navigateDown: () => void;
|
navigateDown: () => void;
|
||||||
handleAutocomplete: (indexToUse: number) => void;
|
handleAutocomplete: (indexToUse: number) => void;
|
||||||
promptCompletion: PromptCompletion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCommandCompletion(
|
export function useCommandCompletion(
|
||||||
@@ -126,32 +119,13 @@ export function useCommandCompletion(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for prompt completion - only if enabled
|
|
||||||
const trimmedText = buffer.text.trim();
|
|
||||||
const isPromptCompletionEnabled =
|
|
||||||
config?.getEnablePromptCompletion() ?? false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
isPromptCompletionEnabled &&
|
|
||||||
trimmedText.length >= PROMPT_COMPLETION_MIN_LENGTH &&
|
|
||||||
!isSlashCommand(trimmedText) &&
|
|
||||||
!trimmedText.includes('@')
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
completionMode: CompletionMode.PROMPT,
|
|
||||||
query: trimmedText,
|
|
||||||
completionStart: 0,
|
|
||||||
completionEnd: trimmedText.length,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
completionMode: CompletionMode.IDLE,
|
completionMode: CompletionMode.IDLE,
|
||||||
query: null,
|
query: null,
|
||||||
completionStart: -1,
|
completionStart: -1,
|
||||||
completionEnd: -1,
|
completionEnd: -1,
|
||||||
};
|
};
|
||||||
}, [cursorRow, cursorCol, buffer.lines, buffer.text, config]);
|
}, [cursorRow, cursorCol, buffer.lines]);
|
||||||
|
|
||||||
useAtCompletion({
|
useAtCompletion({
|
||||||
enabled: completionMode === CompletionMode.AT,
|
enabled: completionMode === CompletionMode.AT,
|
||||||
@@ -172,12 +146,6 @@ export function useCommandCompletion(
|
|||||||
setIsPerfectMatch,
|
setIsPerfectMatch,
|
||||||
});
|
});
|
||||||
|
|
||||||
const promptCompletion = usePromptCompletion({
|
|
||||||
buffer,
|
|
||||||
config,
|
|
||||||
enabled: completionMode === CompletionMode.PROMPT,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActiveSuggestionIndex(suggestions.length > 0 ? 0 : -1);
|
setActiveSuggestionIndex(suggestions.length > 0 ? 0 : -1);
|
||||||
setVisibleStartIndex(0);
|
setVisibleStartIndex(0);
|
||||||
@@ -264,6 +232,5 @@ export function useCommandCompletion(
|
|||||||
navigateUp,
|
navigateUp,
|
||||||
navigateDown,
|
navigateDown,
|
||||||
handleAutocomplete,
|
handleAutocomplete,
|
||||||
promptCompletion,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,254 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2025 Google LLC
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
|
|
||||||
import type { Config } from '@qwen-code/qwen-code-core';
|
|
||||||
import {
|
|
||||||
DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
|
||||||
getResponseText,
|
|
||||||
} from '@qwen-code/qwen-code-core';
|
|
||||||
import type { Content, GenerateContentConfig } from '@google/genai';
|
|
||||||
import type { TextBuffer } from '../components/shared/text-buffer.js';
|
|
||||||
import { isSlashCommand } from '../utils/commandUtils.js';
|
|
||||||
|
|
||||||
export const PROMPT_COMPLETION_MIN_LENGTH = 5;
|
|
||||||
export const PROMPT_COMPLETION_DEBOUNCE_MS = 250;
|
|
||||||
|
|
||||||
export interface PromptCompletion {
|
|
||||||
text: string;
|
|
||||||
isLoading: boolean;
|
|
||||||
isActive: boolean;
|
|
||||||
accept: () => void;
|
|
||||||
clear: () => void;
|
|
||||||
markSelected: (selectedText: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UsePromptCompletionOptions {
|
|
||||||
buffer: TextBuffer;
|
|
||||||
config?: Config;
|
|
||||||
enabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function usePromptCompletion({
|
|
||||||
buffer,
|
|
||||||
config,
|
|
||||||
enabled,
|
|
||||||
}: UsePromptCompletionOptions): PromptCompletion {
|
|
||||||
const [ghostText, setGhostText] = useState<string>('');
|
|
||||||
const [isLoadingGhostText, setIsLoadingGhostText] = useState<boolean>(false);
|
|
||||||
const abortControllerRef = useRef<AbortController | null>(null);
|
|
||||||
const [justSelectedSuggestion, setJustSelectedSuggestion] =
|
|
||||||
useState<boolean>(false);
|
|
||||||
const lastSelectedTextRef = useRef<string>('');
|
|
||||||
const lastRequestedTextRef = useRef<string>('');
|
|
||||||
|
|
||||||
const isPromptCompletionEnabled =
|
|
||||||
enabled && (config?.getEnablePromptCompletion() ?? false);
|
|
||||||
|
|
||||||
const clearGhostText = useCallback(() => {
|
|
||||||
setGhostText('');
|
|
||||||
setIsLoadingGhostText(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const acceptGhostText = useCallback(() => {
|
|
||||||
if (ghostText && ghostText.length > buffer.text.length) {
|
|
||||||
buffer.setText(ghostText);
|
|
||||||
setGhostText('');
|
|
||||||
setJustSelectedSuggestion(true);
|
|
||||||
lastSelectedTextRef.current = ghostText;
|
|
||||||
}
|
|
||||||
}, [ghostText, buffer]);
|
|
||||||
|
|
||||||
const markSuggestionSelected = useCallback((selectedText: string) => {
|
|
||||||
setJustSelectedSuggestion(true);
|
|
||||||
lastSelectedTextRef.current = selectedText;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const generatePromptSuggestions = useCallback(async () => {
|
|
||||||
const trimmedText = buffer.text.trim();
|
|
||||||
const geminiClient = config?.getGeminiClient();
|
|
||||||
|
|
||||||
if (trimmedText === lastRequestedTextRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abortControllerRef.current) {
|
|
||||||
abortControllerRef.current.abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
trimmedText.length < PROMPT_COMPLETION_MIN_LENGTH ||
|
|
||||||
!geminiClient ||
|
|
||||||
isSlashCommand(trimmedText) ||
|
|
||||||
trimmedText.includes('@') ||
|
|
||||||
!isPromptCompletionEnabled
|
|
||||||
) {
|
|
||||||
clearGhostText();
|
|
||||||
lastRequestedTextRef.current = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastRequestedTextRef.current = trimmedText;
|
|
||||||
setIsLoadingGhostText(true);
|
|
||||||
|
|
||||||
abortControllerRef.current = new AbortController();
|
|
||||||
const signal = abortControllerRef.current.signal;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const contents: Content[] = [
|
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
text: `You are a professional prompt engineering assistant. Complete the user's partial prompt with expert precision and clarity. User's input: "${trimmedText}" Continue this prompt by adding specific, actionable details that align with the user's intent. Focus on: clear, precise language; structured requirements; professional terminology; measurable outcomes. Length Guidelines: Keep suggestions concise (ideally 10-20 characters); prioritize brevity while maintaining clarity; use essential keywords only; avoid redundant phrases. Start your response with the exact user text ("${trimmedText}") followed by your completion. Provide practical, implementation-focused suggestions rather than creative interpretations. Format: Plain text only. Single completion. Match the user's language. Emphasize conciseness over elaboration.`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const generationConfig: GenerateContentConfig = {
|
|
||||||
temperature: 0.3,
|
|
||||||
maxOutputTokens: 16000,
|
|
||||||
thinkingConfig: {
|
|
||||||
thinkingBudget: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await geminiClient.generateContent(
|
|
||||||
contents,
|
|
||||||
generationConfig,
|
|
||||||
signal,
|
|
||||||
DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (signal.aborted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response) {
|
|
||||||
const responseText = getResponseText(response);
|
|
||||||
|
|
||||||
if (responseText) {
|
|
||||||
const suggestionText = responseText.trim();
|
|
||||||
|
|
||||||
if (
|
|
||||||
suggestionText.length > 0 &&
|
|
||||||
suggestionText.startsWith(trimmedText)
|
|
||||||
) {
|
|
||||||
setGhostText(suggestionText);
|
|
||||||
} else {
|
|
||||||
clearGhostText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (
|
|
||||||
!(
|
|
||||||
signal.aborted ||
|
|
||||||
(error instanceof Error && error.name === 'AbortError')
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
console.error('prompt completion error:', error);
|
|
||||||
// Clear the last requested text to allow retry only on real errors
|
|
||||||
lastRequestedTextRef.current = '';
|
|
||||||
}
|
|
||||||
clearGhostText();
|
|
||||||
} finally {
|
|
||||||
if (!signal.aborted) {
|
|
||||||
setIsLoadingGhostText(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [buffer.text, config, clearGhostText, isPromptCompletionEnabled]);
|
|
||||||
|
|
||||||
const isCursorAtEnd = useCallback(() => {
|
|
||||||
const [cursorRow, cursorCol] = buffer.cursor;
|
|
||||||
const totalLines = buffer.lines.length;
|
|
||||||
if (cursorRow !== totalLines - 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastLine = buffer.lines[cursorRow] || '';
|
|
||||||
return cursorCol === lastLine.length;
|
|
||||||
}, [buffer.cursor, buffer.lines]);
|
|
||||||
|
|
||||||
const handlePromptCompletion = useCallback(() => {
|
|
||||||
if (!isCursorAtEnd()) {
|
|
||||||
clearGhostText();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const trimmedText = buffer.text.trim();
|
|
||||||
|
|
||||||
if (justSelectedSuggestion && trimmedText === lastSelectedTextRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trimmedText !== lastSelectedTextRef.current) {
|
|
||||||
setJustSelectedSuggestion(false);
|
|
||||||
lastSelectedTextRef.current = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
generatePromptSuggestions();
|
|
||||||
}, [
|
|
||||||
buffer.text,
|
|
||||||
generatePromptSuggestions,
|
|
||||||
justSelectedSuggestion,
|
|
||||||
isCursorAtEnd,
|
|
||||||
clearGhostText,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Debounce prompt completion
|
|
||||||
useEffect(() => {
|
|
||||||
const timeoutId = setTimeout(
|
|
||||||
handlePromptCompletion,
|
|
||||||
PROMPT_COMPLETION_DEBOUNCE_MS,
|
|
||||||
);
|
|
||||||
return () => clearTimeout(timeoutId);
|
|
||||||
}, [buffer.text, buffer.cursor, handlePromptCompletion]);
|
|
||||||
|
|
||||||
// Ghost text validation - clear if it doesn't match current text or cursor not at end
|
|
||||||
useEffect(() => {
|
|
||||||
const currentText = buffer.text.trim();
|
|
||||||
|
|
||||||
if (ghostText && !isCursorAtEnd()) {
|
|
||||||
clearGhostText();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
ghostText &&
|
|
||||||
currentText.length > 0 &&
|
|
||||||
!ghostText.startsWith(currentText)
|
|
||||||
) {
|
|
||||||
clearGhostText();
|
|
||||||
}
|
|
||||||
}, [buffer.text, buffer.cursor, ghostText, clearGhostText, isCursorAtEnd]);
|
|
||||||
|
|
||||||
// Cleanup on unmount
|
|
||||||
useEffect(() => () => abortControllerRef.current?.abort(), []);
|
|
||||||
|
|
||||||
const isActive = useMemo(() => {
|
|
||||||
if (!isPromptCompletionEnabled) return false;
|
|
||||||
|
|
||||||
if (!isCursorAtEnd()) return false;
|
|
||||||
|
|
||||||
const trimmedText = buffer.text.trim();
|
|
||||||
return (
|
|
||||||
trimmedText.length >= PROMPT_COMPLETION_MIN_LENGTH &&
|
|
||||||
!isSlashCommand(trimmedText) &&
|
|
||||||
!trimmedText.includes('@')
|
|
||||||
);
|
|
||||||
}, [buffer.text, isPromptCompletionEnabled, isCursorAtEnd]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
text: ghostText,
|
|
||||||
isLoading: isLoadingGhostText,
|
|
||||||
isActive,
|
|
||||||
accept: acceptGhostText,
|
|
||||||
clear: clearGhostText,
|
|
||||||
markSelected: markSuggestionSelected,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -280,7 +280,6 @@ export interface ConfigParameters {
|
|||||||
skipNextSpeakerCheck?: boolean;
|
skipNextSpeakerCheck?: boolean;
|
||||||
shellExecutionConfig?: ShellExecutionConfig;
|
shellExecutionConfig?: ShellExecutionConfig;
|
||||||
extensionManagement?: boolean;
|
extensionManagement?: boolean;
|
||||||
enablePromptCompletion?: boolean;
|
|
||||||
skipLoopDetection?: boolean;
|
skipLoopDetection?: boolean;
|
||||||
vlmSwitchMode?: string;
|
vlmSwitchMode?: string;
|
||||||
truncateToolOutputThreshold?: number;
|
truncateToolOutputThreshold?: number;
|
||||||
@@ -377,7 +376,6 @@ export class Config {
|
|||||||
private readonly skipNextSpeakerCheck: boolean;
|
private readonly skipNextSpeakerCheck: boolean;
|
||||||
private shellExecutionConfig: ShellExecutionConfig;
|
private shellExecutionConfig: ShellExecutionConfig;
|
||||||
private readonly extensionManagement: boolean = true;
|
private readonly extensionManagement: boolean = true;
|
||||||
private readonly enablePromptCompletion: boolean = false;
|
|
||||||
private readonly skipLoopDetection: boolean;
|
private readonly skipLoopDetection: boolean;
|
||||||
private readonly skipStartupContext: boolean;
|
private readonly skipStartupContext: boolean;
|
||||||
private readonly vlmSwitchMode: string | undefined;
|
private readonly vlmSwitchMode: string | undefined;
|
||||||
@@ -495,7 +493,6 @@ export class Config {
|
|||||||
this.useSmartEdit = params.useSmartEdit ?? false;
|
this.useSmartEdit = params.useSmartEdit ?? false;
|
||||||
this.extensionManagement = params.extensionManagement ?? true;
|
this.extensionManagement = params.extensionManagement ?? true;
|
||||||
this.storage = new Storage(this.targetDir);
|
this.storage = new Storage(this.targetDir);
|
||||||
this.enablePromptCompletion = params.enablePromptCompletion ?? false;
|
|
||||||
this.vlmSwitchMode = params.vlmSwitchMode;
|
this.vlmSwitchMode = params.vlmSwitchMode;
|
||||||
this.fileExclusions = new FileExclusions(this);
|
this.fileExclusions = new FileExclusions(this);
|
||||||
this.eventEmitter = params.eventEmitter;
|
this.eventEmitter = params.eventEmitter;
|
||||||
@@ -1038,10 +1035,6 @@ export class Config {
|
|||||||
return this.accessibility.screenReader ?? false;
|
return this.accessibility.screenReader ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getEnablePromptCompletion(): boolean {
|
|
||||||
return this.enablePromptCompletion;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSkipLoopDetection(): boolean {
|
getSkipLoopDetection(): boolean {
|
||||||
return this.skipLoopDetection;
|
return this.skipLoopDetection;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user