1 Commits

Author SHA1 Message Date
Alex
3d5146a5fb Merge baa5973128 into 68fc9b1212 2025-06-20 22:15:46 +03:00
11 changed files with 305 additions and 313 deletions

View File

@@ -1,64 +1,9 @@
// 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 <memory> // for shared_ptr, __shared_ptr_access
#include <string> // for operator+, to_string
#include "ftxui/component/component.hpp"
#include "ftxui/component/screen_interactive.hpp"
#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;
int main(){
auto screen = ftxui::ScreenInteractive::Fullscreen();
auto testComponent = ftxui::Renderer([](){return ftxui::text("test Component");});
screen.Loop(testComponent);
return 0;
}

View File

@@ -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.
///

View File

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

View File

@@ -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;

View File

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

View File

@@ -84,8 +84,7 @@ constexpr int timeout_milliseconds = 20;
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
auto console = GetStdHandle(STD_INPUT_HANDLE);
auto parser =
TerminalInputParser([&](Event event) { out->Send(std::move(event)); });
auto parser = TerminalInputParser(out->Clone());
while (!*quit) {
// Throttle ReadConsoleInput by waiting 250ms, this wait function will
// return if there is input in the console.
@@ -138,8 +137,7 @@ void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
// Read char from the terminal.
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
auto parser =
TerminalInputParser([&](Event event) { out->Send(std::move(event)); });
auto parser = TerminalInputParser(std::move(out));
char c;
while (!*quit) {
@@ -175,8 +173,7 @@ int CheckStdinReady(int usec_timeout) {
// Read char from the terminal.
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
auto parser =
TerminalInputParser([&](Event event) { out->Send(std::move(event)); });
auto parser = TerminalInputParser(std::move(out));
while (!*quit) {
if (!CheckStdinReady(timeout_microseconds)) {
@@ -384,10 +381,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,
};
}
@@ -412,7 +409,7 @@ ScreenInteractive ScreenInteractive::TerminalOutput() {
return {
Dimension::TerminalOutput,
terminal.dimx,
terminal.dimy, // Best guess.
terminal.dimy, // Best guess.
/*use_alternative_screen=*/false,
};
}
@@ -424,8 +421,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,
};
}

View File

@@ -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,7 +188,9 @@ 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();
@@ -239,6 +241,7 @@ TEST(ScreenInteractive, FixedSizeInitialFrame) {
"\r\n"sv;
ASSERT_EQ(expected, output);
#endif
}
} // namespace ftxui

View File

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

View File

@@ -4,11 +4,12 @@
#ifndef FTXUI_COMPONENT_TERMINAL_INPUT_PARSER
#define FTXUI_COMPONENT_TERMINAL_INPUT_PARSER
#include <functional>
#include <string> // for string
#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 {
struct Event;
@@ -16,7 +17,7 @@ struct Event;
// Parse a sequence of |char| accross |time|. Produces |Event|.
class TerminalInputParser {
public:
explicit TerminalInputParser(std::function<void(Event)> out);
explicit TerminalInputParser(Sender<Task> out);
void Timeout(int time);
void Add(char c);
@@ -61,7 +62,7 @@ class TerminalInputParser {
Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments);
Output ParseCursorPosition(std::vector<int> arguments);
std::function<void(Event)> out_;
Sender<Task> out_;
int position_ = -1;
int timeout_ = 0;
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
// the LICENSE file.
#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Left, Mouse::Middle, Mouse::Pressed, Mouse::Released, Mouse::Right
#include <functional> // for function
#include <ftxui/component/task.hpp> // for Task
#include <initializer_list> // for initializer_list
#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/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,197 +22,228 @@ TEST(Event, Character) {
for (char c = 'A'; c <= 'Z'; ++c)
basic_char.push_back(c);
std::vector<Event> received_events;
auto parser = TerminalInputParser(
[&](Event event) { received_events.push_back(std::move(event)); });
for (char c : basic_char)
parser.Add(c);
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]);
auto event_receiver = MakeReceiver<Task>();
{
auto parser = TerminalInputParser(event_receiver->MakeSender());
for (char c : basic_char)
parser.Add(c);
}
EXPECT_EQ(received_events.size(), basic_char.size());
Task received;
for (char c : basic_char) {
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(std::get<Event>(received).is_character());
EXPECT_EQ(c, std::get<Event>(received).character()[0]);
}
EXPECT_FALSE(event_receiver->Receive(&received));
}
TEST(Event, EscapeKeyWithoutWaiting) {
std::vector<Event> received_events;
auto parser = TerminalInputParser(
[&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('');
auto event_receiver = MakeReceiver<Task>();
{
auto parser = TerminalInputParser(event_receiver->MakeSender());
parser.Add('\x1B');
}
EXPECT_TRUE(received_events.empty());
Task received;
EXPECT_FALSE(event_receiver->Receive(&received));
}
TEST(Event, EscapeKeyNotEnoughWait) {
std::vector<Event> received_events;
auto parser = TerminalInputParser(
[&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('');
parser.Timeout(49);
auto event_receiver = MakeReceiver<Task>();
{
auto parser = TerminalInputParser(event_receiver->MakeSender());
parser.Add('\x1B');
parser.Timeout(49);
}
EXPECT_TRUE(received_events.empty());
Task received;
EXPECT_FALSE(event_receiver->Receive(&received));
}
TEST(Event, EscapeKeyEnoughWait) {
std::vector<Event> received_events;
auto parser = TerminalInputParser(
[&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('');
parser.Timeout(50);
auto event_receiver = MakeReceiver<Task>();
{
auto parser = TerminalInputParser(event_receiver->MakeSender());
parser.Add('\x1B');
parser.Timeout(50);
}
EXPECT_EQ(1, received_events.size());
EXPECT_EQ(received_events[0], Event::Escape);
Task received;
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_EQ(std::get<Event>(received), Event::Escape);
EXPECT_FALSE(event_receiver->Receive(&received));
}
TEST(Event, EscapeFast) {
std::vector<Event> 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);
auto event_receiver = MakeReceiver<Task>();
{
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<Event>(received), Event::AltA);
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_EQ(std::get<Event>(received), Event::AltB);
EXPECT_FALSE(event_receiver->Receive(&received));
}
TEST(Event, MouseLeftClickPressed) {
std::vector<Event> 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');
auto event_receiver = MakeReceiver<Task>();
{
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');
}
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);
Task received;
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(std::get<Event>(received).is_mouse());
EXPECT_EQ(Mouse::Left, std::get<Event>(received).mouse().button);
EXPECT_EQ(12, std::get<Event>(received).mouse().x);
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
EXPECT_EQ(std::get<Event>(received).mouse().motion, Mouse::Pressed);
EXPECT_FALSE(event_receiver->Receive(&received));
}
TEST(Event, MouseLeftMoved) {
std::vector<Event> 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');
auto event_receiver = MakeReceiver<Task>();
{
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');
}
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);
Task received;
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(std::get<Event>(received).is_mouse());
EXPECT_EQ(Mouse::Left, std::get<Event>(received).mouse().button);
EXPECT_EQ(12, std::get<Event>(received).mouse().x);
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
EXPECT_EQ(std::get<Event>(received).mouse().motion, Mouse::Moved);
EXPECT_FALSE(event_receiver->Receive(&received));
}
TEST(Event, MouseLeftClickReleased) {
std::vector<Event> 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');
auto event_receiver = MakeReceiver<Task>();
{
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');
}
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);
Task received;
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(std::get<Event>(received).is_mouse());
EXPECT_EQ(Mouse::Left, std::get<Event>(received).mouse().button);
EXPECT_EQ(12, std::get<Event>(received).mouse().x);
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
EXPECT_EQ(std::get<Event>(received).mouse().motion, Mouse::Released);
EXPECT_FALSE(event_receiver->Receive(&received));
}
TEST(Event, MouseReporting) {
std::vector<Event> 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');
auto event_receiver = MakeReceiver<Task>();
{
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');
}
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());
Task received;
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(std::get<Event>(received).is_cursor_position());
EXPECT_EQ(42, std::get<Event>(received).cursor_x());
EXPECT_EQ(12, std::get<Event>(received).cursor_y());
EXPECT_FALSE(event_receiver->Receive(&received));
}
TEST(Event, MouseMiddleClick) {
std::vector<Event> 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');
auto event_receiver = MakeReceiver<Task>();
{
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');
}
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);
Task received;
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(std::get<Event>(received).is_mouse());
EXPECT_EQ(Mouse::Middle, std::get<Event>(received).mouse().button);
EXPECT_EQ(12, std::get<Event>(received).mouse().x);
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
EXPECT_FALSE(event_receiver->Receive(&received));
}
TEST(Event, MouseRightClick) {
std::vector<Event> 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');
auto event_receiver = MakeReceiver<Task>();
{
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');
}
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);
Task received;
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(std::get<Event>(received).is_mouse());
EXPECT_EQ(Mouse::Right, std::get<Event>(received).mouse().button);
EXPECT_EQ(12, std::get<Event>(received).mouse().x);
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
EXPECT_FALSE(event_receiver->Receive(&received));
}
TEST(Event, UTF8) {
@@ -282,29 +313,31 @@ TEST(Event, UTF8) {
};
for (auto test : kTestCase) {
std::vector<Event> 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_EQ(1, received_events.size());
EXPECT_TRUE(received_events[0].is_character());
} else {
EXPECT_TRUE(received_events.empty());
auto event_receiver = MakeReceiver<Task>();
{
auto parser = TerminalInputParser(event_receiver->MakeSender());
for (auto input : test.input)
parser.Add(input);
}
Task received;
if (test.valid) {
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(std::get<Event>(received).is_character());
}
EXPECT_FALSE(event_receiver->Receive(&received));
}
}
TEST(Event, NewLine) {
for (char newline : {'\r', '\n'}) {
std::vector<Event> 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);
auto event_receiver = MakeReceiver<Task>();
{
auto parser = TerminalInputParser(event_receiver->MakeSender());
parser.Add(newline);
}
Task received;
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(std::get<Event>(received) == Event::Return);
}
}
@@ -324,16 +357,17 @@ TEST(Event, Control) {
cases.push_back({char(127), false});
for (auto test : cases) {
std::vector<Event> received_events;
auto parser = TerminalInputParser(
[&](Event event) { received_events.push_back(std::move(event)); });
parser.Add(test.input);
auto event_receiver = MakeReceiver<Task>();
{
auto parser = TerminalInputParser(event_receiver->MakeSender());
parser.Add(test.input);
}
Task received;
if (test.cancel) {
EXPECT_TRUE(received_events.empty());
EXPECT_FALSE(event_receiver->Receive(&received));
} else {
EXPECT_EQ(1, received_events.size());
EXPECT_EQ(received_events[0], Event::Special({test.input}));
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_EQ(std::get<Event>(received), Event::Special({test.input}));
}
}
}
@@ -351,9 +385,10 @@ TEST(Event, Special) {
Event expected;
} kTestCase[] = {
// Arrow (default cursor mode)
{str(""), Event::ArrowUp}, {str(""), Event::ArrowDown},
{str(""), Event::ArrowRight}, {str(""), Event::ArrowLeft},
{str(""), Event::Home}, {str(""), Event::End},
{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},
/*
// Arrow (application cursor mode)
{str("\x1BOA"), Event::ArrowUp},
@@ -434,38 +469,45 @@ TEST(Event, Special) {
// Custom:
{{0}, Event::Custom},
*/
};
for (auto test : kTestCase) {
std::vector<Event> received_events;
auto parser = TerminalInputParser(
[&](Event event) { received_events.push_back(std::move(event)); });
for (auto input : test.input) {
parser.Add(input);
auto event_receiver = MakeReceiver<Task>();
{
auto parser = TerminalInputParser(event_receiver->MakeSender());
for (auto input : test.input) {
parser.Add(input);
}
}
EXPECT_EQ(1, received_events.size());
EXPECT_EQ(received_events[0], test.expected);
Task received;
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_EQ(std::get<Event>(received), test.expected);
EXPECT_FALSE(event_receiver->Receive(&received));
}
}
TEST(Event, DeviceControlString) {
std::vector<Event> 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)
auto event_receiver = MakeReceiver<Task>();
{
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)
}
EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(received_events[0].is_cursor_shape());
EXPECT_EQ(1, received_events[0].cursor_shape());
Task received;
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(std::get<Event>(received).is_cursor_shape());
EXPECT_EQ(1, std::get<Event>(received).cursor_shape());
EXPECT_FALSE(event_receiver->Receive(&received));
}
} // namespace ftxui

View File

@@ -1,16 +1,21 @@
// 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 <cstddef>
#include <ftxui/component/event.hpp>
#include "ftxui/component/terminal_input_parser.hpp"
extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) {
using namespace ftxui;
auto parser = TerminalInputParser([&](Event) {});
for (size_t i = 0; i < size; ++i) {
parser.Add(data[i]);
auto event_receiver = MakeReceiver<Task>();
{
auto parser = TerminalInputParser(event_receiver->MakeSender());
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.
}