mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-13 23:44:09 +01:00
Merge pull request #84000 from microsoft/tyriar/exitCode
Support Terminal.exitStatus API
This commit is contained in:
@@ -97,6 +97,27 @@ suite('window namespace tests', () => {
|
||||
const terminal = window.createTerminal('b');
|
||||
});
|
||||
|
||||
test('exitStatus.code should be set to undefined after a terminal is disposed', (done) => {
|
||||
disposables.push(window.onDidOpenTerminal(term => {
|
||||
try {
|
||||
equal(term, terminal);
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
disposables.push(window.onDidCloseTerminal(t => {
|
||||
try {
|
||||
deepEqual(t.exitStatus, { code: undefined });
|
||||
} catch (e) {
|
||||
done(e);
|
||||
return;
|
||||
}
|
||||
done();
|
||||
}));
|
||||
terminal.dispose();
|
||||
}));
|
||||
const terminal = window.createTerminal();
|
||||
});
|
||||
|
||||
// test('onDidChangeActiveTerminal should fire when new terminals are created', (done) => {
|
||||
// const reg1 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => {
|
||||
// equal(active, terminal);
|
||||
@@ -362,9 +383,97 @@ suite('window namespace tests', () => {
|
||||
const pty: Pseudoterminal = {
|
||||
onDidWrite: writeEmitter.event,
|
||||
onDidOverrideDimensions: overrideDimensionsEmitter.event,
|
||||
open: () => {
|
||||
overrideDimensionsEmitter.fire({ columns: 10, rows: 5 });
|
||||
},
|
||||
open: () => overrideDimensionsEmitter.fire({ columns: 10, rows: 5 }),
|
||||
close: () => { }
|
||||
};
|
||||
const terminal = window.createTerminal({ name: 'foo', pty });
|
||||
});
|
||||
|
||||
test('exitStatus.code should be set to the exit code (undefined)', (done) => {
|
||||
disposables.push(window.onDidOpenTerminal(term => {
|
||||
try {
|
||||
equal(terminal, term);
|
||||
equal(terminal.exitStatus, undefined);
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
disposables.push(window.onDidCloseTerminal(t => {
|
||||
try {
|
||||
equal(terminal, t);
|
||||
deepEqual(terminal.exitStatus, { code: undefined });
|
||||
} catch (e) {
|
||||
done(e);
|
||||
return;
|
||||
}
|
||||
done();
|
||||
}));
|
||||
}));
|
||||
const writeEmitter = new EventEmitter<string>();
|
||||
const closeEmitter = new EventEmitter<number | undefined>();
|
||||
const pty: Pseudoterminal = {
|
||||
onDidWrite: writeEmitter.event,
|
||||
onDidClose: closeEmitter.event,
|
||||
open: () => closeEmitter.fire(),
|
||||
close: () => { }
|
||||
};
|
||||
const terminal = window.createTerminal({ name: 'foo', pty });
|
||||
});
|
||||
|
||||
test('exitStatus.code should be set to the exit code (zero)', (done) => {
|
||||
disposables.push(window.onDidOpenTerminal(term => {
|
||||
try {
|
||||
equal(terminal, term);
|
||||
equal(terminal.exitStatus, undefined);
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
disposables.push(window.onDidCloseTerminal(t => {
|
||||
try {
|
||||
equal(terminal, t);
|
||||
deepEqual(terminal.exitStatus, { code: 0 });
|
||||
} catch (e) {
|
||||
done(e);
|
||||
return;
|
||||
}
|
||||
done();
|
||||
}));
|
||||
}));
|
||||
const writeEmitter = new EventEmitter<string>();
|
||||
const closeEmitter = new EventEmitter<number | undefined>();
|
||||
const pty: Pseudoterminal = {
|
||||
onDidWrite: writeEmitter.event,
|
||||
onDidClose: closeEmitter.event,
|
||||
open: () => closeEmitter.fire(0),
|
||||
close: () => { }
|
||||
};
|
||||
const terminal = window.createTerminal({ name: 'foo', pty });
|
||||
});
|
||||
|
||||
test('exitStatus.code should be set to the exit code (non-zero)', (done) => {
|
||||
disposables.push(window.onDidOpenTerminal(term => {
|
||||
try {
|
||||
equal(terminal, term);
|
||||
equal(terminal.exitStatus, undefined);
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
disposables.push(window.onDidCloseTerminal(t => {
|
||||
try {
|
||||
equal(terminal, t);
|
||||
deepEqual(terminal.exitStatus, { code: 22 });
|
||||
} catch (e) {
|
||||
done(e);
|
||||
return;
|
||||
}
|
||||
done();
|
||||
}));
|
||||
}));
|
||||
const writeEmitter = new EventEmitter<string>();
|
||||
const closeEmitter = new EventEmitter<number | undefined>();
|
||||
const pty: Pseudoterminal = {
|
||||
onDidWrite: writeEmitter.event,
|
||||
onDidClose: closeEmitter.event,
|
||||
open: () => closeEmitter.fire(22),
|
||||
close: () => { }
|
||||
};
|
||||
const terminal = window.createTerminal({ name: 'foo', pty });
|
||||
|
||||
Vendored
+26
@@ -674,6 +674,17 @@ declare module 'vscode' {
|
||||
readonly data: string;
|
||||
}
|
||||
|
||||
export interface TerminalExitStatus {
|
||||
/**
|
||||
* The exit code that a terminal exited with, it can have the following values:
|
||||
* - Zero: the terminal process or custom execution succeeded.
|
||||
* - Non-zero: the terminal process or custom execution failed.
|
||||
* - `undefined`: the user forcefully closed the terminal or a custom execution exited
|
||||
* without providing an exit code.
|
||||
*/
|
||||
readonly code: number | undefined;
|
||||
}
|
||||
|
||||
namespace window {
|
||||
/**
|
||||
* An event which fires when the [dimensions](#Terminal.dimensions) of the terminal change.
|
||||
@@ -695,6 +706,21 @@ declare module 'vscode' {
|
||||
* created.
|
||||
*/
|
||||
readonly dimensions: TerminalDimensions | undefined;
|
||||
|
||||
/**
|
||||
* The exit status of the terminal, this will be undefined while the terminal is active.
|
||||
*
|
||||
* **Example:** Show a notification with the exit code when the terminal exits with a
|
||||
* non-zero exit code.
|
||||
* ```typescript
|
||||
* window.onDidCloseTerminal(t => {
|
||||
* if (t.exitStatus && t.exitStatus.code) {
|
||||
* vscode.window.showInformationMessage(`Exit code: ${t.exitStatus.code}`);
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
readonly exitStatus: TerminalExitStatus | undefined;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -163,7 +163,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
}
|
||||
|
||||
private _onTerminalDisposed(terminalInstance: ITerminalInstance): void {
|
||||
this._proxy.$acceptTerminalClosed(terminalInstance.id);
|
||||
this._proxy.$acceptTerminalClosed(terminalInstance.id, terminalInstance.exitCode);
|
||||
}
|
||||
|
||||
private _onTerminalOpened(terminalInstance: ITerminalInstance): void {
|
||||
@@ -257,7 +257,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
this._getTerminalProcess(terminalId).then(e => e.emitReady(pid, cwd));
|
||||
}
|
||||
|
||||
public $sendProcessExit(terminalId: number, exitCode: number): void {
|
||||
public $sendProcessExit(terminalId: number, exitCode: number | undefined): void {
|
||||
this._getTerminalProcess(terminalId).then(e => e.emitExit(exitCode));
|
||||
this._terminalProcesses.delete(terminalId);
|
||||
}
|
||||
|
||||
@@ -420,7 +420,7 @@ export interface MainThreadTerminalServiceShape extends IDisposable {
|
||||
$sendProcessTitle(terminalId: number, title: string): void;
|
||||
$sendProcessData(terminalId: number, data: string): void;
|
||||
$sendProcessReady(terminalId: number, pid: number, cwd: string): void;
|
||||
$sendProcessExit(terminalId: number, exitCode: number): void;
|
||||
$sendProcessExit(terminalId: number, exitCode: number | undefined): void;
|
||||
$sendProcessInitialCwd(terminalId: number, cwd: string): void;
|
||||
$sendProcessCwd(terminalId: number, initialCwd: string): void;
|
||||
$sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void;
|
||||
@@ -1196,7 +1196,7 @@ export interface ITerminalDimensionsDto {
|
||||
}
|
||||
|
||||
export interface ExtHostTerminalServiceShape {
|
||||
$acceptTerminalClosed(id: number): void;
|
||||
$acceptTerminalClosed(id: number, exitCode: number | undefined): void;
|
||||
$acceptTerminalOpened(id: number, name: string): void;
|
||||
$acceptActiveTerminalChanged(id: number | null): void;
|
||||
$acceptTerminalProcessId(id: number, processId: number): void;
|
||||
|
||||
@@ -97,6 +97,7 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
|
||||
private _cols: number | undefined;
|
||||
private _pidPromiseComplete: ((value: number | undefined) => any) | undefined;
|
||||
private _rows: number | undefined;
|
||||
private _exitStatus: vscode.TerminalExitStatus | undefined;
|
||||
|
||||
public isOpen: boolean = false;
|
||||
|
||||
@@ -138,6 +139,10 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
public get exitStatus(): vscode.TerminalExitStatus | undefined {
|
||||
return this._exitStatus;
|
||||
}
|
||||
|
||||
public get dimensions(): vscode.TerminalDimensions | undefined {
|
||||
if (this._cols === undefined || this._rows === undefined) {
|
||||
return undefined;
|
||||
@@ -148,6 +153,10 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
|
||||
};
|
||||
}
|
||||
|
||||
public setExitCode(code: number | undefined) {
|
||||
this._exitStatus = Object.freeze({ code });
|
||||
}
|
||||
|
||||
public setDimensions(cols: number, rows: number): boolean {
|
||||
if (cols === this._cols && rows === this._rows) {
|
||||
// Nothing changed
|
||||
@@ -210,8 +219,8 @@ class ApiRequest {
|
||||
export class ExtHostPseudoterminal implements ITerminalChildProcess {
|
||||
private readonly _onProcessData = new Emitter<string>();
|
||||
public readonly onProcessData: Event<string> = this._onProcessData.event;
|
||||
private readonly _onProcessExit = new Emitter<number>();
|
||||
public readonly onProcessExit: Event<number> = this._onProcessExit.event;
|
||||
private readonly _onProcessExit = new Emitter<number | undefined>();
|
||||
public readonly onProcessExit: Event<number | undefined> = this._onProcessExit.event;
|
||||
private readonly _onProcessReady = new Emitter<{ pid: number, cwd: string }>();
|
||||
public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; }
|
||||
private readonly _onProcessTitleChanged = new Emitter<string>();
|
||||
@@ -253,7 +262,9 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess {
|
||||
// Attach the listeners
|
||||
this._pty.onDidWrite(e => this._onProcessData.fire(e));
|
||||
if (this._pty.onDidClose) {
|
||||
this._pty.onDidClose(e => this._onProcessExit.fire(e || 0));
|
||||
this._pty.onDidClose((e: number | undefined = undefined) => {
|
||||
this._onProcessExit.fire(e);
|
||||
});
|
||||
}
|
||||
if (this._pty.onDidOverrideDimensions) {
|
||||
this._pty.onDidOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e ? { cols: e.columns, rows: e.rows } : e));
|
||||
@@ -381,11 +392,12 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
|
||||
}
|
||||
}
|
||||
|
||||
public async $acceptTerminalClosed(id: number): Promise<void> {
|
||||
public async $acceptTerminalClosed(id: number, exitCode: number | undefined): Promise<void> {
|
||||
await this._getTerminalByIdEventually(id);
|
||||
const index = this._getTerminalObjectIndexById(this.terminals, id);
|
||||
if (index !== null) {
|
||||
const terminal = this._terminals.splice(index, 1)[0];
|
||||
terminal.setExitCode(exitCode);
|
||||
this._onDidCloseTerminal.fire(terminal);
|
||||
}
|
||||
}
|
||||
@@ -511,7 +523,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
|
||||
return id;
|
||||
}
|
||||
|
||||
private _onProcessExit(id: number, exitCode: number): void {
|
||||
private _onProcessExit(id: number, exitCode: number | undefined): void {
|
||||
this._bufferer.stopBuffering(id);
|
||||
|
||||
// Remove process reference
|
||||
|
||||
@@ -236,6 +236,8 @@ export interface ITerminalInstance {
|
||||
*/
|
||||
onExit: Event<number | undefined>;
|
||||
|
||||
readonly exitCode: number | undefined;
|
||||
|
||||
processReady: Promise<void>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -181,6 +181,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
private _hadFocusOnExit: boolean;
|
||||
private _isVisible: boolean;
|
||||
private _isDisposed: boolean;
|
||||
private _exitCode: number | undefined;
|
||||
private _skipTerminalCommands: string[];
|
||||
private _shellType: TerminalShellType;
|
||||
private _title: string = '';
|
||||
@@ -227,6 +228,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
// TODO: How does this work with detached processes?
|
||||
// TODO: Should this be an event as it can fire twice?
|
||||
public get processReady(): Promise<void> { return this._processManager.ptyProcessReady; }
|
||||
public get exitCode(): number | undefined { return this._exitCode; }
|
||||
public get title(): string { return this._title; }
|
||||
public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
|
||||
public get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; }
|
||||
@@ -1011,6 +1013,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
|
||||
this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`);
|
||||
|
||||
this._exitCode = exitCode;
|
||||
this._isExiting = true;
|
||||
let exitCodeMessage: string | undefined;
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
|
||||
|
||||
private readonly _onProcessData = this._register(new Emitter<string>());
|
||||
public readonly onProcessData: Event<string> = this._onProcessData.event;
|
||||
private readonly _onProcessExit = this._register(new Emitter<number>());
|
||||
public readonly onProcessExit: Event<number> = this._onProcessExit.event;
|
||||
private readonly _onProcessExit = this._register(new Emitter<number | undefined>());
|
||||
public readonly onProcessExit: Event<number | undefined> = this._onProcessExit.event;
|
||||
private 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>());
|
||||
@@ -87,7 +87,7 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
|
||||
this._onProcessReady.fire({ pid, cwd });
|
||||
}
|
||||
|
||||
public emitExit(exitCode: number): void {
|
||||
public emitExit(exitCode: number | undefined): void {
|
||||
this._onProcessExit.fire(exitCode);
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
||||
public get onProcessData(): Event<string> { return this._onProcessData.event; }
|
||||
private readonly _onProcessTitle = this._register(new Emitter<string>());
|
||||
public get onProcessTitle(): Event<string> { return this._onProcessTitle.event; }
|
||||
private readonly _onProcessExit = this._register(new Emitter<number>());
|
||||
public get onProcessExit(): Event<number> { return this._onProcessExit.event; }
|
||||
private readonly _onProcessExit = this._register(new Emitter<number | undefined>());
|
||||
public get onProcessExit(): Event<number | undefined> { return this._onProcessExit.event; }
|
||||
private readonly _onProcessOverrideDimensions = this._register(new Emitter<ITerminalDimensions | undefined>());
|
||||
public get onProcessOverrideDimensions(): Event<ITerminalDimensions | undefined> { return this._onProcessOverrideDimensions.event; }
|
||||
private readonly _onProcessOverrideShellLaunchConfig = this._register(new Emitter<IShellLaunchConfig>());
|
||||
@@ -285,7 +285,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
||||
return Promise.resolve(this._latency);
|
||||
}
|
||||
|
||||
private _onExit(exitCode: number): void {
|
||||
private _onExit(exitCode: number | undefined): void {
|
||||
this._process = null;
|
||||
|
||||
// If the process is marked as launching then mark the process as killed
|
||||
|
||||
@@ -285,7 +285,7 @@ export interface ITerminalProcessManager extends IDisposable {
|
||||
readonly onBeforeProcessData: Event<IBeforeProcessDataEvent>;
|
||||
readonly onProcessData: Event<string>;
|
||||
readonly onProcessTitle: Event<string>;
|
||||
readonly onProcessExit: Event<number>;
|
||||
readonly onProcessExit: Event<number | undefined>;
|
||||
readonly onProcessOverrideDimensions: Event<ITerminalDimensions | undefined>;
|
||||
readonly onProcessResolvedShellLaunchConfig: Event<IShellLaunchConfig>;
|
||||
|
||||
@@ -324,7 +324,7 @@ export interface ITerminalProcessExtHostProxy extends IDisposable {
|
||||
emitData(data: string): void;
|
||||
emitTitle(title: string): void;
|
||||
emitReady(pid: number, cwd: string): void;
|
||||
emitExit(exitCode: number): void;
|
||||
emitExit(exitCode: number | undefined): void;
|
||||
emitOverrideDimensions(dimensions: ITerminalDimensions | undefined): void;
|
||||
emitResolvedShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig): void;
|
||||
emitInitialCwd(initialCwd: string): void;
|
||||
@@ -388,7 +388,7 @@ export interface IWindowsShellHelper extends IDisposable {
|
||||
*/
|
||||
export interface ITerminalChildProcess {
|
||||
onProcessData: Event<string>;
|
||||
onProcessExit: Event<number>;
|
||||
onProcessExit: Event<number | undefined>;
|
||||
onProcessReady: Event<{ pid: number, cwd: string }>;
|
||||
onProcessTitleChanged: Event<string>;
|
||||
onProcessOverrideDimensions?: Event<ITerminalDimensions | undefined>;
|
||||
|
||||
Reference in New Issue
Block a user