2 Commits

Author SHA1 Message Date
Mirion
1073ba414d Remove redundant member from ButtonBase (#1076)
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
2025-07-08 08:55:37 +02:00
Arthur Sonzogni
b78b97056b 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
2025-07-02 15:23:01 +02:00
21 changed files with 306 additions and 520 deletions

View File

@@ -144,7 +144,6 @@ add_library(component
src/ftxui/component/resizable_split.cpp src/ftxui/component/resizable_split.cpp
src/ftxui/component/screen_interactive.cpp src/ftxui/component/screen_interactive.cpp
src/ftxui/component/slider.cpp src/ftxui/component/slider.cpp
src/ftxui/component/terminal_id.cpp
src/ftxui/component/terminal_input_parser.cpp src/ftxui/component/terminal_input_parser.cpp
src/ftxui/component/terminal_input_parser.hpp src/ftxui/component/terminal_input_parser.hpp
src/ftxui/component/util.cpp src/ftxui/component/util.cpp

View File

@@ -46,7 +46,6 @@ example(slider_direction)
example(slider_rgb) example(slider_rgb)
example(tab_horizontal) example(tab_horizontal)
example(tab_vertical) example(tab_vertical)
example(terminal_id)
example(textarea) example(textarea)
example(toggle) example(toggle)
example(window) example(window)

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
int main(){ #include "ftxui/component/captured_mouse.hpp" // for ftxui
auto screen = ftxui::ScreenInteractive::Fullscreen(); #include "ftxui/component/component.hpp" // for Button, Horizontal, Renderer
auto testComponent = ftxui::Renderer([](){return ftxui::text("test Component");}); #include "ftxui/component/component_base.hpp" // for ComponentBase
screen.Loop(testComponent); #include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
return 0; #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;
} }

View File

@@ -1,33 +0,0 @@
// Copyright 2025 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 "ftxui/component/component.hpp" // for Renderer
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
#include "ftxui/dom/elements.hpp" // for text, hbox, separator, Element, operator|, vbox, border
int main() {
using namespace ftxui;
std::string terminal_id = "UNKNOWN";
auto screen =
ScreenInteractive::TerminalOutput();
screen.OnTerminalIDUpdate([&terminal_id] (
TerminalID const& terminal_id_update)
{
std::stringstream stream;
stream << terminal_id_update;
terminal_id = stream.str();
});
auto renderer = Renderer([&]
{
return vbox({
text("Terminal id " + terminal_id),
}) | border;
});
screen.Loop(renderer);
}

View File

@@ -4,9 +4,8 @@
#ifndef FTXUI_COMPONENT_EVENT_HPP #ifndef FTXUI_COMPONENT_EVENT_HPP
#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 <ftxui/component/terminal_id.hpp> // for TerminalID #include <string> // for string, operator==
#include <string> // for string, operator==
namespace ftxui { namespace ftxui {
@@ -36,7 +35,6 @@ struct Event {
static Event Mouse(std::string, Mouse mouse); static Event Mouse(std::string, Mouse mouse);
static Event CursorPosition(std::string, int x, int y); // Internal static Event CursorPosition(std::string, int x, int y); // Internal
static Event CursorShape(std::string, int shape); // Internal static Event CursorShape(std::string, int shape); // Internal
static Event TerminalID(std::string input, enum TerminalID terminal_id); // Internal
// --- Arrow --- // --- Arrow ---
static const Event ArrowLeft; static const Event ArrowLeft;
@@ -119,9 +117,6 @@ struct Event {
bool is_cursor_shape() const { return type_ == Type::CursorShape; } bool is_cursor_shape() const { return type_ == Type::CursorShape; }
int cursor_shape() const { return data_.cursor_shape; } int cursor_shape() const { return data_.cursor_shape; }
bool is_terminal_id() const { return type_ == Type::TerminalID; }
enum TerminalID terminal_id() const { return data_.terminal_id; }
// Debug // Debug
std::string DebugString() const; std::string DebugString() const;
@@ -137,7 +132,6 @@ struct Event {
Mouse, Mouse,
CursorPosition, CursorPosition,
CursorShape, CursorShape,
TerminalID
}; };
Type type_ = Type::Unknown; Type type_ = Type::Unknown;
@@ -150,7 +144,6 @@ struct Event {
struct Mouse mouse; struct Mouse mouse;
struct Cursor cursor; struct Cursor cursor;
int cursor_shape; int cursor_shape;
enum TerminalID terminal_id;
} data_ = {}; } data_ = {};
std::string input_; std::string input_;

View File

@@ -10,7 +10,6 @@
#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 <list>
#include "ftxui/component/animation.hpp" // for TimePoint #include "ftxui/component/animation.hpp" // for TimePoint
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse #include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
@@ -25,8 +24,6 @@ class Loop;
struct Event; struct Event;
using Component = std::shared_ptr<ComponentBase>; using Component = std::shared_ptr<ComponentBase>;
using TerminalIDUpdateCallback = std::function<void (const TerminalID &)>;
class ScreenInteractivePrivate; class ScreenInteractivePrivate;
/// @brief ScreenInteractive is a `Screen` that can handle events, run a main /// @brief ScreenInteractive is a `Screen` that can handle events, run a main
@@ -75,10 +72,6 @@ class ScreenInteractive : public Screen {
void ForceHandleCtrlC(bool force); void ForceHandleCtrlC(bool force);
void ForceHandleCtrlZ(bool force); void ForceHandleCtrlZ(bool force);
TerminalID TerminalId() const;
void OnTerminalIDUpdate(const TerminalIDUpdateCallback& callback);
// Selection API. // Selection API.
std::string GetSelection(); std::string GetSelection();
void SelectionChange(std::function<void()> callback); void SelectionChange(std::function<void()> callback);
@@ -163,11 +156,6 @@ class ScreenInteractive : public Screen {
std::unique_ptr<Selection> selection_; std::unique_ptr<Selection> selection_;
std::function<void()> selection_on_change_; std::function<void()> selection_on_change_;
TerminalID m_terminal_id = TerminalID::Unknown;
using TerminalIDUpdateCallbackContainer = std::list<TerminalIDUpdateCallback>;
TerminalIDUpdateCallbackContainer terminal_id_callback_;
friend class Loop; friend class Loop;
public: public:

View File

@@ -1,35 +0,0 @@
// Copyright 2025 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#ifndef FTXUI_COMPONENT_TERMINAL_ID_HPP
#define FTXUI_COMPONENT_TERMINAL_ID_HPP
#include <ostream>
#include <string>
namespace ftxui {
/// @brief The TerminalID enum class represents different types of terminal
/// emulators that FTXUI can detect. It is used to identify the terminal
/// emulator in use, which can affect how FTXUI renders its output and handles
/// input events.
/// @ingroup component
enum class TerminalID {
Unknown,
// --
Konsole,
LinuxVC,
Urxvt,
Vte,
Xterm,
};
std::ostream& operator<<(
std::ostream& os,
TerminalID terminal_id);
} // namespace ftxui
#endif /* end of include guard: FTXUI_COMPONENT_TERMINAL_ID_HPP */

View File

@@ -30,7 +30,7 @@ namespace ftxui {
/// - 2x4 braille characters (1x1 pixel) /// - 2x4 braille characters (1x1 pixel)
/// - 2x2 block characters (2x2 pixels) /// - 2x2 block characters (2x2 pixels)
/// - 2x4 normal characters (2x4 pixels) /// - 2x4 normal characters (2x4 pixels)
/// ///
/// You need to multiply the x coordinate by 2 and the y coordinate by 4 to /// You need to multiply the x coordinate by 2 and the y coordinate by 4 to
/// get the correct position in the terminal. /// get the correct position in the terminal.
/// ///

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

@@ -11,7 +11,7 @@ namespace ftxui {
/// It is defined by its minimum and maximum coordinates along the x and y axes. /// 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 /// Note that the coordinates are inclusive, meaning that the box includes both
/// the minimum and maximum values. /// the minimum and maximum values.
/// ///
/// @ingroup screen /// @ingroup screen
struct Box { struct Box {
int x_min = 0; int x_min = 0;

View File

@@ -139,7 +139,6 @@ class ButtonBase : public ComponentBase, public ButtonOption {
private: private:
bool mouse_hover_ = false; bool mouse_hover_ = false;
Box box_; Box box_;
ButtonOption option_;
float animation_background_ = 0; float animation_background_ = 0;
float animation_foreground_ = 0; float animation_foreground_ = 0;
animation::Animator animator_background_ = animation::Animator animator_background_ =

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); });
parser.Add(data[i]);
for (size_t i = 0; i < size; ++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

@@ -87,16 +87,6 @@ Event Event::CursorPosition(std::string input, int x, int y) {
return event; return event;
} }
/// @internal
// static
Event Event::TerminalID(std::string input, enum TerminalID terminal_id) {
Event event;
event.input_ = std::move(input);
event.type_ = Type::TerminalID;
event.data_.terminal_id = terminal_id;
return event;
}
/// @brief Return a string representation of the event. /// @brief Return a string representation of the event.
std::string Event::DebugString() const { std::string Event::DebugString() const {
static std::map<Event, const char*> event_to_string = { static std::map<Event, const char*> event_to_string = {

View File

@@ -1,7 +1,6 @@
// Copyright 2022 Arthur Sonzogni. All rights reserved. // Copyright 2022 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 <iostream>
#include "ftxui/component/loop.hpp" #include "ftxui/component/loop.hpp"
#include <utility> // for move #include <utility> // for move

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)) {
@@ -321,8 +324,6 @@ std::string DeviceStatusReport(DSRMode ps) {
return CSI + std::to_string(int(ps)) + "n"; return CSI + std::to_string(int(ps)) + "n";
} }
const std::string TerminalIdReport = "\x1b[0c";
class CapturedMouseImpl : public CapturedMouseInterface { class CapturedMouseImpl : public CapturedMouseInterface {
public: public:
explicit CapturedMouseImpl(std::function<void(void)> callback) explicit CapturedMouseImpl(std::function<void(void)> callback)
@@ -726,9 +727,6 @@ void ScreenInteractive::Install() {
enable({DECMode::kMouseSgrExtMode}); enable({DECMode::kMouseSgrExtMode});
} }
// Report the Terminal ID.
std::cout << TerminalIdReport;
// After installing the new configuration, flush it to the terminal to // After installing the new configuration, flush it to the terminal to
// ensure it is fully applied: // ensure it is fully applied:
Flush(); Flush();
@@ -799,17 +797,6 @@ void ScreenInteractive::HandleTask(Component component, Task& task) {
return; return;
} }
if (arg.is_terminal_id()) {
m_terminal_id = arg.terminal_id();
for(auto & callback : terminal_id_callback_) {
if (callback) {
callback(m_terminal_id);
}
}
return;
}
if (arg.is_mouse()) { if (arg.is_mouse()) {
arg.mouse().x -= cursor_x_; arg.mouse().x -= cursor_x_;
arg.mouse().y -= cursor_y_; arg.mouse().y -= cursor_y_;
@@ -1100,13 +1087,4 @@ bool ScreenInteractive::SelectionData::operator!=(
return !(*this == other); return !(*this == other);
} }
TerminalID ScreenInteractive::TerminalId() const {
return m_terminal_id;
}
void ScreenInteractive::OnTerminalIDUpdate(
const TerminalIDUpdateCallback& callback) {
terminal_id_callback_.push_back(callback);
}
} // namespace ftxui. } // namespace ftxui.

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();
@@ -207,7 +205,6 @@ TEST(ScreenInteractive, FixedSizeInitialFrame) {
"\x1B[?1003h" // Enable mouse motion tracking. "\x1B[?1003h" // Enable mouse motion tracking.
"\x1B[?1015h" // Enable mouse wheel tracking. "\x1B[?1015h" // Enable mouse wheel tracking.
"\x1B[?1006h" // Enable SGR mouse tracking. "\x1B[?1006h" // Enable SGR mouse tracking.
"\x1B[0c" // Query terminal ID.
"\0" // Flush stdout. "\0" // Flush stdout.
// Reset the screen. // Reset the screen.
@@ -242,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

@@ -1,41 +0,0 @@
// Copyright 2025 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 "ftxui/component/terminal_id.hpp"
namespace ftxui
{
std::ostream& operator<<(std::ostream& os, TerminalID terminal_id) {
switch (terminal_id) {
case TerminalID::Unknown: {
os << "Unknown";
break;
}
case TerminalID::Urxvt: {
os << "Urxvt";
break;
}
case TerminalID::LinuxVC: {
os << "LinuxVC";
break;
}
case TerminalID::Konsole: {
os << "Konsole";
break;
}
case TerminalID::Vte: {
os << "Vte";
break;
}
case TerminalID::Xterm: {
os << "Xterm";
break;
}
}
return os;
}
} // 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,30 +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();
return;
case TERMINAL_ID:
out_->Send(Event::TerminalID(std::move(pending_), output.terminal_id));
pending_.clear(); pending_.clear();
return; return;
} }
@@ -379,8 +374,6 @@ TerminalInputParser::Output TerminalInputParser::ParseCSI() {
return ParseMouse(altered, false, std::move(arguments)); return ParseMouse(altered, false, std::move(arguments));
case 'R': case 'R':
return ParseCursorPosition(std::move(arguments)); return ParseCursorPosition(std::move(arguments));
case 'c':
return ParseTerminalID(std::move(arguments));
default: default:
return SPECIAL; return SPECIAL;
} }
@@ -468,22 +461,4 @@ TerminalInputParser::Output TerminalInputParser::ParseCursorPosition(
return output; return output;
} }
TerminalInputParser::Output TerminalInputParser::ParseTerminalID(
std::vector<int> arguments) {
if (arguments.empty()) {
return TerminalID::Unknown;
}
switch (arguments[0]) {
case 1:
return TerminalID::Xterm;
case 6:
return TerminalID::LinuxVC;
case 62:
return TerminalID::Konsole;
default:
return TerminalID::Unknown;
}
}
} // namespace ftxui } // namespace ftxui

View File

@@ -4,13 +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/terminal_id.hpp" // for TerminalId
#include "ftxui/component/receiver.hpp" // for Sender
#include "ftxui/component/task.hpp" // for Task
namespace ftxui { namespace ftxui {
struct Event; struct Event;
@@ -18,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);
@@ -34,7 +32,6 @@ class TerminalInputParser {
CURSOR_POSITION, CURSOR_POSITION,
CURSOR_SHAPE, CURSOR_SHAPE,
SPECIAL, SPECIAL,
TERMINAL_ID
}; };
struct CursorPosition { struct CursorPosition {
@@ -48,14 +45,10 @@ class TerminalInputParser {
Mouse mouse; Mouse mouse;
CursorPosition cursor{}; CursorPosition cursor{};
int cursor_shape; int cursor_shape;
TerminalID terminal_id;
}; };
Output(Type t) // NOLINT Output(Type t) // NOLINT
: type(t) {} : type(t) {}
Output(TerminalID terminal_id) // NOLINT
: type(TERMINAL_ID), terminal_id(terminal_id) {}
}; };
void Send(Output output); void Send(Output output);
@@ -67,9 +60,8 @@ class TerminalInputParser {
Output ParseOSC(); Output ParseOSC();
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);
Output ParseTerminalID(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,228 +22,197 @@ 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(';');
parser.Add('1'); parser.Add('1');
parser.Add('2'); parser.Add('2');
parser.Add(';'); parser.Add(';');
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');
parser.Add(';'); parser.Add(';');
parser.Add('1'); parser.Add('1');
parser.Add('2'); parser.Add('2');
parser.Add(';'); parser.Add(';');
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(';');
parser.Add('1'); parser.Add('1');
parser.Add('2'); parser.Add('2');
parser.Add(';'); parser.Add(';');
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');
parser.Add(';'); parser.Add(';');
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');
parser.Add(';'); parser.Add(';');
parser.Add('1'); parser.Add('1');
parser.Add('2'); parser.Add('2');
parser.Add(';'); parser.Add(';');
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');
parser.Add(';'); parser.Add(';');
parser.Add('1'); parser.Add('1');
parser.Add('2'); parser.Add('2');
parser.Add(';'); parser.Add(';');
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,67 +434,38 @@ 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);
}
} }
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_EQ(received_events[0], test.expected);
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
parser.Add(36); // $ parser.Add(36); // $
parser.Add(114); // r parser.Add(114); // r
parser.Add(49); // 1 parser.Add(49); // 1
parser.Add(32); // SP parser.Add(32); // SP
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));
}
TEST(Event, TerminalID) {
// Test terminal id for KDE konsole
auto event_receiver = MakeReceiver<Task>();
{
auto parser = TerminalInputParser(event_receiver->MakeSender());
parser.Add('\x1B');
parser.Add('[');
parser.Add('?');
parser.Add('6');
parser.Add('2');
parser.Add(';');
parser.Add('2');
parser.Add('c');
}
Task received;
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(std::get<Event>(received).is_terminal_id());
EXPECT_EQ(TerminalID::Konsole, std::get<Event>(received).terminal_id());
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) {});
{ for (size_t i = 0; i < size; ++i) {
auto parser = TerminalInputParser(event_receiver->MakeSender()); parser.Add(data[i]);
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. return 0; // Non-zero return values are reserved for future use.
} }