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

terminal: more correct style parsing in typeahead

上级 42473289
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { equals as arrayEqual } from 'vs/base/common/arrays';
import { Color } from 'vs/base/common/color'; import { Color } from 'vs/base/common/color';
import { debounce } from 'vs/base/common/decorators'; import { debounce } from 'vs/base/common/decorators';
import { Emitter } from 'vs/base/common/event'; import { Emitter } from 'vs/base/common/event';
...@@ -688,7 +687,7 @@ export class PredictionTimeline { ...@@ -688,7 +687,7 @@ export class PredictionTimeline {
return; return;
} }
console.log('set predictions:', show); // console.log('set predictions:', show);
this.showPredictions = show; this.showPredictions = show;
const buffer = this.getActiveBuffer(); const buffer = this.getActiveBuffer();
...@@ -763,6 +762,11 @@ export class PredictionTimeline { ...@@ -763,6 +762,11 @@ export class PredictionTimeline {
// and clear predictions, since they are no longer valid // and clear predictions, since they are no longer valid
const rollback = this.expected.filter(p => p.gen === startingGen).reverse(); const rollback = this.expected.filter(p => p.gen === startingGen).reverse();
output += rollback.map(({ p }) => p.rollback(this.getCursor(buffer))).join(''); output += rollback.map(({ p }) => p.rollback(this.getCursor(buffer))).join('');
if (rollback.some(r => r.p.affectsStyle)) {
// reading the current style should generally be safe, since predictions
// always restore the style if they modify it.
output += attributesToSeq(core(this.terminal)._inputHandler._curAttrData);
}
this.expected = []; this.expected = [];
this.cursor = undefined; this.cursor = undefined;
this.failedEmitter.fire(prediction); this.failedEmitter.fire(prediction);
...@@ -825,7 +829,7 @@ export class PredictionTimeline { ...@@ -825,7 +829,7 @@ export class PredictionTimeline {
if (prediction.affectsStyle) { if (prediction.affectsStyle) {
this.style.expectIncomingStyle(); this.style.expectIncomingStyle();
} }
console.log('predict:', JSON.stringify(text)); // console.log('predict:', JSON.stringify(text));
this.terminal.write(text); this.terminal.write(text);
} }
...@@ -899,6 +903,56 @@ const attributesToSeq = (cell: XTermAttributes) => cell.isAttributeDefault() ...@@ -899,6 +903,56 @@ const attributesToSeq = (cell: XTermAttributes) => cell.isAttributeDefault()
cell.isBgDefault() && `${CSI}49m`, cell.isBgDefault() && `${CSI}49m`,
].filter(seq => !!seq).join(''); ].filter(seq => !!seq).join('');
const arrayHasPrefixAt = <T>(a: ReadonlyArray<T>, ai: number, b: ReadonlyArray<T>) => {
if (a.length - ai > b.length) {
return false;
}
for (let bi = 0; bi < b.length; bi++, ai++) {
if (b[ai] !== a[ai]) {
return false;
}
}
return true;
};
/**
* @see https://github.com/xtermjs/xterm.js/blob/065eb13a9d3145bea687239680ec9696d9112b8e/src/common/InputHandler.ts#L2127
*/
const getColorWidth = (params: (number | number[])[], pos: number) => {
const accu = [0, 0, -1, 0, 0, 0];
let cSpace = 0;
let advance = 0;
do {
const v = params[pos + advance];
accu[advance + cSpace] = typeof v === 'number' ? v : v[0];
if (typeof v !== 'number') {
let i = 0;
do {
if (accu[1] === 5) {
cSpace = 1;
}
accu[advance + i + 1 + cSpace] = v[i];
} while (++i < v.length && i + advance + 1 + cSpace < accu.length);
break;
}
// exit early if can decide color mode with semicolons
if ((accu[1] === 5 && advance + cSpace >= 2)
|| (accu[1] === 2 && advance + cSpace >= 5)) {
break;
}
// offset colorSpace slot for semicolon mode
if (accu[1]) {
cSpace = 1;
}
} while (++advance + pos < params.length && advance + cSpace < accu.length);
return advance;
};
class TypeAheadStyle { class TypeAheadStyle {
private static compileArgs(args: ReadonlyArray<number>) { private static compileArgs(args: ReadonlyArray<number>) {
return `${CSI}${args.join(';')}m`; return `${CSI}${args.join(';')}m`;
...@@ -909,9 +963,9 @@ class TypeAheadStyle { ...@@ -909,9 +963,9 @@ class TypeAheadStyle {
* we see a style coming in, we know that the PTY actually wanted to update. * we see a style coming in, we know that the PTY actually wanted to update.
*/ */
private expectedIncomingStyles = 0; private expectedIncomingStyles = 0;
private applyArgs!: number[]; private applyArgs!: ReadonlyArray<number>;
private undoArgs!: number[]; private originalUndoArgs!: ReadonlyArray<number>;
private colorFg = false; private undoArgs!: ReadonlyArray<number>;
public apply!: string; public apply!: string;
public undo!: string; public undo!: string;
...@@ -932,21 +986,60 @@ class TypeAheadStyle { ...@@ -932,21 +986,60 @@ class TypeAheadStyle {
* Should be called when an attribut eupdate happens in the terminal. * Should be called when an attribut eupdate happens in the terminal.
*/ */
public onDidWriteSGR(args: (number | number[])[]) { public onDidWriteSGR(args: (number | number[])[]) {
if (!this.colorFg) { const originalUndo = this.undoArgs;
return; // no need to keep track if typeahead is not rgb for (let i = 0; i < args.length;) {
} const px = args[i];
const p = typeof px === 'number' ? px : px[0];
if (this.expectedIncomingStyles) {
if (arrayHasPrefixAt(args, i, this.undoArgs)) {
this.expectedIncomingStyles--;
i += this.undoArgs.length;
continue;
}
if (arrayHasPrefixAt(args, i, this.applyArgs)) {
this.expectedIncomingStyles--;
i += this.applyArgs.length;
continue;
}
}
if (this.expectedIncomingStyles && (arrayEqual(args, this.applyArgs) || arrayEqual(args, this.undoArgs))) { const width = p === 38 || p === 48 || p === 58 ? getColorWidth(args, i) : 1;
this.expectedIncomingStyles--; switch (this.applyArgs[0]) {
return; case 1:
if (p === 2) {
this.undoArgs = [22, 2];
} else if (p === 22 || p === 0) {
this.undoArgs = [22];
}
break;
case 2:
if (p === 1) {
this.undoArgs = [22, 1];
} else if (p === 22 || p === 0) {
this.undoArgs = [22];
}
break;
case 38:
if (p === 0 || p === 39 || p === 100) {
this.undoArgs = [39];
} else if ((p >= 30 && p <= 38) || (p >= 90 && p <= 97)) {
this.undoArgs = args.slice(i, i + width) as number[];
}
break;
default:
if (p === this.applyArgs[0]) {
this.undoArgs = this.applyArgs;
} else if (p === 0) {
this.undoArgs = this.originalUndoArgs;
}
// no-op
}
i += width;
} }
const p = args[0]; if (originalUndo !== this.undoArgs) {
if (p === 0 || p === 39 || p === 100) {
this.undoArgs = [39];
this.undo = TypeAheadStyle.compileArgs(this.undoArgs);
} else if ((p >= 30 && p <= 37) || (p >= 90 && p <= 97)) {
this.undoArgs = args as number[];
this.undo = TypeAheadStyle.compileArgs(this.undoArgs); this.undo = TypeAheadStyle.compileArgs(this.undoArgs);
} }
} }
...@@ -957,10 +1050,9 @@ class TypeAheadStyle { ...@@ -957,10 +1050,9 @@ class TypeAheadStyle {
public onUpdate(style: ITerminalConfiguration['typeaheadStyle']) { public onUpdate(style: ITerminalConfiguration['typeaheadStyle']) {
const { applyArgs, undoArgs } = this.getArgs(style); const { applyArgs, undoArgs } = this.getArgs(style);
this.applyArgs = applyArgs; this.applyArgs = applyArgs;
this.undoArgs = undoArgs; this.undoArgs = this.originalUndoArgs = undoArgs;
this.apply = TypeAheadStyle.compileArgs(this.applyArgs); this.apply = TypeAheadStyle.compileArgs(this.applyArgs);
this.undo = TypeAheadStyle.compileArgs(this.undoArgs); this.undo = TypeAheadStyle.compileArgs(this.undoArgs);
this.colorFg = this.applyArgs[0] === 38;
} }
private getArgs(style: ITerminalConfiguration['typeaheadStyle']) { private getArgs(style: ITerminalConfiguration['typeaheadStyle']) {
...@@ -1076,7 +1168,7 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon { ...@@ -1076,7 +1168,7 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
return; return;
} }
console.log('user data:', JSON.stringify(data)); // console.log('user data:', JSON.stringify(data));
const terminal = this.timeline.terminal; const terminal = this.timeline.terminal;
const buffer = terminal.buffer.active; const buffer = terminal.buffer.active;
...@@ -1169,9 +1261,9 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon { ...@@ -1169,9 +1261,9 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
return; return;
} }
console.log('incoming data:', JSON.stringify(event.data)); // console.log('incoming data:', JSON.stringify(event.data));
event.data = this.timeline.beforeServerInput(event.data); event.data = this.timeline.beforeServerInput(event.data);
console.log('emitted data:', JSON.stringify(event.data)); // console.log('emitted data:', JSON.stringify(event.data));
// If there's something that looks like a password prompt, omit giving // If there's something that looks like a password prompt, omit giving
// input. This is approximate since there's no TTY "password here" code, // input. This is approximate since there's no TTY "password here" code,
......
...@@ -18,7 +18,10 @@ export interface XTermCore { ...@@ -18,7 +18,10 @@ export interface XTermCore {
_coreService: { _coreService: {
triggerDataEvent(data: string, wasUserInput?: boolean): void; triggerDataEvent(data: string, wasUserInput?: boolean): void;
};
_inputHandler: {
_curAttrData: XTermAttributes;
}; };
_renderService: { _renderService: {
......
...@@ -130,6 +130,7 @@ suite('Workbench - Terminal Typeahead', () => { ...@@ -130,6 +130,7 @@ suite('Workbench - Terminal Typeahead', () => {
`${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}X`, // delete character
`${CSI}0m`, // reset style
'q', // new character 'q', // new character
`${CSI}?25h`, // show cursor `${CSI}?25h`, // show cursor
].join('')); ].join(''));
...@@ -139,7 +140,7 @@ suite('Workbench - Terminal Typeahead', () => { ...@@ -139,7 +140,7 @@ suite('Workbench - Terminal Typeahead', () => {
test('restores cursor graphics mode', () => { test('restores cursor graphics mode', () => {
const t = createMockTerminal({ const t = createMockTerminal({
lines: ['hello|'], lines: ['hello|'],
cursorAttrs: { isBold: true, isFgPalette: true, getFgColor: 1 }, cursorAttrs: { isAttributeDefault: false, isBold: true, isFgPalette: true, getFgColor: 1 },
}); });
addon.activate(t.terminal); addon.activate(t.terminal);
t.onData('o'); t.onData('o');
...@@ -148,6 +149,8 @@ suite('Workbench - Terminal Typeahead', () => { ...@@ -148,6 +149,8 @@ suite('Workbench - Terminal Typeahead', () => {
`${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}X`, // delete character
`${CSI}1m`, // reset style
`${CSI}38;5;1m`, // reset style
'q', // new character 'q', // new character
`${CSI}?25h`, // show cursor `${CSI}?25h`, // show cursor
].join('')); ].join(''));
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册