mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 20:26:08 +00:00
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as fs from 'fs/promises';
|
||||
import * as vscode from 'vscode';
|
||||
import { isExecutable } from '../helpers/executable';
|
||||
import { isExecutable, WindowsExecutableExtensionsCache } from '../helpers/executable';
|
||||
import { osIsWindows } from '../helpers/os';
|
||||
import type { ICompletionResource } from '../types';
|
||||
import { getFriendlyResourcePath } from '../helpers/uri';
|
||||
@@ -22,7 +22,7 @@ export interface IExecutablesInPath {
|
||||
export class PathExecutableCache implements vscode.Disposable {
|
||||
private _disposables: vscode.Disposable[] = [];
|
||||
|
||||
private _cachedWindowsExeExtensions: { [key: string]: boolean | undefined } | undefined;
|
||||
private readonly _windowsExecutableExtensionsCache: WindowsExecutableExtensionsCache | undefined;
|
||||
private _cachedExes: Map<string, Set<ICompletionResource> | undefined> = new Map();
|
||||
|
||||
private _inProgressRequest: {
|
||||
@@ -33,10 +33,10 @@ export class PathExecutableCache implements vscode.Disposable {
|
||||
|
||||
constructor() {
|
||||
if (isWindows) {
|
||||
this._cachedWindowsExeExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly);
|
||||
this._windowsExecutableExtensionsCache = new WindowsExecutableExtensionsCache(this._getConfiguredWindowsExecutableExtensions());
|
||||
this._disposables.push(vscode.workspace.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(SettingsIds.CachedWindowsExecutableExtensions)) {
|
||||
this._cachedWindowsExeExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly);
|
||||
this._windowsExecutableExtensionsCache?.update(this._getConfiguredWindowsExecutableExtensions());
|
||||
this._cachedExes.clear();
|
||||
}
|
||||
}));
|
||||
@@ -159,6 +159,7 @@ export class PathExecutableCache implements vscode.Disposable {
|
||||
const result = new Set<ICompletionResource>();
|
||||
const fileResource = vscode.Uri.file(path);
|
||||
const files = await vscode.workspace.fs.readDirectory(fileResource);
|
||||
const windowsExecutableExtensions = this._windowsExecutableExtensionsCache?.getExtensions();
|
||||
await Promise.all(
|
||||
files.map(([file, fileType]) => (async () => {
|
||||
let kind: vscode.TerminalCompletionItemKind | undefined;
|
||||
@@ -175,7 +176,7 @@ export class PathExecutableCache implements vscode.Disposable {
|
||||
if (lstat.isSymbolicLink()) {
|
||||
try {
|
||||
const symlinkRealPath = await fs.realpath(resource.fsPath);
|
||||
const isExec = await isExecutable(symlinkRealPath, this._cachedWindowsExeExtensions);
|
||||
const isExec = await isExecutable(symlinkRealPath, windowsExecutableExtensions);
|
||||
if (!isExec) {
|
||||
return;
|
||||
}
|
||||
@@ -197,7 +198,7 @@ export class PathExecutableCache implements vscode.Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
const isExec = kind === vscode.TerminalCompletionItemKind.Method || await isExecutable(formattedPath, this._cachedWindowsExeExtensions);
|
||||
const isExec = kind === vscode.TerminalCompletionItemKind.Method || await isExecutable(resource.fsPath, windowsExecutableExtensions);
|
||||
if (!isExec) {
|
||||
return;
|
||||
}
|
||||
@@ -216,6 +217,10 @@ export class PathExecutableCache implements vscode.Disposable {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _getConfiguredWindowsExecutableExtensions(): { [key: string]: boolean | undefined } | undefined {
|
||||
return vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly);
|
||||
}
|
||||
}
|
||||
|
||||
export type ITerminalEnvironment = { [key: string]: string | undefined };
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
import { osIsWindows } from './os';
|
||||
import * as fs from 'fs/promises';
|
||||
|
||||
export function isExecutable(filePath: string, configuredWindowsExecutableExtensions?: { [key: string]: boolean | undefined } | undefined): Promise<boolean> | boolean {
|
||||
export function isExecutable(filePath: string, windowsExecutableExtensions?: Set<string>): Promise<boolean> | boolean {
|
||||
if (osIsWindows()) {
|
||||
const resolvedWindowsExecutableExtensions = resolveWindowsExecutableExtensions(configuredWindowsExecutableExtensions);
|
||||
return resolvedWindowsExecutableExtensions.find(ext => filePath.endsWith(ext)) !== undefined;
|
||||
const extensions = windowsExecutableExtensions ?? defaultWindowsExecutableExtensionsSet;
|
||||
return hasWindowsExecutableExtension(filePath, extensions);
|
||||
}
|
||||
return isExecutableUnix(filePath);
|
||||
}
|
||||
@@ -25,22 +25,6 @@ export async function isExecutableUnix(filePath: string): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function resolveWindowsExecutableExtensions(configuredWindowsExecutableExtensions?: { [key: string]: boolean | undefined }): string[] {
|
||||
const resolvedWindowsExecutableExtensions: string[] = windowsDefaultExecutableExtensions;
|
||||
const excluded = new Set<string>();
|
||||
if (configuredWindowsExecutableExtensions) {
|
||||
for (const [key, value] of Object.entries(configuredWindowsExecutableExtensions)) {
|
||||
if (value === true) {
|
||||
resolvedWindowsExecutableExtensions.push(key);
|
||||
} else {
|
||||
excluded.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(new Set(resolvedWindowsExecutableExtensions)).filter(ext => !excluded.has(ext));
|
||||
}
|
||||
|
||||
export const windowsDefaultExecutableExtensions: string[] = [
|
||||
'.exe', // Executable file
|
||||
'.bat', // Batch file
|
||||
@@ -59,3 +43,65 @@ export const windowsDefaultExecutableExtensions: string[] = [
|
||||
'.pl', // Perl script (requires Perl interpreter)
|
||||
'.sh', // Shell script (via WSL or third-party tools)
|
||||
];
|
||||
|
||||
const defaultWindowsExecutableExtensionsSet = new Set<string>();
|
||||
for (const ext of windowsDefaultExecutableExtensions) {
|
||||
defaultWindowsExecutableExtensionsSet.add(ext);
|
||||
}
|
||||
|
||||
export class WindowsExecutableExtensionsCache {
|
||||
private _rawConfig: { [key: string]: boolean | undefined } | undefined;
|
||||
private _cachedExtensions: Set<string> | undefined;
|
||||
|
||||
constructor(rawConfig?: { [key: string]: boolean | undefined }) {
|
||||
this._rawConfig = rawConfig;
|
||||
}
|
||||
|
||||
update(rawConfig: { [key: string]: boolean | undefined } | undefined): void {
|
||||
this._rawConfig = rawConfig;
|
||||
this._cachedExtensions = undefined;
|
||||
}
|
||||
|
||||
getExtensions(): Set<string> {
|
||||
if (!this._cachedExtensions) {
|
||||
this._cachedExtensions = resolveWindowsExecutableExtensions(this._rawConfig);
|
||||
}
|
||||
return this._cachedExtensions;
|
||||
}
|
||||
}
|
||||
|
||||
function hasWindowsExecutableExtension(filePath: string, extensions: Set<string>): boolean {
|
||||
const fileName = filePath.slice(Math.max(filePath.lastIndexOf('\\'), filePath.lastIndexOf('/')) + 1);
|
||||
for (const ext of extensions) {
|
||||
if (fileName.endsWith(ext)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function resolveWindowsExecutableExtensions(configuredWindowsExecutableExtensions?: { [key: string]: boolean | undefined }): Set<string> {
|
||||
const extensions = new Set<string>();
|
||||
const configured = configuredWindowsExecutableExtensions ?? {};
|
||||
const excluded = new Set<string>();
|
||||
|
||||
for (const [ext, value] of Object.entries(configured)) {
|
||||
if (value !== true) {
|
||||
excluded.add(ext);
|
||||
}
|
||||
}
|
||||
|
||||
for (const ext of windowsDefaultExecutableExtensions) {
|
||||
if (!excluded.has(ext)) {
|
||||
extensions.add(ext);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [ext, value] of Object.entries(configured)) {
|
||||
if (value === true) {
|
||||
extensions.add(ext);
|
||||
}
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'mocha';
|
||||
import { deepStrictEqual, strictEqual } from 'node:assert';
|
||||
import type { MarkdownString } from 'vscode';
|
||||
import { PathExecutableCache } from '../../env/pathExecutableCache';
|
||||
import { WindowsExecutableExtensionsCache, windowsDefaultExecutableExtensions } from '../../helpers/executable';
|
||||
|
||||
suite('PathExecutableCache', () => {
|
||||
test('cache should return empty for empty PATH', async () => {
|
||||
@@ -67,4 +68,43 @@ suite('PathExecutableCache', () => {
|
||||
strictEqual(symlinkDoc, `${symlinkPath} -> ${realPath}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
suite('WindowsExecutableExtensionsCache', () => {
|
||||
test('returns default extensions when not configured', () => {
|
||||
const cache = new WindowsExecutableExtensionsCache();
|
||||
const extensions = cache.getExtensions();
|
||||
|
||||
for (const ext of windowsDefaultExecutableExtensions) {
|
||||
strictEqual(extensions.has(ext), true, `expected default extension ${ext}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('honors configured additions and removals', () => {
|
||||
const cache = new WindowsExecutableExtensionsCache({
|
||||
'.added': true,
|
||||
'.bat': false
|
||||
});
|
||||
|
||||
const extensions = cache.getExtensions();
|
||||
strictEqual(extensions.has('.added'), true);
|
||||
strictEqual(extensions.has('.bat'), false);
|
||||
strictEqual(extensions.has('.exe'), true);
|
||||
});
|
||||
|
||||
test('recomputes only after update is called', () => {
|
||||
const cache = new WindowsExecutableExtensionsCache({ '.one': true });
|
||||
|
||||
const first = cache.getExtensions();
|
||||
const second = cache.getExtensions();
|
||||
strictEqual(first, second, 'expected cached set to be reused');
|
||||
|
||||
cache.update({ '.two': true });
|
||||
const third = cache.getExtensions();
|
||||
strictEqual(third.has('.two'), true);
|
||||
strictEqual(third.has('.one'), false);
|
||||
strictEqual(third === first, false, 'expected cache to recompute after update');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user