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