diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 3e7dc44d2c5..a8d537334e7 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -20,11 +20,13 @@ function getBuiltinCommands(shell: string): string[] | undefined { if (cachedCommands) { return cachedCommands; } + // fixes a bug with file/folder completions brought about by the '.' command + const filter = (cmd: string) => cmd && cmd !== '.'; const options: ExecOptionsWithStringEncoding = { encoding: 'utf-8', shell }; switch (shellType) { case 'bash': { const bashOutput = execSync('compgen -b', options); - const bashResult = bashOutput.split('\n').filter(cmd => cmd); + const bashResult = bashOutput.split('\n').filter(filter); if (bashResult.length) { cachedBuiltinCommands?.set(shellType, bashResult); return bashResult; @@ -33,7 +35,7 @@ function getBuiltinCommands(shell: string): string[] | undefined { } case 'zsh': { const zshOutput = execSync('printf "%s\\n" ${(k)builtins}', options); - const zshResult = zshOutput.split('\n').filter(cmd => cmd); + const zshResult = zshOutput.split('\n').filter(filter); if (zshResult.length) { cachedBuiltinCommands?.set(shellType, zshResult); return zshResult; @@ -43,7 +45,7 @@ function getBuiltinCommands(shell: string): string[] | undefined { // TODO: ghost text in the command line prevents // completions from working ATM for fish const fishOutput = execSync('functions -n', options); - const fishResult = fishOutput.split(', ').filter(cmd => cmd); + const fishResult = fishOutput.split(', ').filter(filter); if (fishResult.length) { cachedBuiltinCommands?.set(shellType, fishResult); return fishResult; @@ -64,122 +66,81 @@ function getBuiltinCommands(shell: string): string[] | undefined { export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.window.registerTerminalCompletionProvider({ id: 'terminal-suggest', - async provideTerminalCompletions(terminal: vscode.Terminal, terminalContext: { commandLine: string; cursorPosition: number }, token: vscode.CancellationToken): Promise { + async provideTerminalCompletions(terminal: vscode.Terminal, terminalContext: { commandLine: string; cursorPosition: number }, token: vscode.CancellationToken): Promise { if (token.isCancellationRequested) { return; } - const availableCommands = await getCommandsInPath(); - if (!availableCommands) { - return; - } - // TODO: Leverage shellType when available https://github.com/microsoft/vscode/issues/230165 const shellPath = 'shellPath' in terminal.creationOptions ? terminal.creationOptions.shellPath : vscode.env.shell; if (!shellPath) { return; } + const commandsInPath = await getCommandsInPath(); const builtinCommands = getBuiltinCommands(shellPath); - builtinCommands?.forEach(command => availableCommands.add(command)); - - const prefix = getPrefix(terminalContext.commandLine, terminalContext.cursorPosition); - let result: vscode.TerminalCompletionItem[] = []; - const specs = [codeCompletionSpec, codeInsidersCompletionSpec]; - for (const spec of specs) { - const specName = getLabel(spec); - if (!specName || !availableCommands.has(specName)) { - continue; - } - if (terminalContext.commandLine.startsWith(specName)) { - if ('options' in codeInsidersCompletionSpec && codeInsidersCompletionSpec.options) { - for (const option of codeInsidersCompletionSpec.options) { - const optionLabel = getLabel(option); - if (!optionLabel) { - continue; - } - - if (optionLabel.startsWith(prefix) || (prefix.length > specName.length && prefix.trim() === specName)) { - result.push(createCompletionItem(terminalContext.cursorPosition, prefix, optionLabel, option.description, false, vscode.TerminalCompletionItemKind.Flag)); - } - if (option.args !== undefined) { - const args = Array.isArray(option.args) ? option.args : [option.args]; - for (const arg of args) { - if (!arg) { - continue; - } - - if (arg.template) { - // TODO: return file/folder completion items - if (arg.template === 'filepaths') { - // if (label.startsWith(prefix+\s*)) { - // result.push(FilePathCompletionItem) - // } - } else if (arg.template === 'folders') { - // if (label.startsWith(prefix+\s*)) { - // result.push(FolderPathCompletionItem) - // } - } - continue; - } - - const precedingText = terminalContext.commandLine.slice(0, terminalContext.cursorPosition); - const expectedText = `${optionLabel} `; - if (arg.suggestions?.length && precedingText.includes(expectedText)) { - // there are specific suggestions to show - result = []; - const indexOfPrecedingText = terminalContext.commandLine.lastIndexOf(expectedText); - const currentPrefix = precedingText.slice(indexOfPrecedingText + expectedText.length); - for (const suggestion of arg.suggestions) { - const suggestionLabel = getLabel(suggestion); - if (suggestionLabel && suggestionLabel.startsWith(currentPrefix)) { - const hasSpaceBeforeCursor = terminalContext.commandLine[terminalContext.cursorPosition - 1] === ' '; - // prefix will be '' if there is a space before the cursor - result.push(createCompletionItem(terminalContext.cursorPosition, precedingText, suggestionLabel, arg.name, hasSpaceBeforeCursor, vscode.TerminalCompletionItemKind.Argument)); - } - } - if (result.length) { - return result; - } - } - } - } - } - } - } + if (!commandsInPath || !builtinCommands) { + return; } + const commands = [...commandsInPath, ...builtinCommands]; - for (const command of availableCommands) { - if (command.startsWith(prefix)) { - result.push(createCompletionItem(terminalContext.cursorPosition, prefix, command)); + const items: vscode.TerminalCompletionItem[] = []; + const prefix = getPrefix(terminalContext.commandLine, terminalContext.cursorPosition); + + const specs = [codeCompletionSpec, codeInsidersCompletionSpec]; + const specCompletions = await getCompletionItemsFromSpecs(specs, terminalContext, new Set(commands), prefix, token); + + let filesRequested = specCompletions.filesRequested; + let foldersRequested = specCompletions.foldersRequested; + items.push(...specCompletions.items); + + if (!specCompletions.specificSuggestionsProvided) { + for (const command of commands) { + if (command.startsWith(prefix)) { + items.push(createCompletionItem(terminalContext.cursorPosition, prefix, command)); + } } } if (token.isCancellationRequested) { return undefined; } + const uniqueResults = new Map(); - for (const item of result) { + for (const item of items) { if (!uniqueResults.has(item.label)) { uniqueResults.set(item.label, item); } } - return uniqueResults.size ? Array.from(uniqueResults.values()) : undefined; + const resultItems = uniqueResults.size ? Array.from(uniqueResults.values()) : undefined; + + // If no completions are found, the prefix is a path, and neither files nor folders + // are going to be requested (for a specific spec's argument), show file/folder completions + const shouldShowResourceCompletions = !resultItems?.length && prefix.match(/^[./\\ ]/) && !filesRequested && !foldersRequested; + if (shouldShowResourceCompletions) { + filesRequested = true; + foldersRequested = true; + } + + if (filesRequested || foldersRequested) { + return new vscode.TerminalCompletionList(resultItems, { filesRequested, foldersRequested, cwd: terminal.shellIntegration?.cwd, pathSeparator: shellPath.includes('/') ? '/' : '\\' }); + } + return resultItems; } })); } -function getLabel(spec: Fig.Spec | Fig.Arg | Fig.Suggestion | string): string | undefined { +function getLabel(spec: Fig.Spec | Fig.Arg | Fig.Suggestion | string): string[] | undefined { if (typeof spec === 'string') { - return spec; + return [spec]; } if (typeof spec.name === 'string') { - return spec.name; + return [spec.name]; } if (!Array.isArray(spec.name) || spec.name.length === 0) { return; } - return spec.name[0]; + return spec.name; } function createCompletionItem(cursorPosition: number, prefix: string, label: string, description?: string, hasSpaceBeforeCursor?: boolean, kind?: vscode.TerminalCompletionItemKind): vscode.TerminalCompletionItem { @@ -245,3 +206,89 @@ function getPrefix(commandLine: string, cursorPosition: number): string { return match ? match[0] : ''; } +export function asArray(x: T | T[]): T[]; +export function asArray(x: T | readonly T[]): readonly T[]; +export function asArray(x: T | T[]): T[] { + return Array.isArray(x) ? x : [x]; +} + +function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalContext: { commandLine: string; cursorPosition: number }, availableCommands: Set, prefix: string, token: vscode.CancellationToken): { items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; specificSuggestionsProvided: boolean } { + let items: vscode.TerminalCompletionItem[] = []; + let filesRequested = false; + let foldersRequested = false; + for (const spec of specs) { + const specLabels = getLabel(spec); + if (!specLabels) { + continue; + } + for (const specLabel of specLabels) { + if (!availableCommands.has(specLabel) || token.isCancellationRequested) { + continue; + } + if (terminalContext.commandLine.startsWith(specLabel)) { + if ('options' in spec && spec.options) { + for (const option of spec.options) { + const optionLabels = getLabel(option); + if (!optionLabels) { + continue; + } + for (const optionLabel of optionLabels) { + if (optionLabel.startsWith(prefix) || (prefix.length > specLabel.length && prefix.trim() === specLabel)) { + items.push(createCompletionItem(terminalContext.cursorPosition, prefix, optionLabel, option.description, false, vscode.TerminalCompletionItemKind.Flag)); + } + if (!option.args) { + continue; + } + const args = asArray(option.args); + for (const arg of args) { + if (!arg) { + continue; + } + const precedingText = terminalContext.commandLine.slice(0, terminalContext.cursorPosition + 1); + const expectedText = `${specLabel} ${optionLabel} `; + if (!precedingText.includes(expectedText)) { + continue; + } + if (arg.template) { + if (arg.template === 'filepaths') { + if (precedingText.includes(expectedText)) { + filesRequested = true; + } + } else if (arg.template === 'folders') { + if (precedingText.includes(expectedText)) { + foldersRequested = true; + } + } + } + if (arg.suggestions?.length) { + // there are specific suggestions to show + items = []; + const indexOfPrecedingText = terminalContext.commandLine.lastIndexOf(expectedText); + const currentPrefix = precedingText.slice(indexOfPrecedingText + expectedText.length); + for (const suggestion of arg.suggestions) { + const suggestionLabels = getLabel(suggestion); + if (!suggestionLabels) { + continue; + } + for (const suggestionLabel of suggestionLabels) { + if (suggestionLabel && suggestionLabel.startsWith(currentPrefix.trim())) { + const hasSpaceBeforeCursor = terminalContext.commandLine[terminalContext.cursorPosition - 1] === ' '; + // prefix will be '' if there is a space before the cursor + items.push(createCompletionItem(terminalContext.cursorPosition, precedingText, suggestionLabel, arg.name, hasSpaceBeforeCursor, vscode.TerminalCompletionItemKind.Argument)); + } + } + } + if (items.length) { + return { items, filesRequested, foldersRequested, specificSuggestionsProvided: true }; + } + } + } + } + } + } + } + } + } + return { items, filesRequested, foldersRequested, specificSuggestionsProvided: false }; +} + diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 2f20db169f7..8d2a972f62b 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1667,6 +1667,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I TerminalShellExecutionCommandLineConfidence: extHostTypes.TerminalShellExecutionCommandLineConfidence, TerminalCompletionItem: extHostTypes.TerminalCompletionItem, TerminalCompletionItemKind: extHostTypes.TerminalCompletionItemKind, + TerminalCompletionList: extHostTypes.TerminalCompletionList, TextDocumentSaveReason: extHostTypes.TextDocumentSaveReason, TextEdit: extHostTypes.TextEdit, SnippetTextEdit: extHostTypes.SnippetTextEdit, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 2ddf94fa6b8..d57fd4541a7 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -84,7 +84,7 @@ import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from '../../servic import * as search from '../../services/search/common/search.js'; import { TextSearchCompleteMessage } from '../../services/search/common/searchExtTypes.js'; import { ISaveProfileResult } from '../../services/userDataProfile/common/userDataProfile.js'; -import { TerminalCompletionItem, TerminalShellExecutionCommandLineConfidence } from './extHostTypes.js'; +import { TerminalCompletionItem, TerminalCompletionList, TerminalShellExecutionCommandLineConfidence } from './extHostTypes.js'; import * as tasks from './shared/tasks.js'; export interface IWorkspaceData extends IStaticWorkspaceData { @@ -2430,7 +2430,7 @@ export interface ExtHostTerminalServiceShape { $acceptDefaultProfile(profile: ITerminalProfile, automationProfile: ITerminalProfile): void; $createContributedProfileTerminal(id: string, options: ICreateContributedTerminalProfileOptions): Promise; $provideTerminalQuickFixes(id: string, matchResult: TerminalCommandMatchResultDto, token: CancellationToken): Promise | undefined>; - $provideTerminalCompletions(id: string, options: ITerminalCompletionContextDto, token: CancellationToken): Promise; + $provideTerminalCompletions(id: string, options: ITerminalCompletionContextDto, token: CancellationToken): Promise; } export interface ExtHostTerminalShellIntegrationShape { diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 0ee84d5a859..907fb7125e5 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -56,7 +56,7 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID getEnvironmentVariableCollection(extension: IExtensionDescription): IEnvironmentVariableCollection; getTerminalById(id: number): ExtHostTerminal | null; getTerminalIdByApiObject(apiTerminal: vscode.Terminal): number | null; - registerTerminalCompletionProvider(extension: IExtensionDescription, provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable; + registerTerminalCompletionProvider(extension: IExtensionDescription, provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable; } interface IEnvironmentVariableCollection extends vscode.EnvironmentVariableCollection { @@ -746,7 +746,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I }); } - public async $provideTerminalCompletions(id: string, options: ITerminalCompletionContextDto): Promise { + public async $provideTerminalCompletions(id: string, options: ITerminalCompletionContextDto): Promise { const token = new CancellationTokenSource().token; if (token.isCancellationRequested || !this.activeTerminal) { return undefined; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index a3f704841bb..35082a1e9e9 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2145,6 +2145,41 @@ export class TerminalCompletionItem implements vscode.TerminalCompletionItem { } +/** + * Represents a collection of {@link CompletionItem completion items} to be presented + * in the editor. + */ +export class TerminalCompletionList { + + /** + * Resources should be shown in the completions list + */ + resourceRequestConfig?: TerminalResourceRequestConfig; + + /** + * The completion items. + */ + items: T[]; + + /** + * Creates a new completion list. + * + * @param items The completion items. + * @param isIncomplete The list is not complete. + */ + constructor(items?: T[], resourceRequestConfig?: TerminalResourceRequestConfig) { + this.items = items ?? []; + this.resourceRequestConfig = resourceRequestConfig; + } +} + +export interface TerminalResourceRequestConfig { + filesRequested?: boolean; + foldersRequested?: boolean; + cwd?: vscode.Uri; + pathSeparator: string; +} + export enum TaskRevealKind { Always = 1, diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index ead61540bf2..4e8379c847f 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Disposable, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; +import { URI } from '../../../../../base/common/uri.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IFileService } from '../../../../../platform/files/common/files.js'; import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js'; import { TerminalSettingId, TerminalShellType } from '../../../../../platform/terminal/common/terminal.js'; import { ISimpleCompletion } from '../../../../services/suggest/browser/simpleCompletionItem.js'; @@ -24,10 +26,47 @@ export interface ITerminalCompletion extends ISimpleCompletion { kind?: TerminalCompletionItemKind; } + +/** + * Represents a collection of {@link CompletionItem completion items} to be presented + * in the editor. + */ +export class TerminalCompletionList { + + /** + * Resources should be shown in the completions list + */ + resourceRequestConfig?: TerminalResourceRequestConfig; + + /** + * The completion items. + */ + items?: ITerminalCompletion[]; + + /** + * Creates a new completion list. + * + * @param items The completion items. + * @param isIncomplete The list is not complete. + */ + constructor(items?: ITerminalCompletion[], resourceRequestConfig?: TerminalResourceRequestConfig) { + this.items = items; + this.resourceRequestConfig = resourceRequestConfig; + } +} + +export interface TerminalResourceRequestConfig { + filesRequested?: boolean; + foldersRequested?: boolean; + cwd?: URI; + pathSeparator: string; +} + + export interface ITerminalCompletionProvider { id: string; shellTypes?: TerminalShellType[]; - provideCompletions(value: string, cursorPosition: number, token: CancellationToken): Promise; + provideCompletions(value: string, cursorPosition: number, token: CancellationToken): Promise | undefined>; triggerCharacters?: string[]; isBuiltin?: boolean; } @@ -55,7 +94,9 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo } } - constructor(@IConfigurationService private readonly _configurationService: IConfigurationService) { + constructor(@IConfigurationService private readonly _configurationService: IConfigurationService, + @IFileService private readonly _fileService: IFileService + ) { super(); } @@ -79,9 +120,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo }); } - async provideCompletions(promptValue: string, cursorPosition: number, shellType: TerminalShellType, token: CancellationToken, triggerCharacter?: boolean): Promise { - const completionItems: ISimpleCompletion[] = []; - + async provideCompletions(promptValue: string, cursorPosition: number, shellType: TerminalShellType, token: CancellationToken, triggerCharacter?: boolean): Promise { if (!this._providers || !this._providers.values) { return undefined; } @@ -110,31 +149,93 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo providers = providers.filter(p => p.isBuiltin); } - await this._collectCompletions(providers, shellType, promptValue, cursorPosition, completionItems, token); - return completionItems.length > 0 ? completionItems : undefined; + return this._collectCompletions(providers, shellType, promptValue, cursorPosition, token); } - private async _collectCompletions(providers: ITerminalCompletionProvider[], shellType: TerminalShellType, promptValue: string, cursorPosition: number, completionItems: ISimpleCompletion[], token: CancellationToken) { + private async _collectCompletions(providers: ITerminalCompletionProvider[], shellType: TerminalShellType, promptValue: string, cursorPosition: number, token: CancellationToken): Promise { const completionPromises = providers.map(async provider => { if (provider.shellTypes && !provider.shellTypes.includes(shellType)) { - return []; + return undefined; + } + const completions: ITerminalCompletion[] | TerminalCompletionList | undefined = await provider.provideCompletions(promptValue, cursorPosition, token); + if (!completions) { + return undefined; } - const completions = await provider.provideCompletions(promptValue, cursorPosition, token); const devModeEnabled = this._configurationService.getValue(TerminalSettingId.DevMode); - if (completions) { - return completions.map(completion => { - if (devModeEnabled && !completion.detail?.includes(provider.id)) { - completion.detail = `(${provider.id}) ${completion.detail ?? ''}`; - } - return completion; - }); + const completionItems = Array.isArray(completions) ? completions : completions.items ?? []; + + const itemsWithModifiedLabels = completionItems.map(completion => { + if (devModeEnabled && !completion.detail?.includes(provider.id)) { + completion.detail = `(${provider.id}) ${completion.detail ?? ''}`; + } + return completion; + }); + + if (Array.isArray(completions)) { + return itemsWithModifiedLabels; } - return []; + if (completions.resourceRequestConfig) { + const resourceCompletions = await this._resolveResources(completions.resourceRequestConfig, promptValue, cursorPosition); + if (resourceCompletions) { + itemsWithModifiedLabels.push(...resourceCompletions); + } + return itemsWithModifiedLabels; + } + return; }); const results = await Promise.all(completionPromises); - results.forEach(completions => completionItems.push(...completions)); + return results.filter(result => !!result).flat(); + } + + private async _resolveResources(resourceRequestConfig: TerminalResourceRequestConfig, promptValue: string, cursorPosition: number): Promise { + const cwd = URI.revive(resourceRequestConfig.cwd); + const foldersRequested = resourceRequestConfig.foldersRequested ?? false; + const filesRequested = resourceRequestConfig.filesRequested ?? false; + if (!cwd || (!foldersRequested && !filesRequested)) { + return; + } + + const resourceCompletions: ITerminalCompletion[] = []; + const fileStat = await this._fileService.resolve(cwd, { resolveSingleChildDescendants: true }); + + if (!fileStat || !fileStat?.children) { + return; + } + + for (const stat of fileStat.children) { + let kind: TerminalCompletionItemKind | undefined; + if (foldersRequested && stat.isDirectory) { + kind = TerminalCompletionItemKind.Folder; + } + if (filesRequested && !stat.isDirectory && (stat.isFile || stat.resource.scheme === 'file')) { + kind = TerminalCompletionItemKind.File; + } + if (kind === undefined) { + continue; + } + const lastWord = promptValue.substring(0, cursorPosition).split(' ').pop(); + const lastIndexOfDot = lastWord?.lastIndexOf('.') ?? -1; + const lastIndexOfSlash = lastWord?.lastIndexOf(resourceRequestConfig.pathSeparator) ?? -1; + let label; + if (lastIndexOfSlash > -1) { + label = stat.resource.fsPath.replace(cwd.fsPath, '').substring(1); + } else if (lastIndexOfDot === -1) { + label = '.' + stat.resource.fsPath.replace(cwd.fsPath, ''); + } else { + label = stat.resource.fsPath.replace(cwd.fsPath, ''); + } + + resourceCompletions.push({ + label, + kind, + isDirectory: kind === TerminalCompletionItemKind.Folder, + isFile: kind === TerminalCompletionItemKind.File, + replacementIndex: cursorPosition, + replacementLength: label.length + }); + } + return resourceCompletions.length ? resourceCompletions : undefined; } } - diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index 8eef1f305b3..8e9dd3c793b 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -202,7 +202,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest normalizedLeadingLineContent = normalizePathSeparator(normalizedLeadingLineContent, this._pathSeparator); } for (const completion of completions) { - if (!completion.icon && completion.kind) { + if (!completion.icon && completion.kind !== undefined) { completion.icon = this._kindToIconMap.get(completion.kind); } } diff --git a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts index 69dc907809b..095fdcc867a 100644 --- a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts @@ -16,10 +16,9 @@ declare module 'vscode' { * @param token A cancellation token. * @return A list of completions. */ - provideTerminalCompletions(terminal: Terminal, context: TerminalCompletionContext, token: CancellationToken): ProviderResult; + provideTerminalCompletions(terminal: Terminal, context: TerminalCompletionContext, token: CancellationToken): ProviderResult>; } - export interface TerminalCompletionItem { /** * The label of the completion. @@ -80,4 +79,48 @@ declare module 'vscode' { */ export function registerTerminalCompletionProvider(provider: TerminalCompletionProvider, ...triggerCharacters: string[]): Disposable; } + + /** + * Represents a collection of {@link TerminalCompletionItem completion items} to be presented + * in the terminal. + */ + export class TerminalCompletionList { + + /** + * Resources that should be shown in the completions list for the cwd of the terminal. + */ + resourceRequestConfig?: TerminalResourceRequestConfig; + + /** + * The completion items. + */ + items: T[]; + + /** + * Creates a new completion list. + * + * @param items The completion items. + * @param resourceRequestConfig Indicates which resources should be shown as completions for the cwd of the terminal. + */ + constructor(items?: T[], resourceRequestConfig?: TerminalResourceRequestConfig); + } + + export interface TerminalResourceRequestConfig { + /** + * Show files as completion items. + */ + filesRequested?: boolean; + /** + * Show folders as completion items. + */ + foldersRequested?: boolean; + /** + * If no cwd is provided, no resources will be shown as completions. + */ + cwd?: Uri; + /** + * The path separator to use when constructing paths. + */ + pathSeparator: string; + } }