mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2024-11-22 18:59:59 +08:00
Feature: Add multi-line input. (#630)
This commit is contained in:
parent
6215240b37
commit
7de4f8683d
@ -5,6 +5,8 @@ current (development)
|
|||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
### Component
|
### Component
|
||||||
|
- Feature: `input` is now multi-line.
|
||||||
|
- Feature: `input` style can now be customized.
|
||||||
- Feature: Support `ResizableSplit` with customizable separator.
|
- Feature: Support `ResizableSplit` with customizable separator.
|
||||||
- Breaking: MenuDirection enum is renamed Direction
|
- Breaking: MenuDirection enum is renamed Direction
|
||||||
- Fix: Remove useless new line when using an alternative screen.
|
- Fix: Remove useless new line when using an alternative screen.
|
||||||
@ -12,9 +14,7 @@ current (development)
|
|||||||
### Dom
|
### Dom
|
||||||
- Feature: Add the dashed style for border and separator.
|
- Feature: Add the dashed style for border and separator.
|
||||||
- Feature: Add colored borders.
|
- Feature: Add colored borders.
|
||||||
- Feature: Customize with gradient color effect. Add the following decorators:
|
- Feature: Add `LinearGradient`!
|
||||||
- `colorgrad`
|
|
||||||
- `bgcolorgrad`
|
|
||||||
- Improvement: Color::Interpolate() uses gamma correction.
|
- Improvement: Color::Interpolate() uses gamma correction.
|
||||||
|
|
||||||
###
|
###
|
||||||
|
@ -71,3 +71,5 @@ include(GoogleTest)
|
|||||||
gtest_discover_tests(ftxui-tests
|
gtest_discover_tests(ftxui-tests
|
||||||
DISCOVERY_TIMEOUT 600
|
DISCOVERY_TIMEOUT 600
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(CMAKE_CTEST_ARGUMENTS "--rerun-failed --output-on-failure")
|
||||||
|
@ -17,6 +17,7 @@ example(focus_cursor)
|
|||||||
example(gallery)
|
example(gallery)
|
||||||
example(homescreen)
|
example(homescreen)
|
||||||
example(input)
|
example(input)
|
||||||
|
example(input_style)
|
||||||
example(linear_gradient_gallery)
|
example(linear_gradient_gallery)
|
||||||
example(maybe)
|
example(maybe)
|
||||||
example(menu)
|
example(menu)
|
||||||
@ -40,5 +41,6 @@ example(slider_direction)
|
|||||||
example(slider_rgb)
|
example(slider_rgb)
|
||||||
example(tab_horizontal)
|
example(tab_horizontal)
|
||||||
example(tab_vertical)
|
example(tab_vertical)
|
||||||
|
example(textarea)
|
||||||
example(toggle)
|
example(toggle)
|
||||||
example(with_restored_io)
|
example(with_restored_io)
|
||||||
|
98
examples/component/input_style.cpp
Normal file
98
examples/component/input_style.cpp
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
#include <ftxui/dom/linear_gradient.hpp> // for LinearGradient
|
||||||
|
#include <ftxui/screen/color.hpp> // for Color, Color::White, Color::Red, Color::Blue, Color::Black, Color::GrayDark, ftxui
|
||||||
|
#include <functional> // for function
|
||||||
|
#include <string> // for allocator, string
|
||||||
|
#include <utility> // for move
|
||||||
|
|
||||||
|
#include "ftxui/component/component.hpp" // for Input, Horizontal, Vertical, operator|
|
||||||
|
#include "ftxui/component/component_base.hpp" // for Component
|
||||||
|
#include "ftxui/component/component_options.hpp" // for InputState, InputOption
|
||||||
|
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||||
|
#include "ftxui/dom/elements.hpp" // for operator|=, Element, bgcolor, operator|, separatorEmpty, color, borderEmpty, separator, text, center, dim, hbox, vbox, border, borderDouble, borderRounded
|
||||||
|
|
||||||
|
int main(int argc, const char* argv[]) {
|
||||||
|
using namespace ftxui;
|
||||||
|
|
||||||
|
InputOption style_1 = InputOption::Default();
|
||||||
|
|
||||||
|
InputOption style_2 = InputOption::Spacious();
|
||||||
|
|
||||||
|
InputOption style_3 = InputOption::Spacious();
|
||||||
|
style_3.transform = [](InputState state) {
|
||||||
|
state.element |= borderEmpty;
|
||||||
|
|
||||||
|
if (state.is_placeholder) {
|
||||||
|
state.element |= dim;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.focused) {
|
||||||
|
state.element |= borderDouble;
|
||||||
|
state.element |= bgcolor(Color::White);
|
||||||
|
state.element |= color(Color::Black);
|
||||||
|
} else if (state.hovered) {
|
||||||
|
state.element |= borderRounded;
|
||||||
|
state.element |= bgcolor(LinearGradient(90, Color::Blue, Color::Red));
|
||||||
|
state.element |= color(Color::White);
|
||||||
|
} else {
|
||||||
|
state.element |= border;
|
||||||
|
state.element |= bgcolor(LinearGradient(0, Color::Blue, Color::Red));
|
||||||
|
state.element |= color(Color::White);
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.element;
|
||||||
|
};
|
||||||
|
|
||||||
|
InputOption style_4 = InputOption::Spacious();
|
||||||
|
style_4.transform = [](InputState state) {
|
||||||
|
state.element = hbox({
|
||||||
|
text("Theorem") | center | borderEmpty | bgcolor(Color::Red),
|
||||||
|
separatorEmpty(),
|
||||||
|
separator() | color(Color::White),
|
||||||
|
separatorEmpty(),
|
||||||
|
std::move(state.element),
|
||||||
|
});
|
||||||
|
|
||||||
|
state.element |= borderEmpty;
|
||||||
|
if (state.is_placeholder) {
|
||||||
|
state.element |= dim;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.focused) {
|
||||||
|
state.element |= bgcolor(Color::Black);
|
||||||
|
} else {
|
||||||
|
state.element |= bgcolor(Color::Blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.hovered) {
|
||||||
|
state.element |= bgcolor(Color::GrayDark);
|
||||||
|
}
|
||||||
|
|
||||||
|
return vbox({state.element, separatorEmpty()});
|
||||||
|
};
|
||||||
|
|
||||||
|
auto generateUiFromStyle = [&](InputOption style) {
|
||||||
|
auto first_name = new std::string(); // Leaked
|
||||||
|
auto middle_name = new std::string(); // Leaked
|
||||||
|
auto last_name = new std::string(); // Leaked
|
||||||
|
return Container::Vertical({
|
||||||
|
Input(first_name, "first name", style),
|
||||||
|
Input(middle_name, "middle name", style),
|
||||||
|
Input(last_name, "last name", style),
|
||||||
|
}) |
|
||||||
|
borderEmpty;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto ui = Container::Horizontal({
|
||||||
|
generateUiFromStyle(style_1),
|
||||||
|
generateUiFromStyle(style_2),
|
||||||
|
generateUiFromStyle(style_3),
|
||||||
|
generateUiFromStyle(style_4),
|
||||||
|
});
|
||||||
|
|
||||||
|
auto screen = ScreenInteractive::TerminalOutput();
|
||||||
|
screen.Loop(ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
35
examples/component/textarea.cpp
Normal file
35
examples/component/textarea.cpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#include <memory> // for allocator, __shared_ptr_access, shared_ptr
|
||||||
|
#include <string> // for string
|
||||||
|
|
||||||
|
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||||
|
#include "ftxui/component/component.hpp" // for Input, Renderer, ResizableSplitLeft
|
||||||
|
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
||||||
|
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||||
|
#include "ftxui/dom/elements.hpp" // for operator|, separator, text, Element, flex, vbox, border
|
||||||
|
|
||||||
|
int main(int argc, const char* argv[]) {
|
||||||
|
using namespace ftxui;
|
||||||
|
|
||||||
|
std::string content_1;
|
||||||
|
std::string content_2;
|
||||||
|
auto textarea_1 = Input(&content_1);
|
||||||
|
auto textarea_2 = Input(&content_2);
|
||||||
|
int size = 50;
|
||||||
|
auto layout = ResizableSplitLeft(textarea_1, textarea_2, &size);
|
||||||
|
|
||||||
|
auto component = Renderer(layout, [&] {
|
||||||
|
return vbox({
|
||||||
|
text("Input:"),
|
||||||
|
separator(),
|
||||||
|
layout->Render() | flex,
|
||||||
|
}) |
|
||||||
|
border;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto screen = ScreenInteractive::Fullscreen();
|
||||||
|
screen.Loop(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
@ -51,8 +51,9 @@ Component Checkbox(ConstStringRef label,
|
|||||||
bool* checked,
|
bool* checked,
|
||||||
Ref<CheckboxOption> option = CheckboxOption::Simple());
|
Ref<CheckboxOption> option = CheckboxOption::Simple());
|
||||||
|
|
||||||
|
Component Input(StringRef content, Ref<InputOption> option = {});
|
||||||
Component Input(StringRef content,
|
Component Input(StringRef content,
|
||||||
ConstStringRef placeholder,
|
StringRef placeholder,
|
||||||
Ref<InputOption> option = {});
|
Ref<InputOption> option = {});
|
||||||
|
|
||||||
Component Menu(ConstStringListRef entries,
|
Component Menu(ConstStringListRef entries,
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#include <ftxui/component/animation.hpp> // for Duration, QuadraticInOut, Function
|
#include <ftxui/component/animation.hpp> // for Duration, QuadraticInOut, Function
|
||||||
#include <ftxui/dom/direction.hpp> // for Direction, Direction::Left, Direction::Right, Direction::Down
|
#include <ftxui/dom/direction.hpp> // for Direction, Direction::Left, Direction::Right, Direction::Down
|
||||||
#include <ftxui/dom/elements.hpp> // for Element, separator
|
#include <ftxui/dom/elements.hpp> // for Element, separator
|
||||||
#include <ftxui/util/ref.hpp> // for Ref, ConstRef
|
#include <ftxui/util/ref.hpp> // for Ref, ConstRef, StringRef
|
||||||
#include <functional> // for function
|
#include <functional> // for function
|
||||||
#include <optional> // for optional
|
#include <optional> // for optional
|
||||||
#include <string> // for string
|
#include <string> // for string
|
||||||
@ -134,20 +134,42 @@ struct CheckboxOption {
|
|||||||
std::function<void()> on_change = [] {};
|
std::function<void()> on_change = [] {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// @brief Used to define style for the Input component.
|
||||||
|
struct InputState {
|
||||||
|
Element element;
|
||||||
|
bool hovered; /// < Whether the input is hovered by the mouse.
|
||||||
|
bool focused; /// < Whether the input is focused by the user.
|
||||||
|
bool is_placeholder; /// < Whether the input is empty and displaying the
|
||||||
|
/// < placeholder.
|
||||||
|
};
|
||||||
|
|
||||||
/// @brief Option for the Input component.
|
/// @brief Option for the Input component.
|
||||||
/// @ingroup component
|
/// @ingroup component
|
||||||
struct InputOption {
|
struct InputOption {
|
||||||
|
// A set of predefined styles:
|
||||||
|
|
||||||
|
/// @brief Create the default input style:
|
||||||
|
static InputOption Default();
|
||||||
|
/// @brief A white on black style with high margins:
|
||||||
|
static InputOption Spacious();
|
||||||
|
/// @brief A style with a border:
|
||||||
|
static InputOption Arthur();
|
||||||
|
|
||||||
|
/// The content of the input when it's empty.
|
||||||
|
StringRef placeholder = "";
|
||||||
|
|
||||||
|
// Style:
|
||||||
|
std::function<Element(InputState)> transform;
|
||||||
|
Ref<bool> password = false; /// < Obscure the input content using '*'.
|
||||||
|
Ref<bool> multiline = true; /// < Whether the input can be multiline.
|
||||||
|
|
||||||
/// Called when the content changes.
|
/// Called when the content changes.
|
||||||
std::function<void()> on_change = [] {};
|
std::function<void()> on_change = [] {};
|
||||||
/// Called when the user presses enter.
|
/// Called when the user presses enter.
|
||||||
std::function<void()> on_enter = [] {};
|
std::function<void()> on_enter = [] {};
|
||||||
|
|
||||||
/// Obscure the input content using '*'.
|
// The char position of the cursor:
|
||||||
Ref<bool> password = false;
|
Ref<int> cursor_position = 0;
|
||||||
|
|
||||||
/// When set different from -1, this attributes is used to store the cursor
|
|
||||||
/// position.
|
|
||||||
Ref<int> cursor_position = -1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @brief Option for the Radiobox component.
|
/// @brief Option for the Radiobox component.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#ifndef FTXUI_COMPONENT_RECEIVER_HPP_
|
#ifndef FTXUI_COMPONENT_RECEIVER_HPP_
|
||||||
#define FTXUI_COMPONENT_RECEIVER_HPP_
|
#define FTXUI_COMPONENT_RECEIVER_HPP_
|
||||||
|
|
||||||
#include <algorithm> // for copy
|
#include <algorithm> // for copy, max
|
||||||
#include <atomic> // for atomic, __atomic_base
|
#include <atomic> // for atomic, __atomic_base
|
||||||
#include <condition_variable> // for condition_variable
|
#include <condition_variable> // for condition_variable
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
@ -15,44 +15,10 @@ std::wstring to_wstring(T s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int string_width(const std::string&);
|
int string_width(const std::string&);
|
||||||
|
|
||||||
// Split the string into a its glyphs. An empty one is inserted ater fullwidth
|
// Split the string into a its glyphs. An empty one is inserted ater fullwidth
|
||||||
// ones.
|
// ones.
|
||||||
std::vector<std::string> Utf8ToGlyphs(const std::string& input);
|
std::vector<std::string> Utf8ToGlyphs(const std::string& input);
|
||||||
// If |input| was an array of glyphs, this returns the number of char to eat
|
|
||||||
// before reaching the glyph at index |glyph_index|.
|
|
||||||
int GlyphPosition(const std::string& input,
|
|
||||||
size_t glyph_index,
|
|
||||||
size_t start = 0);
|
|
||||||
// Returns the number of glyphs in |input|.
|
|
||||||
int GlyphCount(const std::string& input);
|
|
||||||
|
|
||||||
// Properties from:
|
|
||||||
// https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/WordBreakProperty.txt
|
|
||||||
enum class WordBreakProperty {
|
|
||||||
ALetter,
|
|
||||||
CR,
|
|
||||||
Double_Quote,
|
|
||||||
Extend,
|
|
||||||
ExtendNumLet,
|
|
||||||
Format,
|
|
||||||
Hebrew_Letter,
|
|
||||||
Katakana,
|
|
||||||
LF,
|
|
||||||
MidLetter,
|
|
||||||
MidNum,
|
|
||||||
MidNumLet,
|
|
||||||
Newline,
|
|
||||||
Numeric,
|
|
||||||
Regional_Indicator,
|
|
||||||
Single_Quote,
|
|
||||||
WSegSpace,
|
|
||||||
ZWJ,
|
|
||||||
};
|
|
||||||
WordBreakProperty CodepointToWordBreakProperty(uint32_t codepoint);
|
|
||||||
std::vector<WordBreakProperty> Utf8ToWordBreakProperty(
|
|
||||||
const std::string& input);
|
|
||||||
|
|
||||||
bool IsWordBreakingCharacter(const std::string& input, size_t glyph_index);
|
|
||||||
|
|
||||||
// Map every cells drawn by |input| to their corresponding Glyphs. Half-size
|
// Map every cells drawn by |input| to their corresponding Glyphs. Half-size
|
||||||
// Glyphs takes one cell, full-size Glyphs take two cells.
|
// Glyphs takes one cell, full-size Glyphs take two cells.
|
||||||
|
@ -48,6 +48,7 @@ class StringRef {
|
|||||||
StringRef(const wchar_t* ref) : StringRef(to_string(std::wstring(ref))) {}
|
StringRef(const wchar_t* ref) : StringRef(to_string(std::wstring(ref))) {}
|
||||||
StringRef(const char* ref) : StringRef(std::string(ref)) {}
|
StringRef(const char* ref) : StringRef(std::string(ref)) {}
|
||||||
std::string& operator*() { return address_ ? *address_ : owned_; }
|
std::string& operator*() { return address_ ? *address_ : owned_; }
|
||||||
|
std::string& operator()() { return address_ ? *address_ : owned_; }
|
||||||
std::string* operator->() { return address_ ? address_ : &owned_; }
|
std::string* operator->() { return address_ ? address_ : &owned_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, EXPECT_EQ, Test, EXPECT_FALSE, EXPECT_TRUE, TestInfo (ptr only), TEST
|
|
||||||
#include <chrono> // for operator""s, chrono_literals
|
#include <chrono> // for operator""s, chrono_literals
|
||||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||||
#include <string> // for string
|
#include <string> // for string
|
||||||
@ -12,6 +11,7 @@
|
|||||||
#include "ftxui/dom/node.hpp" // for Render
|
#include "ftxui/dom/node.hpp" // for Render
|
||||||
#include "ftxui/screen/screen.hpp" // for Screen
|
#include "ftxui/screen/screen.hpp" // for Screen
|
||||||
#include "ftxui/screen/terminal.hpp" // for SetColorSupport, Color, TrueColor
|
#include "ftxui/screen/terminal.hpp" // for SetColorSupport, Color, TrueColor
|
||||||
|
#include "gtest/gtest.h" // for AssertionResult, Message, TestPartResult, EXPECT_EQ, Test, EXPECT_FALSE, EXPECT_TRUE, TEST
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
#include <gtest/gtest.h>
|
|
||||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||||
|
#include <string> // for string
|
||||||
|
|
||||||
#include "ftxui/component/component.hpp" // for Collapsible, Renderer
|
#include "ftxui/component/component.hpp" // for Collapsible, Renderer
|
||||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||||
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown
|
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown
|
||||||
#include "ftxui/dom/elements.hpp" // for text, Element
|
#include "ftxui/dom/elements.hpp" // for Element, text
|
||||||
#include "ftxui/dom/node.hpp" // for Render
|
#include "ftxui/dom/node.hpp" // for Render
|
||||||
#include "ftxui/screen/screen.hpp" // for Screen
|
#include "ftxui/screen/screen.hpp" // for Screen
|
||||||
|
#include "gtest/gtest.h" // for AssertionResult, Message, Test, TestPartResult, EXPECT_EQ, EXPECT_FALSE, EXPECT_TRUE, TEST
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
#include "ftxui/component/component_options.hpp"
|
#include "ftxui/component/component_options.hpp"
|
||||||
|
|
||||||
#include <ftxui/screen/color.hpp> // for Color, Color::Black, Color::White, Color::GrayDark, Color::GrayLight
|
#include <ftxui/dom/linear_gradient.hpp> // for LinearGradient
|
||||||
|
#include <ftxui/screen/color.hpp> // for Color, Color::White, Color::Black, Color::GrayDark, Color::Blue, Color::GrayLight, Color::Red
|
||||||
#include <memory> // for shared_ptr
|
#include <memory> // for shared_ptr
|
||||||
#include <utility> // for move
|
#include <utility> // for move
|
||||||
|
|
||||||
#include "ftxui/component/animation.hpp" // for Function, Duration
|
#include "ftxui/component/animation.hpp" // for Function, Duration
|
||||||
#include "ftxui/dom/elements.hpp" // for operator|=, text, Element, bold, inverted, operator|, dim, hbox, automerge, borderEmpty, borderLight
|
#include "ftxui/dom/elements.hpp" // for operator|=, Element, text, bgcolor, inverted, bold, dim, operator|, color, borderEmpty, hbox, automerge, border, borderLight
|
||||||
|
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
|
||||||
@ -257,6 +258,77 @@ RadioboxOption RadioboxOption::Simple() {
|
|||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
InputOption InputOption::Default() {
|
||||||
|
InputOption option;
|
||||||
|
option.transform = [](InputState state) {
|
||||||
|
state.element |= color(Color::White);
|
||||||
|
|
||||||
|
if (state.is_placeholder) {
|
||||||
|
state.element |= dim;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.focused) {
|
||||||
|
state.element |= inverted;
|
||||||
|
} else if (state.hovered) {
|
||||||
|
state.element |= bgcolor(Color::GrayDark);
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.element;
|
||||||
|
};
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
InputOption InputOption::Spacious() {
|
||||||
|
InputOption option;
|
||||||
|
option.transform = [](InputState state) {
|
||||||
|
state.element |= borderEmpty;
|
||||||
|
state.element |= color(Color::White);
|
||||||
|
|
||||||
|
if (state.is_placeholder) {
|
||||||
|
state.element |= dim;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.focused) {
|
||||||
|
state.element |= bgcolor(Color::Black);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.hovered) {
|
||||||
|
state.element |= bgcolor(Color::GrayDark);
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.element;
|
||||||
|
};
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
InputOption InputOption::Arthur() {
|
||||||
|
InputOption option;
|
||||||
|
option.transform = [](InputState state) {
|
||||||
|
state.element |= borderEmpty;
|
||||||
|
state.element |= color(Color::White);
|
||||||
|
|
||||||
|
if (state.is_placeholder) {
|
||||||
|
state.element |= dim;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.focused) {
|
||||||
|
state.element |= bgcolor(Color::Black);
|
||||||
|
} else {
|
||||||
|
state.element |= bgcolor(LinearGradient(0, Color::Blue, Color::Red));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.hovered) {
|
||||||
|
state.element |= bgcolor(Color::GrayDark);
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.element;
|
||||||
|
};
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ftxui
|
} // namespace ftxui
|
||||||
|
|
||||||
// Copyright 2022 Arthur Sonzogni. All rights reserved.
|
// Copyright 2022 Arthur Sonzogni. All rights reserved.
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
#include <gtest/gtest.h>
|
|
||||||
#include <memory> // for shared_ptr, __shared_ptr_access, allocator, __shared_ptr_access<>::element_type, make_shared
|
#include <memory> // for shared_ptr, __shared_ptr_access, allocator, __shared_ptr_access<>::element_type, make_shared
|
||||||
|
#include <string> // for string
|
||||||
|
|
||||||
#include "ftxui/component/component.hpp" // for Make
|
#include "ftxui/component/component.hpp" // for Make
|
||||||
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
||||||
|
#include "gtest/gtest.h" // for Message, TestPartResult, EXPECT_EQ, Test, AssertionResult, TEST, EXPECT_FALSE
|
||||||
|
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
#include <gtest/gtest.h>
|
|
||||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||||
|
#include <string> // for string
|
||||||
|
|
||||||
#include "ftxui/component/component.hpp" // for Horizontal, Vertical, Button, Tab
|
#include "ftxui/component/component.hpp" // for Horizontal, Vertical, Button, Tab
|
||||||
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
||||||
#include "ftxui/component/event.hpp" // for Event, Event::Tab, Event::TabReverse, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp
|
#include "ftxui/component/event.hpp" // for Event, Event::Tab, Event::TabReverse, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp
|
||||||
|
#include "gtest/gtest.h" // for AssertionResult, Message, TestPartResult, EXPECT_EQ, EXPECT_FALSE, Test, EXPECT_TRUE, TEST
|
||||||
|
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, EXPECT_FALSE, EXPECT_EQ, Test, EXPECT_TRUE, TestInfo (ptr only), TEST
|
|
||||||
#include <ftxui/dom/elements.hpp> // for Element, text
|
#include <ftxui/dom/elements.hpp> // for Element, text
|
||||||
#include <memory> // for shared_ptr, __shared_ptr_access, allocator
|
#include <memory> // for shared_ptr, __shared_ptr_access, allocator
|
||||||
|
#include <string> // for string
|
||||||
|
|
||||||
#include "ftxui/component/component.hpp" // for Hoverable, Horizontal, operator|=, Renderer
|
#include "ftxui/component/component.hpp" // for Hoverable, Horizontal, operator|=, Renderer
|
||||||
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
||||||
@ -8,6 +8,7 @@
|
|||||||
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Released
|
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Released
|
||||||
#include "ftxui/dom/node.hpp" // for Render
|
#include "ftxui/dom/node.hpp" // for Render
|
||||||
#include "ftxui/screen/screen.hpp" // for Screen
|
#include "ftxui/screen/screen.hpp" // for Screen
|
||||||
|
#include "gtest/gtest.h" // for AssertionResult, Message, TestPartResult, EXPECT_FALSE, EXPECT_EQ, Test, EXPECT_TRUE, TEST
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
#include <stdint.h> // for uint32_t
|
||||||
#include <algorithm> // for max, min
|
#include <algorithm> // for max, min
|
||||||
#include <cstddef> // for size_t
|
#include <cstddef> // for size_t
|
||||||
#include <functional> // for function
|
#include <functional> // for function
|
||||||
#include <memory> // for shared_ptr
|
#include <memory> // for allocator, shared_ptr, allocator_traits<>::value_type
|
||||||
#include <string> // for string, allocator
|
#include <sstream> // for basic_istream, stringstream
|
||||||
|
#include <string> // for string, basic_string, operator==, getline
|
||||||
#include <utility> // for move
|
#include <utility> // for move
|
||||||
#include <vector> // for vector
|
#include <vector> // for vector
|
||||||
|
|
||||||
@ -10,23 +12,44 @@
|
|||||||
#include "ftxui/component/component.hpp" // for Make, Input
|
#include "ftxui/component/component.hpp" // for Make, Input
|
||||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||||
#include "ftxui/component/component_options.hpp" // for InputOption
|
#include "ftxui/component/component_options.hpp" // for InputOption
|
||||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowLeft, Event::ArrowLeftCtrl, Event::ArrowRight, Event::ArrowRightCtrl, Event::Backspace, Event::Custom, Event::Delete, Event::End, Event::Home, Event::Return
|
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowLeft, Event::ArrowLeftCtrl, Event::ArrowRight, Event::ArrowRightCtrl, Event::ArrowUp, Event::Backspace, Event::Delete, Event::End, Event::Home, Event::Return
|
||||||
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
|
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
|
||||||
#include "ftxui/component/screen_interactive.hpp" // for Component
|
#include "ftxui/component/screen_interactive.hpp" // for Component
|
||||||
#include "ftxui/dom/elements.hpp" // for operator|, text, Element, reflect, operator|=, flex, inverted, hbox, size, bold, dim, focus, focusCursorBarBlinking, frame, select, Decorator, EQUAL, HEIGHT
|
#include "ftxui/dom/elements.hpp" // for operator|, reflect, text, Element, xflex, hbox, Elements, frame, operator|=, vbox, focus, focusCursorBarBlinking, select
|
||||||
#include "ftxui/screen/box.hpp" // for Box
|
#include "ftxui/screen/box.hpp" // for Box
|
||||||
#include "ftxui/screen/string.hpp" // for GlyphPosition, WordBreakProperty, GlyphCount, Utf8ToWordBreakProperty, CellToGlyphIndex, WordBreakProperty::ALetter, WordBreakProperty::CR, WordBreakProperty::Double_Quote, WordBreakProperty::Extend, WordBreakProperty::ExtendNumLet, WordBreakProperty::Format, WordBreakProperty::Hebrew_Letter, WordBreakProperty::Katakana, WordBreakProperty::LF, WordBreakProperty::MidLetter, WordBreakProperty::MidNum, WordBreakProperty::MidNumLet, WordBreakProperty::Newline, WordBreakProperty::Numeric, WordBreakProperty::Regional_Indicator, WordBreakProperty::Single_Quote, WordBreakProperty::WSegSpace, WordBreakProperty::ZWJ
|
#include "ftxui/screen/string.hpp" // for string_width
|
||||||
|
#include "ftxui/screen/string_internal.hpp" // for GlyphNext, GlyphPrevious, WordBreakProperty, EatCodePoint, CodepointToWordBreakProperty, IsFullWidth, WordBreakProperty::ALetter, WordBreakProperty::CR, WordBreakProperty::Double_Quote, WordBreakProperty::Extend, WordBreakProperty::ExtendNumLet, WordBreakProperty::Format, WordBreakProperty::Hebrew_Letter, WordBreakProperty::Katakana, WordBreakProperty::LF, WordBreakProperty::MidLetter, WordBreakProperty::MidNum, WordBreakProperty::MidNumLet, WordBreakProperty::Newline, WordBreakProperty::Numeric, WordBreakProperty::Regional_Indicator, WordBreakProperty::Single_Quote, WordBreakProperty::WSegSpace, WordBreakProperty::ZWJ
|
||||||
#include "ftxui/screen/util.hpp" // for clamp
|
#include "ftxui/screen/util.hpp" // for clamp
|
||||||
#include "ftxui/util/ref.hpp" // for StringRef, Ref, ConstStringRef
|
#include "ftxui/util/ref.hpp" // for StringRef, Ref
|
||||||
|
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Group together several propertiej so they appear to form a similar group.
|
std::vector<std::string> Split(const std::string& input) {
|
||||||
// For instance, letters are grouped with number and form a single word.
|
std::vector<std::string> output;
|
||||||
bool IsWordCharacter(WordBreakProperty property) {
|
std::stringstream ss(input);
|
||||||
switch (property) {
|
std::string line;
|
||||||
|
while (std::getline(ss, line)) {
|
||||||
|
output.push_back(line);
|
||||||
|
}
|
||||||
|
if (input.back() == '\n') {
|
||||||
|
output.push_back("");
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GlyphWidth(const std::string& input, size_t iter) {
|
||||||
|
uint32_t ucs = 0;
|
||||||
|
if (!EatCodePoint(input, iter, &iter, &ucs))
|
||||||
|
return 0;
|
||||||
|
if (IsFullWidth(ucs))
|
||||||
|
return 2;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsWordCodePoint(uint32_t codepoint) {
|
||||||
|
switch (CodepointToWordBreakProperty(codepoint)) {
|
||||||
case WordBreakProperty::ALetter:
|
case WordBreakProperty::ALetter:
|
||||||
case WordBreakProperty::Hebrew_Letter:
|
case WordBreakProperty::Hebrew_Letter:
|
||||||
case WordBreakProperty::Katakana:
|
case WordBreakProperty::Katakana:
|
||||||
@ -42,7 +65,7 @@ bool IsWordCharacter(WordBreakProperty property) {
|
|||||||
case WordBreakProperty::Newline:
|
case WordBreakProperty::Newline:
|
||||||
case WordBreakProperty::Single_Quote:
|
case WordBreakProperty::Single_Quote:
|
||||||
case WordBreakProperty::WSegSpace:
|
case WordBreakProperty::WSegSpace:
|
||||||
// Unsure:
|
// Unexpected/Unsure
|
||||||
case WordBreakProperty::Extend:
|
case WordBreakProperty::Extend:
|
||||||
case WordBreakProperty::ExtendNumLet:
|
case WordBreakProperty::ExtendNumLet:
|
||||||
case WordBreakProperty::Format:
|
case WordBreakProperty::Format:
|
||||||
@ -50,216 +73,398 @@ bool IsWordCharacter(WordBreakProperty property) {
|
|||||||
case WordBreakProperty::ZWJ:
|
case WordBreakProperty::ZWJ:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true; // NOT_REACHED();
|
return false; // NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string PasswordField(size_t size) {
|
bool IsWordCharacter(const std::string& input, size_t iter) {
|
||||||
std::string out;
|
uint32_t ucs = 0;
|
||||||
out.reserve(2 * size);
|
if (!EatCodePoint(input, iter, &iter, &ucs)) {
|
||||||
while (size--) {
|
return false;
|
||||||
out += "•";
|
|
||||||
}
|
}
|
||||||
return out;
|
|
||||||
|
return IsWordCodePoint(ucs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// An input box. The user can type text into it.
|
// An input box. The user can type text into it.
|
||||||
class InputBase : public ComponentBase {
|
class InputBase : public ComponentBase {
|
||||||
public:
|
public:
|
||||||
InputBase(StringRef content,
|
// NOLINTNEXTLINE
|
||||||
ConstStringRef placeholder,
|
InputBase(StringRef content, Ref<InputOption> option)
|
||||||
Ref<InputOption> option)
|
: content_(std::move(content)), option_(std::move(option)) {}
|
||||||
: content_(std::move(content)),
|
|
||||||
placeholder_(std::move(placeholder)),
|
|
||||||
option_(std::move(option)) {}
|
|
||||||
|
|
||||||
int cursor_position_internal_ = 0;
|
|
||||||
int& cursor_position() {
|
|
||||||
int& opt = option_->cursor_position();
|
|
||||||
if (opt != -1) {
|
|
||||||
return opt;
|
|
||||||
}
|
|
||||||
return cursor_position_internal_;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private:
|
||||||
// Component implementation:
|
// Component implementation:
|
||||||
Element Render() override {
|
Element Render() override {
|
||||||
std::string password_content;
|
|
||||||
if (option_->password()) {
|
|
||||||
password_content = PasswordField(content_->size());
|
|
||||||
}
|
|
||||||
const std::string& content =
|
|
||||||
option_->password() ? password_content : *content_;
|
|
||||||
|
|
||||||
const int size = GlyphCount(content);
|
|
||||||
|
|
||||||
cursor_position() = std::max(0, std::min<int>(size, cursor_position()));
|
|
||||||
auto main_decorator = flex | ftxui::size(HEIGHT, EQUAL, 1);
|
|
||||||
const bool is_focused = Focused();
|
const bool is_focused = Focused();
|
||||||
|
const auto focused =
|
||||||
|
(is_focused || hovered_) ? focusCursorBarBlinking : select;
|
||||||
|
|
||||||
|
auto transform = option_->transform ? option_->transform
|
||||||
|
: InputOption::Default().transform;
|
||||||
|
|
||||||
// placeholder.
|
// placeholder.
|
||||||
if (size == 0) {
|
if (content_->empty()) {
|
||||||
auto element = text(*placeholder_) | dim | main_decorator | reflect(box_);
|
auto element = text(option_->placeholder()) | xflex | frame;
|
||||||
if (is_focused) {
|
if (is_focused) {
|
||||||
element |= focus;
|
element |= focus;
|
||||||
}
|
}
|
||||||
if (hovered_ || is_focused) {
|
|
||||||
element |= inverted;
|
|
||||||
}
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not focused.
|
return transform({
|
||||||
if (!is_focused) {
|
std::move(element), hovered_, is_focused,
|
||||||
auto element = text(content) | main_decorator | reflect(box_);
|
true // placeholder
|
||||||
if (hovered_) {
|
|
||||||
element |= inverted;
|
|
||||||
}
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int index_before_cursor = GlyphPosition(content, cursor_position());
|
|
||||||
const int index_after_cursor =
|
|
||||||
GlyphPosition(content, 1, index_before_cursor);
|
|
||||||
const std::string part_before_cursor =
|
|
||||||
content.substr(0, index_before_cursor);
|
|
||||||
std::string part_at_cursor = " ";
|
|
||||||
if (cursor_position() < size) {
|
|
||||||
part_at_cursor = content.substr(index_before_cursor,
|
|
||||||
index_after_cursor - index_before_cursor);
|
|
||||||
}
|
|
||||||
const std::string part_after_cursor = content.substr(index_after_cursor);
|
|
||||||
auto focused = (is_focused || hovered_) ? focusCursorBarBlinking : select;
|
|
||||||
return hbox({
|
|
||||||
text(part_before_cursor),
|
|
||||||
text(part_at_cursor) | focused | reflect(cursor_box_),
|
|
||||||
text(part_after_cursor),
|
|
||||||
}) |
|
}) |
|
||||||
flex | frame | bold | main_decorator | reflect(box_);
|
reflect(box_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Elements elements;
|
||||||
|
std::vector<std::string> lines = Split(*content_);
|
||||||
|
|
||||||
|
int& cursor_position = option_->cursor_position();
|
||||||
|
cursor_position = util::clamp(cursor_position, 0, (int)content_->size());
|
||||||
|
|
||||||
|
// Find the line and index of the cursor.
|
||||||
|
int cursor_line = 0;
|
||||||
|
int cursor_char_index = cursor_position;
|
||||||
|
for (const auto& line : lines) {
|
||||||
|
if (cursor_char_index <= (int)line.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor_char_index -= line.size() + 1;
|
||||||
|
cursor_line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lines.empty()) {
|
||||||
|
elements.push_back(text("") | focused);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < lines.size(); ++i) {
|
||||||
|
const std::string& line = lines[i];
|
||||||
|
|
||||||
|
// This is not the cursor line.
|
||||||
|
if (int(i) != cursor_line) {
|
||||||
|
elements.push_back(Text(line));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The cursor is at the end of the line.
|
||||||
|
if (cursor_char_index >= (int)line.size()) {
|
||||||
|
elements.push_back(hbox({
|
||||||
|
Text(line),
|
||||||
|
text(" ") | focused | reflect(cursor_box_),
|
||||||
|
}) |
|
||||||
|
xflex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The cursor is on this line.
|
||||||
|
const int glyph_start = cursor_char_index;
|
||||||
|
const int glyph_end = GlyphNext(line, glyph_start);
|
||||||
|
const std::string part_before_cursor = line.substr(0, glyph_start);
|
||||||
|
const std::string part_at_cursor =
|
||||||
|
line.substr(glyph_start, glyph_end - glyph_start);
|
||||||
|
const std::string part_after_cursor = line.substr(glyph_end);
|
||||||
|
auto element = hbox({
|
||||||
|
Text(part_before_cursor),
|
||||||
|
Text(part_at_cursor) | focused | reflect(cursor_box_),
|
||||||
|
Text(part_after_cursor),
|
||||||
|
}) |
|
||||||
|
xflex;
|
||||||
|
elements.push_back(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto element = vbox(std::move(elements)) | frame;
|
||||||
|
return transform({
|
||||||
|
std::move(element), hovered_, is_focused,
|
||||||
|
false // placeholder
|
||||||
|
}) |
|
||||||
|
xflex | reflect(box_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Element Text(const std::string& input) {
|
||||||
|
if (!option_->password()) {
|
||||||
|
return text(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string out;
|
||||||
|
out.reserve(10 + input.size() * 3 / 2);
|
||||||
|
for (size_t i = 0; i < input.size(); ++i) {
|
||||||
|
out += "•";
|
||||||
|
}
|
||||||
|
return text(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HandleBackspace() {
|
||||||
|
int& cursor_position = option_->cursor_position();
|
||||||
|
if (cursor_position == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const size_t start = GlyphPrevious(content_(), cursor_position);
|
||||||
|
const size_t end = cursor_position;
|
||||||
|
content_->erase(start, end - start);
|
||||||
|
cursor_position = start;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HandleDelete() {
|
||||||
|
int& cursor_position = option_->cursor_position();
|
||||||
|
if (cursor_position == (int)content_->size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const size_t start = cursor_position;
|
||||||
|
const size_t end = GlyphNext(content_(), cursor_position);
|
||||||
|
content_->erase(start, end - start);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HandleArrowLeft() {
|
||||||
|
int& cursor_position = option_->cursor_position();
|
||||||
|
if (cursor_position == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor_position = GlyphPrevious(content_(), cursor_position);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HandleArrowRight() {
|
||||||
|
int& cursor_position = option_->cursor_position();
|
||||||
|
if (cursor_position == (int)content_->size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor_position = GlyphNext(content_(), cursor_position);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t CursorColumn() {
|
||||||
|
int& cursor_position = option_->cursor_position();
|
||||||
|
size_t iter = cursor_position;
|
||||||
|
int width = 0;
|
||||||
|
while (true) {
|
||||||
|
if (iter == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
iter = GlyphPrevious(content_(), iter);
|
||||||
|
if (content_()[iter] == '\n') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
width += GlyphWidth(content_(), iter);
|
||||||
|
}
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the cursor `columns` on the right, if possible.
|
||||||
|
void MoveCursorColumn(int columns) {
|
||||||
|
int& cursor_position = option_->cursor_position();
|
||||||
|
while (columns > 0) {
|
||||||
|
if (cursor_position == (int)content_().size() ||
|
||||||
|
content_()[cursor_position] == '\n') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
columns -= GlyphWidth(content_(), cursor_position);
|
||||||
|
cursor_position = GlyphNext(content_(), cursor_position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HandleArrowUp() {
|
||||||
|
int& cursor_position = option_->cursor_position();
|
||||||
|
if (cursor_position == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t columns = CursorColumn();
|
||||||
|
|
||||||
|
// Move cursor at the beginning of 2 lines above.
|
||||||
|
while (true) {
|
||||||
|
if (cursor_position == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
size_t previous = GlyphPrevious(content_(), cursor_position);
|
||||||
|
if (content_()[previous] == '\n') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cursor_position = previous;
|
||||||
|
}
|
||||||
|
cursor_position = GlyphPrevious(content_(), cursor_position);
|
||||||
|
while (true) {
|
||||||
|
if (cursor_position == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
size_t previous = GlyphPrevious(content_(), cursor_position);
|
||||||
|
if (content_()[previous] == '\n') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cursor_position = previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveCursorColumn(columns);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HandleArrowDown() {
|
||||||
|
int& cursor_position = option_->cursor_position();
|
||||||
|
if (cursor_position == (int)content_->size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t columns = CursorColumn();
|
||||||
|
|
||||||
|
// Move cursor at the beginning of the next line
|
||||||
|
while (true) {
|
||||||
|
if (content_()[cursor_position] == '\n') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cursor_position = GlyphNext(content_(), cursor_position);
|
||||||
|
if (cursor_position == (int)content_().size()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor_position = GlyphNext(content_(), cursor_position);
|
||||||
|
|
||||||
|
MoveCursorColumn(columns);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HandleHome() {
|
||||||
|
int& cursor_position = option_->cursor_position();
|
||||||
|
cursor_position = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HandleEnd() {
|
||||||
|
int& cursor_position = option_->cursor_position();
|
||||||
|
cursor_position = content_->size();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HandleReturn() {
|
||||||
|
int& cursor_position = option_->cursor_position();
|
||||||
|
content_->insert(cursor_position, "\n");
|
||||||
|
cursor_position++;
|
||||||
|
option_->on_change();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HandleCharacter(const std::string& character) {
|
||||||
|
if (character == "\n" && !option_->multiline()) {
|
||||||
|
option_->on_enter();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int& cursor_position = option_->cursor_position();
|
||||||
|
content_->insert(cursor_position, character);
|
||||||
|
cursor_position += character.size();
|
||||||
|
option_->on_change();
|
||||||
|
|
||||||
|
if (character == "\n") {
|
||||||
|
option_->on_enter();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OnEvent(Event event) override {
|
bool OnEvent(Event event) override {
|
||||||
cursor_position() =
|
int& cursor_position = option_->cursor_position();
|
||||||
std::max(0, std::min<int>((int)content_->size(), cursor_position()));
|
cursor_position = util::clamp(cursor_position, 0, (int)content_->size());
|
||||||
|
|
||||||
|
if (event.is_character()) {
|
||||||
|
return HandleCharacter(event.character());
|
||||||
|
}
|
||||||
if (event.is_mouse()) {
|
if (event.is_mouse()) {
|
||||||
return OnMouseEvent(event);
|
return HandleMouse(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backspace.
|
|
||||||
if (event == Event::Backspace) {
|
if (event == Event::Backspace) {
|
||||||
if (cursor_position() == 0) {
|
return HandleBackspace();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
const size_t start = GlyphPosition(*content_, cursor_position() - 1);
|
|
||||||
const size_t end = GlyphPosition(*content_, cursor_position());
|
|
||||||
content_->erase(start, end - start);
|
|
||||||
cursor_position()--;
|
|
||||||
option_->on_change();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete
|
|
||||||
if (event == Event::Delete) {
|
if (event == Event::Delete) {
|
||||||
if (cursor_position() == int(content_->size())) {
|
return HandleDelete();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
const size_t start = GlyphPosition(*content_, cursor_position());
|
if (event == Event::ArrowLeft) {
|
||||||
const size_t end = GlyphPosition(*content_, cursor_position() + 1);
|
return HandleArrowLeft();
|
||||||
content_->erase(start, end - start);
|
|
||||||
option_->on_change();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
if (event == Event::ArrowRight) {
|
||||||
// Enter.
|
return HandleArrowRight();
|
||||||
if (event == Event::Return) {
|
|
||||||
option_->on_enter();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
if (event == Event::ArrowUp) {
|
||||||
if (event == Event::Custom) {
|
return HandleArrowUp();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
if (event == Event::ArrowDown) {
|
||||||
// Arrow
|
return HandleArrowDown();
|
||||||
if (event == Event::ArrowLeft && cursor_position() > 0) {
|
|
||||||
cursor_position()--;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
if (event == Event::Home) {
|
||||||
if (event == Event::ArrowRight &&
|
return HandleHome();
|
||||||
cursor_position() < static_cast<int>(content_->size())) {
|
}
|
||||||
cursor_position()++;
|
if (event == Event::End) {
|
||||||
return true;
|
return HandleEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
// CTRL + Arrow:
|
|
||||||
if (event == Event::ArrowLeftCtrl) {
|
if (event == Event::ArrowLeftCtrl) {
|
||||||
HandleLeftCtrl();
|
return HandleLeftCtrl();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
if (event == Event::ArrowRightCtrl) {
|
if (event == Event::ArrowRightCtrl) {
|
||||||
HandleRightCtrl();
|
return HandleRightCtrl();
|
||||||
return true;
|
}
|
||||||
|
if (event == Event::Return) {
|
||||||
|
return HandleReturn();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event == Event::Home) {
|
|
||||||
cursor_position() = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event == Event::End) {
|
|
||||||
cursor_position() = GlyphCount(*content_);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Content
|
|
||||||
if (event.is_character()) {
|
|
||||||
const size_t start = GlyphPosition(*content_, cursor_position());
|
|
||||||
content_->insert(start, event.character());
|
|
||||||
cursor_position()++;
|
|
||||||
option_->on_change();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
bool HandleLeftCtrl() {
|
||||||
void HandleLeftCtrl() {
|
int& cursor_position = option_->cursor_position();
|
||||||
auto properties = Utf8ToWordBreakProperty(*content_);
|
if (cursor_position == 0) {
|
||||||
|
return false;
|
||||||
// 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 it not a word.
|
||||||
|
while (cursor_position) {
|
||||||
|
size_t previous = GlyphPrevious(content_(), cursor_position);
|
||||||
|
if (IsWordCharacter(content_(), previous)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cursor_position = previous;
|
||||||
|
}
|
||||||
// Move left, as long as left is a word character:
|
// Move left, as long as left is a word character:
|
||||||
while (cursor_position() > 0 &&
|
while (cursor_position) {
|
||||||
IsWordCharacter(properties[cursor_position() - 1])) {
|
size_t previous = GlyphPrevious(content_(), cursor_position);
|
||||||
cursor_position()--;
|
if (!IsWordCharacter(content_(), previous)) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
cursor_position = previous;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleRightCtrl() {
|
bool HandleRightCtrl() {
|
||||||
auto properties = Utf8ToWordBreakProperty(*content_);
|
int& cursor_position = option_->cursor_position();
|
||||||
const int max = properties.size();
|
if (cursor_position == (int)content_().size()) {
|
||||||
|
return false;
|
||||||
// Move right, as long as right is not a word character.
|
|
||||||
while (cursor_position() < max &&
|
|
||||||
!IsWordCharacter(properties[cursor_position()])) {
|
|
||||||
cursor_position()++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move right, until entering a word.
|
||||||
|
while (cursor_position < (int)content_().size()) {
|
||||||
|
cursor_position = GlyphNext(content_(), cursor_position);
|
||||||
|
if (IsWordCharacter(content_(), cursor_position)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Move right, as long as right is a word character:
|
// Move right, as long as right is a word character:
|
||||||
while (cursor_position() < max &&
|
while (cursor_position < (int)content_().size()) {
|
||||||
IsWordCharacter(properties[cursor_position()])) {
|
size_t next = GlyphNext(content_(), cursor_position);
|
||||||
cursor_position()++;
|
if (!IsWordCharacter(content_(), cursor_position)) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
cursor_position = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OnMouseEvent(Event event) {
|
return true;
|
||||||
hovered_ =
|
}
|
||||||
box_.Contain(event.mouse().x, event.mouse().y) && CaptureMouse(event);
|
|
||||||
|
bool HandleMouse(Event event) {
|
||||||
|
hovered_ = box_.Contain(event.mouse().x, //
|
||||||
|
event.mouse().y) &&
|
||||||
|
CaptureMouse(event);
|
||||||
if (!hovered_) {
|
if (!hovered_) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -270,32 +475,56 @@ class InputBase : public ComponentBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TakeFocus();
|
TakeFocus();
|
||||||
|
|
||||||
if (content_->empty()) {
|
if (content_->empty()) {
|
||||||
|
option_->cursor_position() = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto mapping = CellToGlyphIndex(*content_);
|
// Find the line and index of the cursor.
|
||||||
int original_glyph = cursor_position();
|
std::vector<std::string> lines = Split(*content_);
|
||||||
original_glyph = util::clamp(original_glyph, 0, int(mapping.size()));
|
int& cursor_position = option_->cursor_position();
|
||||||
size_t original_cell = 0;
|
int cursor_line = 0;
|
||||||
for (size_t i = 0; i < mapping.size(); i++) {
|
int cursor_char_index = cursor_position;
|
||||||
if (mapping[i] == original_glyph) {
|
for (const auto& line : lines) {
|
||||||
original_cell = i;
|
if (cursor_char_index <= (int)line.size()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cursor_char_index -= line.size() + 1;
|
||||||
|
cursor_line++;
|
||||||
}
|
}
|
||||||
if (mapping[original_cell] != original_glyph) {
|
int cursor_column =
|
||||||
original_cell = mapping.size();
|
string_width(lines[cursor_line].substr(0, cursor_char_index));
|
||||||
|
|
||||||
|
int new_cursor_column = cursor_column + event.mouse().x - cursor_box_.x_min;
|
||||||
|
int new_cursor_line = cursor_line + event.mouse().y - cursor_box_.y_min;
|
||||||
|
|
||||||
|
// Fix the new cursor position:
|
||||||
|
new_cursor_line = std::max(std::min(new_cursor_line, (int)lines.size()), 0);
|
||||||
|
|
||||||
|
std::string empty_string;
|
||||||
|
const std::string& line = new_cursor_line < (int)lines.size()
|
||||||
|
? lines[new_cursor_line]
|
||||||
|
: empty_string;
|
||||||
|
new_cursor_column = util::clamp(new_cursor_column, 0, string_width(line));
|
||||||
|
|
||||||
|
if (new_cursor_column == cursor_column && //
|
||||||
|
new_cursor_line == cursor_line) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
const int target_cell =
|
|
||||||
int(original_cell) + event.mouse().x - cursor_box_.x_min;
|
// Convert back the new_cursor_{line,column} toward cursor_position:
|
||||||
int target_glyph = target_cell < int(mapping.size()) ? mapping[target_cell]
|
cursor_position = 0;
|
||||||
: int(mapping.size());
|
for (int i = 0; i < new_cursor_line; ++i) {
|
||||||
target_glyph = util::clamp(target_glyph, 0, GlyphCount(*content_));
|
cursor_position += lines[i].size() + 1;
|
||||||
if (cursor_position() != target_glyph) {
|
}
|
||||||
cursor_position() = target_glyph;
|
while (new_cursor_column > 0) {
|
||||||
|
new_cursor_column -= GlyphWidth(content_(), cursor_position);
|
||||||
|
cursor_position = GlyphNext(content_(), cursor_position);
|
||||||
|
}
|
||||||
|
|
||||||
option_->on_change();
|
option_->on_change();
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,7 +532,6 @@ class InputBase : public ComponentBase {
|
|||||||
|
|
||||||
bool hovered_ = false;
|
bool hovered_ = false;
|
||||||
StringRef content_;
|
StringRef content_;
|
||||||
ConstStringRef placeholder_;
|
|
||||||
|
|
||||||
Box box_;
|
Box box_;
|
||||||
Box cursor_box_;
|
Box cursor_box_;
|
||||||
@ -314,7 +542,6 @@ class InputBase : public ComponentBase {
|
|||||||
|
|
||||||
/// @brief An input box for editing text.
|
/// @brief An input box for editing text.
|
||||||
/// @param content The editable content.
|
/// @param content The editable content.
|
||||||
/// @param placeholder The text displayed when content is still empty.
|
|
||||||
/// @param option Additional optional parameters.
|
/// @param option Additional optional parameters.
|
||||||
/// @ingroup component
|
/// @ingroup component
|
||||||
/// @see InputBase
|
/// @see InputBase
|
||||||
@ -334,15 +561,19 @@ class InputBase : public ComponentBase {
|
|||||||
/// ```bash
|
/// ```bash
|
||||||
/// placeholder
|
/// placeholder
|
||||||
/// ```
|
/// ```
|
||||||
|
Component Input(StringRef content, Ref<InputOption> option) {
|
||||||
|
return Make<InputBase>(std::move(content), std::move(option));
|
||||||
|
}
|
||||||
|
|
||||||
Component Input(StringRef content,
|
Component Input(StringRef content,
|
||||||
ConstStringRef placeholder,
|
StringRef placeholder,
|
||||||
Ref<InputOption> option) {
|
Ref<InputOption> option) {
|
||||||
return Make<InputBase>(std::move(content), std::move(placeholder),
|
option->placeholder = placeholder;
|
||||||
std::move(option));
|
return Make<InputBase>(std::move(content), std::move(option));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ftxui
|
} // namespace ftxui
|
||||||
|
|
||||||
// Copyright 2020 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.
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,3 @@
|
|||||||
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, EXPECT_EQ, EXPECT_TRUE, Test, TestInfo (ptr only), EXPECT_FALSE, TEST
|
|
||||||
#include <ftxui/dom/elements.hpp> // for yframe
|
#include <ftxui/dom/elements.hpp> // for yframe
|
||||||
#include <ftxui/dom/node.hpp> // for Render
|
#include <ftxui/dom/node.hpp> // for Render
|
||||||
#include <ftxui/screen/screen.hpp> // for Screen
|
#include <ftxui/screen/screen.hpp> // for Screen
|
||||||
@ -11,6 +10,7 @@
|
|||||||
#include "ftxui/component/component_options.hpp" // for RadioboxOption
|
#include "ftxui/component/component_options.hpp" // for RadioboxOption
|
||||||
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown, Event::End, Event::Home, Event::Tab, Event::TabReverse, Event::PageDown, Event::PageUp, Event::ArrowUp
|
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown, Event::End, Event::Home, Event::Tab, Event::TabReverse, Event::PageDown, Event::PageUp, Event::ArrowUp
|
||||||
#include "ftxui/util/ref.hpp" // for Ref
|
#include "ftxui/util/ref.hpp" // for Ref
|
||||||
|
#include "gtest/gtest.h" // for AssertionResult, Message, TestPartResult, EXPECT_EQ, EXPECT_TRUE, Test, EXPECT_FALSE, TEST
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <string> // for string
|
||||||
#include <thread> // for thread
|
#include <thread> // for thread
|
||||||
#include <utility> // for move
|
#include <utility> // for move
|
||||||
|
|
||||||
#include "ftxui/component/receiver.hpp"
|
#include "ftxui/component/receiver.hpp"
|
||||||
|
#include "gtest/gtest.h" // for AssertionResult, Message, Test, TestPartResult, EXPECT_EQ, EXPECT_TRUE, EXPECT_FALSE, TEST
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, Test, EXPECT_EQ, EXPECT_TRUE, TestInfo (ptr only), TEST
|
|
||||||
#include <ftxui/dom/direction.hpp> // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up
|
#include <ftxui/dom/direction.hpp> // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up
|
||||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||||
|
#include <string> // for string
|
||||||
|
|
||||||
#include "ftxui/component/component.hpp" // for ResizableSplit, Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop
|
#include "ftxui/component/component.hpp" // for ResizableSplit, Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop
|
||||||
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
||||||
@ -9,6 +9,7 @@
|
|||||||
#include "ftxui/dom/elements.hpp" // for Element, separatorDouble, text
|
#include "ftxui/dom/elements.hpp" // for Element, separatorDouble, text
|
||||||
#include "ftxui/dom/node.hpp" // for Render
|
#include "ftxui/dom/node.hpp" // for Render
|
||||||
#include "ftxui/screen/screen.hpp" // for Screen
|
#include "ftxui/screen/screen.hpp" // for Screen
|
||||||
|
#include "gtest/gtest.h" // for AssertionResult, Message, TestPartResult, Test, EXPECT_EQ, EXPECT_TRUE, TEST
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, Test, EXPECT_EQ, EXPECT_TRUE, TestInfo (ptr only), EXPECT_FALSE, TEST
|
|
||||||
#include <array> // for array
|
#include <array> // for array
|
||||||
#include <cstddef> // for size_t
|
#include <cstddef> // for size_t
|
||||||
#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Left, Mouse::Pressed, Mouse::Released
|
#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Left, Mouse::Pressed, Mouse::Released
|
||||||
#include <ftxui/dom/direction.hpp> // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up
|
#include <ftxui/dom/direction.hpp> // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up
|
||||||
#include <ftxui/dom/elements.hpp> // for frame
|
#include <ftxui/dom/elements.hpp> // for frame
|
||||||
#include <memory> // for shared_ptr, __shared_ptr_access, allocator
|
#include <memory> // for shared_ptr, __shared_ptr_access, allocator
|
||||||
#include <string> // for to_string
|
#include <string> // for string, to_string
|
||||||
|
|
||||||
#include "ftxui/component/component.hpp" // for Slider, Vertical, operator|=
|
#include "ftxui/component/component.hpp" // for Slider, Vertical, operator|=
|
||||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown
|
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown
|
||||||
#include "ftxui/dom/node.hpp" // for Render
|
#include "ftxui/dom/node.hpp" // for Render
|
||||||
#include "ftxui/screen/screen.hpp" // for Screen
|
#include "ftxui/screen/screen.hpp" // for Screen
|
||||||
|
#include "gtest/gtest.h" // for AssertionResult, Message, TestPartResult, Test, EXPECT_EQ, EXPECT_TRUE, EXPECT_FALSE, TEST
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
#include <gtest/gtest.h> // for AssertionResult, Test, Message, TestPartResult, SuiteApiResolver, TestInfo (ptr only), EXPECT_EQ, EXPECT_TRUE, TEST, TestFactoryImpl, EXPECT_FALSE
|
|
||||||
#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 <ftxui/component/task.hpp> // for Task
|
||||||
#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 <variant> // for get
|
#include <variant> // for get
|
||||||
|
|
||||||
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::Backspace, Event::Custom, Event::Delete, Event::End, Event::F10, Event::F11, Event::F12, Event::F5, Event::F6, Event::F7, Event::F8, Event::F9, Event::Home, 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/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
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, EXPECT_EQ, Test, EXPECT_TRUE, TestInfo (ptr only), EXPECT_FALSE, TEST
|
|
||||||
#include <functional> // for function
|
#include <functional> // for function
|
||||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||||
#include <string> // for string, basic_string
|
#include <string> // for string, basic_string
|
||||||
@ -9,6 +8,7 @@
|
|||||||
#include "ftxui/component/component_options.hpp" // for MenuOption
|
#include "ftxui/component/component_options.hpp" // for MenuOption
|
||||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowLeft, Event::ArrowRight, Event::Return, Event::Tab, Event::TabReverse
|
#include "ftxui/component/event.hpp" // for Event, Event::ArrowLeft, Event::ArrowRight, Event::Return, Event::Tab, Event::TabReverse
|
||||||
#include "ftxui/util/ref.hpp" // for Ref
|
#include "ftxui/util/ref.hpp" // for Ref
|
||||||
|
#include "gtest/gtest.h" // for AssertionResult, Message, TestPartResult, EXPECT_EQ, Test, EXPECT_TRUE, EXPECT_FALSE, TEST
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <string> // for allocator, string
|
||||||
#include <string> // for allocator
|
|
||||||
|
|
||||||
#include "ftxui/dom/elements.hpp" // for operator|, text, blink, Element
|
#include "ftxui/dom/elements.hpp" // for operator|, text, blink, Element
|
||||||
#include "ftxui/dom/node.hpp" // for Render
|
#include "ftxui/dom/node.hpp" // for Render
|
||||||
#include "ftxui/screen/screen.hpp" // for Screen, Pixel
|
#include "ftxui/screen/screen.hpp" // for Screen, Pixel
|
||||||
|
#include "gtest/gtest.h" // for Test, AssertionResult, EXPECT_TRUE, Message, TEST, TestPartResult
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <string> // for allocator, string
|
||||||
#include <string> // for allocator
|
|
||||||
|
|
||||||
#include "ftxui/dom/elements.hpp" // for operator|, text, bold, Element
|
#include "ftxui/dom/elements.hpp" // for operator|, text, bold, Element
|
||||||
#include "ftxui/dom/node.hpp" // for Render
|
#include "ftxui/dom/node.hpp" // for Render
|
||||||
#include "ftxui/screen/screen.hpp" // for Screen, Pixel
|
#include "ftxui/screen/screen.hpp" // for Screen, Pixel
|
||||||
|
#include "gtest/gtest.h" // for Test, AssertionResult, EXPECT_TRUE, Message, TEST, TestPartResult
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <string> // for allocator, string
|
||||||
#include <string> // for allocator
|
|
||||||
|
|
||||||
#include "ftxui/dom/elements.hpp" // for operator|, text, dim, Element
|
#include "ftxui/dom/elements.hpp" // for operator|, text, dim, Element
|
||||||
#include "ftxui/dom/node.hpp" // for Render
|
#include "ftxui/dom/node.hpp" // for Render
|
||||||
#include "ftxui/screen/screen.hpp" // for Screen, Pixel
|
#include "ftxui/screen/screen.hpp" // for Screen, Pixel
|
||||||
|
#include "gtest/gtest.h" // for Test, AssertionResult, EXPECT_TRUE, Message, TEST, TestPartResult
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
@ -35,6 +35,9 @@ class Text : public Node {
|
|||||||
if (x > box_.x_max) {
|
if (x > box_.x_max) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (cell == "\n") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
screen.PixelAt(x, y).character = cell;
|
screen.PixelAt(x, y).character = cell;
|
||||||
++x;
|
++x;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <string> // for allocator, string
|
||||||
#include <string> // for allocator
|
|
||||||
|
|
||||||
#include "ftxui/dom/elements.hpp" // for operator|, text, underlined, Element
|
#include "ftxui/dom/elements.hpp" // for operator|, text, underlined, Element
|
||||||
#include "ftxui/dom/node.hpp" // for Render
|
#include "ftxui/dom/node.hpp" // for Render
|
||||||
#include "ftxui/screen/screen.hpp" // for Screen, Pixel
|
#include "ftxui/screen/screen.hpp" // for Screen, Pixel
|
||||||
|
#include "gtest/gtest.h" // for Test, AssertionResult, EXPECT_TRUE, Message, TEST, TestPartResult
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
@ -7,15 +7,22 @@
|
|||||||
|
|
||||||
#include "ftxui/screen/string.hpp"
|
#include "ftxui/screen/string.hpp"
|
||||||
|
|
||||||
|
#include <stddef.h> // for size_t
|
||||||
#include <array> // for array
|
#include <array> // for array
|
||||||
#include <cstdint> // for uint32_t, uint8_t, uint16_t, int32_t
|
#include <cstdint> // for uint32_t, uint8_t, uint16_t, int32_t
|
||||||
#include <string> // for string, basic_string, wstring
|
#include <string> // for string, basic_string, wstring
|
||||||
#include <tuple> // for _Swallow_assign, ignore
|
#include <tuple> // for _Swallow_assign, ignore
|
||||||
|
|
||||||
#include "ftxui/screen/deprecated.hpp" // for wchar_width, wstring_width
|
#include "ftxui/screen/deprecated.hpp" // for wchar_width, wstring_width
|
||||||
|
#include "ftxui/screen/string_internal.hpp" // for WordBreakProperty, EatCodePoint, CodepointToWordBreakProperty, GlyphCount, GlyphIterate, GlyphNext, GlyphPrevious, IsCombining, IsControl, IsFullWidth, Utf8ToWordBreakProperty
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
using ftxui::EatCodePoint;
|
||||||
|
using ftxui::IsCombining;
|
||||||
|
using ftxui::IsControl;
|
||||||
|
using ftxui::IsFullWidth;
|
||||||
|
|
||||||
struct Interval {
|
struct Interval {
|
||||||
uint32_t first;
|
uint32_t first;
|
||||||
uint32_t last;
|
uint32_t last;
|
||||||
@ -1411,46 +1418,26 @@ bool Bisearch(uint32_t ucs, const std::array<C, N> table, C* out) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsCombining(uint32_t ucs) {
|
|
||||||
return ftxui::CodepointToWordBreakProperty(ucs) == WBP::Extend;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsFullWidth(uint32_t ucs) {
|
|
||||||
if (ucs < 0x0300) // Quick path: // NOLINT
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return Bisearch(ucs, g_full_width_characters);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsControl(uint32_t ucs) {
|
|
||||||
if (ucs == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (ucs < 32) { // NOLINT
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (ucs >= 0x7f && ucs < 0xa0) { // NOLINT
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int codepoint_width(uint32_t ucs) {
|
int codepoint_width(uint32_t ucs) {
|
||||||
if (IsControl(ucs)) {
|
if (ftxui::IsControl(ucs)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsCombining(ucs)) {
|
if (ftxui::IsCombining(ucs)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsFullWidth(ucs)) {
|
if (ftxui::IsFullWidth(ucs)) {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace ftxui {
|
||||||
|
|
||||||
// From UTF8 encoded string |input|, eat in between 1 and 4 byte representing
|
// From UTF8 encoded string |input|, eat in between 1 and 4 byte representing
|
||||||
// one codepoint. Put the codepoint into |ucs|. Start at |start| and update
|
// one codepoint. Put the codepoint into |ucs|. Start at |start| and update
|
||||||
// |end| to represent the beginning of the next byte to eat for consecutive
|
// |end| to represent the beginning of the next byte to eat for consecutive
|
||||||
@ -1563,9 +1550,29 @@ bool EatCodePoint(const std::wstring& input,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
bool IsCombining(uint32_t ucs) {
|
||||||
|
return ftxui::CodepointToWordBreakProperty(ucs) == WBP::Extend;
|
||||||
|
}
|
||||||
|
|
||||||
namespace ftxui {
|
bool IsFullWidth(uint32_t ucs) {
|
||||||
|
if (ucs < 0x0300) // Quick path: // NOLINT
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Bisearch(ucs, g_full_width_characters);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsControl(uint32_t ucs) {
|
||||||
|
if (ucs == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ucs < 32) { // NOLINT
|
||||||
|
return ucs != 10; // 10 => Line feed.
|
||||||
|
}
|
||||||
|
if (ucs >= 0x7f && ucs < 0xa0) { // NOLINT
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
WordBreakProperty CodepointToWordBreakProperty(uint32_t codepoint) {
|
WordBreakProperty CodepointToWordBreakProperty(uint32_t codepoint) {
|
||||||
WordBreakPropertyInterval interval = {0, 0, WBP::ALetter};
|
WordBreakPropertyInterval interval = {0, 0, WBP::ALetter};
|
||||||
@ -1660,12 +1667,35 @@ std::vector<std::string> Utf8ToGlyphs(const std::string& input) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GlyphPosition(const std::string& input, size_t glyph_index, size_t start) {
|
size_t GlyphPrevious(const std::string& input, size_t start) {
|
||||||
if (glyph_index <= 0) {
|
while (true) {
|
||||||
|
if (start == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
start--;
|
||||||
|
|
||||||
|
// Skip the UTF8 continuation bytes.
|
||||||
|
if ((input[start] & 0b1100'0000) == 0b1000'0000) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t codepoint = 0;
|
||||||
size_t end = 0;
|
size_t end = 0;
|
||||||
|
const bool eaten = EatCodePoint(input, start, &end, &codepoint);
|
||||||
|
|
||||||
|
// Ignore invalid, control characters and combining characters.
|
||||||
|
if (!eaten || IsControl(codepoint) || IsCombining(codepoint)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GlyphNext(const std::string& input, size_t start) {
|
||||||
|
bool glyph_found = false;
|
||||||
while (start < input.size()) {
|
while (start < input.size()) {
|
||||||
|
size_t end = 0;
|
||||||
uint32_t codepoint = 0;
|
uint32_t codepoint = 0;
|
||||||
const bool eaten = EatCodePoint(input, start, &end, &codepoint);
|
const bool eaten = EatCodePoint(input, start, &end, &codepoint);
|
||||||
|
|
||||||
@ -1677,17 +1707,31 @@ int GlyphPosition(const std::string& input, size_t glyph_index, size_t start) {
|
|||||||
|
|
||||||
// We eat the beginning of the next glyph. If we are eating the one
|
// We eat the beginning of the next glyph. If we are eating the one
|
||||||
// requested, return its start position immediately.
|
// requested, return its start position immediately.
|
||||||
if (glyph_index == 0) {
|
if (glyph_found) {
|
||||||
return static_cast<int>(start);
|
return static_cast<int>(start);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, skip this glyph and iterate:
|
// Otherwise, skip this glyph and iterate:
|
||||||
glyph_index--;
|
glyph_found = true;
|
||||||
start = end;
|
start = end;
|
||||||
}
|
}
|
||||||
return static_cast<int>(input.size());
|
return static_cast<int>(input.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t GlyphIterate(const std::string& input, int glyph_offset, size_t start) {
|
||||||
|
if (glyph_offset >= 0) {
|
||||||
|
for (int i = 0; i < glyph_offset; ++i) {
|
||||||
|
start = GlyphNext(input, start);
|
||||||
|
}
|
||||||
|
return start;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < -glyph_offset; ++i) {
|
||||||
|
start = GlyphPrevious(input, start);
|
||||||
|
}
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<int> CellToGlyphIndex(const std::string& input) {
|
std::vector<int> CellToGlyphIndex(const std::string& input) {
|
||||||
int x = -1;
|
int x = -1;
|
||||||
std::vector<int> out;
|
std::vector<int> out;
|
||||||
|
64
src/ftxui/screen/string_internal.hpp
Normal file
64
src/ftxui/screen/string_internal.hpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#ifndef FTXUI_SCREEN_STRING_INTERNAL_HPP
|
||||||
|
#define FTXUI_SCREEN_STRING_INTERNAL_HPP
|
||||||
|
|
||||||
|
namespace ftxui {
|
||||||
|
|
||||||
|
bool EatCodePoint(const std::string& input,
|
||||||
|
size_t start,
|
||||||
|
size_t* end,
|
||||||
|
uint32_t* ucs);
|
||||||
|
bool EatCodePoint(const std::wstring& input,
|
||||||
|
size_t start,
|
||||||
|
size_t* end,
|
||||||
|
uint32_t* ucs);
|
||||||
|
|
||||||
|
bool IsCombining(uint32_t ucs);
|
||||||
|
bool IsFullWidth(uint32_t ucs);
|
||||||
|
bool IsControl(uint32_t ucs);
|
||||||
|
|
||||||
|
size_t GlyphPrevious(const std::string& input, size_t start);
|
||||||
|
size_t GlyphNext(const std::string& input, size_t start);
|
||||||
|
|
||||||
|
// Return the index in the |input| string of the glyph at |glyph_offset|,
|
||||||
|
// starting at |start|
|
||||||
|
size_t GlyphIterate(const std::string& input,
|
||||||
|
int glyph_offset,
|
||||||
|
size_t start = 0);
|
||||||
|
|
||||||
|
// Returns the number of glyphs in |input|.
|
||||||
|
int GlyphCount(const std::string& input);
|
||||||
|
|
||||||
|
// Properties from:
|
||||||
|
// https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/WordBreakProperty.txt
|
||||||
|
enum class WordBreakProperty {
|
||||||
|
ALetter,
|
||||||
|
CR,
|
||||||
|
Double_Quote,
|
||||||
|
Extend,
|
||||||
|
ExtendNumLet,
|
||||||
|
Format,
|
||||||
|
Hebrew_Letter,
|
||||||
|
Katakana,
|
||||||
|
LF,
|
||||||
|
MidLetter,
|
||||||
|
MidNum,
|
||||||
|
MidNumLet,
|
||||||
|
Newline,
|
||||||
|
Numeric,
|
||||||
|
Regional_Indicator,
|
||||||
|
Single_Quote,
|
||||||
|
WSegSpace,
|
||||||
|
ZWJ,
|
||||||
|
};
|
||||||
|
WordBreakProperty CodepointToWordBreakProperty(uint32_t codepoint);
|
||||||
|
std::vector<WordBreakProperty> Utf8ToWordBreakProperty(
|
||||||
|
const std::string& input);
|
||||||
|
|
||||||
|
bool IsWordBreakingCharacter(const std::string& input, size_t glyph_index);
|
||||||
|
} // namespace ftxui
|
||||||
|
|
||||||
|
#endif /* end of include guard: FTXUI_SCREEN_STRING_INTERNAL_HPP */
|
||||||
|
|
||||||
|
// Copyright 2023 Arthur Sonzogni. All rights reserved.
|
||||||
|
// Use of this source code is governed by the MIT license that can be found in
|
||||||
|
// the LICENSE file.
|
@ -1,6 +1,7 @@
|
|||||||
#include "ftxui/screen/string.hpp"
|
#include "ftxui/screen/string.hpp"
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <string> // for allocator, string
|
#include <string> // for allocator, string
|
||||||
|
#include "ftxui/screen/string_internal.hpp"
|
||||||
|
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
|
||||||
@ -61,41 +62,41 @@ TEST(StringTest, GlyphCount) {
|
|||||||
EXPECT_EQ(GlyphCount("a\1a"), 2);
|
EXPECT_EQ(GlyphCount("a\1a"), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(StringTest, GlyphPosition) {
|
TEST(StringTest, GlyphIterate) {
|
||||||
// Basic:
|
// Basic:
|
||||||
EXPECT_EQ(GlyphPosition("", -1), 0);
|
EXPECT_EQ(GlyphIterate("", -1), 0);
|
||||||
EXPECT_EQ(GlyphPosition("", 0), 0);
|
EXPECT_EQ(GlyphIterate("", 0), 0);
|
||||||
EXPECT_EQ(GlyphPosition("", 1), 0);
|
EXPECT_EQ(GlyphIterate("", 1), 0);
|
||||||
EXPECT_EQ(GlyphPosition("a", 0), 0);
|
EXPECT_EQ(GlyphIterate("a", 0), 0);
|
||||||
EXPECT_EQ(GlyphPosition("a", 1), 1);
|
EXPECT_EQ(GlyphIterate("a", 1), 1);
|
||||||
EXPECT_EQ(GlyphPosition("ab", 0), 0);
|
EXPECT_EQ(GlyphIterate("ab", 0), 0);
|
||||||
EXPECT_EQ(GlyphPosition("ab", 1), 1);
|
EXPECT_EQ(GlyphIterate("ab", 1), 1);
|
||||||
EXPECT_EQ(GlyphPosition("ab", 2), 2);
|
EXPECT_EQ(GlyphIterate("ab", 2), 2);
|
||||||
EXPECT_EQ(GlyphPosition("abc", 0), 0);
|
EXPECT_EQ(GlyphIterate("abc", 0), 0);
|
||||||
EXPECT_EQ(GlyphPosition("abc", 1), 1);
|
EXPECT_EQ(GlyphIterate("abc", 1), 1);
|
||||||
EXPECT_EQ(GlyphPosition("abc", 2), 2);
|
EXPECT_EQ(GlyphIterate("abc", 2), 2);
|
||||||
EXPECT_EQ(GlyphPosition("abc", 3), 3);
|
EXPECT_EQ(GlyphIterate("abc", 3), 3);
|
||||||
// Fullwidth glyphs:
|
// Fullwidth glyphs:
|
||||||
EXPECT_EQ(GlyphPosition("测", 0), 0);
|
EXPECT_EQ(GlyphIterate("测", 0), 0);
|
||||||
EXPECT_EQ(GlyphPosition("测", 1), 3);
|
EXPECT_EQ(GlyphIterate("测", 1), 3);
|
||||||
EXPECT_EQ(GlyphPosition("测试", 0), 0);
|
EXPECT_EQ(GlyphIterate("测试", 0), 0);
|
||||||
EXPECT_EQ(GlyphPosition("测试", 1), 3);
|
EXPECT_EQ(GlyphIterate("测试", 1), 3);
|
||||||
EXPECT_EQ(GlyphPosition("测试", 2), 6);
|
EXPECT_EQ(GlyphIterate("测试", 2), 6);
|
||||||
EXPECT_EQ(GlyphPosition("测试", 1, 3), 6);
|
EXPECT_EQ(GlyphIterate("测试", 1, 3), 6);
|
||||||
EXPECT_EQ(GlyphPosition("测试", 1, 0), 3);
|
EXPECT_EQ(GlyphIterate("测试", 1, 0), 3);
|
||||||
// Combining characters:
|
// Combining characters:
|
||||||
EXPECT_EQ(GlyphPosition("ā", 0), 0);
|
EXPECT_EQ(GlyphIterate("ā", 0), 0);
|
||||||
EXPECT_EQ(GlyphPosition("ā", 1), 3);
|
EXPECT_EQ(GlyphIterate("ā", 1), 3);
|
||||||
EXPECT_EQ(GlyphPosition("a⃒a̗ā", 0), 0);
|
EXPECT_EQ(GlyphIterate("a⃒a̗ā", 0), 0);
|
||||||
EXPECT_EQ(GlyphPosition("a⃒a̗ā", 1), 4);
|
EXPECT_EQ(GlyphIterate("a⃒a̗ā", 1), 4);
|
||||||
EXPECT_EQ(GlyphPosition("a⃒a̗ā", 2), 7);
|
EXPECT_EQ(GlyphIterate("a⃒a̗ā", 2), 7);
|
||||||
EXPECT_EQ(GlyphPosition("a⃒a̗ā", 3), 10);
|
EXPECT_EQ(GlyphIterate("a⃒a̗ā", 3), 10);
|
||||||
// Control characters:
|
// Control characters:
|
||||||
EXPECT_EQ(GlyphPosition("\1", 0), 0);
|
EXPECT_EQ(GlyphIterate("\1", 0), 0);
|
||||||
EXPECT_EQ(GlyphPosition("\1", 1), 1);
|
EXPECT_EQ(GlyphIterate("\1", 1), 1);
|
||||||
EXPECT_EQ(GlyphPosition("a\1a", 0), 0);
|
EXPECT_EQ(GlyphIterate("a\1a", 0), 0);
|
||||||
EXPECT_EQ(GlyphPosition("a\1a", 1), 2);
|
EXPECT_EQ(GlyphIterate("a\1a", 1), 2);
|
||||||
EXPECT_EQ(GlyphPosition("a\1a", 2), 3);
|
EXPECT_EQ(GlyphIterate("a\1a", 2), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(StringTest, CellToGlyphIndex) {
|
TEST(StringTest, CellToGlyphIndex) {
|
||||||
@ -135,7 +136,7 @@ TEST(StringTest, Utf8ToWordBreakProperty) {
|
|||||||
EXPECT_EQ(Utf8ToWordBreakProperty(":"), T({P::MidLetter}));
|
EXPECT_EQ(Utf8ToWordBreakProperty(":"), T({P::MidLetter}));
|
||||||
EXPECT_EQ(Utf8ToWordBreakProperty("."), T({P::MidNumLet}));
|
EXPECT_EQ(Utf8ToWordBreakProperty("."), T({P::MidNumLet}));
|
||||||
EXPECT_EQ(Utf8ToWordBreakProperty("\r"), T({})); // FIXME
|
EXPECT_EQ(Utf8ToWordBreakProperty("\r"), T({})); // FIXME
|
||||||
EXPECT_EQ(Utf8ToWordBreakProperty("\n"), T({})); // FIXME
|
EXPECT_EQ(Utf8ToWordBreakProperty("\n"), T({P::LF}));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(StringTest, to_string) {
|
TEST(StringTest, to_string) {
|
||||||
|
Loading…
Reference in New Issue
Block a user