Skip to content

Commit f1b4bb8

Browse files
authored
check if windows file is executable (microsoft#238142)
fix microsoft#237596
1 parent bb65589 commit f1b4bb8

File tree

4 files changed

+58
-26
lines changed

4 files changed

+58
-26
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { osIsWindows } from './os';
7+
import * as fs from 'fs/promises';
8+
9+
export async function isExecutable(filePath: string): Promise<boolean> {
10+
if (osIsWindows()) {
11+
return windowsExecutableExtensions.find(ext => filePath.endsWith(ext)) !== undefined;
12+
}
13+
try {
14+
const stats = await fs.stat(filePath);
15+
// On macOS/Linux, check if the executable bit is set
16+
return (stats.mode & 0o100) !== 0;
17+
} catch (error) {
18+
// If the file does not exist or cannot be accessed, it's not executable
19+
return false;
20+
}
21+
}
22+
const windowsExecutableExtensions: string[] = [
23+
'.exe', // Executable file
24+
'.bat', // Batch file
25+
'.cmd', // Command script
26+
'.com', // Command file
27+
28+
'.msi', // Windows Installer package
29+
30+
'.ps1', // PowerShell script
31+
32+
'.vbs', // VBScript file
33+
'.js', // JScript file
34+
'.jar', // Java Archive (requires Java runtime)
35+
'.py', // Python script (requires Python interpreter)
36+
'.rb', // Ruby script (requires Ruby interpreter)
37+
'.pl', // Perl script (requires Perl interpreter)
38+
'.sh', // Shell script (via WSL or third-party tools)
39+
];
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as os from 'os';
7+
8+
export function osIsWindows(): boolean {
9+
return os.platform() === 'win32';
10+
}

extensions/terminal-suggest/src/terminalSuggestMain.ts

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55
import * as vscode from 'vscode';
6-
import * as os from 'os';
76
import * as fs from 'fs/promises';
87
import * as path from 'path';
98
import { ExecOptionsWithStringEncoding, execSync } from 'child_process';
109
import { upstreamSpecs } from './constants';
1110
import codeCompletionSpec from './completions/code';
1211
import cdSpec from './completions/cd';
1312
import codeInsidersCompletionSpec from './completions/code-insiders';
13+
import { osIsWindows } from './helpers/os';
14+
import { isExecutable } from './helpers/executable';
1415

16+
const isWindows = osIsWindows();
1517
let cachedAvailableCommandsPath: string | undefined;
1618
let cachedAvailableCommands: Set<string> | undefined;
1719
const cachedBuiltinCommands: Map<string, string[] | undefined> = new Map();
@@ -101,7 +103,7 @@ export async function activate(context: vscode.ExtensionContext) {
101103
const result = await getCompletionItemsFromSpecs(availableSpecs, terminalContext, commands, prefix, terminal.shellIntegration?.cwd, token);
102104
if (result.cwd && (result.filesRequested || result.foldersRequested)) {
103105
// const cwd = resolveCwdFromPrefix(prefix, terminal.shellIntegration?.cwd) ?? terminal.shellIntegration?.cwd;
104-
return new vscode.TerminalCompletionList(result.items, { filesRequested: result.filesRequested, foldersRequested: result.foldersRequested, cwd: result.cwd, pathSeparator: osIsWindows() ? '\\' : '/' });
106+
return new vscode.TerminalCompletionList(result.items, { filesRequested: result.filesRequested, foldersRequested: result.foldersRequested, cwd: result.cwd, pathSeparator: isWindows ? '\\' : '/' });
105107
}
106108
return result.items;
107109
}
@@ -123,7 +125,7 @@ export async function resolveCwdFromPrefix(prefix: string, currentCwd?: vscode.U
123125
// Get the nearest folder path from the prefix. This ignores everything after the `/` as
124126
// they are what triggers changes in the directory.
125127
let lastSlashIndex: number;
126-
if (osIsWindows()) {
128+
if (isWindows) {
127129
// TODO: This support is very basic, ideally the slashes supported would depend upon the
128130
// shell type. For example git bash under Windows does not allow using \ as a path
129131
// separator.
@@ -179,28 +181,10 @@ function createCompletionItem(cursorPosition: number, prefix: string, label: str
179181
};
180182
}
181183

182-
async function isExecutable(filePath: string): Promise<boolean> {
183-
// Windows doesn't have the concept of an executable bit and running any
184-
// file is possible. We considered using $PATHEXT here but since it's mostly
185-
// there for legacy reasons and it would be easier and more intuitive to add
186-
// a setting if needed instead.
187-
if (osIsWindows()) {
188-
return true;
189-
}
190-
try {
191-
const stats = await fs.stat(filePath);
192-
// On macOS/Linux, check if the executable bit is set
193-
return (stats.mode & 0o100) !== 0;
194-
} catch (error) {
195-
// If the file does not exist or cannot be accessed, it's not executable
196-
return false;
197-
}
198-
}
199-
200184
async function getCommandsInPath(env: { [key: string]: string | undefined } = process.env): Promise<Set<string> | undefined> {
201185
// Get PATH value
202186
let pathValue: string | undefined;
203-
if (osIsWindows()) {
187+
if (isWindows) {
204188
const caseSensitivePathKey = Object.keys(env).find(key => key.toLowerCase() === 'path');
205189
if (caseSensitivePathKey) {
206190
pathValue = env[caseSensitivePathKey];
@@ -218,7 +202,6 @@ async function getCommandsInPath(env: { [key: string]: string | undefined } = pr
218202
}
219203

220204
// Extract executables from PATH
221-
const isWindows = osIsWindows();
222205
const paths = pathValue.split(isWindows ? ';' : ':');
223206
const pathSeparator = isWindows ? '\\' : '/';
224207
const executables = new Set<string>();
@@ -484,9 +467,7 @@ function getCompletionItemsFromArgs(args: Fig.SingleOrArray<Fig.Arg> | undefined
484467
return { items, filesRequested, foldersRequested };
485468
}
486469

487-
function osIsWindows(): boolean {
488-
return os.platform() === 'win32';
489-
}
470+
490471

491472
function getFirstCommand(commandLine: string): string | undefined {
492473
const wordsOnLine = commandLine.split(' ');

src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ const fileExtScores = new Map<string, number>(isWindows ? [
234234
['exe', 0.08],
235235
['bat', 0.07],
236236
['cmd', 0.07],
237+
['msi', 0.06],
238+
['com', 0.06],
237239
// Non-Windows
238240
['sh', -0.05],
239241
['bash', -0.05],

0 commit comments

Comments
 (0)