Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,13 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
// For normal terminals write a message indicating what happened and relaunch
// using the previous shellLaunchConfig
const message = localize('ptyHostRelaunch', "Restarting the terminal because the connection to the shell process was lost...");
this._onProcessData.fire({ data: formatMessageForTerminal(message, { loudFormatting: true }), trackCommit: false });
// Align with the pty service's revive logic (_reviveTerminalProcess in src/vs/platform/terminal/node/ptyService.ts)
// to hedge against PSReadLine `GetConsoleCursorInfo` and cursor handling from conpty.
let postRestartMessage = '';
if (this.os === OperatingSystem.Windows && this._dimensions.rows > 0) {
postRestartMessage = '\r\n'.repeat(this._dimensions.rows - 1) + `\x1b[H`;
Comment thread
anthonykim1 marked this conversation as resolved.
}
this._onProcessData.fire({ data: formatMessageForTerminal(message, { loudFormatting: true }) + postRestartMessage, trackCommit: false });
await this.relaunch(this._shellLaunchConfig, this._dimensions.cols, this._dimensions.rows, false);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { strictEqual } from 'assert';
import { Event } from '../../../../../base/common/event.js';
import { deepStrictEqual, strictEqual } from 'assert';
import { Emitter, Event } from '../../../../../base/common/event.js';
import { Schemas } from '../../../../../base/common/network.js';
import { OperatingSystem } from '../../../../../base/common/platform.js';
import { URI } from '../../../../../base/common/uri.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
import { IConfigurationService, type IConfigurationChangeEvent } from '../../../../../platform/configuration/common/configuration.js';
Expand Down Expand Up @@ -51,12 +52,13 @@ class TestTerminalChildProcess implements ITerminalChildProcess {
}

class TestTerminalInstanceService implements Partial<ITerminalInstanceService> {
readonly ptyHostRestartEmitter = new Emitter<void>();
async getBackend() {
return {
onPtyHostExit: Event.None,
onPtyHostUnresponsive: Event.None,
onPtyHostResponsive: Event.None,
onPtyHostRestart: Event.None,
onPtyHostRestart: this.ptyHostRestartEmitter.event,
onDidMoveWindowInstance: Event.None,
onDidRequestDetach: Event.None,
createProcess: (
Expand All @@ -77,6 +79,7 @@ class TestTerminalInstanceService implements Partial<ITerminalInstanceService> {

suite('Workbench - TerminalProcessManager', () => {
let manager: TerminalProcessManager;
let terminalInstanceService: TestTerminalInstanceService;

const store = ensureNoDisposablesAreLeakedInTestSuite();

Expand All @@ -96,7 +99,9 @@ suite('Workbench - TerminalProcessManager', () => {
configurationService.onDidChangeConfigurationEmitter.fire({
affectsConfiguration: () => true,
} satisfies Partial<IConfigurationChangeEvent> as unknown as IConfigurationChangeEvent);
instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService());
terminalInstanceService = new TestTerminalInstanceService();
store.add(terminalInstanceService.ptyHostRestartEmitter);
instantiationService.stub(ITerminalInstanceService, terminalInstanceService);
instantiationService.stub(ITerminalService, { setNextCommandId: async () => { } } as Partial<ITerminalService>);

manager = store.add(instantiationService.createInstance(TerminalProcessManager, 1, undefined, undefined, undefined));
Expand Down Expand Up @@ -141,4 +146,39 @@ suite('Workbench - TerminalProcessManager', () => {
});
});
});

suite('pty host restart', () => {
async function fireRestartAndCaptureData(os: OperatingSystem, rows: number): Promise<string> {
await manager.createProcess({}, 80, rows, false);
manager.os = os;
let captured: string | undefined;
store.add(manager.onProcessData(e => captured = e.data));
terminalInstanceService.ptyHostRestartEmitter.fire();
return captured!;
}

test('appends viewport-clearing newlines and ESC[H on Windows', async () => {
const data = await fireRestartAndCaptureData(OperatingSystem.Windows, 24);
deepStrictEqual(
{ endsWithViewportClear: data.endsWith('\r\n'.repeat(23) + '\x1b[H') },
{ endsWithViewportClear: true }
);
});

test('does not append viewport-clearing sequence on non-Windows', async () => {
const data = await fireRestartAndCaptureData(OperatingSystem.Linux, 24);
deepStrictEqual(
{ containsCursorHome: data.includes('\x1b[H') },
{ containsCursorHome: false }
);
});

test('does not append viewport-clearing sequence on Windows when rows is 0', async () => {
const data = await fireRestartAndCaptureData(OperatingSystem.Windows, 0);
deepStrictEqual(
{ containsCursorHome: data.includes('\x1b[H') },
{ containsCursorHome: false }
);
});
});
});
Loading