зеркало из
https://github.com/glebtv/tui-textarea.git
synced 2026-08-28 11:36:17 +03:00
chore/integrate-open-prs (#5)
* docs: integrate upstream PR #116 * feat: integrate upstream PR #113 * chore: add portable-atomic for viewport atomics (upstream PR #111) * fix(examples): integrate upstream PR #100 * fix(word): integrate upstream PR #98 * fix(widget): handle wide unicode char widths in horizontal scroll (upstream PR #94) * feat(highlight): integrate upstream PR #93 * chore(release): prepare v0.8.0 docs and CI-clean merged upstream updates * docs(license): rename LICENSE.txt to LICENSE and fix header capitalization
Этот коммит содержится в:
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,3 +1,16 @@
|
|||||||
|
<a id="v0.8.0"></a>
|
||||||
|
# [v0.8.0](https://github.com/srothgan/tui-textarea/releases/tag/v0.8.0) - 2026-02-18
|
||||||
|
|
||||||
|
- Add `TextArea::clear()` API to clear all text content with undo/redo coverage. (upstream PR [#113](https://github.com/rhysd/tui-textarea/pull/113))
|
||||||
|
- Add custom highlight ranges with priority via `TextArea::custom_highlight()` and `TextArea::clear_custom_highlight()`. (upstream PR [#93](https://github.com/rhysd/tui-textarea/pull/93))
|
||||||
|
- Fix horizontal scrolling with wide Unicode characters by using display width for cursor column calculations. (upstream PR [#94](https://github.com/rhysd/tui-textarea/pull/94))
|
||||||
|
- Treat `_` as part of words for word navigation/edit operations. (upstream PR [#98](https://github.com/rhysd/tui-textarea/pull/98))
|
||||||
|
- Fix editor examples to preserve modified state across multiple inputs. (upstream PR [#100](https://github.com/rhysd/tui-textarea/pull/100))
|
||||||
|
- Use `portable-atomic` for viewport atomics to improve target compatibility. (upstream PR [#111](https://github.com/rhysd/tui-textarea/pull/111))
|
||||||
|
- Fix README wording for line-number default behavior. (upstream PR [#116](https://github.com/rhysd/tui-textarea/pull/116))
|
||||||
|
|
||||||
|
[Changes][v0.8.0]
|
||||||
|
|
||||||
<a id="v0.7.1"></a>
|
<a id="v0.7.1"></a>
|
||||||
# [v0.7.1](https://github.com/srothgan/tui-textarea/releases/tag/v0.7.1) - 2026-02-18
|
# [v0.7.1](https://github.com/srothgan/tui-textarea/releases/tag/v0.7.1) - 2026-02-18
|
||||||
|
|
||||||
@@ -449,6 +462,7 @@ First release :tada:
|
|||||||
|
|
||||||
[Changes][v0.1.0]
|
[Changes][v0.1.0]
|
||||||
|
|
||||||
|
[v0.8.0]: https://github.com/srothgan/tui-textarea/compare/v0.7.1...v0.8.0
|
||||||
[v0.7.1]: https://github.com/srothgan/tui-textarea/compare/v0.7.0...v0.7.1
|
[v0.7.1]: https://github.com/srothgan/tui-textarea/compare/v0.7.0...v0.7.1
|
||||||
[v0.7.0]: https://github.com/rhysd/tui-textarea/compare/v0.6.1...v0.7.0
|
[v0.7.0]: https://github.com/rhysd/tui-textarea/compare/v0.6.1...v0.7.0
|
||||||
[v0.6.1]: https://github.com/rhysd/tui-textarea/compare/v0.6.0...v0.6.1
|
[v0.6.1]: https://github.com/rhysd/tui-textarea/compare/v0.6.0...v0.6.1
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name = "tui-textarea-2"
|
|||||||
homepage = "https://github.com/srothgan/tui-textarea#readme"
|
homepage = "https://github.com/srothgan/tui-textarea#readme"
|
||||||
repository = "https://github.com/srothgan/tui-textarea"
|
repository = "https://github.com/srothgan/tui-textarea"
|
||||||
documentation = "https://docs.rs/tui-textarea-2/latest/tui_textarea/"
|
documentation = "https://docs.rs/tui-textarea-2/latest/tui_textarea/"
|
||||||
version = "0.7.1"
|
version = "0.8.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.56.1" # for `tui` crate support
|
rust-version = "1.56.1" # for `tui` crate support
|
||||||
authors = ["Simon Peter Rothgang <simonrothgang@icloud.com>", "rhysd <lin90162@yahoo.co.jp>"]
|
authors = ["Simon Peter Rothgang <simonrothgang@icloud.com>", "rhysd <lin90162@yahoo.co.jp>"]
|
||||||
@@ -15,7 +15,7 @@ license = "MIT"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
categories = ["text-editors", "text-processing"]
|
categories = ["text-editors", "text-processing"]
|
||||||
keywords = ["tui", "textarea", "editor", "input", "ratatui"]
|
keywords = ["tui", "textarea", "editor", "input", "ratatui"]
|
||||||
include = ["/src", "/examples", "/tests", "/README.md", "/LICENSE.txt"]
|
include = ["/src", "/examples", "/tests", "/README.md", "/LICENSE"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "tui_textarea"
|
name = "tui_textarea"
|
||||||
@@ -46,6 +46,7 @@ crossterm-028 = { package = "crossterm", version = "0.28", optional = true }
|
|||||||
crossterm-025 = { package = "crossterm", version = "0.25", optional = true }
|
crossterm-025 = { package = "crossterm", version = "0.25", optional = true }
|
||||||
ratatui-core = { version = "0.1.0", default-features = false, optional = true }
|
ratatui-core = { version = "0.1.0", default-features = false, optional = true }
|
||||||
ratatui-widgets = { version = "0.3.0", default-features = false, optional = true }
|
ratatui-widgets = { version = "0.3.0", default-features = false, optional = true }
|
||||||
|
portable-atomic = "1.11.1"
|
||||||
regex = { version = "1", optional = true }
|
regex = { version = "1", optional = true }
|
||||||
termion = { version = "4.0", optional = true }
|
termion = { version = "4.0", optional = true }
|
||||||
termion-15 = { package = "termion", version = "1.5", optional = true }
|
termion-15 = { package = "termion", version = "1.5", optional = true }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
the MIT License
|
The MIT License
|
||||||
|
|
||||||
Copyright (c) 2022 rhysd
|
Copyright (c) 2022 rhysd
|
||||||
|
|
||||||
@@ -343,7 +343,7 @@ assert_eq!(textarea.into_lines(), [""]);
|
|||||||
|
|
||||||
### Show line number
|
### Show line number
|
||||||
|
|
||||||
By default, `TextArea` does now show line numbers. To enable, set a style for rendering line numbers by
|
By default, `TextArea` does not show line numbers. To enable, set a style for rendering line numbers by
|
||||||
`TextArea::set_line_number_style()`. For example, the following renders line numbers in dark gray background
|
`TextArea::set_line_number_style()`. For example, the following renders line numbers in dark gray background
|
||||||
color.
|
color.
|
||||||
|
|
||||||
@@ -485,6 +485,7 @@ notify how to move the cursor.
|
|||||||
| `textarea.delete_line_by_head()` | Delete from cursor until the head of line |
|
| `textarea.delete_line_by_head()` | Delete from cursor until the head of line |
|
||||||
| `textarea.delete_word()` | Delete one word before cursor |
|
| `textarea.delete_word()` | Delete one word before cursor |
|
||||||
| `textarea.delete_next_word()` | Delete one word next to cursor |
|
| `textarea.delete_next_word()` | Delete one word next to cursor |
|
||||||
|
| `textarea.clear()` | Clear all text |
|
||||||
| `textarea.undo()` | Undo |
|
| `textarea.undo()` | Undo |
|
||||||
| `textarea.redo()` | Redo |
|
| `textarea.redo()` | Redo |
|
||||||
| `textarea.copy()` | Copy selected text |
|
| `textarea.copy()` | Copy selected text |
|
||||||
@@ -493,6 +494,8 @@ notify how to move the cursor.
|
|||||||
| `textarea.start_selection()` | Start text selection |
|
| `textarea.start_selection()` | Start text selection |
|
||||||
| `textarea.cancel_selection()` | Cancel text selection |
|
| `textarea.cancel_selection()` | Cancel text selection |
|
||||||
| `textarea.select_all()` | Select entire text |
|
| `textarea.select_all()` | Select entire text |
|
||||||
|
| `textarea.custom_highlight(range, style, priority)` | Add a custom highlighted range |
|
||||||
|
| `textarea.clear_custom_highlight()` | Clear all custom highlights |
|
||||||
| `textarea.move_cursor(CursorMove::Forward)` | Move cursor forward by one character |
|
| `textarea.move_cursor(CursorMove::Forward)` | Move cursor forward by one character |
|
||||||
| `textarea.move_cursor(CursorMove::Back)` | Move cursor backward by one character |
|
| `textarea.move_cursor(CursorMove::Back)` | Move cursor backward by one character |
|
||||||
| `textarea.move_cursor(CursorMove::Up)` | Move cursor up by one line |
|
| `textarea.move_cursor(CursorMove::Up)` | Move cursor up by one line |
|
||||||
@@ -709,7 +712,7 @@ Please read [CONTRIBUTING.md](./CONTRIBUTING.md) before reporting an issue or ma
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
tui-textarea-2 is distributed under [The MIT License](./LICENSE.txt).
|
tui-textarea-2 is distributed under [The MIT License](./LICENSE).
|
||||||
|
|
||||||
[crates-io-badge]: https://img.shields.io/crates/v/tui-textarea-2.svg
|
[crates-io-badge]: https://img.shields.io/crates/v/tui-textarea-2.svg
|
||||||
[crate]: https://crates.io/crates/tui-textarea-2
|
[crate]: https://crates.io/crates/tui-textarea-2
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ impl Editor<'_> {
|
|||||||
}
|
}
|
||||||
input => {
|
input => {
|
||||||
let buffer = &mut self.buffers[self.current];
|
let buffer = &mut self.buffers[self.current];
|
||||||
buffer.modified = buffer.textarea.input(input);
|
buffer.modified |= buffer.textarea.input(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ impl Editor<'_> {
|
|||||||
}
|
}
|
||||||
input => {
|
input => {
|
||||||
let buffer = &mut self.buffers[self.current];
|
let buffer = &mut self.buffers[self.current];
|
||||||
buffer.modified = buffer.textarea.input(input);
|
buffer.modified |= buffer.textarea.input(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ enum Boundary {
|
|||||||
Select(Style),
|
Select(Style),
|
||||||
#[cfg(feature = "search")]
|
#[cfg(feature = "search")]
|
||||||
Search(Style),
|
Search(Style),
|
||||||
|
Custom(Style, u8), // style, priority
|
||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,10 +23,11 @@ impl Boundary {
|
|||||||
fn cmp(&self, other: &Boundary) -> Ordering {
|
fn cmp(&self, other: &Boundary) -> Ordering {
|
||||||
fn rank(b: &Boundary) -> u8 {
|
fn rank(b: &Boundary) -> u8 {
|
||||||
match b {
|
match b {
|
||||||
Boundary::Cursor(_) => 3,
|
Boundary::Cursor(_) => 30,
|
||||||
#[cfg(feature = "search")]
|
#[cfg(feature = "search")]
|
||||||
Boundary::Search(_) => 2,
|
Boundary::Search(_) => 20,
|
||||||
Boundary::Select(_) => 1,
|
Boundary::Select(_) => 10,
|
||||||
|
Boundary::Custom(_, p) => *p,
|
||||||
Boundary::End => 0,
|
Boundary::End => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,6 +40,7 @@ impl Boundary {
|
|||||||
Boundary::Select(s) => Some(*s),
|
Boundary::Select(s) => Some(*s),
|
||||||
#[cfg(feature = "search")]
|
#[cfg(feature = "search")]
|
||||||
Boundary::Search(s) => Some(*s),
|
Boundary::Search(s) => Some(*s),
|
||||||
|
Boundary::Custom(s, _) => Some(*s),
|
||||||
Boundary::End => None,
|
Boundary::End => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,13 +159,15 @@ impl<'a> LineHighlighter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selection(
|
// Shared code for selection and custom highlights
|
||||||
|
fn multiline_highlight(
|
||||||
&mut self,
|
&mut self,
|
||||||
current_row: usize,
|
current_row: usize,
|
||||||
start_row: usize,
|
start_row: usize,
|
||||||
start_off: usize,
|
start_off: usize,
|
||||||
end_row: usize,
|
end_row: usize,
|
||||||
end_off: usize,
|
end_off: usize,
|
||||||
|
boundary: Boundary,
|
||||||
) {
|
) {
|
||||||
let (start, end) = if current_row == start_row {
|
let (start, end) = if current_row == start_row {
|
||||||
if start_row == end_row {
|
if start_row == end_row {
|
||||||
@@ -180,12 +185,47 @@ impl<'a> LineHighlighter<'a> {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if start != end {
|
if start != end {
|
||||||
self.boundaries
|
self.boundaries.push((boundary, start));
|
||||||
.push((Boundary::Select(self.select_style), start));
|
|
||||||
self.boundaries.push((Boundary::End, end));
|
self.boundaries.push((Boundary::End, end));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn selection(
|
||||||
|
&mut self,
|
||||||
|
current_row: usize,
|
||||||
|
start_row: usize,
|
||||||
|
start_off: usize,
|
||||||
|
end_row: usize,
|
||||||
|
end_off: usize,
|
||||||
|
) {
|
||||||
|
self.multiline_highlight(
|
||||||
|
current_row,
|
||||||
|
start_row,
|
||||||
|
start_off,
|
||||||
|
end_row,
|
||||||
|
end_off,
|
||||||
|
Boundary::Select(self.select_style),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn custom(
|
||||||
|
&mut self,
|
||||||
|
current_row: usize,
|
||||||
|
range: ((usize, usize), (usize, usize)),
|
||||||
|
style: Style,
|
||||||
|
priority: u8,
|
||||||
|
) {
|
||||||
|
let ((start_row, start_off), (end_row, end_off)) = range;
|
||||||
|
self.multiline_highlight(
|
||||||
|
current_row,
|
||||||
|
start_row,
|
||||||
|
start_off,
|
||||||
|
end_row,
|
||||||
|
end_off,
|
||||||
|
Boundary::Custom(style, priority),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_spans(self) -> Line<'a> {
|
pub fn into_spans(self) -> Line<'a> {
|
||||||
let Self {
|
let Self {
|
||||||
line,
|
line,
|
||||||
|
|||||||
@@ -55,6 +55,13 @@ impl fmt::Display for YankText {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct CustomHighlight {
|
||||||
|
range: ((usize, usize), (usize, usize)),
|
||||||
|
style: Style,
|
||||||
|
priority: u8,
|
||||||
|
}
|
||||||
|
|
||||||
/// A type to manage state of textarea. These are some important methods:
|
/// A type to manage state of textarea. These are some important methods:
|
||||||
///
|
///
|
||||||
/// - [`TextArea::default`] creates an empty textarea.
|
/// - [`TextArea::default`] creates an empty textarea.
|
||||||
@@ -125,6 +132,7 @@ pub struct TextArea<'a> {
|
|||||||
mask: Option<char>,
|
mask: Option<char>,
|
||||||
selection_start: Option<(usize, usize)>,
|
selection_start: Option<(usize, usize)>,
|
||||||
select_style: Style,
|
select_style: Style,
|
||||||
|
custom_highlights: Vec<CustomHighlight>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert any iterator whose elements can be converted into [`String`] into [`TextArea`]. Each [`String`] element is
|
/// Convert any iterator whose elements can be converted into [`String`] into [`TextArea`]. Each [`String`] element is
|
||||||
@@ -230,6 +238,7 @@ impl<'a> TextArea<'a> {
|
|||||||
mask: None,
|
mask: None,
|
||||||
selection_start: None,
|
selection_start: None,
|
||||||
select_style: Style::default().bg(Color::LightBlue),
|
select_style: Style::default().bg(Color::LightBlue),
|
||||||
|
custom_highlights: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1299,6 +1308,37 @@ impl<'a> TextArea<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clears the whole content of the text area.
|
||||||
|
/// This will result in an empty text area.
|
||||||
|
/// The position of the cursor does matter for clearing the entire text.
|
||||||
|
///
|
||||||
|
/// This method returns if some text was deleted or not in the textarea.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tui_textarea::TextArea;
|
||||||
|
///
|
||||||
|
/// let mut textarea = TextArea::from(["aaa\nbbb ccc"]);
|
||||||
|
///
|
||||||
|
/// textarea.clear();
|
||||||
|
/// assert!(textarea.is_empty());
|
||||||
|
/// ```
|
||||||
|
pub fn clear(&mut self) -> bool {
|
||||||
|
if self.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let all_lines = self.lines();
|
||||||
|
let summed_up_chars_new_lines: usize = all_lines
|
||||||
|
.iter()
|
||||||
|
.map(|line| line.chars().map(|_| 1usize).sum::<usize>())
|
||||||
|
.sum();
|
||||||
|
let all_chars = all_lines.len() + summed_up_chars_new_lines;
|
||||||
|
self.move_cursor(CursorMove::Jump(0, 0));
|
||||||
|
self.delete_str(all_chars);
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// Paste a string previously deleted by [`TextArea::delete_line_by_head`], [`TextArea::delete_line_by_end`],
|
/// Paste a string previously deleted by [`TextArea::delete_line_by_head`], [`TextArea::delete_line_by_end`],
|
||||||
/// [`TextArea::delete_word`], [`TextArea::delete_next_word`]. This method returns if some text was inserted or not
|
/// [`TextArea::delete_word`], [`TextArea::delete_next_word`]. This method returns if some text was inserted or not
|
||||||
/// in the textarea.
|
/// in the textarea.
|
||||||
@@ -1355,6 +1395,23 @@ impl<'a> TextArea<'a> {
|
|||||||
self.selection_start = None;
|
self.selection_start = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn custom_highlight(
|
||||||
|
&mut self,
|
||||||
|
range: ((usize, usize), (usize, usize)),
|
||||||
|
style: Style,
|
||||||
|
priority: u8,
|
||||||
|
) {
|
||||||
|
self.custom_highlights.push(CustomHighlight {
|
||||||
|
range,
|
||||||
|
style,
|
||||||
|
priority,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_custom_highlight(&mut self) {
|
||||||
|
self.custom_highlights.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/// Select the entire text. Cursor moves to the end of the text buffer. When text selection is already ongoing,
|
/// Select the entire text. Cursor moves to the end of the text buffer. When text selection is already ongoing,
|
||||||
/// it is canceled.
|
/// it is canceled.
|
||||||
/// ```
|
/// ```
|
||||||
@@ -1612,6 +1669,20 @@ impl<'a> TextArea<'a> {
|
|||||||
hl.selection(row, start.row, start.offset, end.row, end.offset);
|
hl.selection(row, start.row, start.offset, end.row, end.offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for CustomHighlight {
|
||||||
|
range: ((start_row, start_offset), (end_row, end_offset)),
|
||||||
|
style,
|
||||||
|
priority,
|
||||||
|
} in &self.custom_highlights
|
||||||
|
{
|
||||||
|
hl.custom(
|
||||||
|
row,
|
||||||
|
((*start_row, *start_offset), (*end_row, *end_offset)),
|
||||||
|
*style,
|
||||||
|
*priority,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
hl.into_spans()
|
hl.into_spans()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ use crate::ratatui::text::{Span, Text};
|
|||||||
use crate::ratatui::widgets::{Paragraph, Widget};
|
use crate::ratatui::widgets::{Paragraph, Widget};
|
||||||
use crate::textarea::TextArea;
|
use crate::textarea::TextArea;
|
||||||
use crate::util::num_digits;
|
use crate::util::num_digits;
|
||||||
|
use portable_atomic::{AtomicU64, Ordering};
|
||||||
#[cfg(feature = "ratatui")]
|
#[cfg(feature = "ratatui")]
|
||||||
use ratatui_core::text::Line;
|
use ratatui_core::text::Line;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
|
||||||
#[cfg(feature = "tuirs")]
|
#[cfg(feature = "tuirs")]
|
||||||
use tui::text::Spans as Line;
|
use tui::text::Spans as Line;
|
||||||
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
// &mut 'a (u16, u16, u16, u16) is not available since `render` method takes immutable reference of TextArea
|
// &mut 'a (u16, u16, u16, u16) is not available since `render` method takes immutable reference of TextArea
|
||||||
// instance. In the case, the TextArea instance cannot be accessed from any other objects since it is mutablly
|
// instance. In the case, the TextArea instance cannot be accessed from any other objects since it is mutablly
|
||||||
@@ -114,7 +115,15 @@ impl<'a> TextArea<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_top_col(&self, prev_top: u16, width: u16) -> u16 {
|
fn scroll_top_col(&self, prev_top: u16, width: u16) -> u16 {
|
||||||
let mut cursor = self.cursor().1 as u16;
|
let (row, col) = self.cursor();
|
||||||
|
|
||||||
|
// Adujst the cursor position due to the width of non-latine characters.
|
||||||
|
let mut cursor = self.lines()[row]
|
||||||
|
.chars()
|
||||||
|
.take(col)
|
||||||
|
.map(|c| c.width().unwrap_or(0))
|
||||||
|
.sum::<usize>() as u16;
|
||||||
|
|
||||||
// Adjust the cursor position due to the width of line number.
|
// Adjust the cursor position due to the width of line number.
|
||||||
if self.line_number_style().is_some() {
|
if self.line_number_style().is_some() {
|
||||||
let lnum = num_digits(self.lines().len()) as u16 + 2; // `+ 2` for margins
|
let lnum = num_digits(self.lines().len()) as u16 + 2; // `+ 2` for margins
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ impl CharKind {
|
|||||||
fn new(c: char) -> Self {
|
fn new(c: char) -> Self {
|
||||||
if c.is_whitespace() {
|
if c.is_whitespace() {
|
||||||
Self::Space
|
Self::Space
|
||||||
|
} else if c == '_' {
|
||||||
|
Self::Other
|
||||||
} else if c.is_ascii_punctuation() {
|
} else if c.is_ascii_punctuation() {
|
||||||
Self::Punct
|
Self::Punct
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1437,6 +1437,44 @@ fn test_delete_selection_before_insert() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clear_on_empty_text() {
|
||||||
|
let mut t = TextArea::default();
|
||||||
|
|
||||||
|
let was_cleared = t.clear();
|
||||||
|
assert!(!was_cleared);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clear_text() {
|
||||||
|
let mut t = TextArea::default();
|
||||||
|
let input = "
|
||||||
|
Hello world
|
||||||
|
|
||||||
|
Again
|
||||||
|
";
|
||||||
|
t.insert_str(input);
|
||||||
|
let was_cleared = t.clear();
|
||||||
|
assert!(
|
||||||
|
t.lines().join("\n").is_empty(),
|
||||||
|
"Textarea should be empty after clearing the text"
|
||||||
|
);
|
||||||
|
assert!(was_cleared);
|
||||||
|
|
||||||
|
t.undo();
|
||||||
|
assert_eq!(
|
||||||
|
t.lines().join("\n"),
|
||||||
|
input,
|
||||||
|
"Textare should have the previous content before the reset. Orginal input: {input}",
|
||||||
|
);
|
||||||
|
|
||||||
|
t.redo();
|
||||||
|
assert!(
|
||||||
|
t.lines().join("\n").is_empty(),
|
||||||
|
"Textarea should be empty after clearing the text again"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_undo_redo_stop_selection() {
|
fn test_undo_redo_stop_selection() {
|
||||||
fn check(t: &mut TextArea, f: fn(&mut TextArea) -> bool) {
|
fn check(t: &mut TextArea, f: fn(&mut TextArea) -> bool) {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user