mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-25 11:08:51 +01:00
Merge pull request #48226 from Microsoft/tyriar/terminal_process
Refactor terminal processes
This commit is contained in:
@@ -114,7 +114,7 @@ export function createApiFactory(
|
||||
const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures));
|
||||
const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService());
|
||||
const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands));
|
||||
const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol));
|
||||
const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol, extHostConfiguration, extHostLogService));
|
||||
const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService));
|
||||
const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, new ExtHostSearch(rpcProtocol));
|
||||
const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace));
|
||||
|
||||
@@ -319,6 +319,11 @@ export interface MainThreadTerminalServiceShape extends IDisposable {
|
||||
$hide(terminalId: number): void;
|
||||
$sendText(terminalId: number, text: string, addNewLine: boolean): void;
|
||||
$show(terminalId: number, preserveFocus: boolean): void;
|
||||
|
||||
$sendProcessTitle(terminalId: number, title: string): void;
|
||||
$sendProcessData(terminalId: number, data: string): void;
|
||||
$sendProcessPid(terminalId: number, pid: number): void;
|
||||
$sendProcessExit(terminalId: number, exitCode: number): void;
|
||||
}
|
||||
|
||||
export interface MyQuickPickItems extends IPickOpenEntry {
|
||||
@@ -729,10 +734,22 @@ export interface ExtHostQuickOpenShape {
|
||||
$validateInput(input: string): TPromise<string>;
|
||||
}
|
||||
|
||||
export interface ShellLaunchConfigDto {
|
||||
name?: string;
|
||||
executable?: string;
|
||||
args?: string[] | string;
|
||||
cwd?: string;
|
||||
env?: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface ExtHostTerminalServiceShape {
|
||||
$acceptTerminalClosed(id: number): void;
|
||||
$acceptTerminalOpened(id: number, name: string): void;
|
||||
$acceptTerminalProcessId(id: number, processId: number): void;
|
||||
$createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, cols: number, rows: number): void;
|
||||
$acceptProcessInput(id: number, data: string): void;
|
||||
$acceptProcessResize(id: number, cols: number, rows: number): void;
|
||||
$acceptProcessShutdown(id: number): void;
|
||||
}
|
||||
|
||||
export interface ExtHostSCMShape {
|
||||
|
||||
@@ -5,11 +5,18 @@
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as cp from 'child_process';
|
||||
import * as os from 'os';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as terminalEnvironment from 'vs/workbench/parts/terminal/node/terminalEnvironment';
|
||||
import Uri from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext } from './extHost.protocol';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext, ShellLaunchConfigDto } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { IMessageFromTerminalProcess } from 'vs/workbench/parts/terminal/node/terminal';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export class ExtHostTerminal implements vscode.Terminal {
|
||||
|
||||
private _name: string;
|
||||
private _id: number;
|
||||
private _proxy: MainThreadTerminalServiceShape;
|
||||
@@ -81,8 +88,11 @@ export class ExtHostTerminal implements vscode.Terminal {
|
||||
}
|
||||
|
||||
public _setProcessId(processId: number): void {
|
||||
this._pidPromiseComplete(processId);
|
||||
this._pidPromiseComplete = null;
|
||||
// The event may fire 2 times when the panel is restored
|
||||
if (this._pidPromiseComplete) {
|
||||
this._pidPromiseComplete(processId);
|
||||
this._pidPromiseComplete = null;
|
||||
}
|
||||
}
|
||||
|
||||
private _queueApiRequest(callback: (...args: any[]) => void, args: any[]) {
|
||||
@@ -102,19 +112,22 @@ export class ExtHostTerminal implements vscode.Terminal {
|
||||
}
|
||||
|
||||
export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
|
||||
private readonly _onDidCloseTerminal: Emitter<vscode.Terminal>;
|
||||
private readonly _onDidOpenTerminal: Emitter<vscode.Terminal>;
|
||||
private _proxy: MainThreadTerminalServiceShape;
|
||||
private _terminals: ExtHostTerminal[];
|
||||
private _terminals: ExtHostTerminal[] = [];
|
||||
private _terminalProcesses: { [id: number]: cp.ChildProcess } = {};
|
||||
|
||||
public get terminals(): ExtHostTerminal[] { return this._terminals; }
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private _extHostConfiguration: ExtHostConfiguration,
|
||||
private _logService: ILogService
|
||||
) {
|
||||
this._onDidCloseTerminal = new Emitter<vscode.Terminal>();
|
||||
this._onDidOpenTerminal = new Emitter<vscode.Terminal>();
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadTerminalService);
|
||||
this._terminals = [];
|
||||
}
|
||||
|
||||
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[]): vscode.Terminal {
|
||||
@@ -168,6 +181,99 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
}
|
||||
}
|
||||
|
||||
public $createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, cols: number, rows: number): void {
|
||||
// TODO: This function duplicates a lot of TerminalProcessManager.createProcess, ideally
|
||||
// they would be merged into a single implementation.
|
||||
|
||||
const terminalConfig = this._extHostConfiguration.getConfiguration('terminal.integrated');
|
||||
|
||||
const locale = terminalConfig.get('setLocaleVariables') ? platform.locale : undefined;
|
||||
if (!shellLaunchConfig.executable) {
|
||||
// TODO: This duplicates some of TerminalConfigHelper.mergeDefaultShellPathAndArgs and should be merged
|
||||
// this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig);
|
||||
|
||||
const platformKey = platform.isWindows ? 'windows' : platform.isMacintosh ? 'osx' : 'linux';
|
||||
const shellConfigValue: string = terminalConfig.get(`shell.${platformKey}`);
|
||||
const shellArgsConfigValue: string = terminalConfig.get(`shellArgs.${platformKey}`);
|
||||
|
||||
shellLaunchConfig.executable = shellConfigValue;
|
||||
shellLaunchConfig.args = shellArgsConfigValue;
|
||||
}
|
||||
|
||||
// TODO: Base the cwd on the last active workspace root
|
||||
// const lastActiveWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot('file');
|
||||
// this.initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, lastActiveWorkspaceRootUri, this._configHelper);
|
||||
const initialCwd = os.homedir();
|
||||
|
||||
// TODO: Pull in and resolve config settings
|
||||
// // Resolve env vars from config and shell
|
||||
// const lastActiveWorkspaceRoot = this._workspaceContextService.getWorkspaceFolder(lastActiveWorkspaceRootUri);
|
||||
// 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);
|
||||
// shellLaunchConfig.env = envFromShell;
|
||||
|
||||
// Merge process env with the env from config
|
||||
const parentEnv = { ...process.env };
|
||||
// terminalEnvironment.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 = terminalEnvironment.createTerminalEnv(parentEnv, shellLaunchConfig, initialCwd, locale, cols, rows);
|
||||
let cwd = Uri.parse(require.toUrl('../../parts/terminal/node')).fsPath;
|
||||
const options = { env, cwd, execArgv: [] };
|
||||
|
||||
// Fork the process and listen for messages
|
||||
this._logService.debug(`Terminal process launching on ext host`, options);
|
||||
this._terminalProcesses[id] = cp.fork(Uri.parse(require.toUrl('bootstrap')).fsPath, ['--type=terminal'], options);
|
||||
this._terminalProcesses[id].on('message', (message: IMessageFromTerminalProcess) => {
|
||||
switch (message.type) {
|
||||
case 'pid': this._proxy.$sendProcessPid(id, <number>message.content); break;
|
||||
case 'title': this._proxy.$sendProcessTitle(id, <string>message.content); break;
|
||||
case 'data': this._proxy.$sendProcessData(id, <string>message.content); break;
|
||||
}
|
||||
});
|
||||
this._terminalProcesses[id].on('exit', (exitCode) => this._onProcessExit(id, exitCode));
|
||||
}
|
||||
|
||||
public $acceptProcessInput(id: number, data: string): void {
|
||||
if (this._terminalProcesses[id].connected) {
|
||||
this._terminalProcesses[id].send({ event: 'input', data });
|
||||
}
|
||||
}
|
||||
|
||||
public $acceptProcessResize(id: number, cols: number, rows: number): void {
|
||||
if (this._terminalProcesses[id].connected) {
|
||||
try {
|
||||
this._terminalProcesses[id].send({ event: 'resize', cols, rows });
|
||||
} catch (error) {
|
||||
// We tried to write to a closed pipe / channel.
|
||||
if (error.code !== 'EPIPE' && error.code !== 'ERR_IPC_CHANNEL_CLOSED') {
|
||||
throw (error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public $acceptProcessShutdown(id: number): void {
|
||||
if (this._terminalProcesses[id].connected) {
|
||||
this._terminalProcesses[id].send({ event: 'shutdown' });
|
||||
}
|
||||
}
|
||||
|
||||
private _onProcessExit(id: number, exitCode: number): void {
|
||||
// Remove listeners
|
||||
const process = this._terminalProcesses[id];
|
||||
process.removeAllListeners('message');
|
||||
process.removeAllListeners('exit');
|
||||
|
||||
// Remove process reference
|
||||
delete this._terminalProcesses[id];
|
||||
|
||||
// Send exit event to main side
|
||||
this._proxy.$sendProcessExit(id, exitCode);
|
||||
}
|
||||
|
||||
private _getTerminalById(id: number): ExtHostTerminal {
|
||||
let index = this._getTerminalIndexById(id);
|
||||
return index !== null ? this._terminals[index] : null;
|
||||
@@ -176,6 +282,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
private _getTerminalIndexById(id: number): number {
|
||||
let index: number = null;
|
||||
this._terminals.some((terminal, i) => {
|
||||
// TODO: This shouldn't be cas
|
||||
let thisId = (<any>terminal)._id;
|
||||
if (thisId === id) {
|
||||
index = i;
|
||||
@@ -188,7 +295,6 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
}
|
||||
|
||||
class ApiRequest {
|
||||
|
||||
private _callback: (...args: any[]) => void;
|
||||
private _args: any[];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user