Support SIGTSTP and task posting. (#331)

- Add support for SIGTSTP:
https://github.com/ArthurSonzogni/FTXUI/issues/330
This

- Add support for task posting.
This allows folks to defer function execution, and execute it directly
below the main loop. The task are executed in a FIFO order.
This commit is contained in:
Arthur Sonzogni 2022-02-13 11:11:34 +01:00 committed by GitHub
parent 62747a49b6
commit 9c4218c2a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 165 additions and 94 deletions

View File

@ -18,6 +18,10 @@ Element gaugeDown(float ratio);
Element gaugeDirection(float ratio, GaugeDirection); Element gaugeDirection(float ratio, GaugeDirection);
``` ```
#### Component
- Support SIGTSTP. (ctrl+z).
- Support task posting. `ScreenInteractive::Post(Task)`.
2.0.0 2.0.0
----- -----

View File

@ -90,6 +90,7 @@ add_library(component
include/ftxui/component/mouse.hpp include/ftxui/component/mouse.hpp
include/ftxui/component/receiver.hpp include/ftxui/component/receiver.hpp
include/ftxui/component/screen_interactive.hpp include/ftxui/component/screen_interactive.hpp
include/ftxui/component/task.hpp
src/ftxui/component/button.cpp src/ftxui/component/button.cpp
src/ftxui/component/catch_event.cpp src/ftxui/component/catch_event.cpp
src/ftxui/component/checkbox.cpp src/ftxui/component/checkbox.cpp

View File

@ -32,8 +32,8 @@ Element make_grid() {
}; };
int main(int argc, const char* argv[]) { int main(int argc, const char* argv[]) {
float focus_x = 0.0f; float focus_x = 0.5f;
float focus_y = 0.0f; float focus_y = 0.5f;
auto slider_x = Slider("x", &focus_x, 0.f, 1.f, 0.01f); auto slider_x = Slider("x", &focus_x, 0.f, 1.f, 0.01f);
auto slider_y = Slider("y", &focus_y, 0.f, 1.f, 0.01f); auto slider_y = Slider("y", &focus_y, 0.f, 1.f, 0.01f);

View File

@ -18,14 +18,12 @@ int main() {
// temporarily uninstall the terminal hook and execute the provided callback // temporarily uninstall the terminal hook and execute the provided callback
// function. This allow running the application in a non-interactive mode. // function. This allow running the application in a non-interactive mode.
auto btn_run = Button("Execute with restored IO", screen.WithRestoredIO([] { auto btn_run = Button("Execute with restored IO", screen.WithRestoredIO([] {
std::system("bash");
std::cout << "This is a child program using stdin/stdout." << std::endl; std::cout << "This is a child program using stdin/stdout." << std::endl;
for (int i = 0; i < 10; ++i) { for (int i = 0; i < 10; ++i) {
std::cout << "Please enter 10 strings (" << i << "/10)" << std::flush; std::cout << "Please enter 10 strings (" << i << "/10)" << std::flush;
std::string input; std::string input;
std::getline(std::cin, input); std::getline(std::cin, input);
} }
std::system("bash");
})); }));
auto btn_quit = Button("Quit", screen.ExitLoopClosure()); auto btn_quit = Button("Quit", screen.ExitLoopClosure());

View File

@ -34,7 +34,7 @@ std::vector<std::vector<ColorInfo>> ColorInfoSorted2D() {
for (int i = 0; i < int(column.size()) - 1; ++i) { for (int i = 0; i < int(column.size()) - 1; ++i) {
int best_index = i + 1; int best_index = i + 1;
int best_distance = 255 * 255 * 3; int best_distance = 255 * 255 * 3;
for (size_t j = i + 1; j < column.size(); ++j) { for (int j = i + 1; j < column.size(); ++j) {
int dx = column[i].red - column[j].red; int dx = column[i].red - column[j].red;
int dy = column[i].green - column[j].green; int dy = column[i].green - column[j].green;
int dz = column[i].blue - column[j].blue; int dz = column[i].blue - column[j].blue;

View File

@ -2,6 +2,7 @@
#define FTXUI_COMPONENT_EVENT_HPP #define FTXUI_COMPONENT_EVENT_HPP
#include <ftxui/component/mouse.hpp> // for Mouse #include <ftxui/component/mouse.hpp> // for Mouse
#include <functional>
#include <string> // for string, operator== #include <string> // for string, operator==
#include <vector> #include <vector>

View File

@ -7,9 +7,11 @@
#include <memory> // for shared_ptr #include <memory> // for shared_ptr
#include <string> // for string #include <string> // for string
#include <thread> // for thread #include <thread> // for thread
#include <variant> // for variant
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse #include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
#include "ftxui/component/event.hpp" // for Event #include "ftxui/component/event.hpp" // for Event
#include "ftxui/component/task.hpp" // for Closure, Task
#include "ftxui/screen/screen.hpp" // for Screen #include "ftxui/screen/screen.hpp" // for Screen
namespace ftxui { namespace ftxui {
@ -17,37 +19,38 @@ class ComponentBase;
struct Event; struct Event;
using Component = std::shared_ptr<ComponentBase>; using Component = std::shared_ptr<ComponentBase>;
class ScreenInteractivePrivate;
class ScreenInteractive : public Screen { class ScreenInteractive : public Screen {
public: public:
using Callback = std::function<void()>;
static ScreenInteractive FixedSize(int dimx, int dimy); static ScreenInteractive FixedSize(int dimx, int dimy);
static ScreenInteractive Fullscreen(); static ScreenInteractive Fullscreen();
static ScreenInteractive FitComponent(); static ScreenInteractive FitComponent();
static ScreenInteractive TerminalOutput(); static ScreenInteractive TerminalOutput();
void Loop(Component); void Loop(Component);
Callback ExitLoopClosure(); Closure ExitLoopClosure();
void Post(Task task);
void PostEvent(Event event); void PostEvent(Event event);
CapturedMouse CaptureMouse(); CapturedMouse CaptureMouse();
// Decorate a function. The outputted one will execute similarly to the // Decorate a function. The outputted one will execute similarly to the
// inputted one, but with the currently active screen terminal hooks // inputted one, but with the currently active screen terminal hooks
// temporarily uninstalled. // temporarily uninstalled.
Callback WithRestoredIO(Callback); Closure WithRestoredIO(Closure);
private: private:
void Install(); void Install();
void Uninstall(); void Uninstall();
void Main(Component component); void Main(Component component);
ScreenInteractive* suspended_screen_ = nullptr;
void Draw(Component component); void Draw(Component component);
void EventLoop(Component component); void SigStop();
ScreenInteractive* suspended_screen_ = nullptr;
enum class Dimension { enum class Dimension {
FitComponent, FitComponent,
Fixed, Fixed,
@ -61,8 +64,8 @@ class ScreenInteractive : public Screen {
Dimension dimension, Dimension dimension,
bool use_alternative_screen); bool use_alternative_screen);
Sender<Event> event_sender_; Sender<Task> task_sender_;
Receiver<Event> event_receiver_; Receiver<Task> task_receiver_;
std::string set_cursor_position; std::string set_cursor_position;
std::string reset_cursor_position; std::string reset_cursor_position;
@ -75,6 +78,13 @@ class ScreenInteractive : public Screen {
bool mouse_captured = false; bool mouse_captured = false;
bool previous_frame_resized_ = false; bool previous_frame_resized_ = false;
public:
class Private {
public:
static void SigStop(ScreenInteractive& s) { return s.SigStop(); }
};
friend Private;
}; };
} // namespace ftxui } // namespace ftxui

View File

@ -0,0 +1,12 @@
#include <functional>
#include <variant>
#include "ftxui/component/event.hpp"
namespace ftxui {
using Closure = std::function<void()>;
using Task = std::variant<Event, Closure>;
} // namespace ftxui
// Copyright 2022 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.

View File

@ -151,16 +151,16 @@ 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<Event>(); auto event_receiver = MakeReceiver<Task>();
{ {
auto parser = TerminalInputParser(event_receiver->MakeSender()); 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]);
} }
Event event; Task event;
while (event_receiver->Receive(&event)) { 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

@ -1,18 +1,20 @@
#include <stdio.h> // for fileno, stdin #include <stdio.h> // for fileno, stdin
#include <algorithm> // for copy, max, min #include <algorithm> // for copy, max, min
#include <csignal> // for signal, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, SIGWINCH #include <csignal> // for signal, raise, SIGTSTP, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, SIGWINCH
#include <cstdlib> // for NULL #include <cstdlib> // for NULL
#include <functional> // for function
#include <initializer_list> // for initializer_list #include <initializer_list> // for initializer_list
#include <iostream> // for cout, ostream, basic_ostream, operator<<, endl, flush #include <iostream> // for cout, ostream, basic_ostream, operator<<, endl, flush
#include <stack> // for stack #include <stack> // for stack
#include <thread> // for thread #include <thread> // for thread
#include <type_traits> // for decay_t
#include <utility> // for swap, move #include <utility> // for swap, move
#include <variant> // for visit
#include <vector> // for vector #include <vector> // for vector
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface #include "ftxui/component/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface
#include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/event.hpp" // for Event #include "ftxui/component/event.hpp" // for Event
#include "ftxui/component/mouse.hpp" // for Mouse
#include "ftxui/component/receiver.hpp" // for ReceiverImpl, MakeReceiver, Sender, SenderImpl, Receiver #include "ftxui/component/receiver.hpp" // for ReceiverImpl, MakeReceiver, Sender, SenderImpl, Receiver
#include "ftxui/component/screen_interactive.hpp" #include "ftxui/component/screen_interactive.hpp"
#include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser #include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser
@ -32,7 +34,7 @@
#endif #endif
#else #else
#include <sys/select.h> // for select, FD_ISSET, FD_SET, FD_ZERO, fd_set #include <sys/select.h> // for select, FD_ISSET, FD_SET, FD_ZERO, fd_set
#include <termios.h> // for tcsetattr, tcgetattr, cc_t #include <termios.h> // for tcsetattr, termios, tcgetattr, TCSANOW, cc_t, ECHO, ICANON, VMIN, VTIME
#include <unistd.h> // for STDIN_FILENO, read #include <unistd.h> // for STDIN_FILENO, read
#endif #endif
@ -45,6 +47,8 @@ namespace ftxui {
namespace { namespace {
ScreenInteractive* g_active_screen = nullptr;
void Flush() { void Flush() {
// Emscripten doesn't implement flush. We interpret zero as flush. // Emscripten doesn't implement flush. We interpret zero as flush.
std::cout << '\0' << std::flush; std::cout << '\0' << std::flush;
@ -54,7 +58,7 @@ constexpr int timeout_milliseconds = 20;
constexpr int timeout_microseconds = timeout_milliseconds * 1000; constexpr int timeout_microseconds = timeout_milliseconds * 1000;
#if defined(_WIN32) #if defined(_WIN32)
void EventListener(std::atomic<bool>* quit, Sender<Event> 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(out->Clone());
while (!*quit) { while (!*quit) {
@ -104,7 +108,7 @@ void EventListener(std::atomic<bool>* quit, Sender<Event> out) {
#include <emscripten.h> #include <emscripten.h>
// Read char from the terminal. // Read char from the terminal.
void EventListener(std::atomic<bool>* quit, Sender<Event> out) { void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
(void)timeout_microseconds; (void)timeout_microseconds;
auto parser = TerminalInputParser(std::move(out)); auto parser = TerminalInputParser(std::move(out));
@ -131,7 +135,7 @@ int CheckStdinReady(int usec_timeout) {
} }
// Read char from the terminal. // Read char from the terminal.
void EventListener(std::atomic<bool>* quit, Sender<Event> out) { void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
const int buffer_size = 100; const int buffer_size = 100;
auto parser = TerminalInputParser(std::move(out)); auto parser = TerminalInputParser(std::move(out));
@ -200,7 +204,7 @@ const std::string DeviceStatusReport(DSRMode ps) {
} }
using SignalHandler = void(int); using SignalHandler = void(int);
std::stack<ScreenInteractive::Callback> on_exit_functions; std::stack<Closure> on_exit_functions;
void OnExit(int signal) { void OnExit(int signal) {
(void)signal; (void)signal;
while (!on_exit_functions.empty()) { while (!on_exit_functions.empty()) {
@ -211,14 +215,18 @@ void OnExit(int signal) {
auto install_signal_handler = [](int sig, SignalHandler handler) { auto install_signal_handler = [](int sig, SignalHandler handler) {
auto old_signal_handler = std::signal(sig, handler); auto old_signal_handler = std::signal(sig, handler);
on_exit_functions.push([&] { std::signal(sig, old_signal_handler); }); on_exit_functions.push([=] { std::signal(sig, old_signal_handler); });
}; };
ScreenInteractive::Callback on_resize = [] {}; Closure on_resize = [] {};
void OnResize(int /* signal */) { void OnResize(int /* signal */) {
on_resize(); on_resize();
} }
void OnSigStop(int /*signal*/) {
ScreenInteractive::Private::SigStop(*g_active_screen);
}
class CapturedMouseImpl : public CapturedMouseInterface { class CapturedMouseImpl : public CapturedMouseInterface {
public: public:
CapturedMouseImpl(std::function<void(void)> callback) : callback_(callback) {} CapturedMouseImpl(std::function<void(void)> callback) : callback_(callback) {}
@ -230,8 +238,6 @@ class CapturedMouseImpl : public CapturedMouseInterface {
} // namespace } // namespace
ScreenInteractive* g_active_screen = nullptr;
ScreenInteractive::ScreenInteractive(int dimx, ScreenInteractive::ScreenInteractive(int dimx,
int dimy, int dimy,
Dimension dimension, Dimension dimension,
@ -239,8 +245,7 @@ ScreenInteractive::ScreenInteractive(int dimx,
: Screen(dimx, dimy), : Screen(dimx, dimy),
dimension_(dimension), dimension_(dimension),
use_alternative_screen_(use_alternative_screen) { use_alternative_screen_(use_alternative_screen) {
event_receiver_ = MakeReceiver<Event>(); task_receiver_ = MakeReceiver<Task>();
event_sender_ = event_receiver_->MakeSender();
} }
// static // static
@ -263,9 +268,12 @@ ScreenInteractive ScreenInteractive::FitComponent() {
return ScreenInteractive(0, 0, Dimension::FitComponent, false); return ScreenInteractive(0, 0, Dimension::FitComponent, false);
} }
void ScreenInteractive::PostEvent(Event event) { void ScreenInteractive::Post(Task task) {
if (!quit_) if (!quit_)
event_sender_->Send(event); task_sender_->Send(task);
}
void ScreenInteractive::PostEvent(Event event) {
Post(event);
} }
CapturedMouse ScreenInteractive::CaptureMouse() { CapturedMouse ScreenInteractive::CaptureMouse() {
@ -314,7 +322,7 @@ void ScreenInteractive::Loop(Component component) {
/// @brief Decorate a function. It executes the same way, but with the currently /// @brief Decorate a function. It executes the same way, but with the currently
/// active screen terminal hooks temporarilly uninstalled during its execution. /// active screen terminal hooks temporarilly uninstalled during its execution.
/// @param fn The function to decorate. /// @param fn The function to decorate.
ScreenInteractive::Callback ScreenInteractive::WithRestoredIO(Callback fn) { Closure ScreenInteractive::WithRestoredIO(Closure fn) {
return [this, fn] { return [this, fn] {
Uninstall(); Uninstall();
fn(); fn();
@ -381,8 +389,11 @@ void ScreenInteractive::Install() {
tcsetattr(STDIN_FILENO, TCSANOW, &terminal); tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
// Handle resize. // Handle resize.
on_resize = [&] { event_sender_->Send(Event::Special({0})); }; on_resize = [&] { task_sender_->Send(Event::Special({0})); };
install_signal_handler(SIGWINCH, OnResize); install_signal_handler(SIGWINCH, OnResize);
// Handle SIGTSTP/SIGCONT.
install_signal_handler(SIGTSTP, OnSigStop);
#endif #endif
auto enable = [&](std::vector<DECMode> parameters) { auto enable = [&](std::vector<DECMode> parameters) {
@ -418,8 +429,9 @@ void ScreenInteractive::Install() {
Flush(); Flush();
quit_ = false; quit_ = false;
task_sender_ = task_receiver_->MakeSender();
event_listener_ = event_listener_ =
std::thread(&EventListener, &quit_, event_receiver_->MakeSender()); std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
} }
void ScreenInteractive::Uninstall() { void ScreenInteractive::Uninstall() {
@ -431,30 +443,43 @@ void ScreenInteractive::Uninstall() {
void ScreenInteractive::Main(Component component) { void ScreenInteractive::Main(Component component) {
while (!quit_) { while (!quit_) {
if (!event_receiver_->HasPending()) { if (!task_receiver_->HasPending()) {
Draw(component); Draw(component);
std::cout << ToString() << set_cursor_position; std::cout << ToString() << set_cursor_position;
Flush(); Flush();
Clear(); Clear();
} }
Event event; Task task;
if (!event_receiver_->Receive(&event)) if (!task_receiver_->Receive(&task))
break; break;
if (event.is_cursor_reporting()) { std::visit(
cursor_x_ = event.cursor_x(); [&](auto&& arg) {
cursor_y_ = event.cursor_y(); using T = std::decay_t<decltype(arg)>;
continue;
// Handle Event.
if constexpr (std::is_same_v<T, Event>) {
if (arg.is_cursor_reporting()) {
cursor_x_ = arg.cursor_x();
cursor_y_ = arg.cursor_y();
return;
} }
if (event.is_mouse()) { if (arg.is_mouse()) {
event.mouse().x -= cursor_x_; arg.mouse().x -= cursor_x_;
event.mouse().y -= cursor_y_; arg.mouse().y -= cursor_y_;
} }
event.screen_ = this; arg.screen_ = this;
component->OnEvent(event); component->OnEvent(arg);
}
// Handle callback
if constexpr (std::is_same_v<T, Closure>)
arg();
},
task);
} }
} }
@ -537,13 +562,31 @@ void ScreenInteractive::Draw(Component component) {
} }
} }
ScreenInteractive::Callback ScreenInteractive::ExitLoopClosure() { Closure ScreenInteractive::ExitLoopClosure() {
return [this] { return [this] {
quit_ = true; quit_ = true;
event_sender_.reset(); task_sender_.reset();
}; };
} }
void ScreenInteractive::SigStop() {
#if defined(_WIN32)
// Windows do no support SIGTSTP.
#else
Post([&] {
Uninstall();
std::cout << reset_cursor_position;
reset_cursor_position = "";
std::cout << ResetPosition(/*clear=*/true);
dimx_ = 0;
dimy_ = 0;
Flush();
std::raise(SIGTSTP);
Install();
});
#endif
}
} // namespace ftxui. } // namespace ftxui.
// Copyright 2020 Arthur Sonzogni. All rights reserved. // Copyright 2020 Arthur Sonzogni. All rights reserved.

View File

@ -1,15 +1,15 @@
#include "ftxui/component/terminal_input_parser.hpp" #include "ftxui/component/terminal_input_parser.hpp"
#include <algorithm> // for max #include <cstdint> // for uint32_t
#include <cstdint>
#include <memory> // for unique_ptr #include <memory> // for unique_ptr
#include <utility> // for move #include <utility> // for move
#include "ftxui/component/event.hpp" // for Event #include "ftxui/component/event.hpp" // for Event
#include "ftxui/component/task.hpp" // for Task
namespace ftxui { namespace ftxui {
TerminalInputParser::TerminalInputParser(Sender<Event> out) TerminalInputParser::TerminalInputParser(Sender<Task> out)
: out_(std::move(out)) {} : out_(std::move(out)) {}
void TerminalInputParser::Timeout(int time) { void TerminalInputParser::Timeout(int time) {

View File

@ -8,6 +8,7 @@
#include "ftxui/component/event.hpp" // for Event (ptr only) #include "ftxui/component/event.hpp" // for Event (ptr only)
#include "ftxui/component/mouse.hpp" // for Mouse #include "ftxui/component/mouse.hpp" // for Mouse
#include "ftxui/component/receiver.hpp" // for Sender #include "ftxui/component/receiver.hpp" // for Sender
#include "ftxui/component/task.hpp" // for Task
namespace ftxui { namespace ftxui {
struct Event; struct Event;
@ -15,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:
TerminalInputParser(Sender<Event> out); TerminalInputParser(Sender<Task> out);
void Timeout(int time); void Timeout(int time);
void Add(char c); void Add(char c);
@ -57,7 +58,7 @@ class TerminalInputParser {
Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments); Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments);
Output ParseCursorReporting(std::vector<int> arguments); Output ParseCursorReporting(std::vector<int> arguments);
Sender<Event> out_; Sender<Task> out_;
int position_ = -1; int position_ = -1;
int timeout_ = 0; int timeout_ = 0;
std::string pending_; std::string pending_;

View File

@ -2,6 +2,7 @@
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl #include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
#include <algorithm> // for max #include <algorithm> // for max
#include <memory> // for unique_ptr, allocator #include <memory> // for unique_ptr, allocator
#include <variant> // for get
#include "ftxui/component/event.hpp" // for Event, Event::Escape #include "ftxui/component/event.hpp" // for Event, Event::Escape
#include "ftxui/component/receiver.hpp" // for MakeReceiver, ReceiverImpl #include "ftxui/component/receiver.hpp" // for MakeReceiver, ReceiverImpl
@ -18,61 +19,61 @@ 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<Event>(); auto event_receiver = MakeReceiver<Task>();
{ {
auto parser = TerminalInputParser(event_receiver->MakeSender()); auto parser = TerminalInputParser(event_receiver->MakeSender());
for (char c : basic_char) for (char c : basic_char)
parser.Add(c); parser.Add(c);
} }
Event received; Task received;
for (char c : basic_char) { for (char c : basic_char) {
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(received.is_character()); EXPECT_TRUE(std::get<Event>(received).is_character());
EXPECT_EQ(c, received.character()[0]); EXPECT_EQ(c, std::get<Event>(received).character()[0]);
} }
EXPECT_FALSE(event_receiver->Receive(&received)); EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, EscapeKeyWithoutWaiting) { TEST(Event, EscapeKeyWithoutWaiting) {
auto event_receiver = MakeReceiver<Event>(); auto event_receiver = MakeReceiver<Task>();
{ {
auto parser = TerminalInputParser(event_receiver->MakeSender()); auto parser = TerminalInputParser(event_receiver->MakeSender());
parser.Add('\x1B'); parser.Add('\x1B');
} }
Event received; Task received;
EXPECT_FALSE(event_receiver->Receive(&received)); EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, EscapeKeyNotEnoughWait) { TEST(Event, EscapeKeyNotEnoughWait) {
auto event_receiver = MakeReceiver<Event>(); auto event_receiver = MakeReceiver<Task>();
{ {
auto parser = TerminalInputParser(event_receiver->MakeSender()); auto parser = TerminalInputParser(event_receiver->MakeSender());
parser.Add('\x1B'); parser.Add('\x1B');
parser.Timeout(49); parser.Timeout(49);
} }
Event received; Task received;
EXPECT_FALSE(event_receiver->Receive(&received)); EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, EscapeKeyEnoughWait) { TEST(Event, EscapeKeyEnoughWait) {
auto event_receiver = MakeReceiver<Event>(); auto event_receiver = MakeReceiver<Task>();
{ {
auto parser = TerminalInputParser(event_receiver->MakeSender()); auto parser = TerminalInputParser(event_receiver->MakeSender());
parser.Add('\x1B'); parser.Add('\x1B');
parser.Timeout(50); parser.Timeout(50);
} }
Event received; Task received;
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_EQ(received, Event::Escape); EXPECT_EQ(std::get<Event>(received), Event::Escape);
EXPECT_FALSE(event_receiver->Receive(&received)); EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseLeftClick) { TEST(Event, MouseLeftClick) {
auto event_receiver = MakeReceiver<Event>(); auto event_receiver = MakeReceiver<Task>();
{ {
auto parser = TerminalInputParser(event_receiver->MakeSender()); auto parser = TerminalInputParser(event_receiver->MakeSender());
parser.Add('\x1B'); parser.Add('\x1B');
@ -88,17 +89,17 @@ TEST(Event, MouseLeftClick) {
parser.Add('M'); parser.Add('M');
} }
Event received; Task received;
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(received.is_mouse()); EXPECT_TRUE(std::get<Event>(received).is_mouse());
EXPECT_EQ(Mouse::Left, received.mouse().button); EXPECT_EQ(Mouse::Left, std::get<Event>(received).mouse().button);
EXPECT_EQ(12, received.mouse().x); EXPECT_EQ(12, std::get<Event>(received).mouse().x);
EXPECT_EQ(42, received.mouse().y); EXPECT_EQ(42, std::get<Event>(received).mouse().y);
EXPECT_FALSE(event_receiver->Receive(&received)); EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseMiddleClick) { TEST(Event, MouseMiddleClick) {
auto event_receiver = MakeReceiver<Event>(); auto event_receiver = MakeReceiver<Task>();
{ {
auto parser = TerminalInputParser(event_receiver->MakeSender()); auto parser = TerminalInputParser(event_receiver->MakeSender());
parser.Add('\x1B'); parser.Add('\x1B');
@ -114,17 +115,17 @@ TEST(Event, MouseMiddleClick) {
parser.Add('M'); parser.Add('M');
} }
Event received; Task received;
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(received.is_mouse()); EXPECT_TRUE(std::get<Event>(received).is_mouse());
EXPECT_EQ(Mouse::Middle, received.mouse().button); EXPECT_EQ(Mouse::Middle, std::get<Event>(received).mouse().button);
EXPECT_EQ(12, received.mouse().x); EXPECT_EQ(12, std::get<Event>(received).mouse().x);
EXPECT_EQ(42, received.mouse().y); EXPECT_EQ(42, std::get<Event>(received).mouse().y);
EXPECT_FALSE(event_receiver->Receive(&received)); EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseRightClick) { TEST(Event, MouseRightClick) {
auto event_receiver = MakeReceiver<Event>(); auto event_receiver = MakeReceiver<Task>();
{ {
auto parser = TerminalInputParser(event_receiver->MakeSender()); auto parser = TerminalInputParser(event_receiver->MakeSender());
parser.Add('\x1B'); parser.Add('\x1B');
@ -140,12 +141,12 @@ TEST(Event, MouseRightClick) {
parser.Add('M'); parser.Add('M');
} }
Event received; Task received;
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(received.is_mouse()); EXPECT_TRUE(std::get<Event>(received).is_mouse());
EXPECT_EQ(Mouse::Right, received.mouse().button); EXPECT_EQ(Mouse::Right, std::get<Event>(received).mouse().button);
EXPECT_EQ(12, received.mouse().x); EXPECT_EQ(12, std::get<Event>(received).mouse().x);
EXPECT_EQ(42, received.mouse().y); EXPECT_EQ(42, std::get<Event>(received).mouse().y);
EXPECT_FALSE(event_receiver->Receive(&received)); EXPECT_FALSE(event_receiver->Receive(&received));
} }
@ -216,16 +217,16 @@ TEST(Event, UTF8) {
}; };
for (auto test : kTestCase) { for (auto test : kTestCase) {
auto event_receiver = MakeReceiver<Event>(); auto event_receiver = MakeReceiver<Task>();
{ {
auto parser = TerminalInputParser(event_receiver->MakeSender()); auto parser = TerminalInputParser(event_receiver->MakeSender());
for (auto input : test.input) for (auto input : test.input)
parser.Add(input); parser.Add(input);
} }
Event received; Task received;
if (test.valid) { if (test.valid) {
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(received.is_character()); EXPECT_TRUE(std::get<Event>(received).is_character());
} }
EXPECT_FALSE(event_receiver->Receive(&received)); EXPECT_FALSE(event_receiver->Receive(&received));
} }

View File

@ -5,14 +5,14 @@
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<Event>(); auto event_receiver = MakeReceiver<Task>();
{ {
auto parser = TerminalInputParser(event_receiver->MakeSender()); 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]);
} }
Event received; Task received;
while (event_receiver->Receive(&received)) while (event_receiver->Receive(&received))
; ;
return 0; // Non-zero return values are reserved for future use. return 0; // Non-zero return values are reserved for future use.