Stop using Sender/Receiver in TerminalInputParser. (#1073)
Some checks failed
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled

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
This commit is contained in:
Arthur Sonzogni 2025-07-02 15:23:01 +02:00 committed by GitHub
parent 68fc9b1212
commit b78b97056b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 311 additions and 303 deletions

View File

@ -1,9 +1,64 @@
#include "ftxui/component/component.hpp" // Copyright 2020 Arthur Sonzogni. All rights reserved.
#include "ftxui/component/screen_interactive.hpp" // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#include <memory> // for shared_ptr, __shared_ptr_access
#include <string> // for operator+, to_string
#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 main() {
auto screen = ftxui::ScreenInteractive::Fullscreen(); int value = 50;
auto testComponent = ftxui::Renderer([](){return ftxui::text("test Component");});
screen.Loop(testComponent); // 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; return 0;
} }

View File

@ -12,7 +12,6 @@
namespace ftxui { namespace ftxui {
/// @brief FlexboxConfig is a configuration structure that defines the layout /// @brief FlexboxConfig is a configuration structure that defines the layout
/// properties for a flexbox container. /// properties for a flexbox container.
// //

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by the MIT license that can be found in // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file. // the LICENSE file.
#include <cassert> #include <cassert>
#include <ftxui/component/event.hpp>
#include <vector> #include <vector>
#include "ftxui/component/component.hpp" #include "ftxui/component/component.hpp"
#include "ftxui/component/terminal_input_parser.hpp" #include "ftxui/component/terminal_input_parser.hpp"
@ -212,16 +213,17 @@ extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) {
auto screen = auto screen =
Screen::Create(Dimension::Fixed(width), Dimension::Fixed(height)); Screen::Create(Dimension::Fixed(width), Dimension::Fixed(height));
auto event_receiver = MakeReceiver<Task>(); // Generate some events.
{ std::vector<Event> events;
auto parser = TerminalInputParser(event_receiver->MakeSender()); auto parser =
for (size_t i = 0; i < size; ++i) TerminalInputParser([&](const Event& event) { events.push_back(event); });
for (size_t i = 0; i < size; ++i) {
parser.Add(data[i]); parser.Add(data[i]);
} }
Task event; for (const auto& event : events) {
while (event_receiver->Receive(&event)) { component->OnEvent(event);
component->OnEvent(std::get<Event>(event));
auto document = component->Render(); auto document = component->Render();
Render(screen, document); Render(screen, document);
} }

View File

@ -84,7 +84,8 @@ constexpr int timeout_milliseconds = 20;
void EventListener(std::atomic<bool>* quit, Sender<Task> out) { void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
auto console = GetStdHandle(STD_INPUT_HANDLE); auto console = GetStdHandle(STD_INPUT_HANDLE);
auto parser = TerminalInputParser(out->Clone()); auto parser =
TerminalInputParser([&](Event event) { out->Send(std::move(event)); });
while (!*quit) { while (!*quit) {
// Throttle ReadConsoleInput by waiting 250ms, this wait function will // Throttle ReadConsoleInput by waiting 250ms, this wait function will
// return if there is input in the console. // return if there is input in the console.
@ -137,7 +138,8 @@ void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
// Read char from the terminal. // Read char from the terminal.
void EventListener(std::atomic<bool>* quit, Sender<Task> out) { void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
auto parser = TerminalInputParser(std::move(out)); auto parser =
TerminalInputParser([&](Event event) { out->Send(std::move(event)); });
char c; char c;
while (!*quit) { while (!*quit) {
@ -173,7 +175,8 @@ int CheckStdinReady(int usec_timeout) {
// Read char from the terminal. // Read char from the terminal.
void EventListener(std::atomic<bool>* quit, Sender<Task> out) { void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
auto parser = TerminalInputParser(std::move(out)); auto parser =
TerminalInputParser([&](Event event) { out->Send(std::move(event)); });
while (!*quit) { while (!*quit) {
if (!CheckStdinReady(timeout_microseconds)) { if (!CheckStdinReady(timeout_microseconds)) {

View File

@ -27,9 +27,9 @@ namespace {
// Capture the standard output (stdout) to a string. // Capture the standard output (stdout) to a string.
class StdCapture { class StdCapture {
public: public:
explicit StdCapture(std::string* captured) explicit StdCapture(std::string* captured) : captured_(captured) {
: captured_(captured) { if (pipe(pipefd_) != 0)
if (pipe(pipefd_) != 0) return; return;
old_stdout_ = dup(fileno(stdout)); old_stdout_ = dup(fileno(stdout));
fflush(stdout); fflush(stdout);
dup2(pipefd_[1], fileno(stdout)); dup2(pipefd_[1], fileno(stdout));
@ -188,9 +188,7 @@ TEST(ScreenInteractive, FixedSizeInitialFrame) {
auto capture = StdCapture(&output); auto capture = StdCapture(&output);
auto screen = ScreenInteractive::FixedSize(2, 2); auto screen = ScreenInteractive::FixedSize(2, 2);
auto component = Renderer([&] { auto component = Renderer([&] { return text("AB"); });
return text("AB");
});
Loop loop(&screen, component); Loop loop(&screen, component);
loop.RunOnce(); loop.RunOnce();
@ -241,7 +239,6 @@ TEST(ScreenInteractive, FixedSizeInitialFrame) {
"\r\n"sv; "\r\n"sv;
ASSERT_EQ(expected, output); ASSERT_EQ(expected, output);
#endif #endif
} }
} // namespace ftxui } // namespace ftxui

View File

@ -5,7 +5,7 @@
#include <cstdint> // for uint32_t #include <cstdint> // for uint32_t
#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Button, Mouse::Motion #include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Button, Mouse::Motion
#include <ftxui/component/receiver.hpp> // for SenderImpl, Sender #include <functional> // for std::function
#include <map> #include <map>
#include <memory> // for unique_ptr, allocator #include <memory> // for unique_ptr, allocator
#include <utility> // for move #include <utility> // for move
@ -90,7 +90,7 @@ const std::map<std::string, std::string> g_uniformize = {
{"\x1B[X", "\x1B[24~"}, // F12 {"\x1B[X", "\x1B[24~"}, // F12
}; };
TerminalInputParser::TerminalInputParser(Sender<Task> out) TerminalInputParser::TerminalInputParser(std::function<void(Event)> out)
: out_(std::move(out)) {} : out_(std::move(out)) {}
void TerminalInputParser::Timeout(int time) { void TerminalInputParser::Timeout(int time) {
@ -131,7 +131,7 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) {
return; return;
case CHARACTER: case CHARACTER:
out_->Send(Event::Character(std::move(pending_))); out_(Event::Character(std::move(pending_)));
pending_.clear(); pending_.clear();
return; return;
@ -140,25 +140,25 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) {
if (it != g_uniformize.end()) { if (it != g_uniformize.end()) {
pending_ = it->second; pending_ = it->second;
} }
out_->Send(Event::Special(std::move(pending_))); out_(Event::Special(std::move(pending_)));
pending_.clear(); pending_.clear();
} }
return; return;
case MOUSE: case MOUSE:
out_->Send(Event::Mouse(std::move(pending_), output.mouse)); // NOLINT out_(Event::Mouse(std::move(pending_), output.mouse)); // NOLINT
pending_.clear(); pending_.clear();
return; return;
case CURSOR_POSITION: case CURSOR_POSITION:
out_->Send(Event::CursorPosition(std::move(pending_), // NOLINT out_(Event::CursorPosition(std::move(pending_), // NOLINT
output.cursor.x, // NOLINT output.cursor.x, // NOLINT
output.cursor.y)); // NOLINT output.cursor.y)); // NOLINT
pending_.clear(); pending_.clear();
return; return;
case CURSOR_SHAPE: case CURSOR_SHAPE:
out_->Send(Event::CursorShape(std::move(pending_), output.cursor_shape)); out_(Event::CursorShape(std::move(pending_), output.cursor_shape));
pending_.clear(); pending_.clear();
return; return;
} }

View File

@ -4,12 +4,11 @@
#ifndef FTXUI_COMPONENT_TERMINAL_INPUT_PARSER #ifndef FTXUI_COMPONENT_TERMINAL_INPUT_PARSER
#define FTXUI_COMPONENT_TERMINAL_INPUT_PARSER #define FTXUI_COMPONENT_TERMINAL_INPUT_PARSER
#include <functional>
#include <string> // for string #include <string> // for string
#include <vector> // for vector #include <vector> // for vector
#include "ftxui/component/mouse.hpp" // for Mouse #include "ftxui/component/mouse.hpp" // for Mouse
#include "ftxui/component/receiver.hpp" // for Sender
#include "ftxui/component/task.hpp" // for Task
namespace ftxui { namespace ftxui {
struct Event; struct Event;
@ -17,7 +16,7 @@ struct Event;
// Parse a sequence of |char| accross |time|. Produces |Event|. // Parse a sequence of |char| accross |time|. Produces |Event|.
class TerminalInputParser { class TerminalInputParser {
public: public:
explicit TerminalInputParser(Sender<Task> out); explicit TerminalInputParser(std::function<void(Event)> out);
void Timeout(int time); void Timeout(int time);
void Add(char c); void Add(char c);
@ -62,7 +61,7 @@ class TerminalInputParser {
Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments); Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments);
Output ParseCursorPosition(std::vector<int> arguments); Output ParseCursorPosition(std::vector<int> arguments);
Sender<Task> out_; std::function<void(Event)> out_;
int position_ = -1; int position_ = -1;
int timeout_ = 0; int timeout_ = 0;
std::string pending_; std::string pending_;

View File

@ -2,12 +2,12 @@
// Use of this source code is governed by the MIT license that can be found in // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file. // the LICENSE file.
#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Left, Mouse::Middle, Mouse::Pressed, Mouse::Released, Mouse::Right #include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Left, Mouse::Middle, Mouse::Pressed, Mouse::Released, Mouse::Right
#include <ftxui/component/task.hpp> // for Task #include <functional> // for function
#include <initializer_list> // for initializer_list #include <initializer_list> // for initializer_list
#include <memory> // for allocator, unique_ptr #include <memory> // for allocator, unique_ptr
#include <vector> // 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/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 "ftxui/component/terminal_input_parser.hpp"
#include "gtest/gtest.h" // for AssertionResult, Test, Message, TestPartResult, EXPECT_EQ, EXPECT_TRUE, TEST, EXPECT_FALSE #include "gtest/gtest.h" // for AssertionResult, Test, Message, TestPartResult, EXPECT_EQ, EXPECT_TRUE, TEST, EXPECT_FALSE
@ -22,82 +22,69 @@ TEST(Event, Character) {
for (char c = 'A'; c <= 'Z'; ++c) for (char c = 'A'; c <= 'Z'; ++c)
basic_char.push_back(c); basic_char.push_back(c);
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
for (char c : basic_char) for (char c : basic_char)
parser.Add(c); parser.Add(c);
}
Task received; for (size_t i = 0; i < basic_char.size(); ++i) {
for (char c : basic_char) { EXPECT_TRUE(received_events[i].is_character());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_EQ(basic_char[i], received_events[i].character()[0]);
EXPECT_TRUE(std::get<Event>(received).is_character());
EXPECT_EQ(c, std::get<Event>(received).character()[0]);
} }
EXPECT_FALSE(event_receiver->Receive(&received)); EXPECT_EQ(received_events.size(), basic_char.size());
} }
TEST(Event, EscapeKeyWithoutWaiting) { TEST(Event, EscapeKeyWithoutWaiting) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
}
Task received; EXPECT_TRUE(received_events.empty());
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, EscapeKeyNotEnoughWait) { TEST(Event, EscapeKeyNotEnoughWait) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Timeout(49); parser.Timeout(49);
}
Task received; EXPECT_TRUE(received_events.empty());
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, EscapeKeyEnoughWait) { TEST(Event, EscapeKeyEnoughWait) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Timeout(50); parser.Timeout(50);
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_EQ(received_events[0], Event::Escape);
EXPECT_EQ(std::get<Event>(received), Event::Escape);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, EscapeFast) { TEST(Event, EscapeFast) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Add('a'); parser.Add('a');
parser.Add('\x1B'); parser.Add('');
parser.Add('b'); parser.Add('b');
parser.Timeout(49); parser.Timeout(49);
}
Task received; EXPECT_EQ(2, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_EQ(received_events[0], Event::AltA);
EXPECT_EQ(std::get<Event>(received), Event::AltA); EXPECT_EQ(received_events[1], Event::AltB);
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_EQ(std::get<Event>(received), Event::AltB);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseLeftClickPressed) { TEST(Event, MouseLeftClickPressed) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Add('['); parser.Add('[');
parser.Add('0'); parser.Add('0');
parser.Add(';'); parser.Add(';');
@ -107,23 +94,20 @@ TEST(Event, MouseLeftClickPressed) {
parser.Add('4'); parser.Add('4');
parser.Add('2'); parser.Add('2');
parser.Add('M'); parser.Add('M');
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events[0].is_mouse());
EXPECT_TRUE(std::get<Event>(received).is_mouse()); EXPECT_EQ(Mouse::Left, received_events[0].mouse().button);
EXPECT_EQ(Mouse::Left, std::get<Event>(received).mouse().button); EXPECT_EQ(12, received_events[0].mouse().x);
EXPECT_EQ(12, std::get<Event>(received).mouse().x); EXPECT_EQ(42, received_events[0].mouse().y);
EXPECT_EQ(42, std::get<Event>(received).mouse().y); EXPECT_EQ(received_events[0].mouse().motion, Mouse::Pressed);
EXPECT_EQ(std::get<Event>(received).mouse().motion, Mouse::Pressed);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseLeftMoved) { TEST(Event, MouseLeftMoved) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Add('['); parser.Add('[');
parser.Add('3'); parser.Add('3');
parser.Add('2'); parser.Add('2');
@ -134,23 +118,20 @@ TEST(Event, MouseLeftMoved) {
parser.Add('4'); parser.Add('4');
parser.Add('2'); parser.Add('2');
parser.Add('M'); parser.Add('M');
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events[0].is_mouse());
EXPECT_TRUE(std::get<Event>(received).is_mouse()); EXPECT_EQ(Mouse::Left, received_events[0].mouse().button);
EXPECT_EQ(Mouse::Left, std::get<Event>(received).mouse().button); EXPECT_EQ(12, received_events[0].mouse().x);
EXPECT_EQ(12, std::get<Event>(received).mouse().x); EXPECT_EQ(42, received_events[0].mouse().y);
EXPECT_EQ(42, std::get<Event>(received).mouse().y); EXPECT_EQ(received_events[0].mouse().motion, Mouse::Moved);
EXPECT_EQ(std::get<Event>(received).mouse().motion, Mouse::Moved);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseLeftClickReleased) { TEST(Event, MouseLeftClickReleased) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Add('['); parser.Add('[');
parser.Add('0'); parser.Add('0');
parser.Add(';'); parser.Add(';');
@ -160,23 +141,20 @@ TEST(Event, MouseLeftClickReleased) {
parser.Add('4'); parser.Add('4');
parser.Add('2'); parser.Add('2');
parser.Add('m'); parser.Add('m');
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events[0].is_mouse());
EXPECT_TRUE(std::get<Event>(received).is_mouse()); EXPECT_EQ(Mouse::Left, received_events[0].mouse().button);
EXPECT_EQ(Mouse::Left, std::get<Event>(received).mouse().button); EXPECT_EQ(12, received_events[0].mouse().x);
EXPECT_EQ(12, std::get<Event>(received).mouse().x); EXPECT_EQ(42, received_events[0].mouse().y);
EXPECT_EQ(42, std::get<Event>(received).mouse().y); EXPECT_EQ(received_events[0].mouse().motion, Mouse::Released);
EXPECT_EQ(std::get<Event>(received).mouse().motion, Mouse::Released);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseReporting) { TEST(Event, MouseReporting) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Add('['); parser.Add('[');
parser.Add('1'); parser.Add('1');
parser.Add('2'); parser.Add('2');
@ -184,21 +162,18 @@ TEST(Event, MouseReporting) {
parser.Add('4'); parser.Add('4');
parser.Add('2'); parser.Add('2');
parser.Add('R'); parser.Add('R');
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events[0].is_cursor_position());
EXPECT_TRUE(std::get<Event>(received).is_cursor_position()); EXPECT_EQ(42, received_events[0].cursor_x());
EXPECT_EQ(42, std::get<Event>(received).cursor_x()); EXPECT_EQ(12, received_events[0].cursor_y());
EXPECT_EQ(12, std::get<Event>(received).cursor_y());
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseMiddleClick) { TEST(Event, MouseMiddleClick) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Add('['); parser.Add('[');
parser.Add('3'); parser.Add('3');
parser.Add('3'); parser.Add('3');
@ -209,22 +184,19 @@ TEST(Event, MouseMiddleClick) {
parser.Add('4'); parser.Add('4');
parser.Add('2'); parser.Add('2');
parser.Add('M'); parser.Add('M');
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events[0].is_mouse());
EXPECT_TRUE(std::get<Event>(received).is_mouse()); EXPECT_EQ(Mouse::Middle, received_events[0].mouse().button);
EXPECT_EQ(Mouse::Middle, std::get<Event>(received).mouse().button); EXPECT_EQ(12, received_events[0].mouse().x);
EXPECT_EQ(12, std::get<Event>(received).mouse().x); EXPECT_EQ(42, received_events[0].mouse().y);
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseRightClick) { TEST(Event, MouseRightClick) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Add('['); parser.Add('[');
parser.Add('3'); parser.Add('3');
parser.Add('4'); parser.Add('4');
@ -235,15 +207,12 @@ TEST(Event, MouseRightClick) {
parser.Add('4'); parser.Add('4');
parser.Add('2'); parser.Add('2');
parser.Add('M'); parser.Add('M');
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events[0].is_mouse());
EXPECT_TRUE(std::get<Event>(received).is_mouse()); EXPECT_EQ(Mouse::Right, received_events[0].mouse().button);
EXPECT_EQ(Mouse::Right, std::get<Event>(received).mouse().button); EXPECT_EQ(12, received_events[0].mouse().x);
EXPECT_EQ(12, std::get<Event>(received).mouse().x); EXPECT_EQ(42, received_events[0].mouse().y);
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, UTF8) { TEST(Event, UTF8) {
@ -313,31 +282,29 @@ TEST(Event, UTF8) {
}; };
for (auto test : kTestCase) { for (auto test : kTestCase) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
for (auto input : test.input) for (auto input : test.input)
parser.Add(input); parser.Add(input);
}
Task received;
if (test.valid) { if (test.valid) {
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(std::get<Event>(received).is_character()); EXPECT_TRUE(received_events[0].is_character());
} else {
EXPECT_TRUE(received_events.empty());
} }
EXPECT_FALSE(event_receiver->Receive(&received));
} }
} }
TEST(Event, NewLine) { TEST(Event, NewLine) {
for (char newline : {'\r', '\n'}) { for (char newline : {'\r', '\n'}) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add(newline); parser.Add(newline);
} EXPECT_EQ(1, received_events.size());
Task received; EXPECT_TRUE(received_events[0] == Event::Return);
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(std::get<Event>(received) == Event::Return);
} }
} }
@ -357,17 +324,16 @@ TEST(Event, Control) {
cases.push_back({char(127), false}); cases.push_back({char(127), false});
for (auto test : cases) { for (auto test : cases) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add(test.input); parser.Add(test.input);
}
Task received;
if (test.cancel) { if (test.cancel) {
EXPECT_FALSE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events.empty());
} else { } else {
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_EQ(1, received_events.size());
EXPECT_EQ(std::get<Event>(received), Event::Special({test.input})); EXPECT_EQ(received_events[0], Event::Special({test.input}));
} }
} }
} }
@ -385,10 +351,9 @@ TEST(Event, Special) {
Event expected; Event expected;
} kTestCase[] = { } kTestCase[] = {
// Arrow (default cursor mode) // Arrow (default cursor mode)
{str("\x1B[A"), Event::ArrowUp}, {str("\x1B[B"), Event::ArrowDown}, {str(""), Event::ArrowUp}, {str(""), Event::ArrowDown},
{str("\x1B[C"), Event::ArrowRight}, {str("\x1B[D"), Event::ArrowLeft}, {str(""), Event::ArrowRight}, {str(""), Event::ArrowLeft},
{str("\x1B[H"), Event::Home}, {str("\x1B[F"), Event::End}, {str(""), Event::Home}, {str(""), Event::End},
/*
// Arrow (application cursor mode) // Arrow (application cursor mode)
{str("\x1BOA"), Event::ArrowUp}, {str("\x1BOA"), Event::ArrowUp},
@ -469,28 +434,24 @@ TEST(Event, Special) {
// Custom: // Custom:
{{0}, Event::Custom}, {{0}, Event::Custom},
*/
}; };
for (auto test : kTestCase) { for (auto test : kTestCase) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
for (auto input : test.input) { for (auto input : test.input) {
parser.Add(input); parser.Add(input);
} }
} EXPECT_EQ(1, received_events.size());
Task received; EXPECT_EQ(received_events[0], test.expected);
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_EQ(std::get<Event>(received), test.expected);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
} }
TEST(Event, DeviceControlString) { TEST(Event, DeviceControlString) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add(27); // ESC parser.Add(27); // ESC
parser.Add(80); // P parser.Add(80); // P
parser.Add(49); // 1 parser.Add(49); // 1
@ -501,13 +462,10 @@ TEST(Event, DeviceControlString) {
parser.Add(113); // q parser.Add(113); // q
parser.Add(27); // ESC parser.Add(27); // ESC
parser.Add(92); // (backslash) parser.Add(92); // (backslash)
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events[0].is_cursor_shape());
EXPECT_TRUE(std::get<Event>(received).is_cursor_shape()); EXPECT_EQ(1, received_events[0].cursor_shape());
EXPECT_EQ(1, std::get<Event>(received).cursor_shape());
EXPECT_FALSE(event_receiver->Receive(&received));
} }
} // namespace ftxui } // namespace ftxui

View File

@ -1,21 +1,16 @@
// Copyright 2021 Arthur Sonzogni. All rights reserved. // Copyright 2021 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file. // the LICENSE file.
#include <cstddef>
#include <ftxui/component/event.hpp>
#include "ftxui/component/terminal_input_parser.hpp" #include "ftxui/component/terminal_input_parser.hpp"
extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) { extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) {
using namespace ftxui; using namespace ftxui;
auto event_receiver = MakeReceiver<Task>(); auto parser = TerminalInputParser([&](Event) {});
{
auto parser = TerminalInputParser(event_receiver->MakeSender());
for (size_t i = 0; i < size; ++i) { for (size_t i = 0; i < size; ++i) {
parser.Add(data[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. return 0; // Non-zero return values are reserved for future use.
} }