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

Refactor terminal launch logic to improve error

Fixes #99905
Part of #99996
上级 314446f9
......@@ -243,6 +243,10 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess {
constructor(private readonly _pty: vscode.Pseudoterminal) { }
async start(): Promise<undefined> {
return undefined;
}
shutdown(): void {
this._pty.close();
}
......
......@@ -25,7 +25,7 @@ import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderB
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager';
import { IShellLaunchConfig, ITerminalDimensions, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, LEGACY_CONSOLE_MODE_EXIT_CODE, DEFAULT_COMMANDS_TO_SKIP_SHELL } from 'vs/workbench/contrib/terminal/common/terminal';
import { IShellLaunchConfig, ITerminalDimensions, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, LEGACY_CONSOLE_MODE_EXIT_CODE, DEFAULT_COMMANDS_TO_SKIP_SHELL, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
......@@ -911,9 +911,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// Create the process asynchronously to allow the terminal's container
// to be created so dimensions are accurate
setTimeout(() => {
this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._accessibilityService.isScreenReaderOptimized());
}, 0);
this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._accessibilityService.isScreenReaderOptimized()).then(error => {
if (error) {
// TODO: Tear down
this._onProcessExit(error);
}
});
}
private getShellType(executable: string): TerminalShellType {
......@@ -948,48 +951,53 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
* @param exitCode The exit code of the process, this is undefined when the terminal was exited
* through user action.
*/
private _onProcessExit(exitCode?: number): void {
private _onProcessExit(exitCodeOrError?: number | ITerminalLaunchError): void {
// Prevent dispose functions being triggered multiple times
if (this._isExiting) {
return;
}
this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`);
this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${this._exitCode}`);
this._exitCode = exitCode;
this._isExiting = true;
let exitCodeMessage: string | undefined;
// Create exit code message
if (exitCode) {
if (exitCode === SHELL_PATH_INVALID_EXIT_CODE) {
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPath', 'The terminal shell path "{0}" does not exist', this._shellLaunchConfig.executable);
} else if (exitCode === SHELL_PATH_DIRECTORY_EXIT_CODE) {
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPathDirectory', 'The terminal shell path "{0}" is a directory', this._shellLaunchConfig.executable);
} else if (exitCode === SHELL_CWD_INVALID_EXIT_CODE && this._shellLaunchConfig.cwd) {
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidCWD', 'The terminal shell CWD "{0}" does not exist', this._shellLaunchConfig.cwd.toString());
} else if (exitCode === LEGACY_CONSOLE_MODE_EXIT_CODE) {
exitCodeMessage = nls.localize('terminal.integrated.legacyConsoleModeError', 'The terminal failed to launch properly because your system has legacy console mode enabled, uncheck "Use legacy console" cmd.exe\'s properties to fix this.');
} else if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
let args = '';
if (typeof this._shellLaunchConfig.args === 'string') {
args = ` ${this._shellLaunchConfig.args}`;
} else if (this._shellLaunchConfig.args && this._shellLaunchConfig.args.length) {
args = ' ' + this._shellLaunchConfig.args.map(a => {
if (typeof a === 'string' && a.indexOf(' ') !== -1) {
return `'${a}'`;
}
return a;
}).join(' ');
}
if (this._shellLaunchConfig.executable) {
exitCodeMessage = nls.localize('terminal.integrated.launchFailed', 'The terminal process command \'{0}{1}\' failed to launch (exit code: {2})', this._shellLaunchConfig.executable, args, exitCode);
switch (typeof exitCodeOrError) {
case 'number':
this._exitCode = exitCodeOrError;
// TODO: Add button for all failures to a help page
if (this._exitCode === LEGACY_CONSOLE_MODE_EXIT_CODE) {
exitCodeMessage = nls.localize('terminal.integrated.legacyConsoleModeError', 'The terminal failed to launch properly because your system has legacy console mode enabled, uncheck "Use legacy console" cmd.exe\'s properties to fix this.');
} else if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
let args = '';
if (typeof this._shellLaunchConfig.args === 'string') {
args = ` ${this._shellLaunchConfig.args}`;
} else if (this._shellLaunchConfig.args && this._shellLaunchConfig.args.length) {
args = ' ' + this._shellLaunchConfig.args.map(a => {
if (typeof a === 'string' && a.indexOf(' ') !== -1) {
return `'${a}'`;
}
return a;
}).join(' ');
}
if (this._shellLaunchConfig.executable) {
console.log('FAIL 1', this._shellLaunchConfig.executable);
console.log('FAIL 2', args);
console.log('FAIL 3', '"' + this._exitCode + '"');
exitCodeMessage = nls.localize('terminal.integrated.launchFailed', 'The terminal process command \'{0}{1}\' failed to launch (exit code: {2})', this._shellLaunchConfig.executable, args, this._exitCode);
} else {
exitCodeMessage = nls.localize('terminal.integrated.launchFailedExtHost', 'The terminal process failed to launch (exit code: {0})', this._exitCode);
}
} else {
exitCodeMessage = nls.localize('terminal.integrated.launchFailedExtHost', 'The terminal process failed to launch (exit code: {0})', exitCode);
exitCodeMessage = nls.localize('terminal.integrated.exitedWithCode', 'The terminal process terminated with exit code: {0}', this._exitCode);
}
} else {
exitCodeMessage = nls.localize('terminal.integrated.exitedWithCode', 'The terminal process terminated with exit code: {0}', exitCode);
}
break;
case 'object':
this._exitCode = exitCodeOrError.code;
exitCodeMessage = `${nls.localize('launchError.failed', "The terminal failed to launch:")} ${exitCodeOrError.message}`;
break;
}
this._logService.debug(`Terminal process exit (id: ${this.id}) state ${this._processManager.processState}`);
......@@ -1028,7 +1036,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
}
this._onExit.fire(exitCode);
this._onExit.fire(this._exitCode);
}
private _attachPressAnyKeyToCloseListener(xterm: XTermTerminal) {
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensions, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
......@@ -28,6 +28,8 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter<IShellLaunchConfig>());
public get onProcessResolvedShellLaunchConfig(): Event<IShellLaunchConfig> { return this._onProcessResolvedShellLaunchConfig.event; }
private readonly _onStart = this._register(new Emitter<void>());
public readonly onStart: Event<void> = this._onStart.event;
private readonly _onInput = this._register(new Emitter<string>());
public readonly onInput: Event<string> = this._onInput.event;
private readonly _onResize: Emitter<{ cols: number, rows: number }> = this._register(new Emitter<{ cols: number, rows: number }>());
......@@ -47,31 +49,15 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
constructor(
public terminalId: number,
shellLaunchConfig: IShellLaunchConfig,
activeWorkspaceRootUri: URI | undefined,
cols: number,
rows: number,
configHelper: ITerminalConfigHelper,
private _shellLaunchConfig: IShellLaunchConfig,
private _activeWorkspaceRootUri: URI | undefined,
private _cols: number,
private _rows: number,
private _configHelper: ITerminalConfigHelper,
@ITerminalService private readonly _terminalService: ITerminalService,
@IRemoteAgentService readonly remoteAgentService: IRemoteAgentService
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService
) {
super();
// Request a process if needed, if this is a virtual process this step can be skipped as
// there is no real "process" and we know it's ready on the ext host already.
if (shellLaunchConfig.isExtensionTerminal) {
this._terminalService.requestStartExtensionTerminal(this, cols, rows);
} else {
remoteAgentService.getEnvironment().then(env => {
if (!env) {
throw new Error('Could not fetch environment');
}
this._terminalService.requestSpawnExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, configHelper.checkWorkspaceShellPermissions(env.os));
});
if (!hasReceivedResponse) {
setTimeout(() => this._onProcessTitleChanged.fire(nls.localize('terminal.integrated.starting', "Starting...")), 0);
}
}
}
public emitData(data: string): void {
......@@ -118,6 +104,26 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
}
}
public async start(): Promise<ITerminalLaunchError | undefined> {
// Request a process if needed, if this is a virtual process this step can be skipped as
// there is no real "process" and we know it's ready on the ext host already.
if (this._shellLaunchConfig.isExtensionTerminal) {
this._terminalService.requestStartExtensionTerminal(this, this._cols, this._rows);
} else {
const env = await this._remoteAgentService.getEnvironment();
if (!env) {
throw new Error('Could not fetch environment');
}
this._terminalService.requestSpawnExtHostProcess(this, this._shellLaunchConfig, this._activeWorkspaceRootUri, this._cols, this._rows, this._configHelper.checkWorkspaceShellPermissions(env.os));
if (!hasReceivedResponse) {
setTimeout(() => this._onProcessTitleChanged.fire(nls.localize('terminal.integrated.starting', "Starting...")), 0);
}
}
// TODO: Wait and return
return undefined;
}
public shutdown(immediate: boolean): void {
this._onShutdown.fire(immediate);
}
......
......@@ -6,7 +6,7 @@
import * as platform from 'vs/base/common/platform';
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { env as processEnv } from 'vs/base/common/process';
import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal';
import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment, ITerminalDimensions, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
import { ILogService } from 'vs/platform/log/common/log';
import { Emitter, Event } from 'vs/base/common/event';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
......@@ -127,7 +127,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
cols: number,
rows: number,
isScreenReaderModeEnabled: boolean
): Promise<void> {
): Promise<ITerminalLaunchError | undefined> {
if (shellLaunchConfig.isExtensionTerminal) {
this._processType = ProcessType.ExtensionTerminal;
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, undefined, cols, rows, this._configHelper);
......@@ -162,7 +162,12 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
this._process = await this._launchProcess(shellLaunchConfig, cols, rows, this.userHome, isScreenReaderModeEnabled);
}
}
this.processState = ProcessState.LAUNCHING;
const error = await this._process.start();
if (error) {
return error;
}
this._process.onProcessData(data => {
const beforeProcessDataEvent: IBeforeProcessDataEvent = { data };
......@@ -198,6 +203,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
this.processState = ProcessState.RUNNING;
}
}, LAUNCHING_DURATION);
return undefined;
}
private async _launchProcess(
......
......@@ -70,9 +70,6 @@ export const TERMINAL_ACTION_CATEGORY = nls.localize('terminalCategory', "Termin
export const DEFAULT_LETTER_SPACING = 0;
export const MINIMUM_LETTER_SPACING = -5;
export const DEFAULT_LINE_HEIGHT = 1;
export const SHELL_PATH_INVALID_EXIT_CODE = -1;
export const SHELL_PATH_DIRECTORY_EXIT_CODE = -2;
export const SHELL_CWD_INVALID_EXIT_CODE = -3;
export const LEGACY_CONSOLE_MODE_EXIT_CODE = 3221225786; // microsoft/vscode#73790
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
......@@ -308,7 +305,7 @@ export interface ITerminalProcessManager extends IDisposable {
readonly onEnvironmentVariableInfoChanged: Event<IEnvironmentVariableInfo>;
dispose(immediate?: boolean): void;
createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, isScreenReaderModeEnabled: boolean): Promise<void>;
createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, isScreenReaderModeEnabled: boolean): Promise<ITerminalLaunchError | undefined>;
write(data: string): void;
setDimensions(cols: number, rows: number): void;
......@@ -402,6 +399,11 @@ export interface IWindowsShellHelper extends IDisposable {
getShellName(): Promise<string>;
}
export interface ITerminalLaunchError {
message: string;
code?: number;
}
/**
* An interface representing a raw terminal child process, this contains a subset of the
* child_process.ChildProcess node.js interface.
......@@ -414,6 +416,14 @@ export interface ITerminalChildProcess {
onProcessOverrideDimensions?: Event<ITerminalDimensions | undefined>;
onProcessResolvedShellLaunchConfig?: Event<IShellLaunchConfig>;
/**
* Starts the process.
*
* @returns undefined when the process was successfully started, otherwise an object containing
* information on what went wrong.
*/
start(): Promise<ITerminalLaunchError | undefined>;
/**
* Shutdown the terminal process.
*
......
......@@ -11,22 +11,27 @@ import * as fs from 'fs';
import { Event, Emitter } from 'vs/base/common/event';
import { getWindowsBuildNumber } from 'vs/workbench/contrib/terminal/node/terminal';
import { Disposable } from 'vs/base/common/lifecycle';
import { IShellLaunchConfig, ITerminalChildProcess, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal';
import { IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
import { exec } from 'child_process';
import { ILogService } from 'vs/platform/log/common/log';
import { stat } from 'vs/base/node/pfs';
import { findExecutable } from 'vs/workbench/contrib/terminal/node/terminalEnvironment';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
export class TerminalProcess extends Disposable implements ITerminalChildProcess {
private _exitCode: number | undefined;
private _exitMessage: string | undefined;
private _closeTimeout: any;
private _ptyProcess: pty.IPty | undefined;
private _currentTitle: string = '';
private _processStartupComplete: Promise<void> | undefined;
private _isDisposed: boolean = false;
private _titleInterval: NodeJS.Timer | null = null;
private _initialCwd: string;
private readonly _initialCwd: string;
private readonly _ptyOptions: pty.IPtyForkOptions | pty.IWindowsPtyForkOptions;
public get exitMessage(): string | undefined { return this._exitMessage; }
private readonly _onProcessData = this._register(new Emitter<string>());
public get onProcessData(): Event<string> { return this._onProcessData.event; }
......@@ -38,7 +43,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
public get onProcessTitleChanged(): Event<string> { return this._onProcessTitleChanged.event; }
constructor(
shellLaunchConfig: IShellLaunchConfig,
private readonly _shellLaunchConfig: IShellLaunchConfig,
cwd: string,
cols: number,
rows: number,
......@@ -47,73 +52,89 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
@ILogService private readonly _logService: ILogService
) {
super();
let shellName: string;
let name: string;
if (os.platform() === 'win32') {
shellName = path.basename(shellLaunchConfig.executable || '');
name = path.basename(this._shellLaunchConfig.executable || '');
} else {
// Using 'xterm-256color' here helps ensure that the majority of Linux distributions will use a
// color prompt as defined in the default ~/.bashrc file.
shellName = 'xterm-256color';
name = 'xterm-256color';
}
this._initialCwd = cwd;
const useConpty = windowsEnableConpty && process.platform === 'win32' && getWindowsBuildNumber() >= 18309;
const options: pty.IPtyForkOptions | pty.IWindowsPtyForkOptions = {
name: shellName,
this._ptyOptions = {
name,
cwd,
env,
cols,
rows,
useConpty,
// This option will force conpty to not redraw the whole viewport on launch
conptyInheritCursor: useConpty && !!shellLaunchConfig.initialText
conptyInheritCursor: useConpty && !!_shellLaunchConfig.initialText
};
}
// TODO: Pull verification out into its own function
const cwdVerification = stat(cwd).then(async stat => {
if (!stat.isDirectory()) {
return Promise.reject(SHELL_CWD_INVALID_EXIT_CODE);
}
public async start(): Promise<ITerminalLaunchError | undefined> {
const results = await Promise.all([this._validateCwd(), this._validateExecutable()]);
const firstError = results.find(r => r !== undefined);
if (firstError) {
// TODO: Anything else we need to do here?
// this._processStartupComplete = Promise.resolve(undefined);
// this._onProcessExit.fire(0);
return firstError;
}
try {
this.setupPtyProcess(this._shellLaunchConfig, this._ptyOptions);
return undefined;
}, async err => {
if (err && err.code === 'ENOENT') {
// So we can include in the error message the specified CWD
shellLaunchConfig.cwd = cwd;
return Promise.reject(SHELL_CWD_INVALID_EXIT_CODE);
} catch (err) {
// TODO: Anything else we need to do here?
// TODO: Extract code and message from the native exception, currently they're combined
// A native exception occurred
this._logService.trace('IPty#spawn native exception', err);
return { message: `A native exception occurred during launch (${err.message})` };
}
}
private async _validateCwd(): Promise<undefined | ITerminalLaunchError> {
try {
const result = await stat(this._initialCwd);
if (!result.isDirectory()) {
return { message: localize('launchFail.cwdNotDirectory', 'Starting directory (cwd) "{0}" is not a directory', this._initialCwd.toString()) };
}
return undefined;
});
} catch (err) {
if (err?.code === 'ENOENT') {
return { message: localize('launchFail.cwdDoesNotExist', 'Starting directory (cwd) "{0}" does not exist', this._initialCwd.toString()) };
}
}
return undefined;
}
const executableVerification = stat(shellLaunchConfig.executable!).then(async stat => {
if (!stat.isFile() && !stat.isSymbolicLink()) {
return Promise.reject(stat.isDirectory() ? SHELL_PATH_DIRECTORY_EXIT_CODE : SHELL_PATH_INVALID_EXIT_CODE);
private async _validateExecutable(): Promise<undefined | ITerminalLaunchError> {
const slc = this._shellLaunchConfig;
if (!slc.executable) {
throw new Error('IShellLaunchConfig.executable not set');
}
try {
const result = await stat(slc.executable);
if (!result.isFile() && !result.isSymbolicLink()) {
return { message: localize('launchFail.executableIsNotFileOrSymlink', 'Shell path "{0}" is not a file of a symlink', slc.executable) };
}
return undefined;
}, async (err) => {
if (err && err.code === 'ENOENT') {
let cwd = shellLaunchConfig.cwd instanceof URI ? shellLaunchConfig.cwd.path : shellLaunchConfig.cwd!;
// Try to get path
const envPaths: string[] | undefined = (shellLaunchConfig.env && shellLaunchConfig.env.PATH) ? shellLaunchConfig.env.PATH.split(path.delimiter) : undefined;
const executable = await findExecutable(shellLaunchConfig.executable!, cwd, envPaths);
} catch (err) {
if (err?.code === 'ENOENT') {
// The executable isn't an absolute path, try find it on the PATH or CWD
let cwd = slc.cwd instanceof URI ? slc.cwd.path : slc.cwd!;
const envPaths: string[] | undefined = (slc.env && slc.env.PATH) ? slc.env.PATH.split(path.delimiter) : undefined;
const executable = await findExecutable(slc.executable!, cwd, envPaths);
if (!executable) {
return Promise.reject(SHELL_PATH_INVALID_EXIT_CODE);
return { message: localize('launchFail.executableDoesNotExist', 'Shell path "{0}" does not exist') };
}
}
return undefined;
});
Promise.all([cwdVerification, executableVerification]).then(() => {
this.setupPtyProcess(shellLaunchConfig, options);
}).catch((exitCode: number) => {
return this._launchFailed(exitCode);
});
}
private _launchFailed(exitCode: number): void {
this._exitCode = exitCode;
this._queueProcessExit();
this._processStartupComplete = Promise.resolve(undefined);
}
return undefined;
}
private setupPtyProcess(shellLaunchConfig: IShellLaunchConfig, options: pty.IPtyForkOptions): void {
......@@ -132,14 +153,12 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
}
});
ptyProcess.onExit(e => {
console.log('onExit', e);
this._exitCode = e.exitCode;
this._queueProcessExit();
});
this._setupTitlePolling(ptyProcess);
// TODO: We should no longer need to delay this since pty.spawn is sync
setTimeout(() => {
this._sendProcessId(ptyProcess);
}, 500);
this._sendProcessId(ptyProcess.pid);
}
public dispose(): void {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册