]>
Commit | Line | Data |
---|---|---|
ef60e50e DI |
1 | /** |
2 | * @license MIT | |
3 | */ | |
4 | ||
659fb90c DI |
5 | import { C0 } from './EscapeSequences'; |
6 | import { IInputHandler } from './Interfaces'; | |
1848fff4 | 7 | import { CHARSETS } from './Charsets'; |
659fb90c | 8 | |
fa3484cd DI |
9 | const normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {}; |
10 | normalStateHandler[C0.BEL] = (parser, handler) => handler.bell(); | |
11 | normalStateHandler[C0.LF] = (parser, handler) => handler.lineFeed(); | |
659fb90c DI |
12 | normalStateHandler[C0.VT] = normalStateHandler[C0.LF]; |
13 | normalStateHandler[C0.FF] = normalStateHandler[C0.LF]; | |
fa3484cd DI |
14 | normalStateHandler[C0.CR] = (parser, handler) => handler.carriageReturn(); |
15 | normalStateHandler[C0.BS] = (parser, handler) => handler.backspace(); | |
16 | normalStateHandler[C0.HT] = (parser, handler) => handler.tab(); | |
17 | normalStateHandler[C0.SO] = (parser, handler) => handler.shiftOut(); | |
18 | normalStateHandler[C0.SI] = (parser, handler) => handler.shiftIn(); | |
19 | normalStateHandler[C0.ESC] = (parser, handler) => parser.setState(ParserState.ESCAPED); | |
672dc1a2 | 20 | |
ecd7e69f DI |
21 | // TODO: Remove terminal when parser owns params and currentParam |
22 | const escapedStateHandler: {[key: string]: (parser: Parser, terminal: any) => void} = {}; | |
23 | escapedStateHandler['['] = (parser, terminal) => { | |
24 | // ESC [ Control Sequence Introducer (CSI is 0x9b) | |
25 | terminal.params = []; | |
26 | terminal.currentParam = 0; | |
27 | parser.setState(ParserState.CSI_PARAM); | |
28 | }; | |
29 | escapedStateHandler[']'] = (parser, terminal) => { | |
30 | // ESC ] Operating System Command (OSC is 0x9d) | |
31 | terminal.params = []; | |
32 | terminal.currentParam = 0; | |
33 | parser.setState(ParserState.OSC); | |
34 | }; | |
35 | escapedStateHandler['P'] = (parser, terminal) => { | |
36 | // ESC P Device Control String (DCS is 0x90) | |
37 | terminal.params = []; | |
38 | terminal.currentParam = 0; | |
39 | parser.setState(ParserState.DCS); | |
40 | }; | |
41 | escapedStateHandler['_'] = (parser, terminal) => { | |
42 | // ESC _ Application Program Command ( APC is 0x9f). | |
43 | parser.setState(ParserState.IGNORE); | |
4448ebe3 | 44 | }; |
ecd7e69f DI |
45 | escapedStateHandler['^'] = (parser, terminal) => { |
46 | // ESC ^ Privacy Message ( PM is 0x9e). | |
47 | parser.setState(ParserState.IGNORE); | |
4448ebe3 | 48 | }; |
ecd7e69f DI |
49 | escapedStateHandler['c'] = (parser, terminal) => { |
50 | // ESC c Full Reset (RIS). | |
51 | terminal.reset(); | |
4448ebe3 | 52 | }; |
ecd7e69f DI |
53 | escapedStateHandler['E'] = (parser, terminal) => { |
54 | // ESC E Next Line ( NEL is 0x85). | |
55 | terminal.x = 0; | |
56 | terminal.index(); | |
57 | parser.setState(ParserState.NORMAL); | |
4448ebe3 | 58 | }; |
ecd7e69f DI |
59 | escapedStateHandler['D'] = (parser, terminal) => { |
60 | // ESC D Index ( IND is 0x84). | |
61 | terminal.index(); | |
62 | parser.setState(ParserState.NORMAL); | |
63 | }; | |
64 | escapedStateHandler['M'] = (parser, terminal) => { | |
65 | // ESC M Reverse Index ( RI is 0x8d). | |
66 | terminal.reverseIndex(); | |
67 | parser.setState(ParserState.NORMAL); | |
68 | }; | |
69 | escapedStateHandler['%'] = (parser, terminal) => { | |
70 | // ESC % Select default/utf-8 character set. | |
71 | // @ = default, G = utf-8 | |
72 | terminal.setgLevel(0); | |
73 | terminal.setgCharset(0, CHARSETS.US); | |
74 | parser.setState(ParserState.NORMAL); | |
75 | parser.skipNextChar(); | |
76 | }; | |
77 | ||
7572dd5f DI |
78 | const csiParamStateHandler: {[key: string]: (parser: Parser) => void} = {}; |
79 | csiParamStateHandler['?'] = (parser) => parser.setPrefix('?'); | |
80 | csiParamStateHandler['>'] = (parser) => parser.setPrefix('>'); | |
81 | csiParamStateHandler['!'] = (parser) => parser.setPrefix('!'); | |
82 | csiParamStateHandler['0'] = (parser) => parser.setParam(parser.getParam() * 10); | |
83 | csiParamStateHandler['1'] = (parser) => parser.setParam(parser.getParam() * 10 + 1); | |
84 | csiParamStateHandler['2'] = (parser) => parser.setParam(parser.getParam() * 10 + 2); | |
85 | csiParamStateHandler['3'] = (parser) => parser.setParam(parser.getParam() * 10 + 3); | |
86 | csiParamStateHandler['4'] = (parser) => parser.setParam(parser.getParam() * 10 + 4); | |
87 | csiParamStateHandler['5'] = (parser) => parser.setParam(parser.getParam() * 10 + 5); | |
88 | csiParamStateHandler['6'] = (parser) => parser.setParam(parser.getParam() * 10 + 6); | |
89 | csiParamStateHandler['7'] = (parser) => parser.setParam(parser.getParam() * 10 + 7); | |
90 | csiParamStateHandler['8'] = (parser) => parser.setParam(parser.getParam() * 10 + 8); | |
91 | csiParamStateHandler['9'] = (parser) => parser.setParam(parser.getParam() * 10 + 9); | |
92 | csiParamStateHandler['$'] = (parser) => parser.setPostfix('$'); | |
93 | csiParamStateHandler['"'] = (parser) => parser.setPostfix('"'); | |
94 | csiParamStateHandler[' '] = (parser) => parser.setPostfix(' '); | |
95 | csiParamStateHandler['\''] = (parser) => parser.setPostfix('\''); | |
c7fa2d55 | 96 | csiParamStateHandler[';'] = (parser) => parser.finalizeParam(); |
9942477b | 97 | |
0bd469f5 | 98 | const csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[], prefix: string, postfix: string) => void} = {}; |
f4846aa1 DI |
99 | csiStateHandler['@'] = (handler, params, prefix) => handler.insertChars(params); |
100 | csiStateHandler['A'] = (handler, params, prefix) => handler.cursorUp(params); | |
101 | csiStateHandler['B'] = (handler, params, prefix) => handler.cursorDown(params); | |
102 | csiStateHandler['C'] = (handler, params, prefix) => handler.cursorForward(params); | |
103 | csiStateHandler['D'] = (handler, params, prefix) => handler.cursorBackward(params); | |
104 | csiStateHandler['E'] = (handler, params, prefix) => handler.cursorNextLine(params); | |
105 | csiStateHandler['F'] = (handler, params, prefix) => handler.cursorPrecedingLine(params); | |
106 | csiStateHandler['G'] = (handler, params, prefix) => handler.cursorCharAbsolute(params); | |
107 | csiStateHandler['H'] = (handler, params, prefix) => handler.cursorPosition(params); | |
108 | csiStateHandler['I'] = (handler, params, prefix) => handler.cursorForwardTab(params); | |
109 | csiStateHandler['J'] = (handler, params, prefix) => handler.eraseInDisplay(params); | |
110 | csiStateHandler['K'] = (handler, params, prefix) => handler.eraseInLine(params); | |
111 | csiStateHandler['L'] = (handler, params, prefix) => handler.insertLines(params); | |
112 | csiStateHandler['M'] = (handler, params, prefix) => handler.deleteLines(params); | |
113 | csiStateHandler['P'] = (handler, params, prefix) => handler.deleteChars(params); | |
114 | csiStateHandler['S'] = (handler, params, prefix) => handler.scrollUp(params); | |
115 | csiStateHandler['T'] = (handler, params, prefix) => { | |
116 | if (params.length < 2 && !prefix) { | |
117 | handler.scrollDown(params); | |
118 | } | |
119 | }; | |
120 | csiStateHandler['X'] = (handler, params, prefix) => handler.eraseChars(params); | |
121 | csiStateHandler['Z'] = (handler, params, prefix) => handler.cursorBackwardTab(params); | |
122 | csiStateHandler['`'] = (handler, params, prefix) => handler.charPosAbsolute(params); | |
123 | csiStateHandler['a'] = (handler, params, prefix) => handler.HPositionRelative(params); | |
124 | csiStateHandler['b'] = (handler, params, prefix) => handler.repeatPrecedingCharacter(params); | |
125 | csiStateHandler['c'] = (handler, params, prefix) => handler.sendDeviceAttributes(params); | |
126 | csiStateHandler['d'] = (handler, params, prefix) => handler.linePosAbsolute(params); | |
127 | csiStateHandler['e'] = (handler, params, prefix) => handler.VPositionRelative(params); | |
128 | csiStateHandler['f'] = (handler, params, prefix) => handler.HVPosition(params); | |
129 | csiStateHandler['g'] = (handler, params, prefix) => handler.tabClear(params); | |
130 | csiStateHandler['h'] = (handler, params, prefix) => handler.setMode(params); | |
131 | csiStateHandler['l'] = (handler, params, prefix) => handler.resetMode(params); | |
132 | csiStateHandler['m'] = (handler, params, prefix) => handler.charAttributes(params); | |
133 | csiStateHandler['n'] = (handler, params, prefix) => handler.deviceStatus(params); | |
134 | csiStateHandler['p'] = (handler, params, prefix) => { | |
135 | switch (prefix) { | |
136 | case '!': handler.softReset(params); break; | |
137 | } | |
138 | }; | |
0bd469f5 DI |
139 | csiStateHandler['q'] = (handler, params, prefix, postfix) => { |
140 | if (postfix === ' ') { | |
141 | handler.setCursorStyle(params); | |
142 | } | |
143 | }; | |
9b662080 DI |
144 | csiStateHandler['r'] = (handler, params) => handler.setScrollRegion(params); |
145 | csiStateHandler['s'] = (handler, params) => handler.saveCursor(params); | |
146 | csiStateHandler['u'] = (handler, params) => handler.restoreCursor(params); | |
a31921ae | 147 | |
4448ebe3 DI |
148 | // TODO: Many codes/charsets appear to not be supported |
149 | // See: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html | |
150 | const charsetMap = { | |
151 | '0': CHARSETS.SCLD, | |
152 | 'A': CHARSETS.UK, // United Kingdom | |
153 | 'B': CHARSETS.US, // United States (USASCII) | |
154 | '4': CHARSETS.Dutch, | |
155 | 'C': CHARSETS.Finnish, | |
156 | '5': CHARSETS.Finnish, | |
157 | 'f': CHARSETS.French, | |
158 | 'Q': CHARSETS.FrenchCanadian, | |
159 | 'K': CHARSETS.German, | |
160 | 'Y': CHARSETS.Italian, | |
161 | 'E': CHARSETS.NorwegianDanish, | |
162 | '6': CHARSETS.NorwegianDanish, | |
163 | 'Z': CHARSETS.Spanish, | |
164 | 'H': CHARSETS.Swedish, | |
165 | '7': CHARSETS.Swedish, | |
166 | '=': CHARSETS.Swiss, | |
167 | '/': CHARSETS.ISOLatin // ISOLatin is actually /A | |
168 | }; | |
169 | ||
a31921ae DI |
170 | enum ParserState { |
171 | NORMAL = 0, | |
172 | ESCAPED = 1, | |
c7fa2d55 DI |
173 | CSI_PARAM = 2, |
174 | CSI = 3, | |
175 | OSC = 4, | |
176 | CHARSET = 5, | |
177 | DCS = 6, | |
178 | IGNORE = 7 | |
a31921ae DI |
179 | } |
180 | ||
ef60e50e DI |
181 | /** |
182 | * The terminal's parser, all input into the terminal goes through the parser | |
183 | * which parses and defers the actual input handling the the IInputHandler | |
184 | * specified in the constructor. | |
185 | */ | |
a31921ae | 186 | export class Parser { |
ecd7e69f DI |
187 | private _state: ParserState; |
188 | private _position: number; | |
a31921ae DI |
189 | |
190 | // TODO: Remove terminal when handler can do everything | |
191 | constructor( | |
192 | private _inputHandler: IInputHandler, | |
193 | private _terminal: any | |
194 | ) { | |
ecd7e69f | 195 | this._state = ParserState.NORMAL; |
a31921ae DI |
196 | } |
197 | ||
ef60e50e DI |
198 | /** |
199 | * Parse and handle data. | |
200 | * | |
201 | * @param data The data to parse. | |
202 | */ | |
a31921ae | 203 | public parse(data: string) { |
ecd7e69f | 204 | let l = data.length, j, cs, ch, code, low; |
a31921ae | 205 | |
ecd7e69f | 206 | this._position = 0; |
a31921ae DI |
207 | // apply leftover surrogate high from last write |
208 | if (this._terminal.surrogate_high) { | |
209 | data = this._terminal.surrogate_high + data; | |
210 | this._terminal.surrogate_high = ''; | |
211 | } | |
212 | ||
ecd7e69f DI |
213 | for (; this._position < l; this._position++) { |
214 | ch = data[this._position]; | |
a31921ae DI |
215 | |
216 | // FIXME: higher chars than 0xa0 are not allowed in escape sequences | |
217 | // --> maybe move to default | |
ecd7e69f | 218 | code = data.charCodeAt(this._position); |
a31921ae DI |
219 | if (0xD800 <= code && code <= 0xDBFF) { |
220 | // we got a surrogate high | |
221 | // get surrogate low (next 2 bytes) | |
ecd7e69f | 222 | low = data.charCodeAt(this._position + 1); |
a31921ae DI |
223 | if (isNaN(low)) { |
224 | // end of data stream, save surrogate high | |
225 | this._terminal.surrogate_high = ch; | |
226 | continue; | |
227 | } | |
228 | code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; | |
ecd7e69f | 229 | ch += data.charAt(this._position + 1); |
a31921ae DI |
230 | } |
231 | // surrogate low - already handled above | |
232 | if (0xDC00 <= code && code <= 0xDFFF) | |
233 | continue; | |
234 | ||
ecd7e69f | 235 | switch (this._state) { |
a31921ae | 236 | case ParserState.NORMAL: |
fa3484cd DI |
237 | if (ch in normalStateHandler) { |
238 | normalStateHandler[ch](this, this._inputHandler); | |
239 | } else { | |
240 | this._inputHandler.addChar(ch, code); | |
a31921ae DI |
241 | } |
242 | break; | |
243 | case ParserState.ESCAPED: | |
ecd7e69f DI |
244 | if (ch in escapedStateHandler) { |
245 | escapedStateHandler[ch](this, this._terminal); | |
246 | // Skip switch as it was just handled | |
247 | break; | |
248 | } | |
a31921ae | 249 | switch (ch) { |
a31921ae DI |
250 | |
251 | // ESC (,),*,+,-,. Designate G0-G2 Character Set. | |
252 | case '(': // <-- this seems to get all the attention | |
253 | case ')': | |
254 | case '*': | |
255 | case '+': | |
256 | case '-': | |
257 | case '.': | |
258 | switch (ch) { | |
259 | case '(': | |
991b2843 | 260 | this._terminal.gcharset = 0; |
a31921ae DI |
261 | break; |
262 | case ')': | |
991b2843 | 263 | this._terminal.gcharset = 1; |
a31921ae DI |
264 | break; |
265 | case '*': | |
991b2843 | 266 | this._terminal.gcharset = 2; |
a31921ae DI |
267 | break; |
268 | case '+': | |
991b2843 | 269 | this._terminal.gcharset = 3; |
a31921ae DI |
270 | break; |
271 | case '-': | |
991b2843 | 272 | this._terminal.gcharset = 1; |
a31921ae DI |
273 | break; |
274 | case '.': | |
991b2843 | 275 | this._terminal.gcharset = 2; |
a31921ae DI |
276 | break; |
277 | } | |
ecd7e69f | 278 | this._state = ParserState.CHARSET; |
a31921ae DI |
279 | break; |
280 | ||
281 | // Designate G3 Character Set (VT300). | |
282 | // A = ISO Latin-1 Supplemental. | |
283 | // Not implemented. | |
284 | case '/': | |
991b2843 | 285 | this._terminal.gcharset = 3; |
ecd7e69f DI |
286 | this._state = ParserState.CHARSET; |
287 | this._position--; | |
a31921ae DI |
288 | break; |
289 | ||
290 | // ESC N | |
291 | // Single Shift Select of G2 Character Set | |
292 | // ( SS2 is 0x8e). This affects next character only. | |
293 | case 'N': | |
294 | break; | |
295 | // ESC O | |
296 | // Single Shift Select of G3 Character Set | |
297 | // ( SS3 is 0x8f). This affects next character only. | |
298 | case 'O': | |
299 | break; | |
300 | // ESC n | |
301 | // Invoke the G2 Character Set as GL (LS2). | |
302 | case 'n': | |
991b2843 | 303 | this._terminal.setgLevel(2); |
a31921ae DI |
304 | break; |
305 | // ESC o | |
306 | // Invoke the G3 Character Set as GL (LS3). | |
307 | case 'o': | |
991b2843 | 308 | this._terminal.setgLevel(3); |
a31921ae DI |
309 | break; |
310 | // ESC | | |
311 | // Invoke the G3 Character Set as GR (LS3R). | |
312 | case '|': | |
991b2843 | 313 | this._terminal.setgLevel(3); |
a31921ae DI |
314 | break; |
315 | // ESC } | |
316 | // Invoke the G2 Character Set as GR (LS2R). | |
317 | case '}': | |
991b2843 | 318 | this._terminal.setgLevel(2); |
a31921ae DI |
319 | break; |
320 | // ESC ~ | |
321 | // Invoke the G1 Character Set as GR (LS1R). | |
322 | case '~': | |
991b2843 | 323 | this._terminal.setgLevel(1); |
a31921ae DI |
324 | break; |
325 | ||
326 | // ESC 7 Save Cursor (DECSC). | |
327 | case '7': | |
f4846aa1 | 328 | this._inputHandler.saveCursor(); |
ecd7e69f | 329 | this._state = ParserState.NORMAL; |
a31921ae DI |
330 | break; |
331 | ||
332 | // ESC 8 Restore Cursor (DECRC). | |
333 | case '8': | |
f4846aa1 | 334 | this._inputHandler.restoreCursor(); |
ecd7e69f | 335 | this._state = ParserState.NORMAL; |
a31921ae DI |
336 | break; |
337 | ||
338 | // ESC # 3 DEC line height/width | |
339 | case '#': | |
ecd7e69f DI |
340 | this._state = ParserState.NORMAL; |
341 | this._position++; | |
a31921ae DI |
342 | break; |
343 | ||
344 | // ESC H Tab Set (HTS is 0x88). | |
345 | case 'H': | |
991b2843 | 346 | this._terminal.tabSet(); |
ecd7e69f | 347 | this._state = ParserState.NORMAL; |
a31921ae DI |
348 | break; |
349 | ||
350 | // ESC = Application Keypad (DECKPAM). | |
351 | case '=': | |
991b2843 DI |
352 | this._terminal.log('Serial port requested application keypad.'); |
353 | this._terminal.applicationKeypad = true; | |
354 | this._terminal.viewport.syncScrollArea(); | |
ecd7e69f | 355 | this._state = ParserState.NORMAL; |
a31921ae DI |
356 | break; |
357 | ||
358 | // ESC > Normal Keypad (DECKPNM). | |
359 | case '>': | |
991b2843 DI |
360 | this._terminal.log('Switching back to normal keypad.'); |
361 | this._terminal.applicationKeypad = false; | |
362 | this._terminal.viewport.syncScrollArea(); | |
ecd7e69f | 363 | this._state = ParserState.NORMAL; |
a31921ae DI |
364 | break; |
365 | ||
366 | default: | |
ecd7e69f | 367 | this._state = ParserState.NORMAL; |
991b2843 | 368 | this._terminal.error('Unknown ESC control: %s.', ch); |
a31921ae DI |
369 | break; |
370 | } | |
371 | break; | |
372 | ||
373 | case ParserState.CHARSET: | |
4448ebe3 DI |
374 | if (ch in charsetMap) { |
375 | cs = charsetMap[ch]; | |
376 | if (ch === '/') { // ISOLatin is actually /A | |
377 | this.skipNextChar(); | |
378 | } | |
379 | } else { | |
380 | cs = CHARSETS.US; // Default | |
a31921ae | 381 | } |
991b2843 DI |
382 | this._terminal.setgCharset(this._terminal.gcharset, cs); |
383 | this._terminal.gcharset = null; | |
ecd7e69f | 384 | this._state = ParserState.NORMAL; |
a31921ae DI |
385 | break; |
386 | ||
387 | case ParserState.OSC: | |
388 | // OSC Ps ; Pt ST | |
389 | // OSC Ps ; Pt BEL | |
390 | // Set Text Parameters. | |
391 | if (ch === C0.ESC || ch === C0.BEL) { | |
ecd7e69f | 392 | if (ch === C0.ESC) this._position++; |
a31921ae | 393 | |
991b2843 | 394 | this._terminal.params.push(this._terminal.currentParam); |
a31921ae | 395 | |
991b2843 | 396 | switch (this._terminal.params[0]) { |
a31921ae DI |
397 | case 0: |
398 | case 1: | |
399 | case 2: | |
991b2843 DI |
400 | if (this._terminal.params[1]) { |
401 | this._terminal.title = this._terminal.params[1]; | |
402 | this._terminal.handleTitle(this._terminal.title); | |
a31921ae DI |
403 | } |
404 | break; | |
405 | case 3: | |
406 | // set X property | |
407 | break; | |
408 | case 4: | |
409 | case 5: | |
410 | // change dynamic colors | |
411 | break; | |
412 | case 10: | |
413 | case 11: | |
414 | case 12: | |
415 | case 13: | |
416 | case 14: | |
417 | case 15: | |
418 | case 16: | |
419 | case 17: | |
420 | case 18: | |
421 | case 19: | |
422 | // change dynamic ui colors | |
423 | break; | |
424 | case 46: | |
425 | // change log file | |
426 | break; | |
427 | case 50: | |
428 | // dynamic font | |
429 | break; | |
430 | case 51: | |
431 | // emacs shell | |
432 | break; | |
433 | case 52: | |
434 | // manipulate selection data | |
435 | break; | |
436 | case 104: | |
437 | case 105: | |
438 | case 110: | |
439 | case 111: | |
440 | case 112: | |
441 | case 113: | |
442 | case 114: | |
443 | case 115: | |
444 | case 116: | |
445 | case 117: | |
446 | case 118: | |
447 | // reset colors | |
448 | break; | |
449 | } | |
450 | ||
991b2843 DI |
451 | this._terminal.params = []; |
452 | this._terminal.currentParam = 0; | |
ecd7e69f | 453 | this._state = ParserState.NORMAL; |
a31921ae | 454 | } else { |
991b2843 | 455 | if (!this._terminal.params.length) { |
a31921ae | 456 | if (ch >= '0' && ch <= '9') { |
991b2843 DI |
457 | this._terminal.currentParam = |
458 | this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48; | |
a31921ae | 459 | } else if (ch === ';') { |
991b2843 DI |
460 | this._terminal.params.push(this._terminal.currentParam); |
461 | this._terminal.currentParam = ''; | |
a31921ae DI |
462 | } |
463 | } else { | |
991b2843 | 464 | this._terminal.currentParam += ch; |
a31921ae DI |
465 | } |
466 | } | |
467 | break; | |
468 | ||
c7fa2d55 | 469 | case ParserState.CSI_PARAM: |
9942477b | 470 | if (ch in csiParamStateHandler) { |
7572dd5f | 471 | csiParamStateHandler[ch](this); |
a31921ae DI |
472 | break; |
473 | } | |
c7fa2d55 DI |
474 | this.finalizeParam(); |
475 | // Fall through the CSI as this character should be the CSI code. | |
ecd7e69f | 476 | this._state = ParserState.CSI; |
a31921ae | 477 | |
c7fa2d55 | 478 | case ParserState.CSI: |
9942477b | 479 | if (ch in csiStateHandler) { |
0bd469f5 | 480 | csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix); |
f4846aa1 DI |
481 | } else { |
482 | this._terminal.error('Unknown CSI code: %s.', ch); | |
a31921ae DI |
483 | } |
484 | ||
ecd7e69f | 485 | this._state = ParserState.NORMAL; |
991b2843 DI |
486 | this._terminal.prefix = ''; |
487 | this._terminal.postfix = ''; | |
a31921ae DI |
488 | break; |
489 | ||
490 | case ParserState.DCS: | |
491 | if (ch === C0.ESC || ch === C0.BEL) { | |
ecd7e69f | 492 | if (ch === C0.ESC) this._position++; |
a31921ae | 493 | |
991b2843 | 494 | switch (this._terminal.prefix) { |
a31921ae DI |
495 | // User-Defined Keys (DECUDK). |
496 | case '': | |
497 | break; | |
498 | ||
499 | // Request Status String (DECRQSS). | |
500 | // test: echo -e '\eP$q"p\e\\' | |
501 | case '$q': | |
991b2843 | 502 | let pt = this._terminal.currentParam |
a31921ae DI |
503 | , valid = false; |
504 | ||
505 | switch (pt) { | |
506 | // DECSCA | |
507 | case '"q': | |
508 | pt = '0"q'; | |
509 | break; | |
510 | ||
511 | // DECSCL | |
512 | case '"p': | |
513 | pt = '61"p'; | |
514 | break; | |
515 | ||
516 | // DECSTBM | |
517 | case 'r': | |
518 | pt = '' | |
991b2843 | 519 | + (this._terminal.scrollTop + 1) |
a31921ae | 520 | + ';' |
991b2843 | 521 | + (this._terminal.scrollBottom + 1) |
a31921ae DI |
522 | + 'r'; |
523 | break; | |
524 | ||
525 | // SGR | |
526 | case 'm': | |
527 | pt = '0m'; | |
528 | break; | |
529 | ||
530 | default: | |
991b2843 | 531 | this._terminal.error('Unknown DCS Pt: %s.', pt); |
a31921ae DI |
532 | pt = ''; |
533 | break; | |
534 | } | |
535 | ||
991b2843 | 536 | this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\'); |
a31921ae DI |
537 | break; |
538 | ||
539 | // Set Termcap/Terminfo Data (xterm, experimental). | |
540 | case '+p': | |
541 | break; | |
542 | ||
543 | // Request Termcap/Terminfo String (xterm, experimental) | |
544 | // Regular xterm does not even respond to this sequence. | |
545 | // This can cause a small glitch in vim. | |
546 | // test: echo -ne '\eP+q6b64\e\\' | |
547 | case '+q': | |
991b2843 DI |
548 | // TODO: Don't declare pt twice |
549 | /*let*/ pt = this._terminal.currentParam | |
a31921ae DI |
550 | , valid = false; |
551 | ||
991b2843 | 552 | this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\'); |
a31921ae DI |
553 | break; |
554 | ||
555 | default: | |
991b2843 | 556 | this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix); |
a31921ae DI |
557 | break; |
558 | } | |
559 | ||
991b2843 DI |
560 | this._terminal.currentParam = 0; |
561 | this._terminal.prefix = ''; | |
ecd7e69f | 562 | this._state = ParserState.NORMAL; |
991b2843 DI |
563 | } else if (!this._terminal.currentParam) { |
564 | if (!this._terminal.prefix && ch !== '$' && ch !== '+') { | |
565 | this._terminal.currentParam = ch; | |
566 | } else if (this._terminal.prefix.length === 2) { | |
567 | this._terminal.currentParam = ch; | |
a31921ae | 568 | } else { |
991b2843 | 569 | this._terminal.prefix += ch; |
a31921ae DI |
570 | } |
571 | } else { | |
991b2843 | 572 | this._terminal.currentParam += ch; |
a31921ae DI |
573 | } |
574 | break; | |
575 | ||
576 | case ParserState.IGNORE: | |
577 | // For PM and APC. | |
578 | if (ch === C0.ESC || ch === C0.BEL) { | |
ecd7e69f DI |
579 | if (ch === C0.ESC) this._position++; |
580 | this._state = ParserState.NORMAL; | |
a31921ae DI |
581 | } |
582 | break; | |
583 | } | |
584 | } | |
585 | } | |
672dc1a2 | 586 | |
ef60e50e DI |
587 | /** |
588 | * Set the parser's current parsing state. | |
589 | * | |
590 | * @param state The new state. | |
591 | */ | |
fa3484cd | 592 | public setState(state: ParserState): void { |
ecd7e69f | 593 | this._state = state; |
fa3484cd DI |
594 | } |
595 | ||
ef60e50e DI |
596 | /** |
597 | * Sets the parsier's current prefix. CSI codes can have prefixes of '?', '>' | |
598 | * or '!'. | |
599 | * | |
600 | * @param prefix The prefix. | |
601 | */ | |
635a4d76 | 602 | public setPrefix(prefix: string): void { |
672dc1a2 DI |
603 | this._terminal.prefix = prefix; |
604 | } | |
605 | ||
ef60e50e DI |
606 | /** |
607 | * Sets the parsier's current prefix. CSI codes can have postfixes of '$', | |
608 | * '"', ' ', '\''. | |
609 | * | |
610 | * @param postfix The postfix. | |
611 | */ | |
612 | public setPostfix(postfix: string): void { | |
613 | this._terminal.postfix = postfix; | |
614 | } | |
615 | ||
616 | /** | |
617 | * Sets the parser's current parameter. | |
618 | * | |
619 | * @param param the parameter. | |
620 | */ | |
672dc1a2 DI |
621 | public setParam(param: number) { |
622 | this._terminal.currentParam = param; | |
623 | } | |
624 | ||
ef60e50e DI |
625 | /** |
626 | * Gets the parser's current parameter. | |
627 | */ | |
672dc1a2 DI |
628 | public getParam(): number { |
629 | return this._terminal.currentParam; | |
630 | } | |
635a4d76 | 631 | |
ef60e50e DI |
632 | /** |
633 | * Finalizes the parser's current parameter, adding it to the list of | |
634 | * parameters and setting the new current parameter to 0. | |
635 | */ | |
c7fa2d55 DI |
636 | public finalizeParam(): void { |
637 | this._terminal.params.push(this._terminal.currentParam); | |
638 | this._terminal.currentParam = 0; | |
639 | } | |
640 | ||
ef60e50e DI |
641 | /** |
642 | * Tell the parser to skip the next character. | |
643 | */ | |
ecd7e69f DI |
644 | public skipNextChar(): void { |
645 | this._position++; | |
646 | } | |
647 | ||
ef60e50e DI |
648 | /** |
649 | * Tell the parser to repeat parsing the current character (for example if it | |
650 | * needs parsing using a different state. | |
651 | */ | |
ecd7e69f DI |
652 | // public repeatChar(): void { |
653 | // this._position--; | |
654 | // } | |
a31921ae | 655 | } |