提交 2fb8ab51 编写于 作者: D Daniel Imms

Fix most of process proxy

上级 6726ef4e
......@@ -178,7 +178,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
};
this._proxy.$createProcess(request.proxy.terminalId, shellLaunchConfigDto, request.cols, request.rows);
request.proxy.onInput(data => this._proxy.$acceptProcessInput(request.proxy.terminalId, data));
request.proxy.onResize((cols, rows) => this._proxy.$acceptProcessResize(request.proxy.terminalId, cols, rows));
request.proxy.onResize(dimensions => this._proxy.$acceptProcessResize(request.proxy.terminalId, dimensions.cols, dimensions.rows));
request.proxy.onShutdown(() => this._proxy.$acceptProcessShutdown(request.proxy.terminalId));
}
......
......@@ -5,17 +5,16 @@
'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, 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';
import { EXT_HOST_CREATION_DELAY } from 'vs/workbench/parts/terminal/common/terminal';
import { TerminalProcess } from 'vs/workbench/parts/terminal/node/terminalProcess';
const RENDERER_NO_PROCESS_ID = -1;
......@@ -226,7 +225,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
private _proxy: MainThreadTerminalServiceShape;
private _activeTerminal: ExtHostTerminal;
private _terminals: ExtHostTerminal[] = [];
private _terminalProcesses: { [id: number]: cp.ChildProcess } = {};
private _terminalProcesses: { [id: number]: TerminalProcess } = {};
private _terminalRenderers: ExtHostTerminalRenderer[] = [];
public get activeTerminal(): ExtHostTerminal { return this._activeTerminal; }
......@@ -397,27 +396,29 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
// 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));
this._terminalProcesses[id] = new TerminalProcess(shellLaunchConfig.executable, shellLaunchConfig.args, cwd, cols, rows);
// this._terminalProcesses[id].on
// this._terminalProcesses[id].on('message', (message: IMessageFromTerminalProcess) => {
// switch (message.type) {
// });
this._terminalProcesses[id].onProcessIdReady(pid => this._proxy.$sendProcessPid(id, pid));
this._terminalProcesses[id].onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title));
this._terminalProcesses[id].onProcessData(data => this._proxy.$sendProcessData(id, data));
this._terminalProcesses[id].onProcessExit((exitCode) => this._onProcessExit(id, exitCode));
}
public $acceptProcessInput(id: number, data: string): void {
if (this._terminalProcesses[id].connected) {
this._terminalProcesses[id].send({ event: 'input', data });
if (this._terminalProcesses[id].isConnected) {
this._terminalProcesses[id].input(data);
// this._terminalProcesses[id].send({ event: 'input', data });
}
}
public $acceptProcessResize(id: number, cols: number, rows: number): void {
if (this._terminalProcesses[id].connected) {
if (this._terminalProcesses[id].isConnected) {
try {
this._terminalProcesses[id].send({ event: 'resize', cols, rows });
this._terminalProcesses[id].resize(cols, rows);
// 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') {
......@@ -428,16 +429,17 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
}
public $acceptProcessShutdown(id: number): void {
if (this._terminalProcesses[id].connected) {
this._terminalProcesses[id].send({ event: 'shutdown' });
if (this._terminalProcesses[id].isConnected) {
this._terminalProcesses[id].shutdown();
// 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');
// const process = this._terminalProcesses[id];
// process.removeAllListeners('message');
// process.removeAllListeners('exit');
// Remove process reference
delete this._terminalProcesses[id];
......
......@@ -596,9 +596,9 @@ export interface ITerminalProcessExtHostProxy extends IDisposable {
emitPid(pid: number): void;
emitExit(exitCode: number): void;
onInput(listener: (data: string) => void): void;
onResize(listener: (cols: number, rows: number) => void): void;
onShutdown(listener: () => void): void;
onInput: Event<string>;
onResize: Event<{ cols: number, rows: number }>;
onShutdown: Event<void>;
}
export interface ITerminalProcessExtHostRequest {
......
......@@ -15,8 +15,8 @@ import { Emitter, Event } from 'vs/base/common/event';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
// import { ITerminalChildProcess, IMessageFromTerminalProcess } from 'vs/workbench/parts/terminal/node/terminal';
// import { TerminalProcessExtHostProxy } from 'vs/workbench/parts/terminal/node/terminalProcessExtHostProxy';
import { ITerminalChildProcess } from 'vs/workbench/parts/terminal/node/terminal';
import { TerminalProcessExtHostProxy } from 'vs/workbench/parts/terminal/node/terminalProcessExtHostProxy';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TerminalProcess } from 'vs/workbench/parts/terminal/node/terminalProcess';
......@@ -37,7 +37,7 @@ export class TerminalProcessManager implements ITerminalProcessManager {
public shellProcessId: number;
public initialCwd: string;
private _process: TerminalProcess;
private _process: ITerminalChildProcess;
private _preLaunchInputQueue: string[] = [];
private _disposables: IDisposable[] = [];
......@@ -59,7 +59,6 @@ export class TerminalProcessManager implements ITerminalProcessManager {
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILogService private _logService: ILogService
) {
console.log(this._terminalId, this._instantiationService);
this.ptyProcessReady = new TPromise<void>(c => {
this.onProcessReady(() => {
this._logService.debug(`Terminal process ready (shellProcessId: ${this.shellProcessId})`);
......@@ -93,42 +92,42 @@ 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);
// } else {
const locale = this._configHelper.config.setLocaleVariables ? platform.locale : undefined;
if (!shellLaunchConfig.executable) {
this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig);
}
const extensionHostOwned = (<any>this._configHelper.config).extHostProcess;
if (extensionHostOwned) {
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, cols, rows);
} else {
const locale = this._configHelper.config.setLocaleVariables ? platform.locale : undefined;
if (!shellLaunchConfig.executable) {
this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig);
}
const lastActiveWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot('file');
this.initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, lastActiveWorkspaceRootUri, this._configHelper);
// 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, this.initialCwd, locale, cols, rows);
const cwd = Uri.parse(require.toUrl('../node')).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._process = new TerminalProcess(env['PTYSHELL'], [], env['PTYCWD'], cols, rows);
// }
const lastActiveWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot('file');
this.initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, lastActiveWorkspaceRootUri, this._configHelper);
// 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, this.initialCwd, locale, cols, rows);
const cwd = Uri.parse(require.toUrl('../node')).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._process = new TerminalProcess(env['PTYSHELL'], [], env['PTYCWD'], cols, rows);
}
this.processState = ProcessState.LAUNCHING;
this._process.onData(data => {
this._process.onProcessData(data => {
this._onProcessData.fire(data);
});
......@@ -143,8 +142,8 @@ export class TerminalProcessManager implements ITerminalProcessManager {
}
});
this._process.onTitleChanged(title => this._onProcessTitle.fire(title));
this._process.onExit(exitCode => this._onExit(exitCode));
this._process.onProcessTitleChanged(title => this._onProcessTitle.fire(title));
this._process.onProcessExit(exitCode => this._onExit(exitCode));
// this._process.on('message', message => this._onMessage(message));
// this._process.on('exit', exitCode => this._onExit(exitCode));
......
......@@ -7,30 +7,24 @@ import * as os from 'os';
import * as platform from 'vs/base/common/platform';
import * as processes from 'vs/base/node/processes';
import { readFile, fileExists } from 'vs/base/node/pfs';
export interface IMessageFromTerminalProcess {
type: 'pid' | 'data' | 'title';
content: number | string;
}
export interface IMessageToTerminalProcess {
event: 'resize' | 'input' | 'shutdown';
data?: string;
cols?: number;
rows?: number;
}
import { Event } from 'vs/base/common/event';
/**
* An interface representing a raw terminal child process, this is a subset of the
* child_process.ChildProcess node.js interface.
*/
export interface ITerminalChildProcess {
readonly connected: boolean;
// TODO: Remove connected and references to it
readonly isConnected: boolean;
send(message: IMessageToTerminalProcess): boolean;
onProcessData: Event<string>;
onProcessExit: Event<number>;
onProcessIdReady: Event<number>;
onProcessTitleChanged: Event<string>;
on(event: 'exit', listener: (code: number) => void): this;
on(event: 'message', listener: (message: IMessageFromTerminalProcess) => void): this;
shutdown(): void;
input(data: string): void;
resize(cols: number, rows: number): void;
}
let _TERMINAL_DEFAULT_SHELL_UNIX_LIKE: string = null;
......
......@@ -58,23 +58,23 @@ export function createTerminalEnv(parentEnv: IStringDictionary<string>, shell: I
mergeEnvironments(env, shell.env);
}
env['PTYPID'] = process.pid.toString();
env['PTYSHELL'] = shell.executable;
// 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;
// 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'] = _getLangEnvVariable(locale);
if (cols && rows) {
env['PTYCOLS'] = cols.toString();
env['PTYROWS'] = rows.toString();
}
// if (cols && rows) {
// env['PTYCOLS'] = cols.toString();
// env['PTYROWS'] = rows.toString();
// }
env['AMD_ENTRYPOINT'] = 'vs/workbench/parts/terminal/node/terminalProcess';
return env;
}
......
......@@ -8,21 +8,22 @@ import * as path from 'path';
import * as pty from 'node-pty';
import { Event, Emitter } from 'vs/base/common/event';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { ITerminalChildProcess } from 'vs/workbench/parts/terminal/node/terminal';
export class TerminalProcess {
export class TerminalProcess implements ITerminalChildProcess {
private _exitCode: number;
private _closeTimeout: number;
private _ptyProcess: pty.IPty;
private _currentTitle: string = '';
private readonly _onData: Emitter<string> = new Emitter<string>();
public get onData(): Event<string> { return this._onData.event; }
private readonly _onExit: Emitter<number> = new Emitter<number>();
public get onExit(): Event<number> { return this._onExit.event; }
private readonly _onProcessData: Emitter<string> = new Emitter<string>();
public get onProcessData(): Event<string> { return this._onProcessData.event; }
private readonly _onProcessExit: Emitter<number> = new Emitter<number>();
public get onProcessExit(): Event<number> { return this._onProcessExit.event; }
private readonly _onProcessIdReady: Emitter<number> = new Emitter<number>();
public get onProcessIdReady(): Event<number> { return this._onProcessIdReady.event; }
private readonly _onTitleChanged: Emitter<string> = new Emitter<string>();
public get onTitleChanged(): Event<string> { return this._onTitleChanged.event; }
private readonly _onProcessTitleChanged: Emitter<string> = new Emitter<string>();
public get onProcessTitleChanged(): Event<string> { return this._onProcessTitleChanged.event; }
constructor(shell: string, args: string | string[], cwd: string, cols: number, rows: number) {
// The pty process needs to be run in its own child process to get around maxing out CPU on Mac,
......@@ -46,7 +47,7 @@ export class TerminalProcess {
this._ptyProcess = pty.spawn(shell, args, options);
this._ptyProcess.on('data', (data) => {
this._onData.fire(data);
this._onProcessData.fire(data);
if (this._closeTimeout) {
clearTimeout(this._closeTimeout);
this._queueProcessExit();
......@@ -112,7 +113,7 @@ export class TerminalProcess {
// TODO: Dispose correctly
this._closeTimeout = setTimeout(() => {
this._ptyProcess.kill();
this._onExit.fire(this._exitCode);
this._onProcessExit.fire(this._exitCode);
}, 250);
}
......@@ -121,7 +122,7 @@ export class TerminalProcess {
}
private _sendProcessTitle(): void {
this._currentTitle = this._ptyProcess.process;
this._onTitleChanged.fire(this._currentTitle);
this._onProcessTitleChanged.fire(this._currentTitle);
}
public shutdown(): void {
......
......@@ -3,17 +3,33 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITerminalChildProcess, IMessageToTerminalProcess, IMessageFromTerminalProcess } from 'vs/workbench/parts/terminal/node/terminal';
import { EventEmitter } from 'events';
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, toDisposable } from 'vs/base/common/lifecycle';
import { IDisposable } from 'vs/base/common/lifecycle';
export class TerminalProcessExtHostProxy extends EventEmitter implements ITerminalChildProcess, ITerminalProcessExtHostProxy {
export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerminalProcessExtHostProxy {
// For ext host processes connected checks happen on the ext host
public connected: boolean = true;
public isConnected: boolean = true;
private _disposables: IDisposable[] = [];
private readonly _onProcessData: Emitter<string> = new Emitter<string>();
public get onProcessData(): Event<string> { return this._onProcessData.event; }
private readonly _onProcessExit: Emitter<number> = new Emitter<number>();
public get onProcessExit(): Event<number> { return this._onProcessExit.event; }
private readonly _onProcessIdReady: Emitter<number> = new Emitter<number>();
public get onProcessIdReady(): Event<number> { return this._onProcessIdReady.event; }
private readonly _onProcessTitleChanged: Emitter<string> = new Emitter<string>();
public get onProcessTitleChanged(): Event<string> { return this._onProcessTitleChanged.event; }
private readonly _onInput: Emitter<string> = new Emitter<string>();
public get onInput(): Event<string> { return this._onInput.event; }
private readonly _onResize: Emitter<{ cols: number, rows: number }> = new Emitter<{ cols: number, rows: number }>();
public get onResize(): Event<{ cols: number, rows: number }> { return this._onResize.event; }
private readonly _onShutdown: Emitter<void> = new Emitter<void>();
public get onShutdown(): Event<void> { return this._onShutdown.event; }
constructor(
public terminalId: number,
shellLaunchConfig: IShellLaunchConfig,
......@@ -21,8 +37,6 @@ export class TerminalProcessExtHostProxy extends EventEmitter implements ITermin
rows: number,
@ITerminalService private _terminalService: ITerminalService
) {
super();
// TODO: Return TPromise<boolean> indicating success? Teardown if failure?
this._terminalService.requestExtHostProcess(this, shellLaunchConfig, cols, rows);
}
......@@ -33,46 +47,59 @@ export class TerminalProcessExtHostProxy extends EventEmitter implements ITermin
}
public emitData(data: string): void {
this.emit('message', { type: 'data', content: data } as IMessageFromTerminalProcess);
this._onProcessData.fire(data);
}
public emitTitle(title: string): void {
this.emit('message', { type: 'title', content: title } as IMessageFromTerminalProcess);
this._onProcessTitleChanged.fire(title);
}
public emitPid(pid: number): void {
this.emit('message', { type: 'pid', content: pid } as IMessageFromTerminalProcess);
this._onProcessIdReady.fire(pid);
}
public emitExit(exitCode: number): void {
this.emit('exit', exitCode);
this._onProcessExit.fire(exitCode);
this.dispose();
}
public send(message: IMessageToTerminalProcess): boolean {
switch (message.event) {
case 'input': this.emit('input', message.data); break;
case 'resize': this.emit('resize', message.cols, message.rows); break;
case 'shutdown': this.emit('shutdown'); break;
}
return true;
}
// public send(message: IMessageToTerminalProcess): boolean {
// switch (message.event) {
// case 'input': this.emit('input', message.data); break;
// case 'resize': this.emit('resize', message.cols, message.rows); break;
// case 'shutdown': this.emit('shutdown'); break;
// }
// return true;
// }
public onInput(listener: (data: string) => void): void {
const outerListener = (data) => listener(data);
this.on('input', outerListener);
this._disposables.push(toDisposable(() => this.removeListener('input', outerListener)));
public shutdown(): void {
this._onShutdown.fire();
}
public onResize(listener: (cols: number, rows: number) => void): void {
const outerListener = (cols, rows) => listener(cols, rows);
this.on('resize', outerListener);
this._disposables.push(toDisposable(() => this.removeListener('resize', outerListener)));
public input(data: string): void {
this._onInput.fire(data);
}
public onShutdown(listener: () => void): void {
const outerListener = () => listener();
this.on('shutdown', outerListener);
this._disposables.push(toDisposable(() => this.removeListener('shutdown', outerListener)));
public resize(cols: number, rows: number): void {
this._onResize.fire({ cols, rows });
}
// public onInput(listener: (data: string) => void): void {
// const outerListener = (data) => listener(data);
// this.on('input', outerListener);
// this._disposables.push(toDisposable(() => this.removeListener('input', outerListener)));
// }
// public onResize(listener: (cols: number, rows: number) => void): void {
// const outerListener = (cols, rows) => listener(cols, rows);
// this.on('resize', outerListener);
// this._disposables.push(toDisposable(() => this.removeListener('resize', outerListener)));
// }
// public onShutdown(listener: () => void): void {
// const outerListener = () => listener();
// this.on('shutdown', outerListener);
// this._disposables.push(toDisposable(() => this.removeListener('shutdown', outerListener)));
// }
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册