Merge pull request #160992 from microsoft/tyriar/clear_run_command

Send ctrl+c before running a command unless there is no input
This commit is contained in:
Daniel Imms
2022-09-15 10:41:49 -07:00
committed by GitHub
3 changed files with 48 additions and 1 deletions

View File

@@ -146,6 +146,11 @@ export interface ICommandDetectionCapability {
readonly executingCommandObject: ITerminalCommand | undefined; readonly executingCommandObject: ITerminalCommand | undefined;
/** The current cwd at the cursor's position. */ /** The current cwd at the cursor's position. */
readonly cwd: string | undefined; 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<ITerminalCommand>; readonly onCommandStarted: Event<ITerminalCommand>;
readonly onCommandFinished: Event<ITerminalCommand>; readonly onCommandFinished: Event<ITerminalCommand>;
readonly onCommandInvalidated: Event<ITerminalCommand[]>; readonly onCommandInvalidated: Event<ITerminalCommand[]>;

View File

@@ -72,6 +72,23 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
return undefined; return undefined;
} }
get cwd(): string | undefined { return this._cwd; } 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<ITerminalCommand>(); private readonly _onCommandStarted = new Emitter<ITerminalCommand>();
readonly onCommandStarted = this._onCommandStarted.event; readonly onCommandStarted = this._onCommandStarted.event;
@@ -316,6 +333,16 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
} }
this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX; this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX;
this._currentCommand.commandStartMarker = options?.marker || this._terminal.registerMarker(0); 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._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); this._logService.debug('CommandDetectionCapability#handleCommandStart', this._currentCommand.commandStartX, this._currentCommand.commandStartMarker?.line);
} }

View File

@@ -26,6 +26,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { showWithPinnedItems } from 'vs/platform/quickinput/browser/quickPickPin'; import { showWithPinnedItems } from 'vs/platform/quickinput/browser/quickPickPin';
import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStorageService } from 'vs/platform/storage/common/storage';
import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { timeout } from 'vs/base/common/async';
export async function showRunRecentQuickPick( export async function showRunRecentQuickPick(
accessor: ServicesAccessor, accessor: ServicesAccessor,
@@ -264,8 +265,8 @@ export async function showRunRecentQuickPick(
} else { // command } else { // command
text = result.rawLabel; text = result.rawLabel;
} }
instance.sendText(text, !quickPick.keyMods.alt, true);
quickPick.hide(); quickPick.hide();
runCommand(instance, text, !quickPick.keyMods.alt);
if (quickPick.keyMods.alt) { if (quickPick.keyMods.alt) {
instance.focus(); instance.focus();
} }
@@ -283,6 +284,20 @@ export async function showRunRecentQuickPick(
}); });
} }
async function runCommand(instance: ITerminalInstance, commandLine: string, addNewLine: boolean): Promise<void> {
// 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 { class TerminalOutputProvider implements ITextModelContentProvider {
static scheme = 'TERMINAL_OUTPUT'; static scheme = 'TERMINAL_OUTPUT';