mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-09-16 16:08:08 +08:00
Featre: Support ctrl+arrow in input. (#494)
CTRL+LEFT: Move the cursor to the beginning of the word. CTRL+RIGHT: Move the cursor to the beginning of the word. This was requested by: https://github.com/ArthurSonzogni/FTXUI/issues/490
This commit is contained in:
@@ -51,26 +51,30 @@ Event Event::CursorReporting(std::string input, int x, int y) {
|
||||
}
|
||||
|
||||
// --- Arrow ---
|
||||
const Event Event::ArrowLeft = Event::Special("\x1B[D"); // NOLINT
|
||||
const Event Event::ArrowRight = Event::Special("\x1B[C"); // NOLINT
|
||||
const Event Event::ArrowUp = Event::Special("\x1B[A"); // NOLINT
|
||||
const Event Event::ArrowDown = Event::Special("\x1B[B"); // NOLINT
|
||||
const Event Event::Backspace = Event::Special({127}); // NOLINT
|
||||
const Event Event::Delete = Event::Special("\x1B[3~"); // NOLINT
|
||||
const Event Event::Escape = Event::Special("\x1B"); // NOLINT
|
||||
const Event Event::Return = Event::Special({10}); // NOLINT
|
||||
const Event Event::Tab = Event::Special({9}); // NOLINT
|
||||
const Event Event::TabReverse = Event::Special({27, 91, 90}); // NOLINT
|
||||
const Event Event::F1 = Event::Special("\x1B[OP"); // NOLINT
|
||||
const Event Event::F2 = Event::Special("\x1B[OQ"); // NOLINT
|
||||
const Event Event::F3 = Event::Special("\x1B[OR"); // NOLINT
|
||||
const Event Event::F4 = Event::Special("\x1B[OS"); // NOLINT
|
||||
const Event Event::F5 = Event::Special("\x1B[15~"); // NOLINT
|
||||
const Event Event::F6 = Event::Special("\x1B[17~"); // NOLINT
|
||||
const Event Event::F7 = Event::Special("\x1B[18~"); // NOLINT
|
||||
const Event Event::F8 = Event::Special("\x1B[19~"); // NOLINT
|
||||
const Event Event::F9 = Event::Special("\x1B[20~"); // NOLINT
|
||||
const Event Event::F10 = Event::Special("\x1B[21~"); // NOLINT
|
||||
const Event Event::ArrowLeft = Event::Special("\x1B[D"); // NOLINT
|
||||
const Event Event::ArrowRight = Event::Special("\x1B[C"); // NOLINT
|
||||
const Event Event::ArrowUp = Event::Special("\x1B[A"); // NOLINT
|
||||
const Event Event::ArrowDown = Event::Special("\x1B[B"); // NOLINT
|
||||
const Event Event::ArrowLeftCtrl = Event::Special("\x1B[1;5D"); // NOLINT
|
||||
const Event Event::ArrowRightCtrl = Event::Special("\x1B[1;5C"); // NOLINT
|
||||
const Event Event::ArrowUpCtrl = Event::Special("\x1B[1;5A"); // NOLINT
|
||||
const Event Event::ArrowDownCtrl = Event::Special("\x1B[1;5B"); // NOLINT
|
||||
const Event Event::Backspace = Event::Special({127}); // NOLINT
|
||||
const Event Event::Delete = Event::Special("\x1B[3~"); // NOLINT
|
||||
const Event Event::Escape = Event::Special("\x1B"); // NOLINT
|
||||
const Event Event::Return = Event::Special({10}); // NOLINT
|
||||
const Event Event::Tab = Event::Special({9}); // NOLINT
|
||||
const Event Event::TabReverse = Event::Special({27, 91, 90}); // NOLINT
|
||||
const Event Event::F1 = Event::Special("\x1B[OP"); // NOLINT
|
||||
const Event Event::F2 = Event::Special("\x1B[OQ"); // NOLINT
|
||||
const Event Event::F3 = Event::Special("\x1B[OR"); // NOLINT
|
||||
const Event Event::F4 = Event::Special("\x1B[OS"); // NOLINT
|
||||
const Event Event::F5 = Event::Special("\x1B[15~"); // NOLINT
|
||||
const Event Event::F6 = Event::Special("\x1B[17~"); // NOLINT
|
||||
const Event Event::F7 = Event::Special("\x1B[18~"); // NOLINT
|
||||
const Event Event::F8 = Event::Special("\x1B[19~"); // NOLINT
|
||||
const Event Event::F9 = Event::Special("\x1B[20~"); // NOLINT
|
||||
const Event Event::F10 = Event::Special("\x1B[21~"); // NOLINT
|
||||
const Event Event::F11 = Event::Special("\x1B[21~"); // Doesn't exist // NOLINT
|
||||
const Event Event::F12 = Event::Special("\x1B[24~"); // NOLINT
|
||||
const Event Event::Home = Event::Special({27, 91, 72}); // NOLINT
|
||||
|
@@ -22,6 +22,36 @@ namespace ftxui {
|
||||
|
||||
namespace {
|
||||
|
||||
// Group together several propertiej so they appear to form a similar group.
|
||||
// For instance, letters are grouped with number and form a single word.
|
||||
bool IsWordCharacter(WordBreakProperty property) {
|
||||
switch (property) {
|
||||
case WordBreakProperty::ALetter:
|
||||
case WordBreakProperty::Hebrew_Letter:
|
||||
case WordBreakProperty::Katakana:
|
||||
case WordBreakProperty::Numeric:
|
||||
return true;
|
||||
|
||||
case WordBreakProperty::CR:
|
||||
case WordBreakProperty::Double_Quote:
|
||||
case WordBreakProperty::LF:
|
||||
case WordBreakProperty::MidLetter:
|
||||
case WordBreakProperty::MidNum:
|
||||
case WordBreakProperty::MidNumLet:
|
||||
case WordBreakProperty::Newline:
|
||||
case WordBreakProperty::Single_Quote:
|
||||
case WordBreakProperty::WSegSpace:
|
||||
// Unsure:
|
||||
case WordBreakProperty::Extend:
|
||||
case WordBreakProperty::ExtendNumLet:
|
||||
case WordBreakProperty::Format:
|
||||
case WordBreakProperty::Regional_Indicator:
|
||||
case WordBreakProperty::ZWJ:
|
||||
return false;
|
||||
};
|
||||
return true; // NOT_REACHED();
|
||||
};
|
||||
|
||||
std::string PasswordField(size_t size) {
|
||||
std::string out;
|
||||
out.reserve(2 * size);
|
||||
@@ -111,7 +141,6 @@ class InputBase : public ComponentBase {
|
||||
if (event.is_mouse()) {
|
||||
return OnMouseEvent(event);
|
||||
}
|
||||
|
||||
std::string c;
|
||||
|
||||
// Backspace.
|
||||
@@ -149,6 +178,7 @@ class InputBase : public ComponentBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Arrow
|
||||
if (event == Event::ArrowLeft && cursor_position() > 0) {
|
||||
cursor_position()--;
|
||||
return true;
|
||||
@@ -160,6 +190,16 @@ class InputBase : public ComponentBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
// CTRL + Arrow:
|
||||
if (event == Event::ArrowLeftCtrl) {
|
||||
HandleLeftCtrl();
|
||||
return true;
|
||||
}
|
||||
if (event == Event::ArrowRightCtrl) {
|
||||
HandleRightCtrl();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event == Event::Home) {
|
||||
cursor_position() = 0;
|
||||
return true;
|
||||
@@ -182,6 +222,39 @@ class InputBase : public ComponentBase {
|
||||
}
|
||||
|
||||
private:
|
||||
void HandleLeftCtrl() {
|
||||
auto properties = Utf8ToWordBreakProperty(*content_);
|
||||
|
||||
// Move left, as long as left is not a word character.
|
||||
while (cursor_position() > 0 &&
|
||||
!IsWordCharacter(properties[cursor_position() - 1])) {
|
||||
cursor_position()--;
|
||||
}
|
||||
|
||||
// Move left, as long as left is a word character:
|
||||
while (cursor_position() > 0 &&
|
||||
IsWordCharacter(properties[cursor_position() - 1])) {
|
||||
cursor_position()--;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleRightCtrl() {
|
||||
auto properties = Utf8ToWordBreakProperty(*content_);
|
||||
int max = (int)properties.size();
|
||||
|
||||
// Move right, as long as right is not a word character.
|
||||
while (cursor_position() < max &&
|
||||
!IsWordCharacter(properties[cursor_position()])) {
|
||||
cursor_position()++;
|
||||
}
|
||||
|
||||
// Move right, as long as right is a word character:
|
||||
while (cursor_position() < max &&
|
||||
IsWordCharacter(properties[cursor_position()])) {
|
||||
cursor_position()++;
|
||||
}
|
||||
}
|
||||
|
||||
bool OnMouseEvent(Event event) {
|
||||
hovered_ =
|
||||
box_.Contain(event.mouse().x, event.mouse().y) && CaptureMouse(event);
|
||||
|
@@ -369,6 +369,124 @@ TEST(InputTest, MouseClickComplex) {
|
||||
EXPECT_EQ(option.cursor_position(), 4u);
|
||||
}
|
||||
|
||||
TEST(InputTest, CtrlArrowLeft) {
|
||||
std::string content = "word word 测ord wo测d word";
|
||||
// 0 5 10 15 20
|
||||
std::string placeholder;
|
||||
auto option = InputOption();
|
||||
option.cursor_position = 22;
|
||||
auto input = Input(&content, &placeholder, &option);
|
||||
|
||||
// Use CTRL+Left several time
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 20u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 15u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 10u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 5u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 0u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 0u);
|
||||
}
|
||||
|
||||
TEST(InputTest, CtrlArrowLeft2) {
|
||||
std::string content = " word word 测ord wo测d word ";
|
||||
// 0 3 6 9 12 15 18 21 24 27 30 33
|
||||
std::string placeholder;
|
||||
auto option = InputOption();
|
||||
option.cursor_position = 33;
|
||||
auto input = Input(&content, &placeholder, &option);
|
||||
|
||||
// Use CTRL+Left several time
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 27u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 21u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 15u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 9u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 3u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 0u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 0u);
|
||||
}
|
||||
|
||||
TEST(InputTest, CtrlArrowRight) {
|
||||
std::string content = "word word 测ord wo测d word";
|
||||
// 0 5 10 15 20
|
||||
std::string placeholder;
|
||||
auto option = InputOption();
|
||||
option.cursor_position = 2;
|
||||
auto input = Input(&content, &placeholder, &option);
|
||||
|
||||
// Use CTRL+Left several time
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 4);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 9);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 14u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 19u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 24u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 24u);
|
||||
}
|
||||
|
||||
TEST(InputTest, CtrlArrowRight2) {
|
||||
std::string content = " word word 测ord wo测d word ";
|
||||
// 0 3 6 9 12 15 18 21 24 27 30 33
|
||||
std::string placeholder;
|
||||
auto option = InputOption();
|
||||
option.cursor_position = 0;
|
||||
auto input = Input(&content, &placeholder, &option);
|
||||
|
||||
// Use CTRL+Left several time
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 7u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 13u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 19u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 25u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 31u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 34u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 34u);
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2021 Arthur Sonzogni. All rights reserved.
|
||||
|
Reference in New Issue
Block a user