FTXUI/src/ftxui/component/terminal_input_parser.cpp

350 lines
9.2 KiB
C++
Raw Normal View History

#include "ftxui/component/terminal_input_parser.hpp"
2022-06-12 23:08:22 +08:00
#include <cstdint> // for uint32_t
#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Button, Mouse::Motion
#include <ftxui/component/receiver.hpp> // for SenderImpl, Sender
#include <map>
#include <memory> // for unique_ptr, allocator
#include <utility> // for move
2021-05-15 04:00:49 +08:00
#include "ftxui/component/event.hpp" // for Event
#include "ftxui/component/task.hpp" // for Task
2021-05-02 02:40:35 +08:00
namespace ftxui {
// NOLINTNEXTLINE
const std::map<std::string, std::string> g_uniformize = {{
// Microsoft's terminal uses a different new line character for the return
// key. This also happens with linux with the `bind` command:
// See https://github.com/ArthurSonzogni/FTXUI/issues/337
// Here, we uniformize the new line character to `\n`.
{"\r", "\n"},
// See: https://github.com/ArthurSonzogni/FTXUI/issues/508
{std::string({8}), std::string({127})},
}};
TerminalInputParser::TerminalInputParser(Sender<Task> out)
: out_(std::move(out)) {}
void TerminalInputParser::Timeout(int time) {
timeout_ += time;
2022-03-31 08:17:43 +08:00
const int timeout_threshold = 50;
if (timeout_ < timeout_threshold) {
return;
2022-03-31 08:17:43 +08:00
}
timeout_ = 0;
2022-03-31 08:17:43 +08:00
if (!pending_.empty()) {
Send(SPECIAL);
2022-03-31 08:17:43 +08:00
}
}
void TerminalInputParser::Add(char c) {
pending_ += c;
timeout_ = 0;
position_ = -1;
Send(Parse());
}
unsigned char TerminalInputParser::Current() {
return pending_[position_];
}
bool TerminalInputParser::Eat() {
position_++;
return position_ < (int)pending_.size();
}
2021-04-19 00:32:38 +08:00
void TerminalInputParser::Send(TerminalInputParser::Output output) {
switch (output.type) {
case UNCOMPLETED:
return;
case DROP:
2021-04-25 21:22:38 +08:00
pending_.clear();
return;
case CHARACTER:
out_->Send(Event::Character(std::move(pending_)));
pending_.clear();
2021-04-25 21:22:38 +08:00
return;
case SPECIAL: {
auto it = g_uniformize.find(pending_);
if (it != g_uniformize.end()) {
pending_ = it->second;
2022-03-31 08:17:43 +08:00
}
out_->Send(Event::Special(std::move(pending_)));
pending_.clear();
}
2021-04-25 21:22:38 +08:00
return;
2021-04-19 00:32:38 +08:00
2021-04-25 21:22:38 +08:00
case MOUSE:
2022-03-31 08:17:43 +08:00
out_->Send(Event::Mouse(std::move(pending_), output.mouse)); // NOLINT
pending_.clear();
2021-04-25 21:22:38 +08:00
return;
case CURSOR_REPORTING:
2022-03-31 08:17:43 +08:00
out_->Send(Event::CursorReporting(std::move(pending_), // NOLINT
output.cursor.x, // NOLINT
output.cursor.y)); // NOLINT
pending_.clear();
2021-04-25 21:22:38 +08:00
return;
}
2021-04-25 21:22:38 +08:00
// NOT_REACHED().
}
2021-04-19 00:32:38 +08:00
TerminalInputParser::Output TerminalInputParser::Parse() {
2022-03-31 08:17:43 +08:00
if (!Eat()) {
return UNCOMPLETED;
2022-03-31 08:17:43 +08:00
}
switch (Current()) {
2022-03-31 08:17:43 +08:00
case 24: // CAN NOLINT
case 26: // SUB NOLINT
return DROP;
case '\x1B':
return ParseESC();
default:
break;
}
2022-03-31 08:17:43 +08:00
if (Current() < 32) { // C0 NOLINT
return SPECIAL;
2022-03-31 08:17:43 +08:00
}
2022-03-31 08:17:43 +08:00
if (Current() == 127) { // Delete // NOLINT
return SPECIAL;
2022-03-31 08:17:43 +08:00
}
return ParseUTF8();
}
// Code point <-> UTF-8 conversion
//
// ┏━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
// ┃Byte 1 ┃Byte 2 ┃Byte 3 ┃Byte 4 ┃
// ┡━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
// │0xxxxxxx│ │ │ │
// ├────────┼────────┼────────┼────────┤
// │110xxxxx│10xxxxxx│ │ │
// ├────────┼────────┼────────┼────────┤
// │1110xxxx│10xxxxxx│10xxxxxx│ │
// ├────────┼────────┼────────┼────────┤
// │11110xxx│10xxxxxx│10xxxxxx│10xxxxxx│
// └────────┴────────┴────────┴────────┘
//
// Then some sequences are illegal if it exist a shorter representation of the
// same codepoint.
2021-04-19 00:32:38 +08:00
TerminalInputParser::Output TerminalInputParser::ParseUTF8() {
2022-03-31 08:17:43 +08:00
auto head = static_cast<unsigned char>(Current());
unsigned char selector = 0b1000'0000; // NOLINT
// The non code-point part of the first byte.
unsigned char mask = selector;
// Find the first zero in the first byte.
2022-03-31 08:17:43 +08:00
unsigned int first_zero = 8; // NOLINT
for (unsigned int i = 0; i < 8; ++i) { // NOLINT
mask |= selector;
if (!(head & selector)) {
first_zero = i;
break;
}
selector >>= 1U;
}
// Accumulate the value of the first byte.
2022-03-31 08:17:43 +08:00
auto value = uint32_t(head & ~mask); // NOLINT
// Invalid UTF8, with more than 5 bytes.
2022-03-31 08:17:43 +08:00
const unsigned int max_utf8_bytes = 5;
if (first_zero == 1 || first_zero >= max_utf8_bytes) {
return DROP;
2022-03-31 08:17:43 +08:00
}
// Multi byte UTF-8.
2022-03-31 08:17:43 +08:00
for (unsigned int i = 2; i <= first_zero; ++i) {
if (!Eat()) {
return UNCOMPLETED;
2022-03-31 08:17:43 +08:00
}
// Invalid continuation byte.
head = static_cast<unsigned char>(Current());
2022-03-31 08:17:43 +08:00
if ((head & 0b1100'0000) != 0b1000'0000) { // NOLINT
return DROP;
2022-03-31 08:17:43 +08:00
}
value <<= 6; // NOLINT
value += head & 0b0011'1111; // NOLINT
}
// Check for overlong UTF8 encoding.
2022-03-31 08:17:43 +08:00
int extra_byte = 0;
if (value <= 0b000'0000'0111'1111) { // NOLINT
extra_byte = 0; // NOLINT
} else if (value <= 0b000'0111'1111'1111) { // NOLINT
extra_byte = 1; // NOLINT
} else if (value <= 0b1111'1111'1111'1111) { // NOLINT
extra_byte = 2; // NOLINT
} else if (value <= 0b1'0000'1111'1111'1111'1111) { // NOLINT
extra_byte = 3; // NOLINT
} else { // NOLINT
return DROP;
}
2022-03-31 08:17:43 +08:00
if (extra_byte != position_) {
return DROP;
2022-03-31 08:17:43 +08:00
}
return CHARACTER;
}
2021-04-19 00:32:38 +08:00
TerminalInputParser::Output TerminalInputParser::ParseESC() {
2022-03-31 08:17:43 +08:00
if (!Eat()) {
return UNCOMPLETED;
2022-03-31 08:17:43 +08:00
}
switch (Current()) {
case 'P':
return ParseDCS();
case '[':
return ParseCSI();
case ']':
return ParseOSC();
default:
2022-03-31 08:17:43 +08:00
if (!Eat()) {
return UNCOMPLETED;
2022-03-31 08:17:43 +08:00
} else {
return SPECIAL;
}
}
}
2021-04-19 00:32:38 +08:00
TerminalInputParser::Output TerminalInputParser::ParseDCS() {
// Parse until the string terminator ST.
2022-03-31 08:17:43 +08:00
while (true) {
if (!Eat()) {
return UNCOMPLETED;
2022-03-31 08:17:43 +08:00
}
2022-03-31 08:17:43 +08:00
if (Current() != '\x1B') {
continue;
2022-03-31 08:17:43 +08:00
}
2022-03-31 08:17:43 +08:00
if (!Eat()) {
return UNCOMPLETED;
2022-03-31 08:17:43 +08:00
}
2022-03-31 08:17:43 +08:00
if (Current() != '\\') {
continue;
2022-03-31 08:17:43 +08:00
}
return SPECIAL;
}
}
2021-04-19 00:32:38 +08:00
TerminalInputParser::Output TerminalInputParser::ParseCSI() {
2021-04-25 21:22:38 +08:00
bool altered = false;
int argument = 0;
2021-04-19 00:32:38 +08:00
std::vector<int> arguments;
while (true) {
2022-03-31 08:17:43 +08:00
if (!Eat()) {
return UNCOMPLETED;
2022-03-31 08:17:43 +08:00
}
2021-04-25 21:22:38 +08:00
if (Current() == '<') {
altered = true;
continue;
}
2021-04-19 00:32:38 +08:00
if (Current() >= '0' && Current() <= '9') {
2022-03-31 08:17:43 +08:00
argument *= 10; // NOLINT
2021-04-19 00:32:38 +08:00
argument += int(Current() - '0');
continue;
2021-04-19 00:32:38 +08:00
}
2021-04-19 00:32:38 +08:00
if (Current() == ';') {
arguments.push_back(argument);
argument = 0;
continue;
2021-04-19 00:32:38 +08:00
}
if (Current() >= ' ' && Current() <= '~' && Current() != '<') {
2021-04-19 00:32:38 +08:00
arguments.push_back(argument);
2022-03-31 08:17:43 +08:00
argument = 0; // NOLINT
2021-04-19 00:32:38 +08:00
switch (Current()) {
case 'M':
2021-04-25 21:22:38 +08:00
return ParseMouse(altered, true, std::move(arguments));
case 'm':
return ParseMouse(altered, false, std::move(arguments));
case 'R':
return ParseCursorReporting(std::move(arguments));
2021-04-19 00:32:38 +08:00
default:
return SPECIAL;
}
}
// Invalid ESC in CSI.
2022-03-31 08:17:43 +08:00
if (Current() == '\x1B') {
return SPECIAL;
2022-03-31 08:17:43 +08:00
}
}
}
2021-04-19 00:32:38 +08:00
TerminalInputParser::Output TerminalInputParser::ParseOSC() {
// Parse until the string terminator ST.
while (true) {
2022-03-31 08:17:43 +08:00
if (!Eat()) {
return UNCOMPLETED;
2022-03-31 08:17:43 +08:00
}
if (Current() != '\x1B') {
continue;
2022-03-31 08:17:43 +08:00
}
if (!Eat()) {
return UNCOMPLETED;
2022-03-31 08:17:43 +08:00
}
if (Current() != '\\') {
continue;
2022-03-31 08:17:43 +08:00
}
return SPECIAL;
}
}
2021-04-19 00:32:38 +08:00
2022-03-31 08:17:43 +08:00
TerminalInputParser::Output TerminalInputParser::ParseMouse( // NOLINT
2021-04-25 21:22:38 +08:00
bool altered,
bool pressed,
2021-04-19 00:32:38 +08:00
std::vector<int> arguments) {
2022-03-31 08:17:43 +08:00
if (arguments.size() != 3) {
2021-04-19 00:32:38 +08:00
return SPECIAL;
2022-03-31 08:17:43 +08:00
}
2021-04-25 21:22:38 +08:00
(void)altered;
Output output(MOUSE);
2022-03-31 08:17:43 +08:00
output.mouse.button = Mouse::Button((arguments[0] & 3) + // NOLINT
((arguments[0] & 64) >> 4)); // NOLINT
output.mouse.motion = Mouse::Motion(pressed); // NOLINT
output.mouse.shift = bool(arguments[0] & 4); // NOLINT
output.mouse.meta = bool(arguments[0] & 8); // NOLINT
output.mouse.x = arguments[1]; // NOLINT
output.mouse.y = arguments[2]; // NOLINT
2021-04-25 21:22:38 +08:00
return output;
2021-04-19 00:32:38 +08:00
}
2022-03-31 08:17:43 +08:00
// NOLINTNEXTLINE
TerminalInputParser::Output TerminalInputParser::ParseCursorReporting(
std::vector<int> arguments) {
2022-03-31 08:17:43 +08:00
if (arguments.size() != 2) {
return SPECIAL;
2022-03-31 08:17:43 +08:00
}
2021-04-25 21:22:38 +08:00
Output output(CURSOR_REPORTING);
2022-03-31 08:17:43 +08:00
output.cursor.y = arguments[0]; // NOLINT
output.cursor.x = arguments[1]; // NOLINT
2021-04-25 21:22:38 +08:00
return output;
}
} // namespace ftxui
2021-05-02 02:40:35 +08:00
// Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.