sussudio
Version:
An unofficial VS Code Internal API
247 lines (246 loc) • 12.7 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as os from 'os';
import { FileAccess } from "../../../base/common/network.mjs";
import { getCaseInsensitive } from "../../../base/common/objects.mjs";
import * as path from "../../../base/common/path.mjs";
import { isWindows } from "../../../base/common/platform.mjs";
import * as process from "../../../base/common/process.mjs";
import { format } from "../../../base/common/strings.mjs";
import { isString } from "../../../base/common/types.mjs";
import * as pfs from "../../../base/node/pfs.mjs";
export function getWindowsBuildNumber() {
const osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release());
let buildNumber = 0;
if (osVersion && osVersion.length === 4) {
buildNumber = parseInt(osVersion[3]);
}
return buildNumber;
}
export async function findExecutable(command, cwd, paths, env = process.env, exists = pfs.Promises.exists) {
// If we have an absolute path then we take it.
if (path.isAbsolute(command)) {
return await exists(command) ? command : undefined;
}
if (cwd === undefined) {
cwd = process.cwd();
}
const dir = path.dirname(command);
if (dir !== '.') {
// We have a directory and the directory is relative (see above). Make the path absolute
// to the current working directory.
const fullPath = path.join(cwd, command);
return await exists(fullPath) ? fullPath : undefined;
}
const envPath = getCaseInsensitive(env, 'PATH');
if (paths === undefined && isString(envPath)) {
paths = envPath.split(path.delimiter);
}
// No PATH environment. Make path absolute to the cwd.
if (paths === undefined || paths.length === 0) {
const fullPath = path.join(cwd, command);
return await exists(fullPath) ? fullPath : undefined;
}
// We have a simple file name. We get the path variable from the env
// and try to find the executable on the path.
for (const pathEntry of paths) {
// The path entry is absolute.
let fullPath;
if (path.isAbsolute(pathEntry)) {
fullPath = path.join(pathEntry, command);
}
else {
fullPath = path.join(cwd, pathEntry, command);
}
if (await exists(fullPath)) {
return fullPath;
}
if (isWindows) {
let withExtension = fullPath + '.com';
if (await exists(withExtension)) {
return withExtension;
}
withExtension = fullPath + '.exe';
if (await exists(withExtension)) {
return withExtension;
}
}
}
const fullPath = path.join(cwd, command);
return await exists(fullPath) ? fullPath : undefined;
}
/**
* For a given shell launch config, returns arguments to replace and an optional environment to
* mixin to the SLC's environment to enable shell integration. This must be run within the context
* that creates the process to ensure accuracy. Returns undefined if shell integration cannot be
* enabled.
*/
export function getShellIntegrationInjection(shellLaunchConfig, options, env, logService, productService) {
// Shell integration arg injection is disabled when:
// - The global setting is disabled
// - There is no executable (not sure what script to run)
// - The terminal is used by a feature like tasks or debugging
if (!options.shellIntegration.enabled || !shellLaunchConfig.executable || shellLaunchConfig.isFeatureTerminal || shellLaunchConfig.hideFromUser || shellLaunchConfig.ignoreShellIntegration || (isWindows && !options.windowsEnableConpty)) {
return undefined;
}
const originalArgs = shellLaunchConfig.args;
const shell = process.platform === 'win32' ? path.basename(shellLaunchConfig.executable).toLowerCase() : path.basename(shellLaunchConfig.executable);
const appRoot = path.dirname(FileAccess.asFileUri('').fsPath);
let newArgs;
const envMixin = {
'VSCODE_INJECTION': '1'
};
// Windows
if (isWindows) {
if (shell === 'pwsh.exe' || shell === 'powershell.exe') {
if (!originalArgs || arePwshImpliedArgs(originalArgs)) {
newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.WindowsPwsh);
}
else if (arePwshLoginArgs(originalArgs)) {
newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.WindowsPwshLogin);
}
if (!newArgs) {
return undefined;
}
newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array
newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot, '');
return { newArgs, envMixin };
}
logService.warn(`Shell integration cannot be enabled for executable "${shellLaunchConfig.executable}" and args`, shellLaunchConfig.args);
return undefined;
}
// Linux & macOS
switch (shell) {
case 'bash': {
if (!originalArgs || originalArgs.length === 0) {
newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash);
}
else if (areZshBashLoginArgs(originalArgs)) {
envMixin['VSCODE_SHELL_LOGIN'] = '1';
newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash);
}
if (!newArgs) {
return undefined;
}
newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array
newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot);
return { newArgs, envMixin };
}
case 'fish': {
// The injection mechanism used for fish is to add a custom dir to $XDG_DATA_DIRS which
// is similar to $ZDOTDIR in zsh but contains a list of directories to run from.
const oldDataDirs = env?.XDG_DATA_DIRS ?? '/usr/local/share:/usr/share';
const newDataDir = path.join(appRoot, 'out/vs/workbench/contrib/xdg_data');
envMixin['XDG_DATA_DIRS'] = `${oldDataDirs}:${newDataDir}`;
return { newArgs: undefined, envMixin };
}
case 'pwsh': {
if (!originalArgs || arePwshImpliedArgs(originalArgs)) {
newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Pwsh);
}
else if (arePwshLoginArgs(originalArgs)) {
newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.PwshLogin);
}
if (!newArgs) {
return undefined;
}
newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array
newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot, '');
return { newArgs, envMixin };
}
case 'zsh': {
if (!originalArgs || originalArgs.length === 0) {
newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Zsh);
}
else if (areZshBashLoginArgs(originalArgs)) {
newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.ZshLogin);
}
else if (originalArgs === shellIntegrationArgs.get(ShellIntegrationExecutable.Zsh) || originalArgs === shellIntegrationArgs.get(ShellIntegrationExecutable.ZshLogin)) {
newArgs = originalArgs;
}
if (!newArgs) {
return undefined;
}
newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array
newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot);
// Move .zshrc into $ZDOTDIR as the way to activate the script
let username;
try {
username = os.userInfo().username;
}
catch {
username = 'unknown';
}
const zdotdir = path.join(os.tmpdir(), `${username}-${productService.applicationName}-zsh`);
envMixin['ZDOTDIR'] = zdotdir;
const userZdotdir = env?.ZDOTDIR ?? os.homedir() ?? `~`;
envMixin['USER_ZDOTDIR'] = userZdotdir;
const filesToCopy = [];
filesToCopy.push({
source: path.join(appRoot, 'out/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh'),
dest: path.join(zdotdir, '.zshrc')
});
filesToCopy.push({
source: path.join(appRoot, 'out/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh'),
dest: path.join(zdotdir, '.zprofile')
});
filesToCopy.push({
source: path.join(appRoot, 'out/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh'),
dest: path.join(zdotdir, '.zshenv')
});
filesToCopy.push({
source: path.join(appRoot, 'out/vs/workbench/contrib/terminal/browser/media/shellIntegration-login.zsh'),
dest: path.join(zdotdir, '.zlogin')
});
return { newArgs, envMixin, filesToCopy };
}
}
logService.warn(`Shell integration cannot be enabled for executable "${shellLaunchConfig.executable}" and args`, shellLaunchConfig.args);
return undefined;
}
var ShellIntegrationExecutable;
(function (ShellIntegrationExecutable) {
ShellIntegrationExecutable["WindowsPwsh"] = "windows-pwsh";
ShellIntegrationExecutable["WindowsPwshLogin"] = "windows-pwsh-login";
ShellIntegrationExecutable["Pwsh"] = "pwsh";
ShellIntegrationExecutable["PwshLogin"] = "pwsh-login";
ShellIntegrationExecutable["Zsh"] = "zsh";
ShellIntegrationExecutable["ZshLogin"] = "zsh-login";
ShellIntegrationExecutable["Bash"] = "bash";
})(ShellIntegrationExecutable || (ShellIntegrationExecutable = {}));
const shellIntegrationArgs = new Map();
// The try catch swallows execution policy errors in the case of the archive distributable
shellIntegrationArgs.set(ShellIntegrationExecutable.WindowsPwsh, ['-noexit', '-command', 'try { . \"{0}\\out\\vs\\workbench\\contrib\\terminal\\browser\\media\\shellIntegration.ps1\" } catch {}{1}']);
shellIntegrationArgs.set(ShellIntegrationExecutable.WindowsPwshLogin, ['-l', '-noexit', '-command', 'try { . \"{0}\\out\\vs\\workbench\\contrib\\terminal\\browser\\media\\shellIntegration.ps1\" } catch {}{1}']);
shellIntegrationArgs.set(ShellIntegrationExecutable.Pwsh, ['-noexit', '-command', '. "{0}/out/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1"{1}']);
shellIntegrationArgs.set(ShellIntegrationExecutable.PwshLogin, ['-l', '-noexit', '-command', '. "{0}/out/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1"']);
shellIntegrationArgs.set(ShellIntegrationExecutable.Zsh, ['-i']);
shellIntegrationArgs.set(ShellIntegrationExecutable.ZshLogin, ['-il']);
shellIntegrationArgs.set(ShellIntegrationExecutable.Bash, ['--init-file', '{0}/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh']);
const loginArgs = ['-login', '-l'];
const pwshImpliedArgs = ['-nol', '-nologo'];
function arePwshLoginArgs(originalArgs) {
if (typeof originalArgs === 'string') {
return loginArgs.includes(originalArgs.toLowerCase());
}
else {
return originalArgs.length === 1 && loginArgs.includes(originalArgs[0].toLowerCase()) ||
(originalArgs.length === 2 &&
(((loginArgs.includes(originalArgs[0].toLowerCase())) || loginArgs.includes(originalArgs[1].toLowerCase())))
&& ((pwshImpliedArgs.includes(originalArgs[0].toLowerCase())) || pwshImpliedArgs.includes(originalArgs[1].toLowerCase())));
}
}
function arePwshImpliedArgs(originalArgs) {
if (typeof originalArgs === 'string') {
return pwshImpliedArgs.includes(originalArgs.toLowerCase());
}
else {
return originalArgs.length === 0 || originalArgs?.length === 1 && pwshImpliedArgs.includes(originalArgs[0].toLowerCase());
}
}
function areZshBashLoginArgs(originalArgs) {
return originalArgs === 'string' && loginArgs.includes(originalArgs.toLowerCase())
|| typeof originalArgs !== 'string' && originalArgs.length === 1 && loginArgs.includes(originalArgs[0].toLowerCase());
}