提交 394b7ece 编写于 作者: D Daniel Imms

Move more create process over, remove node dep on terminal instance

上级 c9e02ff7
...@@ -470,10 +470,11 @@ export interface ITerminalProcessManager extends IDisposable { ...@@ -470,10 +470,11 @@ export interface ITerminalProcessManager extends IDisposable {
ptyProcessReady: TPromise<void>; ptyProcessReady: TPromise<void>;
shellProcessId: number; shellProcessId: number;
onShellProcessIdReady: Event<number>; onShellProcessIdReady: Event<number>;
initialCwd: string;
acceptProcessMessage(message): void; acceptProcessMessage(message): void;
addDisposable(disposable: IDisposable); addDisposable(disposable: IDisposable);
createProcess(launchConfig: IShellLaunchConfig); createProcess(shellLaunchConfig: IShellLaunchConfig);
write(data: string): void; write(data: string): void;
} }
......
...@@ -3,21 +3,17 @@ ...@@ -3,21 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as cp from 'child_process';
import * as os from 'os';
import * as path from 'path';
import * as lifecycle from 'vs/base/common/lifecycle'; import * as lifecycle from 'vs/base/common/lifecycle';
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
import * as platform from 'vs/base/common/platform'; import * as platform from 'vs/base/common/platform';
import * as dom from 'vs/base/browser/dom'; import * as dom from 'vs/base/browser/dom';
import * as paths from 'vs/base/common/paths';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import Uri from 'vs/base/common/uri';
import { WindowsShellHelper } from 'vs/workbench/parts/terminal/electron-browser/windowsShellHelper'; import { WindowsShellHelper } from 'vs/workbench/parts/terminal/electron-browser/windowsShellHelper';
import { Terminal as XTermTerminal } from 'vscode-xterm'; import { Terminal as XTermTerminal } from 'vscode-xterm';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IStringDictionary } from 'vs/base/common/collections';
import { ITerminalInstance, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, IShellLaunchConfig, ITerminalProcessManager, ITerminalProcessMessage } from 'vs/workbench/parts/terminal/common/terminal'; import { ITerminalInstance, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, IShellLaunchConfig, ITerminalProcessManager, ITerminalProcessMessage } from 'vs/workbench/parts/terminal/common/terminal';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
...@@ -29,12 +25,8 @@ import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } ...@@ -29,12 +25,8 @@ import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService }
import { scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import pkg from 'vs/platform/node/package';
import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/parts/terminal/electron-browser/terminalColorRegistry'; import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/parts/terminal/electron-browser/terminalColorRegistry';
import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
...@@ -100,7 +92,7 @@ export class TerminalInstance implements ITerminalInstance { ...@@ -100,7 +92,7 @@ export class TerminalInstance implements ITerminalInstance {
private _rows: number; private _rows: number;
private _messageTitleListener: (message: { type: string, content: string }) => void; private _messageTitleListener: (message: { type: string, content: string }) => void;
// private _preLaunchInputQueue: string; // private _preLaunchInputQueue: string;
private _initialCwd: string; // private _initialCwd: string;
private _windowsShellHelper: WindowsShellHelper; private _windowsShellHelper: WindowsShellHelper;
private _onLineDataListeners: ((lineData: string) => void)[]; private _onLineDataListeners: ((lineData: string) => void)[];
private _xtermReadyPromise: TPromise<void>; private _xtermReadyPromise: TPromise<void>;
...@@ -136,10 +128,7 @@ export class TerminalInstance implements ITerminalInstance { ...@@ -136,10 +128,7 @@ export class TerminalInstance implements ITerminalInstance {
@IPanelService private readonly _panelService: IPanelService, @IPanelService private readonly _panelService: IPanelService,
@IInstantiationService private readonly _instantiationService: IInstantiationService, @IInstantiationService private readonly _instantiationService: IInstantiationService,
@IClipboardService private readonly _clipboardService: IClipboardService, @IClipboardService private readonly _clipboardService: IClipboardService,
@IHistoryService private readonly _historyService: IHistoryService,
@IThemeService private readonly _themeService: IThemeService, @IThemeService private readonly _themeService: IThemeService,
@IConfigurationResolverService private readonly _configurationResolverService: IConfigurationResolverService,
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
@IConfigurationService private readonly _configurationService: IConfigurationService, @IConfigurationService private readonly _configurationService: IConfigurationService,
@ILogService private _logService: ILogService @ILogService private _logService: ILogService
) { ) {
...@@ -315,7 +304,7 @@ export class TerminalInstance implements ITerminalInstance { ...@@ -315,7 +304,7 @@ export class TerminalInstance implements ITerminalInstance {
this._xterm.on('linefeed', () => this._onLineFeed()); this._xterm.on('linefeed', () => this._onLineFeed());
this._processManager.process.on('message', (message) => this._sendPtyDataToXterm(message)); this._processManager.process.on('message', (message) => this._sendPtyDataToXterm(message));
this._xterm.on('data', data => this._processManager.write(data)); this._xterm.on('data', data => this._processManager.write(data));
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, platform.platform, this._initialCwd); this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, platform.platform, this._processManager.initialCwd);
this._commandTracker = new TerminalCommandTracker(this._xterm); this._commandTracker = new TerminalCommandTracker(this._xterm);
this._instanceDisposables.push(this._themeService.onThemeChange(theme => this._updateTheme(theme))); this._instanceDisposables.push(this._themeService.onThemeChange(theme => this._updateTheme(theme)));
} }
...@@ -636,67 +625,39 @@ export class TerminalInstance implements ITerminalInstance { ...@@ -636,67 +625,39 @@ export class TerminalInstance implements ITerminalInstance {
this._terminalHasTextContextKey.set(isActive && this.hasSelection()); this._terminalHasTextContextKey.set(isActive && this.hasSelection());
} }
protected _getCwd(shell: IShellLaunchConfig, root: Uri): string {
if (shell.cwd) {
return shell.cwd;
}
let cwd: string;
// TODO: Handle non-existent customCwd
if (!shell.ignoreConfigurationCwd) {
// Evaluate custom cwd first
const customCwd = this._configHelper.config.cwd;
if (customCwd) {
if (path.isAbsolute(customCwd)) {
cwd = customCwd;
} else if (root) {
cwd = path.normalize(path.join(root.fsPath, customCwd));
}
}
}
// If there was no custom cwd or it was relative with no workspace
if (!cwd) {
cwd = root ? root.fsPath : os.homedir();
}
return TerminalInstance._sanitizeCwd(cwd);
}
protected _createProcess(): void { protected _createProcess(): void {
// TODO: This should be injected in to the terminal instance (from service?) // TODO: This should be injected in to the terminal instance (from service?)
this._processManager = this._instantiationService.createInstance(TerminalProcessManager); this._processManager = this._instantiationService.createInstance(TerminalProcessManager, this._configHelper, this._cols, this._rows);
this._processManager.onShellProcessIdReady(() => this._onProcessIdReady.fire(this)); this._processManager.onShellProcessIdReady(() => this._onProcessIdReady.fire(this));
this._processManager.createProcess(this._shellLaunchConfig); this._processManager.createProcess(this._shellLaunchConfig);
const locale = this._configHelper.config.setLocaleVariables ? platform.locale : undefined; // const locale = this._configHelper.config.setLocaleVariables ? platform.locale : undefined;
if (!this._shellLaunchConfig.executable) { // if (!this._shellLaunchConfig.executable) {
this._configHelper.mergeDefaultShellPathAndArgs(this._shellLaunchConfig); // this._configHelper.mergeDefaultShellPathAndArgs(this._shellLaunchConfig);
} // }
const lastActiveWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot('file'); // const lastActiveWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot('file');
this._initialCwd = this._getCwd(this._shellLaunchConfig, lastActiveWorkspaceRootUri); // this._initialCwd = this._getCwd(this._shellLaunchConfig, lastActiveWorkspaceRootUri);
// Resolve env vars from config and shell // // Resolve env vars from config and shell
const lastActiveWorkspaceRoot = this._workspaceContextService.getWorkspaceFolder(lastActiveWorkspaceRootUri); // const lastActiveWorkspaceRoot = this._workspaceContextService.getWorkspaceFolder(lastActiveWorkspaceRootUri);
const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux'); // const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
const envFromConfig = TerminalInstance.resolveConfigurationVariables(this._configurationResolverService, { ...this._configHelper.config.env[platformKey] }, lastActiveWorkspaceRoot); // const envFromConfig = TerminalInstance.resolveConfigurationVariables(this._configurationResolverService, { ...this._configHelper.config.env[platformKey] }, lastActiveWorkspaceRoot);
const envFromShell = TerminalInstance.resolveConfigurationVariables(this._configurationResolverService, { ...this._shellLaunchConfig.env }, lastActiveWorkspaceRoot); // const envFromShell = TerminalInstance.resolveConfigurationVariables(this._configurationResolverService, { ...this._shellLaunchConfig.env }, lastActiveWorkspaceRoot);
this._shellLaunchConfig.env = envFromShell; // this._shellLaunchConfig.env = envFromShell;
// Merge process env with the env from config // // Merge process env with the env from config
const parentEnv = { ...process.env }; // const parentEnv = { ...process.env };
TerminalInstance.mergeEnvironments(parentEnv, envFromConfig); // TerminalInstance.mergeEnvironments(parentEnv, envFromConfig);
// Continue env initialization, merging in the env from the launch // // Continue env initialization, merging in the env from the launch
// config and adding keys that are needed to create the process // // config and adding keys that are needed to create the process
const env = TerminalInstance.createTerminalEnv(parentEnv, this._shellLaunchConfig, this._initialCwd, locale, this._cols, this._rows); // const env = TerminalInstance.createTerminalEnv(parentEnv, this._shellLaunchConfig, this._initialCwd, locale, this._cols, this._rows);
const cwd = Uri.parse(path.dirname(require.toUrl('../node/terminalProcess'))).fsPath; // const cwd = Uri.parse(path.dirname(require.toUrl('../node/terminalProcess'))).fsPath;
const options = { env, cwd }; // const options = { env, cwd };
this._logService.debug(`Terminal process launching (id: ${this.id})`, options); // this._logService.debug(`Terminal process launching (id: ${this.id})`, options);
this._processManager.process = cp.fork(Uri.parse(require.toUrl('bootstrap')).fsPath, ['--type=terminal'], options); // this._processManager.process = cp.fork(Uri.parse(require.toUrl('bootstrap')).fsPath, ['--type=terminal'], options);
this._processManager.processState = ProcessState.LAUNCHING; // this._processManager.processState = ProcessState.LAUNCHING;
if (this._shellLaunchConfig.name) { if (this._shellLaunchConfig.name) {
this.setTitle(this._shellLaunchConfig.name, false); this.setTitle(this._shellLaunchConfig.name, false);
...@@ -722,16 +683,6 @@ export class TerminalInstance implements ITerminalInstance { ...@@ -722,16 +683,6 @@ export class TerminalInstance implements ITerminalInstance {
}, LAUNCHING_DURATION); }, LAUNCHING_DURATION);
} }
// TODO: Should be protected
private static resolveConfigurationVariables(configurationResolverService: IConfigurationResolverService, env: IStringDictionary<string>, lastActiveWorkspaceRoot: IWorkspaceFolder): IStringDictionary<string> {
Object.keys(env).forEach((key) => {
if (typeof env[key] === 'string') {
env[key] = configurationResolverService.resolve(lastActiveWorkspaceRoot, env[key]);
}
});
return env;
}
private _sendPtyDataToXterm(message: { type: string, content: string }): void { private _sendPtyDataToXterm(message: { type: string, content: string }): void {
this._logService.debug(`Terminal process message (id: ${this.id})`, message); this._logService.debug(`Terminal process message (id: ${this.id})`, message);
if (message.type === 'data') { if (message.type === 'data') {
...@@ -868,69 +819,6 @@ export class TerminalInstance implements ITerminalInstance { ...@@ -868,69 +819,6 @@ export class TerminalInstance implements ITerminalInstance {
this._shellLaunchConfig = shell; this._shellLaunchConfig = shell;
} }
public static mergeEnvironments(parent: IStringDictionary<string>, other: IStringDictionary<string>) {
if (!other) {
return;
}
// On Windows apply the new values ignoring case, while still retaining
// the case of the original key.
if (platform.isWindows) {
for (let configKey in other) {
let actualKey = configKey;
for (let envKey in parent) {
if (configKey.toLowerCase() === envKey.toLowerCase()) {
actualKey = envKey;
break;
}
}
const value = other[configKey];
TerminalInstance._mergeEnvironmentValue(parent, actualKey, value);
}
} else {
Object.keys(other).forEach((key) => {
const value = other[key];
TerminalInstance._mergeEnvironmentValue(parent, key, value);
});
}
}
private static _mergeEnvironmentValue(env: IStringDictionary<string>, key: string, value: string | null) {
if (typeof value === 'string') {
env[key] = value;
} else {
delete env[key];
}
}
// TODO: This should be private/protected
public static createTerminalEnv(parentEnv: IStringDictionary<string>, shell: IShellLaunchConfig, cwd: string, locale: string, cols?: number, rows?: number): IStringDictionary<string> {
const env = { ...parentEnv };
if (shell.env) {
TerminalInstance.mergeEnvironments(env, shell.env);
}
env['PTYPID'] = process.pid.toString();
env['PTYSHELL'] = shell.executable;
env['TERM_PROGRAM'] = 'vscode';
env['TERM_PROGRAM_VERSION'] = pkg.version;
if (shell.args) {
if (typeof shell.args === 'string') {
env[`PTYSHELLCMDLINE`] = shell.args;
} else {
shell.args.forEach((arg, i) => env[`PTYSHELLARG${i}`] = arg);
}
}
env['PTYCWD'] = cwd;
env['LANG'] = TerminalInstance._getLangEnvVariable(locale);
if (cols && rows) {
env['PTYCOLS'] = cols.toString();
env['PTYROWS'] = rows.toString();
}
env['AMD_ENTRYPOINT'] = 'vs/workbench/parts/terminal/node/terminalProcess';
return env;
}
public onLineData(listener: (lineData: string) => void): lifecycle.IDisposable { public onLineData(listener: (lineData: string) => void): lifecycle.IDisposable {
this._onLineDataListeners.push(listener); this._onLineDataListeners.push(listener);
return { return {
...@@ -981,47 +869,6 @@ export class TerminalInstance implements ITerminalInstance { ...@@ -981,47 +869,6 @@ export class TerminalInstance implements ITerminalInstance {
}; };
} }
private static _sanitizeCwd(cwd: string) {
// Make the drive letter uppercase on Windows (see #9448)
if (platform.platform === platform.Platform.Windows && cwd && cwd[1] === ':') {
return cwd[0].toUpperCase() + cwd.substr(1);
}
return cwd;
}
private static _getLangEnvVariable(locale?: string) {
const parts = locale ? locale.split('-') : [];
const n = parts.length;
if (n === 0) {
// Fallback to en_US to prevent possible encoding issues.
return 'en_US.UTF-8';
}
if (n === 1) {
// app.getLocale can return just a language without a variant, fill in the variant for
// supported languages as many shells expect a 2-part locale.
const languageVariants = {
de: 'DE',
en: 'US',
es: 'ES',
fi: 'FI',
fr: 'FR',
it: 'IT',
ja: 'JP',
ko: 'KR',
pl: 'PL',
ru: 'RU',
zh: 'CN'
};
if (parts[0] in languageVariants) {
parts.push(languageVariants[parts[0]]);
}
} else {
// Ensure the variant is uppercase
parts[1] = parts[1].toUpperCase();
}
return parts.join('_') + '.UTF-8';
}
public updateConfig(): void { public updateConfig(): void {
this._setCursorBlink(this._configHelper.config.cursorBlinking); this._setCursorBlink(this._configHelper.config.cursorBlinking);
this._setCursorStyle(this._configHelper.config.cursorStyle); this._setCursorStyle(this._configHelper.config.cursorStyle);
...@@ -1156,7 +1003,7 @@ export class TerminalInstance implements ITerminalInstance { ...@@ -1156,7 +1003,7 @@ export class TerminalInstance implements ITerminalInstance {
return; return;
} }
if (eventFromProcess) { if (eventFromProcess) {
title = path.basename(title); title = paths.basename(title);
if (platform.isWindows) { if (platform.isWindows) {
// Remove the .exe extension // Remove the .exe extension
title = title.split('.exe')[0]; title = title.split('.exe')[0];
......
...@@ -3,12 +3,21 @@ ...@@ -3,12 +3,21 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { ChildProcess } from 'child_process'; import * as cp from 'child_process';
import * as os from 'os';
import * as path from 'path';
import * as platform from 'vs/base/common/platform';
import Uri from 'vs/base/common/uri';
import pkg from 'vs/platform/node/package';
import { IDisposable } from 'vs/base/common/lifecycle'; import { IDisposable } from 'vs/base/common/lifecycle';
import { ProcessState, ITerminalProcessManager, ITerminalProcessMessage, IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal'; import { ProcessState, ITerminalProcessManager, ITerminalProcessMessage, IShellLaunchConfig, ITerminalConfigHelper } from 'vs/workbench/parts/terminal/common/terminal';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
import { IStringDictionary } from 'vs/base/common/collections';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
/** /**
* Holds all state related to the creation and management of terminal processes. * Holds all state related to the creation and management of terminal processes.
...@@ -21,9 +30,10 @@ import { Emitter, Event } from 'vs/base/common/event'; ...@@ -21,9 +30,10 @@ import { Emitter, Event } from 'vs/base/common/event';
export class TerminalProcessManager implements ITerminalProcessManager { export class TerminalProcessManager implements ITerminalProcessManager {
public processState: ProcessState = ProcessState.UNINITIALIZED; public processState: ProcessState = ProcessState.UNINITIALIZED;
// _process // _process
public process: ChildProcess; public process: cp.ChildProcess;
public ptyProcessReady: TPromise<void>; public ptyProcessReady: TPromise<void>;
public shellProcessId: number; public shellProcessId: number;
public initialCwd: string;
private _preLaunchInputQueue: string[] = []; private _preLaunchInputQueue: string[] = [];
private _disposables: IDisposable[] = []; private _disposables: IDisposable[] = [];
...@@ -32,6 +42,12 @@ export class TerminalProcessManager implements ITerminalProcessManager { ...@@ -32,6 +42,12 @@ export class TerminalProcessManager implements ITerminalProcessManager {
public get onShellProcessIdReady(): Event<number> { return this._onShellProcessIdReady.event; } public get onShellProcessIdReady(): Event<number> { return this._onShellProcessIdReady.event; }
constructor( constructor(
private _configHelper: ITerminalConfigHelper,
private _cols: number,
private _rows: number,
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
@IHistoryService private readonly _historyService: IHistoryService,
@IConfigurationResolverService private readonly _configurationResolverService: IConfigurationResolverService,
@ILogService private _logService: ILogService @ILogService private _logService: ILogService
) { ) {
} }
...@@ -45,13 +61,69 @@ export class TerminalProcessManager implements ITerminalProcessManager { ...@@ -45,13 +61,69 @@ export class TerminalProcessManager implements ITerminalProcessManager {
this._disposables.push(disposable); this._disposables.push(disposable);
} }
public createProcess(launchConfig: IShellLaunchConfig): void { public createProcess(shellLaunchConfig: IShellLaunchConfig): void {
this.ptyProcessReady = new TPromise<void>(c => { this.ptyProcessReady = new TPromise<void>(c => {
this.onShellProcessIdReady(() => { this.onShellProcessIdReady(() => {
this._logService.debug(`Terminal process ready (shellProcessId: ${this.shellProcessId})`); this._logService.debug(`Terminal process ready (shellProcessId: ${this.shellProcessId})`);
c(void 0); c(void 0);
}); });
}); });
const locale = this._configHelper.config.setLocaleVariables ? platform.locale : undefined;
if (!shellLaunchConfig.executable) {
this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig);
}
const lastActiveWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot('file');
this.initialCwd = this._getCwd(shellLaunchConfig, lastActiveWorkspaceRootUri);
// Resolve env vars from config and shell
const lastActiveWorkspaceRoot = this._workspaceContextService.getWorkspaceFolder(lastActiveWorkspaceRootUri);
const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
const envFromConfig = TerminalProcessManager.resolveConfigurationVariables(this._configurationResolverService, { ...this._configHelper.config.env[platformKey] }, lastActiveWorkspaceRoot);
const envFromShell = TerminalProcessManager.resolveConfigurationVariables(this._configurationResolverService, { ...shellLaunchConfig.env }, lastActiveWorkspaceRoot);
shellLaunchConfig.env = envFromShell;
// Merge process env with the env from config
const parentEnv = { ...process.env };
TerminalProcessManager.mergeEnvironments(parentEnv, envFromConfig);
// Continue env initialization, merging in the env from the launch
// config and adding keys that are needed to create the process
const env = TerminalProcessManager.createTerminalEnv(parentEnv, shellLaunchConfig, this.initialCwd, locale, this._cols, this._rows);
const cwd = Uri.parse(path.dirname(require.toUrl('../node/terminalProcess'))).fsPath;
const options = { env, cwd };
this._logService.debug(`Terminal process launching`, options);
this.process = cp.fork(Uri.parse(require.toUrl('bootstrap')).fsPath, ['--type=terminal'], options);
this.processState = ProcessState.LAUNCHING;
}
protected _getCwd(shell: IShellLaunchConfig, root: Uri): string {
if (shell.cwd) {
return shell.cwd;
}
let cwd: string;
// TODO: Handle non-existent customCwd
if (!shell.ignoreConfigurationCwd) {
// Evaluate custom cwd first
const customCwd = this._configHelper.config.cwd;
if (customCwd) {
if (path.isAbsolute(customCwd)) {
cwd = customCwd;
} else if (root) {
cwd = path.normalize(path.join(root.fsPath, customCwd));
}
}
}
// If there was no custom cwd or it was relative with no workspace
if (!cwd) {
cwd = root ? root.fsPath : os.homedir();
}
return TerminalProcessManager._sanitizeCwd(cwd);
} }
public write(data: string): void { public write(data: string): void {
...@@ -83,6 +155,120 @@ export class TerminalProcessManager implements ITerminalProcessManager { ...@@ -83,6 +155,120 @@ export class TerminalProcessManager implements ITerminalProcessManager {
} }
} }
public static mergeEnvironments(parent: IStringDictionary<string>, other: IStringDictionary<string>) {
if (!other) {
return;
}
// On Windows apply the new values ignoring case, while still retaining
// the case of the original key.
if (platform.isWindows) {
for (let configKey in other) {
let actualKey = configKey;
for (let envKey in parent) {
if (configKey.toLowerCase() === envKey.toLowerCase()) {
actualKey = envKey;
break;
}
}
const value = other[configKey];
TerminalProcessManager._mergeEnvironmentValue(parent, actualKey, value);
}
} else {
Object.keys(other).forEach((key) => {
const value = other[key];
TerminalProcessManager._mergeEnvironmentValue(parent, key, value);
});
}
}
private static _mergeEnvironmentValue(env: IStringDictionary<string>, key: string, value: string | null) {
if (typeof value === 'string') {
env[key] = value;
} else {
delete env[key];
}
}
// TODO: This should be private/protected
public static createTerminalEnv(parentEnv: IStringDictionary<string>, shell: IShellLaunchConfig, cwd: string, locale: string, cols?: number, rows?: number): IStringDictionary<string> {
const env = { ...parentEnv };
if (shell.env) {
TerminalProcessManager.mergeEnvironments(env, shell.env);
}
env['PTYPID'] = process.pid.toString();
env['PTYSHELL'] = shell.executable;
env['TERM_PROGRAM'] = 'vscode';
env['TERM_PROGRAM_VERSION'] = pkg.version;
if (shell.args) {
if (typeof shell.args === 'string') {
env[`PTYSHELLCMDLINE`] = shell.args;
} else {
shell.args.forEach((arg, i) => env[`PTYSHELLARG${i}`] = arg);
}
}
env['PTYCWD'] = cwd;
env['LANG'] = TerminalProcessManager._getLangEnvVariable(locale);
if (cols && rows) {
env['PTYCOLS'] = cols.toString();
env['PTYROWS'] = rows.toString();
}
env['AMD_ENTRYPOINT'] = 'vs/workbench/parts/terminal/node/terminalProcess';
return env;
}
// TODO:should be protected/non-static
private static resolveConfigurationVariables(configurationResolverService: IConfigurationResolverService, env: IStringDictionary<string>, lastActiveWorkspaceRoot: IWorkspaceFolder): IStringDictionary<string> {
Object.keys(env).forEach((key) => {
if (typeof env[key] === 'string') {
env[key] = configurationResolverService.resolve(lastActiveWorkspaceRoot, env[key]);
}
});
return env;
}
private static _sanitizeCwd(cwd: string) {
// Make the drive letter uppercase on Windows (see #9448)
if (platform.platform === platform.Platform.Windows && cwd && cwd[1] === ':') {
return cwd[0].toUpperCase() + cwd.substr(1);
}
return cwd;
}
private static _getLangEnvVariable(locale?: string) {
const parts = locale ? locale.split('-') : [];
const n = parts.length;
if (n === 0) {
// Fallback to en_US to prevent possible encoding issues.
return 'en_US.UTF-8';
}
if (n === 1) {
// app.getLocale can return just a language without a variant, fill in the variant for
// supported languages as many shells expect a 2-part locale.
const languageVariants = {
de: 'DE',
en: 'US',
es: 'ES',
fi: 'FI',
fr: 'FR',
it: 'IT',
ja: 'JP',
ko: 'KR',
pl: 'PL',
ru: 'RU',
zh: 'CN'
};
if (parts[0] in languageVariants) {
parts.push(languageVariants[parts[0]]);
}
} else {
// Ensure the variant is uppercase
parts[1] = parts[1].toUpperCase();
}
return parts.join('_') + '.UTF-8';
}
// Should this be here or in instance? // Should this be here or in instance?
// private _isExiting: boolean; // private _isExiting: boolean;
......
...@@ -3,213 +3,213 @@ ...@@ -3,213 +3,213 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; // 'use strict';
import * as assert from 'assert'; // import * as assert from 'assert';
import * as os from 'os'; // import * as os from 'os';
import * as platform from 'vs/base/common/platform'; // import * as platform from 'vs/base/common/platform';
import Uri from 'vs/base/common/uri'; // import Uri from 'vs/base/common/uri';
import { IStringDictionary } from 'vs/base/common/collections'; // import { IStringDictionary } from 'vs/base/common/collections';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; // import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance'; // import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance';
import { IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal'; // import { IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; // import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { TestNotificationService, TestContextService, TestHistoryService } from 'vs/workbench/test/workbenchTestServices'; // import { TestNotificationService, TestContextService, TestHistoryService } from 'vs/workbench/test/workbenchTestServices';
import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; // import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; // import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; // import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IHistoryService } from 'vs/workbench/services/history/common/history'; // import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { TPromise } from 'vs/base/common/winjs.base'; // import { TPromise } from 'vs/base/common/winjs.base';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; // import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; // import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService } from 'vs/platform/notification/common/notification'; // import { INotificationService } from 'vs/platform/notification/common/notification';
import { ILogService, NullLogService } from 'vs/platform/log/common/log'; // import { ILogService, NullLogService } from 'vs/platform/log/common/log';
class TestTerminalInstance extends TerminalInstance { // class TestTerminalInstance extends TerminalInstance {
public _getCwd(shell: IShellLaunchConfig, root: Uri): string { // public _getCwd(shell: IShellLaunchConfig, root: Uri): string {
return super._getCwd(shell, root); // return super._getCwd(shell, root);
} // }
protected _createProcess(): void { } // protected _createProcess(): void { }
protected _createXterm(): TPromise<void> { return TPromise.as(void 0); } // protected _createXterm(): TPromise<void> { return TPromise.as(void 0); }
} // }
suite('Workbench - TerminalInstance', () => { // suite('Workbench - TerminalInstance', () => {
let instantiationService: TestInstantiationService; // let instantiationService: TestInstantiationService;
setup(() => { // setup(() => {
instantiationService = new TestInstantiationService(); // instantiationService = new TestInstantiationService();
instantiationService.stub(INotificationService, new TestNotificationService()); // instantiationService.stub(INotificationService, new TestNotificationService());
instantiationService.stub(IHistoryService, new TestHistoryService()); // instantiationService.stub(IHistoryService, new TestHistoryService());
}); // });
test('createTerminalEnv', function () { // test('createTerminalEnv', function () {
const shell1 = { // const shell1 = {
executable: '/bin/foosh', // executable: '/bin/foosh',
args: ['-bar', 'baz'] // args: ['-bar', 'baz']
}; // };
const parentEnv1: IStringDictionary<string> = { // const parentEnv1: IStringDictionary<string> = {
ok: true // ok: true
} as any; // } as any;
const env1 = TerminalInstance.createTerminalEnv(parentEnv1, shell1, '/foo', 'en-au'); // const env1 = TerminalInstance.createTerminalEnv(parentEnv1, shell1, '/foo', 'en-au');
assert.ok(env1['ok'], 'Parent environment is copied'); // assert.ok(env1['ok'], 'Parent environment is copied');
assert.deepStrictEqual(parentEnv1, { ok: true }, 'Parent environment is unchanged'); // assert.deepStrictEqual(parentEnv1, { ok: true }, 'Parent environment is unchanged');
assert.equal(env1['PTYPID'], process.pid.toString(), 'PTYPID is equal to the current PID'); // assert.equal(env1['PTYPID'], process.pid.toString(), 'PTYPID is equal to the current PID');
assert.equal(env1['PTYSHELL'], '/bin/foosh', 'PTYSHELL is equal to the provided shell'); // assert.equal(env1['PTYSHELL'], '/bin/foosh', 'PTYSHELL is equal to the provided shell');
assert.equal(env1['PTYSHELLARG0'], '-bar', 'PTYSHELLARG0 is equal to the first shell argument'); // assert.equal(env1['PTYSHELLARG0'], '-bar', 'PTYSHELLARG0 is equal to the first shell argument');
assert.equal(env1['PTYSHELLARG1'], 'baz', 'PTYSHELLARG1 is equal to the first shell argument'); // assert.equal(env1['PTYSHELLARG1'], 'baz', 'PTYSHELLARG1 is equal to the first shell argument');
assert.ok(!('PTYSHELLARG2' in env1), 'PTYSHELLARG2 is unset'); // assert.ok(!('PTYSHELLARG2' in env1), 'PTYSHELLARG2 is unset');
assert.equal(env1['PTYCWD'], '/foo', 'PTYCWD is equal to requested cwd'); // assert.equal(env1['PTYCWD'], '/foo', 'PTYCWD is equal to requested cwd');
assert.equal(env1['LANG'], 'en_AU.UTF-8', 'LANG is equal to the requested locale with UTF-8'); // assert.equal(env1['LANG'], 'en_AU.UTF-8', 'LANG is equal to the requested locale with UTF-8');
const shell2: IShellLaunchConfig = { // const shell2: IShellLaunchConfig = {
executable: '/bin/foosh', // executable: '/bin/foosh',
args: [] // args: []
}; // };
const parentEnv2: IStringDictionary<string> = { // const parentEnv2: IStringDictionary<string> = {
LANG: 'en_US.UTF-8' // LANG: 'en_US.UTF-8'
}; // };
const env2 = TerminalInstance.createTerminalEnv(parentEnv2, shell2, '/foo', 'en-au'); // const env2 = TerminalInstance.createTerminalEnv(parentEnv2, shell2, '/foo', 'en-au');
assert.ok(!('PTYSHELLARG0' in env2), 'PTYSHELLARG0 is unset'); // assert.ok(!('PTYSHELLARG0' in env2), 'PTYSHELLARG0 is unset');
assert.equal(env2['PTYCWD'], '/foo', 'PTYCWD is equal to /foo'); // assert.equal(env2['PTYCWD'], '/foo', 'PTYCWD is equal to /foo');
assert.equal(env2['LANG'], 'en_AU.UTF-8', 'LANG is equal to the requested locale with UTF-8'); // assert.equal(env2['LANG'], 'en_AU.UTF-8', 'LANG is equal to the requested locale with UTF-8');
const env3 = TerminalInstance.createTerminalEnv(parentEnv1, shell1, '/', null); // const env3 = TerminalInstance.createTerminalEnv(parentEnv1, shell1, '/', null);
assert.equal(env3['LANG'], 'en_US.UTF-8', 'LANG is equal to en_US.UTF-8 as fallback.'); // More info on issue #14586 // assert.equal(env3['LANG'], 'en_US.UTF-8', 'LANG is equal to en_US.UTF-8 as fallback.'); // More info on issue #14586
const env4 = TerminalInstance.createTerminalEnv(parentEnv2, shell1, '/', null); // const env4 = TerminalInstance.createTerminalEnv(parentEnv2, shell1, '/', null);
assert.equal(env4['LANG'], 'en_US.UTF-8', 'LANG is equal to the parent environment\'s LANG'); // assert.equal(env4['LANG'], 'en_US.UTF-8', 'LANG is equal to the parent environment\'s LANG');
}); // });
suite('mergeEnvironments', () => { // suite('mergeEnvironments', () => {
test('should add keys', () => { // test('should add keys', () => {
const parent = { // const parent = {
a: 'b' // a: 'b'
}; // };
const other = { // const other = {
c: 'd' // c: 'd'
}; // };
TerminalInstance.mergeEnvironments(parent, other); // TerminalInstance.mergeEnvironments(parent, other);
assert.deepEqual(parent, { // assert.deepEqual(parent, {
a: 'b', // a: 'b',
c: 'd' // c: 'd'
}); // });
}); // });
test('should add keys ignoring case on Windows', () => { // test('should add keys ignoring case on Windows', () => {
if (!platform.isWindows) { // if (!platform.isWindows) {
return; // return;
} // }
const parent = { // const parent = {
a: 'b' // a: 'b'
}; // };
const other = { // const other = {
A: 'c' // A: 'c'
}; // };
TerminalInstance.mergeEnvironments(parent, other); // TerminalInstance.mergeEnvironments(parent, other);
assert.deepEqual(parent, { // assert.deepEqual(parent, {
a: 'c' // a: 'c'
}); // });
}); // });
test('null values should delete keys from the parent env', () => { // test('null values should delete keys from the parent env', () => {
const parent = { // const parent = {
a: 'b', // a: 'b',
c: 'd' // c: 'd'
}; // };
const other: IStringDictionary<string> = { // const other: IStringDictionary<string> = {
a: null // a: null
}; // };
TerminalInstance.mergeEnvironments(parent, other); // TerminalInstance.mergeEnvironments(parent, other);
assert.deepEqual(parent, { // assert.deepEqual(parent, {
c: 'd' // c: 'd'
}); // });
}); // });
test('null values should delete keys from the parent env ignoring case on Windows', () => { // test('null values should delete keys from the parent env ignoring case on Windows', () => {
if (!platform.isWindows) { // if (!platform.isWindows) {
return; // return;
} // }
const parent = { // const parent = {
a: 'b', // a: 'b',
c: 'd' // c: 'd'
}; // };
const other: IStringDictionary<string> = { // const other: IStringDictionary<string> = {
A: null // A: null
}; // };
TerminalInstance.mergeEnvironments(parent, other); // TerminalInstance.mergeEnvironments(parent, other);
assert.deepEqual(parent, { // assert.deepEqual(parent, {
c: 'd' // c: 'd'
}); // });
}); // });
}); // });
suite('_getCwd', () => { // suite('_getCwd', () => {
let instance: TestTerminalInstance; // let instance: TestTerminalInstance;
let instantiationService: TestInstantiationService; // let instantiationService: TestInstantiationService;
let configHelper: { config: { cwd: string } }; // let configHelper: { config: { cwd: string } };
setup(() => { // setup(() => {
let contextKeyService = new MockContextKeyService(); // let contextKeyService = new MockContextKeyService();
let keybindingService = new MockKeybindingService(); // let keybindingService = new MockKeybindingService();
let terminalFocusContextKey = contextKeyService.createKey('test', false); // let terminalFocusContextKey = contextKeyService.createKey('test', false);
instantiationService = new TestInstantiationService(); // instantiationService = new TestInstantiationService();
instantiationService.stub(IConfigurationService, new TestConfigurationService()); // instantiationService.stub(IConfigurationService, new TestConfigurationService());
instantiationService.stub(INotificationService, new TestNotificationService()); // instantiationService.stub(INotificationService, new TestNotificationService());
instantiationService.stub(IWorkspaceContextService, new TestContextService()); // instantiationService.stub(IWorkspaceContextService, new TestContextService());
instantiationService.stub(IKeybindingService, keybindingService); // instantiationService.stub(IKeybindingService, keybindingService);
instantiationService.stub(IContextKeyService, contextKeyService); // instantiationService.stub(IContextKeyService, contextKeyService);
instantiationService.stub(IHistoryService, new TestHistoryService()); // instantiationService.stub(IHistoryService, new TestHistoryService());
instantiationService.stub(ILogService, new NullLogService()); // instantiationService.stub(ILogService, new NullLogService());
configHelper = { // configHelper = {
config: { // config: {
cwd: null // cwd: null
} // }
}; // };
instance = instantiationService.createInstance(TestTerminalInstance, terminalFocusContextKey, configHelper, null, null); // instance = instantiationService.createInstance(TestTerminalInstance, terminalFocusContextKey, configHelper, null, null);
}); // });
// This helper checks the paths in a cross-platform friendly manner // // This helper checks the paths in a cross-platform friendly manner
function assertPathsMatch(a: string, b: string): void { // function assertPathsMatch(a: string, b: string): void {
assert.equal(Uri.file(a).fsPath, Uri.file(b).fsPath); // assert.equal(Uri.file(a).fsPath, Uri.file(b).fsPath);
} // }
test('should default to os.homedir() for an empty workspace', () => { // test('should default to os.homedir() for an empty workspace', () => {
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir()); // assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir());
}); // });
test('should use to the workspace if it exists', () => { // test('should use to the workspace if it exists', () => {
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, Uri.file('/foo')), '/foo'); // assertPathsMatch(instance._getCwd({ executable: null, args: [] }, Uri.file('/foo')), '/foo');
}); // });
test('should use an absolute custom cwd as is', () => { // test('should use an absolute custom cwd as is', () => {
configHelper.config.cwd = '/foo'; // configHelper.config.cwd = '/foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), '/foo'); // assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), '/foo');
}); // });
test('should normalize a relative custom cwd against the workspace path', () => { // test('should normalize a relative custom cwd against the workspace path', () => {
configHelper.config.cwd = 'foo'; // configHelper.config.cwd = 'foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, Uri.file('/bar')), '/bar/foo'); // assertPathsMatch(instance._getCwd({ executable: null, args: [] }, Uri.file('/bar')), '/bar/foo');
configHelper.config.cwd = './foo'; // configHelper.config.cwd = './foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, Uri.file('/bar')), '/bar/foo'); // assertPathsMatch(instance._getCwd({ executable: null, args: [] }, Uri.file('/bar')), '/bar/foo');
configHelper.config.cwd = '../foo'; // configHelper.config.cwd = '../foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, Uri.file('/bar'), ), '/foo'); // assertPathsMatch(instance._getCwd({ executable: null, args: [] }, Uri.file('/bar'), ), '/foo');
}); // });
test('should fall back for relative a custom cwd that doesn\'t have a workspace', () => { // test('should fall back for relative a custom cwd that doesn\'t have a workspace', () => {
configHelper.config.cwd = 'foo'; // configHelper.config.cwd = 'foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir()); // assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir());
configHelper.config.cwd = './foo'; // configHelper.config.cwd = './foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir()); // assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir());
configHelper.config.cwd = '../foo'; // configHelper.config.cwd = '../foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir()); // assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir());
}); // });
test('should ignore custom cwd when told to ignore', () => { // test('should ignore custom cwd when told to ignore', () => {
configHelper.config.cwd = '/foo'; // configHelper.config.cwd = '/foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [], ignoreConfigurationCwd: true }, Uri.file('/bar')), '/bar'); // assertPathsMatch(instance._getCwd({ executable: null, args: [], ignoreConfigurationCwd: true }, Uri.file('/bar')), '/bar');
}); // });
}); // });
}); // });
\ No newline at end of file \ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册