Add UTF8 support and a better xterm parsing.

This fixes: https://github.com/ArthurSonzogni/FTXUI/issues/2
This commit is contained in:
ArthurSonzogni 2019-06-23 17:47:33 +02:00
parent 8fede35dc5
commit 001a0ae925
9 changed files with 220 additions and 125 deletions

View File

@ -15,19 +15,12 @@ class DrawKey : public Component {
Element Render() override { Element Render() override {
Elements children; Elements children;
for (size_t i = std::max(0, (int)keys.size() - 10); i < keys.size(); ++i) { for (size_t i = std::max(0, (int)keys.size() - 10); i < keys.size(); ++i) {
std::string code = ""; std::wstring code;
for (size_t j = 0; j < 5; ++j) for(auto& it : keys[i].input())
code += " " + std::to_string(keys[i].values[j]); code += L" " + std::to_wstring((int)it);
try { code = L"(" + code + L" ) -> " + keys[i].character() + L")";
std::string line = code + " -> " + std::to_string(keys[i].values[0]) + children.push_back(text(code));
" (" + 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)));
}
} }
return vbox(std::move(children)); return vbox(std::move(children));
} }

View File

@ -1,22 +1,32 @@
#ifndef FTXUI_COMPONENT_EVENT_HPP #ifndef FTXUI_COMPONENT_EVENT_HPP
#define FTXUI_COMPONENT_EVENT_HPP #define FTXUI_COMPONENT_EVENT_HPP
#include <vector>
#include <array> #include <array>
#include <vector>
#include <functional>
namespace ftxui { namespace ftxui {
struct Event{ // Documentation:
// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
//
struct Event {
public: public:
// --- Character --- // --- Constructor section ---------------------------------------------------
static Event Character(int); 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<char()> getchar);
// --- Arrow --- // --- Arrow ---
static Event ArrowLeft; static Event ArrowLeft;
static Event ArrowRight; static Event ArrowRight;
static Event ArrowUp; static Event ArrowUp;
static Event ArrowDown; static Event ArrowDown;
// --- Other --- // --- Other ---
static Event Backspace; static Event Backspace;
static Event Delete; static Event Delete;
@ -27,15 +37,20 @@ struct Event{
// --- Custom --- // --- Custom ---
static 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. bool operator==(const Event& other) { return input_ == other.input_; }
std::array<int, 5> values = {0, 0, 0, 0, 0};
//--- 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 */ #endif /* end of include guard: FTXUI_COMPONENT_EVENT_HPP */

View File

@ -36,7 +36,7 @@ Decorator bgcolor(Color);
Element color(Color, Element); Element color(Color, Element);
Element bgcolor(Color, Element); Element bgcolor(Color, Element);
// --- Layout --- // --- Layout is
// Horizontal, Vertical or stacked set of elements. // Horizontal, Vertical or stacked set of elements.
Element hbox(Elements); Element hbox(Elements);
Element vbox(Elements); Element vbox(Elements);

View File

@ -1,39 +1,160 @@
#include "ftxui/component/event.hpp" #include "ftxui/component/event.hpp"
#include "ftxui/screen/string.hpp"
namespace ftxui { 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 --- // static
Event Event::Character(int c) { Event Event::Character(char c) {
return Event{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<char()>& 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<char()> getchar, std::string input) {
while (1) {
char key = getchar();
input += key;
if ('0' <= key && key <= '9')
continue;
return;
}
}
Event ParseCSI(std::function<char()> 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<char()> 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<char()> 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<char()> 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<char()> 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 --- // --- Arrow ---
Event Event::ArrowLeft{ESC, '[', 'D'}; Event Event::ArrowLeft = Event::Special("\e[D");
Event Event::ArrowRight{ESC, '[', 'C'}; Event Event::ArrowRight = Event::Special("\e[C");
Event Event::ArrowUp{ESC, '[', 'A'}; Event Event::ArrowUp = Event::Special("\e[A");
Event Event::ArrowDown{ESC, '[', 'B'}; 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 --- } // namespace ftxui
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

View File

@ -73,10 +73,8 @@ bool Input::OnEvent(Event event) {
} }
// Content // Content
constexpr char ESC = char(27); if (event.is_character()) {
if (event.values[0] != ESC) { content.insert(cursor_position, 1, event.character());
wchar_t v = (char)event.values[0];
content.insert(cursor_position, 1, v);
cursor_position++; cursor_position++;
return true; return true;
} }

View File

@ -4,47 +4,13 @@
#include <termios.h> #include <termios.h>
#include <unistd.h> #include <unistd.h>
#include <iostream> #include <iostream>
#include "ftxui/component/component.hpp"
#include "ftxui/screen/terminal.hpp"
#include <thread> #include <thread>
#include "ftxui/component/component.hpp"
#include "ftxui/screen/string.hpp"
#include "ftxui/screen/terminal.hpp"
namespace ftxui { 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, ScreenInteractive::ScreenInteractive(int dimx,
int dimy, int dimy,
Dimension dimension) Dimension dimension)
@ -109,8 +75,10 @@ void ScreenInteractive::Loop(Component* component) {
tcsetattr(STDIN_FILENO, TCSANOW, &terminal_configuration_new); tcsetattr(STDIN_FILENO, TCSANOW, &terminal_configuration_new);
std::thread read_char([this]() { std::thread read_char([this]() {
while (!quit_) while (!quit_) {
PostEvent(GetEvent()); auto event = Event::GetEvent([] { return (char)getchar(); });
PostEvent(std::move(event));
}
}); });
std::string reset_position; std::string reset_position;

View File

@ -1,5 +1,5 @@
#include "ftxui/dom/node.hpp"
#include "ftxui/dom/elements.hpp" #include "ftxui/dom/elements.hpp"
#include "ftxui/dom/node.hpp"
namespace ftxui { namespace ftxui {

View File

@ -9,7 +9,7 @@ namespace ftxui {
namespace { namespace {
static const wchar_t* BOLD_SET = L"\e[1m"; 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_SET = L"\e[2m";
static const wchar_t* DIM_RESET = L"\e[22m"; 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; Pixel dev_null_pixel;
} // namespace } // namespace
Dimension Dimension::Fixed(int v) { Dimension Dimension::Fixed(int v) {
return Dimension{v, 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 || if (next.foreground_color != previous.foreground_color ||
next.background_color != previous.background_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((uint8_t)next.foreground_color)) +
ss << L"\e[" + to_wstring(std::to_string(10 + (uint8_t)next.background_color)) + L"m"; L"m";
ss << L"\e[" +
to_wstring(std::to_string(10 + (uint8_t)next.background_color)) +
L"m";
} }
previous = next; previous = next;
@ -105,7 +108,6 @@ std::string Screen::ToString() {
UpdatePixelStyle(ss, previous_pixel, pixels_[y][x]); UpdatePixelStyle(ss, previous_pixel, pixels_[y][x]);
ss << pixels_[y][x].character; ss << pixels_[y][x].character;
} }
} }
Pixel final_pixel; Pixel final_pixel;
@ -115,7 +117,7 @@ std::string Screen::ToString() {
} }
wchar_t& Screen::at(int x, int y) { 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) { Pixel& Screen::PixelAt(int x, int y) {
@ -137,36 +139,34 @@ void Screen::Clear() {
} }
void Screen::ApplyShader() { void Screen::ApplyShader() {
// Merge box characters togethers. // Merge box characters togethers.
for(int y = 1; y<dimy_; ++y) { for (int y = 1; y < dimy_; ++y) {
for(int x = 1; x<dimx_; ++x) { for (int x = 1; x < dimx_; ++x) {
wchar_t& left = at(x - 1, y); wchar_t& left = at(x - 1, y);
wchar_t& top = at(x, y - 1); wchar_t& top = at(x, y - 1);
wchar_t& cur = at(x, y); wchar_t& cur = at(x, y);
// Left vs current // Left vs current
if (cur== U'' && left == U'') if (cur == U'' && left == U'')
cur= U''; cur = U'';
if (cur== U'' && left == U'') if (cur == U'' && left == U'')
left = U''; left = U'';
if (cur== U'' && left == U'') if (cur == U'' && left == U'')
cur= U''; cur = U'';
if (cur== U'' && left == U'') if (cur == U'' && left == U'')
left = U''; left = U'';
// Top vs current // Top vs current
if (cur== U'' && top == U'') if (cur == U'' && top == U'')
cur= U''; cur = U'';
if (cur== U'' && top == U'') if (cur == U'' && top == U'')
top = U''; top = U'';
if (cur== U'' && top == U'') if (cur == U'' && top == U'')
cur= U''; cur = U'';
if (cur== U'' && top == U'') if (cur == U'' && top == U'')
top = U''; top = U'';
} }
} }
} }
}; // namespace ftxui }; // namespace ftxui

View File

@ -1,7 +1,7 @@
#include <iostream>
#include <sys/ioctl.h>
#include <stdio.h> #include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h> #include <unistd.h>
#include <iostream>
#include "ftxui/screen/terminal.hpp" #include "ftxui/screen/terminal.hpp"
@ -9,7 +9,7 @@ namespace ftxui {
Terminal::Dimensions Terminal::Size() { Terminal::Dimensions Terminal::Size() {
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
return Dimensions{80,43}; return Dimensions{80, 43};
#else #else
winsize w; winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
@ -17,4 +17,4 @@ Terminal::Dimensions Terminal::Size() {
#endif #endif
} }
} // namespace ftxui } // namespace ftxui