未验证 提交 732d4ff8 编写于 作者: T Tyler James Leonhardt 提交者: GitHub

Make PowerShell 7 default if available and show in choose shell menu (#112768)

* make PowerShell 7 default if available and show in choose shell menu

* misc feedback

* better handle ARM and use pfs everywhere also update pfs to handle AppExecLinks

* fix test

* move to async

* add logging

* powershell global tool is in the image apparently

* have path test be the same

* try/catch the readlink

* await exists

* fix test

* check what arch node is

* fix indexes

* address daniel's feedback

* have getProgramFilesPath return null instead
上级 74038b7e
......@@ -247,6 +247,10 @@ export function renameIgnoreError(oldPath: string, newPath: string): Promise<voi
return new Promise(resolve => fs.rename(oldPath, newPath, () => resolve()));
}
export function readlink(path: string): Promise<string> {
return promisify(fs.readlink)(path);
}
export function unlink(path: string): Promise<void> {
return promisify(fs.unlink)(path);
}
......@@ -422,7 +426,15 @@ export async function dirExists(path: string): Promise<boolean> {
return fileStat.isDirectory();
} catch (error) {
return false;
// This catch will be called on some symbolic links on Windows (AppExecLink for example).
// So we try our best to see if it's a Directory.
try {
const fileStat = await stat(await readlink(path));
return fileStat.isDirectory();
} catch {
return false;
}
}
}
......@@ -432,7 +444,15 @@ export async function fileExists(path: string): Promise<boolean> {
return fileStat.isFile();
} catch (error) {
return false;
// This catch will be called on some symbolic links on Windows (AppExecLink for example).
// So we try our best to see if it's a File.
try {
const fileStat = await stat(await readlink(path));
return fileStat.isFile();
} catch {
return false;
}
}
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as pfs from 'vs/base/node/pfs';
import * as os from 'os';
import * as path from 'vs/base/common/path';
import { env } from 'vs/base/common/process';
const WindowsPowerShell64BitLabel = 'Windows PowerShell';
const WindowsPowerShell32BitLabel = 'Windows PowerShell (x86)';
// This is required, since parseInt("7-preview") will return 7.
const IntRegex: RegExp = /^\d+$/;
const PwshMsixRegex: RegExp = /^Microsoft.PowerShell_.*/;
const PwshPreviewMsixRegex: RegExp = /^Microsoft.PowerShellPreview_.*/;
// The platform details descriptor for the platform we're on
const isProcess64Bit: boolean = process.arch === 'x64';
const isOS64Bit: boolean = isProcess64Bit || env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
export interface IPowerShellExeDetails {
readonly displayName: string;
readonly exePath: string;
}
export interface IPossiblePowerShellExe extends IPowerShellExeDetails {
exists(): Promise<boolean>;
}
class PossiblePowerShellExe implements IPossiblePowerShellExe {
constructor(
public readonly exePath: string,
public readonly displayName: string,
private knownToExist?: boolean) { }
public async exists(): Promise<boolean> {
if (this.knownToExist === undefined) {
this.knownToExist = await pfs.fileExists(this.exePath);
}
return this.knownToExist;
}
}
function getProgramFilesPath(
{ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): string | null {
if (!useAlternateBitness) {
// Just use the native system bitness
return env.ProgramFiles || null;
}
// We might be a 64-bit process looking for 32-bit program files
if (isProcess64Bit) {
return env['ProgramFiles(x86)'] || null;
}
// We might be a 32-bit process looking for 64-bit program files
if (isOS64Bit) {
return env.ProgramW6432 || null;
}
// We're a 32-bit process on 32-bit Windows, there is no other Program Files dir
return null;
}
function getSystem32Path({ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): string {
const windir: string = env.windir!;
if (!useAlternateBitness) {
// Just use the native system bitness
return path.join(windir, 'System32');
}
// We might be a 64-bit process looking for 32-bit system32
if (isProcess64Bit) {
return path.join(windir, 'SysWOW64');
}
// We might be a 32-bit process looking for 64-bit system32
if (isOS64Bit) {
return path.join(windir, 'Sysnative');
}
// We're on a 32-bit Windows, so no alternate bitness
return path.join(windir, 'System32');
}
async function findPSCoreWindowsInstallation(
{ useAlternateBitness = false, findPreview = false }:
{ useAlternateBitness?: boolean; findPreview?: boolean } = {}): Promise<IPossiblePowerShellExe | null> {
const programFilesPath = getProgramFilesPath({ useAlternateBitness });
if (!programFilesPath) {
return null;
}
const powerShellInstallBaseDir = path.join(programFilesPath, 'PowerShell');
// Ensure the base directory exists
if (!await pfs.dirExists(powerShellInstallBaseDir)) {
return null;
}
let highestSeenVersion: number = -1;
let pwshExePath: string | null = null;
for (const item of await pfs.readdir(powerShellInstallBaseDir)) {
let currentVersion: number = -1;
if (findPreview) {
// We are looking for something like "7-preview"
// Preview dirs all have dashes in them
const dashIndex = item.indexOf('-');
if (dashIndex < 0) {
continue;
}
// Verify that the part before the dash is an integer
// and that the part after the dash is "preview"
const intPart: string = item.substring(0, dashIndex);
if (!IntRegex.test(intPart) || item.substring(dashIndex + 1) !== 'preview') {
continue;
}
currentVersion = parseInt(intPart, 10);
} else {
// Search for a directory like "6" or "7"
if (!IntRegex.test(item)) {
continue;
}
currentVersion = parseInt(item, 10);
}
// Ensure we haven't already seen a higher version
if (currentVersion <= highestSeenVersion) {
continue;
}
// Now look for the file
const exePath = path.join(powerShellInstallBaseDir, item, 'pwsh.exe');
if (!await pfs.fileExists(exePath)) {
continue;
}
pwshExePath = exePath;
highestSeenVersion = currentVersion;
}
if (!pwshExePath) {
return null;
}
const bitness: string = programFilesPath.includes('x86') ? ' (x86)' : '';
const preview: string = findPreview ? ' Preview' : '';
return new PossiblePowerShellExe(pwshExePath, `PowerShell${preview}${bitness}`, true);
}
async function findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}): Promise<IPossiblePowerShellExe | null> {
// We can't proceed if there's no LOCALAPPDATA path
if (!env.LOCALAPPDATA) {
return null;
}
// Find the base directory for MSIX application exe shortcuts
const msixAppDir = path.join(env.LOCALAPPDATA, 'Microsoft', 'WindowsApps');
if (!await pfs.dirExists(msixAppDir)) {
return null;
}
// Define whether we're looking for the preview or the stable
const { pwshMsixDirRegex, pwshMsixName } = findPreview
? { pwshMsixDirRegex: PwshPreviewMsixRegex, pwshMsixName: 'PowerShell Preview (Store)' }
: { pwshMsixDirRegex: PwshMsixRegex, pwshMsixName: 'PowerShell (Store)' };
// We should find only one such application, so return on the first one
for (const subdir of await pfs.readdir(msixAppDir)) {
if (pwshMsixDirRegex.test(subdir)) {
const pwshMsixPath = path.join(msixAppDir, subdir, 'pwsh.exe');
return new PossiblePowerShellExe(pwshMsixPath, pwshMsixName);
}
}
// If we find nothing, return null
return null;
}
function findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe {
const dotnetGlobalToolExePath: string = path.join(os.homedir(), '.dotnet', 'tools', 'pwsh.exe');
return new PossiblePowerShellExe(dotnetGlobalToolExePath, '.NET Core PowerShell Global Tool');
}
function findWinPS({ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): IPossiblePowerShellExe | null {
// x86 and ARM only have one WinPS on them
if (!isOS64Bit && useAlternateBitness) {
return null;
}
const systemFolderPath = getSystem32Path({ useAlternateBitness });
const winPSPath = path.join(systemFolderPath, 'WindowsPowerShell', 'v1.0', 'powershell.exe');
let displayName: string;
if (isProcess64Bit) {
displayName = useAlternateBitness
? WindowsPowerShell32BitLabel
: WindowsPowerShell64BitLabel;
} else if (isOS64Bit) {
displayName = useAlternateBitness
? WindowsPowerShell64BitLabel
: WindowsPowerShell32BitLabel;
} else {
// NOTE: ARM Windows devices also have Windows PowerShell x86 on them. There is no
// "ARM Windows PowerShell".
displayName = WindowsPowerShell32BitLabel;
}
return new PossiblePowerShellExe(winPSPath, displayName, true);
}
/**
* Iterates through all the possible well-known PowerShell installations on a machine.
* Returned values may not exist, but come with an .exists property
* which will check whether the executable exists.
*/
async function* enumerateDefaultPowerShellInstallations(): AsyncIterable<IPossiblePowerShellExe> {
// Find PSCore stable first
let pwshExe = await findPSCoreWindowsInstallation();
if (pwshExe) {
yield pwshExe;
}
// Windows may have a 32-bit pwsh.exe
pwshExe = await findPSCoreWindowsInstallation({ useAlternateBitness: true });
if (pwshExe) {
yield pwshExe;
}
// Also look for the MSIX/UWP installation
pwshExe = await findPSCoreMsix();
if (pwshExe) {
yield pwshExe;
}
// Look for the .NET global tool
// Some older versions of PowerShell have a bug in this where startup will fail,
// but this is fixed in newer versions
pwshExe = findPSCoreDotnetGlobalTool();
if (pwshExe) {
yield pwshExe;
}
// Look for PSCore preview
pwshExe = await findPSCoreWindowsInstallation({ findPreview: true });
if (pwshExe) {
yield pwshExe;
}
// Find a preview MSIX
pwshExe = await findPSCoreMsix({ findPreview: true });
if (pwshExe) {
yield pwshExe;
}
// Look for pwsh-preview with the opposite bitness
pwshExe = await findPSCoreWindowsInstallation({ useAlternateBitness: true, findPreview: true });
if (pwshExe) {
yield pwshExe;
}
// Finally, get Windows PowerShell
// Get the natural Windows PowerShell for the process bitness
pwshExe = findWinPS();
if (pwshExe) {
yield pwshExe;
}
// Get the alternate bitness Windows PowerShell
pwshExe = findWinPS({ useAlternateBitness: true });
if (pwshExe) {
yield pwshExe;
}
}
/**
* Iterates through PowerShell installations on the machine according
* to configuration passed in through the constructor.
* PowerShell items returned by this object are verified
* to exist on the filesystem.
*/
export async function* enumeratePowerShellInstallations(): AsyncIterable<IPowerShellExeDetails> {
// Get the default PowerShell installations first
for await (const defaultPwsh of enumerateDefaultPowerShellInstallations()) {
if (await defaultPwsh.exists()) {
yield defaultPwsh;
}
}
}
/**
* Returns the first available PowerShell executable found in the search order.
*/
export async function getFirstAvailablePowerShellInstallation(): Promise<IPowerShellExeDetails | null> {
for await (const pwsh of enumeratePowerShellInstallations()) {
return pwsh;
}
return null;
}
......@@ -5,6 +5,7 @@
import * as os from 'os';
import * as platform from 'vs/base/common/platform';
import { getFirstAvailablePowerShellInstallation } from 'vs/base/node/powershell';
import * as processes from 'vs/base/node/processes';
/**
......@@ -12,23 +13,37 @@ import * as processes from 'vs/base/node/processes';
* shell that the terminal uses by default.
* @param p The platform to detect the shell of.
*/
export function getSystemShell(p: platform.Platform, env = process.env as platform.IProcessEnvironment): string {
export async function getSystemShell(p: platform.Platform, env = process.env as platform.IProcessEnvironment): Promise<string> {
if (p === platform.Platform.Windows) {
if (platform.isWindows) {
return getSystemShellWindows(env);
return getSystemShellWindows();
}
// Don't detect Windows shell when not on Windows
return processes.getWindowsShell(env);
}
return getSystemShellUnixLike(p, env);
}
export function getSystemShellSync(p: platform.Platform, env = process.env as platform.IProcessEnvironment): string {
if (p === platform.Platform.Windows) {
if (platform.isWindows) {
return getSystemShellWindowsSync(env);
}
// Don't detect Windows shell when not on Windows
return processes.getWindowsShell(env);
}
return getSystemShellUnixLike(p, env);
}
let _TERMINAL_DEFAULT_SHELL_UNIX_LIKE: string | null = null;
function getSystemShellUnixLike(p: platform.Platform, env: platform.IProcessEnvironment): string {
// Only use $SHELL for the current OS
if (platform.isLinux && p === platform.Platform.Mac || platform.isMacintosh && p === platform.Platform.Linux) {
return '/bin/bash';
}
return getSystemShellUnixLike(env);
}
let _TERMINAL_DEFAULT_SHELL_UNIX_LIKE: string | null = null;
function getSystemShellUnixLike(env: platform.IProcessEnvironment): string {
if (!_TERMINAL_DEFAULT_SHELL_UNIX_LIKE) {
let unixLikeTerminal: string;
if (platform.isWindows) {
......@@ -59,12 +74,20 @@ function getSystemShellUnixLike(env: platform.IProcessEnvironment): string {
}
let _TERMINAL_DEFAULT_SHELL_WINDOWS: string | null = null;
function getSystemShellWindows(env: platform.IProcessEnvironment): string {
async function getSystemShellWindows(): Promise<string> {
if (!_TERMINAL_DEFAULT_SHELL_WINDOWS) {
const isAtLeastWindows10 = platform.isWindows && parseFloat(os.release()) >= 10;
const is32ProcessOn64Windows = env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
const powerShellPath = `${env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}\\WindowsPowerShell\\v1.0\\powershell.exe`;
_TERMINAL_DEFAULT_SHELL_WINDOWS = isAtLeastWindows10 ? powerShellPath : processes.getWindowsShell(env);
_TERMINAL_DEFAULT_SHELL_WINDOWS = (await getFirstAvailablePowerShellInstallation())!.exePath;
}
return _TERMINAL_DEFAULT_SHELL_WINDOWS;
}
function getSystemShellWindowsSync(env: platform.IProcessEnvironment): string {
if (_TERMINAL_DEFAULT_SHELL_WINDOWS) {
return _TERMINAL_DEFAULT_SHELL_WINDOWS;
}
const isAtLeastWindows10 = platform.isWindows && parseFloat(os.release()) >= 10;
const is32ProcessOn64Windows = env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
const powerShellPath = `${env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}\\WindowsPowerShell\\v1.0\\powershell.exe`;
return isAtLeastWindows10 ? powerShellPath : processes.getWindowsShell(env);
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as platform from 'vs/base/common/platform';
import * as fs from 'fs';
import { enumeratePowerShellInstallations, getFirstAvailablePowerShellInstallation, IPowerShellExeDetails } from 'vs/base/node/powershell';
function checkPath(exePath: string) {
// Check to see if the path exists
let pathCheckResult = false;
try {
const stat = fs.statSync(exePath);
pathCheckResult = stat.isFile();
} catch {
// fs.exists throws on Windows with SymbolicLinks so we
// also use lstat to try and see if the file exists.
try {
pathCheckResult = fs.statSync(fs.readlinkSync(exePath)).isFile();
} catch {
}
}
assert.strictEqual(pathCheckResult, true);
}
if (platform.isWindows) {
suite('PowerShell finder', () => {
test('Can find first available PowerShell', async () => {
const pwshExe = await getFirstAvailablePowerShellInstallation();
const exePath = pwshExe?.exePath;
assert.notStrictEqual(exePath, null);
assert.notStrictEqual(pwshExe?.displayName, null);
checkPath(exePath!);
});
test('Can enumerate PowerShells', async () => {
const pwshs = new Array<IPowerShellExeDetails>();
for await (const p of enumeratePowerShellInstallations()) {
pwshs.push(p);
}
// In Azure DevOps and GitHub Actions there should be an extra PowerShell since PowerShell 7 comes pre-installed
const minNumberOfPowerShells = process.env.TF_BUILD || process.env.CI ? 3 : 2;
assert.strictEqual(pwshs.length >= minNumberOfPowerShells, true, 'Found these PowerShells:\n' + pwshs.map(p => `${p.displayName}: ${p.exePath}`).join('\n'));
for (const pwsh of pwshs) {
checkPath(pwsh.exePath);
}
const lastIndex = pwshs.length - 1;
checkPath(pwshs[lastIndex].exePath);
assert.strictEqual(pwshs[lastIndex].displayName, 'Windows PowerShell (x86)');
if (process.arch === 'x64') {
const secondToLastIndex = pwshs.length - 2;
checkPath(pwshs[secondToLastIndex].exePath);
assert.strictEqual(pwshs[secondToLastIndex].displayName, 'Windows PowerShell');
}
});
});
}
......@@ -59,7 +59,7 @@ export async function resolveShellEnv(logService: ILogService, args: NativeParse
let unixShellEnvPromise: Promise<typeof process.env> | undefined = undefined;
async function doResolveUnixShellEnv(logService: ILogService): Promise<typeof process.env> {
const promise = new Promise<typeof process.env>((resolve, reject) => {
const promise = new Promise<typeof process.env>(async (resolve, reject) => {
const runAsNode = process.env['ELECTRON_RUN_AS_NODE'];
logService.trace('getUnixShellEnvironment#runAsNode', runAsNode);
......@@ -79,7 +79,7 @@ async function doResolveUnixShellEnv(logService: ILogService): Promise<typeof pr
logService.trace('getUnixShellEnvironment#env', env);
logService.trace('getUnixShellEnvironment#spawn', command);
const systemShellUnix = getSystemShell(platform);
const systemShellUnix = await getSystemShell(platform);
const child = spawn(systemShellUnix, ['-ilc', command], {
detached: true,
stdio: ['ignore', 'pipe', process.stderr],
......
......@@ -24,7 +24,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { withNullAsUndefined } from 'vs/base/common/types';
import { getSystemShell } from 'vs/base/node/shell';
import { getSystemShell, getSystemShellSync } from 'vs/base/node/shell';
import { generateUuid } from 'vs/base/common/uuid';
export class ExtHostTerminalService extends BaseExtHostTerminalService {
......@@ -34,6 +34,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
// TODO: Pull this from main side
private _isWorkspaceShellAllowed: boolean = false;
private _defaultShell: string | undefined;
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
......@@ -44,6 +45,12 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
@IExtHostInitDataService private _extHostInitDataService: IExtHostInitDataService
) {
super(true, extHostRpc);
// Getting the SystemShell is an async operation, however, the ExtHost terminal service is mostly synchronous
// and the API `vscode.env.shell` is also synchronous. The default shell _should_ be set when extensions are
// starting up but if not, we run getSystemShellSync below which gets a sane default.
getSystemShell(platform.platform).then(s => this._defaultShell = s);
this._updateLastActiveWorkspace();
this._updateVariableResolver();
this._registerListeners();
......@@ -78,10 +85,11 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
.inspect<string | string[]>(key.substr(key.lastIndexOf('.') + 1));
return this._apiInspectConfigToPlain<string | string[]>(setting);
};
return terminalEnvironment.getDefaultShell(
fetchSetting,
this._isWorkspaceShellAllowed,
getSystemShell(platform.platform),
this._defaultShell ?? getSystemShellSync(platform.platform),
process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'),
process.env.windir,
terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, this._variableResolver),
......
......@@ -8,12 +8,12 @@ import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/co
import { Registry } from 'vs/platform/registry/common/platform';
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import { getTerminalShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
import { getNoDefaultTerminalShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
// Desktop shell configuration are registered in electron-browser as their default values rely
// on process.env
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
configurationRegistry.registerConfiguration(getTerminalShellConfiguration());
configurationRegistry.registerConfiguration(getNoDefaultTerminalShellConfiguration());
// Register standard external terminal keybinding as integrated terminal when in web as the
// external terminal is not available
......
......@@ -401,7 +401,7 @@ export const terminalConfiguration: IConfigurationNode = {
}
};
export function getTerminalShellConfiguration(getSystemShell?: (p: Platform) => string): IConfigurationNode {
function getTerminalShellConfigurationStub(linux: string, osx: string, windows: string): IConfigurationNode {
return {
id: 'terminal',
order: 100,
......@@ -409,29 +409,34 @@ export function getTerminalShellConfiguration(getSystemShell?: (p: Platform) =>
type: 'object',
properties: {
'terminal.integrated.shell.linux': {
markdownDescription:
getSystemShell
? localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Linux))
: localize('terminal.integrated.shell.linux.noDefault', "The path of the shell that the terminal uses on Linux. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
markdownDescription: linux,
type: ['string', 'null'],
default: null
},
'terminal.integrated.shell.osx': {
markdownDescription:
getSystemShell
? localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Mac))
: localize('terminal.integrated.shell.osx.noDefault', "The path of the shell that the terminal uses on macOS. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
markdownDescription: osx,
type: ['string', 'null'],
default: null
},
'terminal.integrated.shell.windows': {
markdownDescription:
getSystemShell
? localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Windows))
: localize('terminal.integrated.shell.windows.noDefault', "The path of the shell that the terminal uses on Windows. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
markdownDescription: windows,
type: ['string', 'null'],
default: null
}
}
};
}
export function getNoDefaultTerminalShellConfiguration(): IConfigurationNode {
return getTerminalShellConfigurationStub(
localize('terminal.integrated.shell.linux.noDefault', "The path of the shell that the terminal uses on Linux. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
localize('terminal.integrated.shell.osx.noDefault', "The path of the shell that the terminal uses on macOS. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
localize('terminal.integrated.shell.windows.noDefault', "The path of the shell that the terminal uses on Windows. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."));
}
export async function getTerminalShellConfiguration(getSystemShell: (p: Platform) => Promise<string>): Promise<IConfigurationNode> {
return getTerminalShellConfigurationStub(
localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", await getSystemShell(Platform.Linux)),
localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", await getSystemShell(Platform.Mac)),
localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", await getSystemShell(Platform.Windows)));
}
......@@ -24,4 +24,5 @@ workbenchRegistry.registerWorkbenchContribution(TerminalNativeContribution, Life
// Register configurations
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
configurationRegistry.registerConfiguration(getTerminalShellConfiguration(getSystemShell));
getTerminalShellConfiguration(getSystemShell).then(config => configurationRegistry.registerConfiguration(config));
......@@ -82,7 +82,7 @@ export class TerminalInstanceService implements ITerminalInstanceService {
return this._storageService.getBoolean(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, StorageScope.WORKSPACE, false);
}
public getDefaultShellAndArgs(useAutomationShell: boolean, platformOverride: Platform = platform): Promise<{ shell: string, args: string | string[] }> {
public async getDefaultShellAndArgs(useAutomationShell: boolean, platformOverride: Platform = platform): Promise<{ shell: string, args: string | string[] }> {
const isWorkspaceShellAllowed = this._isWorkspaceShellAllowed();
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot();
let lastActiveWorkspace = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : undefined;
......@@ -90,7 +90,7 @@ export class TerminalInstanceService implements ITerminalInstanceService {
const shell = getDefaultShell(
(key) => this._configurationService.inspect(key),
isWorkspaceShellAllowed,
getSystemShell(platformOverride),
await getSystemShell(platformOverride),
process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'),
process.env.windir,
createVariableResolver(lastActiveWorkspace, this._configurationResolverService),
......
......@@ -5,10 +5,11 @@
import * as os from 'os';
import * as platform from 'vs/base/common/platform';
import { readFile, fileExists, stat } from 'vs/base/node/pfs';
import { readFile, fileExists, stat, lstat } from 'vs/base/node/pfs';
import { LinuxDistro, IShellDefinition } from 'vs/workbench/contrib/terminal/common/terminal';
import { coalesce } from 'vs/base/common/arrays';
import { normalize, basename } from 'vs/base/common/path';
import { enumeratePowerShellInstallations } from 'vs/base/node/powershell';
let detectedDistro = LinuxDistro.Unknown;
if (platform.isLinux) {
......@@ -58,8 +59,6 @@ async function detectAvailableWindowsShells(): Promise<IShellDefinition[]> {
const expectedLocations: { [key: string]: string[] } = {
'Command Prompt': [`${system32Path}\\cmd.exe`],
'Windows PowerShell': [`${system32Path}\\WindowsPowerShell\\v1.0\\powershell.exe`],
'PowerShell': [await getShellPathFromRegistry('pwsh')],
'WSL Bash': [`${system32Path}\\${useWSLexe ? 'wsl.exe' : 'bash.exe'}`],
'Git Bash': [
`${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`,
......@@ -74,6 +73,12 @@ async function detectAvailableWindowsShells(): Promise<IShellDefinition[]> {
// `${process.env['HOMEDRIVE']}\\cygwin\\bin\\bash.exe`
// ]
};
// Add all of the different kinds of PowerShells
for await (const pwshExe of enumeratePowerShellInstallations()) {
expectedLocations[pwshExe.displayName] = [pwshExe.exePath];
}
const promises: Promise<IShellDefinition | undefined>[] = [];
Object.keys(expectedLocations).forEach(key => promises.push(validateShellPaths(key, expectedLocations[key])));
const shells = await Promise.all(promises);
......@@ -107,16 +112,22 @@ async function validateShellPaths(label: string, potentialPaths: string[]): Prom
path: current
};
}
} catch { /* noop */ }
return validateShellPaths(label, potentialPaths);
}
async function getShellPathFromRegistry(shellName: string): Promise<string> {
const Registry = await import('vscode-windows-registry');
try {
const shellPath = Registry.GetStringRegKey('HKEY_LOCAL_MACHINE', `SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${shellName}.exe`, '');
return shellPath ? shellPath : '';
} catch (error) {
return '';
} catch (e) {
// Also try using lstat as some symbolic links on Windows
// throw 'permission denied' using 'stat' but don't throw
// using 'lstat'
try {
const result = await lstat(normalize(current));
if (result.isFile() || result.isSymbolicLink()) {
return {
label,
path: current
};
}
}
catch (e) {
// noop
}
}
return validateShellPaths(label, potentialPaths);
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册