Skip to content

Commit 2ab9f20

Browse files
committed
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<activation command>\\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.
1 parent 6ec9f33 commit 2ab9f20

3 files changed

Lines changed: 35 additions & 8 deletions

File tree

src/features/terminal/runInTerminal.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Terminal, TerminalShellExecution } from 'vscode';
22
import { PythonEnvironment, PythonTerminalExecutionOptions } from '../../api';
3+
import { traceLog } from '../../common/logging';
34
import { createDeferred } from '../../common/utils/deferred';
45
import { onDidEndTerminalShellExecution } from '../../common/window.apis';
56
import { ShellConstants } from '../common/shellConstants';
67
import { identifyTerminalShell } from '../common/shellDetector';
78
import { quoteArgs } from '../execution/execUtils';
8-
import { normalizeShellPath } from './shells/common/shellUtils';
9-
import { traceLog } from '../../common/logging';
9+
import { getClearLineSequence, normalizeShellPath } from './shells/common/shellUtils';
1010

1111
export async function runInTerminal(
1212
environment: PythonEnvironment,
@@ -52,10 +52,12 @@ export async function runInTerminal(
5252
await deferred.promise;
5353
} else {
5454
let text = quoteArgs([executable, ...allArgs]).join(' ');
55+
const clearLineSequence = getClearLineSequence(terminal);
5556
if (shellType === ShellConstants.PWSH && !text.startsWith('&')) {
5657
// PowerShell requires commands to be prefixed with '&' to run them.
5758
text = `& ${text}`;
5859
}
60+
terminal.sendText(clearLineSequence, false); // Clear the line before sending the command
5961
terminal.sendText(`${text}\n`);
6062
traceLog(`runInTerminal: sendText ${text}`);
6163
}

src/features/terminal/shells/common/shellUtils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { Terminal } from 'vscode';
12
import { PythonCommandRunConfiguration, PythonEnvironment } from '../../../../api';
23
import { isWindows } from '../../../../common/utils/platformUtils';
34
import { getConfiguration } from '../../../../common/workspace.apis';
45
import { ShellConstants } from '../../../common/shellConstants';
6+
import { identifyTerminalShell } from '../../../common/shellDetector';
57
import { quoteArgs } from '../../../execution/execUtils';
68

79
/**
@@ -58,6 +60,7 @@ export function normalizeShellPath(filePath: string, shellType?: string): string
5860
}
5961
return filePath;
6062
}
63+
6164
export function getShellActivationCommand(
6265
shell: string,
6366
environment: PythonEnvironment,
@@ -76,6 +79,7 @@ export function getShellActivationCommand(
7679

7780
return activation;
7881
}
82+
7983
export function getShellDeactivationCommand(
8084
shell: string,
8185
environment: PythonEnvironment,
@@ -160,3 +164,19 @@ export const shellIntegrationSupportedShells = [
160164
export function shouldUseProfileActivation(shellType: string): boolean {
161165
return isWsl() || !shellIntegrationSupportedShells.includes(shellType);
162166
}
167+
168+
/**
169+
* Returns the appropriate sequence to clear the current line in the terminal based on the shell type.
170+
* For PowerShell and CMD, it uses the ANSI escape code to clear the entire line and return the cursor to the start.
171+
* For other shells, it uses Ctrl+U to clear from the cursor to the start of the line.
172+
*/
173+
export function getClearLineSequence(terminal: Terminal): string {
174+
const shell = identifyTerminalShell(terminal);
175+
switch (shell) {
176+
case ShellConstants.PWSH:
177+
case ShellConstants.CMD:
178+
return '\x1b[2K\r'; // Clear entire line and return cursor to start
179+
default:
180+
return '\x15'; // Ctrl+U to clear from cursor to start of line
181+
}
182+
}

src/features/terminal/terminalActivationState.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { PythonEnvironment } from '../../api';
1111
import { traceError, traceInfo, traceVerbose } from '../../common/logging';
1212
import { onDidEndTerminalShellExecution, onDidStartTerminalShellExecution } from '../../common/window.apis';
1313
import { getActivationCommand, getDeactivationCommand } from '../common/activation';
14+
import { getClearLineSequence } from './shells/common/shellUtils';
1415
import { getShellIntegrationTimeout, isTaskTerminal } from './utils';
1516

1617
export interface DidChangeTerminalActivationStateEvent {
@@ -195,17 +196,21 @@ export class TerminalActivationImpl implements TerminalActivationInternal {
195196
}
196197

197198
private activateLegacy(terminal: Terminal, environment: PythonEnvironment) {
198-
const activationCommands = getActivationCommand(terminal, environment);
199-
if (activationCommands) {
200-
terminal.sendText(activationCommands);
199+
const activationCommand = getActivationCommand(terminal, environment);
200+
const clearLineSequence = getClearLineSequence(terminal);
201+
if (activationCommand) {
202+
terminal.sendText(clearLineSequence, false); // Clear the line before activation command
203+
terminal.sendText(activationCommand);
201204
this.activatedTerminals.set(terminal, environment);
202205
}
203206
}
204207

205208
private deactivateLegacy(terminal: Terminal, environment: PythonEnvironment) {
206-
const deactivationCommands = getDeactivationCommand(terminal, environment);
207-
if (deactivationCommands) {
208-
terminal.sendText(deactivationCommands);
209+
const deactivationCommand = getDeactivationCommand(terminal, environment);
210+
const clearLineSequence = getClearLineSequence(terminal);
211+
if (deactivationCommand) {
212+
terminal.sendText(clearLineSequence, false); // Clear the line before deactivation command
213+
terminal.sendText(deactivationCommand);
209214
this.activatedTerminals.delete(terminal);
210215
}
211216
}

0 commit comments

Comments
 (0)