]>
Commit | Line | Data |
---|---|---|
659fb90c DI |
1 | import { C0 } from './EscapeSequences'; |
2 | import { IInputHandler } from './Interfaces'; | |
1848fff4 | 3 | import { CHARSETS } from './Charsets'; |
659fb90c | 4 | |
a31921ae | 5 | const normalStateHandler: {[key: string]: (handler: IInputHandler) => void} = {}; |
659fb90c DI |
6 | normalStateHandler[C0.BEL] = (handler) => handler.bell(); |
7 | normalStateHandler[C0.LF] = (handler) => handler.lineFeed(); | |
8 | normalStateHandler[C0.VT] = normalStateHandler[C0.LF]; | |
9 | normalStateHandler[C0.FF] = normalStateHandler[C0.LF]; | |
10 | normalStateHandler[C0.CR] = (handler) => handler.carriageReturn(); | |
665a11ac | 11 | normalStateHandler[C0.BS] = (handler) => handler.backspace(); |
659fb90c DI |
12 | normalStateHandler[C0.HT] = (handler) => handler.tab(); |
13 | normalStateHandler[C0.SO] = (handler) => handler.shiftOut(); | |
14 | normalStateHandler[C0.SI] = (handler) => handler.shiftIn(); | |
672dc1a2 DI |
15 | // TODO: Add ESC and Default cases to normalStateHandler |
16 | ||
7572dd5f DI |
17 | const csiParamStateHandler: {[key: string]: (parser: Parser) => void} = {}; |
18 | csiParamStateHandler['?'] = (parser) => parser.setPrefix('?'); | |
19 | csiParamStateHandler['>'] = (parser) => parser.setPrefix('>'); | |
20 | csiParamStateHandler['!'] = (parser) => parser.setPrefix('!'); | |
21 | csiParamStateHandler['0'] = (parser) => parser.setParam(parser.getParam() * 10); | |
22 | csiParamStateHandler['1'] = (parser) => parser.setParam(parser.getParam() * 10 + 1); | |
23 | csiParamStateHandler['2'] = (parser) => parser.setParam(parser.getParam() * 10 + 2); | |
24 | csiParamStateHandler['3'] = (parser) => parser.setParam(parser.getParam() * 10 + 3); | |
25 | csiParamStateHandler['4'] = (parser) => parser.setParam(parser.getParam() * 10 + 4); | |
26 | csiParamStateHandler['5'] = (parser) => parser.setParam(parser.getParam() * 10 + 5); | |
27 | csiParamStateHandler['6'] = (parser) => parser.setParam(parser.getParam() * 10 + 6); | |
28 | csiParamStateHandler['7'] = (parser) => parser.setParam(parser.getParam() * 10 + 7); | |
29 | csiParamStateHandler['8'] = (parser) => parser.setParam(parser.getParam() * 10 + 8); | |
30 | csiParamStateHandler['9'] = (parser) => parser.setParam(parser.getParam() * 10 + 9); | |
31 | csiParamStateHandler['$'] = (parser) => parser.setPostfix('$'); | |
32 | csiParamStateHandler['"'] = (parser) => parser.setPostfix('"'); | |
33 | csiParamStateHandler[' '] = (parser) => parser.setPostfix(' '); | |
34 | csiParamStateHandler['\''] = (parser) => parser.setPostfix('\''); | |
c7fa2d55 | 35 | csiParamStateHandler[';'] = (parser) => parser.finalizeParam(); |
9942477b | 36 | |
f4846aa1 DI |
37 | const csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[], prefix: string) => void} = {}; |
38 | csiStateHandler['@'] = (handler, params, prefix) => handler.insertChars(params); | |
39 | csiStateHandler['A'] = (handler, params, prefix) => handler.cursorUp(params); | |
40 | csiStateHandler['B'] = (handler, params, prefix) => handler.cursorDown(params); | |
41 | csiStateHandler['C'] = (handler, params, prefix) => handler.cursorForward(params); | |
42 | csiStateHandler['D'] = (handler, params, prefix) => handler.cursorBackward(params); | |
43 | csiStateHandler['E'] = (handler, params, prefix) => handler.cursorNextLine(params); | |
44 | csiStateHandler['F'] = (handler, params, prefix) => handler.cursorPrecedingLine(params); | |
45 | csiStateHandler['G'] = (handler, params, prefix) => handler.cursorCharAbsolute(params); | |
46 | csiStateHandler['H'] = (handler, params, prefix) => handler.cursorPosition(params); | |
47 | csiStateHandler['I'] = (handler, params, prefix) => handler.cursorForwardTab(params); | |
48 | csiStateHandler['J'] = (handler, params, prefix) => handler.eraseInDisplay(params); | |
49 | csiStateHandler['K'] = (handler, params, prefix) => handler.eraseInLine(params); | |
50 | csiStateHandler['L'] = (handler, params, prefix) => handler.insertLines(params); | |
51 | csiStateHandler['M'] = (handler, params, prefix) => handler.deleteLines(params); | |
52 | csiStateHandler['P'] = (handler, params, prefix) => handler.deleteChars(params); | |
53 | csiStateHandler['S'] = (handler, params, prefix) => handler.scrollUp(params); | |
54 | csiStateHandler['T'] = (handler, params, prefix) => { | |
55 | if (params.length < 2 && !prefix) { | |
56 | handler.scrollDown(params); | |
57 | } | |
58 | }; | |
59 | csiStateHandler['X'] = (handler, params, prefix) => handler.eraseChars(params); | |
60 | csiStateHandler['Z'] = (handler, params, prefix) => handler.cursorBackwardTab(params); | |
61 | csiStateHandler['`'] = (handler, params, prefix) => handler.charPosAbsolute(params); | |
62 | csiStateHandler['a'] = (handler, params, prefix) => handler.HPositionRelative(params); | |
63 | csiStateHandler['b'] = (handler, params, prefix) => handler.repeatPrecedingCharacter(params); | |
64 | csiStateHandler['c'] = (handler, params, prefix) => handler.sendDeviceAttributes(params); | |
65 | csiStateHandler['d'] = (handler, params, prefix) => handler.linePosAbsolute(params); | |
66 | csiStateHandler['e'] = (handler, params, prefix) => handler.VPositionRelative(params); | |
67 | csiStateHandler['f'] = (handler, params, prefix) => handler.HVPosition(params); | |
68 | csiStateHandler['g'] = (handler, params, prefix) => handler.tabClear(params); | |
69 | csiStateHandler['h'] = (handler, params, prefix) => handler.setMode(params); | |
70 | csiStateHandler['l'] = (handler, params, prefix) => handler.resetMode(params); | |
71 | csiStateHandler['m'] = (handler, params, prefix) => handler.charAttributes(params); | |
72 | csiStateHandler['n'] = (handler, params, prefix) => handler.deviceStatus(params); | |
73 | csiStateHandler['p'] = (handler, params, prefix) => { | |
74 | switch (prefix) { | |
75 | case '!': handler.softReset(params); break; | |
76 | } | |
77 | }; | |
9b662080 DI |
78 | csiStateHandler['r'] = (handler, params) => handler.setScrollRegion(params); |
79 | csiStateHandler['s'] = (handler, params) => handler.saveCursor(params); | |
80 | csiStateHandler['u'] = (handler, params) => handler.restoreCursor(params); | |
a31921ae DI |
81 | |
82 | enum ParserState { | |
83 | NORMAL = 0, | |
84 | ESCAPED = 1, | |
c7fa2d55 DI |
85 | CSI_PARAM = 2, |
86 | CSI = 3, | |
87 | OSC = 4, | |
88 | CHARSET = 5, | |
89 | DCS = 6, | |
90 | IGNORE = 7 | |
a31921ae DI |
91 | } |
92 | ||
93 | export class Parser { | |
94 | private state: ParserState; | |
95 | ||
96 | // TODO: Remove terminal when handler can do everything | |
97 | constructor( | |
98 | private _inputHandler: IInputHandler, | |
99 | private _terminal: any | |
100 | ) { | |
101 | this.state = ParserState.NORMAL; | |
102 | } | |
103 | ||
104 | public parse(data: string) { | |
105 | let l = data.length, i = 0, j, cs, ch, code, low, ch_width, row; | |
106 | ||
107 | // apply leftover surrogate high from last write | |
108 | if (this._terminal.surrogate_high) { | |
109 | data = this._terminal.surrogate_high + data; | |
110 | this._terminal.surrogate_high = ''; | |
111 | } | |
112 | ||
113 | for (; i < l; i++) { | |
114 | ch = data[i]; | |
115 | ||
116 | // FIXME: higher chars than 0xa0 are not allowed in escape sequences | |
117 | // --> maybe move to default | |
118 | code = data.charCodeAt(i); | |
119 | if (0xD800 <= code && code <= 0xDBFF) { | |
120 | // we got a surrogate high | |
121 | // get surrogate low (next 2 bytes) | |
991b2843 | 122 | low = data.charCodeAt(i + 1); |
a31921ae DI |
123 | if (isNaN(low)) { |
124 | // end of data stream, save surrogate high | |
125 | this._terminal.surrogate_high = ch; | |
126 | continue; | |
127 | } | |
128 | code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; | |
991b2843 | 129 | ch += data.charAt(i + 1); |
a31921ae DI |
130 | } |
131 | // surrogate low - already handled above | |
132 | if (0xDC00 <= code && code <= 0xDFFF) | |
133 | continue; | |
134 | ||
135 | if (this.state === ParserState.NORMAL) { | |
136 | if (ch in normalStateHandler) { | |
137 | normalStateHandler[ch](this._inputHandler); | |
138 | // Skip switch statement (eventually everything will be handled this way | |
139 | continue; | |
140 | } | |
141 | } | |
142 | ||
143 | switch (this.state) { | |
144 | case ParserState.NORMAL: | |
145 | switch (ch) { | |
146 | case C0.ESC: | |
147 | this.state = ParserState.ESCAPED; | |
148 | break; | |
149 | ||
150 | default: | |
151 | // ' ' | |
152 | // calculate print space | |
153 | // expensive call, therefore we save width in line buffer | |
154 | ch_width = wcwidth(code); | |
155 | ||
156 | if (ch >= ' ') { | |
991b2843 DI |
157 | if (this._terminal.charset && this._terminal.charset[ch]) { |
158 | ch = this._terminal.charset[ch]; | |
a31921ae DI |
159 | } |
160 | ||
991b2843 | 161 | row = this._terminal.y + this._terminal.ybase; |
a31921ae DI |
162 | |
163 | // insert combining char in last cell | |
164 | // FIXME: needs handling after cursor jumps | |
991b2843 | 165 | if (!ch_width && this._terminal.x) { |
a31921ae | 166 | // dont overflow left |
991b2843 DI |
167 | if (this._terminal.lines.get(row)[this._terminal.x - 1]) { |
168 | if (!this._terminal.lines.get(row)[this._terminal.x - 1][2]) { | |
a31921ae DI |
169 | |
170 | // found empty cell after fullwidth, need to go 2 cells back | |
991b2843 DI |
171 | if (this._terminal.lines.get(row)[this._terminal.x - 2]) |
172 | this._terminal.lines.get(row)[this._terminal.x - 2][1] += ch; | |
a31921ae DI |
173 | |
174 | } else { | |
991b2843 | 175 | this._terminal.lines.get(row)[this._terminal.x - 1][1] += ch; |
a31921ae | 176 | } |
991b2843 | 177 | this._terminal.updateRange(this._terminal.y); |
a31921ae DI |
178 | } |
179 | break; | |
180 | } | |
181 | ||
182 | // goto next line if ch would overflow | |
183 | // TODO: needs a global min terminal width of 2 | |
991b2843 | 184 | if (this._terminal.x + ch_width - 1 >= this._terminal.cols) { |
a31921ae | 185 | // autowrap - DECAWM |
991b2843 DI |
186 | if (this._terminal.wraparoundMode) { |
187 | this._terminal.x = 0; | |
188 | this._terminal.y++; | |
189 | if (this._terminal.y > this._terminal.scrollBottom) { | |
190 | this._terminal.y--; | |
191 | this._terminal.scroll(); | |
a31921ae DI |
192 | } |
193 | } else { | |
991b2843 DI |
194 | this._terminal.x = this._terminal.cols - 1; |
195 | if (ch_width === 2) // FIXME: check for xterm behavior | |
a31921ae DI |
196 | continue; |
197 | } | |
198 | } | |
991b2843 | 199 | row = this._terminal.y + this._terminal.ybase; |
a31921ae DI |
200 | |
201 | // insert mode: move characters to right | |
991b2843 | 202 | if (this._terminal.insertMode) { |
a31921ae | 203 | // do this twice for a fullwidth char |
991b2843 | 204 | for (let moves = 0; moves < ch_width; ++moves) { |
a31921ae DI |
205 | // remove last cell, if it's width is 0 |
206 | // we have to adjust the second last cell as well | |
991b2843 DI |
207 | const removed = this._terminal.lines.get(this._terminal.y + this._terminal.ybase).pop(); |
208 | if (removed[2] === 0 | |
209 | && this._terminal.lines.get(row)[this._terminal.cols - 2] | |
210 | && this._terminal.lines.get(row)[this._terminal.cols - 2][2] === 2) | |
211 | this._terminal.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1]; | |
a31921ae DI |
212 | |
213 | // insert empty cell at cursor | |
991b2843 | 214 | this._terminal.lines.get(row).splice(this._terminal.x, 0, [this._terminal.curAttr, ' ', 1]); |
a31921ae DI |
215 | } |
216 | } | |
217 | ||
991b2843 DI |
218 | this._terminal.lines.get(row)[this._terminal.x] = [this._terminal.curAttr, ch, ch_width]; |
219 | this._terminal.x++; | |
220 | this._terminal.updateRange(this._terminal.y); | |
a31921ae DI |
221 | |
222 | // fullwidth char - set next cell width to zero and advance cursor | |
991b2843 DI |
223 | if (ch_width === 2) { |
224 | this._terminal.lines.get(row)[this._terminal.x] = [this._terminal.curAttr, '', 0]; | |
225 | this._terminal.x++; | |
a31921ae DI |
226 | } |
227 | } | |
228 | break; | |
229 | } | |
230 | break; | |
231 | case ParserState.ESCAPED: | |
232 | switch (ch) { | |
233 | // ESC [ Control Sequence Introducer ( CSI is 0x9b). | |
234 | case '[': | |
991b2843 DI |
235 | this._terminal.params = []; |
236 | this._terminal.currentParam = 0; | |
c7fa2d55 | 237 | this.state = ParserState.CSI_PARAM; |
a31921ae DI |
238 | break; |
239 | ||
240 | // ESC ] Operating System Command ( OSC is 0x9d). | |
241 | case ']': | |
991b2843 DI |
242 | this._terminal.params = []; |
243 | this._terminal.currentParam = 0; | |
244 | this.state = ParserState.OSC; | |
a31921ae DI |
245 | break; |
246 | ||
247 | // ESC P Device Control String ( DCS is 0x90). | |
248 | case 'P': | |
991b2843 DI |
249 | this._terminal.params = []; |
250 | this._terminal.currentParam = 0; | |
251 | this.state = ParserState.DCS; | |
a31921ae DI |
252 | break; |
253 | ||
254 | // ESC _ Application Program Command ( APC is 0x9f). | |
255 | case '_': | |
256 | this.state = ParserState.IGNORE; | |
257 | break; | |
258 | ||
259 | // ESC ^ Privacy Message ( PM is 0x9e). | |
260 | case '^': | |
261 | this.state = ParserState.IGNORE; | |
262 | break; | |
263 | ||
264 | // ESC c Full Reset (RIS). | |
265 | case 'c': | |
991b2843 | 266 | this._terminal.reset(); |
a31921ae DI |
267 | break; |
268 | ||
269 | // ESC E Next Line ( NEL is 0x85). | |
270 | // ESC D Index ( IND is 0x84). | |
271 | case 'E': | |
991b2843 | 272 | this._terminal.x = 0; |
a31921ae DI |
273 | ; |
274 | case 'D': | |
991b2843 | 275 | this._terminal.index(); |
6deaaa8e | 276 | this.state = ParserState.NORMAL; |
a31921ae DI |
277 | break; |
278 | ||
279 | // ESC M Reverse Index ( RI is 0x8d). | |
280 | case 'M': | |
991b2843 | 281 | this._terminal.reverseIndex(); |
6deaaa8e | 282 | this.state = ParserState.NORMAL; |
a31921ae DI |
283 | break; |
284 | ||
285 | // ESC % Select default/utf-8 character set. | |
286 | // @ = default, G = utf-8 | |
287 | case '%': | |
991b2843 DI |
288 | // this.charset = null; |
289 | this._terminal.setgLevel(0); | |
1848fff4 | 290 | this._terminal.setgCharset(0, CHARSETS.US); |
a31921ae DI |
291 | this.state = ParserState.NORMAL; |
292 | i++; | |
293 | break; | |
294 | ||
295 | // ESC (,),*,+,-,. Designate G0-G2 Character Set. | |
296 | case '(': // <-- this seems to get all the attention | |
297 | case ')': | |
298 | case '*': | |
299 | case '+': | |
300 | case '-': | |
301 | case '.': | |
302 | switch (ch) { | |
303 | case '(': | |
991b2843 | 304 | this._terminal.gcharset = 0; |
a31921ae DI |
305 | break; |
306 | case ')': | |
991b2843 | 307 | this._terminal.gcharset = 1; |
a31921ae DI |
308 | break; |
309 | case '*': | |
991b2843 | 310 | this._terminal.gcharset = 2; |
a31921ae DI |
311 | break; |
312 | case '+': | |
991b2843 | 313 | this._terminal.gcharset = 3; |
a31921ae DI |
314 | break; |
315 | case '-': | |
991b2843 | 316 | this._terminal.gcharset = 1; |
a31921ae DI |
317 | break; |
318 | case '.': | |
991b2843 | 319 | this._terminal.gcharset = 2; |
a31921ae DI |
320 | break; |
321 | } | |
991b2843 | 322 | this.state = ParserState.CHARSET; |
a31921ae DI |
323 | break; |
324 | ||
325 | // Designate G3 Character Set (VT300). | |
326 | // A = ISO Latin-1 Supplemental. | |
327 | // Not implemented. | |
328 | case '/': | |
991b2843 | 329 | this._terminal.gcharset = 3; |
a31921ae DI |
330 | this.state = ParserState.CHARSET; |
331 | i--; | |
332 | break; | |
333 | ||
334 | // ESC N | |
335 | // Single Shift Select of G2 Character Set | |
336 | // ( SS2 is 0x8e). This affects next character only. | |
337 | case 'N': | |
338 | break; | |
339 | // ESC O | |
340 | // Single Shift Select of G3 Character Set | |
341 | // ( SS3 is 0x8f). This affects next character only. | |
342 | case 'O': | |
343 | break; | |
344 | // ESC n | |
345 | // Invoke the G2 Character Set as GL (LS2). | |
346 | case 'n': | |
991b2843 | 347 | this._terminal.setgLevel(2); |
a31921ae DI |
348 | break; |
349 | // ESC o | |
350 | // Invoke the G3 Character Set as GL (LS3). | |
351 | case 'o': | |
991b2843 | 352 | this._terminal.setgLevel(3); |
a31921ae DI |
353 | break; |
354 | // ESC | | |
355 | // Invoke the G3 Character Set as GR (LS3R). | |
356 | case '|': | |
991b2843 | 357 | this._terminal.setgLevel(3); |
a31921ae DI |
358 | break; |
359 | // ESC } | |
360 | // Invoke the G2 Character Set as GR (LS2R). | |
361 | case '}': | |
991b2843 | 362 | this._terminal.setgLevel(2); |
a31921ae DI |
363 | break; |
364 | // ESC ~ | |
365 | // Invoke the G1 Character Set as GR (LS1R). | |
366 | case '~': | |
991b2843 | 367 | this._terminal.setgLevel(1); |
a31921ae DI |
368 | break; |
369 | ||
370 | // ESC 7 Save Cursor (DECSC). | |
371 | case '7': | |
f4846aa1 | 372 | this._inputHandler.saveCursor(); |
a31921ae DI |
373 | this.state = ParserState.NORMAL; |
374 | break; | |
375 | ||
376 | // ESC 8 Restore Cursor (DECRC). | |
377 | case '8': | |
f4846aa1 | 378 | this._inputHandler.restoreCursor(); |
a31921ae DI |
379 | this.state = ParserState.NORMAL; |
380 | break; | |
381 | ||
382 | // ESC # 3 DEC line height/width | |
383 | case '#': | |
384 | this.state = ParserState.NORMAL; | |
385 | i++; | |
386 | break; | |
387 | ||
388 | // ESC H Tab Set (HTS is 0x88). | |
389 | case 'H': | |
991b2843 | 390 | this._terminal.tabSet(); |
6deaaa8e | 391 | this.state = ParserState.NORMAL; |
a31921ae DI |
392 | break; |
393 | ||
394 | // ESC = Application Keypad (DECKPAM). | |
395 | case '=': | |
991b2843 DI |
396 | this._terminal.log('Serial port requested application keypad.'); |
397 | this._terminal.applicationKeypad = true; | |
398 | this._terminal.viewport.syncScrollArea(); | |
a31921ae DI |
399 | this.state = ParserState.NORMAL; |
400 | break; | |
401 | ||
402 | // ESC > Normal Keypad (DECKPNM). | |
403 | case '>': | |
991b2843 DI |
404 | this._terminal.log('Switching back to normal keypad.'); |
405 | this._terminal.applicationKeypad = false; | |
406 | this._terminal.viewport.syncScrollArea(); | |
a31921ae DI |
407 | this.state = ParserState.NORMAL; |
408 | break; | |
409 | ||
410 | default: | |
411 | this.state = ParserState.NORMAL; | |
991b2843 | 412 | this._terminal.error('Unknown ESC control: %s.', ch); |
a31921ae DI |
413 | break; |
414 | } | |
415 | break; | |
416 | ||
417 | case ParserState.CHARSET: | |
418 | switch (ch) { | |
419 | case '0': // DEC Special Character and Line Drawing Set. | |
1848fff4 | 420 | cs = CHARSETS.SCLD; |
a31921ae DI |
421 | break; |
422 | case 'A': // UK | |
1848fff4 | 423 | cs = CHARSETS.UK; |
a31921ae DI |
424 | break; |
425 | case 'B': // United States (USASCII). | |
1848fff4 | 426 | cs = CHARSETS.US; |
a31921ae DI |
427 | break; |
428 | case '4': // Dutch | |
1848fff4 | 429 | cs = CHARSETS.Dutch; |
a31921ae DI |
430 | break; |
431 | case 'C': // Finnish | |
432 | case '5': | |
1848fff4 | 433 | cs = CHARSETS.Finnish; |
a31921ae DI |
434 | break; |
435 | case 'R': // French | |
1848fff4 | 436 | cs = CHARSETS.French; |
a31921ae DI |
437 | break; |
438 | case 'Q': // FrenchCanadian | |
1848fff4 | 439 | cs = CHARSETS.FrenchCanadian; |
a31921ae DI |
440 | break; |
441 | case 'K': // German | |
1848fff4 | 442 | cs = CHARSETS.German; |
a31921ae DI |
443 | break; |
444 | case 'Y': // Italian | |
1848fff4 | 445 | cs = CHARSETS.Italian; |
a31921ae DI |
446 | break; |
447 | case 'E': // NorwegianDanish | |
448 | case '6': | |
1848fff4 | 449 | cs = CHARSETS.NorwegianDanish; |
a31921ae DI |
450 | break; |
451 | case 'Z': // Spanish | |
1848fff4 | 452 | cs = CHARSETS.Spanish; |
a31921ae DI |
453 | break; |
454 | case 'H': // Swedish | |
455 | case '7': | |
1848fff4 | 456 | cs = CHARSETS.Swedish; |
a31921ae DI |
457 | break; |
458 | case '=': // Swiss | |
1848fff4 | 459 | cs = CHARSETS.Swiss; |
a31921ae DI |
460 | break; |
461 | case '/': // ISOLatin (actually /A) | |
1848fff4 | 462 | cs = CHARSETS.ISOLatin; |
a31921ae DI |
463 | i++; |
464 | break; | |
465 | default: // Default | |
1848fff4 | 466 | cs = CHARSETS.US; |
a31921ae DI |
467 | break; |
468 | } | |
991b2843 DI |
469 | this._terminal.setgCharset(this._terminal.gcharset, cs); |
470 | this._terminal.gcharset = null; | |
a31921ae DI |
471 | this.state = ParserState.NORMAL; |
472 | break; | |
473 | ||
474 | case ParserState.OSC: | |
475 | // OSC Ps ; Pt ST | |
476 | // OSC Ps ; Pt BEL | |
477 | // Set Text Parameters. | |
478 | if (ch === C0.ESC || ch === C0.BEL) { | |
479 | if (ch === C0.ESC) i++; | |
480 | ||
991b2843 | 481 | this._terminal.params.push(this._terminal.currentParam); |
a31921ae | 482 | |
991b2843 | 483 | switch (this._terminal.params[0]) { |
a31921ae DI |
484 | case 0: |
485 | case 1: | |
486 | case 2: | |
991b2843 DI |
487 | if (this._terminal.params[1]) { |
488 | this._terminal.title = this._terminal.params[1]; | |
489 | this._terminal.handleTitle(this._terminal.title); | |
a31921ae DI |
490 | } |
491 | break; | |
492 | case 3: | |
493 | // set X property | |
494 | break; | |
495 | case 4: | |
496 | case 5: | |
497 | // change dynamic colors | |
498 | break; | |
499 | case 10: | |
500 | case 11: | |
501 | case 12: | |
502 | case 13: | |
503 | case 14: | |
504 | case 15: | |
505 | case 16: | |
506 | case 17: | |
507 | case 18: | |
508 | case 19: | |
509 | // change dynamic ui colors | |
510 | break; | |
511 | case 46: | |
512 | // change log file | |
513 | break; | |
514 | case 50: | |
515 | // dynamic font | |
516 | break; | |
517 | case 51: | |
518 | // emacs shell | |
519 | break; | |
520 | case 52: | |
521 | // manipulate selection data | |
522 | break; | |
523 | case 104: | |
524 | case 105: | |
525 | case 110: | |
526 | case 111: | |
527 | case 112: | |
528 | case 113: | |
529 | case 114: | |
530 | case 115: | |
531 | case 116: | |
532 | case 117: | |
533 | case 118: | |
534 | // reset colors | |
535 | break; | |
536 | } | |
537 | ||
991b2843 DI |
538 | this._terminal.params = []; |
539 | this._terminal.currentParam = 0; | |
a31921ae DI |
540 | this.state = ParserState.NORMAL; |
541 | } else { | |
991b2843 | 542 | if (!this._terminal.params.length) { |
a31921ae | 543 | if (ch >= '0' && ch <= '9') { |
991b2843 DI |
544 | this._terminal.currentParam = |
545 | this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48; | |
a31921ae | 546 | } else if (ch === ';') { |
991b2843 DI |
547 | this._terminal.params.push(this._terminal.currentParam); |
548 | this._terminal.currentParam = ''; | |
a31921ae DI |
549 | } |
550 | } else { | |
991b2843 | 551 | this._terminal.currentParam += ch; |
a31921ae DI |
552 | } |
553 | } | |
554 | break; | |
555 | ||
c7fa2d55 | 556 | case ParserState.CSI_PARAM: |
9942477b | 557 | if (ch in csiParamStateHandler) { |
7572dd5f | 558 | csiParamStateHandler[ch](this); |
a31921ae DI |
559 | break; |
560 | } | |
c7fa2d55 DI |
561 | this.finalizeParam(); |
562 | // Fall through the CSI as this character should be the CSI code. | |
563 | this.state = ParserState.CSI; | |
a31921ae | 564 | |
c7fa2d55 | 565 | case ParserState.CSI: |
9942477b | 566 | if (ch in csiStateHandler) { |
f4846aa1 DI |
567 | csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix); |
568 | } else { | |
569 | this._terminal.error('Unknown CSI code: %s.', ch); | |
a31921ae DI |
570 | } |
571 | ||
f4846aa1 | 572 | this.state = ParserState.NORMAL; |
991b2843 DI |
573 | this._terminal.prefix = ''; |
574 | this._terminal.postfix = ''; | |
a31921ae DI |
575 | break; |
576 | ||
577 | case ParserState.DCS: | |
578 | if (ch === C0.ESC || ch === C0.BEL) { | |
579 | if (ch === C0.ESC) i++; | |
580 | ||
991b2843 | 581 | switch (this._terminal.prefix) { |
a31921ae DI |
582 | // User-Defined Keys (DECUDK). |
583 | case '': | |
584 | break; | |
585 | ||
586 | // Request Status String (DECRQSS). | |
587 | // test: echo -e '\eP$q"p\e\\' | |
588 | case '$q': | |
991b2843 | 589 | let pt = this._terminal.currentParam |
a31921ae DI |
590 | , valid = false; |
591 | ||
592 | switch (pt) { | |
593 | // DECSCA | |
594 | case '"q': | |
595 | pt = '0"q'; | |
596 | break; | |
597 | ||
598 | // DECSCL | |
599 | case '"p': | |
600 | pt = '61"p'; | |
601 | break; | |
602 | ||
603 | // DECSTBM | |
604 | case 'r': | |
605 | pt = '' | |
991b2843 | 606 | + (this._terminal.scrollTop + 1) |
a31921ae | 607 | + ';' |
991b2843 | 608 | + (this._terminal.scrollBottom + 1) |
a31921ae DI |
609 | + 'r'; |
610 | break; | |
611 | ||
612 | // SGR | |
613 | case 'm': | |
614 | pt = '0m'; | |
615 | break; | |
616 | ||
617 | default: | |
991b2843 | 618 | this._terminal.error('Unknown DCS Pt: %s.', pt); |
a31921ae DI |
619 | pt = ''; |
620 | break; | |
621 | } | |
622 | ||
991b2843 | 623 | this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\'); |
a31921ae DI |
624 | break; |
625 | ||
626 | // Set Termcap/Terminfo Data (xterm, experimental). | |
627 | case '+p': | |
628 | break; | |
629 | ||
630 | // Request Termcap/Terminfo String (xterm, experimental) | |
631 | // Regular xterm does not even respond to this sequence. | |
632 | // This can cause a small glitch in vim. | |
633 | // test: echo -ne '\eP+q6b64\e\\' | |
634 | case '+q': | |
991b2843 DI |
635 | // TODO: Don't declare pt twice |
636 | /*let*/ pt = this._terminal.currentParam | |
a31921ae DI |
637 | , valid = false; |
638 | ||
991b2843 | 639 | this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\'); |
a31921ae DI |
640 | break; |
641 | ||
642 | default: | |
991b2843 | 643 | this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix); |
a31921ae DI |
644 | break; |
645 | } | |
646 | ||
991b2843 DI |
647 | this._terminal.currentParam = 0; |
648 | this._terminal.prefix = ''; | |
a31921ae | 649 | this.state = ParserState.NORMAL; |
991b2843 DI |
650 | } else if (!this._terminal.currentParam) { |
651 | if (!this._terminal.prefix && ch !== '$' && ch !== '+') { | |
652 | this._terminal.currentParam = ch; | |
653 | } else if (this._terminal.prefix.length === 2) { | |
654 | this._terminal.currentParam = ch; | |
a31921ae | 655 | } else { |
991b2843 | 656 | this._terminal.prefix += ch; |
a31921ae DI |
657 | } |
658 | } else { | |
991b2843 | 659 | this._terminal.currentParam += ch; |
a31921ae DI |
660 | } |
661 | break; | |
662 | ||
663 | case ParserState.IGNORE: | |
664 | // For PM and APC. | |
665 | if (ch === C0.ESC || ch === C0.BEL) { | |
666 | if (ch === C0.ESC) i++; | |
667 | this.state = ParserState.NORMAL; | |
668 | } | |
669 | break; | |
670 | } | |
671 | } | |
672 | } | |
672dc1a2 | 673 | |
635a4d76 | 674 | public setPrefix(prefix: string): void { |
672dc1a2 DI |
675 | this._terminal.prefix = prefix; |
676 | } | |
677 | ||
678 | public setParam(param: number) { | |
679 | this._terminal.currentParam = param; | |
680 | } | |
681 | ||
682 | public getParam(): number { | |
683 | return this._terminal.currentParam; | |
684 | } | |
635a4d76 | 685 | |
c7fa2d55 DI |
686 | public finalizeParam(): void { |
687 | this._terminal.params.push(this._terminal.currentParam); | |
688 | this._terminal.currentParam = 0; | |
689 | } | |
690 | ||
635a4d76 DI |
691 | public setPostfix(postfix: string): void { |
692 | this._terminal.postfix = postfix; | |
693 | } | |
a31921ae | 694 | } |
4a27b9a5 | 695 | |
940bd269 | 696 | const wcwidth = (function(opts) { |
4a27b9a5 DI |
697 | // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c |
698 | // combining characters | |
940bd269 | 699 | const COMBINING = [ |
4a27b9a5 DI |
700 | [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], |
701 | [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], | |
702 | [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], | |
703 | [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], | |
704 | [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], | |
705 | [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], | |
706 | [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], | |
707 | [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], | |
708 | [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], | |
709 | [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], | |
710 | [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], | |
711 | [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], | |
712 | [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], | |
713 | [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], | |
714 | [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], | |
715 | [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], | |
716 | [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], | |
717 | [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], | |
718 | [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], | |
719 | [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], | |
720 | [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], | |
721 | [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], | |
722 | [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], | |
723 | [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], | |
724 | [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], | |
725 | [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], | |
726 | [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], | |
727 | [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], | |
728 | [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], | |
729 | [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], | |
730 | [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], | |
731 | [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], | |
732 | [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], | |
733 | [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], | |
734 | [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], | |
735 | [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], | |
736 | [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], | |
737 | [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], | |
738 | [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], | |
739 | [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], | |
740 | [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], | |
741 | [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], | |
742 | [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB], | |
743 | [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], | |
744 | [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], | |
745 | [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], | |
746 | [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], | |
747 | [0xE0100, 0xE01EF] | |
748 | ]; | |
749 | // binary search | |
750 | function bisearch(ucs) { | |
940bd269 DI |
751 | let min = 0; |
752 | let max = COMBINING.length - 1; | |
753 | let mid; | |
4a27b9a5 DI |
754 | if (ucs < COMBINING[0][0] || ucs > COMBINING[max][1]) |
755 | return false; | |
756 | while (max >= min) { | |
757 | mid = Math.floor((min + max) / 2); | |
758 | if (ucs > COMBINING[mid][1]) | |
759 | min = mid + 1; | |
760 | else if (ucs < COMBINING[mid][0]) | |
761 | max = mid - 1; | |
762 | else | |
763 | return true; | |
764 | } | |
765 | return false; | |
766 | } | |
767 | function wcwidth(ucs) { | |
768 | // test for 8-bit control characters | |
769 | if (ucs === 0) | |
770 | return opts.nul; | |
771 | if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) | |
772 | return opts.control; | |
773 | // binary search in table of non-spacing characters | |
774 | if (bisearch(ucs)) | |
775 | return 0; | |
776 | // if we arrive here, ucs is not a combining or C0/C1 control character | |
940bd269 DI |
777 | if (isWide(ucs)) { |
778 | return 2; | |
779 | } | |
780 | return 1; | |
781 | } | |
782 | function isWide(ucs) { | |
783 | return ( | |
784 | ucs >= 0x1100 && ( | |
4a27b9a5 | 785 | ucs <= 0x115f || // Hangul Jamo init. consonants |
940bd269 DI |
786 | ucs === 0x2329 || |
787 | ucs === 0x232a || | |
788 | (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi | |
4a27b9a5 DI |
789 | (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables |
790 | (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs | |
791 | (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms | |
792 | (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms | |
793 | (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms | |
794 | (ucs >= 0xffe0 && ucs <= 0xffe6) || | |
795 | (ucs >= 0x20000 && ucs <= 0x2fffd) || | |
940bd269 | 796 | (ucs >= 0x30000 && ucs <= 0x3fffd))); |
4a27b9a5 DI |
797 | } |
798 | return wcwidth; | |
799 | })({nul: 0, control: 0}); // configurable options |