From 25849e4630e0a9d2ed862c03ca3f2250c297f902 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Wed, 30 Sep 2026 16:58:18 -0700 Subject: [PATCH 1/4] PyREPL: Send code using bracketed paste markers --- src/client/common/terminal/service.ts | 11 ++++- .../common/terminals/service.unit.test.ts | 41 ++++++++++++++++--- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index b58257b2d6bb..189471ab1d6a 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -90,7 +90,7 @@ export class TerminalService implements ITerminalService, Disposable { ): Promise { if (isPythonShell) { if (this.isReplReady) { - this.terminal?.sendText(commandLine); + this.sendTextToPythonRepl(commandLine); traceVerbose(`Python REPL sendText: ${commandLine}`); } else { // Queue command to run once REPL is ready. @@ -156,11 +156,18 @@ export class TerminalService implements ITerminalService, Disposable { const commandLine = this.pythonReplCommandQueue.shift(); if (commandLine) { traceVerbose(`Executing queued REPL command: ${commandLine}`); - this.terminal?.sendText(commandLine); + this.sendTextToPythonRepl(commandLine); } } } + // PyREPL-only: wrap the code in bracketed-paste markers so the PyREPL + // preserves the code's existing whitespace, then submit with Enter AFTER + // leaving bracketed-paste mode. + private sendTextToPythonRepl(commandLine: string): void { + this.terminal?.sendText(`\x1b[200~${commandLine}\x1b[201~`); + } + private async executeCommandInternal(commandLine: string): Promise { const terminal = this.terminal; if (!terminal) { diff --git a/src/test/common/terminals/service.unit.test.ts b/src/test/common/terminals/service.unit.test.ts index d703f547d14e..097248f02372 100644 --- a/src/test/common/terminals/service.unit.test.ts +++ b/src/test/common/terminals/service.unit.test.ts @@ -40,6 +40,8 @@ import { IInterpreterService } from '../../../client/interpreter/contracts'; import { PythonEnvironment } from '../../../client/pythonEnvironments/info'; suite('Terminal Service', () => { + const bracketedPaste = (text: string) => `\x1b[200~${text}\x1b[201~`; + let service: TerminalService; let terminal: TypeMoq.IMock; let terminalManager: TypeMoq.IMock; @@ -256,7 +258,7 @@ suite('Terminal Service', () => { await executePromise; terminal.verify((t) => t.show(TypeMoq.It.isValue(true)), TypeMoq.Times.exactly(1)); - terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.exactly(1)); + terminal.verify((t) => t.sendText(TypeMoq.It.isValue(bracketedPaste(textToSend))), TypeMoq.Times.exactly(1)); }); test('Ensure sendText is called when terminal.shellIntegration enabled but Python shell integration disabled', async () => { @@ -279,7 +281,7 @@ suite('Terminal Service', () => { await executePromise; terminal.verify((t) => t.show(TypeMoq.It.isValue(true)), TypeMoq.Times.exactly(1)); - terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.exactly(1)); + terminal.verify((t) => t.sendText(TypeMoq.It.isValue(bracketedPaste(textToSend))), TypeMoq.Times.exactly(1)); }); test('Ensure sendText is called when Python shell integration and terminal shell integration are both enabled - Mac, Linux && Python < 3.13', async () => { @@ -303,7 +305,7 @@ suite('Terminal Service', () => { await executePromise; terminal.verify((t) => t.show(TypeMoq.It.isValue(true)), TypeMoq.Times.exactly(1)); - terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.exactly(1)); + terminal.verify((t) => t.sendText(TypeMoq.It.isValue(bracketedPaste(textToSend))), TypeMoq.Times.exactly(1)); }); test('Ensure sendText is called when Python shell integration and terminal shell integration are both enabled - Mac, Linux && Python >= 3.13', async () => { @@ -335,7 +337,7 @@ suite('Terminal Service', () => { onDidWriteTerminalDataEmitter.fire({ terminal: terminal.object, data: '>>> ' }); await executePromise; - terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.once()); + terminal.verify((t) => t.sendText(TypeMoq.It.isValue(bracketedPaste(textToSend))), TypeMoq.Times.once()); }); test('Ensure sendText IS called even when Python shell integration and terminal shell integration are both enabled - Window', async () => { @@ -359,7 +361,7 @@ suite('Terminal Service', () => { await executePromise; terminal.verify((t) => t.show(TypeMoq.It.isValue(true)), TypeMoq.Times.exactly(1)); - terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.exactly(1)); + terminal.verify((t) => t.sendText(TypeMoq.It.isValue(bracketedPaste(textToSend))), TypeMoq.Times.exactly(1)); }); test('Ensure REPL ready when onDidChangeTerminalState fires with python shell', async () => { @@ -383,7 +385,34 @@ suite('Terminal Service', () => { onDidChangeTerminalStateEmitter.fire(terminal.object); await executePromise; - terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.exactly(1)); + terminal.verify((t) => t.sendText(TypeMoq.It.isValue(bracketedPaste(textToSend))), TypeMoq.Times.exactly(1)); + }); + + test('Ensure commands sent after the Python REPL is ready use bracketed paste mode', async () => { + pythonConfig.setup((p) => p.get('terminal.shellIntegration.enabled')).returns(() => false); + terminalHelper + .setup((helper) => helper.getEnvironmentActivationCommands(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(undefined)); + service = new TerminalService(mockServiceContainer.object); + terminalHelper.setup((h) => h.identifyTerminalShell(TypeMoq.It.isAny())).returns(() => TerminalShellType.bash); + terminalManager.setup((t) => t.createTerminal(TypeMoq.It.isAny())).returns(() => terminal.object); + + await service.ensureTerminal(); + await service.executeCommand('queued command', true); + onDidWriteTerminalDataEmitter.fire({ terminal: terminal.object, data: '>>> ' }); + + await service.executeCommand(' indented command', true); + + // Queued command is wrapped when flushed once the REPL becomes ready. + terminal.verify( + (t) => t.sendText(TypeMoq.It.isValue('\x1b[200~queued command\x1b[201~')), + TypeMoq.Times.once(), + ); + // Command sent after the REPL is ready is wrapped too. + terminal.verify( + (t) => t.sendText(TypeMoq.It.isValue('\x1b[200~ indented command\x1b[201~')), + TypeMoq.Times.once(), + ); }); test('Ensure terminal is not shown if `hideFromUser` option is set to `true`', async () => { From e6843db6e01681ab7d99b8bac8c015e3f0626b11 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Wed, 30 Sep 2026 17:15:22 -0700 Subject: [PATCH 2/4] Detect PyREPL correctly --- src/client/common/terminal/service.ts | 11 +---- src/client/terminals/codeExecution/helper.ts | 10 +++-- .../common/terminals/service.unit.test.ts | 41 +++---------------- .../terminals/codeExecution/helper.test.ts | 25 +++++++---- 4 files changed, 30 insertions(+), 57 deletions(-) diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index 189471ab1d6a..b58257b2d6bb 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -90,7 +90,7 @@ export class TerminalService implements ITerminalService, Disposable { ): Promise { if (isPythonShell) { if (this.isReplReady) { - this.sendTextToPythonRepl(commandLine); + this.terminal?.sendText(commandLine); traceVerbose(`Python REPL sendText: ${commandLine}`); } else { // Queue command to run once REPL is ready. @@ -156,18 +156,11 @@ export class TerminalService implements ITerminalService, Disposable { const commandLine = this.pythonReplCommandQueue.shift(); if (commandLine) { traceVerbose(`Executing queued REPL command: ${commandLine}`); - this.sendTextToPythonRepl(commandLine); + this.terminal?.sendText(commandLine); } } } - // PyREPL-only: wrap the code in bracketed-paste markers so the PyREPL - // preserves the code's existing whitespace, then submit with Enter AFTER - // leaving bracketed-paste mode. - private sendTextToPythonRepl(commandLine: string): void { - this.terminal?.sendText(`\x1b[200~${commandLine}\x1b[201~`); - } - private async executeCommandInternal(commandLine: string): Promise { const terminal = this.terminal; if (!terminal) { diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 4efad5ee174e..097a0d5d76df 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -99,12 +99,10 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { const endLineVal = activeEditor?.selection?.end.line ?? 0; const emptyHighlightVal = activeEditor?.selection?.isEmpty ?? true; let smartSendSettingsEnabledVal = true; - let shellIntegrationEnabled = false; const configuration = this.serviceContainer.get(IConfigurationService); if (configuration) { const pythonSettings = configuration.getSettings(this.activeResourceService.getActiveResource()); smartSendSettingsEnabledVal = pythonSettings.REPL.enableREPLSmartSend; - shellIntegrationEnabled = pythonSettings.terminal.shellIntegration.enabled; } const input = JSON.stringify({ @@ -127,8 +125,12 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { await this.moveToNextBlock(lineOffset, activeEditor); } - // For new _pyrepl for Python3.13+ && !shellIntegration, we need to send code via bracketed paste mode. - if (object.attach_bracket_paste && !shellIntegrationEnabled && _replType === ReplType.terminal) { + // For the new _pyrepl in Python 3.13+, send code via bracketed paste mode so the REPL + // preserves the code's indentation instead of applying its own auto-indentation. + // This must happen regardless of the `terminal.shellIntegration.enabled` setting: terminal + // REPL code is always sent through `sendText` (never `shellIntegration.executeCommand`), so + // bracketed paste is the only thing preventing progressively increasing indentation. + if (object.attach_bracket_paste && _replType === ReplType.terminal) { let trimmedNormalized = object.normalized.replace(/\n$/, ''); if (trimmedNormalized.endsWith(':\n')) { // In case where statement is unfinished via :, truncate so auto-indentation lands nicely. diff --git a/src/test/common/terminals/service.unit.test.ts b/src/test/common/terminals/service.unit.test.ts index 097248f02372..d703f547d14e 100644 --- a/src/test/common/terminals/service.unit.test.ts +++ b/src/test/common/terminals/service.unit.test.ts @@ -40,8 +40,6 @@ import { IInterpreterService } from '../../../client/interpreter/contracts'; import { PythonEnvironment } from '../../../client/pythonEnvironments/info'; suite('Terminal Service', () => { - const bracketedPaste = (text: string) => `\x1b[200~${text}\x1b[201~`; - let service: TerminalService; let terminal: TypeMoq.IMock; let terminalManager: TypeMoq.IMock; @@ -258,7 +256,7 @@ suite('Terminal Service', () => { await executePromise; terminal.verify((t) => t.show(TypeMoq.It.isValue(true)), TypeMoq.Times.exactly(1)); - terminal.verify((t) => t.sendText(TypeMoq.It.isValue(bracketedPaste(textToSend))), TypeMoq.Times.exactly(1)); + terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.exactly(1)); }); test('Ensure sendText is called when terminal.shellIntegration enabled but Python shell integration disabled', async () => { @@ -281,7 +279,7 @@ suite('Terminal Service', () => { await executePromise; terminal.verify((t) => t.show(TypeMoq.It.isValue(true)), TypeMoq.Times.exactly(1)); - terminal.verify((t) => t.sendText(TypeMoq.It.isValue(bracketedPaste(textToSend))), TypeMoq.Times.exactly(1)); + terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.exactly(1)); }); test('Ensure sendText is called when Python shell integration and terminal shell integration are both enabled - Mac, Linux && Python < 3.13', async () => { @@ -305,7 +303,7 @@ suite('Terminal Service', () => { await executePromise; terminal.verify((t) => t.show(TypeMoq.It.isValue(true)), TypeMoq.Times.exactly(1)); - terminal.verify((t) => t.sendText(TypeMoq.It.isValue(bracketedPaste(textToSend))), TypeMoq.Times.exactly(1)); + terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.exactly(1)); }); test('Ensure sendText is called when Python shell integration and terminal shell integration are both enabled - Mac, Linux && Python >= 3.13', async () => { @@ -337,7 +335,7 @@ suite('Terminal Service', () => { onDidWriteTerminalDataEmitter.fire({ terminal: terminal.object, data: '>>> ' }); await executePromise; - terminal.verify((t) => t.sendText(TypeMoq.It.isValue(bracketedPaste(textToSend))), TypeMoq.Times.once()); + terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.once()); }); test('Ensure sendText IS called even when Python shell integration and terminal shell integration are both enabled - Window', async () => { @@ -361,7 +359,7 @@ suite('Terminal Service', () => { await executePromise; terminal.verify((t) => t.show(TypeMoq.It.isValue(true)), TypeMoq.Times.exactly(1)); - terminal.verify((t) => t.sendText(TypeMoq.It.isValue(bracketedPaste(textToSend))), TypeMoq.Times.exactly(1)); + terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.exactly(1)); }); test('Ensure REPL ready when onDidChangeTerminalState fires with python shell', async () => { @@ -385,34 +383,7 @@ suite('Terminal Service', () => { onDidChangeTerminalStateEmitter.fire(terminal.object); await executePromise; - terminal.verify((t) => t.sendText(TypeMoq.It.isValue(bracketedPaste(textToSend))), TypeMoq.Times.exactly(1)); - }); - - test('Ensure commands sent after the Python REPL is ready use bracketed paste mode', async () => { - pythonConfig.setup((p) => p.get('terminal.shellIntegration.enabled')).returns(() => false); - terminalHelper - .setup((helper) => helper.getEnvironmentActivationCommands(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)); - service = new TerminalService(mockServiceContainer.object); - terminalHelper.setup((h) => h.identifyTerminalShell(TypeMoq.It.isAny())).returns(() => TerminalShellType.bash); - terminalManager.setup((t) => t.createTerminal(TypeMoq.It.isAny())).returns(() => terminal.object); - - await service.ensureTerminal(); - await service.executeCommand('queued command', true); - onDidWriteTerminalDataEmitter.fire({ terminal: terminal.object, data: '>>> ' }); - - await service.executeCommand(' indented command', true); - - // Queued command is wrapped when flushed once the REPL becomes ready. - terminal.verify( - (t) => t.sendText(TypeMoq.It.isValue('\x1b[200~queued command\x1b[201~')), - TypeMoq.Times.once(), - ); - // Command sent after the REPL is ready is wrapped too. - terminal.verify( - (t) => t.sendText(TypeMoq.It.isValue('\x1b[200~ indented command\x1b[201~')), - TypeMoq.Times.once(), - ); + terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.exactly(1)); }); test('Ensure terminal is not shown if `hideFromUser` option is set to `true`', async () => { diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index b7e0d1617884..dfb277062910 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -137,14 +137,23 @@ suite('Terminal - Code Execution Helper', async () => { editor.setup((e) => e.document).returns(() => document.object); }); - test('normalizeLines with BASIC_REPL does not attach bracketed paste mode', async () => { + test('normalizeLines attaches bracketed paste for 3.13+ terminal even when shell integration is enabled (#26176)', async () => { + // The new PyREPL (3.13+) auto-indents typed input. Terminal REPL code is sent via sendText + // (never shellIntegration.executeCommand), so bracketed paste must be attached regardless of + // the terminal.shellIntegration.enabled setting; otherwise multiline indentation grows + // progressively and eventually raises IndentationError. configurationService .setup((c) => c.getSettings(TypeMoq.It.isAny())) .returns({ REPL: { - EnableREPLSmartSend: false, + enableREPLSmartSend: false, REPLSmartSend: false, }, + terminal: { + shellIntegration: { + enabled: true, + }, + }, // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); const actualProcessService = new ProcessService(); @@ -155,15 +164,13 @@ suite('Terminal - Code Execution Helper', async () => { ); jsonParseStub = sinon.stub(JSON, 'parse'); - const mockResult = { - normalized: 'print("Looks like you are on 3.13")', - attach_bracket_paste: true, - }; - jsonParseStub.returns(mockResult); + const normalized = 'valid = (\n isinstance(x)\n and y\n)\n'; + jsonParseStub.returns({ normalized, attach_bracket_paste: true }); - const result = await helper.normalizeLines('print("Looks like you are on 3.13")', ReplType.terminal); + const result = await helper.normalizeLines('valid = (\n isinstance(x)\n and y\n)', ReplType.terminal); - expect(result).to.equal(`print("Looks like you are on 3.13")`); + // Indentation is preserved verbatim inside the bracketed-paste markers. + expect(result).to.equal('\u001b[200~valid = (\n isinstance(x)\n and y\n)\u001b[201~'); jsonParseStub.restore(); }); From bb631453aab237b8ddb75da3afaf7a69b32c5d27 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Thu, 1 Oct 2026 14:55:42 -0700 Subject: [PATCH 3/4] Ensure sent code is run --- src/client/common/terminal/service.ts | 18 ++++++++++++---- .../common/terminals/service.unit.test.ts | 21 +++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index b58257b2d6bb..485bb3593171 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -90,7 +90,7 @@ export class TerminalService implements ITerminalService, Disposable { ): Promise { if (isPythonShell) { if (this.isReplReady) { - this.terminal?.sendText(commandLine); + await this.sendPythonShellCommand(commandLine); traceVerbose(`Python REPL sendText: ${commandLine}`); } else { // Queue command to run once REPL is ready. @@ -136,7 +136,7 @@ export class TerminalService implements ITerminalService, Disposable { return; } this.isReplReady = true; - this.flushReplQueue(); + void this.flushReplQueue(); this.disposeReplListener(); } @@ -151,16 +151,26 @@ export class TerminalService implements ITerminalService, Disposable { } } - private flushReplQueue(): void { + private async flushReplQueue(): Promise { while (this.pythonReplCommandQueue.length > 0) { const commandLine = this.pythonReplCommandQueue.shift(); if (commandLine) { traceVerbose(`Executing queued REPL command: ${commandLine}`); - this.terminal?.sendText(commandLine); + await this.sendPythonShellCommand(commandLine); } } } + // Send PyREPL's carriage-return binding for Enter separately from the bracketed-paste sequence. + private async sendPythonShellCommand(commandLine: string): Promise { + if (commandLine.startsWith('\u001b[200~') && commandLine.endsWith('\u001b[201~')) { + this.terminal?.sendText(commandLine, false); + this.terminal?.sendText('\r', false); + } else { + this.terminal?.sendText(commandLine); + } + } + private async executeCommandInternal(commandLine: string): Promise { const terminal = this.terminal; if (!terminal) { diff --git a/src/test/common/terminals/service.unit.test.ts b/src/test/common/terminals/service.unit.test.ts index d703f547d14e..bc81d773abcf 100644 --- a/src/test/common/terminals/service.unit.test.ts +++ b/src/test/common/terminals/service.unit.test.ts @@ -338,6 +338,27 @@ suite('Terminal Service', () => { terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.once()); }); + test('Ensure bracketed paste and Enter are sent separately to the Python shell', async () => { + terminalHelper + .setup((helper) => helper.getEnvironmentActivationCommands(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(undefined)); + service = new TerminalService(mockServiceContainer.object); + const textToSend = '\u001b[200~print("hello")\u001b[201~'; + terminalHelper.setup((h) => h.identifyTerminalShell(TypeMoq.It.isAny())).returns(() => TerminalShellType.bash); + terminalManager.setup((t) => t.createTerminal(TypeMoq.It.isAny())).returns(() => terminal.object); + + await service.ensureTerminal(); + const executePromise = service.executeCommand(textToSend, true); + onDidWriteTerminalDataEmitter.fire({ terminal: terminal.object, data: '>>> ' }); + await executePromise; + + terminal.verify( + (t) => t.sendText(TypeMoq.It.isValue(textToSend), TypeMoq.It.isValue(false)), + TypeMoq.Times.once(), + ); + terminal.verify((t) => t.sendText(TypeMoq.It.isValue('\r'), TypeMoq.It.isValue(false)), TypeMoq.Times.once()); + }); + test('Ensure sendText IS called even when Python shell integration and terminal shell integration are both enabled - Window', async () => { isWindowsStub.returns(true); pythonConfig From 9cad12019909f5819d3fa0adb5919b91683d7a1f Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Thu, 1 Oct 2026 16:03:14 -0700 Subject: [PATCH 4/4] Remove unnecesary await --- src/client/common/terminal/service.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index 485bb3593171..2e9f9a7a4dc3 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -90,7 +90,7 @@ export class TerminalService implements ITerminalService, Disposable { ): Promise { if (isPythonShell) { if (this.isReplReady) { - await this.sendPythonShellCommand(commandLine); + this.sendPythonShellCommand(commandLine); traceVerbose(`Python REPL sendText: ${commandLine}`); } else { // Queue command to run once REPL is ready. @@ -136,7 +136,7 @@ export class TerminalService implements ITerminalService, Disposable { return; } this.isReplReady = true; - void this.flushReplQueue(); + this.flushReplQueue(); this.disposeReplListener(); } @@ -151,18 +151,18 @@ export class TerminalService implements ITerminalService, Disposable { } } - private async flushReplQueue(): Promise { + private flushReplQueue(): void { while (this.pythonReplCommandQueue.length > 0) { const commandLine = this.pythonReplCommandQueue.shift(); if (commandLine) { traceVerbose(`Executing queued REPL command: ${commandLine}`); - await this.sendPythonShellCommand(commandLine); + this.sendPythonShellCommand(commandLine); } } } // Send PyREPL's carriage-return binding for Enter separately from the bracketed-paste sequence. - private async sendPythonShellCommand(commandLine: string): Promise { + private sendPythonShellCommand(commandLine: string): void { if (commandLine.startsWith('\u001b[200~') && commandLine.endsWith('\u001b[201~')) { this.terminal?.sendText(commandLine, false); this.terminal?.sendText('\r', false);