mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-20 00:04:14 +08:00
add some more tests, fix terminal completion bug (#234696)
This commit is contained in:
parent
d82817d22b
commit
bd9530171d
@ -7,7 +7,7 @@ import { deepStrictEqual, strictEqual } from 'assert';
|
||||
import 'mocha';
|
||||
import { availableSpecs, getCompletionItemsFromSpecs } from './terminalSuggestMain';
|
||||
|
||||
const availableCommands = ['cd', 'code', 'code-insiders'];
|
||||
const availableCommands = ['cd', 'code', 'code-insiders', 'c++', 'c.test', 'dir', 'ls'];
|
||||
const codeOptions = ['-', '--add', '--category', '--diff', '--disable-extension', '--disable-extensions', '--disable-gpu', '--enable-proposed-api', '--extensions-dir', '--goto', '--help', '--inspect-brk-extensions', '--inspect-extensions', '--install-extension', '--list-extensions', '--locale', '--log', '--max-memory', '--merge', '--new-window', '--pre-release', '--prof-startup', '--profile', '--reuse-window', '--show-versions', '--status', '--sync', '--telemetry', '--uninstall-extension', '--user-data-dir', '--verbose', '--version', '--wait', '-a', '-d', '-g', '-h', '-m', '-n', '-r', '-s', '-v', '-w'];
|
||||
const localeOptions = ['bg', 'de', 'en', 'es', 'fr', 'hu', 'it', 'ja', 'ko', 'pt-br', 'ru', 'tr', 'zh-CN', 'zh-TW'];
|
||||
const categoryOptions = ['azure', 'data science', 'debuggers', 'extension packs', 'education', 'formatters', 'keymaps', 'language packs', 'linters', 'machine learning', 'notebooks', 'programming languages', 'scm providers', 'snippets', 'testing', 'themes', 'visualization', 'other'];
|
||||
@ -15,16 +15,18 @@ const logOptions = ['critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'
|
||||
const syncOptions = ['on', 'off'];
|
||||
|
||||
const testSpecs: ITestSpec[] = [
|
||||
{ input: '|', expectedCompletionLabels: availableCommands },
|
||||
{ input: 'c|', expectedCompletionLabels: availableCommands },
|
||||
{ input: 'ls && c|', expectedCompletionLabels: availableCommands },
|
||||
{ input: '|', expectedCompletionLabels: availableCommands, resourcesRequested: 'both' },
|
||||
{ input: 'c|', expectedCompletionLabels: availableCommands.filter(c => c.startsWith('c')) },
|
||||
{ input: 'ls && c|', expectedCompletionLabels: availableCommands.filter(c => c.startsWith('c')) },
|
||||
{ input: 'cd |', expectedCompletionLabels: ['~', '-'], resourcesRequested: 'folders' },
|
||||
{ input: 'code|', expectedCompletionLabels: ['code-insiders'] },
|
||||
{ input: 'code-insiders|' },
|
||||
{ input: 'cd .|', resourcesRequested: 'folders' },
|
||||
{ input: 'cd ..|', resourcesRequested: 'folders' },
|
||||
{ input: 'code|', expectedCompletionLabels: ['code', 'code-insiders'] },
|
||||
{ input: 'code-insiders|', expectedCompletionLabels: ['code-insiders'] },
|
||||
{ input: 'code |', expectedCompletionLabels: codeOptions },
|
||||
{ input: 'code --locale |', expectedCompletionLabels: localeOptions },
|
||||
{ input: 'code --diff |', resourcesRequested: 'files' },
|
||||
{ input: 'code -di|', expectedCompletionLabels: codeOptions.filter(o => o.startsWith('di')) },
|
||||
{ input: 'code -di|', expectedCompletionLabels: codeOptions.filter(o => o.startsWith('di')), resourcesRequested: 'both' },
|
||||
{ input: 'code --diff ./file1 |', resourcesRequested: 'files' },
|
||||
{ input: 'code --merge |', resourcesRequested: 'files' },
|
||||
{ input: 'code --merge ./file1 ./file2 |', resourcesRequested: 'files' },
|
||||
@ -63,7 +65,7 @@ suite('Terminal Suggest', () => {
|
||||
test(testSpec.input, () => {
|
||||
const commandLine = testSpec.input.split('|')[0];
|
||||
const cursorPosition = testSpec.input.indexOf('|');
|
||||
const prefix = commandLine.slice(0, cursorPosition).split(' ').pop() || '';
|
||||
const prefix = commandLine.slice(0, cursorPosition).split(' ').at(-1) || '';
|
||||
const filesRequested = testSpec.resourcesRequested === 'files' || testSpec.resourcesRequested === 'both';
|
||||
const foldersRequested = testSpec.resourcesRequested === 'folders' || testSpec.resourcesRequested === 'both';
|
||||
const result = getCompletionItemsFromSpecs(availableSpecs, { commandLine, cursorPosition }, availableCommands, prefix);
|
||||
|
||||
@ -88,51 +88,47 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
const commands = [...commandsInPath, ...builtinCommands];
|
||||
|
||||
const items: vscode.TerminalCompletionItem[] = [];
|
||||
const prefix = getPrefix(terminalContext.commandLine, terminalContext.cursorPosition);
|
||||
|
||||
const specCompletions = await getCompletionItemsFromSpecs(availableSpecs, terminalContext, commands, prefix, token);
|
||||
|
||||
items.push(...specCompletions.items);
|
||||
let filesRequested = specCompletions.filesRequested;
|
||||
let foldersRequested = specCompletions.foldersRequested;
|
||||
|
||||
if (!specCompletions.specificSuggestionsProvided) {
|
||||
for (const command of commands) {
|
||||
if (command.startsWith(prefix) && !items.find(item => item.label === command)) {
|
||||
items.push(createCompletionItem(terminalContext.cursorPosition, prefix, command));
|
||||
}
|
||||
}
|
||||
const result = getCompletionItemsFromSpecs(availableSpecs, terminalContext, commands, prefix, token);
|
||||
if (result.filesRequested || result.foldersRequested) {
|
||||
const cwd = await resolveCwdFromPrefix(prefix, terminal.shellIntegration?.cwd) ?? terminal.shellIntegration?.cwd;
|
||||
return new vscode.TerminalCompletionList(result.items, { filesRequested: result.filesRequested, foldersRequested: result.foldersRequested, cwd, pathSeparator: osIsWindows() ? '\\' : '/' });
|
||||
}
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const shouldShowResourceCompletions =
|
||||
(
|
||||
// If the command line is empty
|
||||
terminalContext.commandLine.trim().length === 0
|
||||
// or no completions are found
|
||||
|| !items?.length
|
||||
// or the completion found is '.'
|
||||
|| items.length === 1 && items[0].label === '.'
|
||||
)
|
||||
// and neither files nor folders are going to be requested (for a specific spec's argument)
|
||||
&& (!filesRequested && !foldersRequested);
|
||||
|
||||
if (shouldShowResourceCompletions) {
|
||||
filesRequested = true;
|
||||
foldersRequested = true;
|
||||
}
|
||||
if (filesRequested || foldersRequested) {
|
||||
return new vscode.TerminalCompletionList(items, { filesRequested, foldersRequested, cwd: terminal.shellIntegration?.cwd, pathSeparator: osIsWindows() ? '\\' : '/' });
|
||||
}
|
||||
return items;
|
||||
return result.items;
|
||||
}
|
||||
}));
|
||||
}, '/', '\\'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adjusts the current working directory based on a given prefix if it is a folder.
|
||||
* @param prefix - The folder path prefix.
|
||||
* @param currentCwd - The current working directory.
|
||||
* @returns The new working directory.
|
||||
*/
|
||||
export async function resolveCwdFromPrefix(prefix: string, currentCwd?: vscode.Uri): Promise<vscode.Uri | undefined> {
|
||||
if (!currentCwd) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Resolve the absolute path of the prefix
|
||||
const resolvedPath = path.resolve(currentCwd?.fsPath, prefix);
|
||||
|
||||
const stat = await fs.stat(resolvedPath);
|
||||
// Check if the resolved path exists and is a directory
|
||||
if (stat.isDirectory()) {
|
||||
return currentCwd.with({ path: resolvedPath });
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
|
||||
// If the prefix is not a folder, return the current cwd
|
||||
return currentCwd;
|
||||
}
|
||||
|
||||
|
||||
function getLabel(spec: Fig.Spec | Fig.Arg | Fig.Suggestion | string): string[] | undefined {
|
||||
if (typeof spec === 'string') {
|
||||
return [spec];
|
||||
@ -146,12 +142,12 @@ function getLabel(spec: Fig.Spec | Fig.Arg | Fig.Suggestion | string): string[]
|
||||
return spec.name;
|
||||
}
|
||||
|
||||
function createCompletionItem(cursorPosition: number, prefix: string, label: string, description?: string, hasSpaceBeforeCursor?: boolean, kind?: vscode.TerminalCompletionItemKind): vscode.TerminalCompletionItem {
|
||||
function createCompletionItem(commandLine: string, cursorPosition: number, prefix: string, label: string, description?: string, kind?: vscode.TerminalCompletionItemKind): vscode.TerminalCompletionItem {
|
||||
return {
|
||||
label,
|
||||
detail: description ?? '',
|
||||
replacementIndex: hasSpaceBeforeCursor ? cursorPosition : cursorPosition - 1,
|
||||
replacementLength: label.length - prefix.length,
|
||||
replacementIndex: commandLine[cursorPosition - 1] === ' ' ? cursorPosition : cursorPosition - 1,
|
||||
replacementLength: label.length - prefix.length > 0 ? label.length - prefix.length : label.length,
|
||||
kind: kind ?? vscode.TerminalCompletionItemKind.Method
|
||||
};
|
||||
}
|
||||
@ -215,10 +211,11 @@ export function asArray<T>(x: T | T[]): T[] {
|
||||
return Array.isArray(x) ? x : [x];
|
||||
}
|
||||
|
||||
export function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalContext: { commandLine: string; cursorPosition: number }, availableCommands: string[], prefix: string, token?: vscode.CancellationToken): { items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; specificSuggestionsProvided: boolean } {
|
||||
export function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalContext: { commandLine: string; cursorPosition: number }, availableCommands: string[], prefix: string, token?: vscode.CancellationToken): { items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean } {
|
||||
const items: vscode.TerminalCompletionItem[] = [];
|
||||
let filesRequested = false;
|
||||
let foldersRequested = false;
|
||||
let specificSuggestionsProvided = false;
|
||||
for (const spec of specs) {
|
||||
const specLabels = getLabel(spec);
|
||||
if (!specLabels) {
|
||||
@ -233,10 +230,10 @@ export function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalContext:
|
||||
// If the prompt is empty
|
||||
!terminalContext.commandLine
|
||||
// or the prefix matches the command and the prefix is not equal to the command
|
||||
|| !!prefix && specLabel.startsWith(prefix) && specLabel !== prefix
|
||||
|| !!prefix && specLabel.startsWith(prefix)
|
||||
) {
|
||||
// push it to the completion items
|
||||
items.push(createCompletionItem(terminalContext.cursorPosition, prefix, specLabel));
|
||||
items.push(createCompletionItem(terminalContext.commandLine, terminalContext.cursorPosition, prefix, specLabel));
|
||||
}
|
||||
if (!terminalContext.commandLine.startsWith(specLabel)) {
|
||||
// the spec label is not the first word in the command line, so do not provide options or args
|
||||
@ -251,7 +248,7 @@ export function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalContext:
|
||||
}
|
||||
for (const optionLabel of optionLabels) {
|
||||
if (!items.find(i => i.label === optionLabel) && optionLabel.startsWith(prefix) || (prefix.length > specLabel.length && prefix.trim() === specLabel)) {
|
||||
items.push(createCompletionItem(terminalContext.cursorPosition, prefix, optionLabel, option.description, false, vscode.TerminalCompletionItemKind.Flag));
|
||||
items.push(createCompletionItem(terminalContext.commandLine, terminalContext.cursorPosition, prefix, optionLabel, option.description, vscode.TerminalCompletionItemKind.Flag));
|
||||
}
|
||||
const expectedText = `${specLabel} ${optionLabel} `;
|
||||
if (!precedingText.includes(expectedText)) {
|
||||
@ -263,8 +260,12 @@ export function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalContext:
|
||||
if (!argsCompletions) {
|
||||
continue;
|
||||
}
|
||||
// return early so that we don't show the other completions
|
||||
return argsCompletions;
|
||||
specificSuggestionsProvided = true;
|
||||
const argCompletions = argsCompletions.items;
|
||||
foldersRequested = foldersRequested || argsCompletions.foldersRequested;
|
||||
filesRequested = filesRequested || argsCompletions.filesRequested;
|
||||
specificSuggestionsProvided = argsCompletions.specificSuggestionsProvided;
|
||||
return { items: argCompletions, filesRequested, foldersRequested };
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -280,12 +281,39 @@ export function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalContext:
|
||||
continue;
|
||||
}
|
||||
items.push(...argsCompletions.items);
|
||||
specificSuggestionsProvided = argsCompletions.specificSuggestionsProvided;
|
||||
filesRequested = filesRequested || argsCompletions.filesRequested;
|
||||
foldersRequested = foldersRequested || argsCompletions.foldersRequested;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { items, filesRequested, foldersRequested, specificSuggestionsProvided: false };
|
||||
|
||||
if (!specificSuggestionsProvided) {
|
||||
// Include builitin/available commands in the results
|
||||
for (const command of availableCommands) {
|
||||
if ((!terminalContext.commandLine.trim() || !!prefix) && command.startsWith(prefix) && !items.find(item => item.label === command)) {
|
||||
items.push(createCompletionItem(terminalContext.commandLine, terminalContext.cursorPosition, prefix, command));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const shouldShowResourceCompletions =
|
||||
(
|
||||
// If the command line is empty
|
||||
terminalContext.commandLine.trim().length === 0
|
||||
// or no completions are found and the prefix is empty
|
||||
|| !items?.length
|
||||
// or all of the items are '.' or '..' IE file paths
|
||||
|| items.length && items.every(i => ['.', '..'].includes(i.label))
|
||||
)
|
||||
// and neither files nor folders are going to be requested (for a specific spec's argument)
|
||||
&& (!filesRequested && !foldersRequested);
|
||||
|
||||
if (shouldShowResourceCompletions) {
|
||||
filesRequested = true;
|
||||
foldersRequested = true;
|
||||
}
|
||||
return { items, filesRequested, foldersRequested };
|
||||
}
|
||||
|
||||
function getCompletionItemsFromArgs(args: Fig.SingleOrArray<Fig.Arg> | undefined, currentPrefix: string, terminalContext: { commandLine: string; cursorPosition: number }, precedingText: string): { items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; specificSuggestionsProvided: boolean } | undefined {
|
||||
@ -315,16 +343,18 @@ function getCompletionItemsFromArgs(args: Fig.SingleOrArray<Fig.Arg> | undefined
|
||||
if (!suggestionLabels) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const twoWordsBefore = terminalContext.commandLine.slice(0, terminalContext.cursorPosition).split(' ').at(-2);
|
||||
const wordBefore = terminalContext.commandLine.slice(0, terminalContext.cursorPosition).split(' ').at(-1);
|
||||
for (const suggestionLabel of suggestionLabels) {
|
||||
if (items.find(i => i.label === suggestionLabel)) {
|
||||
continue;
|
||||
}
|
||||
if (suggestionLabel && suggestionLabel.startsWith(currentPrefix.trim()) && suggestionLabel !== currentPrefix.trim()) {
|
||||
const hasSpaceBeforeCursor = terminalContext.commandLine[terminalContext.cursorPosition - 1] === ' ';
|
||||
// prefix will be '' if there is a space before the cursor
|
||||
if (!arg.isVariadic && twoWordsBefore === suggestionLabel && wordBefore?.trim() === '') {
|
||||
return { items: [], filesRequested, foldersRequested, specificSuggestionsProvided: false };
|
||||
}
|
||||
if (suggestionLabel && suggestionLabel.startsWith(currentPrefix.trim())) {
|
||||
const description = typeof suggestion !== 'string' ? suggestion.description : '';
|
||||
items.push(createCompletionItem(terminalContext.cursorPosition, precedingText, suggestionLabel, description, hasSpaceBeforeCursor, vscode.TerminalCompletionItemKind.Argument));
|
||||
items.push(createCompletionItem(terminalContext.commandLine, terminalContext.cursorPosition, precedingText, suggestionLabel, description, vscode.TerminalCompletionItemKind.Argument));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -339,3 +369,4 @@ function getCompletionItemsFromArgs(args: Fig.SingleOrArray<Fig.Arg> | undefined
|
||||
function osIsWindows(): boolean {
|
||||
return os.platform() === 'win32';
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
import { CancellationToken } from '../../../../../base/common/cancellation.js';
|
||||
import { Disposable, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { Schemas } from '../../../../../base/common/network.js';
|
||||
import { basename } from '../../../../../base/common/path.js';
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
|
||||
import { IFileService } from '../../../../../platform/files/common/files.js';
|
||||
@ -76,7 +77,7 @@ export interface ITerminalCompletionService {
|
||||
_serviceBrand: undefined;
|
||||
readonly providers: IterableIterator<ITerminalCompletionProvider>;
|
||||
registerTerminalCompletionProvider(extensionIdentifier: string, id: string, provider: ITerminalCompletionProvider, ...triggerCharacters: string[]): IDisposable;
|
||||
provideCompletions(promptValue: string, cursorPosition: number, shellType: TerminalShellType, token: CancellationToken, triggerCharacter?: boolean): Promise<ITerminalCompletion[] | undefined>;
|
||||
provideCompletions(promptValue: string, cursorPosition: number, shellType: TerminalShellType, token: CancellationToken, triggerCharacter?: boolean, skipExtensionCompletions?: boolean): Promise<ITerminalCompletion[] | undefined>;
|
||||
}
|
||||
|
||||
export class TerminalCompletionService extends Disposable implements ITerminalCompletionService {
|
||||
@ -121,7 +122,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
|
||||
});
|
||||
}
|
||||
|
||||
async provideCompletions(promptValue: string, cursorPosition: number, shellType: TerminalShellType, token: CancellationToken, triggerCharacter?: boolean): Promise<ITerminalCompletion[] | undefined> {
|
||||
async provideCompletions(promptValue: string, cursorPosition: number, shellType: TerminalShellType, token: CancellationToken, triggerCharacter?: boolean, skipExtensionCompletions?: boolean): Promise<ITerminalCompletion[] | undefined> {
|
||||
if (!this._providers || !this._providers.values) {
|
||||
return undefined;
|
||||
}
|
||||
@ -146,7 +147,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
|
||||
providers = [...this._providers.values()].flatMap(providerMap => [...providerMap.values()]);
|
||||
}
|
||||
|
||||
if (!extensionCompletionsEnabled) {
|
||||
if (!extensionCompletionsEnabled || skipExtensionCompletions) {
|
||||
providers = providers.filter(p => p.isBuiltin);
|
||||
}
|
||||
|
||||
@ -200,17 +201,44 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
|
||||
const resourceCompletions: ITerminalCompletion[] = [];
|
||||
|
||||
const parentDirPath = cwd.fsPath.split(resourceRequestConfig.pathSeparator).slice(0, -1).join(resourceRequestConfig.pathSeparator);
|
||||
const parentCwd = URI.from({ scheme: cwd.scheme, path: parentDirPath });
|
||||
const dirToPrefixMap = new Map<URI, string>();
|
||||
|
||||
dirToPrefixMap.set(cwd, '.');
|
||||
dirToPrefixMap.set(parentCwd, '..');
|
||||
dirToPrefixMap.set(cwd.with({ path: parentDirPath }), '..');
|
||||
|
||||
const lastWord = promptValue.substring(0, cursorPosition).split(' ').pop() ?? '';
|
||||
const endsWithSpace = promptValue.substring(0, cursorPosition).endsWith(' ');
|
||||
let lastWord;
|
||||
if (endsWithSpace) {
|
||||
lastWord = '';
|
||||
} else {
|
||||
lastWord = promptValue.substring(0, cursorPosition).trim().split(' ').pop() ?? '';
|
||||
}
|
||||
if (lastWord.includes(basename(cwd.fsPath))) {
|
||||
lastWord = '';
|
||||
}
|
||||
|
||||
// This breaks folder completions because has a different replacement index
|
||||
// which results in replacement index being set to 0
|
||||
// const includeDirs = endsWithSpace || lastWord.match(/.*[\\\/]/);
|
||||
// if (includeDirs) {
|
||||
// resourceCompletions.push({
|
||||
// label: '.' + resourceRequestConfig.pathSeparator,
|
||||
// kind: TerminalCompletionItemKind.Folder,
|
||||
// isDirectory: true,
|
||||
// replacementIndex: cursorPosition,
|
||||
// replacementLength: 2
|
||||
// });
|
||||
// resourceCompletions.push({
|
||||
// label: '..' + resourceRequestConfig.pathSeparator,
|
||||
// kind: TerminalCompletionItemKind.Folder,
|
||||
// isDirectory: true,
|
||||
// replacementIndex: cursorPosition,
|
||||
// replacementLength: 3
|
||||
// });
|
||||
// }
|
||||
|
||||
for (const [dir, prefix] of dirToPrefixMap) {
|
||||
const fileStat = await this._fileService.resolve(dir, { resolveSingleChildDescendants: true });
|
||||
|
||||
if (!fileStat || !fileStat?.children) {
|
||||
return;
|
||||
}
|
||||
@ -226,12 +254,12 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
|
||||
if (kind === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const label = prefix + stat.resource.fsPath.replace(dir.fsPath, '');
|
||||
const isDirectory = kind === TerminalCompletionItemKind.Folder;
|
||||
const label = isDirectory ? prefix + stat.resource.fsPath.replace(dir.fsPath, '') + resourceRequestConfig.pathSeparator : prefix + stat.resource.fsPath.replace(dir.fsPath, '');
|
||||
resourceCompletions.push({
|
||||
label,
|
||||
kind,
|
||||
isDirectory: kind === TerminalCompletionItemKind.Folder,
|
||||
isDirectory,
|
||||
isFile: kind === TerminalCompletionItemKind.File,
|
||||
replacementIndex: cursorPosition - lastWord.length,
|
||||
replacementLength: label.length
|
||||
|
||||
@ -69,6 +69,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
|
||||
|
||||
private _lastUserData?: string;
|
||||
static lastAcceptedCompletionTimestamp: number = 0;
|
||||
private _lastUserDataTimestamp: number = 0;
|
||||
|
||||
private _cancellationTokenSource: CancellationTokenSource | undefined;
|
||||
|
||||
@ -124,6 +125,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
|
||||
this._terminal = xterm;
|
||||
this._register(xterm.onData(async e => {
|
||||
this._lastUserData = e;
|
||||
this._lastUserDataTimestamp = Date.now();
|
||||
}));
|
||||
}
|
||||
|
||||
@ -142,12 +144,19 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
|
||||
return;
|
||||
}
|
||||
|
||||
let doNotRequestExtensionCompletions = false;
|
||||
// Ensure that a key has been pressed since the last accepted completion in order to prevent
|
||||
// completions being requested again right after accepting a completion
|
||||
if (this._lastUserDataTimestamp < SuggestAddon.lastAcceptedCompletionTimestamp) {
|
||||
doNotRequestExtensionCompletions = true;
|
||||
}
|
||||
|
||||
const enableExtensionCompletions = this._configurationService.getValue<ITerminalSuggestConfiguration>(terminalSuggestConfigSection).enableExtensionCompletions;
|
||||
if (enableExtensionCompletions) {
|
||||
if (enableExtensionCompletions && !doNotRequestExtensionCompletions) {
|
||||
await this._extensionService.activateByEvent('onTerminalCompletionsRequested');
|
||||
}
|
||||
|
||||
const providedCompletions = await this._terminalCompletionService.provideCompletions(this._promptInputModel.value, this._promptInputModel.cursorIndex, this._shellType, token, triggerCharacter);
|
||||
const providedCompletions = await this._terminalCompletionService.provideCompletions(this._promptInputModel.value, this._promptInputModel.cursorIndex, this._shellType, token, triggerCharacter, doNotRequestExtensionCompletions);
|
||||
if (!providedCompletions?.length || token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
@ -214,8 +223,6 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
|
||||
this._showCompletions(model);
|
||||
}
|
||||
|
||||
|
||||
|
||||
setContainerWithOverflow(container: HTMLElement): void {
|
||||
this._container = container;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user