未验证 提交 05602b39 编写于 作者: C Connor Peet

terminal: clear typeahead after timeout

Fixes https://github.com/microsoft/vscode/issues/109617
上级 8141dcf2
......@@ -3,15 +3,16 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { disposableTimeout } from 'vs/base/common/async';
import { Color } from 'vs/base/common/color';
import { debounce } from 'vs/base/common/decorators';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { XTermAttributes, XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private';
import { IBeforeProcessDataEvent, ITerminalConfiguration, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal';
import type { IBuffer, IBufferCell, ITerminalAddon, Terminal } from 'xterm';
import type { IBuffer, IBufferCell, IDisposable, ITerminalAddon, Terminal } from 'xterm';
const ESC = '\x1b';
const CSI = `${ESC}[`;
......@@ -171,6 +172,12 @@ export interface IPrediction {
*/
readonly affectsStyle?: boolean;
/**
* If set to false, the prediction will not be cleared if no input is
* received from the server.
*/
readonly clearAfterTimeout?: boolean;
/**
* Returns a sequence to apply the prediction.
* @param buffer to write to
......@@ -292,6 +299,8 @@ class StringReader {
* after it.
*/
class HardBoundary implements IPrediction {
public readonly clearAfterTimeout = false;
public apply() {
return '';
}
......@@ -637,6 +646,20 @@ export class PredictionStats extends Disposable {
};
}
/**
* Gets the maximum observed latency.
*/
public get maxLatency() {
let max = -Infinity;
for (const [latency, correct] of this.stats) {
if (correct) {
max = Math.max(latency, max);
}
}
return max;
}
constructor(timeline: PredictionTimeline) {
super();
this._register(timeline.onPredictionAdded(p => this.addedAtTime.set(p, Date.now())));
......@@ -690,10 +713,18 @@ export class PredictionTimeline {
private readonly succeededEmitter = new Emitter<IPrediction>();
public readonly onPredictionSucceeded = this.succeededEmitter.event;
private get currentGenerationPredictions() {
return this.expected.filter(({ gen }) => gen === this.expected[0].gen).map(({ p }) => p);
}
public get isShowingPredictions() {
return this.showPredictions;
}
public get length() {
return this.expected.length;
}
constructor(public readonly terminal: Terminal, private readonly style: TypeAheadStyle) { }
public setShowPredictions(show: boolean) {
......@@ -709,7 +740,7 @@ export class PredictionTimeline {
return;
}
const toApply = this.expected.filter(({ gen }) => gen === this.expected[0].gen).map(({ p }) => p);
const toApply = this.currentGenerationPredictions;
if (show) {
this.cursor = undefined;
this.style.expectIncomingStyle(toApply.reduce((count, p) => p.affectsStyle ? count + 1 : count, 0));
......@@ -719,6 +750,19 @@ export class PredictionTimeline {
}
}
/**
* Undoes any predictions written and resets expectations.
*/
public undoAllPredictions() {
const buffer = this.getActiveBuffer();
if (this.showPredictions && buffer) {
this.terminal.write(this.currentGenerationPredictions.reverse()
.map(p => p.rollback(this.getCursor(buffer))).join(''));
}
this.expected = [];
}
/**
* Should be called when input is incoming to the temrinal.
*/
......@@ -875,6 +919,13 @@ export class PredictionTimeline {
return this.expected[this.expected.length - 1]?.p;
}
/**
* Peeks the first pending prediction.
*/
public peekStart() {
return this.expected[0]?.p;
}
public getCursor(buffer: IBuffer) {
if (!this.cursor) {
if (this.showPredictions) {
......@@ -1096,12 +1147,18 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
private timeline?: PredictionTimeline;
public stats?: PredictionStats;
/**
* Debounce that clears predictions after a timeout if the PTY doesn't apply them.
*/
private clearPredictionDebounce?: IDisposable;
constructor(
private readonly processManager: ITerminalProcessManager,
private readonly config: TerminalConfigHelper,
@ITelemetryService private readonly telemetryService: ITelemetryService,
) {
super();
this._register(toDisposable(() => this.clearPredictionDebounce?.dispose()));
}
public activate(terminal: Terminal): void {
......@@ -1139,6 +1196,23 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
}));
}
private deferClearingPredictions() {
if (!this.stats || !this.timeline) {
return;
}
this.clearPredictionDebounce?.dispose();
if (this.timeline.length === 0 || this.timeline.peekStart().clearAfterTimeout === false) {
this.clearPredictionDebounce = undefined;
return;
}
this.clearPredictionDebounce = disposableTimeout(
() => this.timeline?.undoAllPredictions(),
Math.max(500, this.stats.maxLatency * 3 / 2),
);
}
/**
* Note on debounce:
*
......@@ -1269,6 +1343,10 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
this.timeline.addBoundary(buffer, new HardBoundary());
break;
}
if (this.timeline.length === 1) {
this.deferClearingPredictions();
}
}
private onBeforeProcessData(event: IBeforeProcessDataEvent): void {
......@@ -1287,5 +1365,7 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
const terminal = this.timeline.terminal;
this.timeline.addBoundary(terminal.buffer.active, new HardBoundary());
}
this.deferClearingPredictions();
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册