未验证 提交 a5c2f2ef 编写于 作者: A Alexandru Dima 提交者: GitHub

Merge pull request #108684 from microsoft/remoteTerminals

Introduce Remote Terminals
......@@ -86,8 +86,8 @@ function terminateProcess(process: cp.ChildProcess, cwd?: string): Promise<Termi
return Promise.resolve({ success: true });
}
export function getWindowsShell(): string {
return process.env['comspec'] || 'cmd.exe';
export function getWindowsShell(environment: Platform.IProcessEnvironment = process.env as Platform.IProcessEnvironment): string {
return environment['comspec'] || 'cmd.exe';
}
export abstract class AbstractProcess<TProgressData> {
......
......@@ -34,12 +34,18 @@ export interface RunCommandPipeArgs {
args: any[];
}
export class CLIServer {
export interface ICommandsExecuter {
executeCommand<T>(id: string, ...args: any[]): Promise<T>;
}
private _server: http.Server;
private _ipcHandlePath: string | undefined;
export class CLIServerBase {
private readonly _server: http.Server;
constructor(@IExtHostCommands private _commands: IExtHostCommands, @ILogService private logService: ILogService) {
constructor(
private readonly _commands: ICommandsExecuter,
private readonly logService: ILogService,
private readonly _ipcHandlePath: string,
) {
this._server = http.createServer((req, res) => this.onRequest(req, res));
this.setup().catch(err => {
logService.error(err);
......@@ -52,8 +58,6 @@ export class CLIServer {
}
private async setup(): Promise<string> {
this._ipcHandlePath = createRandomIPCHandle();
try {
this._server.listen(this.ipcHandlePath);
this._server.on('error', err => this.logService.error(err));
......@@ -176,3 +180,12 @@ export class CLIServer {
}
}
}
export class CLIServer extends CLIServerBase {
constructor(
@IExtHostCommands commands: IExtHostCommands,
@ILogService logService: ILogService
) {
super(commands, logService, createRandomIPCHandle());
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Barrier } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { revive } from 'vs/base/common/marshalling';
import { URI } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IRemoteTerminalService, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IRemoteTerminalProcessExecCommandEvent, IShellLaunchConfigDto, RemoteTerminalChannelClient, REMOTE_TERMINAL_CHANNEL_NAME } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel';
import { IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
export class RemoteTerminalService extends Disposable implements IRemoteTerminalService {
public _serviceBrand: undefined;
private readonly _remoteTerminalChannel: RemoteTerminalChannelClient | null;
constructor(
@ITerminalService _terminalService: ITerminalService,
@ITerminalInstanceService readonly terminalInstanceService: ITerminalInstanceService,
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@ILogService private readonly _logService: ILogService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ICommandService private readonly _commandService: ICommandService,
) {
super();
const connection = this._remoteAgentService.getConnection();
if (connection) {
this._remoteTerminalChannel = this._instantiationService.createInstance(RemoteTerminalChannelClient, connection.remoteAuthority, connection.getChannel(REMOTE_TERMINAL_CHANNEL_NAME));
} else {
this._remoteTerminalChannel = null;
}
}
public async createRemoteTerminalProcess(terminalId: number, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, configHelper: ITerminalConfigHelper,): Promise<ITerminalChildProcess> {
if (!this._remoteTerminalChannel) {
throw new Error(`Cannot create remote terminal when there is no remote!`);
}
return new RemoteTerminalProcess(terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, configHelper, this._remoteTerminalChannel, this._remoteAgentService, this._logService, this._commandService);
}
}
export class RemoteTerminalProcess extends Disposable implements ITerminalChildProcess {
public readonly _onProcessData = this._register(new Emitter<string>());
public readonly onProcessData: Event<string> = this._onProcessData.event;
private readonly _onProcessExit = this._register(new Emitter<number | undefined>());
public readonly onProcessExit: Event<number | undefined> = this._onProcessExit.event;
public readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>());
public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; }
private readonly _onProcessTitleChanged = this._register(new Emitter<string>());
public readonly onProcessTitleChanged: Event<string> = this._onProcessTitleChanged.event;
private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter<IShellLaunchConfig>());
public get onProcessResolvedShellLaunchConfig(): Event<IShellLaunchConfig> { return this._onProcessResolvedShellLaunchConfig.event; }
private _startBarrier: Barrier;
private _remoteTerminalId: number;
constructor(
private readonly _terminalId: number,
private readonly _shellLaunchConfig: IShellLaunchConfig,
private readonly _activeWorkspaceRootUri: URI | undefined,
private readonly _cols: number,
private readonly _rows: number,
private readonly _configHelper: ITerminalConfigHelper,
private readonly _remoteTerminalChannel: RemoteTerminalChannelClient,
private readonly _remoteAgentService: IRemoteAgentService,
private readonly _logService: ILogService,
private readonly _commandService: ICommandService,
) {
super();
this._startBarrier = new Barrier();
this._remoteTerminalId = 0;
}
public async start(): Promise<ITerminalLaunchError | undefined> {
// TODO@remoteAgentTerminals: Add a loading title only if this terminal is
// instantiated before a connection is up and running
setTimeout(() => this._onProcessTitleChanged.fire(nls.localize('terminal.integrated.starting', "Starting...")), 0);
// Fetch the environment to check shell permissions
const env = await this._remoteAgentService.getEnvironment();
if (!env) {
// Extension host processes are only allowed in remote extension hosts currently
throw new Error('Could not fetch remote environment');
}
const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions(env.os);
const shellLaunchConfigDto: IShellLaunchConfigDto = {
name: this._shellLaunchConfig.name,
executable: this._shellLaunchConfig.executable,
args: this._shellLaunchConfig.args,
cwd: this._shellLaunchConfig.cwd,
env: this._shellLaunchConfig.env
};
this._logService.trace('Spawning remote agent process', { terminalId: this._terminalId, shellLaunchConfigDto });
const result = await this._remoteTerminalChannel.createTerminalProcess(
shellLaunchConfigDto,
this._activeWorkspaceRootUri,
this._cols,
this._rows,
isWorkspaceShellAllowed,
);
this._remoteTerminalId = result.terminalId;
this._register(this._remoteTerminalChannel.onTerminalProcessEvent(this._remoteTerminalId)(event => {
switch (event.type) {
case 'ready':
return this._onProcessReady.fire({ pid: event.pid, cwd: event.cwd });
case 'titleChanged':
return this._onProcessTitleChanged.fire(event.title);
case 'data':
return this._onProcessData.fire(event.data);
case 'exit':
return this._onProcessExit.fire(event.exitCode);
case 'execCommand':
return this._execCommand(event);
}
}));
this._onProcessResolvedShellLaunchConfig.fire(reviveIShellLaunchConfig(result.resolvedShellLaunchConfig));
const startResult = await this._remoteTerminalChannel.startTerminalProcess(this._remoteTerminalId);
if (typeof startResult !== 'undefined') {
// An error occurred
return startResult;
}
this._startBarrier.open();
return undefined;
}
public shutdown(immediate: boolean): void {
this._startBarrier.wait().then(_ => {
this._remoteTerminalChannel.shutdownTerminalProcess(this._remoteTerminalId, immediate);
});
}
public input(data: string): void {
this._startBarrier.wait().then(_ => {
this._remoteTerminalChannel.sendInputToTerminalProcess(this._remoteTerminalId, data);
});
}
public resize(cols: number, rows: number): void {
this._startBarrier.wait().then(_ => {
this._remoteTerminalChannel.resizeTerminalProcess(this._remoteTerminalId, cols, rows);
});
}
public async getInitialCwd(): Promise<string> {
await this._startBarrier.wait();
return this._remoteTerminalChannel.getTerminalInitialCwd(this._remoteTerminalId);
}
public async getCwd(): Promise<string> {
await this._startBarrier.wait();
return this._remoteTerminalChannel.getTerminalCwd(this._remoteTerminalId);
}
/**
* TODO@roblourens I don't think this does anything useful in the EH and the value isn't used
*/
public async getLatency(): Promise<number> {
return 0;
}
private async _execCommand(event: IRemoteTerminalProcessExecCommandEvent): Promise<void> {
const reqId = event.reqId;
const commandArgs = event.commandArgs.map(arg => revive(arg));
try {
const result = await this._commandService.executeCommand(event.commandId, ...commandArgs);
this._remoteTerminalChannel.sendCommandResultToTerminalProcess(this._remoteTerminalId, reqId, false, result);
} catch (err) {
this._remoteTerminalChannel.sendCommandResultToTerminalProcess(this._remoteTerminalId, reqId, true, err);
}
}
}
function reviveIShellLaunchConfig(dto: IShellLaunchConfigDto): IShellLaunchConfig {
return {
name: dto.name,
executable: dto.executable,
args: dto.args,
cwd: (
(typeof dto.cwd === 'string' || typeof dto.cwd === 'undefined')
? dto.cwd
: URI.revive(dto.cwd)
),
env: dto.env,
hideFromUser: dto.hideFromUser
};
}
registerSingleton(IRemoteTerminalService, RemoteTerminalService);
......@@ -17,6 +17,7 @@ import { URI } from 'vs/base/common/uri';
export const ITerminalService = createDecorator<ITerminalService>('terminalService');
export const ITerminalInstanceService = createDecorator<ITerminalInstanceService>('terminalInstanceService');
export const IRemoteTerminalService = createDecorator<IRemoteTerminalService>('remoteTerminalService');
/**
* A service used by TerminalInstance (and components owned by it) that allows it to break its
......@@ -174,6 +175,18 @@ export interface ITerminalService {
requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): Promise<ITerminalLaunchError | undefined>;
}
export interface IRemoteExtHostEnvProvider {
getEnvironment(): Promise<IProcessEnvironment>;
}
export interface IRemoteTerminalService {
readonly _serviceBrand: undefined;
dispose(): void;
createRemoteTerminalProcess(terminalId: number, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, configHelper: ITerminalConfigHelper,): Promise<ITerminalChildProcess>;
}
/**
* Similar to xterm.js' ILinkProvider but using promises and hides xterm.js internals (like buffer
* positions, decorations, etc.) from the rest of vscode. This is the interface to use for
......
......@@ -18,7 +18,7 @@ import { Schemas } from 'vs/base/common/network';
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IProductService } from 'vs/platform/product/common/productService';
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IRemoteTerminalService, ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { Disposable } from 'vs/base/common/lifecycle';
......@@ -99,6 +99,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@IPathService private readonly _pathService: IPathService,
@IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService,
@IRemoteTerminalService private readonly _remoteTerminalService: IRemoteTerminalService,
) {
super();
this.ptyProcessReady = new Promise<void>(c => {
......@@ -157,7 +158,12 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
}
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot();
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper);
const enableRemoteAgentTerminals = this._workspaceConfigurationService.getValue('terminal.integrated.serverSpawn');
if (enableRemoteAgentTerminals) {
this._process = await this._remoteTerminalService.createRemoteTerminalProcess(this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper);
} else {
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper);
}
} else {
this._process = await this._launchProcess(shellLaunchConfig, cols, rows, this.userHome, isScreenReaderModeEnabled);
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { withNullAsUndefined } from 'vs/base/common/types';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
import { ITerminalConfiguration, ITerminalEnvironment, ITerminalLaunchError, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { SideBySideEditor, EditorResourceAccessor } from 'vs/workbench/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Schemas } from 'vs/base/common/network';
export const REMOTE_TERMINAL_CHANNEL_NAME = 'remoteterminal';
export interface IShellLaunchConfigDto {
name?: string;
executable?: string;
args?: string[] | string;
cwd?: string | UriComponents;
env?: { [key: string]: string | null; };
hideFromUser?: boolean;
}
export interface ISingleTerminalConfiguration<T> {
userValue: T | undefined;
value: T | undefined;
defaultValue: T | undefined;
}
export interface ICompleteTerminalConfiguration {
'terminal.integrated.automationShell.windows': ISingleTerminalConfiguration<string | string[]>;
'terminal.integrated.automationShell.osx': ISingleTerminalConfiguration<string | string[]>;
'terminal.integrated.automationShell.linux': ISingleTerminalConfiguration<string | string[]>;
'terminal.integrated.shell.windows': ISingleTerminalConfiguration<string | string[]>;
'terminal.integrated.shell.osx': ISingleTerminalConfiguration<string | string[]>;
'terminal.integrated.shell.linux': ISingleTerminalConfiguration<string | string[]>;
'terminal.integrated.shellArgs.windows': ISingleTerminalConfiguration<string | string[]>;
'terminal.integrated.shellArgs.osx': ISingleTerminalConfiguration<string | string[]>;
'terminal.integrated.shellArgs.linux': ISingleTerminalConfiguration<string | string[]>;
'terminal.integrated.env.windows': ISingleTerminalConfiguration<ITerminalEnvironment>;
'terminal.integrated.env.osx': ISingleTerminalConfiguration<ITerminalEnvironment>;
'terminal.integrated.env.linux': ISingleTerminalConfiguration<ITerminalEnvironment>;
'terminal.integrated.inheritEnv': boolean;
'terminal.integrated.cwd': string;
'terminal.integrated.detectLocale': 'auto' | 'off' | 'on';
}
export type ITerminalEnvironmentVariableCollections = [string, ISerializableEnvironmentVariableCollection][];
export interface IWorkspaceFolderData {
uri: UriComponents;
name: string;
index: number;
}
export interface ICreateTerminalProcessArguments {
configuration: ICompleteTerminalConfiguration;
resolvedVariables: { [name: string]: string; };
envVariableCollections: ITerminalEnvironmentVariableCollections;
shellLaunchConfig: IShellLaunchConfigDto;
workspaceFolders: IWorkspaceFolderData[];
activeWorkspaceFolder: IWorkspaceFolderData | null;
activeFileResource: UriComponents | undefined;
cols: number;
rows: number;
isWorkspaceShellAllowed: boolean;
resolverEnv: { [key: string]: string | null; } | undefined
}
export interface ICreateTerminalProcessResult {
terminalId: number;
resolvedShellLaunchConfig: IShellLaunchConfigDto;
}
export interface IStartTerminalProcessArguments {
id: number;
}
export interface ISendInputToTerminalProcessArguments {
id: number;
data: string;
}
export interface IShutdownTerminalProcessArguments {
id: number;
immediate: boolean;
}
export interface IResizeTerminalProcessArguments {
id: number;
cols: number;
rows: number;
}
export interface IGetTerminalInitialCwdArguments {
id: number;
}
export interface IGetTerminalCwdArguments {
id: number;
}
export interface ISendCommandResultToTerminalProcessArguments {
id: number;
reqId: number;
isError: boolean;
payload: any;
}
export interface IRemoteTerminalProcessReadyEvent {
type: 'ready';
pid: number;
cwd: string;
}
export interface IRemoteTerminalProcessTitleChangedEvent {
type: 'titleChanged';
title: string;
}
export interface IRemoteTerminalProcessDataEvent {
type: 'data'
data: string;
}
export interface IRemoteTerminalProcessExitEvent {
type: 'exit'
exitCode: number | undefined;
}
export interface IRemoteTerminalProcessExecCommandEvent {
type: 'execCommand';
reqId: number;
commandId: string;
commandArgs: any[];
}
export type IRemoteTerminalProcessEvent = (
IRemoteTerminalProcessReadyEvent
| IRemoteTerminalProcessTitleChangedEvent
| IRemoteTerminalProcessDataEvent
| IRemoteTerminalProcessExitEvent
| IRemoteTerminalProcessExecCommandEvent
);
export interface IOnTerminalProcessEventArguments {
id: number;
}
export class RemoteTerminalChannelClient {
constructor(
private readonly _remoteAuthority: string,
private readonly _channel: IChannel,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
@IConfigurationResolverService private readonly _resolverService: IConfigurationResolverService,
@IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService,
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@ILogService private readonly _logService: ILogService,
@IEditorService private readonly _editorService: IEditorService,
) {
}
private _readSingleTemrinalConfiguration<T>(key: string): ISingleTerminalConfiguration<T> {
const result = this._configurationService.inspect<T>(key);
return {
userValue: result.userValue,
value: result.value,
defaultValue: result.defaultValue,
};
}
public async createTerminalProcess(shellLaunchConfig: IShellLaunchConfigDto, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<ICreateTerminalProcessResult> {
const terminalConfig = this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION);
const configuration: ICompleteTerminalConfiguration = {
'terminal.integrated.automationShell.windows': this._readSingleTemrinalConfiguration('terminal.integrated.automationShell.windows'),
'terminal.integrated.automationShell.osx': this._readSingleTemrinalConfiguration('terminal.integrated.automationShell.osx'),
'terminal.integrated.automationShell.linux': this._readSingleTemrinalConfiguration('terminal.integrated.automationShell.linux'),
'terminal.integrated.shell.windows': this._readSingleTemrinalConfiguration('terminal.integrated.shell.windows'),
'terminal.integrated.shell.osx': this._readSingleTemrinalConfiguration('terminal.integrated.shell.osx'),
'terminal.integrated.shell.linux': this._readSingleTemrinalConfiguration('terminal.integrated.shell.linux'),
'terminal.integrated.shellArgs.windows': this._readSingleTemrinalConfiguration('terminal.integrated.shellArgs.windows'),
'terminal.integrated.shellArgs.osx': this._readSingleTemrinalConfiguration('terminal.integrated.shellArgs.osx'),
'terminal.integrated.shellArgs.linux': this._readSingleTemrinalConfiguration('terminal.integrated.shellArgs.linux'),
'terminal.integrated.env.windows': this._readSingleTemrinalConfiguration('terminal.integrated.env.windows'),
'terminal.integrated.env.osx': this._readSingleTemrinalConfiguration('terminal.integrated.env.osx'),
'terminal.integrated.env.linux': this._readSingleTemrinalConfiguration('terminal.integrated.env.linux'),
'terminal.integrated.inheritEnv': terminalConfig.inheritEnv,
'terminal.integrated.cwd': terminalConfig.cwd,
'terminal.integrated.detectLocale': terminalConfig.detectLocale,
};
// We will use the resolver service to resolve all the variables in the config / launch config
// But then we will keep only some variables, since the rest need to be resolved on the remote side
const resolvedVariables = Object.create(null);
const lastActiveWorkspace = activeWorkspaceRootUri ? withNullAsUndefined(this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined;
let allResolvedVariables: Map<string, string> | undefined = undefined;
try {
allResolvedVariables = await this._resolverService.resolveWithInteraction(lastActiveWorkspace, {
shellLaunchConfig,
configuration
});
} catch (err) {
this._logService.error(err);
}
if (allResolvedVariables) {
for (const [name, value] of allResolvedVariables.entries()) {
if (/^config:/.test(name) || name === 'selectedText' || name === 'lineNumber') {
resolvedVariables[name] = value;
}
}
}
const envVariableCollections: ITerminalEnvironmentVariableCollections = [];
for (const [k, v] of this._environmentVariableService.collections.entries()) {
envVariableCollections.push([k, serializeEnvironmentVariableCollection(v.map)]);
}
const resolverResult = await this._remoteAuthorityResolverService.resolveAuthority(this._remoteAuthority);
const resolverEnv = resolverResult.options && resolverResult.options.extensionHostEnv;
const workspaceFolders = this._workspaceContextService.getWorkspace().folders;
const activeWorkspaceFolder = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : null;
const activeFileResource = EditorResourceAccessor.getOriginalUri(this._editorService.activeEditor, {
supportSideBySide: SideBySideEditor.PRIMARY,
filterByScheme: [Schemas.file, Schemas.userData, Schemas.vscodeRemote]
});
const args: ICreateTerminalProcessArguments = {
configuration,
resolvedVariables,
envVariableCollections,
shellLaunchConfig,
workspaceFolders,
activeWorkspaceFolder,
activeFileResource,
cols,
rows,
isWorkspaceShellAllowed,
resolverEnv
};
return await this._channel.call<ICreateTerminalProcessResult>('$createTerminalProcess', args);
}
public async startTerminalProcess(terminalId: number): Promise<ITerminalLaunchError | void> {
const args: IStartTerminalProcessArguments = {
id: terminalId
};
return this._channel.call<ITerminalLaunchError | void>('$startTerminalProcess', args);
}
public onTerminalProcessEvent(terminalId: number): Event<IRemoteTerminalProcessEvent> {
const args: IOnTerminalProcessEventArguments = {
id: terminalId
};
return this._channel.listen<IRemoteTerminalProcessEvent>('$onTerminalProcessEvent', args);
}
public sendInputToTerminalProcess(id: number, data: string): Promise<void> {
const args: ISendInputToTerminalProcessArguments = {
id, data
};
return this._channel.call<void>('$sendInputToTerminalProcess', args);
}
public shutdownTerminalProcess(id: number, immediate: boolean): Promise<void> {
const args: IShutdownTerminalProcessArguments = {
id, immediate
};
return this._channel.call<void>('$shutdownTerminalProcess', args);
}
public resizeTerminalProcess(id: number, cols: number, rows: number): Promise<void> {
const args: IResizeTerminalProcessArguments = {
id, cols, rows
};
return this._channel.call<void>('$resizeTerminalProcess', args);
}
public getTerminalInitialCwd(id: number): Promise<string> {
const args: IGetTerminalInitialCwdArguments = {
id
};
return this._channel.call<string>('$getTerminalInitialCwd', args);
}
public getTerminalCwd(id: number): Promise<string> {
const args: IGetTerminalCwdArguments = {
id
};
return this._channel.call<string>('$getTerminalCwd', args);
}
public sendCommandResultToTerminalProcess(id: number, reqId: number, isError: boolean, payload: any): Promise<void> {
const args: ISendCommandResultToTerminalProcessArguments = {
id,
reqId,
isError,
payload
};
return this._channel.call<void>('$sendCommandResultToTerminalProcess', args);
}
}
......@@ -16,27 +16,27 @@ import { normalize, basename } from 'vs/base/common/path';
* shell that the terminal uses by default.
* @param p The platform to detect the shell of.
*/
export function getSystemShell(p: platform.Platform): string {
export function getSystemShell(p: platform.Platform, environment: platform.IProcessEnvironment = process.env as platform.IProcessEnvironment): string {
if (p === platform.Platform.Windows) {
if (platform.isWindows) {
return getSystemShellWindows();
return getSystemShellWindows(environment);
}
// Don't detect Windows shell when not on Windows
return processes.getWindowsShell();
return processes.getWindowsShell(environment);
}
// Only use $SHELL for the current OS
if (platform.isLinux && p === platform.Platform.Mac || platform.isMacintosh && p === platform.Platform.Linux) {
return '/bin/bash';
}
return getSystemShellUnixLike();
return getSystemShellUnixLike(environment);
}
let _TERMINAL_DEFAULT_SHELL_UNIX_LIKE: string | null = null;
function getSystemShellUnixLike(): string {
function getSystemShellUnixLike(environment: platform.IProcessEnvironment): string {
if (!_TERMINAL_DEFAULT_SHELL_UNIX_LIKE) {
let unixLikeTerminal = 'sh';
if (!platform.isWindows && process.env.SHELL) {
unixLikeTerminal = process.env.SHELL;
if (!platform.isWindows && environment.SHELL) {
unixLikeTerminal = environment.SHELL;
// Some systems have $SHELL set to /bin/false which breaks the terminal
if (unixLikeTerminal === '/bin/false') {
unixLikeTerminal = '/bin/bash';
......@@ -51,12 +51,12 @@ function getSystemShellUnixLike(): string {
}
let _TERMINAL_DEFAULT_SHELL_WINDOWS: string | null = null;
function getSystemShellWindows(): string {
function getSystemShellWindows(environment: platform.IProcessEnvironment): string {
if (!_TERMINAL_DEFAULT_SHELL_WINDOWS) {
const isAtLeastWindows10 = platform.isWindows && parseFloat(os.release()) >= 10;
const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
const powerShellPath = `${process.env.windir}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}\\WindowsPowerShell\\v1.0\\powershell.exe`;
_TERMINAL_DEFAULT_SHELL_WINDOWS = isAtLeastWindows10 ? powerShellPath : processes.getWindowsShell();
const is32ProcessOn64Windows = environment.hasOwnProperty('PROCESSOR_ARCHITEW6432');
const powerShellPath = `${environment.windir}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}\\WindowsPowerShell\\v1.0\\powershell.exe`;
_TERMINAL_DEFAULT_SHELL_WINDOWS = isAtLeastWindows10 ? powerShellPath : processes.getWindowsShell(environment);
}
return _TERMINAL_DEFAULT_SHELL_WINDOWS;
}
......
......@@ -10,7 +10,7 @@ import { isString } from 'vs/base/common/types';
let mainProcessParentEnv: IProcessEnvironment | undefined;
export async function getMainProcessParentEnv(): Promise<IProcessEnvironment> {
export async function getMainProcessParentEnv(baseEnvironment: IProcessEnvironment = process.env as IProcessEnvironment): Promise<IProcessEnvironment> {
if (mainProcessParentEnv) {
return mainProcessParentEnv;
}
......@@ -65,15 +65,15 @@ export async function getMainProcessParentEnv(): Promise<IProcessEnvironment> {
'TMPDIR'
];
rootEnvVars.forEach(k => {
if (process.env[k]) {
mainProcessParentEnv![k] = process.env[k]!;
if (baseEnvironment[k]) {
mainProcessParentEnv![k] = baseEnvironment[k]!;
}
});
}
// TODO: Windows should return a fresh environment block, might need native code?
if (isWindows) {
mainProcessParentEnv = process.env as IProcessEnvironment;
mainProcessParentEnv = baseEnvironment;
}
return mainProcessParentEnv!;
......
......@@ -80,6 +80,7 @@ import 'vs/workbench/services/extensionRecommendations/common/workspaceExtension
import 'vs/workbench/services/notification/common/notificationService';
import 'vs/workbench/services/userDataSync/common/userDataSyncUtil';
import 'vs/workbench/services/remote/common/remoteExplorerService';
import 'vs/workbench/contrib/terminal/browser/remoteTerminalService';
import 'vs/workbench/services/workingCopy/common/workingCopyService';
import 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册