提交 daf4deaf 编写于 作者: D Daniel Imms

Terminal process request improvements

上级 421cc14d
......@@ -12,6 +12,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostC
export class MainThreadTerminalService implements MainThreadTerminalServiceShape {
private _proxy: ExtHostTerminalServiceShape;
private _remoteAuthority: string | null;
private _toDispose: IDisposable[] = [];
private _terminalProcesses: { [id: number]: ITerminalProcessExtHostProxy } = {};
private _terminalOnDidWriteDataListeners: { [id: number]: IDisposable } = {};
......@@ -22,6 +23,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
@ITerminalService private terminalService: ITerminalService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService);
this._remoteAuthority = extHostContext.remoteAuthority;
this._toDispose.push(terminalService.onInstanceCreated((instance) => {
// Delay this message so the TerminalInstance constructor has a chance to finish and
// return the ID normally to the extension host. The ID that is passed here will be used
......@@ -197,6 +199,11 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
private _onTerminalRequestExtHostProcess(request: ITerminalProcessExtHostRequest): void {
// Only allow processes on remote ext hosts
if (!this._remoteAuthority) {
return;
}
this._terminalProcesses[request.proxy.terminalId] = request.proxy;
const shellLaunchConfigDto: ShellLaunchConfigDto = {
name: request.shellLaunchConfig.name,
......@@ -205,7 +212,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
cwd: request.shellLaunchConfig.cwd,
env: request.shellLaunchConfig.env
};
this._proxy.$createProcess(request.proxy.terminalId, shellLaunchConfigDto, request.cols, request.rows);
this._proxy.$createProcess(request.proxy.terminalId, shellLaunchConfigDto, request.activeWorkspaceRootUri, request.cols, request.rows);
request.proxy.onInput(data => this._proxy.$acceptProcessInput(request.proxy.terminalId, data));
request.proxy.onResize(dimensions => this._proxy.$acceptProcessResize(request.proxy.terminalId, dimensions.cols, dimensions.rows));
request.proxy.onShutdown(immediate => this._proxy.$acceptProcessShutdown(request.proxy.terminalId, immediate));
......
......@@ -904,7 +904,7 @@ export interface ExtHostTerminalServiceShape {
$acceptTerminalRendererInput(id: number, data: string): void;
$acceptTerminalTitleChange(id: number, name: string): void;
$acceptTerminalRendererDimensions(id: number, cols: number, rows: number): void;
$createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, cols: number, rows: number): void;
$createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, activeWorkspaceRootUri: URI, cols: number, rows: number): void;
$acceptProcessInput(id: number, data: string): void;
$acceptProcessResize(id: number, cols: number, rows: number): void;
$acceptProcessShutdown(id: number, immediate: boolean): void;
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as os from 'os';
import { URI, UriComponents } from 'vs/base/common/uri';
import * as platform from 'vs/base/common/platform';
import * as terminalEnvironment from 'vs/workbench/parts/terminal/node/terminalEnvironment';
import { Event, Emitter } from 'vs/base/common/event';
......@@ -365,7 +365,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
}
}
public $createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, cols: number, rows: number): void {
public $createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number): void {
// TODO: This function duplicates a lot of TerminalProcessManager.createProcess, ideally
// they would be merged into a single implementation.
......@@ -383,10 +383,9 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
shellLaunchConfig.args = shellArgsConfigValue;
}
// TODO: Base the cwd on the last active workspace root
// const lastActiveWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file);
// this.initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, lastActiveWorkspaceRootUri, this._configHelper);
const initialCwd = os.homedir();
// TODO: @daniel
const activeWorkspaceRootUri = URI.revive(activeWorkspaceRootUriComponents);
const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, activeWorkspaceRootUri, terminalConfig.cwd);
// TODO: Pull in and resolve config settings
// // Resolve env vars from config and shell
......
......@@ -8,6 +8,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import { RawContextKey, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
export const TERMINAL_PANEL_ID = 'workbench.panel.terminal';
......@@ -238,7 +239,7 @@ export interface ITerminalService {
selectDefaultWindowsShell(): Promise<string>;
setWorkspaceShellAllowed(isAllowed: boolean): void;
requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number): void;
requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void;
}
export const enum Direction {
......@@ -660,6 +661,7 @@ export interface ITerminalProcessExtHostProxy extends IDisposable {
export interface ITerminalProcessExtHostRequest {
proxy: ITerminalProcessExtHostProxy;
shellLaunchConfig: IShellLaunchConfig;
activeWorkspaceRootUri: URI;
cols: number;
rows: number;
}
\ No newline at end of file
......@@ -10,6 +10,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN } from 'vs/workbench/parts/terminal/common/terminal';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { URI } from 'vs/base/common/uri';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
export abstract class TerminalService implements ITerminalService {
......@@ -89,7 +90,7 @@ export abstract class TerminalService implements ITerminalService {
public abstract getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance;
public abstract selectDefaultWindowsShell(): Promise<string>;
public abstract setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void;
public abstract requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number): void;
public abstract requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void;
private _onWillShutdown(): boolean | PromiseLike<boolean> {
if (this.terminalInstances.length === 0) {
......
......@@ -16,7 +16,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { TerminalProcess } from 'vs/workbench/parts/terminal/node/terminalProcess';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { Schemas } from 'vs/base/common/network';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
/** The amount of time to consider terminal errors to be related to the launch */
const LAUNCHING_DURATION = 500;
......@@ -55,7 +57,8 @@ export class TerminalProcessManager implements ITerminalProcessManager {
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILogService private readonly _logService: ILogService,
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
@IConfigurationResolverService private readonly _configurationResolverService: IConfigurationResolverService
@IConfigurationResolverService private readonly _configurationResolverService: IConfigurationResolverService,
@IWindowService private readonly _windowService: IWindowService
) {
this.ptyProcessReady = new Promise<void>(c => {
this.onProcessReady(() => {
......@@ -87,19 +90,19 @@ export class TerminalProcessManager implements ITerminalProcessManager {
cols: number,
rows: number
): void {
const extensionHostOwned = (<any>this._configHelper.config).extHostProcess;
if (extensionHostOwned) {
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, cols, rows);
if (this._windowService.getConfiguration().remoteAuthority) {
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(REMOTE_HOST_SCHEME);
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows);
} else {
if (!shellLaunchConfig.executable) {
this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig);
}
const lastActiveWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file);
this.initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, lastActiveWorkspaceRootUri, this._configHelper);
// TODO: @daniel
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file);
this.initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, activeWorkspaceRootUri, this._configHelper.config.cwd);
// Resolve env vars from config and shell
const lastActiveWorkspaceRoot = this._workspaceContextService.getWorkspaceFolder(lastActiveWorkspaceRootUri);
const lastActiveWorkspaceRoot = this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri);
const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
const envFromConfig = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...this._configHelper.config.env[platformKey] }, lastActiveWorkspaceRoot);
const envFromShell = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...shellLaunchConfig.env }, lastActiveWorkspaceRoot);
......
......@@ -23,9 +23,10 @@ import { TerminalTab } from 'vs/workbench/parts/terminal/browser/terminalTab';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ipcRenderer as ipc } from 'electron';
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
import { IOpenFileRequest, IWindowService } from 'vs/platform/windows/common/windows';
import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { URI } from 'vs/base/common/uri';
import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput';
export class TerminalService extends AbstractTerminalService implements ITerminalService {
......@@ -48,7 +49,8 @@ export class TerminalService extends AbstractTerminalService implements ITermina
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@INotificationService private readonly _notificationService: INotificationService,
@IDialogService private readonly _dialogService: IDialogService,
@IExtensionService private readonly _extensionService: IExtensionService
@IExtensionService private readonly _extensionService: IExtensionService,
@IWindowService private readonly _windowService: IWindowService,
) {
super(contextKeyService, panelService, partService, lifecycleService, storageService);
......@@ -98,12 +100,12 @@ export class TerminalService extends AbstractTerminalService implements ITermina
return instance;
}
public requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number): void {
public requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void {
// Ensure extension host is ready before requesting a process
this._extensionService.whenInstalledExtensionsRegistered().then(() => {
// TODO: MainThreadTerminalService is not ready at this point, fix this
setTimeout(() => {
this._onInstanceRequestExtHostProcess.fire({ proxy, shellLaunchConfig, cols, rows });
this._onInstanceRequestExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows });
}, 500);
});
}
......@@ -153,6 +155,11 @@ export class TerminalService extends AbstractTerminalService implements ITermina
return;
}
if (this._windowService.getConfiguration().remoteAuthority) {
// Don't suggest if the opened workspace is remote
return;
}
// Don't suggest if the user has explicitly opted out
const neverSuggest = this._storageService.getBoolean(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, StorageScope.GLOBAL, false);
if (neverSuggest) {
......
......@@ -56,6 +56,9 @@ function getTerminalDefaultShellUnixLike(): string {
unixLikeTerminal = '/bin/bash';
}
}
if (platform.isWindows) {
unixLikeTerminal = '/bin/bash'; // for WSL
}
_TERMINAL_DEFAULT_SHELL_UNIX_LIKE = unixLikeTerminal;
}
return _TERMINAL_DEFAULT_SHELL_UNIX_LIKE;
......
......@@ -9,7 +9,7 @@ import * as platform from 'vs/base/common/platform';
import pkg from 'vs/platform/node/package';
import { URI as Uri } from 'vs/base/common/uri';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IShellLaunchConfig, ITerminalConfigHelper } from 'vs/workbench/parts/terminal/common/terminal';
import { IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
/**
......@@ -124,7 +124,7 @@ function _getLangEnvVariable(locale?: string) {
return parts.join('_') + '.UTF-8';
}
export function getCwd(shell: IShellLaunchConfig, root: Uri, configHelper: ITerminalConfigHelper): string {
export function getCwd(shell: IShellLaunchConfig, root: Uri, customCwd: string): string {
if (shell.cwd) {
return shell.cwd;
}
......@@ -132,15 +132,11 @@ export function getCwd(shell: IShellLaunchConfig, root: Uri, configHelper: ITerm
let cwd: string | undefined;
// TODO: Handle non-existent customCwd
if (!shell.ignoreConfigurationCwd) {
// Evaluate custom cwd first
const customCwd = configHelper.config.cwd;
if (customCwd) {
if (paths.isAbsolute(customCwd)) {
cwd = customCwd;
} else if (root) {
cwd = paths.normalize(paths.join(root.fsPath, customCwd));
}
if (!shell.ignoreConfigurationCwd && customCwd) {
if (paths.isAbsolute(customCwd)) {
cwd = customCwd;
} else if (root) {
cwd = paths.normalize(paths.join(root.fsPath, customCwd));
}
}
......
......@@ -7,6 +7,7 @@ import { ITerminalChildProcess } from 'vs/workbench/parts/terminal/node/terminal
import { Event, Emitter } from 'vs/base/common/event';
import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerminalProcessExtHostProxy {
......@@ -31,6 +32,7 @@ export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerm
constructor(
public terminalId: number,
shellLaunchConfig: IShellLaunchConfig,
activeWorkspaceRootUri: URI,
cols: number,
rows: number,
@ITerminalService private _terminalService: ITerminalService,
......@@ -39,7 +41,7 @@ export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerm
this._extensionService.whenInstalledExtensionsRegistered().then(() => {
// TODO: MainThreadTerminalService is not ready at this point, fix this
setTimeout(() => {
this._terminalService.requestExtHostProcess(this, shellLaunchConfig, cols, rows);
this._terminalService.requestExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows);
}, 0);
});
}
......
......@@ -9,7 +9,6 @@ import * as platform from 'vs/base/common/platform';
import * as terminalEnvironment from 'vs/workbench/parts/terminal/node/terminalEnvironment';
import { URI as Uri } from 'vs/base/common/uri';
import { IStringDictionary } from 'vs/base/common/collections';
import { ITerminalConfigHelper } from 'vs/workbench/parts/terminal/common/terminal';
suite('Workbench - TerminalEnvironment', () => {
test('addTerminalEnvironmentKeys', () => {
......@@ -116,55 +115,37 @@ suite('Workbench - TerminalEnvironment', () => {
});
suite('getCwd', () => {
let configHelper: ITerminalConfigHelper;
setup(() => {
configHelper = <ITerminalConfigHelper>{
config: {
cwd: 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(terminalEnvironment.getCwd({ executable: null, args: [] }, null, configHelper), os.homedir());
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, null, undefined), os.homedir());
});
test('should use to the workspace if it exists', () => {
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, Uri.file('/foo'), configHelper), '/foo');
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, Uri.file('/foo'), undefined), '/foo');
});
test('should use an absolute custom cwd as is', () => {
configHelper.config.cwd = '/foo';
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, null, configHelper), '/foo');
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, null, '/foo'), '/foo');
});
test('should normalize a relative custom cwd against the workspace path', () => {
configHelper.config.cwd = 'foo';
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, Uri.file('/bar'), configHelper), '/bar/foo');
configHelper.config.cwd = './foo';
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, Uri.file('/bar'), configHelper), '/bar/foo');
configHelper.config.cwd = '../foo';
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, Uri.file('/bar'), configHelper), '/foo');
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, Uri.file('/bar'), 'foo'), '/bar/foo');
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, Uri.file('/bar'), './foo'), '/bar/foo');
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, Uri.file('/bar'), '../foo'), '/foo');
});
test('should fall back for relative a custom cwd that doesn\'t have a workspace', () => {
configHelper.config.cwd = 'foo';
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, null, configHelper), os.homedir());
configHelper.config.cwd = './foo';
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, null, configHelper), os.homedir());
configHelper.config.cwd = '../foo';
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, null, configHelper), os.homedir());
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, null, 'foo'), os.homedir());
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, null, './foo'), os.homedir());
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [] }, null, '../foo'), os.homedir());
});
test('should ignore custom cwd when told to ignore', () => {
configHelper.config.cwd = '/foo';
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [], ignoreConfigurationCwd: true }, Uri.file('/bar'), configHelper), '/bar');
assertPathsMatch(terminalEnvironment.getCwd({ executable: null, args: [], ignoreConfigurationCwd: true }, Uri.file('/bar'), '/foo'), '/bar');
});
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册