FTXUI 6.1.9
C++ functional terminal UI.
Chargement...
Recherche...
Aucune correspondance
terminal_input_parser.cpp
Aller à la documentation de ce fichier.
1// Copyright 2020 Arthur Sonzogni. Tous droits réservés.
2// L'utilisation de ce code source est régie par la licence MIT qui se trouve dans
3// le fichier LICENSE.
5
6#include <cstdint> // for uint32_t
7#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Button, Mouse::Motion
8#include <functional> // for std::function
9#include <map>
10#include <memory> // for unique_ptr, allocator
11#include <utility> // for move
12#include <vector>
13#include "ftxui/component/event.hpp" // for Event
14#include "ftxui/component/task.hpp" // for Task
15
16namespace ftxui {
17
18// NOLINTNEXTLINE
19const std::map<std::string, std::string> g_uniformize = {
20 // Le terminal de Microsoft utilise un caractère de nouvelle ligne différent pour la touche Entrée.
21 // Cela se produit également sous Linux avec la commande `bind`:
22 // Voir https://github.com/ArthurSonzogni/FTXUI/issues/337
23 // Ici, nous uniformisons le caractère de nouvelle ligne en `\n`.
24 {"\r", "\n"},
25
26 // Voir : https://github.com/ArthurSonzogni/FTXUI/issues/508
27 {std::string({8}), std::string({127})},
28
29 // Voir : https://github.com/ArthurSonzogni/FTXUI/issues/626
30 //
31 // Selon le mode des touches de curseur (DECCKM), le terminal envoie différentes
32 // séquences d'échappement :
33 //
34 // Touche Normal Application
35 // ----- -------- -----------
36 // Haut ESC [ A ESC O A
37 // Bas ESC [ B ESC O B
38 // Droite ESC [ C ESC O C
39 // Gauche ESC [ D ESC O D
40 // Accueil ESC [ H ESC O H
41 // Fin ESC [ F ESC O F
42 //
43 {"\x1BOA", "\x1B[A"}, // HAUT
44 {"\x1BOB", "\x1B[B"}, // BAS
45 {"\x1BOC", "\x1B[C"}, // DROITE
46 {"\x1BOD", "\x1B[D"}, // GAUCHE
47 {"\x1BOH", "\x1B[H"}, // ACCUEIL
48 {"\x1BOF", "\x1B[F"}, // FIN
49
50 // Variations autour des touches FN.
51 // En interne, nous utilisons :
52 // vt220, xterm-vt200, xterm-xf86-v44, xterm-new, mgt, screen
53 // Voir : https://invisible-island.net/xterm/xterm-function-keys.html
54
55 // Pour la console Linux (CTRL+ALT+FN), qui n'appartient à aucune
56 // norme réelle.
57 // Voir : https://github.com/ArthurSonzogni/FTXUI/issues/685
58 {"\x1B[[A", "\x1BOP"}, // F1
59 {"\x1B[[B", "\x1BOQ"}, // F2
60 {"\x1B[[C", "\x1BOR"}, // F3
61 {"\x1B[[D", "\x1BOS"}, // F4
62 {"\x1B[[E", "\x1B[15~"}, // F5
63
64 // xterm-r5, xterm-r6, rxvt
65 {"\x1B[11~", "\x1BOP"}, // F1
66 {"\x1B[12~", "\x1BOQ"}, // F2
67 {"\x1B[13~", "\x1BOR"}, // F3
68 {"\x1B[14~", "\x1BOS"}, // F4
69
70 // vt100
71 {"\x1BOt", "\x1B[15~"}, // F5
72 {"\x1BOu", "\x1B[17~"}, // F6
73 {"\x1BOv", "\x1B[18~"}, // F7
74 {"\x1BOl", "\x1B[19~"}, // F8
75 {"\x1BOw", "\x1B[20~"}, // F9
76 {"\x1BOx", "\x1B[21~"}, // F10
77
78 // scoansi
79 {"\x1B[M", "\x1BOP"}, // F1
80 {"\x1B[N", "\x1BOQ"}, // F2
81 {"\x1B[O", "\x1BOR"}, // F3
82 {"\x1B[P", "\x1BOS"}, // F4
83 {"\x1B[Q", "\x1B[15~"}, // F5
84 {"\x1B[R", "\x1B[17~"}, // F6
85 {"\x1B[S", "\x1B[18~"}, // F7
86 {"\x1B[T", "\x1B[19~"}, // F8
87 {"\x1B[U", "\x1B[20~"}, // F9
88 {"\x1B[V", "\x1B[21~"}, // F10
89 {"\x1B[W", "\x1B[23~"}, // F11
90 {"\x1B[X", "\x1B[24~"}, // F12
91};
92
94 : out_(std::move(out)) {}
95
97 timeout_ += time;
98 const int timeout_threshold = 50;
99 if (timeout_ < timeout_threshold) {
100 return;
101 }
102 timeout_ = 0;
103 if (!pending_.empty()) {
104 Send(SPECIAL);
105 }
106}
107
109 pending_ += c;
110 timeout_ = 0;
111 position_ = -1;
112 Send(Parse());
113}
114
115unsigned char TerminalInputParser::Current() {
116 return pending_[position_];
117}
118
119bool TerminalInputParser::Eat() {
120 position_++;
121 return position_ < static_cast<int>(pending_.size());
122}
123
124void TerminalInputParser::Send(TerminalInputParser::Output output) {
125 switch (output.type) {
126 case UNCOMPLETED:
127 return;
128
129 case DROP:
130 pending_.clear();
131 return;
132
133 case CHARACTER:
134 out_(Event::Character(std::move(pending_)));
135 pending_.clear();
136 return;
137
138 case SPECIAL: {
139 auto it = g_uniformize.find(pending_);
140 if (it != g_uniformize.end()) {
141 pending_ = it->second;
142 }
143 out_(Event::Special(std::move(pending_)));
144 pending_.clear();
145 }
146 return;
147
148 case MOUSE:
149 out_(Event::Mouse(std::move(pending_), output.mouse)); // NOLINT
150 pending_.clear();
151 return;
152
153 case CURSOR_POSITION:
154 out_(Event::CursorPosition(std::move(pending_), // NOLINT
155 output.cursor.x, // NOLINT
156 output.cursor.y)); // NOLINT
157 pending_.clear();
158 return;
159
160 case CURSOR_SHAPE:
161 out_(Event::CursorShape(std::move(pending_), output.cursor_shape));
162 pending_.clear();
163 return;
164 }
165 // NOT_REACHED().
166}
167
168TerminalInputParser::Output TerminalInputParser::Parse() {
169 if (!Eat()) {
170 return UNCOMPLETED;
171 }
172
173 if (Current() == '\x1B') {
174 return ParseESC();
175 }
176
177 if (Current() < 32) { // C0 NOLINT
178 return SPECIAL;
179 }
180
181 if (Current() == 127) { // Delete // NOLINT
182 return SPECIAL;
183 }
184
185 return ParseUTF8();
186}
187
188// Conversion point de code <-> UTF-8
189//
190// ┏━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
191// ┃Octet 1 ┃Octet 2 ┃Octet 3 ┃Octet 4 ┃
192// ┡━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
193// │0xxxxxxx│ │ │ │
194// ├────────┼────────┼────────┼────────┤
195// │110xxxxx│10xxxxxx│ │ │
196// ├────────┼────────┼────────┼────────┤
197// │1110xxxx│10xxxxxx│10xxxxxx│ │
198// ├────────┼────────┼────────┼────────┤
199// │11110xxx│10xxxxxx│10xxxxxx│10xxxxxx│
200// └────────┴────────┴────────┴────────┘
201//
202// Certaines séquences sont illégales s'il existe une représentation plus courte du
203// même point de code.
204TerminalInputParser::Output TerminalInputParser::ParseUTF8() {
205 auto head = Current();
206 unsigned char selector = 0b1000'0000; // NOLINT
207
208 // The non code-point part of the first byte.
209 unsigned char mask = selector;
210
211 // Find the first zero in the first byte.
212 unsigned int first_zero = 8; // NOLINT
213 for (unsigned int i = 0; i < 8; ++i) { // NOLINT
214 mask |= selector;
215 if (!(head & selector)) {
216 first_zero = i;
217 break;
218 }
219 selector >>= 1U;
220 }
221
222 // Accumulate the value of the first byte.
223 auto value = uint32_t(head & ~mask); // NOLINT
224
225 // Invalid UTF8, with more than 5 bytes.
226 const unsigned int max_utf8_bytes = 5;
227 if (first_zero == 1 || first_zero >= max_utf8_bytes) {
228 return DROP;
229 }
230
231 // Multi byte UTF-8.
232 for (unsigned int i = 2; i <= first_zero; ++i) {
233 if (!Eat()) {
234 return UNCOMPLETED;
235 }
236
237 // Invalid continuation byte.
238 head = Current();
239 if ((head & 0b1100'0000) != 0b1000'0000) { // NOLINT
240 return DROP;
241 }
242 value <<= 6; // NOLINT
243 value += head & 0b0011'1111; // NOLINT
244 }
245
246 // Check for overlong UTF8 encoding.
247 int extra_byte = 0;
248 if (value <= 0b000'0000'0111'1111) { // NOLINT
249 extra_byte = 0; // NOLINT
250 } else if (value <= 0b000'0111'1111'1111) { // NOLINT
251 extra_byte = 1; // NOLINT
252 } else if (value <= 0b1111'1111'1111'1111) { // NOLINT
253 extra_byte = 2; // NOLINT
254 } else if (value <= 0b1'0000'1111'1111'1111'1111) { // NOLINT
255 extra_byte = 3; // NOLINT
256 } else { // NOLINT
257 return DROP;
258 }
259
260 if (extra_byte != position_) {
261 return DROP;
262 }
263
264 return CHARACTER;
265}
266
267TerminalInputParser::Output TerminalInputParser::ParseESC() {
268 if (!Eat()) {
269 return UNCOMPLETED;
270 }
271 switch (Current()) {
272 case 'P':
273 return ParseDCS();
274 case '[':
275 return ParseCSI();
276 case ']':
277 return ParseOSC();
278
279 // En attente de 2 caractères.
280 case ' ':
281 case '#':
282 case '%':
283 case '(':
284 case ')':
285 case '*':
286 case '+':
287 case 'O':
288 case 'N': {
289 if (!Eat()) {
290 return UNCOMPLETED;
291 }
292 return SPECIAL;
293 }
294 // En attente de 1 caractère :
295 default:
296 return SPECIAL;
297 }
298}
299
300// ESC P ... ESC BACKSLASH
301TerminalInputParser::Output TerminalInputParser::ParseDCS() {
302 // Analyser jusqu'au terminateur de chaîne ST.
303 while (true) {
304 if (!Eat()) {
305 return UNCOMPLETED;
306 }
307
308 if (Current() != '\x1B') {
309 continue;
310 }
311
312 if (!Eat()) {
313 return UNCOMPLETED;
314 }
315
316 if (Current() != '\\') {
317 continue;
318 }
319
320 if (pending_.size() == 10 && //
321 pending_[2] == '1' && //
322 pending_[3] == '$' && //
323 pending_[4] == 'r' && //
324 true) {
325 Output output(CURSOR_SHAPE);
326 output.cursor_shape = pending_[5] - '0';
327 return output;
328 }
329
330 return SPECIAL;
331 }
332}
333
334TerminalInputParser::Output TerminalInputParser::ParseCSI() {
335 bool altered = false;
336 int argument = 0;
337 std::vector<int> arguments;
338 while (true) {
339 if (!Eat()) {
340 return UNCOMPLETED;
341 }
342 if (Current() == '<') {
343 altered = true;
344 continue;
345 }
346 if (Current() >= '0' && Current() <= '9') {
347 argument *= 10; // NOLINT
348 argument += Current() - '0';
349 continue;
350 }
351 if (Current() == ';') {
352 arguments.push_back(argument);
353 argument = 0;
354 continue;
355 }
356 // Le CSI est terminé par un caractère dans la plage 0x40–0x7E
357 // (ASCII @A–Z[\\\]^_`a–z{|}~),
358 if (Current() >= '@' && Current() <= '~' &&
359 // Note : Je ne me souviens plus pourquoi nous excluons '<'
360 Current() != '<' &&
361 // Pour gérer les touches F1-F4, nous excluons '['.
362 Current() != '[') {
363 arguments.push_back(argument);
364 argument = 0; // NOLINT
365
366 switch (Current()) {
367 case 'M':
368 return ParseMouse(altered, true, std::move(arguments));
369 case 'm':
370 return ParseMouse(altered, false, std::move(arguments));
371 case 'R':
372 return ParseCursorPosition(std::move(arguments));
373 default:
374 return SPECIAL;
375 }
376 }
377 // ESC invalide dans CSI.
378 if (Current() == '\x1B') {
379 return SPECIAL;
380 }
381 }
382}
383
384TerminalInputParser::Output TerminalInputParser::ParseOSC() {
385 // Analyser jusqu'au terminateur de chaîne ST.
386 while (true) {
387 if (!Eat()) {
388 return UNCOMPLETED;
389 }
390 if (Current() != '\x1B') {
391 continue;
392 }
393 if (!Eat()) {
394 return UNCOMPLETED;
395 }
396 if (Current() != '\\') {
397 continue;
398 }
399 return SPECIAL;
400 }
401}
402
403TerminalInputParser::Output TerminalInputParser::ParseMouse( // NOLINT
404 bool altered,
405 bool pressed,
406 std::vector<int> arguments) {
407 if (arguments.size() != 3) {
408 return SPECIAL;
409 }
410
411 (void)altered;
412
413 Output output(MOUSE);
414 output.mouse.motion = Mouse::Motion(pressed); // NOLINT
415
416 // Bits valeur Modificateur Commentaire
417 // ---- ----- ------- ---------
418 // 0 1 1 2 bouton 0 = Gauche, 1 = Milieu, 2 = Droite, 3 = Relâcher
419 // 2 4 Maj
420 // 3 8 Méta
421 // 4 16 Contrôle
422 // 5 32 Déplacer
423 // 6 64 Molette
424
425 // clang-format off
426 const int button = arguments[0] & (1 + 2); // NOLINT
427 const bool is_shift = arguments[0] & 4; // NOLINT
428 const bool is_meta = arguments[0] & 8; // NOLINT
429 const bool is_control = arguments[0] & 16; // NOLINT
430 const bool is_move = arguments[0] & 32; // NOLINT
431 const bool is_wheel = arguments[0] & 64; // NOLINT
432 // clang-format on
433
434 output.mouse.motion = is_move ? Mouse::Moved : Mouse::Motion(pressed);
435 output.mouse.button = is_wheel ? Mouse::Button(Mouse::WheelUp + button) //
436 : Mouse::Button(button);
437 output.mouse.shift = is_shift;
438 output.mouse.meta = is_meta;
439 output.mouse.control = is_control;
440 output.mouse.x = arguments[1]; // NOLINT
441 output.mouse.y = arguments[2]; // NOLINT
442
443 // Événement de mouvement.
444 return output;
445}
446
447// NOLINTNEXTLINE
448TerminalInputParser::Output TerminalInputParser::ParseCursorPosition(
449 std::vector<int> arguments) {
450 if (arguments.size() != 2) {
451 return SPECIAL;
452 }
453 Output output(CURSOR_POSITION);
454 output.cursor.y = arguments[0]; // NOLINT
455 output.cursor.x = arguments[1]; // NOLINT
456 return output;
457}
458
459} // namespace ftxui
TerminalInputParser(std::function< void(Event)> out)
static Event Special(std::string)
Un événement personnalisé dont la signification est définie par l'utilisateur de la bibliothèque.
Definition event.cpp:74
Représente un événement. Il peut s'agir d'un événement de touche, d'un redimensionnement de terminal,...
Definition event.hpp:28
L'espace de noms FTXUI ftxui::
Definition animation.hpp:10
const std::map< std::string, std::string > g_uniformize