-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
new parser #1399
new parser #1399
Conversation
This PR is up for a first technical and conceptual review: About
Interface to xterm.js:
Results so far:
There is still alot refactoring needed, several questions popped during coding (also see code comments):
This is alot at once, maybe someone finds some time to address some of the questions and basic layout ideas. @Tyriar @parisk @mofux 😄 |
src/EscapeSequenceParser.ts
Outdated
|
||
actionCSI(collected: string, params: number[], flag: string): void { | ||
this._terminal.prefix = collected; | ||
switch (flag) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the plan is eventually to move all these back to a map? I moved these from a switch to a map to reduce the number of conditionals at the cost of a function pointer for each option.
Also would something like this be better?
interface IEscapeSequenceParser {
registerCsiHandler(char: string, callback: (params: x) => y): void;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, gonna try to find out, how to jump with the lowest costs. I think emitting the charCode from EscapeSequenceParser
and placing the function pointers directly into an object will be the fastest (at least in chrome numerical properties avoid the key hashing). As soon as string type properties are involved, a switch is faster until ~20 entries or so.
About the interface - since I am not that good with typescript declarations any more typescripty suggestion is welcome.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I was getting at with the interface was a different way of thinking about the problem; instead of having ParserTerminal implement actionCSI, what if ParserTerminal/InputHandler called:
parser.registerCsiHandler('@', this._inputHandler.insertChars);
parser.registerCsiHandler('A', this._inputHandler.cursorUp);
(as opposed to)
case '@': return this._inputHandler.insertChars(params);
case 'A': return this._inputHandler.cursorUp(params);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes thats basically the decorator pattern I had in mind for the DCS part, should work for the others as well. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm do we need support for multiple handlers for a single action? This would allow stacking functionality for addons on top of the the basic functionality. Not sure if maintaining an array of function pointers will hurt performance wise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe starting with support for a single handler is enough for the moment. With the suggested API it wouldn't be complicated to add support for multiple handlers later on if a use-case comes up for it. I'd think that emitting more higher-level events from the input handler (like "linefeed" or "cursorMove") is more appealing for most use-cases.
src/EscapeSequenceParser.ts
Outdated
inst_P?: (dcs: string) => void; | ||
inst_U?: () => void; | ||
inst_E?: () => void; // TODO: real signature | ||
actionPrint?: (data: string, start: number, end: number) => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should make this mandatory (remove ?
), otherwise you would need to check for its existence before calling every time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kinda obsolete with the new jump tables.
src/EscapeSequenceParser.ts
Outdated
|
||
// default transition table points to global object | ||
// Q: Copy table to allow custom sequences w'o changing global object? | ||
export class EscapeSequenceParser { | ||
public initialState: number; | ||
public currentState: number; | ||
public transitions: TransitionTable; | ||
public osc: string; | ||
public params: number[]; | ||
public collected: string; | ||
public term: any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is any just temporary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, term
got removed.
src/EscapeSequenceParser.ts
Outdated
this.transitions = new TransitionTable(4095); | ||
this.transitions.table.set(TRANSITION_TABLE.table); | ||
constructor( | ||
terminal?: IParserTerminal | any, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be:
constructor(
public terminal: IParserTerminal
...
)
src/EscapeSequenceParser.ts
Outdated
if (code > 0x9f) { | ||
switch (currentState) { | ||
case 0: // GROUND -> add char to print string | ||
case STATE.GROUND: // add char to print string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid using whitespace for alignment, align with the :
or put on a new line.
src/EscapeSequenceParser.ts
Outdated
code: code, // actual character code | ||
state: currentState, // current state | ||
print: printed, // print buffer start index | ||
dcs: dcs, // dcs buffer start index |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If key and value are the same you can just use one:
{
pos: i,
code,
state: currentState,
print,
dcs,
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah nice shortcut.
src/EscapeSequenceParser.ts
Outdated
|
||
// FSM actions | ||
export const enum ACTION { | ||
ignore = 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use this naming convention for enums:
Lines 153 to 162 in 716a8d5
export enum ParserState { | |
NORMAL = 0, | |
ESCAPED = 1, | |
CSI_PARAM = 2, | |
CSI = 3, | |
OSC = 4, | |
CHARSET = 5, | |
DCS = 6, | |
IGNORE = 7 | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing. Oh and the enums must be const
, otherwise Typescript will place JS identifiers into the parse method with worse performance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wow, that's a huge difference! We must constify all the enums!
enum a {
b,
c,
d
}
const enum A2 {
B2,
B3,
B4
}
console.log(a.b);
console.log(A2.B2);
Compiles to:
var a;
(function (a) {
a[a["b"] = 0] = "b";
a[a["c"] = 1] = "c";
a[a["d"] = 2] = "d";
})(a || (a = {}));
console.log(a.b);
console.log(0 /* B2 */);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably not enough of an argument against it, but if we ever wanted to use babel to compile the typescript source directly, it's unable to handle const enum
s, because they require type information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure either if you want enums to be const everywhere. I need this feature here to get the plain number at JS level avoiding costly deref at runtime, still I wish I had access to the enum definition (e.g. have the states named for hot patches) and already thought about workarounds (with no handy solution yet lol).
src/EscapeSequenceParser.ts
Outdated
this.transitions.table.set(TRANSITION_TABLE.table); | ||
constructor( | ||
terminal?: IParserTerminal | any, | ||
transitions: TransitionTable = VT500_TRANSITION_TABLE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again you can add public before transitions
and then remove the member above.
src/EscapeSequenceParser.ts
Outdated
|
||
// default transition table points to global object | ||
// Q: Copy table to allow custom sequences w'o changing global object? | ||
export class EscapeSequenceParser { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this going to hide behind an interface and use the readonly trick for properties that shouldn't be edited outside?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure on this one yet: the transition table takes 5kB as Uint8Array and much more as fallback Array implementation (> 40kB, not done yet). I tend to make the transition table private and give the parser a copy on write method for custom states to save memory for multiple terminals.
Not sure either whether to hide EscapeSequenceParser
and give ParserTerminal/InputHandler
the main control API.
- quick'n dirty merge of classes - ParserTerminal is subclass of InputHandler - basic fallback handlers implemented - test regressions due to multiple changes in EscapeSequenceParser - runtime down to 2.3s - TODO: fix tests, implement DCS functions, full merge of classes
The function lookup tables are in place. The lookup tables are objects without a prototype to cut property lookups. I am not yet happy with the lookup code itself: ident = collect + String.fromCharCode(code);
if (this._escHandlers[ident]) this._escHandlers[ident](params, collect);
else this._escHandlerFb(collect, code); There are still some conceptual issues:
and minor performance issues:
Still it performs better than the switch version (~5% gain), compared to the parser in master this version is ~2.5x faster (320ms vs. 120ms for I also merged |
Made some progress towards a nicer integration. The current version does all callback registering directly in |
Let's definitely defer any more changes like this, I want to merge this ASAP to prevent it from becoming stale. |
Let's address it later after we're sure there are no major issues with the changes. Shall we create a follow up issue?
Again let's defer this.
Looks like this was fixed upstream after the fork chjj/term.js@36c1e5c |
Good idea for easier tracking. There is at least one issue that might be affected by this (cant find it atm, was something about a terminal game with weird control code output).
Yup, it follows the xterm meaning there too. This also contains the DCS sequence for custom keys and a tmux passthrough thing. Might be worth a closer look later on. |
src/InputHandler.ts
Outdated
* DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html) | ||
* Request Status String (DECRQSS), VT420 and up. | ||
* Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html) | ||
* FIXME: xterm and DEC flip P0 and P1 to indicate valid requests - which one to go with? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's fix this before merge
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It returns atm 1 for a valid and 0 for an invalid request (xterm style). Should this be flipped? If not I can simply remove the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah you're right it is already fixed 😄 let's remove the FIXME
src/InputHandler.ts
Outdated
switch (this._data) { | ||
// valid: DCS 1 $ r Pt ST (xterm) | ||
case '"q': // DECSCA | ||
return this._terminal.send(C0.ESC + 'P1' + '$r' + '0"q' + C0.ESC + '\\'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These long strings are more readable imo using string interpolation:
${C0.ESC}P1$r0"q${C0.ESC}\\'
Any reason for not going this way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will that work with 'ES5' as target? Thought this is ES6 style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TS compiles it down. This in Renderer.ts:
this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`;
Becomes:
this._terminal.screenElement.style.width = this.dimensions.canvasWidth + "px";
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh sweet, gonna change it.
@jerch I'll merge once I get another go ahead from you 😃 I can pull this into vscode insiders in a few days, I just did a big update with heaps of rendering changes so want to give that some bake time first. |
Guess I am done with this PR. Note that I already have some perf optimizations pending for |
🚀 😨 |
😓 |
WIP - don't merge...
TODO:
ParserTerminal
ParserTerminal
withInputHandler