Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ const enum Constants {
}

let xtermConstructor: Promise<typeof XTermTerminal> | undefined;
const noXtermReadyPromise: Promise<XtermTerminal | undefined> = Promise.resolve(undefined);
const noBarrierWait: Promise<void> = Promise.resolve();

interface ICanvasDimensions {
width: number;
Expand Down Expand Up @@ -151,8 +153,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
* Resolves when xterm.js is ready, this will be undefined if the terminal instance is disposed
* before xterm.js could be created.
*/
private _xtermReadyPromise: Promise<XtermTerminal | undefined>;
get xtermReadyPromise(): Promise<XtermTerminal | undefined> { return this._xtermReadyPromise; }
private _xtermReadyPromise: Promise<XtermTerminal | undefined> | undefined;
get xtermReadyPromise(): Promise<XtermTerminal | undefined> { return this._xtermReadyPromise ?? noXtermReadyPromise; }

private _pressAnyKeyToCloseListener: IDisposable | undefined;
private _instanceId: number;
Expand Down Expand Up @@ -187,8 +189,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _areLinksReady: boolean = false;
private readonly _initialDataEventsListener: MutableDisposable<IDisposable> = this._register(new MutableDisposable());
private _initialDataEvents: string[] | undefined = [];
private _containerReadyBarrier: AutoOpenBarrier;
private _attachBarrier: AutoOpenBarrier;
private _containerReadyBarrier: AutoOpenBarrier | undefined;
private _attachBarrier: AutoOpenBarrier | undefined;
private _icon: TerminalIcon | undefined;
private readonly _messageTitleDisposable: MutableDisposable<IDisposable> = this._register(new MutableDisposable());
private _widgetManager: TerminalWidgetManager;
Expand Down Expand Up @@ -367,7 +369,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;

private readonly _onLineData = this._register(new Emitter<string>({
onDidAddFirstListener: async () => (this.xterm ?? await this._xtermReadyPromise)?.raw.loadAddon(this._lineDataEventAddon!)
onDidAddFirstListener: async () => (this.xterm ?? await this.xtermReadyPromise)?.raw.loadAddon(this._lineDataEventAddon!)
}));
readonly onLineData = this._onLineData.event;

Expand Down Expand Up @@ -540,7 +542,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._xtermReadyPromise = this._createXterm();
this._xtermReadyPromise.then(async () => {
// Wait for a period to allow a container to be ready
await this._containerReadyBarrier.wait();
await (this._containerReadyBarrier?.wait() ?? noBarrierWait);
if (this.isDisposed) {
return;
}

// Resolve the executable ahead of time if shell integration is enabled, this should not
// be done for custom PTYs as that would cause extension Pseudoterminal-based terminals
Expand Down Expand Up @@ -649,7 +654,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
} catch (err) {
onUnexpectedError(err);
}
this._xtermReadyPromise.then(xterm => {
this.xtermReadyPromise.then(xterm => {
if (xterm) {
contribution.xtermReady?.(xterm);
}
Expand Down Expand Up @@ -1055,7 +1060,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
return;
}

if (!this._attachBarrier.isOpen()) {
if (this._attachBarrier && !this._attachBarrier.isOpen()) {
this._attachBarrier.open();
}

Expand Down Expand Up @@ -1108,7 +1113,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// Fire xtermOpen on all contributions
for (const contribution of this._contributions.values()) {
if (!this.xterm) {
this._xtermReadyPromise.then(xterm => {
this.xtermReadyPromise.then(xterm => {
if (xterm) {
contribution.xtermOpen?.(xterm);
}
Expand Down Expand Up @@ -1293,6 +1298,15 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
this._logService.trace(`terminalInstance#dispose (instanceId: ${this.instanceId})`);
dispose(this._widgetManager);
if (this._containerReadyBarrier && !this._containerReadyBarrier.isOpen()) {
this._containerReadyBarrier.open();
}
this._containerReadyBarrier = undefined;
if (this._attachBarrier && !this._attachBarrier.isOpen()) {
this._attachBarrier.open();
}
this._attachBarrier = undefined;
this._xtermReadyPromise = undefined;

if (this.xterm?.raw.element) {
this._hadFocusOnExit = this.hasFocus;
Expand Down Expand Up @@ -1369,8 +1383,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}

async focusWhenReady(force?: boolean): Promise<void> {
await this._xtermReadyPromise;
await this._attachBarrier.wait();
await this.xtermReadyPromise;
await (this._attachBarrier?.wait() ?? noBarrierWait);
if (this.isDisposed) {
return;
}
this.focus(force);
}
Comment thread
vivekjm marked this conversation as resolved.

Expand Down Expand Up @@ -1509,9 +1526,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._setTitle(this._shellLaunchConfig.name, TitleEventSource.Api);
} else {
// Listen to xterm.js' sequence title change event, trigger this async to ensure
// _xtermReadyPromise is ready constructed since this is called from the ctor
// xtermReadyPromise is ready constructed since this is called from the ctor
setTimeout(() => {
this._xtermReadyPromise.then(xterm => {
this.xtermReadyPromise.then(xterm => {
if (xterm) {
this._messageTitleDisposable.value = xterm.raw.onTitleChange(e => this._onTitleChange(e));
}
Expand Down Expand Up @@ -1735,7 +1752,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// user (via the `workbench.action.terminal.kill` command).
const waitOnExit = this.waitOnExit;
if (waitOnExit && this._processManager.processState !== ProcessState.KilledByUser) {
this._xtermReadyPromise.then(xterm => {
this.xtermReadyPromise.then(xterm => {
if (!xterm) {
return;
}
Expand Down Expand Up @@ -2011,14 +2028,14 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._resize();

// Signal the container is ready
if (!this._containerReadyBarrier.isOpen()) {
if (this._containerReadyBarrier && !this._containerReadyBarrier.isOpen()) {
this._containerReadyBarrier.open();
}

// Layout all contributions
for (const contribution of this._contributions.values()) {
if (!this.xterm) {
this._xtermReadyPromise.then(xterm => {
this.xtermReadyPromise.then(xterm => {
if (xterm) {
contribution.layout?.(xterm, dimension);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { deepStrictEqual, strictEqual } from 'assert';
import { deepStrictEqual, ok, strictEqual } from 'assert';
import { Event } from '../../../../../base/common/event.js';
import { Disposable } from '../../../../../base/common/lifecycle.js';
import { Schemas } from '../../../../../base/common/network.js';
Expand Down Expand Up @@ -161,6 +161,27 @@ suite('Workbench - TerminalInstance', () => {
deepStrictEqual(terminalInstance.shellLaunchConfig.env, { TEST: 'TEST' });
});

test('should release startup promises and barriers on dispose', async () => {
const instance = await createTerminalInstance();
const privateInstance = instance as unknown as {
_xtermReadyPromise: Promise<unknown> | undefined;
_containerReadyBarrier: unknown;
_attachBarrier: unknown;
};

ok(privateInstance._xtermReadyPromise instanceof Promise);
ok(privateInstance._containerReadyBarrier);
ok(privateInstance._attachBarrier);

instance.dispose();

strictEqual(privateInstance._xtermReadyPromise, undefined);
strictEqual(privateInstance._containerReadyBarrier, undefined);
strictEqual(privateInstance._attachBarrier, undefined);
strictEqual(await instance.xtermReadyPromise, undefined);
await instance.focusWhenReady();
});

test('should preserve title for task terminals', async () => {
const instantiationService = workbenchInstantiationService({
configurationService: () => new TestConfigurationService({
Expand Down