提交 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 {
ptyProcessReady: TPromise<void>;
shellProcessId: number;
onShellProcessIdReady: Event<number>;
initialCwd: string;
acceptProcessMessage(message): void;
addDisposable(disposable: IDisposable);
createProcess(launchConfig: IShellLaunchConfig);
createProcess(shellLaunchConfig: IShellLaunchConfig);
write(data: string): void;
}
......
......@@ -3,21 +3,17 @@
* 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 nls from 'vs/nls';
import * as platform from 'vs/base/common/platform';
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 Uri from 'vs/base/common/uri';
import { WindowsShellHelper } from 'vs/workbench/parts/terminal/electron-browser/windowsShellHelper';
import { Terminal as XTermTerminal } from 'vscode-xterm';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
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 { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
......@@ -29,12 +25,8 @@ import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService }
import { scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { TPromise } from 'vs/base/common/winjs.base';
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 { 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 { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { INotificationService } from 'vs/platform/notification/common/notification';
......@@ -100,7 +92,7 @@ export class TerminalInstance implements ITerminalInstance {
private _rows: number;
private _messageTitleListener: (message: { type: string, content: string }) => void;
// private _preLaunchInputQueue: string;
private _initialCwd: string;
// private _initialCwd: string;
private _windowsShellHelper: WindowsShellHelper;
private _onLineDataListeners: ((lineData: string) => void)[];
private _xtermReadyPromise: TPromise<void>;
......@@ -136,10 +128,7 @@ export class TerminalInstance implements ITerminalInstance {
@IPanelService private readonly _panelService: IPanelService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IClipboardService private readonly _clipboardService: IClipboardService,
@IHistoryService private readonly _historyService: IHistoryService,
@IThemeService private readonly _themeService: IThemeService,
@IConfigurationResolverService private readonly _configurationResolverService: IConfigurationResolverService,
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ILogService private _logService: ILogService
) {
......@@ -315,7 +304,7 @@ export class TerminalInstance implements ITerminalInstance {
this._xterm.on('linefeed', () => this._onLineFeed());
this._processManager.process.on('message', (message) => this._sendPtyDataToXterm(message));
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._instanceDisposables.push(this._themeService.onThemeChange(theme => this._updateTheme(theme)));
}
......@@ -636,67 +625,39 @@ export class TerminalInstance implements ITerminalInstance {
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 {
// 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.createProcess(this._shellLaunchConfig);
const locale = this._configHelper.config.setLocaleVariables ? platform.locale : undefined;
if (!this._shellLaunchConfig.executable) {
this._configHelper.mergeDefaultShellPathAndArgs(this._shellLaunchConfig);
}
const lastActiveWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot('file');
this._initialCwd = this._getCwd(this._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 = TerminalInstance.resolveConfigurationVariables(this._configurationResolverService, { ...this._configHelper.config.env[platformKey] }, lastActiveWorkspaceRoot);
const envFromShell = TerminalInstance.resolveConfigurationVariables(this._configurationResolverService, { ...this._shellLaunchConfig.env }, lastActiveWorkspaceRoot);
this._shellLaunchConfig.env = envFromShell;
// Merge process env with the env from config
const parentEnv = { ...process.env };
TerminalInstance.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 = TerminalInstance.createTerminalEnv(parentEnv, this._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 (id: ${this.id})`, options);
this._processManager.process = cp.fork(Uri.parse(require.toUrl('bootstrap')).fsPath, ['--type=terminal'], options);
this._processManager.processState = ProcessState.LAUNCHING;
// const locale = this._configHelper.config.setLocaleVariables ? platform.locale : undefined;
// if (!this._shellLaunchConfig.executable) {
// this._configHelper.mergeDefaultShellPathAndArgs(this._shellLaunchConfig);
// }
// const lastActiveWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot('file');
// this._initialCwd = this._getCwd(this._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 = TerminalInstance.resolveConfigurationVariables(this._configurationResolverService, { ...this._configHelper.config.env[platformKey] }, lastActiveWorkspaceRoot);
// const envFromShell = TerminalInstance.resolveConfigurationVariables(this._configurationResolverService, { ...this._shellLaunchConfig.env }, lastActiveWorkspaceRoot);
// this._shellLaunchConfig.env = envFromShell;
// // Merge process env with the env from config
// const parentEnv = { ...process.env };
// TerminalInstance.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 = TerminalInstance.createTerminalEnv(parentEnv, this._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 (id: ${this.id})`, options);
// this._processManager.process = cp.fork(Uri.parse(require.toUrl('bootstrap')).fsPath, ['--type=terminal'], options);
// this._processManager.processState = ProcessState.LAUNCHING;
if (this._shellLaunchConfig.name) {
this.setTitle(this._shellLaunchConfig.name, false);
......@@ -722,16 +683,6 @@ export class TerminalInstance implements ITerminalInstance {
}, 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 {
this._logService.debug(`Terminal process message (id: ${this.id})`, message);
if (message.type === 'data') {
......@@ -868,69 +819,6 @@ export class TerminalInstance implements ITerminalInstance {
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 {
this._onLineDataListeners.push(listener);
return {
......@@ -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 {
this._setCursorBlink(this._configHelper.config.cursorBlinking);
this._setCursorStyle(this._configHelper.config.cursorStyle);
......@@ -1156,7 +1003,7 @@ export class TerminalInstance implements ITerminalInstance {
return;
}
if (eventFromProcess) {
title = path.basename(title);
title = paths.basename(title);
if (platform.isWindows) {
// Remove the .exe extension
title = title.split('.exe')[0];
......
......@@ -3,12 +3,21 @@
* 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 { 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 { ILogService } from 'vs/platform/log/common/log';
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.
......@@ -21,9 +30,10 @@ import { Emitter, Event } from 'vs/base/common/event';
export class TerminalProcessManager implements ITerminalProcessManager {
public processState: ProcessState = ProcessState.UNINITIALIZED;
// _process
public process: ChildProcess;
public process: cp.ChildProcess;
public ptyProcessReady: TPromise<void>;
public shellProcessId: number;
public initialCwd: string;
private _preLaunchInputQueue: string[] = [];
private _disposables: IDisposable[] = [];
......@@ -32,6 +42,12 @@ export class TerminalProcessManager implements ITerminalProcessManager {
public get onShellProcessIdReady(): Event<number> { return this._onShellProcessIdReady.event; }
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
) {
}
......@@ -45,13 +61,69 @@ export class TerminalProcessManager implements ITerminalProcessManager {
this._disposables.push(disposable);
}
public createProcess(launchConfig: IShellLaunchConfig): void {
public createProcess(shellLaunchConfig: IShellLaunchConfig): void {
this.ptyProcessReady = new TPromise<void>(c => {
this.onShellProcessIdReady(() => {
this._logService.debug(`Terminal process ready (shellProcessId: ${this.shellProcessId})`);
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 {
......@@ -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?
// private _isExiting: boolean;
......
......@@ -3,213 +3,213 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import * as os from 'os';
import * as platform from 'vs/base/common/platform';
import Uri from 'vs/base/common/uri';
import { IStringDictionary } from 'vs/base/common/collections';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance';
import { IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { TestNotificationService, TestContextService, TestHistoryService } from 'vs/workbench/test/workbenchTestServices';
import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { TPromise } from 'vs/base/common/winjs.base';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
class TestTerminalInstance extends TerminalInstance {
public _getCwd(shell: IShellLaunchConfig, root: Uri): string {
return super._getCwd(shell, root);
}
protected _createProcess(): void { }
protected _createXterm(): TPromise<void> { return TPromise.as(void 0); }
}
suite('Workbench - TerminalInstance', () => {
let instantiationService: TestInstantiationService;
setup(() => {
instantiationService = new TestInstantiationService();
instantiationService.stub(INotificationService, new TestNotificationService());
instantiationService.stub(IHistoryService, new TestHistoryService());
});
test('createTerminalEnv', function () {
const shell1 = {
executable: '/bin/foosh',
args: ['-bar', 'baz']
};
const parentEnv1: IStringDictionary<string> = {
ok: true
} as any;
const env1 = TerminalInstance.createTerminalEnv(parentEnv1, shell1, '/foo', 'en-au');
assert.ok(env1['ok'], 'Parent environment is copied');
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['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['PTYSHELLARG1'], 'baz', 'PTYSHELLARG1 is equal to the first shell argument');
assert.ok(!('PTYSHELLARG2' in env1), 'PTYSHELLARG2 is unset');
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');
const shell2: IShellLaunchConfig = {
executable: '/bin/foosh',
args: []
};
const parentEnv2: IStringDictionary<string> = {
LANG: 'en_US.UTF-8'
};
const env2 = TerminalInstance.createTerminalEnv(parentEnv2, shell2, '/foo', 'en-au');
assert.ok(!('PTYSHELLARG0' in env2), 'PTYSHELLARG0 is unset');
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');
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
const env4 = TerminalInstance.createTerminalEnv(parentEnv2, shell1, '/', null);
assert.equal(env4['LANG'], 'en_US.UTF-8', 'LANG is equal to the parent environment\'s LANG');
});
suite('mergeEnvironments', () => {
test('should add keys', () => {
const parent = {
a: 'b'
};
const other = {
c: 'd'
};
TerminalInstance.mergeEnvironments(parent, other);
assert.deepEqual(parent, {
a: 'b',
c: 'd'
});
});
test('should add keys ignoring case on Windows', () => {
if (!platform.isWindows) {
return;
}
const parent = {
a: 'b'
};
const other = {
A: 'c'
};
TerminalInstance.mergeEnvironments(parent, other);
assert.deepEqual(parent, {
a: 'c'
});
});
test('null values should delete keys from the parent env', () => {
const parent = {
a: 'b',
c: 'd'
};
const other: IStringDictionary<string> = {
a: null
};
TerminalInstance.mergeEnvironments(parent, other);
assert.deepEqual(parent, {
c: 'd'
});
});
test('null values should delete keys from the parent env ignoring case on Windows', () => {
if (!platform.isWindows) {
return;
}
const parent = {
a: 'b',
c: 'd'
};
const other: IStringDictionary<string> = {
A: null
};
TerminalInstance.mergeEnvironments(parent, other);
assert.deepEqual(parent, {
c: 'd'
});
});
});
suite('_getCwd', () => {
let instance: TestTerminalInstance;
let instantiationService: TestInstantiationService;
let configHelper: { config: { cwd: string } };
setup(() => {
let contextKeyService = new MockContextKeyService();
let keybindingService = new MockKeybindingService();
let terminalFocusContextKey = contextKeyService.createKey('test', false);
instantiationService = new TestInstantiationService();
instantiationService.stub(IConfigurationService, new TestConfigurationService());
instantiationService.stub(INotificationService, new TestNotificationService());
instantiationService.stub(IWorkspaceContextService, new TestContextService());
instantiationService.stub(IKeybindingService, keybindingService);
instantiationService.stub(IContextKeyService, contextKeyService);
instantiationService.stub(IHistoryService, new TestHistoryService());
instantiationService.stub(ILogService, new NullLogService());
configHelper = {
config: {
cwd: null
}
};
instance = instantiationService.createInstance(TestTerminalInstance, terminalFocusContextKey, configHelper, null, null);
});
// This helper checks the paths in a cross-platform friendly manner
function assertPathsMatch(a: string, b: string): void {
assert.equal(Uri.file(a).fsPath, Uri.file(b).fsPath);
}
test('should default to os.homedir() for an empty workspace', () => {
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir());
});
test('should use to the workspace if it exists', () => {
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, Uri.file('/foo')), '/foo');
});
test('should use an absolute custom cwd as is', () => {
configHelper.config.cwd = '/foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), '/foo');
});
test('should normalize a relative custom cwd against the workspace path', () => {
configHelper.config.cwd = 'foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, Uri.file('/bar')), '/bar/foo');
configHelper.config.cwd = './foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, Uri.file('/bar')), '/bar/foo');
configHelper.config.cwd = '../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', () => {
configHelper.config.cwd = 'foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir());
configHelper.config.cwd = './foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir());
configHelper.config.cwd = '../foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir());
});
test('should ignore custom cwd when told to ignore', () => {
configHelper.config.cwd = '/foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [], ignoreConfigurationCwd: true }, Uri.file('/bar')), '/bar');
});
});
});
\ No newline at end of file
// 'use strict';
// import * as assert from 'assert';
// import * as os from 'os';
// import * as platform from 'vs/base/common/platform';
// import Uri from 'vs/base/common/uri';
// import { IStringDictionary } from 'vs/base/common/collections';
// import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
// import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance';
// import { IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal';
// import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
// import { TestNotificationService, TestContextService, TestHistoryService } from 'vs/workbench/test/workbenchTestServices';
// import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
// import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
// import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
// import { IHistoryService } from 'vs/workbench/services/history/common/history';
// import { TPromise } from 'vs/base/common/winjs.base';
// import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
// import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
// import { INotificationService } from 'vs/platform/notification/common/notification';
// import { ILogService, NullLogService } from 'vs/platform/log/common/log';
// class TestTerminalInstance extends TerminalInstance {
// public _getCwd(shell: IShellLaunchConfig, root: Uri): string {
// return super._getCwd(shell, root);
// }
// protected _createProcess(): void { }
// protected _createXterm(): TPromise<void> { return TPromise.as(void 0); }
// }
// suite('Workbench - TerminalInstance', () => {
// let instantiationService: TestInstantiationService;
// setup(() => {
// instantiationService = new TestInstantiationService();
// instantiationService.stub(INotificationService, new TestNotificationService());
// instantiationService.stub(IHistoryService, new TestHistoryService());
// });
// test('createTerminalEnv', function () {
// const shell1 = {
// executable: '/bin/foosh',
// args: ['-bar', 'baz']
// };
// const parentEnv1: IStringDictionary<string> = {
// ok: true
// } as any;
// const env1 = TerminalInstance.createTerminalEnv(parentEnv1, shell1, '/foo', 'en-au');
// assert.ok(env1['ok'], 'Parent environment is copied');
// 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['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['PTYSHELLARG1'], 'baz', 'PTYSHELLARG1 is equal to the first shell argument');
// assert.ok(!('PTYSHELLARG2' in env1), 'PTYSHELLARG2 is unset');
// 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');
// const shell2: IShellLaunchConfig = {
// executable: '/bin/foosh',
// args: []
// };
// const parentEnv2: IStringDictionary<string> = {
// LANG: 'en_US.UTF-8'
// };
// const env2 = TerminalInstance.createTerminalEnv(parentEnv2, shell2, '/foo', 'en-au');
// assert.ok(!('PTYSHELLARG0' in env2), 'PTYSHELLARG0 is unset');
// 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');
// 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
// const env4 = TerminalInstance.createTerminalEnv(parentEnv2, shell1, '/', null);
// assert.equal(env4['LANG'], 'en_US.UTF-8', 'LANG is equal to the parent environment\'s LANG');
// });
// suite('mergeEnvironments', () => {
// test('should add keys', () => {
// const parent = {
// a: 'b'
// };
// const other = {
// c: 'd'
// };
// TerminalInstance.mergeEnvironments(parent, other);
// assert.deepEqual(parent, {
// a: 'b',
// c: 'd'
// });
// });
// test('should add keys ignoring case on Windows', () => {
// if (!platform.isWindows) {
// return;
// }
// const parent = {
// a: 'b'
// };
// const other = {
// A: 'c'
// };
// TerminalInstance.mergeEnvironments(parent, other);
// assert.deepEqual(parent, {
// a: 'c'
// });
// });
// test('null values should delete keys from the parent env', () => {
// const parent = {
// a: 'b',
// c: 'd'
// };
// const other: IStringDictionary<string> = {
// a: null
// };
// TerminalInstance.mergeEnvironments(parent, other);
// assert.deepEqual(parent, {
// c: 'd'
// });
// });
// test('null values should delete keys from the parent env ignoring case on Windows', () => {
// if (!platform.isWindows) {
// return;
// }
// const parent = {
// a: 'b',
// c: 'd'
// };
// const other: IStringDictionary<string> = {
// A: null
// };
// TerminalInstance.mergeEnvironments(parent, other);
// assert.deepEqual(parent, {
// c: 'd'
// });
// });
// });
// suite('_getCwd', () => {
// let instance: TestTerminalInstance;
// let instantiationService: TestInstantiationService;
// let configHelper: { config: { cwd: string } };
// setup(() => {
// let contextKeyService = new MockContextKeyService();
// let keybindingService = new MockKeybindingService();
// let terminalFocusContextKey = contextKeyService.createKey('test', false);
// instantiationService = new TestInstantiationService();
// instantiationService.stub(IConfigurationService, new TestConfigurationService());
// instantiationService.stub(INotificationService, new TestNotificationService());
// instantiationService.stub(IWorkspaceContextService, new TestContextService());
// instantiationService.stub(IKeybindingService, keybindingService);
// instantiationService.stub(IContextKeyService, contextKeyService);
// instantiationService.stub(IHistoryService, new TestHistoryService());
// instantiationService.stub(ILogService, new NullLogService());
// configHelper = {
// config: {
// cwd: null
// }
// };
// instance = instantiationService.createInstance(TestTerminalInstance, terminalFocusContextKey, configHelper, null, null);
// });
// // This helper checks the paths in a cross-platform friendly manner
// function assertPathsMatch(a: string, b: string): void {
// assert.equal(Uri.file(a).fsPath, Uri.file(b).fsPath);
// }
// test('should default to os.homedir() for an empty workspace', () => {
// assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir());
// });
// test('should use to the workspace if it exists', () => {
// assertPathsMatch(instance._getCwd({ executable: null, args: [] }, Uri.file('/foo')), '/foo');
// });
// test('should use an absolute custom cwd as is', () => {
// configHelper.config.cwd = '/foo';
// assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), '/foo');
// });
// test('should normalize a relative custom cwd against the workspace path', () => {
// configHelper.config.cwd = 'foo';
// assertPathsMatch(instance._getCwd({ executable: null, args: [] }, Uri.file('/bar')), '/bar/foo');
// configHelper.config.cwd = './foo';
// assertPathsMatch(instance._getCwd({ executable: null, args: [] }, Uri.file('/bar')), '/bar/foo');
// configHelper.config.cwd = '../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', () => {
// configHelper.config.cwd = 'foo';
// assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir());
// configHelper.config.cwd = './foo';
// assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir());
// configHelper.config.cwd = '../foo';
// assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir());
// });
// test('should ignore custom cwd when told to ignore', () => {
// configHelper.config.cwd = '/foo';
// assertPathsMatch(instance._getCwd({ executable: null, args: [], ignoreConfigurationCwd: true }, Uri.file('/bar')), '/bar');
// });
// });
// });
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册