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のターミナルは、returnキーに異なる改行文字を使用します。
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 OSコンソール (CTRL+ALT+FN) の場合。
59 // 詳細: https://github.com/ArthurSonzogni/FTXUI/issues/685
60 {"\x1B[[A", "\x1BOP"}, // F1
61 {"\x1B[[B", "\x1BOQ"}, // F2
62 {"\x1B[[C", "\x1BOR"}, // F3
63 {"\x1B[[D", "\x1BOS"}, // F4
64 {"\x1B[[E", "\x1B[15~"}, // F5
65
66 // xterm-r5, xterm-r6, rxvt
67 {"\x1B[11~", "\x1BOP"}, // F1
68 {"\x1B[12~", "\x1BOQ"}, // F2
69 {"\x1B[13~", "\x1BOR"}, // F3
70 {"\x1B[14~", "\x1BOS"}, // F4
71
72 // vt100
73 {"\x1BOt", "\x1B[15~"}, // F5
74 {"\x1BOu", "\x1B[17~"}, // F6
75 {"\x1BOv", "\x1B[18~"}, // F7
76 {"\x1BOl", "\x1B[19~"}, // F8
77 {"\x1BOw", "\x1B[20~"}, // F9
78 {"\x1BOx", "\x1B[21~"}, // F10
79
80 // scoansi
81 {"\x1B[M", "\x1BOP"}, // F1
82 {"\x1B[N", "\x1BOQ"}, // F2
83 {"\x1B[O", "\x1BOR"}, // F3
84 {"\x1B[P", "\x1BOS"}, // F4
85 {"\x1B[Q", "\x1B[15~"}, // F5
86 {"\x1B[R", "\x1B[17~"}, // F6
87 {"\x1B[S", "\x1B[18~"}, // F7
88 {"\x1B[T", "\x1B[19~"}, // F8
89 {"\x1B[U", "\x1B[20~"}, // F9
90 {"\x1B[V", "\x1B[21~"}, // F10
91 {"\x1B[W", "\x1B[23~"}, // F11
92 {"\x1B[X", "\x1B[24~"}, // F12
93};
94
95TerminalInputParser::TerminalInputParser(std::function<void(Event)> out)
96 : out_(std::move(out)) {}
97
99 timeout_ += time;
100 const int timeout_threshold = 50;
101 if (timeout_ < timeout_threshold) {
102 return;
103 }
104 timeout_ = 0;
105 if (!pending_.empty()) {
106 Send(SPECIAL);
107 }
108}
109
111 pending_ += c;
112 timeout_ = 0;
113 position_ = -1;
114 Send(Parse());
115}
116
117unsigned char TerminalInputParser::Current() {
118 return pending_[position_];
119}
120
121bool TerminalInputParser::Eat() {
122 position_++;
123 return position_ < static_cast<int>(pending_.size());
124}
125
126void TerminalInputParser::Send(TerminalInputParser::Output output) {
127 switch (output.type) {
128 case UNCOMPLETED:
129 return;
130
131 case DROP:
132 pending_.clear();
133 return;
134
135 case RESYNC: {
136 // これまでに蓄積されたバイト列は、|position_|にあるバイトで
137 // 継続できず、新しいシーケンスを開始する。切り詰められた接頭辞を
138 // 発行し、残りのバイトを再度パースする。
139 std::string next = pending_.substr(position_);
140 pending_.resize(position_);
141 Send(SPECIAL);
142 pending_ = std::move(next);
143 position_ = -1;
144 Send(Parse());
145 return;
146 }
147
148 case CHARACTER:
149 out_(Event::Character(std::move(pending_)));
150 pending_.clear();
151 return;
152
153 case SPECIAL: {
154 auto it = g_uniformize.find(pending_);
155 if (it != g_uniformize.end()) {
156 pending_ = it->second;
157 }
158 out_(Event::Special(std::move(pending_)));
159 pending_.clear();
160 }
161 return;
162
163 case MOUSE:
164 out_(Event::Mouse(std::move(pending_), output.mouse)); // NOLINT
165 pending_.clear();
166 return;
167
168 case CURSOR_POSITION:
169 out_(Event::CursorPosition(std::move(pending_), // NOLINT
170 output.cursor.x, // NOLINT
171 output.cursor.y)); // NOLINT
172 pending_.clear();
173 return;
174
175 case CURSOR_SHAPE:
176 out_(Event::CursorShape(std::move(pending_), output.cursor_shape));
177 pending_.clear();
178 return;
179
180 case TERMINAL_NAME_VERSION:
181 out_(Event::TerminalNameVersion(std::move(pending_),
182 std::move(output.terminal_name),
183 output.terminal_version));
184 pending_.clear();
185 return;
186
187 case TERMINAL_EMULATOR:
188 out_(Event::TerminalEmulator(std::move(pending_),
189 std::move(output.terminal_name),
190 std::move(output.terminal_version_string)));
191 pending_.clear();
192 return;
193
194 case TERMINAL_CAPABILITIES:
195 out_(Event::TerminalCapabilities(
196 std::move(pending_), std::move(output.terminal_capabilities)));
197 pending_.clear();
198 return;
199 }
200 // NOT_REACHED()。
201}
202
203TerminalInputParser::Output TerminalInputParser::Parse() {
204 if (!Eat()) {
205 return UNCOMPLETED;
206 }
207
208 if (Current() == '\x1B') {
209 return ParseESC();
210 }
211
212 if (Current() < 32) { // C0 NOLINT
213 return SPECIAL;
214 }
215
216 if (Current() == 127) { // Delete // NOLINT
217 return SPECIAL;
218 }
219
220 return ParseUTF8();
221}
222
223// コードポイント <-> UTF-8変換
224//
225// ┏━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
226// ┃Byte 1 ┃Byte 2 ┃Byte 3 ┃Byte 4 ┃
227// ┡━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
228// │0xxxxxxx│ │ │ │
229// ├────────┼────────┼────────┼────────┤
230// │110xxxxx│10xxxxxx│ │ │
231// ├────────┼────────┼────────┼────────┤
232// │1110xxxx│10xxxxxx│10xxxxxx│ │
233// ├────────┼────────┼────────┼────────┤
234// │11110xxx│10xxxxxx│10xxxxxx│10xxxxxx│
235// └────────┴────────┴────────┴────────┘
236//
237// 同じコードポイントのより短い表現が存在する場合、一部のシーケンスは不正になります。
238TerminalInputParser::Output TerminalInputParser::ParseUTF8() {
239 auto head = Current();
240 unsigned char selector = 0b1000'0000; // NOLINT
241
242 // 最初のバイトのコードポイント以外の部分。
243 unsigned char mask = selector;
244
245 // 最初のバイトの中の最初のゼロを見つける。
246 unsigned int first_zero = 8; // NOLINT
247 for (unsigned int i = 0; i < 8; ++i) { // NOLINT
248 mask |= selector;
249 if (!(head & selector)) {
250 first_zero = i;
251 break;
252 }
253 selector >>= 1U;
254 }
255
256 // 最初のバイトの値を累積する。
257 auto value = uint32_t(head & ~mask); // NOLINT
258
259 // 5バイトを超える無効なUTF8。
260 const unsigned int max_utf8_bytes = 5;
261 if (first_zero == 1 || first_zero >= max_utf8_bytes) {
262 return DROP;
263 }
264
265 // マルチバイトUTF-8。
266 for (unsigned int i = 2; i <= first_zero; ++i) {
267 if (!Eat()) {
268 return UNCOMPLETED;
269 }
270
271 // 無効な継続バイト。
272 head = Current();
273 if ((head & 0b1100'0000) != 0b1000'0000) { // NOLINT
274 return DROP;
275 }
276 value <<= 6; // NOLINT
277 value += head & 0b0011'1111; // NOLINT
278 }
279
280 // オーバーロングUTF8エンコーディングのチェック。
281 int extra_byte = 0;
282 if (value <= 0b000'0000'0111'1111) { // NOLINT
283 extra_byte = 0; // NOLINT
284 } else if (value <= 0b000'0111'1111'1111) { // NOLINT
285 extra_byte = 1; // NOLINT
286 } else if (value <= 0b1111'1111'1111'1111) { // NOLINT
287 extra_byte = 2; // NOLINT
288 } else if (value <= 0b1'0000'1111'1111'1111'1111) { // NOLINT
289 extra_byte = 3; // NOLINT
290 } else { // NOLINT
291 return DROP;
292 }
293
294 if (extra_byte != position_) {
295 return DROP;
296 }
297
298 return CHARACTER;
299}
300
301TerminalInputParser::Output TerminalInputParser::ParseESC() {
302 if (!Eat()) {
303 return UNCOMPLETED;
304 }
305 switch (Current()) {
306 case 'P':
307 return ParseDCS();
308 case '[':
309 return ParseCSI();
310 case ']':
311 return ParseOSC();
312
313 // ESCはシーケンス内では許可されない。これは新しいシーケンスを開始する。
314 case '\x1B':
315 return RESYNC;
316
317 // 2文字を期待している。
318 case ' ':
319 case '#':
320 case '%':
321 case '(':
322 case ')':
323 case '*':
324 case '+':
325 case 'O':
326 case 'N': {
327 if (!Eat()) {
328 return UNCOMPLETED;
329 }
330 if (Current() == '\x1B') {
331 return RESYNC;
332 }
333 return SPECIAL;
334 }
335 // 1文字を期待している:
336 default:
337 return SPECIAL;
338 }
339}
340
341// ESC P ... ESC BACKSLASH
342TerminalInputParser::Output TerminalInputParser::ParseDCS() {
343 // 文字列終端子STまでパースする。
344 while (true) {
345 if (!Eat()) {
346 return UNCOMPLETED;
347 }
348
349 if (Current() != '\x1B') {
350 continue;
351 }
352
353 if (!Eat()) {
354 return UNCOMPLETED;
355 }
356
357 if (Current() != '\\') {
358 continue;
359 }
360
361 // XTVERSION: ESC P > | name version ST
362 if (pending_.size() >= 5 && pending_[2] == '>' && pending_[3] == '|') {
363 // ESC P > | name (version) ST
364 // 0 1 2 3 4
365 const std::string content = pending_.substr(4, pending_.size() - 6);
366 Output output(TERMINAL_EMULATOR);
367 const size_t space = content.find(' ');
368 const size_t open_paren = content.find('(');
369 if (space != std::string::npos) {
370 output.terminal_name = content.substr(0, space);
371 output.terminal_version_string = content.substr(space + 1);
372 } else if (open_paren != std::string::npos) {
373 output.terminal_name = content.substr(0, open_paren);
374 output.terminal_version_string = content.substr(open_paren + 1);
375 if (!output.terminal_version_string.empty() &&
376 output.terminal_version_string.back() == ')') {
377 output.terminal_version_string.pop_back();
378 }
379 } else {
380 output.terminal_name = content;
381 output.terminal_version_string = "unknown";
382 }
383 return output;
384 }
385
386 if (pending_.size() == 10 && //
387 pending_[2] == '1' && //
388 pending_[3] == '$' && //
389 pending_[4] == 'r' && //
390 true) {
391 Output output(CURSOR_SHAPE);
392 output.cursor_shape = pending_[5] - '0';
393 return output;
394 }
395
396 return SPECIAL;
397 }
398}
399
400TerminalInputParser::Output TerminalInputParser::ParseCSI() {
401 bool altered_less = false;
402 bool altered_greater = false;
403 bool altered_question = false;
404 int argument = 0;
405 std::vector<int> arguments;
406 while (true) {
407 if (!Eat()) {
408 return UNCOMPLETED;
409 }
410
411 if (Current() == '<') {
412 altered_less = true;
413 continue;
414 }
415
416 if (Current() == '>') {
417 altered_greater = true;
418 continue;
419 }
420
421 if (Current() == '?') {
422 altered_question = true;
423 continue;
424 }
425
426 if (Current() >= '0' && Current() <= '9') {
427 argument *= 10; // NOLINT
428 argument += Current() - '0';
429 continue;
430 }
431
432 if (Current() == ';') {
433 arguments.push_back(argument);
434 argument = 0;
435 continue;
436 }
437
438 // CSIは0x40-0x7Eの範囲の文字で終了します。
439 // (ASCII @A–Z[\\\]^_`a–z{|}~),
440 if (Current() >= '@' && Current() <= '~' &&
441 // 注:なぜ '<' を除外するのか覚えていない
442 Current() != '<' &&
443 // F1-F4を処理するために、'['を除外する。
444 Current() != '[') {
445 arguments.push_back(argument);
446 argument = 0; // NOLINT
447
448 switch (Current()) {
449 case 'M':
450 return ParseMouse(altered_less, true, std::move(arguments));
451 case 'm':
452 return ParseMouse(altered_less, false, std::move(arguments));
453 case 'R':
454 return ParseCursorPosition(std::move(arguments));
455 case 'c':
456 return ParseDeviceAttributes(altered_greater, altered_question,
457 std::move(arguments));
458 default:
459 return SPECIAL;
460 }
461 }
462
463 // CSI内の無効なESC。これは新しいシーケンスを開始する。
464 if (Current() == '\x1B') {
465 return RESYNC;
466 }
467 }
468}
469
470TerminalInputParser::Output TerminalInputParser::ParseOSC() {
471 // 文字列終端子STまでパースする。
472 while (true) {
473 if (!Eat()) {
474 return UNCOMPLETED;
475 }
476 if (Current() != '\x1B') {
477 continue;
478 }
479 if (!Eat()) {
480 return UNCOMPLETED;
481 }
482 if (Current() != '\\') {
483 continue;
484 }
485 return SPECIAL;
486 }
487}
488
489TerminalInputParser::Output TerminalInputParser::ParseMouse( // NOLINT
490 bool altered,
491 bool pressed,
492 std::vector<int> arguments) {
493 if (arguments.size() != 3) {
494 return SPECIAL;
495 }
496
497 (void)altered;
498
499 Output output(MOUSE);
500 output.mouse.motion = Mouse::Motion(pressed); // NOLINT
501
502 // Bits value Modifier Comment
503 // ---- ----- ------- ---------
504 // 0 1 1 2 button 0 = Left, 1 = Middle, 2 = Right, 3 = Release
505 // 2 4 Shift
506 // 3 8 Meta
507 // 4 16 Control
508 // 5 32 Move
509 // 6 64 Wheel
510
511 // clang-format off
512 const int button = arguments[0] & (1 + 2); // NOLINT
513 const bool is_shift = arguments[0] & 4; // NOLINT
514 const bool is_meta = arguments[0] & 8; // NOLINT
515 const bool is_control = arguments[0] & 16; // NOLINT
516 const bool is_move = arguments[0] & 32; // NOLINT
517 const bool is_wheel = arguments[0] & 64; // NOLINT
518 // clang-format on
519
520 output.mouse.motion = is_move ? Mouse::Moved : Mouse::Motion(pressed);
521 output.mouse.button = is_wheel ? Mouse::Button(Mouse::WheelUp + button) //
522 : Mouse::Button(button);
523 output.mouse.shift = is_shift;
524 output.mouse.meta = is_meta;
525 output.mouse.control = is_control;
526 output.mouse.x = arguments[1]; // NOLINT
527 output.mouse.y = arguments[2]; // NOLINT
528
529 // 移動イベント。
530 return output;
531}
532
533// NOLINTNEXTLINE
534TerminalInputParser::Output TerminalInputParser::ParseCursorPosition(
535 std::vector<int> arguments) {
536 if (arguments.size() != 2) {
537 return SPECIAL;
538 }
539 Output output(CURSOR_POSITION);
540 output.cursor.y = arguments[0]; // NOLINT
541 output.cursor.x = arguments[1]; // NOLINT
542 return output;
543}
544
545// NOLINTNEXTLINE
546TerminalInputParser::Output TerminalInputParser::ParseDeviceAttributes(
547 bool altered_greater,
548 bool altered_question,
549 std::vector<int> arguments) {
550 if (altered_greater) {
551 // 二次デバイス属性 (DA2)
552 // ESC [ > Pp ; Pv ; Pc c
553 if (arguments.size() >= 3) {
554 // Pp: 端末種別
555 // Pv: ファームウェアバージョン
556 // Pc: ハードウェアオプション
557 Output output(TERMINAL_NAME_VERSION);
558 output.terminal_version = arguments[1];
559 switch (arguments[0]) {
560 case 0:
561 output.terminal_name = "xterm";
562 break;
563 case 1:
564 output.terminal_name = "vt220";
565 break;
566 case 2:
567 output.terminal_name = "vt240";
568 break;
569 case 18:
570 output.terminal_name = "vt330";
571 break;
572 case 19:
573 output.terminal_name = "vt340";
574 break;
575 case 24:
576 output.terminal_name = "vt320";
577 break;
578 case 41:
579 output.terminal_name = "vt420";
580 break;
581 case 61:
582 output.terminal_name = "vt510";
583 break;
584 case 64:
585 output.terminal_name = "vt520";
586 break;
587 case 65:
588 output.terminal_name = "vt525";
589 break;
590 case 84:
591 output.terminal_name = "tmux";
592 break;
593 case 85:
594 output.terminal_name = "urxvt";
595 break;
596 default:
597 output.terminal_name = "unknown";
598 break;
599 }
600 // xtermの特殊ケースで、0;pv;0のような値をよく返すが、
601 // 厳密にDEC VTタイプに従っているわけではない。
602 return output;
603 }
604 } else if (altered_question) {
605 // 一次デバイス属性 (DA1)
606 // ESC [ ? Pp ; ... c
607 Output output(TERMINAL_CAPABILITIES);
608 output.terminal_capabilities = std::move(arguments);
609 return output;
610 }
611 return SPECIAL;
612}
613
614} // 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