diff --git a/examples/component/button.cpp b/examples/component/button.cpp index 63032f42..b336a6e0 100644 --- a/examples/component/button.cpp +++ b/examples/component/button.cpp @@ -1,9 +1,64 @@ -#include "ftxui/component/component.hpp" -#include "ftxui/component/screen_interactive.hpp" +// 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. +#include // for shared_ptr, __shared_ptr_access +#include // for operator+, to_string -int main(){ - auto screen = ftxui::ScreenInteractive::Fullscreen(); - auto testComponent = ftxui::Renderer([](){return ftxui::text("test Component");}); - screen.Loop(testComponent); - return 0; +#include "ftxui/component/captured_mouse.hpp" // for ftxui +#include "ftxui/component/component.hpp" // for Button, Horizontal, Renderer +#include "ftxui/component/component_base.hpp" // for ComponentBase +#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive +#include "ftxui/dom/elements.hpp" // for separator, gauge, text, Element, operator|, vbox, border + +using namespace ftxui; + +// This is a helper function to create a button with a custom style. +// The style is defined by a lambda function that takes an EntryState and +// returns an Element. +// We are using `center` to center the text inside the button, then `border` to +// add a border around the button, and finally `flex` to make the button fill +// the available space. +ButtonOption Style() { + auto option = ButtonOption::Animated(); + option.transform = [](const EntryState& s) { + auto element = text(s.label); + if (s.focused) { + element |= bold; + } + return element | center | borderEmpty | flex; + }; + return option; +} + +int main() { + int value = 50; + + // clang-format off + auto btn_dec_01 = Button("-1", [&] { value += 1; }, Style()); + auto btn_inc_01 = Button("+1", [&] { value -= 1; }, Style()); + auto btn_dec_10 = Button("-10", [&] { value -= 10; }, Style()); + auto btn_inc_10 = Button("+10", [&] { value += 10; }, Style()); + // clang-format on + + // The tree of components. This defines how to navigate using the keyboard. + // The selected `row` is shared to get a grid layout. + int row = 0; + auto buttons = Container::Vertical({ + Container::Horizontal({btn_dec_01, btn_inc_01}, &row) | flex, + Container::Horizontal({btn_dec_10, btn_inc_10}, &row) | flex, + }); + + // Modify the way to render them on screen: + auto component = Renderer(buttons, [&] { + return vbox({ + text("value = " + std::to_string(value)), + separator(), + buttons->Render() | flex, + }) | + flex | border; + }); + + auto screen = ScreenInteractive::FitComponent(); + screen.Loop(component); + return 0; } diff --git a/include/ftxui/dom/canvas.hpp b/include/ftxui/dom/canvas.hpp index 1157772e..2a5ccd8e 100644 --- a/include/ftxui/dom/canvas.hpp +++ b/include/ftxui/dom/canvas.hpp @@ -30,7 +30,7 @@ namespace ftxui { /// - 2x4 braille characters (1x1 pixel) /// - 2x2 block characters (2x2 pixels) /// - 2x4 normal characters (2x4 pixels) -/// +/// /// You need to multiply the x coordinate by 2 and the y coordinate by 4 to /// get the correct position in the terminal. /// diff --git a/include/ftxui/dom/flexbox_config.hpp b/include/ftxui/dom/flexbox_config.hpp index d8e421fd..de437005 100644 --- a/include/ftxui/dom/flexbox_config.hpp +++ b/include/ftxui/dom/flexbox_config.hpp @@ -12,7 +12,6 @@ namespace ftxui { - /// @brief FlexboxConfig is a configuration structure that defines the layout /// properties for a flexbox container. // diff --git a/include/ftxui/screen/box.hpp b/include/ftxui/screen/box.hpp index 192afff4..929098ae 100644 --- a/include/ftxui/screen/box.hpp +++ b/include/ftxui/screen/box.hpp @@ -11,7 +11,7 @@ namespace ftxui { /// It is defined by its minimum and maximum coordinates along the x and y axes. /// Note that the coordinates are inclusive, meaning that the box includes both /// the minimum and maximum values. -/// +/// /// @ingroup screen struct Box { int x_min = 0; diff --git a/src/ftxui/component/component_fuzzer.cpp b/src/ftxui/component/component_fuzzer.cpp index f0bd6ddc..405a2bdd 100644 --- a/src/ftxui/component/component_fuzzer.cpp +++ b/src/ftxui/component/component_fuzzer.cpp @@ -2,6 +2,7 @@ // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. #include +#include #include #include "ftxui/component/component.hpp" #include "ftxui/component/terminal_input_parser.hpp" @@ -212,16 +213,17 @@ extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) { auto screen = Screen::Create(Dimension::Fixed(width), Dimension::Fixed(height)); - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - for (size_t i = 0; i < size; ++i) - parser.Add(data[i]); + // Generate some events. + std::vector events; + auto parser = + TerminalInputParser([&](const Event& event) { events.push_back(event); }); + + for (size_t i = 0; i < size; ++i) { + parser.Add(data[i]); } - Task event; - while (event_receiver->Receive(&event)) { - component->OnEvent(std::get(event)); + for (const auto& event : events) { + component->OnEvent(event); auto document = component->Render(); Render(screen, document); } diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index dadeff7e..e9f3f0d9 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -84,7 +84,8 @@ constexpr int timeout_milliseconds = 20; void EventListener(std::atomic* quit, Sender out) { auto console = GetStdHandle(STD_INPUT_HANDLE); - auto parser = TerminalInputParser(out->Clone()); + auto parser = + TerminalInputParser([&](Event event) { out->Send(std::move(event)); }); while (!*quit) { // Throttle ReadConsoleInput by waiting 250ms, this wait function will // return if there is input in the console. @@ -137,7 +138,8 @@ void EventListener(std::atomic* quit, Sender out) { // Read char from the terminal. void EventListener(std::atomic* quit, Sender out) { - auto parser = TerminalInputParser(std::move(out)); + auto parser = + TerminalInputParser([&](Event event) { out->Send(std::move(event)); }); char c; while (!*quit) { @@ -173,7 +175,8 @@ int CheckStdinReady(int usec_timeout) { // Read char from the terminal. void EventListener(std::atomic* quit, Sender out) { - auto parser = TerminalInputParser(std::move(out)); + auto parser = + TerminalInputParser([&](Event event) { out->Send(std::move(event)); }); while (!*quit) { if (!CheckStdinReady(timeout_microseconds)) { @@ -381,10 +384,10 @@ ScreenInteractive ScreenInteractive::Fullscreen() { ScreenInteractive ScreenInteractive::FullscreenPrimaryScreen() { auto terminal = Terminal::Size(); return { - Dimension::Fullscreen, - terminal.dimx, - terminal.dimy, - /*use_alternative_screen=*/false, + Dimension::Fullscreen, + terminal.dimx, + terminal.dimy, + /*use_alternative_screen=*/false, }; } @@ -409,7 +412,7 @@ ScreenInteractive ScreenInteractive::TerminalOutput() { return { Dimension::TerminalOutput, terminal.dimx, - terminal.dimy, // Best guess. + terminal.dimy, // Best guess. /*use_alternative_screen=*/false, }; } @@ -421,8 +424,8 @@ ScreenInteractive ScreenInteractive::FitComponent() { auto terminal = Terminal::Size(); return { Dimension::FitComponent, - terminal.dimx, // Best guess. - terminal.dimy, // Best guess. + terminal.dimx, // Best guess. + terminal.dimy, // Best guess. false, }; } diff --git a/src/ftxui/component/screen_interactive_test.cpp b/src/ftxui/component/screen_interactive_test.cpp index 7d2da99d..a6869339 100644 --- a/src/ftxui/component/screen_interactive_test.cpp +++ b/src/ftxui/component/screen_interactive_test.cpp @@ -27,9 +27,9 @@ namespace { // Capture the standard output (stdout) to a string. class StdCapture { public: - explicit StdCapture(std::string* captured) - : captured_(captured) { - if (pipe(pipefd_) != 0) return; + explicit StdCapture(std::string* captured) : captured_(captured) { + if (pipe(pipefd_) != 0) + return; old_stdout_ = dup(fileno(stdout)); fflush(stdout); dup2(pipefd_[1], fileno(stdout)); @@ -188,9 +188,7 @@ TEST(ScreenInteractive, FixedSizeInitialFrame) { auto capture = StdCapture(&output); auto screen = ScreenInteractive::FixedSize(2, 2); - auto component = Renderer([&] { - return text("AB"); - }); + auto component = Renderer([&] { return text("AB"); }); Loop loop(&screen, component); loop.RunOnce(); @@ -241,7 +239,6 @@ TEST(ScreenInteractive, FixedSizeInitialFrame) { "\r\n"sv; ASSERT_EQ(expected, output); #endif - } } // namespace ftxui diff --git a/src/ftxui/component/terminal_input_parser.cpp b/src/ftxui/component/terminal_input_parser.cpp index 2100d9ed..5e2c920c 100644 --- a/src/ftxui/component/terminal_input_parser.cpp +++ b/src/ftxui/component/terminal_input_parser.cpp @@ -5,7 +5,7 @@ #include // for uint32_t #include // for Mouse, Mouse::Button, Mouse::Motion -#include // for SenderImpl, Sender +#include // for std::function #include #include // for unique_ptr, allocator #include // for move @@ -90,7 +90,7 @@ const std::map g_uniformize = { {"\x1B[X", "\x1B[24~"}, // F12 }; -TerminalInputParser::TerminalInputParser(Sender out) +TerminalInputParser::TerminalInputParser(std::function out) : out_(std::move(out)) {} void TerminalInputParser::Timeout(int time) { @@ -131,7 +131,7 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) { return; case CHARACTER: - out_->Send(Event::Character(std::move(pending_))); + out_(Event::Character(std::move(pending_))); pending_.clear(); return; @@ -140,25 +140,25 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) { if (it != g_uniformize.end()) { pending_ = it->second; } - out_->Send(Event::Special(std::move(pending_))); + out_(Event::Special(std::move(pending_))); pending_.clear(); } return; case MOUSE: - out_->Send(Event::Mouse(std::move(pending_), output.mouse)); // NOLINT + out_(Event::Mouse(std::move(pending_), output.mouse)); // NOLINT pending_.clear(); return; case CURSOR_POSITION: - out_->Send(Event::CursorPosition(std::move(pending_), // NOLINT - output.cursor.x, // NOLINT - output.cursor.y)); // NOLINT + out_(Event::CursorPosition(std::move(pending_), // NOLINT + output.cursor.x, // NOLINT + output.cursor.y)); // NOLINT pending_.clear(); return; case CURSOR_SHAPE: - out_->Send(Event::CursorShape(std::move(pending_), output.cursor_shape)); + out_(Event::CursorShape(std::move(pending_), output.cursor_shape)); pending_.clear(); return; } diff --git a/src/ftxui/component/terminal_input_parser.hpp b/src/ftxui/component/terminal_input_parser.hpp index 524994a2..2faaf570 100644 --- a/src/ftxui/component/terminal_input_parser.hpp +++ b/src/ftxui/component/terminal_input_parser.hpp @@ -4,12 +4,11 @@ #ifndef FTXUI_COMPONENT_TERMINAL_INPUT_PARSER #define FTXUI_COMPONENT_TERMINAL_INPUT_PARSER +#include #include // for string #include // for vector -#include "ftxui/component/mouse.hpp" // for Mouse -#include "ftxui/component/receiver.hpp" // for Sender -#include "ftxui/component/task.hpp" // for Task +#include "ftxui/component/mouse.hpp" // for Mouse namespace ftxui { struct Event; @@ -17,7 +16,7 @@ struct Event; // Parse a sequence of |char| accross |time|. Produces |Event|. class TerminalInputParser { public: - explicit TerminalInputParser(Sender out); + explicit TerminalInputParser(std::function out); void Timeout(int time); void Add(char c); @@ -62,7 +61,7 @@ class TerminalInputParser { Output ParseMouse(bool altered, bool pressed, std::vector arguments); Output ParseCursorPosition(std::vector arguments); - Sender out_; + std::function out_; int position_ = -1; int timeout_ = 0; std::string pending_; diff --git a/src/ftxui/component/terminal_input_parser_test.cpp b/src/ftxui/component/terminal_input_parser_test.cpp index fec73d9d..4db10f3b 100644 --- a/src/ftxui/component/terminal_input_parser_test.cpp +++ b/src/ftxui/component/terminal_input_parser_test.cpp @@ -2,12 +2,12 @@ // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. #include // for Mouse, Mouse::Left, Mouse::Middle, Mouse::Pressed, Mouse::Released, Mouse::Right -#include // for Task +#include // for function #include // for initializer_list #include // for allocator, unique_ptr +#include // for vector #include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::Backspace, Event::End, Event::Home, Event::Custom, Event::Delete, Event::F1, Event::F10, Event::F11, Event::F12, Event::F2, Event::F3, Event::F4, Event::F5, Event::F6, Event::F7, Event::F8, Event::F9, Event::PageDown, Event::PageUp, Event::Tab, Event::TabReverse, Event::Escape -#include "ftxui/component/receiver.hpp" // for MakeReceiver, ReceiverImpl #include "ftxui/component/terminal_input_parser.hpp" #include "gtest/gtest.h" // for AssertionResult, Test, Message, TestPartResult, EXPECT_EQ, EXPECT_TRUE, TEST, EXPECT_FALSE @@ -22,228 +22,197 @@ TEST(Event, Character) { for (char c = 'A'; c <= 'Z'; ++c) basic_char.push_back(c); - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - for (char c : basic_char) - parser.Add(c); - } + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + for (char c : basic_char) + parser.Add(c); - Task received; - for (char c : basic_char) { - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_TRUE(std::get(received).is_character()); - EXPECT_EQ(c, std::get(received).character()[0]); + for (size_t i = 0; i < basic_char.size(); ++i) { + EXPECT_TRUE(received_events[i].is_character()); + EXPECT_EQ(basic_char[i], received_events[i].character()[0]); } - EXPECT_FALSE(event_receiver->Receive(&received)); + EXPECT_EQ(received_events.size(), basic_char.size()); } TEST(Event, EscapeKeyWithoutWaiting) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - parser.Add('\x1B'); - } + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + parser.Add(''); - Task received; - EXPECT_FALSE(event_receiver->Receive(&received)); + EXPECT_TRUE(received_events.empty()); } TEST(Event, EscapeKeyNotEnoughWait) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - parser.Add('\x1B'); - parser.Timeout(49); - } + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + parser.Add(''); + parser.Timeout(49); - Task received; - EXPECT_FALSE(event_receiver->Receive(&received)); + EXPECT_TRUE(received_events.empty()); } TEST(Event, EscapeKeyEnoughWait) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - parser.Add('\x1B'); - parser.Timeout(50); - } + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + parser.Add(''); + parser.Timeout(50); - Task received; - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_EQ(std::get(received), Event::Escape); - EXPECT_FALSE(event_receiver->Receive(&received)); + EXPECT_EQ(1, received_events.size()); + EXPECT_EQ(received_events[0], Event::Escape); } TEST(Event, EscapeFast) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - parser.Add('\x1B'); - parser.Add('a'); - parser.Add('\x1B'); - parser.Add('b'); - parser.Timeout(49); - } - Task received; - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_EQ(std::get(received), Event::AltA); - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_EQ(std::get(received), Event::AltB); - EXPECT_FALSE(event_receiver->Receive(&received)); + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + parser.Add(''); + parser.Add('a'); + parser.Add(''); + parser.Add('b'); + parser.Timeout(49); + + EXPECT_EQ(2, received_events.size()); + EXPECT_EQ(received_events[0], Event::AltA); + EXPECT_EQ(received_events[1], Event::AltB); } TEST(Event, MouseLeftClickPressed) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - parser.Add('\x1B'); - parser.Add('['); - parser.Add('0'); - parser.Add(';'); - parser.Add('1'); - parser.Add('2'); - parser.Add(';'); - parser.Add('4'); - parser.Add('2'); - parser.Add('M'); - } + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + parser.Add(''); + parser.Add('['); + parser.Add('0'); + parser.Add(';'); + parser.Add('1'); + parser.Add('2'); + parser.Add(';'); + parser.Add('4'); + parser.Add('2'); + parser.Add('M'); - Task received; - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_TRUE(std::get(received).is_mouse()); - EXPECT_EQ(Mouse::Left, std::get(received).mouse().button); - EXPECT_EQ(12, std::get(received).mouse().x); - EXPECT_EQ(42, std::get(received).mouse().y); - EXPECT_EQ(std::get(received).mouse().motion, Mouse::Pressed); - EXPECT_FALSE(event_receiver->Receive(&received)); + EXPECT_EQ(1, received_events.size()); + EXPECT_TRUE(received_events[0].is_mouse()); + EXPECT_EQ(Mouse::Left, received_events[0].mouse().button); + EXPECT_EQ(12, received_events[0].mouse().x); + EXPECT_EQ(42, received_events[0].mouse().y); + EXPECT_EQ(received_events[0].mouse().motion, Mouse::Pressed); } TEST(Event, MouseLeftMoved) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - parser.Add('\x1B'); - parser.Add('['); - parser.Add('3'); - parser.Add('2'); - parser.Add(';'); - parser.Add('1'); - parser.Add('2'); - parser.Add(';'); - parser.Add('4'); - parser.Add('2'); - parser.Add('M'); - } + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + parser.Add(''); + parser.Add('['); + parser.Add('3'); + parser.Add('2'); + parser.Add(';'); + parser.Add('1'); + parser.Add('2'); + parser.Add(';'); + parser.Add('4'); + parser.Add('2'); + parser.Add('M'); - Task received; - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_TRUE(std::get(received).is_mouse()); - EXPECT_EQ(Mouse::Left, std::get(received).mouse().button); - EXPECT_EQ(12, std::get(received).mouse().x); - EXPECT_EQ(42, std::get(received).mouse().y); - EXPECT_EQ(std::get(received).mouse().motion, Mouse::Moved); - EXPECT_FALSE(event_receiver->Receive(&received)); + EXPECT_EQ(1, received_events.size()); + EXPECT_TRUE(received_events[0].is_mouse()); + EXPECT_EQ(Mouse::Left, received_events[0].mouse().button); + EXPECT_EQ(12, received_events[0].mouse().x); + EXPECT_EQ(42, received_events[0].mouse().y); + EXPECT_EQ(received_events[0].mouse().motion, Mouse::Moved); } TEST(Event, MouseLeftClickReleased) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - parser.Add('\x1B'); - parser.Add('['); - parser.Add('0'); - parser.Add(';'); - parser.Add('1'); - parser.Add('2'); - parser.Add(';'); - parser.Add('4'); - parser.Add('2'); - parser.Add('m'); - } + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + parser.Add(''); + parser.Add('['); + parser.Add('0'); + parser.Add(';'); + parser.Add('1'); + parser.Add('2'); + parser.Add(';'); + parser.Add('4'); + parser.Add('2'); + parser.Add('m'); - Task received; - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_TRUE(std::get(received).is_mouse()); - EXPECT_EQ(Mouse::Left, std::get(received).mouse().button); - EXPECT_EQ(12, std::get(received).mouse().x); - EXPECT_EQ(42, std::get(received).mouse().y); - EXPECT_EQ(std::get(received).mouse().motion, Mouse::Released); - EXPECT_FALSE(event_receiver->Receive(&received)); + EXPECT_EQ(1, received_events.size()); + EXPECT_TRUE(received_events[0].is_mouse()); + EXPECT_EQ(Mouse::Left, received_events[0].mouse().button); + EXPECT_EQ(12, received_events[0].mouse().x); + EXPECT_EQ(42, received_events[0].mouse().y); + EXPECT_EQ(received_events[0].mouse().motion, Mouse::Released); } TEST(Event, MouseReporting) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - parser.Add('\x1B'); - parser.Add('['); - parser.Add('1'); - parser.Add('2'); - parser.Add(';'); - parser.Add('4'); - parser.Add('2'); - parser.Add('R'); - } + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + parser.Add(''); + parser.Add('['); + parser.Add('1'); + parser.Add('2'); + parser.Add(';'); + parser.Add('4'); + parser.Add('2'); + parser.Add('R'); - Task received; - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_TRUE(std::get(received).is_cursor_position()); - EXPECT_EQ(42, std::get(received).cursor_x()); - EXPECT_EQ(12, std::get(received).cursor_y()); - EXPECT_FALSE(event_receiver->Receive(&received)); + EXPECT_EQ(1, received_events.size()); + EXPECT_TRUE(received_events[0].is_cursor_position()); + EXPECT_EQ(42, received_events[0].cursor_x()); + EXPECT_EQ(12, received_events[0].cursor_y()); } TEST(Event, MouseMiddleClick) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - parser.Add('\x1B'); - parser.Add('['); - parser.Add('3'); - parser.Add('3'); - parser.Add(';'); - parser.Add('1'); - parser.Add('2'); - parser.Add(';'); - parser.Add('4'); - parser.Add('2'); - parser.Add('M'); - } + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + parser.Add(''); + parser.Add('['); + parser.Add('3'); + parser.Add('3'); + parser.Add(';'); + parser.Add('1'); + parser.Add('2'); + parser.Add(';'); + parser.Add('4'); + parser.Add('2'); + parser.Add('M'); - Task received; - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_TRUE(std::get(received).is_mouse()); - EXPECT_EQ(Mouse::Middle, std::get(received).mouse().button); - EXPECT_EQ(12, std::get(received).mouse().x); - EXPECT_EQ(42, std::get(received).mouse().y); - EXPECT_FALSE(event_receiver->Receive(&received)); + EXPECT_EQ(1, received_events.size()); + EXPECT_TRUE(received_events[0].is_mouse()); + EXPECT_EQ(Mouse::Middle, received_events[0].mouse().button); + EXPECT_EQ(12, received_events[0].mouse().x); + EXPECT_EQ(42, received_events[0].mouse().y); } TEST(Event, MouseRightClick) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - parser.Add('\x1B'); - parser.Add('['); - parser.Add('3'); - parser.Add('4'); - parser.Add(';'); - parser.Add('1'); - parser.Add('2'); - parser.Add(';'); - parser.Add('4'); - parser.Add('2'); - parser.Add('M'); - } + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + parser.Add(''); + parser.Add('['); + parser.Add('3'); + parser.Add('4'); + parser.Add(';'); + parser.Add('1'); + parser.Add('2'); + parser.Add(';'); + parser.Add('4'); + parser.Add('2'); + parser.Add('M'); - Task received; - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_TRUE(std::get(received).is_mouse()); - EXPECT_EQ(Mouse::Right, std::get(received).mouse().button); - EXPECT_EQ(12, std::get(received).mouse().x); - EXPECT_EQ(42, std::get(received).mouse().y); - EXPECT_FALSE(event_receiver->Receive(&received)); + EXPECT_EQ(1, received_events.size()); + EXPECT_TRUE(received_events[0].is_mouse()); + EXPECT_EQ(Mouse::Right, received_events[0].mouse().button); + EXPECT_EQ(12, received_events[0].mouse().x); + EXPECT_EQ(42, received_events[0].mouse().y); } TEST(Event, UTF8) { @@ -313,31 +282,29 @@ TEST(Event, UTF8) { }; for (auto test : kTestCase) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - for (auto input : test.input) - parser.Add(input); - } - Task received; + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + for (auto input : test.input) + parser.Add(input); + if (test.valid) { - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_TRUE(std::get(received).is_character()); + EXPECT_EQ(1, received_events.size()); + EXPECT_TRUE(received_events[0].is_character()); + } else { + EXPECT_TRUE(received_events.empty()); } - EXPECT_FALSE(event_receiver->Receive(&received)); } } TEST(Event, NewLine) { for (char newline : {'\r', '\n'}) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - parser.Add(newline); - } - Task received; - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_TRUE(std::get(received) == Event::Return); + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + parser.Add(newline); + EXPECT_EQ(1, received_events.size()); + EXPECT_TRUE(received_events[0] == Event::Return); } } @@ -357,17 +324,16 @@ TEST(Event, Control) { cases.push_back({char(127), false}); for (auto test : cases) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - parser.Add(test.input); - } - Task received; + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + parser.Add(test.input); + if (test.cancel) { - EXPECT_FALSE(event_receiver->Receive(&received)); + EXPECT_TRUE(received_events.empty()); } else { - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_EQ(std::get(received), Event::Special({test.input})); + EXPECT_EQ(1, received_events.size()); + EXPECT_EQ(received_events[0], Event::Special({test.input})); } } } @@ -385,10 +351,9 @@ TEST(Event, Special) { Event expected; } kTestCase[] = { // Arrow (default cursor mode) - {str("\x1B[A"), Event::ArrowUp}, {str("\x1B[B"), Event::ArrowDown}, - {str("\x1B[C"), Event::ArrowRight}, {str("\x1B[D"), Event::ArrowLeft}, - {str("\x1B[H"), Event::Home}, {str("\x1B[F"), Event::End}, - /* + {str(""), Event::ArrowUp}, {str(""), Event::ArrowDown}, + {str(""), Event::ArrowRight}, {str(""), Event::ArrowLeft}, + {str(""), Event::Home}, {str(""), Event::End}, // Arrow (application cursor mode) {str("\x1BOA"), Event::ArrowUp}, @@ -469,45 +434,38 @@ TEST(Event, Special) { // Custom: {{0}, Event::Custom}, - */ }; for (auto test : kTestCase) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - for (auto input : test.input) { - parser.Add(input); - } + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + for (auto input : test.input) { + parser.Add(input); } - Task received; - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_EQ(std::get(received), test.expected); - EXPECT_FALSE(event_receiver->Receive(&received)); + EXPECT_EQ(1, received_events.size()); + EXPECT_EQ(received_events[0], test.expected); } } TEST(Event, DeviceControlString) { - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - parser.Add(27); // ESC - parser.Add(80); // P - parser.Add(49); // 1 - parser.Add(36); // $ - parser.Add(114); // r - parser.Add(49); // 1 - parser.Add(32); // SP - parser.Add(113); // q - parser.Add(27); // ESC - parser.Add(92); // (backslash) - } + std::vector received_events; + auto parser = TerminalInputParser( + [&](Event event) { received_events.push_back(std::move(event)); }); + parser.Add(27); // ESC + parser.Add(80); // P + parser.Add(49); // 1 + parser.Add(36); // $ + parser.Add(114); // r + parser.Add(49); // 1 + parser.Add(32); // SP + parser.Add(113); // q + parser.Add(27); // ESC + parser.Add(92); // (backslash) - Task received; - EXPECT_TRUE(event_receiver->Receive(&received)); - EXPECT_TRUE(std::get(received).is_cursor_shape()); - EXPECT_EQ(1, std::get(received).cursor_shape()); - EXPECT_FALSE(event_receiver->Receive(&received)); + EXPECT_EQ(1, received_events.size()); + EXPECT_TRUE(received_events[0].is_cursor_shape()); + EXPECT_EQ(1, received_events[0].cursor_shape()); } } // namespace ftxui diff --git a/src/ftxui/component/terminal_input_parser_test_fuzzer.cpp b/src/ftxui/component/terminal_input_parser_test_fuzzer.cpp index 15ccbc8c..00a84284 100644 --- a/src/ftxui/component/terminal_input_parser_test_fuzzer.cpp +++ b/src/ftxui/component/terminal_input_parser_test_fuzzer.cpp @@ -1,21 +1,16 @@ // Copyright 2021 Arthur Sonzogni. All rights reserved. // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. +#include +#include #include "ftxui/component/terminal_input_parser.hpp" extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) { using namespace ftxui; - auto event_receiver = MakeReceiver(); - { - auto parser = TerminalInputParser(event_receiver->MakeSender()); - for (size_t i = 0; i < size; ++i) { - parser.Add(data[i]); - } + auto parser = TerminalInputParser([&](Event) {}); + for (size_t i = 0; i < size; ++i) { + parser.Add(data[i]); } - Task received; - while (event_receiver->Receive(&received)) { - // Do nothing. - } return 0; // Non-zero return values are reserved for future use. }