From 001a0ae92579da947cc5d8fc2fb1143aa5336cc9 Mon Sep 17 00:00:00 2001 From: ArthurSonzogni Date: Sun, 23 Jun 2019 17:47:33 +0200 Subject: [PATCH] Add UTF8 support and a better xterm parsing. This fixes: https://github.com/ArthurSonzogni/FTXUI/issues/2 --- examples/util/print_key_press.cpp | 17 +- include/ftxui/component/event.hpp | 39 +++-- include/ftxui/dom/elements.hpp | 2 +- src/ftxui/component/event.cpp | 181 +++++++++++++++++---- src/ftxui/component/input.cpp | 6 +- src/ftxui/component/screen_interactive.cpp | 46 +----- src/ftxui/dom/vbox.cpp | 2 +- src/ftxui/screen/screen.cpp | 44 ++--- src/ftxui/screen/terminal.cpp | 8 +- 9 files changed, 220 insertions(+), 125 deletions(-) diff --git a/examples/util/print_key_press.cpp b/examples/util/print_key_press.cpp index 874126f..ccb34a3 100644 --- a/examples/util/print_key_press.cpp +++ b/examples/util/print_key_press.cpp @@ -15,19 +15,12 @@ class DrawKey : public Component { Element Render() override { Elements children; for (size_t i = std::max(0, (int)keys.size() - 10); i < keys.size(); ++i) { - std::string code = ""; - for (size_t j = 0; j < 5; ++j) - code += " " + std::to_string(keys[i].values[j]); + std::wstring code; + for(auto& it : keys[i].input()) + code += L" " + std::to_wstring((int)it); - try { - std::string line = code + " -> " + std::to_string(keys[i].values[0]) + - " (" + char(keys[i].values[0]) + ")"; - children.push_back(text(to_wstring(line))); - } catch (...) { - std::string line = - code + " -> " + std::to_string(keys[i].values[0]) + " (undefined)"; - children.push_back(text(to_wstring(line))); - } + code = L"(" + code + L" ) -> " + keys[i].character() + L")"; + children.push_back(text(code)); } return vbox(std::move(children)); } diff --git a/include/ftxui/component/event.hpp b/include/ftxui/component/event.hpp index d9fc17c..0c13f52 100644 --- a/include/ftxui/component/event.hpp +++ b/include/ftxui/component/event.hpp @@ -1,22 +1,32 @@ #ifndef FTXUI_COMPONENT_EVENT_HPP #define FTXUI_COMPONENT_EVENT_HPP -#include #include +#include +#include namespace ftxui { -struct Event{ +// Documentation: +// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html +// +struct Event { public: - // --- Character --- - static Event Character(int); + // --- Constructor section --------------------------------------------------- + static Event Character(char); + static Event Character(wchar_t); + + static Event Character(const std::string&); + static Event Special(const std::string&); + + static Event GetEvent(std::function getchar); // --- Arrow --- static Event ArrowLeft; static Event ArrowRight; static Event ArrowUp; static Event ArrowDown; - + // --- Other --- static Event Backspace; static Event Delete; @@ -27,15 +37,20 @@ struct Event{ // --- Custom --- static Event Custom; - bool operator==(const Event& other) { return values == other.values; } + //--- Method section --------------------------------------------------------- + bool is_character() { return is_character_; } + wchar_t character() { return character_; } + const std::string& input() { return input_; } - // Internal representation. - std::array values = {0, 0, 0, 0, 0}; - + bool operator==(const Event& other) { return input_ == other.input_; } + + //--- State section ---------------------------------------------------------- + private: + std::string input_; + bool is_character_ = false; + wchar_t character_ = '?'; }; - -} // namespace ftxui - +} // namespace ftxui #endif /* end of include guard: FTXUI_COMPONENT_EVENT_HPP */ diff --git a/include/ftxui/dom/elements.hpp b/include/ftxui/dom/elements.hpp index 60ac38a..815f861 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -36,7 +36,7 @@ Decorator bgcolor(Color); Element color(Color, Element); Element bgcolor(Color, Element); -// --- Layout --- +// --- Layout is // Horizontal, Vertical or stacked set of elements. Element hbox(Elements); Element vbox(Elements); diff --git a/src/ftxui/component/event.cpp b/src/ftxui/component/event.cpp index 0e1ca03..4b3faac 100644 --- a/src/ftxui/component/event.cpp +++ b/src/ftxui/component/event.cpp @@ -1,39 +1,160 @@ #include "ftxui/component/event.hpp" +#include "ftxui/screen/string.hpp" namespace ftxui { -constexpr int ESC = int(27); +// static +Event Event::Character(const std::string& input) { + Event event; + event.input_ = input; + event.is_character_ = true; + event.character_ = to_wstring(input)[0]; + return event; +} -// --- Character --- -Event Event::Character(int c) { - return Event{c}; +// static +Event Event::Character(char c) { + return Character(wchar_t(c)); +} + +// static +Event Event::Character(wchar_t c) { + Event event; + event.input_ = {(char)c}; + event.is_character_ = true; + event.character_ = c; + return event; +} + +// static +Event Event::Special(const std::string& input) { + Event event; + event.input_ = std::move(input); + return event; +} + +Event ParseUTF8(std::function& getchar, std::string& input) { + if ((input[0] & 0b11000000) == 0b11000000) + input += getchar(); + if ((input[0] & 0b11100000) == 0b11100000) + input += getchar(); + if ((input[0] & 0b11110000) == 0b11110000) + input += getchar(); + return Event::Character(input); +} + +void ParsePs(std::function getchar, std::string input) { + while (1) { + char key = getchar(); + input += key; + if ('0' <= key && key <= '9') + continue; + return; + } +} + +Event ParseCSI(std::function getchar, std::string& input) { + while (1) { + char c = getchar(); + input += c; + + if (c >= ' ' && c <= '/') + return Event::Special(input); + + // Invalid ESC in CSI. + if (c == '\e') + return Event::Special(input); + + if (c >= '@' && c <= 'v') + return Event::Special(input); + } +} + +Event ParseDCS(std::function getchar, std::string& input) { + // Parse until the string terminator ST. + while (1) { + input += getchar(); + if (input.back() != '\e') + continue; + input += getchar(); + if (input.back() != '\\') + continue; + return Event::Special(input); + } +} + +Event ParseOSC(std::function getchar, std::string& input) { + // Parse until the string terminator ST. + while (1) { + input += getchar(); + if (input.back() != '\e') + continue; + input += getchar(); + if (input.back() != '\\') + continue; + return Event::Special(input); + } +} + +Event ParseESC(std::function getchar, std::string& input) { + input += getchar(); + switch (input.back()) { + case 'P': + return ParseDCS(getchar, input); + case '[': + return ParseCSI(getchar, input); + case ']': + return ParseOSC(getchar, input); + default: + input += getchar(); + return Event::Special(input); + } +} + +// static +Event Event::GetEvent(std::function getchar) { + std::string input; + input += getchar(); + + switch (input[0]) { + case 24: // CAN + case 26: // SUB + return Event(); // Ignored. + + case 'P': + return ParseDCS(getchar, input); + + case '\e': + return ParseESC(getchar, input); + } + + if (input[0] < 32) // C0 + return Event::Special(input); + + return ParseUTF8(getchar, input); } // --- Arrow --- -Event Event::ArrowLeft{ESC, '[', 'D'}; -Event Event::ArrowRight{ESC, '[', 'C'}; -Event Event::ArrowUp{ESC, '[', 'A'}; -Event Event::ArrowDown{ESC, '[', 'B'}; +Event Event::ArrowLeft = Event::Special("\e[D"); +Event Event::ArrowRight = Event::Special("\e[C"); +Event Event::ArrowUp = Event::Special("\e[A"); +Event Event::ArrowDown = Event::Special("\e[B"); +Event Event::Backspace = Event::Special({127}); +Event Event::Delete = Event::Special("\e[3~"); +Event Event::Escape = Event::Special("\e"); +Event Event::Return = Event::Special({10}); +Event Event::F1 = Event::Special("\e[OP"); +Event Event::F2 = Event::Special("\e[OQ"); +Event Event::F3 = Event::Special("\e[OR"); +Event Event::F4 = Event::Special("\e[OS"); +Event Event::F5 = Event::Special("\e[15~"); +Event Event::F6 = Event::Special("\e[17~"); +Event Event::F7 = Event::Special("\e[18~"); +Event Event::F8 = Event::Special("\e[19~"); +Event Event::F9 = Event::Special("\e[20~"); +Event Event::F10 = Event::Special("\e[21~"); +Event Event::F11 = Event::Special("\e[21~"); // Doesn't exist +Event Event::F12 = Event::Special("\e[24~"); +Event Event::Custom = Event::Special({0}); -// --- Other --- -Event Event::Backspace{127}; -Event Event::Delete{ESC, '[', '3', '~'}; -Event Event::Escape{ESC}; -Event Event::Return{10}; - -Event Event::F1{ESC, '[', 'O', 'P'}; -Event Event::F2{ESC, '[', 'O', 'Q'}; -Event Event::F3{ESC, '[', 'O', 'R'}; -Event Event::F4{ESC, '[', 'O', 'S'}; -Event Event::F5{ESC, '[', '1', '5', '~'}; -Event Event::F6{ESC, '[', '1', '7', '~'}; -Event Event::F7{ESC, '[', '1', '8', '~'}; -Event Event::F8{ESC, '[', '1', '9', '~'}; -Event Event::F9{ESC, '[', '2', '0', '~'}; -Event Event::F10{ESC, '[', '2', '1', '~'}; -Event Event::F11{ESC, '[', '2', '1', '~'}; // Same as F10 ? -Event Event::F12{ESC, '[', '2', '4', '~'}; - -Event Event::Custom{0, 0, 0, 0, 0}; - -} // namespace ftxui +} // namespace ftxui diff --git a/src/ftxui/component/input.cpp b/src/ftxui/component/input.cpp index 0f9ae96..a5b8d95 100644 --- a/src/ftxui/component/input.cpp +++ b/src/ftxui/component/input.cpp @@ -73,10 +73,8 @@ bool Input::OnEvent(Event event) { } // Content - constexpr char ESC = char(27); - if (event.values[0] != ESC) { - wchar_t v = (char)event.values[0]; - content.insert(cursor_position, 1, v); + if (event.is_character()) { + content.insert(cursor_position, 1, event.character()); cursor_position++; return true; } diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 3cb4a4a..7e940f5 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -4,47 +4,13 @@ #include #include #include -#include "ftxui/component/component.hpp" -#include "ftxui/screen/terminal.hpp" #include +#include "ftxui/component/component.hpp" +#include "ftxui/screen/string.hpp" +#include "ftxui/screen/terminal.hpp" namespace ftxui { -namespace { -constexpr int ESC = 27; -constexpr int WAT = 195; -constexpr int WAT2 = 194; -constexpr int WATWAIT = 91; - -Event GetEvent() { - int v1 = getchar(); - if (v1 == ESC) { - int v2 = getchar(); - int v3 = getchar(); - - // if (v2 == WATWAIT) { - // int v4 = getchar(); - // int v5 = getchar(); - // return Event{v1, v2, v3, v4, v5}; - //} - return Event{v1, v2, v3}; - } - - if (v1 == WAT) { - int v2 = getchar(); - return Event{v1, v2}; - } - - if (v1 == WAT2) { - int v2 = getchar(); - return Event{v1, v2}; - } - - return Event{v1}; -}; - -}; // namespace - ScreenInteractive::ScreenInteractive(int dimx, int dimy, Dimension dimension) @@ -109,8 +75,10 @@ void ScreenInteractive::Loop(Component* component) { tcsetattr(STDIN_FILENO, TCSANOW, &terminal_configuration_new); std::thread read_char([this]() { - while (!quit_) - PostEvent(GetEvent()); + while (!quit_) { + auto event = Event::GetEvent([] { return (char)getchar(); }); + PostEvent(std::move(event)); + } }); std::string reset_position; diff --git a/src/ftxui/dom/vbox.cpp b/src/ftxui/dom/vbox.cpp index e216057..a9d77e0 100644 --- a/src/ftxui/dom/vbox.cpp +++ b/src/ftxui/dom/vbox.cpp @@ -1,5 +1,5 @@ -#include "ftxui/dom/node.hpp" #include "ftxui/dom/elements.hpp" +#include "ftxui/dom/node.hpp" namespace ftxui { diff --git a/src/ftxui/screen/screen.cpp b/src/ftxui/screen/screen.cpp index 86d7d7c..04fb328 100644 --- a/src/ftxui/screen/screen.cpp +++ b/src/ftxui/screen/screen.cpp @@ -9,7 +9,7 @@ namespace ftxui { namespace { static const wchar_t* BOLD_SET = L"\e[1m"; -static const wchar_t* BOLD_RESET = L"\e[22m"; // Can't use 21 here. +static const wchar_t* BOLD_RESET = L"\e[22m"; // Can't use 21 here. static const wchar_t* DIM_SET = L"\e[2m"; static const wchar_t* DIM_RESET = L"\e[22m"; @@ -34,7 +34,7 @@ bool In(const Box& stencil, int x, int y) { Pixel dev_null_pixel; -} // namespace +} // namespace Dimension Dimension::Fixed(int v) { return Dimension{v, v}; @@ -86,8 +86,11 @@ void UpdatePixelStyle(std::wstringstream& ss, Pixel& previous, Pixel& next) { if (next.foreground_color != previous.foreground_color || next.background_color != previous.background_color) { - ss << L"\e[" + to_wstring(std::to_string((uint8_t)next.foreground_color)) + L"m"; - ss << L"\e[" + to_wstring(std::to_string(10 + (uint8_t)next.background_color)) + L"m"; + ss << L"\e[" + to_wstring(std::to_string((uint8_t)next.foreground_color)) + + L"m"; + ss << L"\e[" + + to_wstring(std::to_string(10 + (uint8_t)next.background_color)) + + L"m"; } previous = next; @@ -105,7 +108,6 @@ std::string Screen::ToString() { UpdatePixelStyle(ss, previous_pixel, pixels_[y][x]); ss << pixels_[y][x].character; } - } Pixel final_pixel; @@ -115,7 +117,7 @@ std::string Screen::ToString() { } wchar_t& Screen::at(int x, int y) { - return PixelAt(x,y).character; + return PixelAt(x, y).character; } Pixel& Screen::PixelAt(int x, int y) { @@ -137,36 +139,34 @@ void Screen::Clear() { } void Screen::ApplyShader() { - // Merge box characters togethers. - for(int y = 1; y -#include #include +#include #include +#include #include "ftxui/screen/terminal.hpp" @@ -9,7 +9,7 @@ namespace ftxui { Terminal::Dimensions Terminal::Size() { #ifdef __EMSCRIPTEN__ - return Dimensions{80,43}; + return Dimensions{80, 43}; #else winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); @@ -17,4 +17,4 @@ Terminal::Dimensions Terminal::Size() { #endif } -} // namespace ftxui +} // namespace ftxui