From b78b97056b59889a35b314c0a75d4c2afb4cd59b Mon Sep 17 00:00:00 2001 From: Arthur Sonzogni Date: Wed, 2 Jul 2025 15:23:01 +0200 Subject: [PATCH] Stop using Sender/Receiver in TerminalInputParser. (#1073) Stop using Sender/Receiver in TerminalInputParser. This will help removing usage of thread. At some point, my goal is to have an initialization step when installing the ScreenInteractive so that we can provide the terminal ID synchronously without losing some events. This will help with: https://github.com/ArthurSonzogni/FTXUI/pull/1069 --- examples/component/button.cpp | 69 ++- include/ftxui/dom/canvas.hpp | 2 +- include/ftxui/dom/flexbox_config.hpp | 1 - include/ftxui/screen/box.hpp | 2 +- src/ftxui/component/component_fuzzer.cpp | 18 +- src/ftxui/component/screen_interactive.cpp | 23 +- .../component/screen_interactive_test.cpp | 11 +- src/ftxui/component/terminal_input_parser.cpp | 18 +- src/ftxui/component/terminal_input_parser.hpp | 9 +- .../component/terminal_input_parser_test.cpp | 446 ++++++++---------- .../terminal_input_parser_test_fuzzer.cpp | 15 +- 11 files changed, 311 insertions(+), 303 deletions(-) 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. }