Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't show fig information for aliases #239426

Merged
merged 5 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions extensions/terminal-suggest/src/env/pathExecutableCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ICompletionResource> | undefined; labels: Set<string> | undefined } | undefined;
private _cachedPathValue: string | undefined;
private _cachedWindowsExeExtensions: { [key: string]: boolean | undefined } | undefined;
private _cachedExes: { completionResources: Set<ICompletionResource> | undefined; labels: Set<string> | 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;
}
}));
}
Expand All @@ -38,7 +38,7 @@ export class PathExecutableCache implements vscode.Disposable {
}
}

async getCommandsInPath(env: { [key: string]: string | undefined } = process.env): Promise<{ completionResources: Set<ICompletionResource> | undefined; labels: Set<string> | undefined } | undefined> {
async getExecutablesInPath(env: { [key: string]: string | undefined } = process.env): Promise<{ completionResources: Set<ICompletionResource> | undefined; labels: Set<string> | undefined } | undefined> {
// Create cache key
let pathValue: string | undefined;
if (isWindows) {
Expand All @@ -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
Expand All @@ -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<string>): Promise<Set<ICompletionResource> | undefined> {
Expand All @@ -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);
}
Expand Down
22 changes: 2 additions & 20 deletions extensions/terminal-suggest/src/shell/bash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>): Promise<(string | ICompletionResource)[]> {
return [
Expand All @@ -22,22 +21,5 @@ async function getBuiltins(options: ExecOptionsWithStringEncoding, existingComma
}

async function getAliases(options: ExecOptionsWithStringEncoding): Promise<ICompletionResource[]> {
// 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 (?<alias>[a-zA-Z0-9\.:-]+)='(?<resolved>.+)'$/);
if (!match?.groups) {
continue;
}
result.push({
label: match.groups.alias,
detail: match.groups.resolved,
kind: vscode.TerminalCompletionItemKind.Alias,
});
}
return result;
return getAliasesHelper('bash', ['-ic', 'alias'], /^alias (?<alias>[a-zA-Z0-9\.:-]+)='(?<resolved>.+)'$/, options);
}
29 changes: 29 additions & 0 deletions extensions/terminal-suggest/src/shell/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
// This must be run with interactive, otherwise there's a good chance aliases won't
Expand Down Expand Up @@ -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<ICompletionResource[]> {
// 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;
}
23 changes: 2 additions & 21 deletions extensions/terminal-suggest/src/shell/fish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>): Promise<(string | ICompletionResource)[]> {
Expand All @@ -22,23 +21,5 @@ async function getBuiltins(options: ExecOptionsWithStringEncoding, existingComma
}

async function getAliases(options: ExecOptionsWithStringEncoding): Promise<ICompletionResource[]> {
// 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 (?<alias>[a-zA-Z0-9\.:-]+) (?<resolved>.+)$/);
if (!match?.groups) {
continue;
}
result.push({
label: match.groups.alias,
detail: match.groups.resolved,
kind: vscode.TerminalCompletionItemKind.Alias,
});
}
return result;
return getAliasesHelper('fish', ['-ic', 'alias'], /^alias (?<alias>[a-zA-Z0-9\.:-]+) (?<resolved>.+)$/, options);
}
9 changes: 9 additions & 0 deletions extensions/terminal-suggest/src/shell/pwsh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,21 @@ async function getAliases(options: ExecOptionsWithStringEncoding, existingComman
if (e.ModuleName && e.Version) {
detailParts.push(`${e.ModuleName} v${e.Version}`);
}
let definitionCommand = undefined;
if (e.Definition) {
let definitionIndex = e.Definition.indexOf(' ');
if (definitionIndex === -1) {
definitionIndex = e.Definition.length;
definitionCommand = e.Definition.substring(0, definitionIndex);
}
}
return {
label: e.Name,
detail: detailParts.join('\n\n'),
kind: (isAlias
? vscode.TerminalCompletionItemKind.Alias
: vscode.TerminalCompletionItemKind.Method),
definitionCommand,
};
});
}
Expand Down
22 changes: 2 additions & 20 deletions extensions/terminal-suggest/src/shell/zsh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>): Promise<(string | ICompletionResource)[]> {
Expand All @@ -22,22 +21,5 @@ async function getBuiltins(options: ExecOptionsWithStringEncoding, existingComma
}

async function getAliases(options: ExecOptionsWithStringEncoding): Promise<ICompletionResource[]> {
// 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(/^(?<alias>[a-zA-Z0-9\.:-]+)=(?:'(?<resolved>.+)'|(?<resolved>.+))$/);
if (!match?.groups) {
continue;
}
result.push({
label: match.groups.alias,
detail: match.groups.resolved,
kind: vscode.TerminalCompletionItemKind.Alias,
});
}
return result;
return getAliasesHelper('zsh', ['-ic', 'alias'], /^(?<alias>[a-zA-Z0-9\.:-]+)=(?:'(?<resolved>.+)'|(?<resolved>.+))$/, options);
}
11 changes: 7 additions & 4 deletions extensions/terminal-suggest/src/terminalSuggestMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -249,18 +249,21 @@ export async function getCompletionItemsFromSpecs(
}

for (const specLabel of specLabels) {
const availableCommand = availableCommands.find(command => command.label === specLabel);
const availableCommand = availableCommands.find(command => specLabel === 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;
}

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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ 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);
});

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);
});
});
5 changes: 5 additions & 0 deletions extensions/terminal-suggest/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import * as vscode from 'vscode';

export interface ICompletionResource {
label: string;
/**
* The definition command of the completion, this will be the resolved value of an alias
* completion.
*/
definitionCommand?: string;
detail?: string;
kind?: vscode.TerminalCompletionItemKind;
}
Loading