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

terminal: improve typeahead handling for wrapped lines

上级 0f1a6fae
...@@ -17,6 +17,7 @@ const CSI = `${ESC}[`; ...@@ -17,6 +17,7 @@ const CSI = `${ESC}[`;
const SHOW_CURSOR = `${CSI}?25h`; const SHOW_CURSOR = `${CSI}?25h`;
const HIDE_CURSOR = `${CSI}?25l`; const HIDE_CURSOR = `${CSI}?25l`;
const DELETE_CHAR = `${CSI}X`; const DELETE_CHAR = `${CSI}X`;
const DELETE_REST_OF_LINE = `${CSI}K`;
const CSI_STYLE_RE = /^\x1b\[[0-9;]*m/; const CSI_STYLE_RE = /^\x1b\[[0-9;]*m/;
const CSI_MOVE_RE = /^\x1b\[([0-9]*)(;[35])?O?([DC])/; const CSI_MOVE_RE = /^\x1b\[([0-9]*)(;[35])?O?([DC])/;
const PASSWORD_INPUT_RE = /(password|passphrase|passwd).*:/i; const PASSWORD_INPUT_RE = /(password|passphrase|passwd).*:/i;
...@@ -41,25 +42,100 @@ const enum CursorMoveDirection { ...@@ -41,25 +42,100 @@ const enum CursorMoveDirection {
Forwards = 'C', Forwards = 'C',
} }
const setCursorPos = (x: number, y: number) => `${CSI}${y + 1};${x + 1}H`;
const setCursorCoordinate = (buffer: IBuffer, c: ICoordinate) => setCursorPos(c.x, c.y + (c.baseY - buffer.baseY));
interface ICoordinate { interface ICoordinate {
x: number; x: number;
y: number; y: number;
baseY: number; baseY: number;
} }
const getCellAtCoordinate = (b: IBuffer, c: ICoordinate) => b.getLine(c.y + c.baseY)?.getCell(c.x); class Cursor implements ICoordinate {
private _x = 0;
private _y = 1;
private _baseY = 1;
public get x() {
return this._x;
}
public get y() {
return this._y;
}
public get baseY() {
return this._baseY;
}
public get coordinate(): ICoordinate {
return { x: this._x, y: this._y, baseY: this._baseY };
}
constructor(public readonly rows: number, public readonly cols: number, private readonly buffer: IBuffer) {
this._x = buffer.cursorX;
this._y = buffer.cursorY;
this._baseY = buffer.baseY;
}
public getCell(loadInto?: IBufferCell) {
return this.buffer.getLine(this._y + this._baseY)?.getCell(this._x, loadInto);
}
public moveTo(coordinate: ICoordinate) {
this._x = coordinate.x;
this._y = (coordinate.y + coordinate.baseY) - this._baseY;
return this.moveInstruction();
}
public clone() {
const c = new Cursor(this.rows, this.cols, this.buffer);
c.moveTo(this);
return c;
}
public move(x: number, y: number) {
this._x = x;
this._y = y;
return this.moveInstruction();
}
public shift(x: number = 0, y: number = 0) {
this._x += x;
this._y += y;
return this.moveInstruction();
}
public moveInstruction() {
if (this._y >= this.rows) {
this._baseY += this._y - (this.rows - 1);
this._y = this.rows - 1;
} else if (this._y < 0) {
this._baseY -= this._y;
this._y = 0;
}
return `${CSI}${this.getRealLineY(this._y) + 1};${this._x + 1}H`;
}
private getRealLineY(y: number) {
let targetY = y;
for (let i = 0; i < y; i++) {
if (this.buffer.getLine(y)?.isWrapped) {
targetY--;
}
}
return targetY;
}
}
const moveToWordBoundary = (b: IBuffer, cursor: ICoordinate, direction: -1 | 1) => { const moveToWordBoundary = (b: IBuffer, cursor: Cursor, direction: -1 | 1) => {
let ateLeadingWhitespace = false; let ateLeadingWhitespace = false;
if (direction < 0) { if (direction < 0) {
cursor.x--; cursor.shift(-1);
} }
let cell: IBufferCell | undefined;
while (cursor.x >= 0) { while (cursor.x >= 0) {
const cell = getCellAtCoordinate(b, cursor); cell = cursor.getCell(cell);
if (!cell?.getCode()) { if (!cell?.getCode()) {
return; return;
} }
...@@ -73,14 +149,12 @@ const moveToWordBoundary = (b: IBuffer, cursor: ICoordinate, direction: -1 | 1) ...@@ -73,14 +149,12 @@ const moveToWordBoundary = (b: IBuffer, cursor: ICoordinate, direction: -1 | 1)
ateLeadingWhitespace = true; ateLeadingWhitespace = true;
} }
cursor.x += direction; cursor.shift(direction);
} }
if (direction < 0) { if (direction < 0) {
cursor.x++; // we want to place the cursor after the whitespace starting the word cursor.shift(1); // we want to place the cursor after the whitespace starting the word
} }
cursor.x = Math.max(0, cursor.x);
}; };
const enum MatchResult { const enum MatchResult {
...@@ -100,19 +174,19 @@ export interface IPrediction { ...@@ -100,19 +174,19 @@ export interface IPrediction {
* @returns a string to be written to the user terminal, or optionally a * @returns a string to be written to the user terminal, or optionally a
* string for the user terminal and real pty. * string for the user terminal and real pty.
*/ */
apply(buffer: IBuffer, cursor: ICoordinate): string; apply(buffer: IBuffer, cursor: Cursor): string;
/** /**
* Returns a sequence to roll back a previous `apply()` call. If * Returns a sequence to roll back a previous `apply()` call. If
* `rollForwards` is not given, then this is also called if a prediction * `rollForwards` is not given, then this is also called if a prediction
* is correct before show the user's data. * is correct before show the user's data.
*/ */
rollback(buffer: IBuffer): string; rollback(cursor: Cursor): string;
/** /**
* If available, this will be called when the prediction is correct. * If available, this will be called when the prediction is correct.
*/ */
rollForwards?(buffer: IBuffer, withInput: string): string; rollForwards(cursor: Cursor, withInput: string): string;
/** /**
* Returns whether the given input is one expected by this prediction. * Returns whether the given input is one expected by this prediction.
...@@ -222,6 +296,10 @@ class HardBoundary implements IPrediction { ...@@ -222,6 +296,10 @@ class HardBoundary implements IPrediction {
return ''; return '';
} }
public rollForwards() {
return '';
}
public matches() { public matches() {
return MatchResult.Failure; return MatchResult.Failure;
} }
...@@ -232,10 +310,12 @@ class HardBoundary implements IPrediction { ...@@ -232,10 +310,12 @@ class HardBoundary implements IPrediction {
* through its `matches` request. * through its `matches` request.
*/ */
class TentativeBoundary implements IPrediction { class TentativeBoundary implements IPrediction {
private expected?: Cursor;
constructor(private readonly inner: IPrediction) { } constructor(private readonly inner: IPrediction) { }
public apply(buffer: IBuffer, cursor: ICoordinate) { public apply(buffer: IBuffer, cursor: Cursor) {
this.inner.apply(buffer, cursor); this.expected = cursor.clone();
this.inner.apply(buffer, this.expected);
return ''; return '';
} }
...@@ -243,6 +323,14 @@ class TentativeBoundary implements IPrediction { ...@@ -243,6 +323,14 @@ class TentativeBoundary implements IPrediction {
return ''; return '';
} }
public rollForwards(cursor: Cursor, withInput: string) {
if (this.expected) {
cursor.moveTo(this.expected);
}
return withInput;
}
public matches(input: StringReader) { public matches(input: StringReader) {
return this.inner.matches(input); return this.inner.matches(input);
} }
...@@ -252,31 +340,40 @@ class TentativeBoundary implements IPrediction { ...@@ -252,31 +340,40 @@ class TentativeBoundary implements IPrediction {
* Prediction for a single alphanumeric character. * Prediction for a single alphanumeric character.
*/ */
class CharacterPrediction implements IPrediction { class CharacterPrediction implements IPrediction {
protected appliedAt?: ICoordinate & { protected appliedAt?: {
pos: ICoordinate,
oldAttributes: string; oldAttributes: string;
oldChar: string; oldChar: string;
}; };
constructor(private readonly style: string, private readonly char: string) { } constructor(private readonly style: string, private readonly char: string) { }
public apply(buffer: IBuffer, cursor: ICoordinate) { public apply(buffer: IBuffer, cursor: Cursor) {
const cell = getCellAtCoordinate(buffer, cursor); const cell = cursor.getCell();
this.appliedAt = cell this.appliedAt = cell
? { ...cursor, oldAttributes: getBufferCellAttributes(cell), oldChar: cell.getChars() } ? { pos: cursor.coordinate, oldAttributes: getBufferCellAttributes(cell), oldChar: cell.getChars() }
: { ...cursor, oldAttributes: '', oldChar: '' }; : { pos: cursor.coordinate, oldAttributes: '', oldChar: '' };
cursor.x++; cursor.shift(1);
return this.style + this.char + this.appliedAt.oldAttributes; return this.style + this.char + this.appliedAt.oldAttributes;
} }
public rollback(buffer: IBuffer) { public rollback(cursor: Cursor) {
if (!this.appliedAt) { if (!this.appliedAt) {
return ''; // not applied return ''; // not applied
} }
const a = this.appliedAt; const { oldAttributes, oldChar, pos } = this.appliedAt;
this.appliedAt = undefined; const r = cursor.moveTo(pos) + (oldChar ? `${oldAttributes}${oldChar}${cursor.moveTo(pos)}` : DELETE_CHAR);
return setCursorCoordinate(buffer, a) + (a.oldChar ? `${a.oldAttributes}${a.oldChar}${setCursorCoordinate(buffer, a)}` : DELETE_CHAR); return r;
}
public rollForwards(cursor: Cursor, input: string) {
if (!this.appliedAt) {
return ''; // not applied
}
return cursor.clone().moveTo(this.appliedAt.pos) + this.appliedAt.oldAttributes + input;
} }
public matches(input: StringReader) { public matches(input: StringReader) {
...@@ -303,14 +400,13 @@ class BackspacePrediction extends CharacterPrediction { ...@@ -303,14 +400,13 @@ class BackspacePrediction extends CharacterPrediction {
super('', '\b'); super('', '\b');
} }
public apply(buffer: IBuffer, cursor: ICoordinate) { public apply(_: IBuffer, cursor: Cursor) {
const cell = getCellAtCoordinate(buffer, cursor); const cell = cursor.getCell();
this.appliedAt = cell this.appliedAt = cell
? { ...cursor, oldAttributes: getBufferCellAttributes(cell), oldChar: cell.getChars() } ? { pos: cursor.coordinate, oldAttributes: getBufferCellAttributes(cell), oldChar: cell.getChars() }
: { ...cursor, oldAttributes: '', oldChar: '' }; : { pos: cursor.coordinate, oldAttributes: '', oldChar: '' };
cursor.x--; return cursor.shift(-1) + DELETE_CHAR;
return setCursorCoordinate(buffer, cursor) + DELETE_CHAR;
} }
public rollForwards() { public rollForwards() {
...@@ -338,21 +434,14 @@ class BackspacePrediction extends CharacterPrediction { ...@@ -338,21 +434,14 @@ class BackspacePrediction extends CharacterPrediction {
class NewlinePrediction implements IPrediction { class NewlinePrediction implements IPrediction {
protected prevPosition?: ICoordinate; protected prevPosition?: ICoordinate;
public apply(_: IBuffer, cursor: ICoordinate) { public apply(_: IBuffer, cursor: Cursor) {
this.prevPosition = { ...cursor }; this.prevPosition = cursor.coordinate;
cursor.x = 0; cursor.move(0, cursor.y + 1);
cursor.y++;
return '\r\n'; return '\r\n';
} }
public rollback(buffer: IBuffer) { public rollback(cursor: Cursor) {
if (!this.prevPosition) { return this.prevPosition ? cursor.moveTo(this.prevPosition) : '';
return ''; // not applied
}
const p = this.prevPosition;
this.prevPosition = undefined;
return setCursorCoordinate(buffer, p) + DELETE_CHAR;
} }
public rollForwards() { public rollForwards() {
...@@ -364,10 +453,35 @@ class NewlinePrediction implements IPrediction { ...@@ -364,10 +453,35 @@ class NewlinePrediction implements IPrediction {
} }
} }
/**
* Prediction when the cursor reaches the end of the line. Similar to newline
* prediction, but shells handle it slightly differently.
*/
class LinewrapPrediction extends NewlinePrediction implements IPrediction {
public apply(_: IBuffer, cursor: Cursor) {
this.prevPosition = cursor.coordinate;
cursor.move(0, cursor.y + 1);
return ' \r';
}
public matches(input: StringReader) {
// bash and zshell add a space which wraps in the terminal, then a CR
const r = input.eatGradually(' \r');
if (r !== MatchResult.Failure) {
// zshell additionally adds a clear line after wrapping to be safe -- eat it
const r2 = input.eatGradually(DELETE_REST_OF_LINE);
return r2 === MatchResult.Buffer ? MatchResult.Buffer : r;
}
return input.eatGradually('\r\n');
}
}
class CursorMovePrediction implements IPrediction { class CursorMovePrediction implements IPrediction {
private applied?: { private applied?: {
rollForward: string; rollForward: string;
rollBack: string; prevPosition: number;
prevAttrs: string;
amount: number; amount: number;
}; };
...@@ -377,31 +491,39 @@ class CursorMovePrediction implements IPrediction { ...@@ -377,31 +491,39 @@ class CursorMovePrediction implements IPrediction {
private readonly amount: number, private readonly amount: number,
) { } ) { }
public apply(buffer: IBuffer, cursor: ICoordinate) { public apply(buffer: IBuffer, cursor: Cursor) {
let rollBack = setCursorCoordinate(buffer, cursor); const prevPosition = cursor.x;
const currentCell = getCellAtCoordinate(buffer, cursor); const currentCell = cursor.getCell();
if (currentCell) { const prevAttrs = currentCell ? getBufferCellAttributes(currentCell) : '';
rollBack += getBufferCellAttributes(currentCell);
}
const { amount, direction, moveByWords } = this; const { amount, direction, moveByWords } = this;
const delta = direction === CursorMoveDirection.Back ? -1 : 1; const delta = direction === CursorMoveDirection.Back ? -1 : 1;
const startX = cursor.x;
const target = cursor.clone();
if (moveByWords) { if (moveByWords) {
for (let i = 0; i < amount; i++) { for (let i = 0; i < amount; i++) {
moveToWordBoundary(buffer, cursor, delta); moveToWordBoundary(buffer, target, delta);
} }
} else { } else {
cursor.x += delta * amount; target.shift(delta * amount);
} }
const rollForward = setCursorCoordinate(buffer, cursor); this.applied = {
this.applied = { amount: Math.abs(cursor.x - startX), rollBack, rollForward }; amount: Math.abs(cursor.x - target.x),
prevPosition,
prevAttrs,
rollForward: cursor.moveTo(target),
};
return this.applied.rollForward; return this.applied.rollForward;
} }
public rollback() { public rollback(cursor: Cursor) {
return this.applied?.rollBack ?? ''; if (!this.applied) {
return '';
}
return cursor.move(this.applied.prevPosition, cursor.y) + this.applied.prevAttrs;
} }
public rollForwards() { public rollForwards() {
...@@ -416,18 +538,19 @@ class CursorMovePrediction implements IPrediction { ...@@ -416,18 +538,19 @@ class CursorMovePrediction implements IPrediction {
const direction = this.direction; const direction = this.direction;
const { amount, rollForward } = this.applied; const { amount, rollForward } = this.applied;
if (amount === 1) {
// arg can be omitted to move one character // arg can be omitted to move one character
const r = input.eatGradually(`${CSI}${direction}`); const r = input.eatGradually(`${CSI}${direction}`.repeat(amount));
if (r !== MatchResult.Failure) {
return r;
}
// \b is the equivalent to moving one character back
if (direction === CursorMoveDirection.Back) {
const r = input.eatGradually(`\b`.repeat(amount));
if (r !== MatchResult.Failure) { if (r !== MatchResult.Failure) {
return r; return r;
} }
// \b is the equivalent to moving one character back
const r2 = input.eatGradually(`\b`);
if (r2 !== MatchResult.Failure) {
return r2;
}
} }
// check if the cursor position is set absolutely // check if the cursor position is set absolutely
...@@ -516,7 +639,7 @@ export class PredictionTimeline { ...@@ -516,7 +639,7 @@ export class PredictionTimeline {
* Cursor position -- kept outside the buffer since it can be ahead if * Cursor position -- kept outside the buffer since it can be ahead if
* typing swiftly. * typing swiftly.
*/ */
private cursor: ICoordinate | undefined; private cursor: Cursor | undefined;
/** /**
* Previously sent data that was buffered and should be prepended to the * Previously sent data that was buffered and should be prepended to the
...@@ -558,7 +681,7 @@ export class PredictionTimeline { ...@@ -558,7 +681,7 @@ export class PredictionTimeline {
this.cursor = undefined; this.cursor = undefined;
this.terminal.write(toApply.map(p => p.apply(buffer, this.getCursor(buffer))).join('')); this.terminal.write(toApply.map(p => p.apply(buffer, this.getCursor(buffer))).join(''));
} else { } else {
this.terminal.write(toApply.reverse().map(p => p.rollback(buffer)).join('')); this.terminal.write(toApply.reverse().map(p => p.rollback(this.getCursor(buffer))).join(''));
} }
} }
...@@ -597,14 +720,14 @@ export class PredictionTimeline { ...@@ -597,14 +720,14 @@ export class PredictionTimeline {
emitPredictionOmitted(); emitPredictionOmitted();
const prediction = this.expected[0].p; const prediction = this.expected[0].p;
const cursor = this.getCursor(buffer);
let beforeTestReaderIndex = reader.index; let beforeTestReaderIndex = reader.index;
switch (prediction.matches(reader)) { switch (prediction.matches(reader)) {
case MatchResult.Success: case MatchResult.Success:
// if the input character matches what the next prediction expected, undo // if the input character matches what the next prediction expected, undo
// the prediction and write the real character out. // the prediction and write the real character out.
const eaten = input.slice(beforeTestReaderIndex, reader.index); const eaten = input.slice(beforeTestReaderIndex, reader.index);
output += prediction.rollForwards?.(buffer, eaten) output += prediction.rollForwards?.(cursor, eaten);
?? (prediction.rollback(buffer) + input.slice(beforeTestReaderIndex, reader.index));
this.succeededEmitter.fire(prediction); this.succeededEmitter.fire(prediction);
this.expected.shift(); this.expected.shift();
break; break;
...@@ -618,8 +741,8 @@ export class PredictionTimeline { ...@@ -618,8 +741,8 @@ export class PredictionTimeline {
// on a failure, roll back all remaining items in this generation // on a failure, roll back all remaining items in this generation
// and clear predictions, since they are no longer valid // and clear predictions, since they are no longer valid
output += this.expected.filter(p => p.gen === startingGen) output += this.expected.filter(p => p.gen === startingGen)
.map(({ p }) => p.rollback(buffer))
.reverse() .reverse()
.map(({ p }) => p.rollback(this.getCursor(buffer)))
.join(''); .join('');
this.expected = []; this.expected = [];
this.cursor = undefined; this.cursor = undefined;
...@@ -658,7 +781,7 @@ export class PredictionTimeline { ...@@ -658,7 +781,7 @@ export class PredictionTimeline {
} }
if (this.cursor) { if (this.cursor) {
output += setCursorCoordinate(buffer, this.cursor); output += this.cursor.moveInstruction();
} }
// prevent cursor flickering while typing // prevent cursor flickering while typing
...@@ -677,9 +800,14 @@ export class PredictionTimeline { ...@@ -677,9 +800,14 @@ export class PredictionTimeline {
if (this.currentGen === this.expected[0].gen) { if (this.currentGen === this.expected[0].gen) {
const text = prediction.apply(buffer, this.getCursor(buffer)); const text = prediction.apply(buffer, this.getCursor(buffer));
if (this.showPredictions) { if (this.showPredictions) {
// console.log('predict:', JSON.stringify(text));
this.terminal.write(text); this.terminal.write(text);
} }
return true;
} }
return false;
} }
/** /**
...@@ -694,12 +822,16 @@ export class PredictionTimeline { ...@@ -694,12 +822,16 @@ export class PredictionTimeline {
public getCursor(buffer: IBuffer) { public getCursor(buffer: IBuffer) {
if (!this.cursor) { if (!this.cursor) {
this.cursor = { baseY: buffer.baseY, y: buffer.cursorY, x: buffer.cursorX }; this.cursor = new Cursor(this.terminal.rows, this.terminal.cols, buffer);
} }
return this.cursor; return this.cursor;
} }
private clearCursor() {
this.cursor = undefined;
}
private getActiveBuffer() { private getActiveBuffer() {
const buffer = this.terminal.buffer.active; const buffer = this.terminal.buffer.active;
return buffer.type === 'normal' ? buffer : undefined; return buffer.type === 'normal' ? buffer : undefined;
...@@ -758,6 +890,7 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon { ...@@ -758,6 +890,7 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
timeline.setShowPredictions(this.typeaheadThreshold === 0); timeline.setShowPredictions(this.typeaheadThreshold === 0);
this._register(terminal.onData(e => this.onUserData(e))); this._register(terminal.onData(e => this.onUserData(e)));
this._register(terminal.onResize(() => timeline.clearCursor()));
this._register(this.config.onConfigChanged(() => { this._register(this.config.onConfigChanged(() => {
this.typeheadStyle = parseTypeheadStyle(this.config.config.typeaheadStyle); this.typeheadStyle = parseTypeheadStyle(this.config.config.typeaheadStyle);
this.typeaheadThreshold = this.config.config.typeaheadThreshold; this.typeaheadThreshold = this.config.config.typeaheadThreshold;
...@@ -852,9 +985,8 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon { ...@@ -852,9 +985,8 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
if (reader.eatCharCode(32, 126)) { // alphanum if (reader.eatCharCode(32, 126)) { // alphanum
const char = data[reader.index - 1]; const char = data[reader.index - 1];
this.timeline.addPrediction(buffer, new CharacterPrediction(this.typeheadStyle, char)); if (this.timeline.addPrediction(buffer, new CharacterPrediction(this.typeheadStyle, char)) && this.timeline.getCursor(buffer).x === terminal.cols) {
if (this.timeline.getCursor(buffer).x === terminal.cols) { this.timeline.addBoundary(buffer, new TentativeBoundary(new LinewrapPrediction()));
this.timeline.addBoundary(buffer, new NewlinePrediction());
} }
continue; continue;
} }
......
...@@ -353,10 +353,10 @@ export const terminalConfiguration: IConfigurationNode = { ...@@ -353,10 +353,10 @@ export const terminalConfiguration: IConfigurationNode = {
default: true default: true
}, },
'terminal.integrated.typeaheadThreshold': { 'terminal.integrated.typeaheadThreshold': {
description: localize('terminal.integrated.typeaheadThreshold', "Experimental: length of time, in milliseconds, where typeahead will activate. If '0', typeahead will always be on, and if '-1' it will be disabled."), description: localize('terminal.integrated.typeaheadThreshold', "Experimental: length of typing delay, in milliseconds, where typeahead will activate. If '0', typeahead will always be on, and if '-1' it will be disabled."),
type: 'integer', type: 'integer',
minimum: -1, minimum: -1,
default: -1, default: 15,
}, },
'terminal.integrated.typeaheadStyle': { 'terminal.integrated.typeaheadStyle': {
description: localize('terminal.integrated.typeaheadStyle', "Experimental: terminal style of typeahead text, either a font style or an RGB color."), description: localize('terminal.integrated.typeaheadStyle', "Experimental: terminal style of typeahead text, either a font style or an RGB color."),
......
...@@ -75,11 +75,9 @@ suite('Workbench - Terminal Typeahead', () => { ...@@ -75,11 +75,9 @@ suite('Workbench - Terminal Typeahead', () => {
let config: ITerminalConfiguration; let config: ITerminalConfiguration;
let addon: TypeAheadAddon; let addon: TypeAheadAddon;
const predictedHelloo = [ const predictedHelloo = [
`${CSI}?25l`, // hide cursor `${CSI}?25l`, // hide cursor
`${CSI}2;7H`, // move cursor cursor `${CSI}2;7H`, // move cursor cursor
`${CSI}X`, // delete character
'o', // new character 'o', // new character
`${CSI}2;8H`, // place cursor back at end of line `${CSI}2;8H`, // place cursor back at end of line
`${CSI}?25h`, // show cursor `${CSI}?25h`, // show cursor
...@@ -145,7 +143,6 @@ suite('Workbench - Terminal Typeahead', () => { ...@@ -145,7 +143,6 @@ suite('Workbench - Terminal Typeahead', () => {
expectProcessed(`${CSI}4mo`, [ expectProcessed(`${CSI}4mo`, [
`${CSI}?25l`, // hide cursor `${CSI}?25l`, // hide cursor
`${CSI}2;7H`, // move cursor cursor `${CSI}2;7H`, // move cursor cursor
`${CSI}X`, // delete character
`${CSI}4m`, // PTY's style `${CSI}4m`, // PTY's style
'o', // new character 'o', // new character
`${CSI}2;8H`, // place cursor back at end of line `${CSI}2;8H`, // place cursor back at end of line
...@@ -162,7 +159,6 @@ suite('Workbench - Terminal Typeahead', () => { ...@@ -162,7 +159,6 @@ suite('Workbench - Terminal Typeahead', () => {
`${CSI}?25l`, // hide cursor from PTY `${CSI}?25l`, // hide cursor from PTY
`${CSI}?25l`, // hide cursor `${CSI}?25l`, // hide cursor
`${CSI}2;7H`, // move cursor cursor `${CSI}2;7H`, // move cursor cursor
`${CSI}X`, // delete character
'o', // new character 'o', // new character
`${CSI}?25h`, // show cursor from PTY `${CSI}?25h`, // show cursor from PTY
`${CSI}2;8H`, // place cursor back at end of line `${CSI}2;8H`, // place cursor back at end of line
...@@ -245,6 +241,7 @@ function stubPrediction(): IPrediction { ...@@ -245,6 +241,7 @@ function stubPrediction(): IPrediction {
apply: () => '', apply: () => '',
rollback: () => '', rollback: () => '',
matches: () => 0, matches: () => 0,
rollForwards: () => '',
}; };
} }
...@@ -275,6 +272,7 @@ function createMockTerminal(...lines: string[]) { ...@@ -275,6 +272,7 @@ function createMockTerminal(...lines: string[]) {
terminal: { terminal: {
cols: 80, cols: 80,
rows: 5, rows: 5,
onResize: new Emitter<void>().event,
onData: onData.event, onData: onData.event,
write(line: string) { write(line: string) {
written.push(line); written.push(line);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册