FTXUI  4.1.0
C++ functional terminal UI.
Loading...
Searching...
No Matches
terminal_input_parser.cpp
Go to the documentation of this file.
2
3#include <cstdint> // for uint32_t
4#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Button, Mouse::Motion
5#include <ftxui/component/receiver.hpp> // for SenderImpl, Sender
6#include <map>
7#include <memory> // for unique_ptr, allocator
8#include <utility> // for move
9
10#include "ftxui/component/event.hpp" // for Event
11#include "ftxui/component/task.hpp" // for Task
12
13namespace ftxui {
14
15// NOLINTNEXTLINE
16const std::map<std::string, std::string> g_uniformize = {
17 // Microsoft's terminal uses a different new line character for the return
18 // key. This also happens with linux with the `bind` command:
19 // See https://github.com/ArthurSonzogni/FTXUI/issues/337
20 // Here, we uniformize the new line character to `\n`.
21 {"\r", "\n"},
22
23 // See: https://github.com/ArthurSonzogni/FTXUI/issues/508
24 {std::string({8}), std::string({127})},
25
26 // See: https://github.com/ArthurSonzogni/FTXUI/issues/626
27 //
28 // Depending on the Cursor Key Mode (DECCKM), the terminal sends different
29 // escape sequences:
30 //
31 // Key Normal Application
32 // ----- -------- -----------
33 // Up ESC [ A ESC O A
34 // Down ESC [ B ESC O B
35 // Right ESC [ C ESC O C
36 // Left ESC [ D ESC O D
37 // Home ESC [ H ESC O H
38 // End ESC [ F ESC O F
39 //
40 {"\x1BOA", "\x1B[A"}, // UP
41 {"\x1BOB", "\x1B[B"}, // DOWN
42 {"\x1BOC", "\x1B[C"}, // RIGHT
43 {"\x1BOD", "\x1B[D"}, // LEFT
44 {"\x1BOH", "\x1B[H"}, // HOME
45 {"\x1BOF", "\x1B[F"}, // END
46
47};
48
51
53 timeout_ += time;
54 const int timeout_threshold = 50;
55 if (timeout_ < timeout_threshold) {
56 return;
57 }
58 timeout_ = 0;
59 if (!pending_.empty()) {
60 Send(SPECIAL);
61 }
62}
63
65 pending_ += c;
66 timeout_ = 0;
67 position_ = -1;
68 Send(Parse());
69}
70
71unsigned char TerminalInputParser::Current() {
72 return pending_[position_];
73}
74
75bool TerminalInputParser::Eat() {
76 position_++;
77 return position_ < (int)pending_.size();
78}
79
80void TerminalInputParser::Send(TerminalInputParser::Output output) {
81 switch (output.type) {
82 case UNCOMPLETED:
83 return;
84
85 case DROP:
86 pending_.clear();
87 return;
88
89 case CHARACTER:
90 out_->Send(Event::Character(std::move(pending_)));
91 pending_.clear();
92 return;
93
94 case SPECIAL: {
95 auto it = g_uniformize.find(pending_);
96 if (it != g_uniformize.end()) {
97 pending_ = it->second;
98 }
99 out_->Send(Event::Special(std::move(pending_)));
100 pending_.clear();
101 }
102 return;
103
104 case MOUSE:
105 out_->Send(Event::Mouse(std::move(pending_), output.mouse)); // NOLINT
106 pending_.clear();
107 return;
108
109 case CURSOR_REPORTING:
110 out_->Send(Event::CursorReporting(std::move(pending_), // NOLINT
111 output.cursor.x, // NOLINT
112 output.cursor.y)); // NOLINT
113 pending_.clear();
114 return;
115 }
116 // NOT_REACHED().
117}
118
119TerminalInputParser::Output TerminalInputParser::Parse() {
120 if (!Eat()) {
121 return UNCOMPLETED;
122 }
123
124 switch (Current()) {
125 case 24: // CAN NOLINT
126 case 26: // SUB NOLINT
127 return DROP;
128
129 case '\x1B':
130 return ParseESC();
131 default:
132 break;
133 }
134
135 if (Current() < 32) { // C0 NOLINT
136 return SPECIAL;
137 }
138
139 if (Current() == 127) { // Delete // NOLINT
140 return SPECIAL;
141 }
142
143 return ParseUTF8();
144}
145
146// Code point <-> UTF-8 conversion
147//
148// ┏━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
149// ┃Byte 1 ┃Byte 2 ┃Byte 3 ┃Byte 4 ┃
150// ┡━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
151// │0xxxxxxx│ │ │ │
152// ├────────┼────────┼────────┼────────┤
153// │110xxxxx│10xxxxxx│ │ │
154// ├────────┼────────┼────────┼────────┤
155// │1110xxxx│10xxxxxx│10xxxxxx│ │
156// ├────────┼────────┼────────┼────────┤
157// │11110xxx│10xxxxxx│10xxxxxx│10xxxxxx│
158// └────────┴────────┴────────┴────────┘
159//
160// Then some sequences are illegal if it exist a shorter representation of the
161// same codepoint.
162TerminalInputParser::Output TerminalInputParser::ParseUTF8() {
163 auto head = static_cast<unsigned char>(Current());
164 unsigned char selector = 0b1000'0000; // NOLINT
165
166 // The non code-point part of the first byte.
167 unsigned char mask = selector;
168
169 // Find the first zero in the first byte.
170 unsigned int first_zero = 8; // NOLINT
171 for (unsigned int i = 0; i < 8; ++i) { // NOLINT
172 mask |= selector;
173 if (!(head & selector)) {
174 first_zero = i;
175 break;
176 }
177 selector >>= 1U;
178 }
179
180 // Accumulate the value of the first byte.
181 auto value = uint32_t(head & ~mask); // NOLINT
182
183 // Invalid UTF8, with more than 5 bytes.
184 const unsigned int max_utf8_bytes = 5;
185 if (first_zero == 1 || first_zero >= max_utf8_bytes) {
186 return DROP;
187 }
188
189 // Multi byte UTF-8.
190 for (unsigned int i = 2; i <= first_zero; ++i) {
191 if (!Eat()) {
192 return UNCOMPLETED;
193 }
194
195 // Invalid continuation byte.
196 head = static_cast<unsigned char>(Current());
197 if ((head & 0b1100'0000) != 0b1000'0000) { // NOLINT
198 return DROP;
199 }
200 value <<= 6; // NOLINT
201 value += head & 0b0011'1111; // NOLINT
202 }
203
204 // Check for overlong UTF8 encoding.
205 int extra_byte = 0;
206 if (value <= 0b000'0000'0111'1111) { // NOLINT
207 extra_byte = 0; // NOLINT
208 } else if (value <= 0b000'0111'1111'1111) { // NOLINT
209 extra_byte = 1; // NOLINT
210 } else if (value <= 0b1111'1111'1111'1111) { // NOLINT
211 extra_byte = 2; // NOLINT
212 } else if (value <= 0b1'0000'1111'1111'1111'1111) { // NOLINT
213 extra_byte = 3; // NOLINT
214 } else { // NOLINT
215 return DROP;
216 }
217
218 if (extra_byte != position_) {
219 return DROP;
220 }
221
222 return CHARACTER;
223}
224
225TerminalInputParser::Output TerminalInputParser::ParseESC() {
226 if (!Eat()) {
227 return UNCOMPLETED;
228 }
229 switch (Current()) {
230 case 'P':
231 return ParseDCS();
232 case '[':
233 return ParseCSI();
234 case ']':
235 return ParseOSC();
236 default:
237 if (!Eat()) {
238 return UNCOMPLETED;
239 } else {
240 return SPECIAL;
241 }
242 }
243}
244
245TerminalInputParser::Output TerminalInputParser::ParseDCS() {
246 // Parse until the string terminator ST.
247 while (true) {
248 if (!Eat()) {
249 return UNCOMPLETED;
250 }
251
252 if (Current() != '\x1B') {
253 continue;
254 }
255
256 if (!Eat()) {
257 return UNCOMPLETED;
258 }
259
260 if (Current() != '\\') {
261 continue;
262 }
263
264 return SPECIAL;
265 }
266}
267
268TerminalInputParser::Output TerminalInputParser::ParseCSI() {
269 bool altered = false;
270 int argument = 0;
271 std::vector<int> arguments;
272 while (true) {
273 if (!Eat()) {
274 return UNCOMPLETED;
275 }
276
277 if (Current() == '<') {
278 altered = true;
279 continue;
280 }
281
282 if (Current() >= '0' && Current() <= '9') {
283 argument *= 10; // NOLINT
284 argument += int(Current() - '0');
285 continue;
286 }
287
288 if (Current() == ';') {
289 arguments.push_back(argument);
290 argument = 0;
291 continue;
292 }
293
294 if (Current() >= ' ' && Current() <= '~' && Current() != '<') {
295 arguments.push_back(argument);
296 argument = 0; // NOLINT
297 switch (Current()) {
298 case 'M':
299 return ParseMouse(altered, true, std::move(arguments));
300 case 'm':
301 return ParseMouse(altered, false, std::move(arguments));
302 case 'R':
303 return ParseCursorReporting(std::move(arguments));
304 default:
305 return SPECIAL;
306 }
307 }
308
309 // Invalid ESC in CSI.
310 if (Current() == '\x1B') {
311 return SPECIAL;
312 }
313 }
314}
315
316TerminalInputParser::Output TerminalInputParser::ParseOSC() {
317 // Parse until the string terminator ST.
318 while (true) {
319 if (!Eat()) {
320 return UNCOMPLETED;
321 }
322 if (Current() != '\x1B') {
323 continue;
324 }
325 if (!Eat()) {
326 return UNCOMPLETED;
327 }
328 if (Current() != '\\') {
329 continue;
330 }
331 return SPECIAL;
332 }
333}
334
335TerminalInputParser::Output TerminalInputParser::ParseMouse( // NOLINT
336 bool altered,
337 bool pressed,
338 std::vector<int> arguments) {
339 if (arguments.size() != 3) {
340 return SPECIAL;
341 }
342
343 (void)altered;
344
345 Output output(MOUSE);
346 output.mouse.button = Mouse::Button((arguments[0] & 3) + // NOLINT
347 ((arguments[0] & 64) >> 4)); // NOLINT
348 output.mouse.motion = Mouse::Motion(pressed); // NOLINT
349 output.mouse.shift = bool(arguments[0] & 4); // NOLINT
350 output.mouse.meta = bool(arguments[0] & 8); // NOLINT
351 output.mouse.x = arguments[1]; // NOLINT
352 output.mouse.y = arguments[2]; // NOLINT
353 return output;
354}
355
356// NOLINTNEXTLINE
357TerminalInputParser::Output TerminalInputParser::ParseCursorReporting(
358 std::vector<int> arguments) {
359 if (arguments.size() != 2) {
360 return SPECIAL;
361 }
362 Output output(CURSOR_REPORTING);
363 output.cursor.y = arguments[0]; // NOLINT
364 output.cursor.x = arguments[1]; // NOLINT
365 return output;
366}
367
368} // namespace ftxui
369
370// Copyright 2020 Arthur Sonzogni. All rights reserved.
371// Use of this source code is governed by the MIT license that can be found in
372// the LICENSE file.
TerminalInputParser(Sender< Task > out)
const std::map< std::string, std::string > g_uniformize
std::unique_ptr< SenderImpl< T > > Sender
Definition receiver.hpp:44
static Event Special(std::string)
Definition event.cpp:37