From cc942157531ed8d04c088da5db53df555f7ec200 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 10 Dec 2021 15:07:04 -0800 Subject: [PATCH] Create terminal auto responder concept Part of #133524 --- src/vs/platform/terminal/common/terminal.ts | 5 ++ .../terminal/common/terminalAutoResponder.ts | 64 +++++++++++++++++++ src/vs/platform/terminal/node/ptyService.ts | 20 +++++- 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/vs/platform/terminal/common/terminalAutoResponder.ts diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index e10c028a2b359..f6ea237cc09cc 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -585,6 +585,11 @@ export interface ITerminalChildProcess { updateProperty(property: T, value: IProcessPropertyMap[T]): Promise; } +export interface ITerminalEventListener { + handleInput(data: string): void; + handleResize(cols: number, rows: number): void; +} + export interface IReconnectConstants { graceTime: number; shortGraceTime: number; diff --git a/src/vs/platform/terminal/common/terminalAutoResponder.ts b/src/vs/platform/terminal/common/terminalAutoResponder.ts new file mode 100644 index 0000000000000..2d18db1717a7a --- /dev/null +++ b/src/vs/platform/terminal/common/terminalAutoResponder.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { isWindows } from 'vs/base/common/platform'; +import { ITerminalChildProcess, ITerminalEventListener } from 'vs/platform/terminal/common/terminal'; + +/** + * Tracks a terminal process's data stream and responds immediately when a matching string is + * received. This is done in a low overhead way and is ideally run on the same process as the + * where the process is handled to minimize latency. + */ +export class TerminalAutoResponder extends Disposable implements ITerminalEventListener { + private _pointer = 0; + private _paused = false; + + constructor( + proc: ITerminalChildProcess, + matchWord: string, + response: string + ) { + super(); + + this._register(proc.onProcessData(e => { + if (this._paused) { + return; + } + console.log('data', e); + const data = typeof e === 'string' ? e : e.data; + for (let i = 0; i < data.length; i++) { + if (data[i] === matchWord[this._pointer]) { + this._pointer++; + } else { + this._reset(); + } + // Auto reply and reset + if (this._pointer === matchWord.length) { + proc.input(response); + this._reset(); + } + } + })); + } + + private _reset() { + this._pointer = 0; + } + + /** + * No auto response will happen after a resize on Windows in case the resize is a result of + * reprinting the screen. + */ + handleResize() { + if (isWindows) { + this._paused = true; + } + } + + handleInput() { + this._paused = false; + } +} diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 1f2af0e28c5b4..25e9d3b79a9ec 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { getSystemShell } from 'vs/base/node/shell'; import { ILogService } from 'vs/platform/log/common/log'; import { RequestStore } from 'vs/platform/terminal/common/requestStore'; -import { IProcessDataEvent, IProcessReadyEvent, IPtyService, IRawTerminalInstanceLayoutInfo, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalInstanceLayoutInfoById, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalTabLayoutInfoById, TerminalIcon, IProcessProperty, TitleEventSource, ProcessPropertyType, IProcessPropertyMap, IFixedTerminalDimensions, ProcessCapability } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IProcessReadyEvent, IPtyService, IRawTerminalInstanceLayoutInfo, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalInstanceLayoutInfoById, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalTabLayoutInfoById, TerminalIcon, IProcessProperty, TitleEventSource, ProcessPropertyType, IProcessPropertyMap, IFixedTerminalDimensions, ProcessCapability, ITerminalEventListener } from 'vs/platform/terminal/common/terminal'; import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering'; import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; import { Terminal as XtermTerminal } from 'xterm-headless'; @@ -23,6 +23,7 @@ import { getWindowsBuildNumber } from 'vs/platform/terminal/node/terminalEnviron import { TerminalProcess } from 'vs/platform/terminal/node/terminalProcess'; import { localize } from 'vs/nls'; import { ignoreProcessNames } from 'vs/platform/terminal/node/childProcessMonitor'; +import { TerminalAutoResponder } from 'vs/platform/terminal/common/terminalAutoResponder'; type WorkspaceId = string; @@ -399,6 +400,7 @@ interface IPersistentTerminalProcessLaunchOptions { export class PersistentTerminalProcess extends Disposable { private readonly _bufferer: TerminalDataBufferer; + private _eventListeners: ITerminalEventListener[] = []; private readonly _pendingCommands = new Map void; reject: (err: any) => void; }>(); @@ -561,6 +563,8 @@ export class PersistentTerminalProcess extends Disposable { // be attached yet). https://github.com/microsoft/terminal/issues/11213 if (this._wasRevived) { this.triggerReplay(); + } else { + this._setupAutoResponder(); } } else { this._onProcessReady.fire({ pid: this._pid, cwd: this._cwd, capabilities: this._terminalProcess.capabilities, requiresWindowsMode: isWindows && getWindowsBuildNumber() < 21376 }); @@ -578,6 +582,9 @@ export class PersistentTerminalProcess extends Disposable { if (this._inReplay) { return; } + for (const listener of this._eventListeners) { + listener.handleInput(data); + } return this._terminalProcess.input(data); } writeBinary(data: string): Promise { @@ -591,6 +598,10 @@ export class PersistentTerminalProcess extends Disposable { // Buffered events should flush when a resize occurs this._bufferer.flushBuffer(this._persistentProcessId); + + for (const listener of this._eventListeners) { + listener.handleResize(cols, rows); + } return this._terminalProcess.resize(cols, rows); } setUnicodeVersion(version: '6' | '11'): void { @@ -624,6 +635,13 @@ export class PersistentTerminalProcess extends Disposable { this._logService.info(`Persistent process "${this._persistentProcessId}": Replaying ${dataLength} chars and ${ev.events.length} size events`); this._onProcessReplay.fire(ev); this._terminalProcess.clearUnacknowledgedChars(); + this._setupAutoResponder(); + } + + private _setupAutoResponder() { + if (isWindows) { + this._eventListeners.push(this._register(new TerminalAutoResponder(this._terminalProcess, 'Terminate batch job (Y/N)', 'Y\r'))); + } } sendCommandResult(reqId: number, isError: boolean, serializedPayload: any): void {