diff --git a/extensions/terminal-suggest/src/helpers/completionItem.ts b/extensions/terminal-suggest/src/helpers/completionItem.ts index 7cb174223c1..b5dba9a2874 100644 --- a/extensions/terminal-suggest/src/helpers/completionItem.ts +++ b/extensions/terminal-suggest/src/helpers/completionItem.ts @@ -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 }; } diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index e075bba947d..7ecc8affa05 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -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 { diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index e60099c6089..cb4cbbddd5d 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -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, diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index cae07b0fd1b..79e8cfa17c0 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -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', }, diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 533e27a5d6e..28ed1215289 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.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; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 67a604b3816..d3482f995c8 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -878,7 +878,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostTerminalService.registerProfileProvider(extension, id, provider); }, registerTerminalCompletionProvider(provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable { - checkProposedApiEnabled(extension, 'terminalCompletionProvider'); return extHostTerminalService.registerTerminalCompletionProvider(extension, provider, ...triggerCharacters); }, registerTerminalQuickFixProvider(id: string, provider: vscode.TerminalQuickFixProvider): vscode.Disposable { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c764273e963..f640cf8e4a5 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -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 { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 0d99b23840c..dda4138c9e3 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -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; } } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/lspCompletionProviderAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/lspCompletionProviderAddon.ts index 8c02694f332..710fd39ddfc 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/lspCompletionProviderAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/lspCompletionProviderAddon.ts @@ -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 | undefined> { + async provideCompletions(value: string, cursorPosition: number, token: CancellationToken): Promise | 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. diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index dce6f24dbe7..270bd346951 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -55,7 +55,7 @@ export class TerminalCompletionList { 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 | undefined>; + provideCompletions(value: string, cursorPosition: number, token: CancellationToken): Promise | 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] }); } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index 567624f5a2c..1981a275da2 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -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 = ''; diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts index 8630afb6430..c0db8abfb87 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts @@ -15,8 +15,7 @@ function createItem(options: Partial): 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 }) ]; diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index 27a681094ae..92eff34116c 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -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 }] }; diff --git a/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts b/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts index 05ce1746f1b..a97a5338e63 100644 --- a/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts +++ b/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts @@ -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 { diff --git a/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts b/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts index 90d1342b597..0e13e0c2636 100644 --- a/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts +++ b/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts @@ -112,9 +112,9 @@ export class SimpleCompletionModel { // '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(); diff --git a/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts b/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts index 214dfb4c047..e45ec4fbd70 100644 --- a/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts +++ b/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts @@ -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(); diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index ff8d57f61ec..a6c205d280c 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -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 Simple provider returning a single completion + * window.registerTerminalCompletionProvider('extension-provider-id', { + * provideTerminalCompletions(terminal, context) { + * return [{ label: '--help', replacementRange: [Math.max(0, context.cursorPosition - 2), context.cursorPosition] }]; + * } + * }); + */ + export interface TerminalCompletionProvider { + /** + * 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>; + } + + + /** + * Represents a completion suggestion for a terminal command line. + * + * @example Completion item for `ls -|` + * 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 Create a completion list that requests files for the terminal cwd + * const list = new TerminalCompletionList([ + * { label: 'ls', replacementRange: [0, 0], kind: TerminalCompletionItemKind.Method } + * ], { showFiles: true, cwd: Uri.file('/home/user') }); + */ + export class TerminalCompletionList { + + /** + * 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 Register a provider for an extension + * window.registerTerminalCompletionProvider('extension-provider-id', { + * provideTerminalCompletions(terminal, context) { + * return new TerminalCompletionList([ + * { label: '--version', replacementRange: [Math.max(0, context.cursorPosition - 2), 2] } + * ]); + * } + * }); + */ + export function registerTerminalCompletionProvider(provider: TerminalCompletionProvider, ...triggerCharacters: string[]): Disposable; /** * Register a file decoration provider. * diff --git a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts deleted file mode 100644 index 4b3836f7a16..00000000000 --- a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts +++ /dev/null @@ -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 Simple provider returning a single completion - * window.registerTerminalCompletionProvider('extension-provider-id', { - * provideTerminalCompletions(terminal, context) { - * return [{ label: '--help', replacementIndex: Math.max(0, context.cursorPosition - 2), replacementLength: 2 }]; - * } - * }); - */ - export interface TerminalCompletionProvider { - /** - * 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>; - } - - - /** - * Represents a completion suggestion for a terminal command line. - * - * @example Completion item for `ls -|` - * 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 Register a provider for an extension - * 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(provider: TerminalCompletionProvider, ...triggerCharacters: string[]): Disposable; - } - - /** - * Represents a collection of {@link TerminalCompletionItem completion items} to be presented - * in the terminal. - * - * @example Create a completion list that requests files for the terminal cwd - * const list = new TerminalCompletionList([ - * { label: 'ls', replacementIndex: 0, replacementLength: 0, kind: TerminalCompletionItemKind.Method } - * ], { showFiles: true, cwd: Uri.file('/home/user') }); - */ - export class TerminalCompletionList { - - /** - * 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; - } -}