From 53790287ceb2cf62383e4b5ef5f86b1681c35fc0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Feb 2025 04:27:47 -0800 Subject: [PATCH 1/4] Improve names on PathExecutableCache --- .../src/env/pathExecutableCache.ts | 26 +++++++++---------- .../src/terminalSuggestMain.ts | 2 +- .../src/test/env/pathExecutableCache.test.ts | 6 ++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/extensions/terminal-suggest/src/env/pathExecutableCache.ts b/extensions/terminal-suggest/src/env/pathExecutableCache.ts index 15f1bbcb694e7..4ce8090f088fe 100644 --- a/extensions/terminal-suggest/src/env/pathExecutableCache.ts +++ b/extensions/terminal-suggest/src/env/pathExecutableCache.ts @@ -16,17 +16,17 @@ const isWindows = osIsWindows(); export class PathExecutableCache implements vscode.Disposable { private _disposables: vscode.Disposable[] = []; - private _cachedAvailableCommandsPath: string | undefined; - private _cachedWindowsExecutableExtensions: { [key: string]: boolean | undefined } | undefined; - private _cachedCommandsInPath: { completionResources: Set | undefined; labels: Set | undefined } | undefined; + private _cachedPathValue: string | undefined; + private _cachedWindowsExeExtensions: { [key: string]: boolean | undefined } | undefined; + private _cachedExes: { completionResources: Set | undefined; labels: Set | undefined } | undefined; constructor() { if (isWindows) { - this._cachedWindowsExecutableExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly); + this._cachedWindowsExeExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly); this._disposables.push(vscode.workspace.onDidChangeConfiguration(e => { if (e.affectsConfiguration(SettingsIds.CachedWindowsExecutableExtensions)) { - this._cachedWindowsExecutableExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly); - this._cachedCommandsInPath = undefined; + this._cachedWindowsExeExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly); + this._cachedExes = undefined; } })); } @@ -38,7 +38,7 @@ export class PathExecutableCache implements vscode.Disposable { } } - async getCommandsInPath(env: { [key: string]: string | undefined } = process.env): Promise<{ completionResources: Set | undefined; labels: Set | undefined } | undefined> { + async getExecutablesInPath(env: { [key: string]: string | undefined } = process.env): Promise<{ completionResources: Set | undefined; labels: Set | undefined } | undefined> { // Create cache key let pathValue: string | undefined; if (isWindows) { @@ -54,8 +54,8 @@ export class PathExecutableCache implements vscode.Disposable { } // Check cache - if (this._cachedCommandsInPath && this._cachedAvailableCommandsPath === pathValue) { - return this._cachedCommandsInPath; + if (this._cachedExes && this._cachedPathValue === pathValue) { + return this._cachedExes; } // Extract executables from PATH @@ -79,9 +79,9 @@ export class PathExecutableCache implements vscode.Disposable { } // Return - this._cachedAvailableCommandsPath = pathValue; - this._cachedCommandsInPath = { completionResources: executables, labels }; - return this._cachedCommandsInPath; + this._cachedPathValue = pathValue; + this._cachedExes = { completionResources: executables, labels }; + return this._cachedExes; } private async _getFilesInPath(path: string, pathSeparator: string, labels: Set): Promise | undefined> { @@ -95,7 +95,7 @@ export class PathExecutableCache implements vscode.Disposable { const files = await vscode.workspace.fs.readDirectory(fileResource); for (const [file, fileType] of files) { const formattedPath = getFriendlyResourcePath(vscode.Uri.joinPath(fileResource, file), pathSeparator); - if (!labels.has(file) && fileType !== vscode.FileType.Unknown && fileType !== vscode.FileType.Directory && await isExecutable(formattedPath, this._cachedWindowsExecutableExtensions)) { + if (!labels.has(file) && fileType !== vscode.FileType.Unknown && fileType !== vscode.FileType.Directory && await isExecutable(formattedPath, this._cachedWindowsExeExtensions)) { result.add({ label: file, detail: formattedPath }); labels.add(file); } diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 808e26a9c6b75..d5119697098e3 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -96,7 +96,7 @@ export async function activate(context: vscode.ExtensionContext) { return; } - const commandsInPath = await pathExecutableCache.getCommandsInPath(terminal.shellIntegration?.env); + const commandsInPath = await pathExecutableCache.getExecutablesInPath(terminal.shellIntegration?.env); const shellGlobals = await getShellGlobals(shellType, commandsInPath?.labels) ?? []; if (!commandsInPath?.completionResources) { return; diff --git a/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts b/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts index 4ba5451eafc26..890aa10103339 100644 --- a/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts +++ b/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts @@ -10,7 +10,7 @@ import { PathExecutableCache } from '../../env/pathExecutableCache'; suite('PathExecutableCache', () => { test('cache should return empty for empty PATH', async () => { const cache = new PathExecutableCache(); - const result = await cache.getCommandsInPath({ PATH: '' }); + const result = await cache.getExecutablesInPath({ PATH: '' }); strictEqual(Array.from(result!.completionResources!).length, 0); strictEqual(Array.from(result!.labels!).length, 0); }); @@ -18,8 +18,8 @@ suite('PathExecutableCache', () => { test('caching is working on successive calls', async () => { const cache = new PathExecutableCache(); const env = { PATH: process.env.PATH }; - const result = await cache.getCommandsInPath(env); - const result2 = await cache.getCommandsInPath(env); + const result = await cache.getExecutablesInPath(env); + const result2 = await cache.getExecutablesInPath(env); strictEqual(result, result2); }); }); From 521e89838af1a28c39bd119dbb449d76bb73a210 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Feb 2025 04:40:31 -0800 Subject: [PATCH 2/4] Don't show fig information for aliases Fixes #239425 --- extensions/terminal-suggest/src/shell/bash.ts | 1 + extensions/terminal-suggest/src/shell/fish.ts | 2 +- extensions/terminal-suggest/src/shell/pwsh.ts | 1 + extensions/terminal-suggest/src/shell/zsh.ts | 1 + extensions/terminal-suggest/src/terminalSuggestMain.ts | 6 ++++-- extensions/terminal-suggest/src/types.ts | 5 +++++ 6 files changed, 13 insertions(+), 3 deletions(-) diff --git a/extensions/terminal-suggest/src/shell/bash.ts b/extensions/terminal-suggest/src/shell/bash.ts index b440af3c8805b..96d3b3765b23f 100644 --- a/extensions/terminal-suggest/src/shell/bash.ts +++ b/extensions/terminal-suggest/src/shell/bash.ts @@ -37,6 +37,7 @@ async function getAliases(options: ExecOptionsWithStringEncoding): Promise[a-zA-Z0-9\.:-]+) (?.+)$/); @@ -38,6 +37,7 @@ async function getAliases(options: ExecOptionsWithStringEncoding): Promise command.label === specLabel); + const availableCommand = availableCommands.find(command => specLabel === (command.definition ?? command.label)); if (!availableCommand || (token && token.isCancellationRequested)) { continue; } // push it to the completion items if (tokenType === TokenType.Command) { - items.push(createCompletionItem(terminalContext.cursorPosition, prefix, { label: specLabel }, getDescription(spec), availableCommand.detail)); + if (availableCommand.kind !== vscode.TerminalCompletionItemKind.Alias) { + items.push(createCompletionItem(terminalContext.cursorPosition, prefix, { label: specLabel }, getDescription(spec), availableCommand.detail)); + } continue; } diff --git a/extensions/terminal-suggest/src/types.ts b/extensions/terminal-suggest/src/types.ts index 43b86a08a93ea..6e9ec6c5f1403 100644 --- a/extensions/terminal-suggest/src/types.ts +++ b/extensions/terminal-suggest/src/types.ts @@ -7,6 +7,11 @@ import * as vscode from 'vscode'; export interface ICompletionResource { label: string; + /** + * The definition of the completion, this will be the resolved value of an + * alias completion. + */ + definition?: string; detail?: string; kind?: vscode.TerminalCompletionItemKind; } From c4cf1864d2fbea6ac48e0219211cc53c26cd4382 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Feb 2025 05:15:25 -0800 Subject: [PATCH 3/4] Resolve fig spec against alias definition command --- extensions/terminal-suggest/src/shell/bash.ts | 8 +++++++- extensions/terminal-suggest/src/shell/fish.ts | 8 +++++++- extensions/terminal-suggest/src/shell/pwsh.ts | 10 +++++++++- extensions/terminal-suggest/src/shell/zsh.ts | 8 +++++++- extensions/terminal-suggest/src/terminalSuggestMain.ts | 5 +++-- extensions/terminal-suggest/src/types.ts | 6 +++--- 6 files changed, 36 insertions(+), 9 deletions(-) diff --git a/extensions/terminal-suggest/src/shell/bash.ts b/extensions/terminal-suggest/src/shell/bash.ts index 96d3b3765b23f..c4ce4598cb39f 100644 --- a/extensions/terminal-suggest/src/shell/bash.ts +++ b/extensions/terminal-suggest/src/shell/bash.ts @@ -33,11 +33,17 @@ async function getAliases(options: ExecOptionsWithStringEncoding): Promise specLabel === (command.definition ?? command.label)); + const availableCommand = availableCommands.find(command => specLabel === command.label); if (!availableCommand || (token && token.isCancellationRequested)) { continue; } @@ -262,7 +262,8 @@ export async function getCompletionItemsFromSpecs( continue; } - if (!terminalContext.commandLine.startsWith(`${specLabel} `)) { + const commandAndAliases = availableCommands.filter(command => specLabel === (command.definitionCommand ?? command.label)); + if (!commandAndAliases.some(e => terminalContext.commandLine.startsWith(`${e.label} `))) { // the spec label is not the first word in the command line, so do not provide options or args continue; } diff --git a/extensions/terminal-suggest/src/types.ts b/extensions/terminal-suggest/src/types.ts index 6e9ec6c5f1403..abc4f4f291b42 100644 --- a/extensions/terminal-suggest/src/types.ts +++ b/extensions/terminal-suggest/src/types.ts @@ -8,10 +8,10 @@ import * as vscode from 'vscode'; export interface ICompletionResource { label: string; /** - * The definition of the completion, this will be the resolved value of an - * alias completion. + * The definition command of the completion, this will be the resolved value of an alias + * completion. */ - definition?: string; + definitionCommand?: string; detail?: string; kind?: vscode.TerminalCompletionItemKind; } From 4f587d78c2ecd5d5d4100aa473ed647ba267331e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Feb 2025 05:18:45 -0800 Subject: [PATCH 4/4] Pull alias parsing into helper --- extensions/terminal-suggest/src/shell/bash.ts | 29 ++----------------- .../terminal-suggest/src/shell/common.ts | 29 +++++++++++++++++++ extensions/terminal-suggest/src/shell/fish.ts | 29 ++----------------- extensions/terminal-suggest/src/shell/zsh.ts | 29 ++----------------- 4 files changed, 35 insertions(+), 81 deletions(-) diff --git a/extensions/terminal-suggest/src/shell/bash.ts b/extensions/terminal-suggest/src/shell/bash.ts index c4ce4598cb39f..bb7e374bed21d 100644 --- a/extensions/terminal-suggest/src/shell/bash.ts +++ b/extensions/terminal-suggest/src/shell/bash.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; import { type ExecOptionsWithStringEncoding } from 'node:child_process'; -import { execHelper, spawnHelper } from './common'; +import { execHelper, getAliasesHelper } from './common'; export async function getBashGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { return [ @@ -22,29 +21,5 @@ async function getBuiltins(options: ExecOptionsWithStringEncoding, existingComma } async function getAliases(options: ExecOptionsWithStringEncoding): Promise { - // This must be run with interactive, otherwise there's a good chance aliases won't - // be set up. Note that this could differ from the actual aliases as it's a new bash - // session, for the same reason this would not include aliases that are created - // by simply running `alias ...` in the terminal. - const aliasOutput = await spawnHelper('bash', ['-ic', 'alias'], options); - const result: ICompletionResource[] = []; - for (const line of aliasOutput.split('\n')) { - const match = line.match(/^alias (?[a-zA-Z0-9\.:-]+)='(?.+)'$/); - if (!match?.groups) { - continue; - } - let definitionCommand = ''; - let definitionIndex = match.groups.resolved.indexOf(' '); - if (definitionIndex === -1) { - definitionIndex = match.groups.resolved.length; - } - definitionCommand = match.groups.resolved.substring(0, definitionIndex); - result.push({ - label: match.groups.alias, - detail: match.groups.resolved, - kind: vscode.TerminalCompletionItemKind.Alias, - definitionCommand, - }); - } - return result; + return getAliasesHelper('bash', ['-ic', 'alias'], /^alias (?[a-zA-Z0-9\.:-]+)='(?.+)'$/, options); } diff --git a/extensions/terminal-suggest/src/shell/common.ts b/extensions/terminal-suggest/src/shell/common.ts index 590195e3600b6..5ef609002a6cb 100644 --- a/extensions/terminal-suggest/src/shell/common.ts +++ b/extensions/terminal-suggest/src/shell/common.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_process'; +import type { ICompletionResource } from '../types'; export async function spawnHelper(command: string, args: string[], options: ExecOptionsWithStringEncoding): Promise { // This must be run with interactive, otherwise there's a good chance aliases won't @@ -38,3 +40,30 @@ export async function execHelper(commandLine: string, options: ExecOptionsWithSt }); } +export async function getAliasesHelper(command: string, args: string[], regex: RegExp, options: ExecOptionsWithStringEncoding): Promise { + // This must be run with interactive, otherwise there's a good chance aliases won't + // be set up. Note that this could differ from the actual aliases as it's a new bash + // session, for the same reason this would not include aliases that are created + // by simply running `alias ...` in the terminal. + const aliasOutput = await spawnHelper(command, args, options); + const result: ICompletionResource[] = []; + for (const line of aliasOutput.split('\n')) { + const match = line.match(regex); + if (!match?.groups) { + continue; + } + let definitionCommand = ''; + let definitionIndex = match.groups.resolved.indexOf(' '); + if (definitionIndex === -1) { + definitionIndex = match.groups.resolved.length; + } + definitionCommand = match.groups.resolved.substring(0, definitionIndex); + result.push({ + label: match.groups.alias, + detail: match.groups.resolved, + kind: vscode.TerminalCompletionItemKind.Alias, + definitionCommand, + }); + } + return result; +} diff --git a/extensions/terminal-suggest/src/shell/fish.ts b/extensions/terminal-suggest/src/shell/fish.ts index 5341a5e5a4f6e..cc2fbaff6e0c8 100644 --- a/extensions/terminal-suggest/src/shell/fish.ts +++ b/extensions/terminal-suggest/src/shell/fish.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; -import { execHelper, spawnHelper } from './common'; +import { execHelper, getAliasesHelper } from './common'; import { type ExecOptionsWithStringEncoding } from 'node:child_process'; export async function getFishGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { @@ -22,29 +21,5 @@ async function getBuiltins(options: ExecOptionsWithStringEncoding, existingComma } async function getAliases(options: ExecOptionsWithStringEncoding): Promise { - // This must be run with interactive, otherwise there's a good chance aliases won't - // be set up. Note that this could differ from the actual aliases as it's a new bash - // session, for the same reason this would not include aliases that are created - // by simply running `alias ...` in the terminal. - const aliasOutput = await spawnHelper('fish', ['-ic', 'alias'], options); - const result: ICompletionResource[] = []; - for (const line of aliasOutput.split('\n')) { - const match = line.match(/^alias (?[a-zA-Z0-9\.:-]+) (?.+)$/); - if (!match?.groups) { - continue; - } - let definitionCommand = ''; - let definitionIndex = match.groups.resolved.indexOf(' '); - if (definitionIndex === -1) { - definitionIndex = match.groups.resolved.length; - } - definitionCommand = match.groups.resolved.substring(0, definitionIndex); - result.push({ - label: match.groups.alias, - detail: match.groups.resolved, - kind: vscode.TerminalCompletionItemKind.Alias, - definitionCommand, - }); - } - return result; + return getAliasesHelper('fish', ['-ic', 'alias'], /^alias (?[a-zA-Z0-9\.:-]+) (?.+)$/, options); } diff --git a/extensions/terminal-suggest/src/shell/zsh.ts b/extensions/terminal-suggest/src/shell/zsh.ts index eb04464be68df..1530c5de58652 100644 --- a/extensions/terminal-suggest/src/shell/zsh.ts +++ b/extensions/terminal-suggest/src/shell/zsh.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; -import { execHelper, spawnHelper } from './common'; +import { execHelper, getAliasesHelper } from './common'; import { type ExecOptionsWithStringEncoding } from 'node:child_process'; export async function getZshGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { @@ -22,29 +21,5 @@ async function getBuiltins(options: ExecOptionsWithStringEncoding, existingComma } async function getAliases(options: ExecOptionsWithStringEncoding): Promise { - // This must be run with interactive, otherwise there's a good chance aliases won't - // be set up. Note that this could differ from the actual aliases as it's a new bash - // session, for the same reason this would not include aliases that are created - // by simply running `alias ...` in the terminal. - const aliasOutput = await spawnHelper('zsh', ['-ic', 'alias'], options); - const result: ICompletionResource[] = []; - for (const line of aliasOutput.split('\n')) { - const match = line.match(/^(?[a-zA-Z0-9\.:-]+)=(?:'(?.+)'|(?.+))$/); - if (!match?.groups) { - continue; - } - let definitionCommand = ''; - let definitionIndex = match.groups.resolved.indexOf(' '); - if (definitionIndex === -1) { - definitionIndex = match.groups.resolved.length; - } - definitionCommand = match.groups.resolved.substring(0, definitionIndex); - result.push({ - label: match.groups.alias, - detail: match.groups.resolved, - kind: vscode.TerminalCompletionItemKind.Alias, - definitionCommand, - }); - } - return result; + return getAliasesHelper('zsh', ['-ic', 'alias'], /^(?[a-zA-Z0-9\.:-]+)=(?:'(?.+)'|(?.+))$/, options); }