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