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