diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index b20488bced6..29d1b8fdcb6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -2135,10 +2135,12 @@ export function registerTerminalActions() { if (!data) { return; } - let escapedData = data; + let escapedData = data + .replace(/\\n/g, '\n') + .replace(/\\r/g, '\r'); while (true) { const match = escapedData.match(/\\x([0-9a-fA-F]{2})/); - if (!match?.index || match.length < 2) { + if (match === null || match.index === undefined || match.length < 2) { break; } escapedData = escapedData.slice(0, match.index) + String.fromCharCode(parseInt(match[1], 16)) + escapedData.slice(match.index + 4); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index 6354f5f7b64..96d111ab7c9 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -194,6 +194,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { } registerCommandDecoration(command: ITerminalCommand, beforeCommandExecution?: boolean): IDecoration | undefined { + console.log('registerCommandDecoration'); if (!this._terminal) { return undefined; } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 2cded1fe98d..c7983b71adf 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -61,7 +61,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II // Always on addons private _commandNavigationAddon: CommandNavigationAddon; private _shellIntegrationAddon: ShellIntegrationAddon; - private _decorationAddon: DecorationAddon | undefined; + private _decorationAddon: DecorationAddon; // Optional addons private _searchAddon?: SearchAddonType; @@ -155,17 +155,13 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II if (e.affectsConfiguration(TerminalSettingId.UnicodeVersion)) { this._updateUnicodeVersion(); } - if (e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationsEnabled) || - e.affectsConfiguration(TerminalSettingId.ShellIntegrationEnabled)) { - this._updateShellIntegrationAddons(); - } })); this.add(this._themeService.onDidColorThemeChange(theme => this._updateTheme(theme))); this.add(this._viewDescriptorService.onDidChangeLocation(({ views }) => { if (views.some(v => v.id === TERMINAL_VIEW_ID)) { this._updateTheme(); - this._decorationAddon?.refreshLayouts(); + this._decorationAddon.refreshLayouts(); } })); @@ -176,17 +172,16 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II this._updateUnicodeVersion(); this._commandNavigationAddon = this._instantiationService.createInstance(CommandNavigationAddon, _capabilities); this.raw.loadAddon(this._commandNavigationAddon); - this._shellIntegrationAddon = this._instantiationService.createInstance(ShellIntegrationAddon, disableShellIntegrationReporting, this._telemetryService); - this.raw.loadAddon(this._shellIntegrationAddon); - this._updateShellIntegrationAddons(); - } - - private _createDecorationAddon(): void { this._decorationAddon = this._instantiationService.createInstance(DecorationAddon, this._capabilities); this._decorationAddon.onDidRequestRunCommand(e => this._onDidRequestRunCommand.fire(e)); this.raw.loadAddon(this._decorationAddon); + this._shellIntegrationAddon = this._instantiationService.createInstance(ShellIntegrationAddon, disableShellIntegrationReporting, this._telemetryService); + this.raw.loadAddon(this._shellIntegrationAddon); } + // private _createDecorationAddon(): void { + // } + async getSelectionAsHtml(command?: ITerminalCommand): Promise { if (!this._serializeAddon) { const Addon = await this._getSerializeAddonConstructor(); @@ -612,24 +607,6 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II } } - private _updateShellIntegrationAddons(): void { - const shellIntegrationEnabled = this._configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled); - const decorationsEnabled = this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled); - if (shellIntegrationEnabled) { - if (decorationsEnabled && !this._decorationAddon) { - this._createDecorationAddon(); - } else if (this._decorationAddon && !decorationsEnabled) { - this._decorationAddon.dispose(); - this._decorationAddon = undefined; - } - return; - } - if (this._decorationAddon) { - this._decorationAddon.dispose(); - this._decorationAddon = undefined; - } - } - // eslint-disable-next-line @typescript-eslint/naming-convention _writeText(data: string): void { this.raw.write(data); diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index dfb24636716..73f169e1c05 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -37,7 +37,8 @@ export enum TerminalCommandIdWithValue { ChangeIcon = 'workbench.action.terminal.changeIcon', NewWithProfile = 'workbench.action.terminal.newWithProfile', SelectDefaultProfile = 'workbench.action.terminal.selectDefaultShell', - AttachToSession = 'workbench.action.terminal.attachToSession' + AttachToSession = 'workbench.action.terminal.attachToSession', + WriteDataToTerminal = 'workbench.action.terminal.writeDataToTerminal' } /** @@ -112,6 +113,9 @@ export class Terminal { } await this.code.dispatchKeybinding(altKey ? 'Alt+Enter' : 'enter'); await this.quickinput.waitForQuickInputClosed(); + if (commandId === TerminalCommandIdWithValue.NewWithProfile) { + await this._waitForTerminal(); + } } async runCommandInTerminal(commandText: string, skipEnter?: boolean): Promise { diff --git a/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts b/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts index 200ab28b019..4b08bdfbf0e 100644 --- a/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts +++ b/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts @@ -16,7 +16,6 @@ export function setup() { app = this.app as Application; terminal = app.workbench.terminal; settingsEditor = app.workbench.settingsEditor; - await setTerminalTestSettings(app, [['terminal.integrated.shellIntegration.enabled', 'true']]); }); afterEach(async function () { @@ -31,8 +30,15 @@ export function setup() { await terminal.runCommandWithValue(TerminalCommandIdWithValue.NewWithProfile, process.platform === 'win32' ? 'PowerShell' : 'bash'); } + async function createSimpleProfile() { + await terminal.runCommandWithValue(TerminalCommandIdWithValue.NewWithProfile, process.platform === 'win32' ? 'Command Prompt' : 'sh'); + } + // TODO: Some agents may not have pwsh installed? - (process.platform === 'win32' ? describe.skip : describe)(`Shell integration`, function () { + (process.platform === 'win32' ? describe.skip : describe)(`Process-based tests`, function () { + before(async function () { + await setTerminalTestSettings(app, [['terminal.integrated.shellIntegration.enabled', 'true']]); + }); describe('Decorations', function () { describe('Should show default icons', function () { @@ -66,5 +72,66 @@ export function setup() { }); }); }); + + describe('Write data-based tests', () => { + before(async function () { + await setTerminalTestSettings(app); + }); + beforeEach(async function () { + // Create the simplest system profile to get as little process interaction as possible + await createSimpleProfile(); + // Erase all content and reset cursor to top + await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${csi('2J')}${csi('H')}`); + }); + describe('VS Code sequences', () => { + it('should handle the simple case', async () => { + await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${vsc('A')}Prompt> ${vsc('B')}exitcode 0`); + await terminal.assertCommandDecorations({ placeholder: 1, success: 0, error: 0 }); + await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `\\r\\n${vsc('C')}Success\\r\\n${vsc('D;0')}`); + await terminal.assertCommandDecorations({ placeholder: 0, success: 1, error: 0 }); + await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${vsc('A')}Prompt> ${vsc('B')}exitcode 1`); + await terminal.assertCommandDecorations({ placeholder: 1, success: 1, error: 0 }); + await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `\\r\\n${vsc('C')}Failure\\r\\n${vsc('D;1')}`); + await terminal.assertCommandDecorations({ placeholder: 0, success: 1, error: 1 }); + await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${vsc('A')}Prompt> ${vsc('B')}exitcode 1`); + await terminal.assertCommandDecorations({ placeholder: 1, success: 1, error: 1 }); + }); + }); + // TODO: This depends on https://github.com/microsoft/vscode/issues/146587 + describe.skip('Final Term sequences', () => { + it('should handle the simple case', async () => { + await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${ft('A')}Prompt> ${ft('B')}exitcode 0`); + await terminal.assertCommandDecorations({ placeholder: 1, success: 0, error: 0 }); + await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `\\r\\n${ft('C')}Success\\r\\n${ft('D;0')}`); + await terminal.assertCommandDecorations({ placeholder: 0, success: 1, error: 0 }); + await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${ft('A')}Prompt> ${ft('B')}exitcode 1`); + await terminal.assertCommandDecorations({ placeholder: 1, success: 1, error: 0 }); + await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `\\r\\n${ft('C')}Failure\\r\\n${ft('D;1')}`); + await terminal.assertCommandDecorations({ placeholder: 0, success: 1, error: 1 }); + await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${ft('A')}Prompt> ${ft('B')}exitcode 1`); + await terminal.assertCommandDecorations({ placeholder: 1, success: 1, error: 1 }); + }); + }); + }); }); } + +function ft(data: string) { + return setTextParams(`133;${data}`); +} + +function vsc(data: string) { + return setTextParams(`633;${data}`); +} + +function setTextParams(data: string) { + return osc(`${data}\\x07`); +} + +function osc(data: string) { + return `\\x1b]${data}`; +} + +function csi(data: string) { + return `\\x1b[${data}`; +}