diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.js b/src/vs/base/parts/sandbox/electron-browser/preload.js index 702a28ea652..3ee0b2ca853 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.js +++ b/src/vs/base/parts/sandbox/electron-browser/preload.js @@ -108,18 +108,13 @@ get type() { return 'renderer'; }, get execPath() { return process.execPath; }, - _resolveEnv: undefined, resolveEnv: /** * @param userEnv {{[key: string]: string}} * @returns {Promise} */ function (userEnv) { - if (!this._resolveEnv) { - this._resolveEnv = resolveEnv(userEnv); - } - - return this._resolveEnv; + return resolveEnv(userEnv); }, getProcessMemoryInfo: @@ -194,6 +189,9 @@ return true; } + /** @type {Promise | undefined} */ + let resolvedEnv = undefined; + /** * If VSCode is not run from a terminal, we should resolve additional * shell specific environment from the OS shell to ensure we are seeing @@ -204,23 +202,27 @@ * @returns {Promise} */ function resolveEnv(userEnv) { + if (!resolvedEnv) { - // Apply `userEnv` directly - Object.assign(process.env, userEnv); + // Apply `userEnv` directly + Object.assign(process.env, userEnv); - // Resolve `shellEnv` from the main side - return new Promise(function (resolve) { - ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnv) { + // Resolve `shellEnv` from the main side + resolvedEnv = new Promise(function (resolve) { + ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnv) { - // Assign all keys of the shell environment to our process environment - // But make sure that the user environment wins in the end - Object.assign(process.env, shellEnv, userEnv); + // Assign all keys of the shell environment to our process environment + // But make sure that the user environment wins in the end + Object.assign(process.env, shellEnv, userEnv); - resolve(); + resolve(); + }); + + ipcRenderer.send('vscode:fetchShellEnv'); }); + } - ipcRenderer.send('vscode:fetchShellEnv'); - }); + return resolvedEnv; } //#endregion diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 1116cf2135f..69f3a2a8637 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -35,9 +35,10 @@ export interface ISandboxNodeProcess extends INodeProcess { readonly execPath: string; /** - * Resolve the true process environment to use. There are different layers of environment - * that will apply: - * - `process.env`: this is the actual environment of the process + * Resolve the true process environment to use and apply it to `process.env`. + * + * There are different layers of environment that will apply: + * - `process.env`: this is the actual environment of the process before this method * - `shellEnv` : if the program was not started from a terminal, we resolve all shell * variables to get the same experience as if the program was started from * a terminal (Linux, macOS) @@ -45,6 +46,9 @@ export interface ISandboxNodeProcess extends INodeProcess { * from a terminal and changed certain variables * * The order of overwrites is `process.env` < `shellEnv` < `userEnv`. + * + * It is critical that every process awaits this method early on startup to get the right + * set of environment in `process.env`. */ resolveEnv(userEnv: IProcessEnvironment): Promise; diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 597c45bad02..5f91f3fd6d7 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -83,7 +83,6 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { EncryptionMainService, IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService'; import { ActiveWindowManager } from 'vs/platform/windows/common/windowTracker'; import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from 'vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { DisplayMainService, IDisplayMainService } from 'vs/platform/display/electron-main/displayMainService'; import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; @@ -272,45 +271,45 @@ export class CodeApplication extends Disposable { let replied = false; function acceptShellEnv(env: NodeJS.ProcessEnv): void { - clearTimeout(shellEnvTimeoutWarningHandle); + clearTimeout(shellEnvSlowWarningHandle); + clearTimeout(shellEnvTimeoutErrorHandle); if (!replied) { - webContents.send('vscode:acceptShellEnv', env); replied = true; + + if (!webContents.isDestroyed()) { + webContents.send('vscode:acceptShellEnv', env); + } } } - const shellEnvTimeoutWarningHandle = setTimeout(function () { - window?.sendWhenReady('vscode:showShellEnvTimeoutWarning'); // notify inside window if we have one + // Handle slow shell environment resolve calls: + // - a warning after 3s but continue to resolve + // - an error after 10s and stop trying to resolve + const shellEnvSlowWarningHandle = setTimeout(() => window?.sendWhenReady('vscode:showShellEnvSlowWarning'), 3000); + const shellEnvTimeoutErrorHandle = setTimeout(function () { + window?.sendWhenReady('vscode:showShellEnvTimeoutError'); acceptShellEnv({}); }, 10000); - try { - - // Prefer to use the args and env from the target window - // when resolving the shell env. It is possible that - // a first window was opened from the UI but a second - // from the CLI and that has implications for wether to - // resolve the shell environment or not. - let args: NativeParsedArgs; - let env: NodeJS.ProcessEnv; - if (window?.config) { - args = window.config; - env = { ...process.env, ...window.config.userEnv }; - } else { - args = this.environmentService.args; - env = process.env; - } - - // Resolve shell env - const shellEnv = await resolveShellEnv(this.logService, args, env); - acceptShellEnv(shellEnv); - } catch (error) { - window?.sendWhenReady('vscode:showShellEnvError', toErrorMessage(error)); // notify inside window if we have one - acceptShellEnv({}); - - this.logService.error('Error fetching shell env', error); + // Prefer to use the args and env from the target window + // when resolving the shell env. It is possible that + // a first window was opened from the UI but a second + // from the CLI and that has implications for wether to + // resolve the shell environment or not. + let args: NativeParsedArgs; + let env: NodeJS.ProcessEnv; + if (window?.config) { + args = window.config; + env = { ...process.env, ...window.config.userEnv }; + } else { + args = this.environmentService.args; + env = process.env; } + + // Resolve shell env + const shellEnv = await resolveShellEnv(this.logService, args, env); + acceptShellEnv(shellEnv); }); ipc.on('vscode:toggleDevTools', (event: IpcMainEvent) => event.sender.toggleDevTools()); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 6019f2d2062..135f1b70b91 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -685,7 +685,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // (for https://github.com/microsoft/vscode/issues/108571) const currentUserEnv = (this.currentConfig ?? this.pendingLoadConfig)?.userEnv; if (currentUserEnv && isLaunchedFromCli(currentUserEnv) && !isLaunchedFromCli(config.userEnv)) { - config.userEnv = currentUserEnv; + config.userEnv = { ...currentUserEnv, ...config.userEnv }; // still allow to override certain environment as passed in } // If this is the first time the window is loaded, we associate the paths diff --git a/src/vs/code/node/shellEnv.ts b/src/vs/code/node/shellEnv.ts index 79ba3d252fe..29ef8c143b3 100644 --- a/src/vs/code/node/shellEnv.ts +++ b/src/vs/code/node/shellEnv.ts @@ -9,6 +9,7 @@ import { isWindows } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; /** * We need to get the environment from a user's shell. @@ -56,7 +57,7 @@ export async function resolveShellEnv(logService: ILogService, args: NativeParse let unixShellEnvPromise: Promise | undefined = undefined; -function doResolveUnixShellEnv(logService: ILogService): Promise { +async function doResolveUnixShellEnv(logService: ILogService): Promise { const promise = new Promise((resolve, reject) => { const runAsNode = process.env['ELECTRON_RUN_AS_NODE']; logService.trace('getUnixShellEnvironment#runAsNode', runAsNode); @@ -125,6 +126,11 @@ function doResolveUnixShellEnv(logService: ILogService): Promise ({})); + try { + return await promise; + } catch (error) { + logService.error('getUnixShellEnvironment#error', toErrorMessage(error)); + + return {}; // ignore any errors + } } diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index cc1d0a1288b..30f5d85c93e 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -7,7 +7,6 @@ import * as fs from 'fs'; import { basename, normalize, join, posix } from 'vs/base/common/path'; import { localize } from 'vs/nls'; import * as arrays from 'vs/base/common/arrays'; -import { mixin } from 'vs/base/common/objects'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; @@ -1369,7 +1368,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow { // Build INativeWindowConfiguration from config and options - const configuration: INativeWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI + const configuration = { ...options.cli } as INativeWindowConfiguration; configuration.appRoot = this.environmentService.appRoot; configuration.machineId = this.machineId; configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir; diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 1b239b03cdd..4557a2c480f 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -246,7 +246,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan diffEditorConfiguration.diffWordWrap = <'off' | 'on' | 'inherit' | undefined>diffEditorConfiguration.wordWrap; delete diffEditorConfiguration.wordWrap; - objects.mixin(editorConfiguration, diffEditorConfiguration); + Object.assign(editorConfiguration, diffEditorConfiguration); } return editorConfiguration; diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index a0416530459..9cae4d21417 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -67,6 +67,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa @IEditorGroupsService protected editorGroupService: IEditorGroupsService ) { super(id, telemetryService, themeService, storageService); + this._instantiationService = instantiationService; this.editorMemento = this.getEditorMemento(editorGroupService, BaseTextEditor.TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100); diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index aabf783b310..cd990d2a6b6 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -32,7 +32,7 @@ import { IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/wo import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; import { IProductService } from 'vs/platform/product/common/productService'; -import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService, IPromptChoice, NeverShowAgainScope, Severity } from 'vs/platform/notification/common/notification'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; @@ -192,15 +192,19 @@ export class NativeWindow extends Disposable { run: () => this.openerService.open('https://go.microsoft.com/fwlink/?linkid=2149667') }]; - ipcRenderer.on('vscode:showShellEnvError', (event: unknown, error: string) => this.notificationService.prompt( - Severity.Error, - nls.localize('shellEnvError', "Unable to resolve your shell environment: {0}", error), - choices + ipcRenderer.on('vscode:showShellEnvSlowWarning', () => this.notificationService.prompt( + Severity.Warning, + nls.localize('shellEnvSlowWarning', "Resolving your shell environment is taking very long. Please review your shell configuration."), + choices, + { + sticky: true, + neverShowAgain: { id: 'ignoreShellEnvSlowWarning', scope: NeverShowAgainScope.GLOBAL } + } )); - ipcRenderer.on('vscode:showShellEnvTimeoutWarning', () => this.notificationService.prompt( - Severity.Warning, - nls.localize('shellEnvTimeoutWarning', "Unable to resolve your shell environment in a reasonable time. Please review your shell configuration."), + ipcRenderer.on('vscode:showShellEnvTimeoutError', () => this.notificationService.prompt( + Severity.Error, + nls.localize('shellEnvTimeoutError', "Unable to resolve your shell environment in a reasonable time. Please review your shell configuration."), choices )); diff --git a/src/vs/workbench/services/timer/browser/timerService.ts b/src/vs/workbench/services/timer/browser/timerService.ts index b63386aa9f2..2f146375db0 100644 --- a/src/vs/workbench/services/timer/browser/timerService.ts +++ b/src/vs/workbench/services/timer/browser/timerService.ts @@ -193,7 +193,7 @@ export interface IStartupMetrics { * * Happens in the renderer-process * * Measured with the `willWaitForShellEnv` and `didWaitForShellEnv` performance marks. */ - readonly ellapsedWaitForShellEnv?: number; + readonly ellapsedWaitForShellEnv: number; /** * The time it took to require the workspace storage DB, connect to it