Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/extern/isocline/src/tty_esc.c
2727 views
1
/* ----------------------------------------------------------------------------
2
Copyright (c) 2021, Daan Leijen
3
This is free software; you can redistribute it and/or modify it
4
under the terms of the MIT License. A copy of the license can be
5
found in the "LICENSE" file at the root of this distribution.
6
-----------------------------------------------------------------------------*/
7
#include <string.h>
8
#include "tty.h"
9
10
/*-------------------------------------------------------------
11
Decoding escape sequences to key codes.
12
This is a bit tricky there are many variants to encode keys as escape sequences, see for example:
13
- <http://www.leonerd.org.uk/hacks/fixterms/>.
14
- <https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences>
15
- <https://www.xfree86.org/current/ctlseqs.html>
16
- <https://vt100.net/docs/vt220-rm/contents.html>
17
- <https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf>
18
19
Generally, for our purposes we accept a subset of escape sequences as:
20
21
escseq ::= ESC
22
| ESC char
23
| ESC start special? (number (';' modifiers)?)? final
24
25
where:
26
char ::= [\x00-\xFF] # any character
27
special ::= [:<=>?]
28
number ::= [0-9+]
29
modifiers ::= [1-9]
30
intermediate ::= [\x20-\x2F] # !"#$%&'()*+,-./
31
final ::= [\x40-\x7F] # @A–Z[\]^_`a–z{|}~
32
ESC ::= '\x1B'
33
CSI ::= ESC '['
34
SS3 ::= ESC 'O'
35
36
In ECMA48 `special? (number (';' modifiers)?)?` is the more liberal `[\x30-\x3F]*`
37
but that seems never used for key codes. If the number (vtcode or unicode) or the
38
modifiers are not given, we assume these are '1'.
39
We then accept the following key sequences:
40
41
key ::= ESC # lone ESC
42
| ESC char # Alt+char
43
| ESC '[' special? vtcode ';' modifiers '~' # vt100 codes
44
| ESC '[' special? '1' ';' modifiers [A-Z] # xterm codes
45
| ESC 'O' special? '1' ';' modifiers [A-Za-z] # SS3 codes
46
| ESC '[' special? unicode ';' modifiers 'u' # direct unicode code
47
48
Moreover, we translate the following special cases that do not fit into the above grammar.
49
First we translate away special starter sequences:
50
---------------------------------------------------------------------
51
ESC '[' '[' .. ~> ESC '[' .. # Linux sometimes uses extra '[' for CSI
52
ESC '[' 'O' .. ~> ESC 'O' .. # Linux sometimes uses extra '[' for SS3
53
ESC 'o' .. ~> ESC 'O' .. # Eterm: ctrl + SS3
54
ESC '?' .. ~> ESC 'O' .. # vt52 treated as SS3
55
56
And then translate the following special cases into a standard form:
57
---------------------------------------------------------------------
58
ESC '[' .. '@' ~> ESC '[' '3' '~' # Del on Mach
59
ESC '[' .. '9' ~> ESC '[' '2' '~' # Ins on Mach
60
ESC .. [^@$] ~> ESC .. '~' # ETerm,xrvt,urxt: ^ = ctrl, $ = shift, @ = alt
61
ESC '[' [a-d] ~> ESC '[' '1' ';' '2' [A-D] # Eterm shift+<cursor>
62
ESC 'O' [1-9] final ~> ESC 'O' '1' ';' [1-9] final # modifiers as parameter 1 (like on Haiku)
63
ESC '[' [1-9] [^~u] ~> ESC 'O' '1' ';' [1-9] final # modifiers as parameter 1
64
65
The modifier keys are encoded as "(modifiers-1) & mask" where the
66
shift mask is 0x01, alt 0x02 and ctrl 0x04. Therefore:
67
------------------------------------------------------------
68
1: - 5: ctrl 9: alt (for minicom)
69
2: shift 6: shift+ctrl
70
3: alt 7: alt+ctrl
71
4: shift+alt 8: shift+alt+ctrl
72
73
The different encodings fox vt100, xterm, and SS3 are:
74
75
vt100: ESC [ vtcode ';' modifiers '~'
76
--------------------------------------
77
1: Home 10-15: F1-F5
78
2: Ins 16 : F5
79
3: Del 17-21: F6-F10
80
4: End 23-26: F11-F14
81
5: PageUp 28 : F15
82
6: PageDn 29 : F16
83
7: Home 31-34: F17-F20
84
8: End
85
86
xterm: ESC [ 1 ';' modifiers [A-Z]
87
-----------------------------------
88
A: Up N: F2
89
B: Down O: F3
90
C: Right P: F4
91
D: Left Q: F5
92
E: '5' R: F6
93
F: End S: F7
94
G: T: F8
95
H: Home U: PageDn
96
I: PageUp V: PageUp
97
J: W: F11
98
K: X: F12
99
L: Ins Y: End
100
M: F1 Z: shift+Tab
101
102
SS3: ESC 'O' 1 ';' modifiers [A-Za-z]
103
---------------------------------------
104
(normal) (numpad)
105
A: Up N: a: Up n:
106
B: Down O: b: Down o:
107
C: Right P: F1 c: Right p: Ins
108
D: Left Q: F2 d: Left q: End
109
E: '5' R: F3 e: r: Down
110
F: End S: F4 f: s: PageDn
111
G: T: F5 g: t: Left
112
H: Home U: F6 h: u: '5'
113
I: Tab V: F7 i: v: Right
114
J: W: F8 j: '*' w: Home
115
K: X: F9 k: '+' x: Up
116
L: Y: F10 l: ',' y: PageUp
117
M: \x0A '\n' Z: shift+Tab m: '-' z:
118
119
-------------------------------------------------------------*/
120
121
//-------------------------------------------------------------
122
// Decode escape sequences
123
//-------------------------------------------------------------
124
125
static code_t esc_decode_vt(uint32_t vt_code ) {
126
switch(vt_code) {
127
case 1: return KEY_HOME;
128
case 2: return KEY_INS;
129
case 3: return KEY_DEL;
130
case 4: return KEY_END;
131
case 5: return KEY_PAGEUP;
132
case 6: return KEY_PAGEDOWN;
133
case 7: return KEY_HOME;
134
case 8: return KEY_END;
135
default:
136
if (vt_code >= 10 && vt_code <= 15) return KEY_F(1 + (vt_code - 10));
137
if (vt_code == 16) return KEY_F5; // minicom
138
if (vt_code >= 17 && vt_code <= 21) return KEY_F(6 + (vt_code - 17));
139
if (vt_code >= 23 && vt_code <= 26) return KEY_F(11 + (vt_code - 23));
140
if (vt_code >= 28 && vt_code <= 29) return KEY_F(15 + (vt_code - 28));
141
if (vt_code >= 31 && vt_code <= 34) return KEY_F(17 + (vt_code - 31));
142
}
143
return KEY_NONE;
144
}
145
146
static code_t esc_decode_xterm( uint8_t xcode ) {
147
// ESC [
148
switch(xcode) {
149
case 'A': return KEY_UP;
150
case 'B': return KEY_DOWN;
151
case 'C': return KEY_RIGHT;
152
case 'D': return KEY_LEFT;
153
case 'E': return '5'; // numpad 5
154
case 'F': return KEY_END;
155
case 'H': return KEY_HOME;
156
case 'Z': return KEY_TAB | KEY_MOD_SHIFT;
157
// Freebsd:
158
case 'I': return KEY_PAGEUP;
159
case 'L': return KEY_INS;
160
case 'M': return KEY_F1;
161
case 'N': return KEY_F2;
162
case 'O': return KEY_F3;
163
case 'P': return KEY_F4; // note: differs from <https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences>
164
case 'Q': return KEY_F5;
165
case 'R': return KEY_F6;
166
case 'S': return KEY_F7;
167
case 'T': return KEY_F8;
168
case 'U': return KEY_PAGEDOWN; // Mach
169
case 'V': return KEY_PAGEUP; // Mach
170
case 'W': return KEY_F11;
171
case 'X': return KEY_F12;
172
case 'Y': return KEY_END; // Mach
173
}
174
return KEY_NONE;
175
}
176
177
static code_t esc_decode_ss3( uint8_t ss3_code ) {
178
// ESC O
179
switch(ss3_code) {
180
case 'A': return KEY_UP;
181
case 'B': return KEY_DOWN;
182
case 'C': return KEY_RIGHT;
183
case 'D': return KEY_LEFT;
184
case 'E': return '5'; // numpad 5
185
case 'F': return KEY_END;
186
case 'H': return KEY_HOME;
187
case 'I': return KEY_TAB;
188
case 'Z': return KEY_TAB | KEY_MOD_SHIFT;
189
case 'M': return KEY_LINEFEED;
190
case 'P': return KEY_F1;
191
case 'Q': return KEY_F2;
192
case 'R': return KEY_F3;
193
case 'S': return KEY_F4;
194
// on Mach
195
case 'T': return KEY_F5;
196
case 'U': return KEY_F6;
197
case 'V': return KEY_F7;
198
case 'W': return KEY_F8;
199
case 'X': return KEY_F9; // '=' on vt220
200
case 'Y': return KEY_F10;
201
// numpad
202
case 'a': return KEY_UP;
203
case 'b': return KEY_DOWN;
204
case 'c': return KEY_RIGHT;
205
case 'd': return KEY_LEFT;
206
case 'j': return '*';
207
case 'k': return '+';
208
case 'l': return ',';
209
case 'm': return '-';
210
case 'n': return KEY_DEL; // '.'
211
case 'o': return '/';
212
case 'p': return KEY_INS;
213
case 'q': return KEY_END;
214
case 'r': return KEY_DOWN;
215
case 's': return KEY_PAGEDOWN;
216
case 't': return KEY_LEFT;
217
case 'u': return '5';
218
case 'v': return KEY_RIGHT;
219
case 'w': return KEY_HOME;
220
case 'x': return KEY_UP;
221
case 'y': return KEY_PAGEUP;
222
}
223
return KEY_NONE;
224
}
225
226
static void tty_read_csi_num(tty_t* tty, uint8_t* ppeek, uint32_t* num, long esc_timeout) {
227
*num = 1; // default
228
ssize_t count = 0;
229
uint32_t i = 0;
230
while (*ppeek >= '0' && *ppeek <= '9' && count < 16) {
231
uint8_t digit = *ppeek - '0';
232
if (!tty_readc_noblock(tty,ppeek,esc_timeout)) break; // peek is not modified in this case
233
count++;
234
i = 10*i + digit;
235
}
236
if (count > 0) *num = i;
237
}
238
239
static code_t tty_read_csi(tty_t* tty, uint8_t c1, uint8_t peek, code_t mods0, long esc_timeout) {
240
// CSI starts with 0x9b (c1=='[') | ESC [ (c1=='[') | ESC [Oo?] (c1 == 'O') /* = SS3 */
241
242
// check for extra starter '[' (Linux sends ESC [ [ 15 ~ for F5 for example)
243
if (c1 == '[' && strchr("[Oo", (char)peek) != NULL) {
244
uint8_t cx = peek;
245
if (tty_readc_noblock(tty,&peek,esc_timeout)) {
246
c1 = cx;
247
}
248
}
249
250
// "special" characters ('?' is used for private sequences)
251
uint8_t special = 0;
252
if (strchr(":<=>?",(char)peek) != NULL) {
253
special = peek;
254
if (!tty_readc_noblock(tty,&peek,esc_timeout)) {
255
tty_cpush_char(tty,special); // recover
256
return (key_unicode(c1) | KEY_MOD_ALT); // Alt+<anychar>
257
}
258
}
259
260
// up to 2 parameters that default to 1
261
uint32_t num1 = 1;
262
uint32_t num2 = 1;
263
tty_read_csi_num(tty,&peek,&num1,esc_timeout);
264
if (peek == ';') {
265
if (!tty_readc_noblock(tty,&peek,esc_timeout)) return KEY_NONE;
266
tty_read_csi_num(tty,&peek,&num2,esc_timeout);
267
}
268
269
// the final character (we do not allow 'intermediate characters')
270
uint8_t final = peek;
271
code_t modifiers = mods0;
272
273
debug_msg("tty: escape sequence: ESC %c %c %d;%d %c\n", c1, (special == 0 ? '_' : special), num1, num2, final);
274
275
// Adjust special cases into standard ones.
276
if ((final == '@' || final == '9') && c1 == '[' && num1 == 1) {
277
// ESC [ @, ESC [ 9 : on Mach
278
if (final == '@') num1 = 3; // DEL
279
else if (final == '9') num1 = 2; // INS
280
final = '~';
281
}
282
else if (final == '^' || final == '$' || final == '@') {
283
// Eterm/rxvt/urxt
284
if (final=='^') modifiers |= KEY_MOD_CTRL;
285
if (final=='$') modifiers |= KEY_MOD_SHIFT;
286
if (final=='@') modifiers |= KEY_MOD_SHIFT | KEY_MOD_CTRL;
287
final = '~';
288
}
289
else if (c1 == '[' && final >= 'a' && final <= 'd') { // note: do not catch ESC [ .. u (for unicode)
290
// ESC [ [a-d] : on Eterm for shift+ cursor
291
modifiers |= KEY_MOD_SHIFT;
292
final = 'A' + (final - 'a');
293
}
294
295
if (((c1 == 'O') || (c1=='[' && final != '~' && final != 'u')) &&
296
(num2 == 1 && num1 > 1 && num1 <= 8))
297
{
298
// on haiku the modifier can be parameter 1, make it parameter 2 instead
299
num2 = num1;
300
num1 = 1;
301
}
302
303
// parameter 2 determines the modifiers
304
if (num2 > 1 && num2 <= 9) {
305
if (num2 == 9) num2 = 3; // iTerm2 in xterm mode
306
num2--;
307
if (num2 & 0x1) modifiers |= KEY_MOD_SHIFT;
308
if (num2 & 0x2) modifiers |= KEY_MOD_ALT;
309
if (num2 & 0x4) modifiers |= KEY_MOD_CTRL;
310
}
311
312
// and translate
313
code_t code = KEY_NONE;
314
if (final == '~') {
315
// vt codes
316
code = esc_decode_vt(num1);
317
}
318
else if (c1 == '[' && final == 'u') {
319
// unicode
320
code = key_unicode(num1);
321
}
322
else if (c1 == 'O' && ((final >= 'A' && final <= 'Z') || (final >= 'a' && final <= 'z'))) {
323
// ss3
324
code = esc_decode_ss3(final);
325
}
326
else if (num1 == 1 && final >= 'A' && final <= 'Z') {
327
// xterm
328
code = esc_decode_xterm(final);
329
}
330
else if (c1 == '[' && final == 'R') {
331
// cursor position
332
code = KEY_NONE;
333
}
334
335
if (code == KEY_NONE && final != 'R') {
336
debug_msg("tty: ignore escape sequence: ESC %c %zu;%zu %c\n", c1, num1, num2, final);
337
}
338
return (code != KEY_NONE ? (code | modifiers) : KEY_NONE);
339
}
340
341
static code_t tty_read_osc( tty_t* tty, uint8_t* ppeek, long esc_timeout ) {
342
debug_msg("discard OSC response..\n");
343
// keep reading until termination: OSC is terminated by BELL, or ESC \ (ST) (and STX)
344
while (true) {
345
uint8_t c = *ppeek;
346
if (c <= '\x07') { // BELL and anything below (STX, ^C, ^D)
347
if (c != '\x07') { tty_cpush_char( tty, c ); }
348
break;
349
}
350
else if (c=='\x1B') {
351
uint8_t c1;
352
if (!tty_readc_noblock(tty, &c1, esc_timeout)) break;
353
if (c1=='\\') break;
354
tty_cpush_char(tty,c1);
355
}
356
if (!tty_readc_noblock(tty, ppeek, esc_timeout)) break;
357
}
358
return KEY_NONE;
359
}
360
361
ic_private code_t tty_read_esc(tty_t* tty, long esc_initial_timeout, long esc_timeout) {
362
code_t mods = 0;
363
uint8_t peek = 0;
364
365
// lone ESC?
366
if (!tty_readc_noblock(tty, &peek, esc_initial_timeout)) return KEY_ESC;
367
368
// treat ESC ESC as Alt modifier (macOS sends ESC ESC [ [A-D] for alt-<cursor>)
369
if (peek == KEY_ESC) {
370
if (!tty_readc_noblock(tty, &peek, esc_timeout)) goto alt;
371
mods |= KEY_MOD_ALT;
372
}
373
374
// CSI ?
375
if (peek == '[') {
376
if (!tty_readc_noblock(tty, &peek, esc_timeout)) goto alt;
377
return tty_read_csi(tty, '[', peek, mods, esc_timeout); // ESC [ ...
378
}
379
380
// SS3?
381
if (peek == 'O' || peek == 'o' || peek == '?' /*vt52*/) {
382
uint8_t c1 = peek;
383
if (!tty_readc_noblock(tty, &peek, esc_timeout)) goto alt;
384
if (c1 == 'o') {
385
// ETerm uses this for ctrl+<cursor>
386
mods |= KEY_MOD_CTRL;
387
}
388
// treat all as standard SS3 'O'
389
return tty_read_csi(tty,'O',peek,mods, esc_timeout); // ESC [Oo?] ...
390
}
391
392
// OSC: we may get a delayed query response; ensure it is ignored
393
if (peek == ']') {
394
if (!tty_readc_noblock(tty, &peek, esc_timeout)) goto alt;
395
return tty_read_osc(tty, &peek, esc_timeout); // ESC ] ...
396
}
397
398
alt:
399
// Alt+<char>
400
return (key_unicode(peek) | KEY_MOD_ALT); // ESC <anychar>
401
}
402
403