diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index 8301b35a1b3..e2e9f8bab98 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -146,6 +146,11 @@ export interface ICommandDetectionCapability { readonly executingCommandObject: ITerminalCommand | undefined; /** The current cwd at the cursor's position. */ readonly cwd: string | undefined; + /** + * Whether a command is currently being input. If the a command is current not being input or + * the state cannot reliably be detected the fallback of undefined will be used. + */ + readonly hasInput: boolean | undefined; readonly onCommandStarted: Event; readonly onCommandFinished: Event; readonly onCommandInvalidated: Event; diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 5bcc9a3e690..1d268538a0a 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -72,6 +72,23 @@ export class CommandDetectionCapability implements ICommandDetectionCapability { return undefined; } get cwd(): string | undefined { return this._cwd; } + private get _isInputting(): boolean { + return !!(this._currentCommand.commandStartMarker && !this._currentCommand.commandExecutedMarker); + } + + get hasInput(): boolean | undefined { + if (!this._isInputting || !this._currentCommand?.commandStartMarker) { + return undefined; + } + if (this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY === this._currentCommand.commandStartMarker?.line) { + const line = this._terminal.buffer.active.getLine(this._terminal.buffer.active.cursorY)?.translateToString(true, this._currentCommand.commandStartX); + if (line === undefined) { + return undefined; + } + return line.length > 0; + } + return true; + } private readonly _onCommandStarted = new Emitter(); readonly onCommandStarted = this._onCommandStarted.event; @@ -316,6 +333,16 @@ export class CommandDetectionCapability implements ICommandDetectionCapability { } this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX; this._currentCommand.commandStartMarker = options?.marker || this._terminal.registerMarker(0); + + // Clear executed as it must happen after command start + this._currentCommand.commandExecutedMarker?.dispose(); + this._currentCommand.commandExecutedMarker = undefined; + this._currentCommand.commandExecutedX = undefined; + for (const m of this._commandMarkers) { + m.dispose(); + } + this._commandMarkers.length = 0; + this._onCommandStarted.fire({ marker: options?.marker || this._currentCommand.commandStartMarker, markProperties: options?.markProperties } as ITerminalCommand); this._logService.debug('CommandDetectionCapability#handleCommandStart', this._currentCommand.commandStartX, this._currentCommand.commandStartMarker?.line); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalRunRecentQuickPick.ts b/src/vs/workbench/contrib/terminal/browser/terminalRunRecentQuickPick.ts index ac800e634ee..207ea0f8ba3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalRunRecentQuickPick.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalRunRecentQuickPick.ts @@ -26,6 +26,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { showWithPinnedItems } from 'vs/platform/quickinput/browser/quickPickPin'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { timeout } from 'vs/base/common/async'; export async function showRunRecentQuickPick( accessor: ServicesAccessor, @@ -264,8 +265,8 @@ export async function showRunRecentQuickPick( } else { // command text = result.rawLabel; } - instance.sendText(text, !quickPick.keyMods.alt, true); quickPick.hide(); + runCommand(instance, text, !quickPick.keyMods.alt); if (quickPick.keyMods.alt) { instance.focus(); } @@ -283,6 +284,20 @@ export async function showRunRecentQuickPick( }); } +async function runCommand(instance: ITerminalInstance, commandLine: string, addNewLine: boolean): Promise { + // Determine whether to send ETX (ctrl+c) before running the command. This should always + // happen unless command detection can reliably say that a command is being entered and + // there is no content in the prompt + if (instance.capabilities.get(TerminalCapability.CommandDetection)?.hasInput !== false) { + await instance.sendText('\x03', false); + // Wait a little before running the command to avoid the sequences being echoed while the ^C + // is being evaluated + await timeout(100); + } + // Use bracketed paste mode only when not running the command + await instance.sendText(commandLine, addNewLine, !addNewLine); +} + class TerminalOutputProvider implements ITextModelContentProvider { static scheme = 'TERMINAL_OUTPUT';