finalize terminal completion provider API (#271338)

This commit is contained in:
Megan Rogge
2025-10-23 08:57:05 -07:00
committed by GitHub
parent 1c2e29a3c7
commit a1b4ecaa32
18 changed files with 371 additions and 364 deletions
@@ -13,8 +13,7 @@ export function createCompletionItem(cursorPosition: number, currentCommandStrin
label: commandResource.label,
detail: detail ?? commandResource.detail ?? '',
documentation,
replacementIndex: cursorPosition - lastWord.length,
replacementLength: lastWord.length,
replacementRange: [cursorPosition - lastWord.length, cursorPosition],
kind: kind ?? commandResource.kind ?? vscode.TerminalCompletionItemKind.Method
};
}
@@ -495,13 +495,9 @@ export async function getCompletionItemsFromSpecs(
}
showFiles = true;
showFolders = true;
}
// For arguments when no fig suggestions are found these are fallback suggestions
else if (!items.length && !showFiles && !showFolders && !hasCurrentArg) {
if (terminalContext.allowFallbackCompletions) {
showFiles = true;
showFolders = true;
}
} else if (!items.length && !showFiles && !showFolders && !hasCurrentArg) {
showFiles = true;
showFolders = true;
}
let cwd: vscode.Uri | undefined;
@@ -509,7 +505,7 @@ export async function getCompletionItemsFromSpecs(
cwd = await resolveCwdFromCurrentCommandString(currentCommandString, shellIntegrationCwd);
}
return { items, showFiles: showFiles, showFolders: showFolders, fileExtensions, cwd };
return { items, showFiles, showFolders, fileExtensions, cwd };
}
function getEnvAsRecord(shellIntegrationEnv: ITerminalEnvironment): Record<string, string> {
@@ -95,7 +95,7 @@ suite('Terminal Suggest', () => {
const currentCommandString = getCurrentCommandAndArgs(commandLine, cursorIndex, undefined);
const showFiles = testSpec.expectedResourceRequests?.type === 'files' || testSpec.expectedResourceRequests?.type === 'both';
const showFolders = testSpec.expectedResourceRequests?.type === 'folders' || testSpec.expectedResourceRequests?.type === 'both';
const terminalContext = { commandLine, cursorIndex, allowFallbackCompletions: true };
const terminalContext = { commandLine, cursorIndex };
const result = await getCompletionItemsFromSpecs(
completionSpecs,
terminalContext,
@@ -394,9 +394,6 @@ const _allApiProposals = {
telemetry: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetry.d.ts',
},
terminalCompletionProvider: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts',
},
terminalDataWriteEvent: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts',
},
@@ -277,8 +277,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
public $registerCompletionProvider(id: string, extensionIdentifier: string, ...triggerCharacters: string[]): void {
this._completionProviders.set(id, this._terminalCompletionService.registerTerminalCompletionProvider(extensionIdentifier, id, {
id,
provideCompletions: async (commandLine, cursorPosition, allowFallbackCompletions, token) => {
const completions = await this._proxy.$provideTerminalCompletions(id, { commandLine, cursorIndex: cursorPosition, allowFallbackCompletions }, token);
provideCompletions: async (commandLine, cursorIndex, token) => {
const completions = await this._proxy.$provideTerminalCompletions(id, { commandLine, cursorIndex }, token);
if (!completions) {
return undefined;
}
@@ -878,7 +878,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostTerminalService.registerProfileProvider(extension, id, provider);
},
registerTerminalCompletionProvider(provider: vscode.TerminalCompletionProvider<vscode.TerminalCompletionItem>, ...triggerCharacters: string[]): vscode.Disposable {
checkProposedApiEnabled(extension, 'terminalCompletionProvider');
return extHostTerminalService.registerTerminalCompletionProvider(extension, provider, ...triggerCharacters);
},
registerTerminalQuickFixProvider(id: string, provider: vscode.TerminalQuickFixProvider): vscode.Disposable {
@@ -2498,7 +2498,6 @@ export interface ITerminalCommandDto {
export interface ITerminalCompletionContextDto {
commandLine: string;
cursorIndex: number;
allowFallbackCompletions: boolean;
}
export interface ITerminalCompletionItemDto {
@@ -2509,8 +2508,7 @@ export interface ITerminalCompletionItemDto {
isFile?: boolean | undefined;
isDirectory?: boolean | undefined;
isKeyword?: boolean | undefined;
replacementIndex: number;
replacementLength: number;
replacementRange: readonly [number, number];
}
export interface ITerminalCompletionProvider {
+3 -5
View File
@@ -940,25 +940,23 @@ export enum TerminalCompletionItemKind {
export class TerminalCompletionItem implements vscode.TerminalCompletionItem {
label: string | CompletionItemLabel;
replacementRange: readonly [number, number];
icon?: ThemeIcon | undefined;
detail?: string | undefined;
documentation?: string | vscode.MarkdownString | undefined;
isFile?: boolean | undefined;
isDirectory?: boolean | undefined;
isKeyword?: boolean | undefined;
replacementIndex: number;
replacementLength: number;
constructor(label: string | CompletionItemLabel, icon?: ThemeIcon, detail?: string, documentation?: string | vscode.MarkdownString, isFile?: boolean, isDirectory?: boolean, isKeyword?: boolean, replacementIndex?: number, replacementLength?: number) {
constructor(label: string | CompletionItemLabel, replacementRange: readonly [number, number], icon?: ThemeIcon, detail?: string, documentation?: string | vscode.MarkdownString, isFile?: boolean, isDirectory?: boolean, isKeyword?: boolean) {
this.label = label;
this.replacementRange = replacementRange;
this.icon = icon;
this.detail = detail;
this.documentation = documentation;
this.isFile = isFile;
this.isDirectory = isDirectory;
this.isKeyword = isKeyword;
this.replacementIndex = replacementIndex ?? 0;
this.replacementLength = replacementLength ?? 0;
}
}
@@ -38,7 +38,7 @@ export class LspCompletionProviderAddon extends Disposable implements ITerminalA
// console.log('activate');
}
async provideCompletions(value: string, cursorPosition: number, allowFallbackCompletions: false, token: CancellationToken): Promise<ITerminalCompletion[] | TerminalCompletionList<ITerminalCompletion> | undefined> {
async provideCompletions(value: string, cursorPosition: number, token: CancellationToken): Promise<ITerminalCompletion[] | TerminalCompletionList<ITerminalCompletion> | undefined> {
// Apply edit for non-executed current commandline --> Pretend we are typing in the real-document.
this._lspTerminalModelContentProvider.trackPromptInputToVirtualFile(value);
@@ -65,8 +65,7 @@ export class LspCompletionProviderAddon extends Disposable implements ITerminalA
detail: item.detail,
documentation: item.documentation,
kind: convertedKind,
replacementIndex: completionItemTemp.replacementIndex,
replacementLength: completionItemTemp.replacementLength,
replacementRange: completionItemTemp.replacementRange,
};
// Store unresolved item and provider for lazy resolution if needed
@@ -95,8 +94,7 @@ export function createCompletionItemPython(
return {
label,
detail: detail ?? '',
replacementIndex: cursorPosition - lastWord.length,
replacementLength: lastWord.length,
replacementRange: [cursorPosition - lastWord.length, cursorPosition],
kind: kind ?? TerminalCompletionItemKind.Method
};
}
@@ -133,14 +131,9 @@ export interface TerminalCompletionItem {
label: string | CompletionItemLabel;
/**
* The index of the start of the range to replace.
* Selection range (inclusive start, exclusive end) to replace when this completion is applied.
*/
replacementIndex: number;
/**
* The length of the range to replace.
*/
replacementLength: number;
replacementRange: readonly [number, number] | undefined;
/**
* The completion's detail which appears on the right of the list.
@@ -55,7 +55,7 @@ export class TerminalCompletionList<ITerminalCompletion> {
export interface TerminalCompletionResourceOptions {
showFiles?: boolean;
showFolders?: boolean;
showDirectories?: boolean;
globPattern?: string | IRelativePattern;
cwd: UriComponents;
pathSeparator: string;
@@ -65,7 +65,7 @@ export interface TerminalCompletionResourceOptions {
export interface ITerminalCompletionProvider {
id: string;
shellTypes?: TerminalShellType[];
provideCompletions(value: string, cursorPosition: number, allowFallbackCompletions: boolean, token: CancellationToken): Promise<ITerminalCompletion[] | TerminalCompletionList<ITerminalCompletion> | undefined>;
provideCompletions(value: string, cursorPosition: number, token: CancellationToken): Promise<ITerminalCompletion[] | TerminalCompletionList<ITerminalCompletion> | undefined>;
triggerCharacters?: string[];
isBuiltin?: boolean;
}
@@ -190,7 +190,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
let completions;
try {
completions = await Promise.race([
provider.provideCompletions(promptValue, cursorPosition, allowFallbackCompletions, token).then(result => {
provider.provideCompletions(promptValue, cursorPosition, token).then(result => {
this._logService.trace(`TerminalCompletionService#_collectCompletions provider ${provider.id} finished`);
return result;
}),
@@ -211,7 +211,8 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
this._logService.trace(`TerminalCompletionService#_collectCompletions amend ${completionItems.length} completion items`);
if (shellType === GeneralShellType.PowerShell) {
for (const completion of completionItems) {
completion.isFileOverride ??= completion.kind === TerminalCompletionItemKind.Method && completion.replacementIndex === 0;
const start = completion.replacementRange ? completion.replacementRange[0] : 0;
completion.isFileOverride ??= completion.kind === TerminalCompletionItemKind.Method && start === 0;
}
}
if (provider.isBuiltin) {
@@ -256,11 +257,11 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
// Files requested implies folders requested since the file could be in any folder. We could
// provide diagnostics when a folder is provided where a file is expected.
const showFolders = (resourceOptions.showFolders || resourceOptions.showFiles) ?? false;
const showDirectories = (resourceOptions.showDirectories || resourceOptions.showFiles) ?? false;
const showFiles = resourceOptions.showFiles ?? false;
const globPattern = resourceOptions.globPattern ?? undefined;
if (!showFolders && !showFiles) {
if (!showDirectories && !showFiles) {
return;
}
@@ -350,8 +351,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
provider,
kind: TerminalCompletionItemKind.Folder,
detail: lastWordFolderResource,
replacementIndex: cursorPosition - lastWord.length,
replacementLength: lastWord.length
replacementRange: [cursorPosition - lastWord.length, cursorPosition]
});
return resourceCompletions;
}
@@ -374,7 +374,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
// - (tilde) `~/|` -> `~/`
// - (tilde) `~/src/|` -> `~/src/`
this._logService.trace(`TerminalCompletionService#resolveResources cwd`);
if (showFolders) {
if (showDirectories) {
let label: string;
switch (type) {
case 'tilde': {
@@ -398,8 +398,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
provider,
kind: TerminalCompletionItemKind.Folder,
detail: getFriendlyPath(this._labelService, lastWordFolderResource, resourceOptions.pathSeparator, TerminalCompletionItemKind.Folder, shellType),
replacementIndex: cursorPosition - lastWord.length,
replacementLength: lastWord.length
replacementRange: [cursorPosition - lastWord.length, cursorPosition]
});
}
@@ -412,7 +411,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
await Promise.all(stat.children.map(child => (async () => {
let kind: TerminalCompletionItemKind | undefined;
let detail: string | undefined = undefined;
if (showFolders && child.isDirectory) {
if (showDirectories && child.isDirectory) {
if (child.isSymbolicLink) {
kind = TerminalCompletionItemKind.SymbolicLinkFolder;
} else {
@@ -468,8 +467,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
provider,
kind,
detail: detail ?? getFriendlyPath(this._labelService, child.resource, resourceOptions.pathSeparator, kind, shellType),
replacementIndex: cursorPosition - lastWord.length,
replacementLength: lastWord.length
replacementRange: [cursorPosition - lastWord.length, cursorPosition]
});
})()));
@@ -477,7 +475,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
//
// - (relative) `|` -> `/foo/vscode` (CDPATH has /foo which contains vscode folder)
this._logService.trace(`TerminalCompletionService#resolveResources CDPATH`);
if (type === 'relative' && showFolders) {
if (type === 'relative' && showDirectories) {
if (promptValue.startsWith('cd ')) {
const config = this._configurationService.getValue(TerminalSuggestSettingId.CdPath);
if (config === 'absolute' || config === 'relative') {
@@ -507,8 +505,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
provider,
kind,
detail,
replacementIndex: cursorPosition - lastWord.length,
replacementLength: lastWord.length
replacementRange: [cursorPosition - lastWord.length, cursorPosition]
});
}
}
@@ -524,7 +521,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
// - (relative) `|` -> `../`
// - (relative) `./src/|` -> `./src/../`
this._logService.trace(`TerminalCompletionService#resolveResources parent dir`);
if (type === 'relative' && showFolders) {
if (type === 'relative' && showDirectories) {
let label = `..${resourceOptions.pathSeparator}`;
if (lastWordFolder.length > 0) {
label = addPathRelativePrefix(lastWordFolder + label, resourceOptions, lastWordFolderHasDotPrefix);
@@ -535,8 +532,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
provider,
kind: TerminalCompletionItemKind.Folder,
detail: getFriendlyPath(this._labelService, parentDir, resourceOptions.pathSeparator, TerminalCompletionItemKind.Folder, shellType),
replacementIndex: cursorPosition - lastWord.length,
replacementLength: lastWord.length
replacementRange: [cursorPosition - lastWord.length, cursorPosition]
});
}
@@ -561,8 +557,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
provider,
kind: TerminalCompletionItemKind.Folder,
detail: typeof homeResource === 'string' ? homeResource : getFriendlyPath(this._labelService, homeResource, resourceOptions.pathSeparator, TerminalCompletionItemKind.Folder, shellType),
replacementIndex: cursorPosition - lastWord.length,
replacementLength: lastWord.length
replacementRange: [cursorPosition - lastWord.length, cursorPosition]
});
}
@@ -156,8 +156,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
// Right arrow is used to accept the completion. This is a common keybinding in pwsh, zsh
// and fish.
inputData: '\x1b[C',
replacementIndex: 0,
replacementLength: 0,
replacementRange: [0, 0],
provider: 'core:inlineSuggestion',
detail: 'Inline suggestion',
kind: TerminalCompletionItemKind.InlineSuggestion,
@@ -650,10 +649,9 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
const replacementIndex = spaceIndex === -1 ? 0 : spaceIndex + 1;
const suggestion = this._currentPromptInputState.value.substring(replacementIndex);
this._inlineCompletion.label = suggestion;
this._inlineCompletion.replacementIndex = replacementIndex;
// Note that the cursor index delta must be taken into account here, otherwise filtering
// wont work correctly.
this._inlineCompletion.replacementLength = this._currentPromptInputState.cursorIndex - replacementIndex - this._cursorIndexDelta;
// Update replacementRange (inclusive start, exclusive end) for replacement
const end = this._currentPromptInputState.cursorIndex - this._cursorIndexDelta;
this._inlineCompletion.replacementRange = [replacementIndex, end];
// Reset the completion item as the object reference must remain the same but its
// contents will differ across syncs. This is done so we don't need to reassign the
// model and the slowdown/flickering that could potentially cause.
@@ -928,7 +926,8 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
// The replacement text is any text after the replacement index for the completions, this
// includes any text that was there before the completions were requested and any text added
// since to refine the completion.
const replacementText = currentPromptInputState.value.substring(suggestion.item.completion.replacementIndex, currentPromptInputState.cursorIndex);
const startIndex = suggestion.item.completion.replacementRange?.[0] ?? currentPromptInputState.cursorIndex;
const replacementText = currentPromptInputState.value.substring(startIndex, currentPromptInputState.cursorIndex);
// Right side of replacement text in the same word
let rightSideReplacementText = '';
@@ -15,8 +15,7 @@ function createItem(options: Partial<ITerminalCompletion>): TerminalCompletionIt
kind: options.kind ?? TerminalCompletionItemKind.Method,
label: options.label || 'defaultLabel',
provider: options.provider || 'defaultProvider',
replacementIndex: options.replacementIndex || 0,
replacementLength: options.replacementLength || 1,
replacementRange: options.replacementRange || [0, 1],
});
}
@@ -254,8 +253,7 @@ suite('TerminalCompletionModel', function () {
new TerminalCompletionItem({
label: 'ab',
provider: 'core',
replacementIndex: 0,
replacementLength: 0,
replacementRange: [0, 0],
kind
})
];
@@ -33,8 +33,7 @@ interface IAssertionTerminalCompletion {
}
interface IAssertionCommandLineConfig {
replacementIndex: number;
replacementLength: number;
replacementRange: [number, number];
}
/**
@@ -47,14 +46,12 @@ function assertCompletions(actual: ITerminalCompletion[] | undefined, expected:
label: e.label,
detail: e.detail ?? '',
kind: e.kind ?? TerminalCompletionItemKind.Folder,
replacementIndex: e.replacementIndex,
replacementLength: e.replacementLength,
replacementRange: e.replacementRange,
})), expected.map(e => ({
label: e.label.replaceAll('/', sep),
detail: e.detail ? e.detail.replaceAll('/', sep) : '',
kind: e.kind ?? TerminalCompletionItemKind.Folder,
replacementIndex: expectedConfig.replacementIndex,
replacementLength: expectedConfig.replacementLength,
replacementRange: expectedConfig.replacementRange,
}))
);
}
@@ -70,16 +67,14 @@ function assertPartialCompletionsExist(actual: ITerminalCompletion[] | undefined
label: e.label.replaceAll('/', pathSeparator),
detail: e.detail ? e.detail.replaceAll('/', pathSeparator) : '',
kind: e.kind ?? TerminalCompletionItemKind.Folder,
replacementIndex: expectedConfig.replacementIndex,
replacementLength: expectedConfig.replacementLength,
replacementRange: expectedConfig.replacementRange,
}));
for (const expectedItem of expectedMapped) {
assert.deepStrictEqual(actual.map(e => ({
label: e.label,
detail: e.detail ?? '',
kind: e.kind ?? TerminalCompletionItemKind.Folder,
replacementIndex: e.replacementIndex,
replacementLength: e.replacementLength,
replacementRange: e.replacementRange,
})).find(e => e.detail === expectedItem.detail), expectedItem);
}
}
@@ -170,7 +165,7 @@ suite('TerminalCompletionService', () => {
test('| should return root-level completions', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showFolders: true,
showDirectories: true,
pathSeparator
};
const result = await terminalCompletionService.resolveResources(resourceOptions, '', 1, provider, capabilities);
@@ -180,13 +175,13 @@ suite('TerminalCompletionService', () => {
{ label: './folder1/', detail: '/test/folder1/' },
{ label: '../', detail: '/' },
standardTidleItem,
], { replacementIndex: 1, replacementLength: 0 });
], { replacementRange: [1, 1] });
});
test('./| should return folder completions', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showFolders: true,
showDirectories: true,
pathSeparator
};
const result = await terminalCompletionService.resolveResources(resourceOptions, './', 3, provider, capabilities);
@@ -195,13 +190,13 @@ suite('TerminalCompletionService', () => {
{ label: './', detail: '/test/' },
{ label: './folder1/', detail: '/test/folder1/' },
{ label: './../', detail: '/' },
], { replacementIndex: 1, replacementLength: 2 });
], { replacementRange: [1, 3] });
});
test('cd ./| should return folder completions', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showFolders: true,
showDirectories: true,
pathSeparator
};
const result = await terminalCompletionService.resolveResources(resourceOptions, 'cd ./', 5, provider, capabilities);
@@ -210,12 +205,12 @@ suite('TerminalCompletionService', () => {
{ label: './', detail: '/test/' },
{ label: './folder1/', detail: '/test/folder1/' },
{ label: './../', detail: '/' },
], { replacementIndex: 3, replacementLength: 2 });
], { replacementRange: [3, 5] });
});
test('cd ./f| should return folder completions', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showFolders: true,
showDirectories: true,
pathSeparator
};
const result = await terminalCompletionService.resolveResources(resourceOptions, 'cd ./f', 6, provider, capabilities);
@@ -224,7 +219,7 @@ suite('TerminalCompletionService', () => {
{ label: './', detail: '/test/' },
{ label: './folder1/', detail: '/test/folder1/' },
{ label: './../', detail: '/' },
], { replacementIndex: 3, replacementLength: 3 });
], { replacementRange: [3, 6] });
});
});
@@ -242,7 +237,7 @@ suite('TerminalCompletionService', () => {
test('./| should handle hidden files and folders', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showFolders: true,
showDirectories: true,
showFiles: true,
pathSeparator
};
@@ -255,13 +250,13 @@ suite('TerminalCompletionService', () => {
{ label: './folder1/', detail: '/test/folder1/' },
{ label: './file1.txt', detail: '/test/file1.txt', kind: TerminalCompletionItemKind.File },
{ label: './../', detail: '/' },
], { replacementIndex: 0, replacementLength: 2 });
], { replacementRange: [0, 2] });
});
test('./h| should handle hidden files and folders', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showFolders: true,
showDirectories: true,
showFiles: true,
pathSeparator
};
@@ -274,7 +269,7 @@ suite('TerminalCompletionService', () => {
{ label: './folder1/', detail: '/test/folder1/' },
{ label: './file1.txt', detail: '/test/file1.txt', kind: TerminalCompletionItemKind.File },
{ label: './../', detail: '/' },
], { replacementIndex: 0, replacementLength: 3 });
], { replacementRange: [0, 3] });
});
});
@@ -293,7 +288,7 @@ suite('TerminalCompletionService', () => {
resourceOptions = {
cwd: URI.parse('file:///test/folder1'),// Updated to reflect home directory
showFiles: true,
showFolders: true,
showDirectories: true,
pathSeparator
};
validResources = [
@@ -314,14 +309,14 @@ suite('TerminalCompletionService', () => {
test('~| should return completion for ~', async () => {
assertPartialCompletionsExist(await terminalCompletionService.resolveResources(resourceOptions, '~', 1, provider, capabilities), [
{ label: '~', detail: '/home/' },
], { replacementIndex: 0, replacementLength: 1 });
], { replacementRange: [0, 1] });
});
test('~/| should return folder completions relative to $HOME', async () => {
assertCompletions(await terminalCompletionService.resolveResources(resourceOptions, '~/', 2, provider, capabilities), [
{ label: '~/', detail: '/home/' },
{ label: '~/vscode/', detail: '/home/vscode/' },
], { replacementIndex: 0, replacementLength: 2 });
], { replacementRange: [0, 2] });
});
test('~/vscode/| should return folder completions relative to $HOME/vscode', async () => {
@@ -329,7 +324,7 @@ suite('TerminalCompletionService', () => {
{ label: '~/vscode/', detail: '/home/vscode/' },
{ label: '~/vscode/foo/', detail: '/home/vscode/foo/' },
{ label: '~/vscode/bar.txt', detail: '/home/vscode/bar.txt', kind: TerminalCompletionItemKind.File },
], { replacementIndex: 0, replacementLength: 9 });
], { replacementRange: [0, 9] });
});
});
@@ -343,7 +338,7 @@ suite('TerminalCompletionService', () => {
test('C:/Foo/| absolute paths on Windows', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///C:'),
showFolders: true,
showDirectories: true,
pathSeparator
};
validResources = [URI.parse('file:///C:/Foo')];
@@ -356,12 +351,12 @@ suite('TerminalCompletionService', () => {
assertCompletions(result, [
{ label: 'C:/Foo/', detail: 'C:/Foo/' },
{ label: 'C:/Foo/Bar/', detail: 'C:/Foo/Bar/' },
], { replacementIndex: 0, replacementLength: 7 });
], { replacementRange: [0, 7] });
});
test('c:/foo/| case insensitivity on Windows', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///c:'),
showFolders: true,
showDirectories: true,
pathSeparator
};
validResources = [URI.parse('file:///c:/foo')];
@@ -374,13 +369,13 @@ suite('TerminalCompletionService', () => {
// Note that the detail is normalizes drive letters to capital case intentionally
{ label: 'c:/foo/', detail: 'C:/foo/' },
{ label: 'c:/foo/Bar/', detail: 'C:/foo/Bar/' },
], { replacementIndex: 0, replacementLength: 7 });
], { replacementRange: [0, 7] });
});
} else {
test('/foo/| absolute paths NOT on Windows', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///'),
showFolders: true,
showDirectories: true,
pathSeparator
};
validResources = [URI.parse('file:///foo')];
@@ -393,7 +388,7 @@ suite('TerminalCompletionService', () => {
assertCompletions(result, [
{ label: '/foo/', detail: '/foo/' },
{ label: '/foo/Bar/', detail: '/foo/Bar/' },
], { replacementIndex: 0, replacementLength: 5 });
], { replacementRange: [0, 5] });
});
}
@@ -401,7 +396,7 @@ suite('TerminalCompletionService', () => {
test('.\\folder | Case insensitivity should resolve correctly on Windows', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///C:/test'),
showFolders: true,
showDirectories: true,
pathSeparator: '\\'
};
@@ -418,13 +413,13 @@ suite('TerminalCompletionService', () => {
{ label: '.\\FolderA\\', detail: 'C:\\test\\FolderA\\' },
{ label: '.\\anotherFolder\\', detail: 'C:\\test\\anotherFolder\\' },
{ label: '.\\..\\', detail: 'C:\\' },
], { replacementIndex: 0, replacementLength: 8 });
], { replacementRange: [0, 8] });
});
} else {
test('./folder | Case sensitivity should resolve correctly on Mac/Unix', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showFolders: true,
showDirectories: true,
pathSeparator: '/'
};
validResources = [URI.parse('file:///test')];
@@ -440,14 +435,14 @@ suite('TerminalCompletionService', () => {
{ label: './FolderA/', detail: '/test/FolderA/' },
{ label: './foldera/', detail: '/test/foldera/' },
{ label: './../', detail: '/' }
], { replacementIndex: 0, replacementLength: 8 });
], { replacementRange: [0, 8] });
});
}
test('| Empty input should resolve to current directory', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showFolders: true,
showDirectories: true,
pathSeparator
};
validResources = [URI.parse('file:///test')];
@@ -463,13 +458,13 @@ suite('TerminalCompletionService', () => {
{ label: './folder2/', detail: '/test/folder2/' },
{ label: '../', detail: '/' },
standardTidleItem,
], { replacementIndex: 0, replacementLength: 0 });
], { replacementRange: [0, 0] });
});
test('./| should handle large directories with many results gracefully', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showFolders: true,
showDirectories: true,
pathSeparator
};
validResources = [URI.parse('file:///test')];
@@ -489,7 +484,7 @@ suite('TerminalCompletionService', () => {
test('./folder| should include current folder with trailing / is missing', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showFolders: true,
showDirectories: true,
pathSeparator
};
validResources = [URI.parse('file:///test')];
@@ -504,20 +499,18 @@ suite('TerminalCompletionService', () => {
{ label: './folder1/', detail: '/test/folder1/' },
{ label: './folder2/', detail: '/test/folder2/' },
{ label: './../', detail: '/' }
], { replacementIndex: 1, replacementLength: 9 });
], { replacementRange: [1, 10] });
});
test('folder/| should normalize current and parent folders', async () => {
test('test/| should normalize current and parent folders', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showFolders: true,
showDirectories: true,
pathSeparator
};
validResources = [
URI.parse('file:///'),
URI.parse('file:///test'),
URI.parse('file:///test/folder1'),
URI.parse('file:///test/folder2'),
URI.parse('file:///test/folder2')
];
childResources = [
{ resource: URI.parse('file:///test/folder1/'), isDirectory: true },
@@ -530,7 +523,7 @@ suite('TerminalCompletionService', () => {
{ label: './test/folder1/', detail: '/test/folder1/' },
{ label: './test/folder2/', detail: '/test/folder2/' },
{ label: './test/../', detail: '/' }
], { replacementIndex: 0, replacementLength: 5 });
], { replacementRange: [0, 5] });
});
});
@@ -553,7 +546,7 @@ suite('TerminalCompletionService', () => {
configurationService.setUserConfiguration('terminal.integrated.suggest.cdPath', 'relative');
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showFolders: true,
showDirectories: true,
showFiles: true,
pathSeparator
};
@@ -561,14 +554,14 @@ suite('TerminalCompletionService', () => {
assertPartialCompletionsExist(result, [
{ label: 'folder1', detail: 'CDPATH /cdpath_value/folder1/' },
], { replacementIndex: 3, replacementLength: 0 });
], { replacementRange: [3, 3] });
});
test('cd | should show paths from $CDPATH (absolute)', async () => {
configurationService.setUserConfiguration('terminal.integrated.suggest.cdPath', 'absolute');
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showFolders: true,
showDirectories: true,
showFiles: true,
pathSeparator
};
@@ -576,7 +569,7 @@ suite('TerminalCompletionService', () => {
assertPartialCompletionsExist(result, [
{ label: '/cdpath_value/folder1/', detail: 'CDPATH' },
], { replacementIndex: 3, replacementLength: 0 });
], { replacementRange: [3, 3] });
});
test('cd | should support pulling from multiple paths in $CDPATH', async () => {
@@ -604,7 +597,7 @@ suite('TerminalCompletionService', () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse(`${uriPathPrefix}test`),
showFolders: true,
showDirectories: true,
showFiles: true,
pathSeparator
};
@@ -616,7 +609,7 @@ suite('TerminalCompletionService', () => {
{ label: 'folder2', detail: `CDPATH ${finalPrefix}cdpath1_value/folder2/` },
{ label: 'folder1', detail: `CDPATH ${finalPrefix}cdpath2_value/inner_dir/folder1/` },
{ label: 'folder2', detail: `CDPATH ${finalPrefix}cdpath2_value/inner_dir/folder2/` },
], { replacementIndex: 3, replacementLength: 0 });
], { replacementRange: [3, 3] });
});
});
@@ -639,7 +632,7 @@ suite('TerminalCompletionService', () => {
test('resolveResources with c:/ style absolute path for Git Bash', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.file('C:\\Users\\foo'),
showFolders: true,
showDirectories: true,
showFiles: true,
pathSeparator: '/'
};
@@ -657,12 +650,12 @@ suite('TerminalCompletionService', () => {
{ label: 'C:/Users/foo/', detail: 'C:\\Users\\foo\\' },
{ label: 'C:/Users/foo/bar/', detail: 'C:\\Users\\foo\\bar\\' },
{ label: 'C:/Users/foo/baz.txt', detail: 'C:\\Users\\foo\\baz.txt', kind: TerminalCompletionItemKind.File },
], { replacementIndex: 0, replacementLength: 13 }, '/');
], { replacementRange: [0, 13] }, '/');
});
test('resolveResources with cwd as Windows path (relative)', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.file('C:\\Users\\foo'),
showFolders: true,
showDirectories: true,
showFiles: true,
pathSeparator: '/'
};
@@ -681,13 +674,13 @@ suite('TerminalCompletionService', () => {
{ label: './bar/', detail: 'C:\\Users\\foo\\bar\\' },
{ label: './baz.txt', detail: 'C:\\Users\\foo\\baz.txt', kind: TerminalCompletionItemKind.File },
{ label: './../', detail: 'C:\\Users\\' }
], { replacementIndex: 0, replacementLength: 2 }, '/');
], { replacementRange: [0, 2] }, '/');
});
test('resolveResources with cwd as Windows path (absolute)', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.file('C:\\Users\\foo'),
showFolders: true,
showDirectories: true,
showFiles: true,
pathSeparator: '/'
};
@@ -705,7 +698,7 @@ suite('TerminalCompletionService', () => {
{ label: '/c/Users/foo/', detail: 'C:\\Users\\foo\\' },
{ label: '/c/Users/foo/bar/', detail: 'C:\\Users\\foo\\bar\\' },
{ label: '/c/Users/foo/baz.txt', detail: 'C:\\Users\\foo\\baz.txt', kind: TerminalCompletionItemKind.File },
], { replacementIndex: 0, replacementLength: 13 }, '/');
], { replacementRange: [0, 13] }, '/');
});
});
}
@@ -716,7 +709,7 @@ suite('TerminalCompletionService', () => {
cwd: URI.parse('file:///test'),
pathSeparator,
showFiles: true,
showFolders: true
showDirectories: true
};
validResources = [URI.parse('file:///test')];
@@ -743,7 +736,7 @@ suite('TerminalCompletionService', () => {
test('| should escape special characters in file/folder names for POSIX shells', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showFolders: true,
showDirectories: true,
showFiles: true,
pathSeparator
};
@@ -764,7 +757,7 @@ suite('TerminalCompletionService', () => {
{ label: './\!special\$chars2\&', detail: '/test/\!special\$chars2\&', kind: TerminalCompletionItemKind.File },
{ label: '../', detail: '/' },
standardTidleItem,
], { replacementIndex: 0, replacementLength: 0 });
], { replacementRange: [0, 0] });
});
});
@@ -790,8 +783,7 @@ suite('TerminalCompletionService', () => {
provideCompletions: async () => [{
label: `completion-from-${id}`,
kind: TerminalCompletionItemKind.Method,
replacementIndex: 0,
replacementLength: 0,
replacementRange: [0, 0],
provider: id
}]
};
@@ -45,14 +45,10 @@ export interface ISimpleCompletion {
documentation?: string | IMarkdownString;
/**
* The start of the replacement.
* Replacement range (inclusive start, exclusive end) of text in the line to be replaced when
* this completion is applied.
*/
replacementIndex: number;
/**
* The length of the replacement.
*/
replacementLength: number;
replacementRange: readonly [number, number] | undefined;
}
export class SimpleCompletionItem {
@@ -112,9 +112,9 @@ export class SimpleCompletionModel<T extends SimpleCompletionItem> {
// 'word' is that remainder of the current line that we
// filter and score against. In theory each suggestion uses a
// different word, but in practice not - that's why we cache
// TODO: Fix
const overwriteBefore = item.completion.replacementLength; // item.position.column - item.editStart.column;
const wordLen = overwriteBefore + characterCountDelta; // - (item.position.column - this._column);
const overwriteBefore = item.completion.replacementRange ? (item.completion.replacementRange[1] - item.completion.replacementRange[0]) : 0;
const wordLen = overwriteBefore + characterCountDelta;
if (word.length !== wordLen) {
word = wordLen === 0 ? '' : leadingLineContent.slice(-wordLen);
wordLow = word.toLowerCase();
@@ -134,8 +134,8 @@ export class SimpleSuggestDetailsWidget {
if (explainMode) {
md += `score: ${item.score[0]}\n`;
md += `prefix: ${item.word ?? '(no prefix)'}\n`;
md += `replacementIndex: ${item.completion.replacementIndex}\n`;
md += `replacementLength: ${item.completion.replacementLength}\n`;
const vs = item.completion.replacementRange;
md += `valueSelection: ${vs ? `[${vs[0]}, ${vs[1]}]` : 'undefined'}\\n`;
md += `index: ${item.idx}\n`;
if (this._getAdvancedExplainModeDetails) {
const advancedDetails = this._getAdvancedExplainModeDetails();
+265
View File
@@ -8192,6 +8192,256 @@ declare module 'vscode' {
constructor(options: TerminalOptions | ExtensionTerminalOptions);
}
/**
* A provider that supplies terminal completion items.
*
* Implementations of this interface should return an array of {@link TerminalCompletionItem} or a
* {@link TerminalCompletionList} describing completions for the current command line.
*
* @example <caption>Simple provider returning a single completion</caption>
* window.registerTerminalCompletionProvider('extension-provider-id', {
* provideTerminalCompletions(terminal, context) {
* return [{ label: '--help', replacementRange: [Math.max(0, context.cursorPosition - 2), context.cursorPosition] }];
* }
* });
*/
export interface TerminalCompletionProvider<T extends TerminalCompletionItem> {
/**
* Provide completions for the given terminal and context.
* @param terminal The terminal for which completions are being provided.
* @param context Information about the terminal's current state.
* @param token A cancellation token.
* @return A list of completions.
*/
provideTerminalCompletions(terminal: Terminal, context: TerminalCompletionContext, token: CancellationToken): ProviderResult<T[] | TerminalCompletionList<T>>;
}
/**
* Represents a completion suggestion for a terminal command line.
*
* @example <caption>Completion item for `ls -|`</caption>
* const item = {
* label: '-A',
* replacementRange: [3, 4], // replace the single character at index 3
* detail: 'List all entries except for . and .. (always set for the super-user)',
* kind: TerminalCompletionItemKind.Flag
* };
*
* The fields on a completion item describe what text should be shown to the user
* and which portion of the command line should be replaced when the item is accepted.
*/
export class TerminalCompletionItem {
/**
* The label of the completion.
*/
label: string | CompletionItemLabel;
/**
* The range in the command line to replace when the completion is accepted. Defined
* as a tuple where the first entry is the inclusive start index and the second entry is the
* exclusive end index. When `undefined` the completion will be inserted at the cursor
* position. When the two numbers are equal only the cursor position changes (insertion).
*
*/
replacementRange: readonly [number, number];
/**
* The completion's detail which appears on the right of the list.
*/
detail?: string;
/**
* A human-readable string that represents a doc-comment.
*/
documentation?: string | MarkdownString;
/**
* The completion's kind. Note that this will map to an icon.
*/
kind?: TerminalCompletionItemKind;
/**
* Creates a new terminal completion item.
*
* @param label The label of the completion.
* @param replacementRange The inclusive start and exclusive end index of the text to replace.
* @param kind The completion's kind.
*/
constructor(
label: string | CompletionItemLabel,
replacementRange: readonly [number, number],
kind?: TerminalCompletionItemKind
);
}
/**
* The kind of an individual terminal completion item.
*
* The kind is used to render an appropriate icon in the suggest list and to convey the semantic
* meaning of the suggestion (file, folder, flag, commit, branch, etc.).
*/
export enum TerminalCompletionItemKind {
/**
* A file completion item.
* Example: `README.md`
*/
File = 0,
/**
* A folder completion item.
* Example: `src/`
*/
Folder = 1,
/**
* A method completion item.
* Example: `git commit`
*/
Method = 2,
/**
* An alias completion item.
* Example: `ll` as an alias for `ls -l`
*/
Alias = 3,
/**
* An argument completion item.
* Example: `origin` in `git push origin master`
*/
Argument = 4,
/**
* An option completion item. An option value is expected to follow.
* Example: `--locale` in `code --locale en`
*/
Option = 5,
/**
* The value of an option completion item.
* Example: `en-US` in `code --locale en-US`
*/
OptionValue = 6,
/**
* A flag completion item.
* Example: `--amend` in `git commit --amend`
*/
Flag = 7,
/**
* A symbolic link file completion item.
* Example: `link.txt` (symlink to a file)
*/
SymbolicLinkFile = 8,
/**
* A symbolic link folder completion item.
* Example: `node_modules/` (symlink to a folder)
*/
SymbolicLinkFolder = 9,
/**
* A source control commit completion item.
* Example: `abc1234` (commit hash)
*/
ScmCommit = 10,
/**
* A source control branch completion item.
* Example: `main`
*/
ScmBranch = 11,
/**
* A source control tag completion item.
* Example: `v1.0.0`
*/
ScmTag = 12,
/**
* A source control stash completion item.
* Example: `stash@{0}`
*/
ScmStash = 13,
/**
* A source control remote completion item.
* Example: `origin`
*/
ScmRemote = 14,
/**
* A pull request completion item.
* Example: `#42 Add new feature`
*/
PullRequest = 15,
/**
* A closed pull request completion item.
* Example: `#41 Fix bug (closed)`
*/
PullRequestDone = 16,
}
/**
* Context information passed to {@link TerminalCompletionProvider.provideTerminalCompletions}.
*
* It contains the full command line, the current cursor position, and a flag indicating whether
* completions were explicitly invoked.
*/
export interface TerminalCompletionContext {
/**
* The complete terminal command line.
*/
readonly commandLine: string;
/**
* The index of the cursor in the command line.
*/
readonly cursorIndex: number;
}
/**
* Represents a collection of {@link TerminalCompletionItem completion items} to be presented
* in the terminal.
*
* @example <caption>Create a completion list that requests files for the terminal cwd</caption>
* const list = new TerminalCompletionList([
* { label: 'ls', replacementRange: [0, 0], kind: TerminalCompletionItemKind.Method }
* ], { showFiles: true, cwd: Uri.file('/home/user') });
*/
export class TerminalCompletionList<T extends TerminalCompletionItem = TerminalCompletionItem> {
/**
* Resources that should be shown in the completions list for the cwd of the terminal.
*/
resourceOptions?: TerminalCompletionResourceOptions;
/**
* The completion items.
*/
items: T[];
/**
* Creates a new completion list.
*
* @param items The completion items.
* @param resourceOptions Indicates which resources should be shown as completions for the cwd of the terminal.
*/
constructor(items: T[], resourceOptions?: TerminalCompletionResourceOptions);
}
/**
* Configuration for requesting file and folder resources to be shown as completions.
*
* When a provider indicates that it wants file/folder resources, the terminal will surface completions for files and
* folders that match {@link globPattern} from the provided {@link cwd}.
*/
export interface TerminalCompletionResourceOptions {
/**
* Show files as completion items.
*/
showFiles: boolean;
/**
* Show folders as completion items.
*/
showDirectories: boolean;
/**
* A glob pattern string that controls which files suggest should surface. Note that this will only apply if {@param showFiles} or {@param showDirectories} is set to true.
*/
globPattern?: string;
/**
* The cwd from which to request resources.
*/
cwd: Uri;
}
/**
* A file decoration represents metadata that can be rendered with a file.
*/
@@ -11768,6 +12018,21 @@ declare module 'vscode' {
* @returns A {@link Disposable disposable} that unregisters the provider.
*/
export function registerTerminalProfileProvider(id: string, provider: TerminalProfileProvider): Disposable;
/**
* Register a completion provider for terminals.
* @param provider The completion provider.
* @returns A {@link Disposable} that unregisters this provider when being disposed.
*
* @example <caption>Register a provider for an extension</caption>
* window.registerTerminalCompletionProvider('extension-provider-id', {
* provideTerminalCompletions(terminal, context) {
* return new TerminalCompletionList([
* { label: '--version', replacementRange: [Math.max(0, context.cursorPosition - 2), 2] }
* ]);
* }
* });
*/
export function registerTerminalCompletionProvider<T extends TerminalCompletionItem>(provider: TerminalCompletionProvider<T>, ...triggerCharacters: string[]): Disposable;
/**
* Register a file decoration provider.
*
@@ -1,218 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'vscode' {
// https://github.com/microsoft/vscode/issues/226562
/**
* A provider that supplies terminal completion items.
*
* Implementations of this interface should return an array of {@link TerminalCompletionItem} or a
* {@link TerminalCompletionList} describing completions for the current command line.
*
* @example <caption>Simple provider returning a single completion</caption>
* window.registerTerminalCompletionProvider('extension-provider-id', {
* provideTerminalCompletions(terminal, context) {
* return [{ label: '--help', replacementIndex: Math.max(0, context.cursorPosition - 2), replacementLength: 2 }];
* }
* });
*/
export interface TerminalCompletionProvider<T extends TerminalCompletionItem> {
/**
* Provide completions for the given terminal and context.
* @param terminal The terminal for which completions are being provided.
* @param context Information about the terminal's current state.
* @param token A cancellation token.
* @return A list of completions.
*/
provideTerminalCompletions(terminal: Terminal, context: TerminalCompletionContext, token: CancellationToken): ProviderResult<T[] | TerminalCompletionList<T>>;
}
/**
* Represents a completion suggestion for a terminal command line.
*
* @example <caption>Completion item for `ls -|`</caption>
* const item = {
* label: '-A',
* replacementIndex: 3,
* replacementLength: 1,
* detail: 'List all entries except for . and .. (always set for the super-user)',
* kind: TerminalCompletionItemKind.Flag
* };
*
* The fields on a completion item describe what text should be shown to the user
* and which portion of the command line should be replaced when the item is accepted.
*/
export interface TerminalCompletionItem {
/**
* The label of the completion.
*/
label: string | CompletionItemLabel;
/**
* The index of the start of the range to replace.
*/
replacementIndex: number;
/**
* The length of the range to replace.
*/
replacementLength: number;
/**
* The completion's detail which appears on the right of the list.
*/
detail?: string;
/**
* A human-readable string that represents a doc-comment.
*/
documentation?: string | MarkdownString;
/**
* The completion's kind. Note that this will map to an icon.
*/
kind?: TerminalCompletionItemKind;
}
/**
* The kind of an individual terminal completion item.
*
* The kind is used to render an appropriate icon in the suggest list and to convey the semantic
* meaning of the suggestion (file, folder, flag, commit, branch, etc.).
*
*/
export enum TerminalCompletionItemKind {
File = 0,
Folder = 1,
Method = 2,
Alias = 3,
Argument = 4,
/**
* An option, for example in `code --locale`, `--locale` is the option
*/
Option = 5,
/**
* The value of an option, for example in `code --locale en-US`, `en-US` is the option value
*/
OptionValue = 6,
/**
* A flag, for example in `git commit --amend"`, `--amend` is the flag
*/
Flag = 7,
SymbolicLinkFile = 8,
SymbolicLinkFolder = 9,
ScmCommit = 10,
ScmBranch = 11,
ScmTag = 12,
ScmStash = 13,
ScmRemote = 14,
PullRequest = 15,
/**
* The pull request has been closed
*/
PullRequestDone = 16,
}
/**
* Context information passed to {@link TerminalCompletionProvider.provideTerminalCompletions}.
*
* It contains the full command line, the current cursor position, and a flag indicating whether
* fallback completions are allowed when the exact completion type cannot be determined.
*/
export interface TerminalCompletionContext {
/**
* The complete terminal command line.
*/
readonly commandLine: string;
/**
* The index of the cursor in the command line.
*/
readonly cursorIndex: number;
/**
* Whether completions should be provided when none are explicitly suggested. This will display
* fallback suggestions like files and folders.
*/
readonly allowFallbackCompletions: boolean;
}
export namespace window {
/**
* Register a completion provider for terminals.
* @param provider The completion provider.
* @returns A {@link Disposable} that unregisters this provider when being disposed.
*
* @example <caption>Register a provider for an extension</caption>
* window.registerTerminalCompletionProvider('extension-provider-id', {
* provideTerminalCompletions(terminal, context) {
* return new TerminalCompletionList([
* { label: '--version', replacementIndex: Math.max(0, context.cursorPosition - 2), replacementLength: 2 }
* ]);
* }
* });
*/
export function registerTerminalCompletionProvider<T extends TerminalCompletionItem>(provider: TerminalCompletionProvider<T>, ...triggerCharacters: string[]): Disposable;
}
/**
* Represents a collection of {@link TerminalCompletionItem completion items} to be presented
* in the terminal.
*
* @example <caption>Create a completion list that requests files for the terminal cwd</caption>
* const list = new TerminalCompletionList([
* { label: 'ls', replacementIndex: 0, replacementLength: 0, kind: TerminalCompletionItemKind.Method }
* ], { showFiles: true, cwd: Uri.file('/home/user') });
*/
export class TerminalCompletionList<T extends TerminalCompletionItem = TerminalCompletionItem> {
/**
* Resources that should be shown in the completions list for the cwd of the terminal.
*/
resourceOptions?: TerminalCompletionResourceOptions;
/**
* The completion items.
*/
items: T[];
/**
* Creates a new completion list.
*
* @param items The completion items.
* @param resourceOptions Indicates which resources should be shown as completions for the cwd of the terminal.
*/
constructor(items: T[], resourceOptions?: TerminalCompletionResourceOptions);
}
/**
* Configuration for requesting file and folder resources to be shown as completions.
*
* When a provider indicates that it wants file/folder resources, the terminal will surface completions for files and
* folders that match {@link globPattern} from the provided {@link cwd}.
*/
export interface TerminalCompletionResourceOptions {
/**
* Show files as completion items.
*/
showFiles?: boolean;
/**
* Show folders as completion items.
*/
showDirectories?: boolean;
/**
* A glob pattern string that controls which files suggest should surface. Note that this will only apply if {@param showFiles} or {@param showDirectories} is set to true.
*/
globPattern?: string;
/**
* The cwd from which to request resources.
*/
cwd: Uri;
}
}