FTXUI 7.0.3
C++ functional terminal UI.
Loading...
Searching...
No Matches
terminal_input_parser.cpp
Go to the documentation of this file.
1// Copyright 2020 Arthur Sonzogni. All rights reserved.
2// Use of this source code is governed by the MIT license that can be found in
3// the LICENSE file.
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 // Microsoft 的終端機對於返回鍵使用不同的換行符號。
21 // 這在 Linux 上使用 `bind` 命令時也會發生:
22 // 請參閱 https://github.com/ArthurSonzogni/FTXUI/issues/337
23 // 在此,我們將換行符號統一為 `\n`。
24 {"\r", "\n"},
25
26 // 請參閱:https://github.com/ArthurSonzogni/FTXUI/issues/508
27 {std::string({8}), std::string({127})},
28
29 // 請參閱:https://github.com/ArthurSonzogni/FTXUI/issues/626
30 //
31 // 根據游標鍵模式 (DECCKM),終端機會發送不同的逸出序列:
32 //
33 // 按鍵 普通模式 應用程式模式
34 // ----- -------- -----------
35 // 上 ESC [ A ESC O A
36 // 下 ESC [ B ESC O B
37 // 右 ESC [ C ESC O C
38 // 左 ESC [ D ESC O D
39 // Home ESC [ H ESC O H
40 // End ESC [ F ESC O F
41 //
42 {"\x1BOA", "\x1B[A"}, // UP
43 {"\x1BOB", "\x1B[B"}, // DOWN
44 {"\x1BOC", "\x1B[C"}, // RIGHT
45 {"\x1BOD", "\x1B[D"}, // LEFT
46 {"\x1BOH", "\x1B[H"}, // Home
47 {"\x1BOF", "\x1B[F"}, // End
48
49 // 終端機和多工器常見的 Home/End 序列。
50 {"\x1B[1~", "\x1B[H"}, // Home
51 {"\x1B[4~", "\x1B[F"}, // End
52
53 // FN 鍵的變體。
54 // 內部我們使用:
55 // vt220, xterm-vt200, xterm-xf86-v44, xterm-new, mgt, screen
56 // 請參閱:https://invisible-island.net/xterm/xterm-function-keys.html
57
58 // 適用於 Linux 作業系統控制台 (CTRL+ALT+FN),不屬於任何
59 // 真實標準。
60 // 請參閱:https://github.com/ArthurSonzogni/FTXUI/issues/685
61 {"\x1B[[A", "\x1BOP"}, // F1
62 {"\x1B[[B", "\x1BOQ"}, // F2
63 {"\x1B[[C", "\x1BOR"}, // F3
64 {"\x1B[[D", "\x1BOS"}, // F4
65 {"\x1B[[E", "\x1B[15~"}, // F5
66
67 // xterm-r5, xterm-r6, rxvt
68 {"\x1B[11~", "\x1BOP"}, // F1
69 {"\x1B[12~", "\x1BOQ"}, // F2
70 {"\x1B[13~", "\x1BOR"}, // F3
71 {"\x1B[14~", "\x1BOS"}, // F4
72
73 // vt100
74 {"\x1BOt", "\x1B[15~"}, // F5
75 {"\x1BOu", "\x1B[17~"}, // F6
76 {"\x1BOv", "\x1B[18~"}, // F7
77 {"\x1BOl", "\x1B[19~"}, // F8
78 {"\x1BOw", "\x1B[20~"}, // F9
79 {"\x1BOx", "\x1B[21~"}, // F10
80
81 // scoansi
82 {"\x1B[M", "\x1BOP"}, // F1
83 {"\x1B[N", "\x1BOQ"}, // F2
84 {"\x1B[O", "\x1BOR"}, // F3
85 {"\x1B[P", "\x1BOS"}, // F4
86 {"\x1B[Q", "\x1B[15~"}, // F5
87 {"\x1B[R", "\x1B[17~"}, // F6
88 {"\x1B[S", "\x1B[18~"}, // F7
89 {"\x1B[T", "\x1B[19~"}, // F8
90 {"\x1B[U", "\x1B[20~"}, // F9
91 {"\x1B[V", "\x1B[21~"}, // F10
92 {"\x1B[W", "\x1B[23~"}, // F11
93 {"\x1B[X", "\x1B[24~"}, // F12
94};
95
96TerminalInputParser::TerminalInputParser(std::function<void(Event)> out)
97 : out_(std::move(out)) {}
98
100 timeout_ += time;
101 const int timeout_threshold = 50;
102 if (timeout_ < timeout_threshold) {
103 return;
104 }
105 timeout_ = 0;
106 if (!pending_.empty()) {
107 Send(SPECIAL);
108 }
109}
110
112 pending_ += c;
113 timeout_ = 0;
114 position_ = -1;
115 Send(Parse());
116}
117
118unsigned char TerminalInputParser::Current() {
119 return pending_[position_];
120}
121
122bool TerminalInputParser::Eat() {
123 position_++;
124 return position_ < static_cast<int>(pending_.size());
125}
126
127void TerminalInputParser::Send(TerminalInputParser::Output output) {
128 switch (output.type) {
129 case UNCOMPLETED:
130 return;
131
132 case DROP:
133 pending_.clear();
134 return;
135
136 case RESYNC: {
137 // 目前累積的位元組,無法被 |position_| 上的那個位元組
138 // 延續,該位元組開啟了一個新的序列。發出截斷後的前綴,並
139 // 重新解析剩餘的位元組。
140 std::string next = pending_.substr(position_);
141 pending_.resize(position_);
142 Send(SPECIAL);
143 pending_ = std::move(next);
144 position_ = -1;
145 Send(Parse());
146 return;
147 }
148
149 case CHARACTER:
150 out_(Event::Character(std::move(pending_)));
151 pending_.clear();
152 return;
153
154 case SPECIAL: {
155 auto it = g_uniformize.find(pending_);
156 if (it != g_uniformize.end()) {
157 pending_ = it->second;
158 }
159 out_(Event::Special(std::move(pending_)));
160 pending_.clear();
161 }
162 return;
163
164 case MOUSE:
165 out_(Event::Mouse(std::move(pending_), output.mouse)); // NOLINT
166 pending_.clear();
167 return;
168
169 case CURSOR_POSITION:
170 out_(Event::CursorPosition(std::move(pending_), // NOLINT
171 output.cursor.x, // NOLINT
172 output.cursor.y)); // NOLINT
173 pending_.clear();
174 return;
175
176 case CURSOR_SHAPE:
177 out_(Event::CursorShape(std::move(pending_), output.cursor_shape));
178 pending_.clear();
179 return;
180
181 case TERMINAL_NAME_VERSION:
182 out_(Event::TerminalNameVersion(std::move(pending_),
183 std::move(output.terminal_name),
184 output.terminal_version));
185 pending_.clear();
186 return;
187
188 case TERMINAL_EMULATOR:
189 out_(Event::TerminalEmulator(std::move(pending_),
190 std::move(output.terminal_name),
191 std::move(output.terminal_version_string)));
192 pending_.clear();
193 return;
194
195 case TERMINAL_CAPABILITIES:
196 out_(Event::TerminalCapabilities(
197 std::move(pending_), std::move(output.terminal_capabilities)));
198 pending_.clear();
199 return;
200 }
201 // NOT_REACHED()。
202}
203
204TerminalInputParser::Output TerminalInputParser::Parse() {
205 if (!Eat()) {
206 return UNCOMPLETED;
207 }
208
209 if (Current() == '\x1B') {
210 return ParseESC();
211 }
212
213 if (Current() < 32) { // C0 NOLINT
214 return SPECIAL;
215 }
216
217 if (Current() == 127) { // Delete // NOLINT
218 return SPECIAL;
219 }
220
221 return ParseUTF8();
222}
223
224// 字碼點 <-> UTF-8 轉換
225//
226// ┏━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
227// ┃位元組 1┃位元組 2┃位元組 3┃位元組 4┃
228// ┡━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
229// │0xxxxxxx│ │ │ │
230// ├────────┼────────┼────────┼────────┤
231// │110xxxxx│10xxxxxx│ │ │
232// ├────────┼────────┼────────┼────────┤
233// │1110xxxx│10xxxxxx│10xxxxxx│ │
234// ├────────┼────────┼────────┼────────┤
235// │11110xxx│10xxxxxx│10xxxxxx│10xxxxxx│
236// └────────┴────────┴────────┴────────┘
237//
238// 如果存在相同字碼點的較短表示,則某些序列是非法的。
239TerminalInputParser::Output TerminalInputParser::ParseUTF8() {
240 auto head = Current();
241 unsigned char selector = 0b1000'0000; // NOLINT
242
243 // 第一個位元組中非 code-point 的部分。
244 unsigned char mask = selector;
245
246 // 在第一個位元組中找到第一個零。
247 unsigned int first_zero = 8; // NOLINT
248 for (unsigned int i = 0; i < 8; ++i) { // NOLINT
249 mask |= selector;
250 if (!(head & selector)) {
251 first_zero = i;
252 break;
253 }
254 selector >>= 1U;
255 }
256
257 // 累積第一個位元組的值。
258 auto value = uint32_t(head & ~mask); // NOLINT
259
260 // 無效的 UTF8,超過 5 個位元組。
261 const unsigned int max_utf8_bytes = 5;
262 if (first_zero == 1 || first_zero >= max_utf8_bytes) {
263 return DROP;
264 }
265
266 // 多位元組 UTF-8。
267 for (unsigned int i = 2; i <= first_zero; ++i) {
268 if (!Eat()) {
269 return UNCOMPLETED;
270 }
271
272 // 無效的延續位元組。
273 head = Current();
274 if ((head & 0b1100'0000) != 0b1000'0000) { // NOLINT
275 return DROP;
276 }
277 value <<= 6; // NOLINT
278 value += head & 0b0011'1111; // NOLINT
279 }
280
281 // 檢查過長的 UTF8 編碼。
282 int extra_byte = 0;
283 if (value <= 0b000'0000'0111'1111) { // NOLINT
284 extra_byte = 0; // NOLINT
285 } else if (value <= 0b000'0111'1111'1111) { // NOLINT
286 extra_byte = 1; // NOLINT
287 } else if (value <= 0b1111'1111'1111'1111) { // NOLINT
288 extra_byte = 2; // NOLINT
289 } else if (value <= 0b1'0000'1111'1111'1111'1111) { // NOLINT
290 extra_byte = 3; // NOLINT
291 } else { // NOLINT
292 return DROP;
293 }
294
295 if (extra_byte != position_) {
296 return DROP;
297 }
298
299 return CHARACTER;
300}
301
302TerminalInputParser::Output TerminalInputParser::ParseESC() {
303 if (!Eat()) {
304 return UNCOMPLETED;
305 }
306 switch (Current()) {
307 case 'P':
308 return ParseDCS();
309 case '[':
310 return ParseCSI();
311 case ']':
312 return ParseOSC();
313
314 // 序列內不允許出現 ESC。這個會開啟一個新的序列。
315 case '\x1B':
316 return RESYNC;
317
318 // 預期 2 個字元。
319 case ' ':
320 case '#':
321 case '%':
322 case '(':
323 case ')':
324 case '*':
325 case '+':
326 case 'O':
327 case 'N': {
328 if (!Eat()) {
329 return UNCOMPLETED;
330 }
331 if (Current() == '\x1B') {
332 return RESYNC;
333 }
334 return SPECIAL;
335 }
336 // 預期 1 個字元:
337 default:
338 return SPECIAL;
339 }
340}
341
342// ESC P ... ESC 反斜線
343TerminalInputParser::Output TerminalInputParser::ParseDCS() {
344 // 解析直到字串終止符 ST。
345 while (true) {
346 if (!Eat()) {
347 return UNCOMPLETED;
348 }
349
350 if (Current() != '\x1B') {
351 continue;
352 }
353
354 if (!Eat()) {
355 return UNCOMPLETED;
356 }
357
358 if (Current() != '\\') {
359 continue;
360 }
361
362 // XTVERSION: ESC P > | name version ST
363 if (pending_.size() >= 5 && pending_[2] == '>' && pending_[3] == '|') {
364 // ESC P > | name (version) ST
365 // 0 1 2 3 4
366 const std::string content = pending_.substr(4, pending_.size() - 6);
367 Output output(TERMINAL_EMULATOR);
368 const size_t space = content.find(' ');
369 const size_t open_paren = content.find('(');
370 if (space != std::string::npos) {
371 output.terminal_name = content.substr(0, space);
372 output.terminal_version_string = content.substr(space + 1);
373 } else if (open_paren != std::string::npos) {
374 output.terminal_name = content.substr(0, open_paren);
375 output.terminal_version_string = content.substr(open_paren + 1);
376 if (!output.terminal_version_string.empty() &&
377 output.terminal_version_string.back() == ')') {
378 output.terminal_version_string.pop_back();
379 }
380 } else {
381 output.terminal_name = content;
382 output.terminal_version_string = "unknown";
383 }
384 return output;
385 }
386
387 if (pending_.size() == 10 && //
388 pending_[2] == '1' && //
389 pending_[3] == '$' && //
390 pending_[4] == 'r' && //
391 true) {
392 Output output(CURSOR_SHAPE);
393 output.cursor_shape = pending_[5] - '0';
394 return output;
395 }
396
397 return SPECIAL;
398 }
399}
400
401TerminalInputParser::Output TerminalInputParser::ParseCSI() {
402 bool altered_less = false;
403 bool altered_greater = false;
404 bool altered_question = false;
405 int argument = 0;
406 std::vector<int> arguments;
407 while (true) {
408 if (!Eat()) {
409 return UNCOMPLETED;
410 }
411
412 if (Current() == '<') {
413 altered_less = true;
414 continue;
415 }
416
417 if (Current() == '>') {
418 altered_greater = true;
419 continue;
420 }
421
422 if (Current() == '?') {
423 altered_question = true;
424 continue;
425 }
426
427 if (Current() >= '0' && Current() <= '9') {
428 argument *= 10; // NOLINT
429 argument += Current() - '0';
430 continue;
431 }
432
433 if (Current() == ';') {
434 arguments.push_back(argument);
435 argument = 0;
436 continue;
437 }
438
439 // CSI 由範圍 0x40–0x7E 中的字元終止
440 // (ASCII @A–Z[\^_`a–z{|}~)。
441 // 注意:我不記得為什麼我們排除了 '<'
442 // 為了處理 F1-F4,我們排除了 '['。
443 if (Current() >= '@' && Current() <= '~' &&
444 // 註:我不記得為什麼要排除 '<'
445 Current() != '<' &&
446 // 為了處理 F1-F4,我們排除 '['。
447 Current() != '[') {
448 arguments.push_back(argument);
449 argument = 0; // NOLINT
450
451 switch (Current()) {
452 case 'M':
453 return ParseMouse(altered_less, true, std::move(arguments));
454 case 'm':
455 return ParseMouse(altered_less, false, std::move(arguments));
456 case 'R':
457 return ParseCursorPosition(std::move(arguments));
458 case 'c':
459 return ParseDeviceAttributes(altered_greater, altered_question,
460 std::move(arguments));
461 default:
462 return SPECIAL;
463 }
464 }
465
466 // CSI 中出現無效的 ESC。它會開啟一個新的序列。
467 if (Current() == '\x1B') {
468 return RESYNC;
469 }
470 }
471}
472
473TerminalInputParser::Output TerminalInputParser::ParseOSC() {
474 // 解析直到字串終止符 ST。
475 while (true) {
476 if (!Eat()) {
477 return UNCOMPLETED;
478 }
479 if (Current() != '\x1B') {
480 continue;
481 }
482 if (!Eat()) {
483 return UNCOMPLETED;
484 }
485 if (Current() != '\\') {
486 continue;
487 }
488 return SPECIAL;
489 }
490}
491
492TerminalInputParser::Output TerminalInputParser::ParseMouse( // NOLINT
493 bool altered,
494 bool pressed,
495 std::vector<int> arguments) {
496 if (arguments.size() != 3) {
497 return SPECIAL;
498 }
499
500 (void)altered;
501
502 Output output(MOUSE);
503 output.mouse.motion = Mouse::Motion(pressed); // NOLINT
504
505 // Bits value Modifier Comment
506 // ---- ----- ------- ---------
507 // 0 1 1 2 button 0 = Left, 1 = Middle, 2 = Right, 3 = Release
508 // 2 4 Shift
509 // 3 8 Meta
510 // 4 16 Control
511 // 5 32 Move
512 // 6 64 Wheel
513
514 // clang-format off
515 const int button = arguments[0] & (1 + 2); // NOLINT
516 const bool is_shift = arguments[0] & 4; // NOLINT
517 const bool is_meta = arguments[0] & 8; // NOLINT
518 const bool is_control = arguments[0] & 16; // NOLINT
519 const bool is_move = arguments[0] & 32; // NOLINT
520 const bool is_wheel = arguments[0] & 64; // NOLINT
521 // clang-format on
522
523 output.mouse.motion = is_move ? Mouse::Moved : Mouse::Motion(pressed);
524 output.mouse.button = is_wheel ? Mouse::Button(Mouse::WheelUp + button) //
525 : Mouse::Button(button);
526 output.mouse.shift = is_shift;
527 output.mouse.meta = is_meta;
528 output.mouse.control = is_control;
529 output.mouse.x = arguments[1]; // NOLINT
530 output.mouse.y = arguments[2]; // NOLINT
531
532 // 移動事件。
533 return output;
534}
535
536// NOLINTNEXTLINE
537TerminalInputParser::Output TerminalInputParser::ParseCursorPosition(
538 std::vector<int> arguments) {
539 if (arguments.size() != 2) {
540 return SPECIAL;
541 }
542 Output output(CURSOR_POSITION);
543 output.cursor.y = arguments[0]; // NOLINT
544 output.cursor.x = arguments[1]; // NOLINT
545 return output;
546}
547
548// NOLINTNEXTLINE
549TerminalInputParser::Output TerminalInputParser::ParseDeviceAttributes(
550 bool altered_greater,
551 bool altered_question,
552 std::vector<int> arguments) {
553 if (altered_greater) {
554 // 次要裝置屬性 (DA2)
555 // ESC [ > Pp ; Pv ; Pc c
556 if (arguments.size() >= 3) {
557 // Pp: 終端機類型
558 // Pv: 韌體版本
559 // Pc: 硬體選項
560 Output output(TERMINAL_NAME_VERSION);
561 output.terminal_version = arguments[1];
562 switch (arguments[0]) {
563 case 0:
564 output.terminal_name = "xterm";
565 break;
566 case 1:
567 output.terminal_name = "vt220";
568 break;
569 case 2:
570 output.terminal_name = "vt240";
571 break;
572 case 18:
573 output.terminal_name = "vt330";
574 break;
575 case 19:
576 output.terminal_name = "vt340";
577 break;
578 case 24:
579 output.terminal_name = "vt320";
580 break;
581 case 41:
582 output.terminal_name = "vt420";
583 break;
584 case 61:
585 output.terminal_name = "vt510";
586 break;
587 case 64:
588 output.terminal_name = "vt520";
589 break;
590 case 65:
591 output.terminal_name = "vt525";
592 break;
593 case 84:
594 output.terminal_name = "tmux";
595 break;
596 case 85:
597 output.terminal_name = "urxvt";
598 break;
599 default:
600 output.terminal_name = "unknown";
601 break;
602 }
603 // xterm 的特殊情況,它經常回傳 0;pv;0 或類似的值
604 // 但這並不嚴格遵循 DEC VT 類型。
605 return output;
606 }
607 } else if (altered_question) {
608 // 主要裝置屬性 (DA1)
609 // ESC [ ? Pp ; ... c
610 Output output(TERMINAL_CAPABILITIES);
611 output.terminal_capabilities = std::move(arguments);
612 return output;
613 }
614 return SPECIAL;
615}
616
617} // namespace ftxui
TerminalInputParser(std::function< void(Event)> out)
FTXUI ftxui:: 命名空間
Definition animation.hpp:11
const std::map< std::string, std::string > g_uniformize
int value
Definition elements.hpp:192