From eefe48435177499a9e92a28d45ddb30c64324e03 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 24 Jan 2025 16:24:25 -0600 Subject: [PATCH] Add `~/` completion (#238727) part of #234352 --- .../src/terminalSuggestMain.ts | 21 +++++++++++++++---- .../browser/terminalCompletionService.ts | 19 +++++++++++++++++ .../browser/terminalCompletionService.test.ts | 14 +++++++++++++ ...e.proposed.terminalCompletionProvider.d.ts | 4 ++++ 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index b0511a31985..6824fbb2e24 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -135,10 +135,18 @@ export async function activate(context: vscode.ExtensionContext) { const commands = [...commandsInPath.completionResources, ...builtinCommands]; const prefix = getPrefix(terminalContext.commandLine, terminalContext.cursorPosition); - + const pathSeparator = isWindows ? '\\' : '/'; const result = await getCompletionItemsFromSpecs(availableSpecs, terminalContext, commands, prefix, terminal.shellIntegration?.cwd, token); + if (terminal.shellIntegration?.env) { + const homeDirCompletion = result.items.find(i => i.label === '~'); + if (homeDirCompletion && terminal.shellIntegration.env.HOME) { + homeDirCompletion.documentation = getFriendlyResourcePath(vscode.Uri.file(terminal.shellIntegration.env.HOME), pathSeparator, vscode.TerminalCompletionItemKind.Folder); + homeDirCompletion.kind = vscode.TerminalCompletionItemKind.Folder; + } + } + if (result.cwd && (result.filesRequested || result.foldersRequested)) { - return new vscode.TerminalCompletionList(result.items, { filesRequested: result.filesRequested, foldersRequested: result.foldersRequested, cwd: result.cwd, pathSeparator: isWindows ? '\\' : '/' }); + return new vscode.TerminalCompletionList(result.items, { filesRequested: result.filesRequested, foldersRequested: result.foldersRequested, cwd: result.cwd, pathSeparator: isWindows ? '\\' : '/', env: terminal.shellIntegration?.env }); } return result.items; } @@ -269,7 +277,7 @@ async function getCommandsInPath(env: { [key: string]: string | undefined } = pr const fileResource = vscode.Uri.file(path); const files = await vscode.workspace.fs.readDirectory(fileResource); for (const [file, fileType] of files) { - const formattedPath = getFriendlyFilePath(vscode.Uri.joinPath(fileResource, file), pathSeparator); + const formattedPath = getFriendlyResourcePath(vscode.Uri.joinPath(fileResource, file), pathSeparator); if (!labels.has(file) && fileType !== vscode.FileType.Unknown && fileType !== vscode.FileType.Directory && await isExecutable(formattedPath, cachedWindowsExecutableExtensions)) { executables.add({ label: file, detail: formattedPath }); labels.add(file); @@ -539,12 +547,17 @@ function getFirstCommand(commandLine: string): string | undefined { return firstCommand; } -function getFriendlyFilePath(uri: vscode.Uri, pathSeparator: string): string { +function getFriendlyResourcePath(uri: vscode.Uri, pathSeparator: string, kind?: vscode.TerminalCompletionItemKind): string { let path = uri.fsPath; // Ensure drive is capitalized on Windows if (pathSeparator === '\\' && path.match(/^[a-zA-Z]:\\/)) { path = `${path[0].toUpperCase()}:${path.slice(2)}`; } + if (kind === vscode.TerminalCompletionItemKind.Folder) { + if (!path.endsWith(pathSeparator)) { + path += pathSeparator; + } + } return path; } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index 669c035acd4..fde380bd77b 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -65,6 +65,7 @@ export interface TerminalResourceRequestConfig { cwd?: URI; pathSeparator: string; shouldNormalizePrefix?: boolean; + env?: { [key: string]: string | null | undefined }; } @@ -246,6 +247,24 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo const lastWordFolderHasDotPrefix = lastWordFolder.match(/^\.\.?[\\\/]/); + const lastWordFolderHasTildePrefix = lastWordFolder.match(/^~[\\\/]/); + if (lastWordFolderHasTildePrefix) { + // Handle specially + const resolvedFolder = resourceRequestConfig.env?.HOME ? URI.file(resourceRequestConfig.env.HOME) : undefined; + if (resolvedFolder) { + resourceCompletions.push({ + label: lastWordFolder, + provider, + kind: TerminalCompletionItemKind.Folder, + isDirectory: true, + isFile: false, + detail: getFriendlyPath(resolvedFolder, resourceRequestConfig.pathSeparator), + replacementIndex: cursorPosition - lastWord.length, + replacementLength: lastWord.length + }); + return resourceCompletions; + } + } // Add current directory. This should be shown at the top because it will be an exact match // and therefore highlight the detail, plus it improves the experience when runOnEnter is // used. 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 679710de5fc..7cdac7c3d67 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 @@ -162,6 +162,20 @@ suite('TerminalCompletionService', () => { { label: './../', detail: '/' }, ], { replacementIndex: 3, replacementLength: 3 }); }); + test('cd ~/| should return home folder completions', async () => { + const resourceRequestConfig: TerminalResourceRequestConfig = { + cwd: URI.parse('file:///test/folder1'),// Updated to reflect home directory + foldersRequested: true, + pathSeparator, + shouldNormalizePrefix: true, + env: { HOME: '/test/' } + }; + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ~/', 5, provider); + + assertCompletions(result, [ + { label: '~/', detail: '/test/' }, + ], { replacementIndex: 3, replacementLength: 2 }); + }); }); suite('resolveResources should handle file and folder completion requests correctly', () => { diff --git a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts index 16b4809790b..90e922f9304 100644 --- a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts @@ -128,5 +128,9 @@ declare module 'vscode' { * The path separator to use when constructing paths. */ pathSeparator: string; + /** + * Environment variables to use when constructing paths. + */ + env?: { [key: string]: string | null | undefined }; } }