mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-12-16 01:48:56 +08:00
Compare commits
2 Commits
1073ba414d
...
ce9964131d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce9964131d | ||
|
|
2715503516 |
@@ -144,6 +144,7 @@ add_library(component
|
||||
src/ftxui/component/resizable_split.cpp
|
||||
src/ftxui/component/screen_interactive.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.hpp
|
||||
src/ftxui/component/util.cpp
|
||||
|
||||
@@ -46,6 +46,7 @@ example(slider_direction)
|
||||
example(slider_rgb)
|
||||
example(tab_horizontal)
|
||||
example(tab_vertical)
|
||||
example(terminal_id)
|
||||
example(textarea)
|
||||
example(toggle)
|
||||
example(window)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
33
examples/component/terminal_id.cpp
Normal file
33
examples/component/terminal_id.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -4,8 +4,9 @@
|
||||
#ifndef FTXUI_COMPONENT_EVENT_HPP
|
||||
#define FTXUI_COMPONENT_EVENT_HPP
|
||||
|
||||
#include <ftxui/component/mouse.hpp> // for Mouse
|
||||
#include <string> // for string, operator==
|
||||
#include <ftxui/component/mouse.hpp> // for Mouse
|
||||
#include <ftxui/component/terminal_id.hpp> // for TerminalID
|
||||
#include <string> // for string, operator==
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
@@ -35,6 +36,7 @@ struct Event {
|
||||
static Event Mouse(std::string, Mouse mouse);
|
||||
static Event CursorPosition(std::string, int x, int y); // Internal
|
||||
static Event CursorShape(std::string, int shape); // Internal
|
||||
static Event TerminalID(std::string input, enum TerminalID terminal_id); // Internal
|
||||
|
||||
// --- Arrow ---
|
||||
static const Event ArrowLeft;
|
||||
@@ -117,6 +119,9 @@ struct Event {
|
||||
bool is_cursor_shape() const { return type_ == Type::CursorShape; }
|
||||
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
|
||||
std::string DebugString() const;
|
||||
|
||||
@@ -132,6 +137,7 @@ struct Event {
|
||||
Mouse,
|
||||
CursorPosition,
|
||||
CursorShape,
|
||||
TerminalID
|
||||
};
|
||||
Type type_ = Type::Unknown;
|
||||
|
||||
@@ -144,6 +150,7 @@ struct Event {
|
||||
struct Mouse mouse;
|
||||
struct Cursor cursor;
|
||||
int cursor_shape;
|
||||
enum TerminalID terminal_id;
|
||||
} data_ = {};
|
||||
|
||||
std::string input_;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for string
|
||||
#include <thread> // for thread
|
||||
#include <list>
|
||||
|
||||
#include "ftxui/component/animation.hpp" // for TimePoint
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
||||
@@ -24,6 +25,8 @@ class Loop;
|
||||
struct Event;
|
||||
|
||||
using Component = std::shared_ptr<ComponentBase>;
|
||||
using TerminalIDUpdateCallback = std::function<void (const TerminalID &)>;
|
||||
|
||||
class ScreenInteractivePrivate;
|
||||
|
||||
/// @brief ScreenInteractive is a `Screen` that can handle events, run a main
|
||||
@@ -72,6 +75,10 @@ class ScreenInteractive : public Screen {
|
||||
void ForceHandleCtrlC(bool force);
|
||||
void ForceHandleCtrlZ(bool force);
|
||||
|
||||
TerminalID TerminalId() const;
|
||||
|
||||
void OnTerminalIDUpdate(const TerminalIDUpdateCallback& callback);
|
||||
|
||||
// Selection API.
|
||||
std::string GetSelection();
|
||||
void SelectionChange(std::function<void()> callback);
|
||||
@@ -156,6 +163,11 @@ class ScreenInteractive : public Screen {
|
||||
std::unique_ptr<Selection> selection_;
|
||||
std::function<void()> selection_on_change_;
|
||||
|
||||
TerminalID m_terminal_id = TerminalID::Unknown;
|
||||
|
||||
using TerminalIDUpdateCallbackContainer = std::list<TerminalIDUpdateCallback>;
|
||||
TerminalIDUpdateCallbackContainer terminal_id_callback_;
|
||||
|
||||
friend class Loop;
|
||||
|
||||
public:
|
||||
|
||||
35
include/ftxui/component/terminal_id.hpp
Normal file
35
include/ftxui/component/terminal_id.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
// 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 */
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
|
||||
/// @brief FlexboxConfig is a configuration structure that defines the layout
|
||||
/// properties for a flexbox container.
|
||||
//
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -139,6 +139,7 @@ class ButtonBase : public ComponentBase, public ButtonOption {
|
||||
private:
|
||||
bool mouse_hover_ = false;
|
||||
Box box_;
|
||||
ButtonOption option_;
|
||||
float animation_background_ = 0;
|
||||
float animation_foreground_ = 0;
|
||||
animation::Animator animator_background_ =
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,16 @@ Event Event::CursorPosition(std::string input, int x, int y) {
|
||||
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.
|
||||
std::string Event::DebugString() const {
|
||||
static std::map<Event, const char*> event_to_string = {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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.
|
||||
#include <iostream>
|
||||
#include "ftxui/component/loop.hpp"
|
||||
|
||||
#include <utility> // for move
|
||||
|
||||
@@ -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)) {
|
||||
@@ -324,6 +321,8 @@ std::string DeviceStatusReport(DSRMode ps) {
|
||||
return CSI + std::to_string(int(ps)) + "n";
|
||||
}
|
||||
|
||||
const std::string TerminalIdReport = "\x1b[0c";
|
||||
|
||||
class CapturedMouseImpl : public CapturedMouseInterface {
|
||||
public:
|
||||
explicit CapturedMouseImpl(std::function<void(void)> callback)
|
||||
@@ -727,6 +726,9 @@ void ScreenInteractive::Install() {
|
||||
enable({DECMode::kMouseSgrExtMode});
|
||||
}
|
||||
|
||||
// Report the Terminal ID.
|
||||
std::cout << TerminalIdReport;
|
||||
|
||||
// After installing the new configuration, flush it to the terminal to
|
||||
// ensure it is fully applied:
|
||||
Flush();
|
||||
@@ -797,6 +799,17 @@ void ScreenInteractive::HandleTask(Component component, Task& task) {
|
||||
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()) {
|
||||
arg.mouse().x -= cursor_x_;
|
||||
arg.mouse().y -= cursor_y_;
|
||||
@@ -1087,4 +1100,13 @@ bool ScreenInteractive::SelectionData::operator!=(
|
||||
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.
|
||||
|
||||
@@ -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();
|
||||
@@ -205,6 +207,7 @@ TEST(ScreenInteractive, FixedSizeInitialFrame) {
|
||||
"\x1B[?1003h" // Enable mouse motion tracking.
|
||||
"\x1B[?1015h" // Enable mouse wheel tracking.
|
||||
"\x1B[?1006h" // Enable SGR mouse tracking.
|
||||
"\x1B[0c" // Query terminal ID.
|
||||
"\0" // Flush stdout.
|
||||
|
||||
// Reset the screen.
|
||||
@@ -239,6 +242,7 @@ TEST(ScreenInteractive, FixedSizeInitialFrame) {
|
||||
"\r\n"sv;
|
||||
ASSERT_EQ(expected, output);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
41
src/ftxui/component/terminal_id.cpp
Normal file
41
src/ftxui/component/terminal_id.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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
|
||||
@@ -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,30 @@ 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;
|
||||
|
||||
case TERMINAL_ID:
|
||||
out_->Send(Event::TerminalID(std::move(pending_), output.terminal_id));
|
||||
pending_.clear();
|
||||
return;
|
||||
}
|
||||
@@ -374,6 +379,8 @@ TerminalInputParser::Output TerminalInputParser::ParseCSI() {
|
||||
return ParseMouse(altered, false, std::move(arguments));
|
||||
case 'R':
|
||||
return ParseCursorPosition(std::move(arguments));
|
||||
case 'c':
|
||||
return ParseTerminalID(std::move(arguments));
|
||||
default:
|
||||
return SPECIAL;
|
||||
}
|
||||
@@ -461,4 +468,22 @@ TerminalInputParser::Output TerminalInputParser::ParseCursorPosition(
|
||||
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
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
#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/terminal_id.hpp" // for TerminalId
|
||||
#include "ftxui/component/receiver.hpp" // for Sender
|
||||
#include "ftxui/component/task.hpp" // for Task
|
||||
|
||||
namespace ftxui {
|
||||
struct Event;
|
||||
@@ -16,7 +18,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);
|
||||
|
||||
@@ -32,6 +34,7 @@ class TerminalInputParser {
|
||||
CURSOR_POSITION,
|
||||
CURSOR_SHAPE,
|
||||
SPECIAL,
|
||||
TERMINAL_ID
|
||||
};
|
||||
|
||||
struct CursorPosition {
|
||||
@@ -45,10 +48,14 @@ class TerminalInputParser {
|
||||
Mouse mouse;
|
||||
CursorPosition cursor{};
|
||||
int cursor_shape;
|
||||
TerminalID terminal_id;
|
||||
};
|
||||
|
||||
Output(Type t) // NOLINT
|
||||
: type(t) {}
|
||||
|
||||
Output(TerminalID terminal_id) // NOLINT
|
||||
: type(TERMINAL_ID), terminal_id(terminal_id) {}
|
||||
};
|
||||
|
||||
void Send(Output output);
|
||||
@@ -60,8 +67,9 @@ class TerminalInputParser {
|
||||
Output ParseOSC();
|
||||
Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments);
|
||||
Output ParseCursorPosition(std::vector<int> arguments);
|
||||
Output ParseTerminalID(std::vector<int> arguments);
|
||||
|
||||
std::function<void(Event)> out_;
|
||||
Sender<Task> out_;
|
||||
int position_ = -1;
|
||||
int timeout_ = 0;
|
||||
std::string pending_;
|
||||
|
||||
@@ -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("[A"), Event::ArrowUp}, {str("[B"), Event::ArrowDown},
|
||||
{str("[C"), Event::ArrowRight}, {str("[D"), Event::ArrowLeft},
|
||||
{str("[H"), Event::Home}, {str("[F"), 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,67 @@ 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));
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user