FTXUI 6.1.9
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// 本原始碼的使用受 MIT 授權條款約束,詳情請見 LICENSE 檔案。
4
5#include <cstdint> // for uint32_t
6#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Button, Mouse::Motion
7#include <functional> // for std::function
8#include <map>
9#include <memory> // for unique_ptr, allocator
10#include <utility> // for move
11#include <vector>
12#include "ftxui/component/event.hpp" // for Event
13#include "ftxui/component/task.hpp" // for Task
14
15namespace ftxui {
16
17// NOLINTNEXTLINE
18const std::map<std::string, std::string> g_uniformize = {
19 // Microsoft 的終端機對於返回鍵使用不同的換行符號。
20 // 這在 Linux 上使用 `bind` 命令時也會發生:
21 // 請參閱 https://github.com/ArthurSonzogni/FTXUI/issues/337
22 // 在此,我們將換行符號統一為 `\n`。
23 {"\r", "\n"},
24
25 // 請參閱:https://github.com/ArthurSonzogni/FTXUI/issues/508
26 {std::string({8}), std::string({127})},
27
28 // 請參閱:https://github.com/ArthurSonzogni/FTXUI/issues/626
29 //
30 // 根據游標鍵模式 (DECCKM),終端機會發送不同的逸出序列:
31 //
32 // 按鍵 普通模式 應用程式模式
33 // ----- -------- -----------
34 // 上 ESC [ A ESC O A
35 // 下 ESC [ B ESC O B
36 // 右 ESC [ C ESC O C
37 // 左 ESC [ D ESC O D
38 // Home ESC [ H ESC O H
39 // End ESC [ F ESC O F
40 //
41 {"\x1BOA", "\x1B[A"}, // 上
42 {"\x1BOB", "\x1B[B"}, // 下
43 {"\x1BOC", "\x1B[C"}, // 右
44 {"\x1BOD", "\x1B[D"}, // 左
45 {"\x1BOH", "\x1B[H"}, // Home
46 {"\x1BOF", "\x1B[F"}, // End
47
48 // FN 鍵的變體。
49 // 內部我們使用:
50 // vt220, xterm-vt200, xterm-xf86-v44, xterm-new, mgt, screen
51 // 請參閱:https://invisible-island.net/xterm/xterm-function-keys.html
52
53 // 適用於 Linux 作業系統控制台 (CTRL+ALT+FN),不屬於任何
54 // 真實標準。
55 // 請參閱:https://github.com/ArthurSonzogni/FTXUI/issues/685
56 {"\x1B[[A", "\x1BOP"}, // F1
57 {"\x1B[[B", "\x1BOQ"}, // F2
58 {"\x1B[[C", "\x1BOR"}, // F3
59 {"\x1B[[D", "\x1BOS"}, // F4
60 {"\x1B[[E", "\x1B[15~"}, // F5
61
62 // xterm-r5, xterm-r6, rxvt
63 {"\x1B[11~", "\x1BOP"}, // F1
64 {"\x1B[12~", "\x1BOQ"}, // F2
65 {"\x1B[13~", "\x1BOR"}, // F3
66 {"\x1B[14~", "\x1BOS"}, // F4
67
68 // vt100
69 {"\x1BOt", "\x1B[15~"}, // F5
70 {"\x1BOu", "\x1B[17~"}, // F6
71 {"\x1BOv", "\x1B[18~"}, // F7
72 {"\x1BOl", "\x1B[19~"}, // F8
73 {"\x1BOw", "\x1B[20~"}, // F9
74 {"\x1BOx", "\x1B[21~"}, // F10
75
76 // scoansi
77 {"\x1B[M", "\x1BOP"}, // F1
78 {"\x1B[N", "\x1BOQ"}, // F2
79 {"\x1B[O", "\x1BOR"}, // F3
80 {"\x1B[P", "\x1BOS"}, // F4
81 {"\x1B[Q", "\x1B[15~"}, // F5
82 {"\x1B[R", "\x1B[17~"}, // F6
83 {"\x1B[S", "\x1B[18~"}, // F7
84 {"\x1B[T", "\x1B[19~"}, // F8
85 {"\x1B[U", "\x1B[20~"}, // F9
86 {"\x1B[V", "\x1B[21~"}, // F10
87 {"\x1B[W", "\x1B[23~"}, // F11
88 {"\x1B[X", "\x1B[24~"}, // F12
89};
90
92 : out_(std::move(out)) {}
93
95 timeout_ += time;
96 const int timeout_threshold = 50;
97 if (timeout_ < timeout_threshold) {
98 return;
99 }
100 timeout_ = 0;
101 if (!pending_.empty()) {
102 Send(SPECIAL);
103 }
104}
105
107 pending_ += c;
108 timeout_ = 0;
109 position_ = -1;
110 Send(Parse());
111}
112
113unsigned char TerminalInputParser::Current() {
114 return pending_[position_];
115}
116
117bool TerminalInputParser::Eat() {
118 position_++;
119 return position_ < static_cast<int>(pending_.size());
120}
121
122void TerminalInputParser::Send(TerminalInputParser::Output output) {
123 switch (output.type) {
124 case UNCOMPLETED:
125 return;
126
127 case DROP:
128 pending_.clear();
129 return;
130
131 case CHARACTER:
132 out_(Event::Character(std::move(pending_)));
133 pending_.clear();
134 return;
135
136 case SPECIAL: {
137 auto it = g_uniformize.find(pending_);
138 if (it != g_uniformize.end()) {
139 pending_ = it->second;
140 }
141 out_(Event::Special(std::move(pending_)));
142 pending_.clear();
143 }
144 return;
145
146 case MOUSE:
147 out_(Event::Mouse(std::move(pending_), output.mouse)); // NOLINT
148 pending_.clear();
149 return;
150
151 case CURSOR_POSITION:
152 out_(Event::CursorPosition(std::move(pending_), // NOLINT
153 output.cursor.x, // NOLINT
154 output.cursor.y)); // NOLINT
155 pending_.clear();
156 return;
157
158 case CURSOR_SHAPE:
159 out_(Event::CursorShape(std::move(pending_), output.cursor_shape));
160 pending_.clear();
161 return;
162 }
163 // NOT_REACHED().
164}
165
166TerminalInputParser::Output TerminalInputParser::Parse() {
167 if (!Eat()) {
168 return UNCOMPLETED;
169 }
170
171 if (Current() == '\x1B') {
172 return ParseESC();
173 }
174
175 if (Current() < 32) { // C0 NOLINT
176 return SPECIAL;
177 }
178
179 if (Current() == 127) { // Delete // NOLINT
180 return SPECIAL;
181 }
182
183 return ParseUTF8();
184}
185
186// 字碼點 <-> UTF-8 轉換
187//
188// ┏━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
189// ┃位元組 1┃位元組 2┃位元組 3┃位元組 4┃
190// ┡━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
191// │0xxxxxxx│ │ │ │
192// ├────────┼────────┼────────┼────────┤
193// │110xxxxx│10xxxxxx│ │ │
194// ├────────┼────────┼────────┼────────┤
195// │1110xxxx│10xxxxxx│10xxxxxx│ │
196// ├────────┼────────┼────────┼────────┤
197// │11110xxx│10xxxxxx│10xxxxxx│10xxxxxx│
198// └────────┴────────┴────────┴────────┘
199//
200// 如果存在相同字碼點的較短表示,則某些序列是非法的。
201TerminalInputParser::Output TerminalInputParser::ParseUTF8() {
202 auto head = Current();
203 unsigned char selector = 0b1000'0000; // NOLINT
204
205 // 第一個位元組中非字碼點的部分。
206 unsigned char mask = selector;
207
208 // 在第一個位元組中找到第一個零。
209 unsigned int first_zero = 8; // NOLINT
210 for (unsigned int i = 0; i < 8; ++i) { // NOLINT
211 mask |= selector;
212 if (!(head & selector)) {
213 first_zero = i;
214 break;
215 }
216 selector >>= 1U;
217 }
218
219 // Accumulate the value of the first byte.
220 auto value = uint32_t(head & ~mask); // NOLINT
221
222 // 無效的 UTF8,超過 5 個位元組。
223 const unsigned int max_utf8_bytes = 5;
224 if (first_zero == 1 || first_zero >= max_utf8_bytes) {
225 return DROP;
226 }
227
228 // 多位元組 UTF-8。
229 for (unsigned int i = 2; i <= first_zero; ++i) {
230 if (!Eat()) {
231 return UNCOMPLETED;
232 }
233
234 // 無效的連續位元組。
235 head = Current();
236 if ((head & 0b1100'0000) != 0b1000'0000) { // NOLINT
237 return DROP;
238 }
239 value <<= 6; // NOLINT
240 value += head & 0b0011'1111; // NOLINT
241 }
242
243 // 檢查過長的 UTF8 編碼。
244 int extra_byte = 0;
245 if (value <= 0b000'0000'0111'1111) { // NOLINT
246 extra_byte = 0; // NOLINT
247 } else if (value <= 0b000'0111'1111'1111) { // NOLINT
248 extra_byte = 1; // NOLINT
249 } else if (value <= 0b1111'1111'1111'1111) { // NOLINT
250 extra_byte = 2; // NOLINT
251 } else if (value <= 0b1'0000'1111'1111'1111'1111) { // NOLINT
252 extra_byte = 3; // NOLINT
253 } else { // NOLINT
254 return DROP;
255 }
256
257 if (extra_byte != position_) {
258 return DROP;
259 }
260
261 return CHARACTER;
262}
263
264TerminalInputParser::Output TerminalInputParser::ParseESC() {
265 if (!Eat()) {
266 return UNCOMPLETED;
267 }
268 switch (Current()) {
269 case 'P':
270 return ParseDCS();
271 case '[':
272 return ParseCSI();
273 case ']':
274 return ParseOSC();
275
276 // Expecting 2 characters.
277 case ' ':
278 case '#':
279 case '%':
280 case '(':
281 case ')':
282 case '*':
283 case '+':
284 case 'O':
285 case 'N': {
286 if (!Eat()) {
287 return UNCOMPLETED;
288 }
289 return SPECIAL;
290 }
291 // Expecting 1 character:
292 default:
293 return SPECIAL;
294 }
295}
296
297// ESC P ... ESC 反斜線
298TerminalInputParser::Output TerminalInputParser::ParseDCS() {
299 // 解析直到字串終止符 ST。
300 while (true) {
301 if (!Eat()) {
302 return UNCOMPLETED;
303 }
304
305 if (Current() != '\x1B') {
306 continue;
307 }
308
309 if (!Eat()) {
310 return UNCOMPLETED;
311 }
312
313 if (Current() != '\\') {
314 continue;
315 }
316
317 if (pending_.size() == 10 && //
318 pending_[2] == '1' && //
319 pending_[3] == '$' && //
320 pending_[4] == 'r' && //
321 true) {
322 Output output(CURSOR_SHAPE);
323 output.cursor_shape = pending_[5] - '0';
324 return output;
325 }
326
327 return SPECIAL;
328 }
329}
330
331TerminalInputParser::Output TerminalInputParser::ParseCSI() {
332 bool altered = false;
333 int argument = 0;
334 std::vector<int> arguments;
335 while (true) {
336 if (!Eat()) {
337 return UNCOMPLETED;
338 }
339
340 if (Current() == '<') {
341 altered = true;
342 continue;
343 }
344
345 if (Current() >= '0' && Current() <= '9') {
346 argument *= 10; // NOLINT
347 argument += Current() - '0';
348 continue;
349 }
350
351 if (Current() == ';') {
352 arguments.push_back(argument);
353 argument = 0;
354 continue;
355 }
356
357 // CSI 由範圍 0x40–0x7E 中的字元終止
358 // (ASCII @A–Z[\^_`a–z{|}~)。
359 // 注意:我不記得為什麼我們排除了 '<'
360 // 為了處理 F1-F4,我們排除了 '['。
361 if (Current() >= '@' && Current() <= '~' &&
362 // Note: I don't remember why we exclude '<'
363 Current() != '<' &&
364 // To handle F1-F4, we exclude '['.
365 Current() != '[') {
366 arguments.push_back(argument);
367 argument = 0; // NOLINT
368
369 switch (Current()) {
370 case 'M':
371 return ParseMouse(altered, true, std::move(arguments));
372 case 'm':
373 return ParseMouse(altered, false, std::move(arguments));
374 case 'R':
375 return ParseCursorPosition(std::move(arguments));
376 default:
377 return SPECIAL;
378 }
379 }
380
381 // CSI 中無效的 ESC。
382 if (Current() == '\x1B') {
383 return SPECIAL;
384 }
385 }
386}
387
388TerminalInputParser::Output TerminalInputParser::ParseOSC() {
389 // 解析直到字串終止符 ST。
390 while (true) {
391 if (!Eat()) {
392 return UNCOMPLETED;
393 }
394 if (Current() != '\x1B') {
395 continue;
396 }
397 if (!Eat()) {
398 return UNCOMPLETED;
399 }
400 if (Current() != '\\') {
401 continue;
402 }
403 return SPECIAL;
404 }
405}
406
407TerminalInputParser::Output TerminalInputParser::ParseMouse( // NOLINT
408 bool altered,
409 bool pressed,
410 std::vector<int> arguments) {
411 if (arguments.size() != 3) {
412 return SPECIAL;
413 }
414
415 (void)altered;
416
417 Output output(MOUSE);
418 output.mouse.motion = Mouse::Motion(pressed); // NOLINT
419
420 // 位元 值 修飾符 註解
421 // ---- ----- ------- ---------
422 // 0 1 1 2 按鈕 0 = 左鍵, 1 = 中鍵, 2 = 右鍵, 3 = 釋放
423 // 2 4 Shift
424 // 3 8 Meta
425 // 4 16 Control
426 // 5 32 移動
427 // 6 64 滾輪
428
429 // clang-format off
430 const int button = arguments[0] & (1 + 2); // NOLINT
431 const bool is_shift = arguments[0] & 4; // NOLINT
432 const bool is_meta = arguments[0] & 8; // NOLINT
433 const bool is_control = arguments[0] & 16; // NOLINT
434 const bool is_move = arguments[0] & 32; // NOLINT
435 const bool is_wheel = arguments[0] & 64; // NOLINT
436 // clang-format on
437
438 output.mouse.motion = is_move ? Mouse::Moved : Mouse::Motion(pressed);
439 output.mouse.button = is_wheel ? Mouse::Button(Mouse::WheelUp + button) //
440 : Mouse::Button(button);
441 output.mouse.shift = is_shift;
442 output.mouse.meta = is_meta;
443 output.mouse.control = is_control;
444 output.mouse.x = arguments[1]; // NOLINT
445 output.mouse.y = arguments[2]; // NOLINT
446
447 // 移動事件。
448 return output;
449}
450
451// NOLINTNEXTLINE
452TerminalInputParser::Output TerminalInputParser::ParseCursorPosition(
453 std::vector<int> arguments) {
454 if (arguments.size() != 2) {
455 return SPECIAL;
456 }
457 Output output(CURSOR_POSITION);
458 output.cursor.y = arguments[0]; // NOLINT
459 output.cursor.x = arguments[1]; // NOLINT
460 return output;
461}
462
463} // namespace ftxui
TerminalInputParser(std::function< void(Event)> out)
auto button
Definition gallery.cpp:84
static Event Special(std::string)
一個自訂事件,其意義由函式庫的使用者定義。
Definition event.cpp:72
代表一個事件。它可以是按鍵事件、終端機大小調整,或更多...
Definition event.hpp:27
FTXUI 的 ftxui:: 命名空間
Definition animation.hpp:10
const std::map< std::string, std::string > g_uniformize