From 2ab9f20171e3f4ef36bef18506bf6cd93ea3dbb3 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Wed, 15 Apr 2026 13:02:03 -0700 Subject: [PATCH] Clear the current line when activating in terminal Ensures that the current terminal line is cleared before the command for activation is passed to the terminal. Before, it would interrupt user input, resulting in corrupted commands like: git pu\\n, which not only produces the wrong command, but can lead to unsafe execution. This occurs on the legacy (de)activation, due to users: - Having setting erminal.integrated.shellIntegration.enabled = false - Using a shell that does not support shell integration - Using slower machines, or remote SSH, and the shell integration timeout elapsing before the handshake. --- src/features/terminal/runInTerminal.ts | 6 ++++-- .../terminal/shells/common/shellUtils.ts | 20 +++++++++++++++++++ .../terminal/terminalActivationState.ts | 17 ++++++++++------ 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/features/terminal/runInTerminal.ts b/src/features/terminal/runInTerminal.ts index b0cc9a59..00ba38e1 100644 --- a/src/features/terminal/runInTerminal.ts +++ b/src/features/terminal/runInTerminal.ts @@ -1,12 +1,12 @@ import { Terminal, TerminalShellExecution } from 'vscode'; import { PythonEnvironment, PythonTerminalExecutionOptions } from '../../api'; +import { traceLog } from '../../common/logging'; import { createDeferred } from '../../common/utils/deferred'; import { onDidEndTerminalShellExecution } from '../../common/window.apis'; import { ShellConstants } from '../common/shellConstants'; import { identifyTerminalShell } from '../common/shellDetector'; import { quoteArgs } from '../execution/execUtils'; -import { normalizeShellPath } from './shells/common/shellUtils'; -import { traceLog } from '../../common/logging'; +import { getClearLineSequence, normalizeShellPath } from './shells/common/shellUtils'; export async function runInTerminal( environment: PythonEnvironment, @@ -52,10 +52,12 @@ export async function runInTerminal( await deferred.promise; } else { let text = quoteArgs([executable, ...allArgs]).join(' '); + const clearLineSequence = getClearLineSequence(terminal); if (shellType === ShellConstants.PWSH && !text.startsWith('&')) { // PowerShell requires commands to be prefixed with '&' to run them. text = `& ${text}`; } + terminal.sendText(clearLineSequence, false); // Clear the line before sending the command terminal.sendText(`${text}\n`); traceLog(`runInTerminal: sendText ${text}`); } diff --git a/src/features/terminal/shells/common/shellUtils.ts b/src/features/terminal/shells/common/shellUtils.ts index 766fd65a..9b93ccd3 100644 --- a/src/features/terminal/shells/common/shellUtils.ts +++ b/src/features/terminal/shells/common/shellUtils.ts @@ -1,7 +1,9 @@ +import { Terminal } from 'vscode'; import { PythonCommandRunConfiguration, PythonEnvironment } from '../../../../api'; import { isWindows } from '../../../../common/utils/platformUtils'; import { getConfiguration } from '../../../../common/workspace.apis'; import { ShellConstants } from '../../../common/shellConstants'; +import { identifyTerminalShell } from '../../../common/shellDetector'; import { quoteArgs } from '../../../execution/execUtils'; /** @@ -58,6 +60,7 @@ export function normalizeShellPath(filePath: string, shellType?: string): string } return filePath; } + export function getShellActivationCommand( shell: string, environment: PythonEnvironment, @@ -76,6 +79,7 @@ export function getShellActivationCommand( return activation; } + export function getShellDeactivationCommand( shell: string, environment: PythonEnvironment, @@ -160,3 +164,19 @@ export const shellIntegrationSupportedShells = [ export function shouldUseProfileActivation(shellType: string): boolean { return isWsl() || !shellIntegrationSupportedShells.includes(shellType); } + +/** + * Returns the appropriate sequence to clear the current line in the terminal based on the shell type. + * For PowerShell and CMD, it uses the ANSI escape code to clear the entire line and return the cursor to the start. + * For other shells, it uses Ctrl+U to clear from the cursor to the start of the line. + */ +export function getClearLineSequence(terminal: Terminal): string { + const shell = identifyTerminalShell(terminal); + switch (shell) { + case ShellConstants.PWSH: + case ShellConstants.CMD: + return '\x1b[2K\r'; // Clear entire line and return cursor to start + default: + return '\x15'; // Ctrl+U to clear from cursor to start of line + } +} \ No newline at end of file diff --git a/src/features/terminal/terminalActivationState.ts b/src/features/terminal/terminalActivationState.ts index 5181c12f..60c9e7c0 100644 --- a/src/features/terminal/terminalActivationState.ts +++ b/src/features/terminal/terminalActivationState.ts @@ -11,6 +11,7 @@ import { PythonEnvironment } from '../../api'; import { traceError, traceInfo, traceVerbose } from '../../common/logging'; import { onDidEndTerminalShellExecution, onDidStartTerminalShellExecution } from '../../common/window.apis'; import { getActivationCommand, getDeactivationCommand } from '../common/activation'; +import { getClearLineSequence } from './shells/common/shellUtils'; import { getShellIntegrationTimeout, isTaskTerminal } from './utils'; export interface DidChangeTerminalActivationStateEvent { @@ -195,17 +196,21 @@ export class TerminalActivationImpl implements TerminalActivationInternal { } private activateLegacy(terminal: Terminal, environment: PythonEnvironment) { - const activationCommands = getActivationCommand(terminal, environment); - if (activationCommands) { - terminal.sendText(activationCommands); + const activationCommand = getActivationCommand(terminal, environment); + const clearLineSequence = getClearLineSequence(terminal); + if (activationCommand) { + terminal.sendText(clearLineSequence, false); // Clear the line before activation command + terminal.sendText(activationCommand); this.activatedTerminals.set(terminal, environment); } } private deactivateLegacy(terminal: Terminal, environment: PythonEnvironment) { - const deactivationCommands = getDeactivationCommand(terminal, environment); - if (deactivationCommands) { - terminal.sendText(deactivationCommands); + const deactivationCommand = getDeactivationCommand(terminal, environment); + const clearLineSequence = getClearLineSequence(terminal); + if (deactivationCommand) { + terminal.sendText(clearLineSequence, false); // Clear the line before deactivation command + terminal.sendText(deactivationCommand); this.activatedTerminals.delete(terminal); } }