Remove precedingCodepoint - use precedingJoinState instead

repeatPrecedingCharacter (for REP sequence) changed to look at
the grapheme cluser to the "left" in the BufferLine, and repeat that.
This is an extension of the xterm behavior, and changes the
semantics of REP, so is not fully compatible.
However, the new behavior is cleaner and saner.

Moved precedingJoinState property to EscapeSequenceParser.
This commit is contained in:
Per Bothner
2023-08-24 07:58:06 -07:00
parent 5ccf4d3311
commit dc6dd6d2a8
4 changed files with 41 additions and 44 deletions
+22 -26
View File
@@ -126,10 +126,6 @@ export class InputHandler extends Disposable implements IInputHandler {
protected _windowTitleStack: string[] = [];
protected _iconNameStack: string[] = [];
// Cached result of getJoinProperties(..., precedingCodepoint).
// Only valid if precedingCodepoint !== 0
public precedingJoinState: number = -1; // UnicodeJoinProperties
private _curAttrData: IAttributeData = DEFAULT_ATTR_DATA.clone();
public getAttrData(): IAttributeData { return this._curAttrData; }
private _eraseAttrDataInternal: IAttributeData = DEFAULT_ATTR_DATA.clone();
@@ -521,8 +517,7 @@ export class InputHandler extends Disposable implements IInputHandler {
bufferRow.setCellFromCodePoint(this._activeBuffer.x - 1, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
}
let precedingJoinState = this._parser.precedingCodepoint === 0 ? 0
: this.precedingJoinState;
let precedingJoinState = this._parser.precedingJoinState;
for (let pos = start; pos < end; ++pos) {
code = data[pos];
@@ -636,18 +631,7 @@ export class InputHandler extends Disposable implements IInputHandler {
}
}
this.precedingJoinState = precedingJoinState;
// store last char in Parser.precedingCodepoint for REP to work correctly
// This needs to check whether:
// - combining: only base char gets carried on (bug in xterm?)
if (end - start > 0) {
bufferRow.loadCell(this._activeBuffer.x - 1, this._workCell);
if (this._workCell.isCombined()) {
this._parser.precedingCodepoint = this._workCell.getChars().charCodeAt(0);
} else {
this._parser.precedingCodepoint = this._workCell.content;
}
}
this._parser.precedingJoinState = precedingJoinState;
// handle wide chars: reset cell to the right if it is second cell of a wide char
if (this._activeBuffer.x < cols && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x) === 0 && !bufferRow.hasContent(this._activeBuffer.x)) {
@@ -1593,9 +1577,8 @@ export class InputHandler extends Disposable implements IInputHandler {
* If the character preceding REP is a control function or part of a control function,
* the effect of REP is not defined by this Standard.
*
* Since we propagate the terminal as xterm-256color we have to follow xterm's behavior:
* - fullwidth + surrogate chars are ignored
* - for combining chars only the base char gets repeated
* We extend xterm's behavior to allow repeating entire grapheme clusters.
* This isn't 100% xterm-compatible, but it seems saner and more useful.
* - text attrs are applied normally
* - wrap around is respected
* - any valid sequence resets the carried forward char
@@ -1609,16 +1592,29 @@ export class InputHandler extends Disposable implements IInputHandler {
* (NOOP for any other sequence in between or NON ASCII characters).
*/
public repeatPrecedingCharacter(params: IParams): boolean {
if (!this._parser.precedingCodepoint) {
const joinState = this._parser.precedingJoinState;
if (!joinState) {
return true;
}
// call print to insert the chars and handle correct wrapping
const length = params.params[0] || 1;
const data = new Uint32Array(length);
for (let i = 0; i < length; ++i) {
data[i] = this._parser.precedingCodepoint;
const chWidth = UnicodeService.extractWidth(joinState);
const x = this._activeBuffer.x - chWidth;
const bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!;
const text = bufferRow.getString(x);
const data = new Uint32Array(text.length * length);
let idata = 0;
for (let itext = 0; itext < text.length; ) {
const ch = text.codePointAt(itext) || 0;
data[idata++] = ch;
itext += ch > 0xffff ? 2 : 1;
}
this.print(data, 0, data.length);
let tlength = idata;
for (let i = 1; i < length; ++i) {
data.copyWithin(tlength, 0, idata);
tlength += idata;
}
this.print(data, 0, tlength);
return true;
}
+9 -9
View File
@@ -230,7 +230,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
export class EscapeSequenceParser extends Disposable implements IEscapeSequenceParser {
public initialState: number;
public currentState: number;
public precedingCodepoint: number;
public precedingJoinState: number; // UnicodeJoinProperties
// buffers over several parse calls
protected _params: Params;
@@ -271,7 +271,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._params = new Params(); // defaults to 32 storable params/subparams
this._params.addParam(0); // ZDM
this._collect = 0;
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
// set default fallback handlers and handler lookup containers
this._printHandlerFb = (data, start, end): void => { };
@@ -448,7 +448,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._params.reset();
this._params.addParam(0); // ZDM
this._collect = 0;
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
// abort pending continuation from async handler
// Here the RESET type indicates, that the next parse call will
// ignore any saved stack, instead continues sync with next codepoint from GROUND
@@ -610,7 +610,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
// cleanup before continuing with the main sync loop
this._parseStack.state = ParserStackType.NONE;
start = this._parseStack.chunkPos + 1;
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
this.currentState = this._parseStack.transition & TableAccess.TRANSITION_STATE_MASK;
}
}
@@ -653,7 +653,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
case ParserAction.EXECUTE:
if (this._executeHandlers[code]) this._executeHandlers[code]();
else this._executeHandlerFb(code);
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
break;
case ParserAction.IGNORE:
break;
@@ -688,7 +688,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
if (j < 0) {
this._csiHandlerFb(this._collect << 8 | code, this._params);
}
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
break;
case ParserAction.PARAM:
// inner loop: digits (0x30 - 0x39) and ; (0x3b) and : (0x3a)
@@ -727,7 +727,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
if (jj < 0) {
this._escHandlerFb(this._collect << 8 | code);
}
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
break;
case ParserAction.CLEAR:
this._params.reset();
@@ -758,7 +758,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._params.reset();
this._params.addParam(0); // ZDM
this._collect = 0;
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
break;
case ParserAction.OSC_START:
this._oscParser.start();
@@ -783,7 +783,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._params.reset();
this._params.addParam(0); // ZDM
this._collect = 0;
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
break;
}
this.currentState = transition & TableAccess.TRANSITION_STATE_MASK;
+5 -4
View File
@@ -146,11 +146,12 @@ export type PrintFallbackHandlerType = PrintHandlerType;
*/
export interface IEscapeSequenceParser extends IDisposable {
/**
* Preceding codepoint to get REP working correctly.
* This must be set by the print handler as last action.
* It gets reset by the parser for any valid sequence beside REP itself.
* Preceding grapheme-join-state.
* Used for joining graphame clusters across calls to `print`.
* Also used by REP to check if repeat a character is allowed.
* It gets reset by the parser for any valid sequence besides text.
*/
precedingCodepoint: number;
precedingJoinState: number; // More specifically: UnicodeJoinProperties
/**
* Reset the parser to its initial state (handlers are kept).
+5 -5
View File
@@ -272,18 +272,18 @@ describe('InputHandler Integration Tests', function(): void {
`);
await pollFor(page, () => getLinesAsArray(4), ['##', '##', '##', '######']);
await pollFor(page, () => getCursor(), { col: 6, row: 3 });
// should not repeat on fullwidth chars
// do repeat on fullwidth chars (change from xterm)
await page.evaluate(`
window.term.reset();
window.term.write('¥\x1b[10b');
`);
await pollFor(page, () => getLinesAsArray(1), ['¥']);
// should repeat only base char of combining
await pollFor(page, () => getLinesAsArray(1), ['¥¥¥¥¥¥¥¥¥¥¥']);
// change from xterm: repeat grapheme cluster
await page.evaluate(`
window.term.reset();
window.term.write('e\u0301\x1b[5b');
window.term.write('e\u0301\x1b[2b');
`);
await pollFor(page, () => getLinesAsArray(1), ['e\u0301eeeee']);
await pollFor(page, () => getLinesAsArray(1), ['e\u0301e\u0301e\u0301']);
// should wrap correctly
await page.evaluate(`
window.term.reset();