зеркало из
https://github.com/glebtv/tui-textarea.git
synced 2026-09-08 08:25:51 +03:00
Merge branch 'multiline' (fixes #42)
Этот коммит содержится в:
507
src/history.rs
507
src/history.rs
@@ -6,12 +6,14 @@ pub enum EditKind {
|
|||||||
DeleteChar(char, usize),
|
DeleteChar(char, usize),
|
||||||
InsertNewline(usize),
|
InsertNewline(usize),
|
||||||
DeleteNewline(usize),
|
DeleteNewline(usize),
|
||||||
Insert(String, usize),
|
InsertStr(String, usize),
|
||||||
Remove(String, usize),
|
DeleteStr(String, usize),
|
||||||
|
InsertChunk(Vec<String>, usize, usize),
|
||||||
|
DeleteChunk(Vec<String>, usize, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditKind {
|
impl EditKind {
|
||||||
fn apply(&self, row: usize, lines: &mut Vec<String>) {
|
pub(crate) fn apply(&self, row: usize, lines: &mut Vec<String>) {
|
||||||
match self {
|
match self {
|
||||||
EditKind::InsertChar(c, i) => {
|
EditKind::InsertChar(c, i) => {
|
||||||
lines[row].insert(*i, *c);
|
lines[row].insert(*i, *c);
|
||||||
@@ -31,13 +33,38 @@ impl EditKind {
|
|||||||
lines[row - 1].push_str(&line);
|
lines[row - 1].push_str(&line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EditKind::Insert(s, i) => {
|
EditKind::InsertStr(s, i) => {
|
||||||
lines[row].insert_str(*i, s.as_str());
|
lines[row].insert_str(*i, s.as_str());
|
||||||
}
|
}
|
||||||
EditKind::Remove(s, i) => {
|
EditKind::DeleteStr(s, i) => {
|
||||||
let end = *i + s.len();
|
let end = *i + s.len();
|
||||||
lines[row].replace_range(*i..end, "");
|
lines[row].replace_range(*i..end, "");
|
||||||
}
|
}
|
||||||
|
EditKind::InsertChunk(c, row, i) => {
|
||||||
|
debug_assert!(c.len() > 1, "Chunk size must be > 1: {:?}", c);
|
||||||
|
let row = *row;
|
||||||
|
|
||||||
|
// Handle first line of chunk
|
||||||
|
let first_line = &mut lines[row];
|
||||||
|
let mut last_line = first_line.drain(*i..).as_str().to_string();
|
||||||
|
first_line.push_str(&c[0]);
|
||||||
|
|
||||||
|
// Handle last line of chunk
|
||||||
|
last_line.insert_str(0, c.last().unwrap());
|
||||||
|
lines.insert(row + 1, last_line);
|
||||||
|
|
||||||
|
// Handle last line of chunk
|
||||||
|
lines.splice(row + 1..row + 1, c[1..c.len() - 1].iter().cloned());
|
||||||
|
}
|
||||||
|
EditKind::DeleteChunk(c, row, i) => {
|
||||||
|
debug_assert!(c.len() > 1, "Chunk size must be > 1: {:?}", c);
|
||||||
|
let row = *row;
|
||||||
|
|
||||||
|
lines[row].truncate(*i);
|
||||||
|
let mut last_line = lines.drain(row + 1..row + c.len()).last().unwrap();
|
||||||
|
last_line.drain(..c[c.len() - 1].len());
|
||||||
|
lines[row].push_str(&last_line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,8 +75,10 @@ impl EditKind {
|
|||||||
DeleteChar(c, i) => InsertChar(c, i),
|
DeleteChar(c, i) => InsertChar(c, i),
|
||||||
InsertNewline(i) => DeleteNewline(i),
|
InsertNewline(i) => DeleteNewline(i),
|
||||||
DeleteNewline(i) => InsertNewline(i),
|
DeleteNewline(i) => InsertNewline(i),
|
||||||
Insert(s, i) => Remove(s, i),
|
InsertStr(s, i) => DeleteStr(s, i),
|
||||||
Remove(s, i) => Insert(s, i),
|
DeleteStr(s, i) => InsertStr(s, i),
|
||||||
|
InsertChunk(c, r, i) => DeleteChunk(c, r, i),
|
||||||
|
DeleteChunk(c, r, i) => InsertChunk(c, r, i),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,3 +177,467 @@ impl History {
|
|||||||
self.max_items
|
self.max_items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_delete_chunk() {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let tests = [
|
||||||
|
// Positions
|
||||||
|
(
|
||||||
|
// Text before edit
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
// (row, offset) position before edit
|
||||||
|
(0, 0),
|
||||||
|
// Chunk to be inserted
|
||||||
|
&[
|
||||||
|
"x", "y",
|
||||||
|
][..],
|
||||||
|
// Text after edit
|
||||||
|
&[
|
||||||
|
"x",
|
||||||
|
"yab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(0, 1),
|
||||||
|
&[
|
||||||
|
"x", "y",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"ax",
|
||||||
|
"yb",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(0, 2),
|
||||||
|
&[
|
||||||
|
"x", "y",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"abx",
|
||||||
|
"y",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 0),
|
||||||
|
&[
|
||||||
|
"x", "y",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"x",
|
||||||
|
"ycd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 1),
|
||||||
|
&[
|
||||||
|
"x", "y",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cx",
|
||||||
|
"yd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 2),
|
||||||
|
&[
|
||||||
|
"x", "y",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cdx",
|
||||||
|
"y",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(2, 0),
|
||||||
|
&[
|
||||||
|
"x", "y",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"x",
|
||||||
|
"yef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(2, 1),
|
||||||
|
&[
|
||||||
|
"x", "y",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ex",
|
||||||
|
"yf",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(2, 2),
|
||||||
|
&[
|
||||||
|
"x", "y",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"efx",
|
||||||
|
"y",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
// More than 2 lines
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 1),
|
||||||
|
&[
|
||||||
|
"x", "y", "z", "w"
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cx",
|
||||||
|
"y",
|
||||||
|
"z",
|
||||||
|
"wd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
// Empty lines
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
&[
|
||||||
|
"x", "y", "z"
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"z",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
(1, 0),
|
||||||
|
&[
|
||||||
|
"x", "y", "z"
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"z",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
(2, 0),
|
||||||
|
&[
|
||||||
|
"x", "y", "z"
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"z",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
// Empty buffer
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
&[
|
||||||
|
"x", "y", "z"
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"z",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
// Insert empty lines
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
&[
|
||||||
|
"", "", "",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 0),
|
||||||
|
&[
|
||||||
|
"", "", "",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 1),
|
||||||
|
&[
|
||||||
|
"", "", "",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"c",
|
||||||
|
"",
|
||||||
|
"d",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 2),
|
||||||
|
&[
|
||||||
|
"", "", "",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(2, 2),
|
||||||
|
&[
|
||||||
|
"", "", "",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
// Multi-byte characters
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
&[
|
||||||
|
"🐷", "🐼", "🐴",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"🐷",
|
||||||
|
"🐼",
|
||||||
|
"🐴🐶🐱",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
(0, 4 * 2),
|
||||||
|
&[
|
||||||
|
"🐷", "🐼", "🐴",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"🐶🐱🐷",
|
||||||
|
"🐼",
|
||||||
|
"🐴",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
(1, 0),
|
||||||
|
&[
|
||||||
|
"🐷", "🐼", "🐴",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐷",
|
||||||
|
"🐼",
|
||||||
|
"🐴🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
(1, 4 * 1),
|
||||||
|
&[
|
||||||
|
"🐷", "🐼", "🐴",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐮🐷",
|
||||||
|
"🐼",
|
||||||
|
"🐴🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
(2, 4 * 2),
|
||||||
|
&[
|
||||||
|
"🐷", "🐼", "🐴",
|
||||||
|
][..],
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭🐷",
|
||||||
|
"🐼",
|
||||||
|
"🐴",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (before, pos, input, expected) in tests {
|
||||||
|
let (row, offset) = pos;
|
||||||
|
let mut lines: Vec<_> = before.iter().map(|s| s.to_string()).collect();
|
||||||
|
let chunk: Vec<_> = input.iter().map(|s| s.to_string()).collect();
|
||||||
|
|
||||||
|
let edit = EditKind::InsertChunk(chunk.clone(), row, offset);
|
||||||
|
edit.apply(row, &mut lines);
|
||||||
|
assert_eq!(
|
||||||
|
&lines, expected,
|
||||||
|
"{:?} at {:?} with {:?}",
|
||||||
|
before, pos, input,
|
||||||
|
);
|
||||||
|
|
||||||
|
let edit = EditKind::DeleteChunk(chunk, row, offset);
|
||||||
|
edit.apply(row, &mut lines);
|
||||||
|
assert_eq!(
|
||||||
|
&lines, &before,
|
||||||
|
"{:?} at {:?} with {:?}",
|
||||||
|
before, pos, input,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
200
src/textarea.rs
200
src/textarea.rs
@@ -17,6 +17,33 @@ use ratatui::text::Line;
|
|||||||
use tui::text::Spans as Line;
|
use tui::text::Spans as Line;
|
||||||
use unicode_width::UnicodeWidthChar as _;
|
use unicode_width::UnicodeWidthChar as _;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum YankText {
|
||||||
|
Piece(String),
|
||||||
|
Chunk(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for YankText {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Piece(String::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Into<String>> From<S> for YankText {
|
||||||
|
fn from(s: S) -> Self {
|
||||||
|
Self::Piece(s.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for YankText {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Piece(s) => s.clone(),
|
||||||
|
Self::Chunk(ss) => ss.join("\n"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A type to manage state of textarea.
|
/// A type to manage state of textarea.
|
||||||
///
|
///
|
||||||
/// [`TextArea::default`] creates an empty textarea. [`TextArea::new`] creates a textarea with given text lines.
|
/// [`TextArea::default`] creates an empty textarea. [`TextArea::new`] creates a textarea with given text lines.
|
||||||
@@ -50,7 +77,7 @@ pub struct TextArea<'a> {
|
|||||||
line_number_style: Option<Style>,
|
line_number_style: Option<Style>,
|
||||||
pub(crate) viewport: Viewport,
|
pub(crate) viewport: Viewport,
|
||||||
cursor_style: Style,
|
cursor_style: Style,
|
||||||
yank: String,
|
yank: YankText,
|
||||||
#[cfg(feature = "search")]
|
#[cfg(feature = "search")]
|
||||||
search: Search,
|
search: Search,
|
||||||
alignment: Alignment,
|
alignment: Alignment,
|
||||||
@@ -150,7 +177,7 @@ impl<'a> TextArea<'a> {
|
|||||||
line_number_style: None,
|
line_number_style: None,
|
||||||
viewport: Viewport::default(),
|
viewport: Viewport::default(),
|
||||||
cursor_style: Style::default().add_modifier(Modifier::REVERSED),
|
cursor_style: Style::default().add_modifier(Modifier::REVERSED),
|
||||||
yank: String::new(),
|
yank: YankText::default(),
|
||||||
#[cfg(feature = "search")]
|
#[cfg(feature = "search")]
|
||||||
search: Search::default(),
|
search: Search::default(),
|
||||||
alignment: Alignment::Left,
|
alignment: Alignment::Left,
|
||||||
@@ -612,8 +639,7 @@ impl<'a> TextArea<'a> {
|
|||||||
self.push_history(EditKind::InsertChar(c, i), (row, col));
|
self.push_history(EditKind::InsertChar(c, i), (row, col));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a string at current cursor position. Currently the string must not contain any newlines. This method
|
/// Insert a string at current cursor position. This method returns if some text was inserted or not in the textarea.
|
||||||
/// returns if some text was inserted or not in the textarea.
|
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::TextArea;
|
/// use tui_textarea::TextArea;
|
||||||
///
|
///
|
||||||
@@ -621,9 +647,43 @@ impl<'a> TextArea<'a> {
|
|||||||
///
|
///
|
||||||
/// textarea.insert_str("hello");
|
/// textarea.insert_str("hello");
|
||||||
/// assert_eq!(textarea.lines(), ["hello"]);
|
/// assert_eq!(textarea.lines(), ["hello"]);
|
||||||
|
///
|
||||||
|
/// textarea.insert_str(", world\ngoodbye, world");
|
||||||
|
/// assert_eq!(textarea.lines(), ["hello, world", "goodbye, world"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn insert_str<S: Into<String>>(&mut self, s: S) -> bool {
|
pub fn insert_str<S: AsRef<str>>(&mut self, s: S) -> bool {
|
||||||
let s = s.into();
|
let mut lines: Vec<_> = s.as_ref().lines().map(ToString::to_string).collect();
|
||||||
|
match lines.len() {
|
||||||
|
0 => false,
|
||||||
|
1 => self.insert_piece(lines.remove(0)),
|
||||||
|
_ => self.insert_chunk(lines),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_chunk(&mut self, chunk: Vec<String>) -> bool {
|
||||||
|
debug_assert!(chunk.len() > 1, "Chunk size must be > 1: {:?}", chunk);
|
||||||
|
|
||||||
|
let (row, col) = self.cursor;
|
||||||
|
let line = &mut self.lines[row];
|
||||||
|
let i = line
|
||||||
|
.char_indices()
|
||||||
|
.nth(col)
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.unwrap_or(line.len());
|
||||||
|
|
||||||
|
self.cursor = (
|
||||||
|
row + chunk.len() - 1,
|
||||||
|
chunk[chunk.len() - 1].chars().count(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let edit = EditKind::InsertChunk(chunk, row, i);
|
||||||
|
edit.apply(row, &mut self.lines);
|
||||||
|
|
||||||
|
self.push_history(edit, (row, col));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_piece(&mut self, s: String) -> bool {
|
||||||
if s.is_empty() {
|
if s.is_empty() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -631,7 +691,7 @@ impl<'a> TextArea<'a> {
|
|||||||
let (row, col) = self.cursor;
|
let (row, col) = self.cursor;
|
||||||
let line = &mut self.lines[row];
|
let line = &mut self.lines[row];
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
!line.contains('\n'),
|
!s.contains('\n'),
|
||||||
"string given to insert_str must not contain newline: {:?}",
|
"string given to insert_str must not contain newline: {:?}",
|
||||||
line,
|
line,
|
||||||
);
|
);
|
||||||
@@ -644,21 +704,104 @@ impl<'a> TextArea<'a> {
|
|||||||
line.insert_str(i, &s);
|
line.insert_str(i, &s);
|
||||||
|
|
||||||
self.cursor.1 += s.chars().count();
|
self.cursor.1 += s.chars().count();
|
||||||
self.push_history(EditKind::Insert(s, i), (row, col));
|
self.push_history(EditKind::InsertStr(s, i), (row, col));
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a string in current cursor line. The `chars` parameter means number of characters, not a byte length of
|
/// Delete a string from the current cursor position. The `chars` parameter means number of characters, not a byte
|
||||||
/// the string. This method returns if some text was deleted or not in the textarea.
|
/// length of the string. Newlines at the end of lines are counted in the number. This method returns if some text
|
||||||
|
/// was deleted or not.
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::TextArea;
|
/// use tui_textarea::{TextArea, CursorMove};
|
||||||
///
|
///
|
||||||
/// let mut textarea = TextArea::from(["🐱🐶🐰🐮"]);
|
/// let mut textarea = TextArea::from(["🐱🐶🐰🐮"]);
|
||||||
|
/// textarea.move_cursor(CursorMove::Forward);
|
||||||
///
|
///
|
||||||
/// textarea.delete_str(1, 2);
|
/// textarea.delete_str(2);
|
||||||
/// assert_eq!(textarea.lines(), ["🐱🐮"]);
|
/// assert_eq!(textarea.lines(), ["🐱🐮"]);
|
||||||
|
///
|
||||||
|
/// let mut textarea = TextArea::from(["🐱", "🐶", "🐰", "🐮"]);
|
||||||
|
/// textarea.move_cursor(CursorMove::Down);
|
||||||
|
///
|
||||||
|
/// textarea.delete_str(4); // Deletes 🐶, \n, 🐰, \n
|
||||||
|
/// assert_eq!(textarea.lines(), ["🐱", "🐮"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn delete_str(&mut self, col: usize, chars: usize) -> bool {
|
pub fn delete_str(&mut self, chars: usize) -> bool {
|
||||||
|
if chars == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (row, col) = self.cursor;
|
||||||
|
|
||||||
|
let mut remaining = chars;
|
||||||
|
let mut find_end = move |line: &str| {
|
||||||
|
for (i, _) in line.char_indices() {
|
||||||
|
if remaining == 0 {
|
||||||
|
return Some(i);
|
||||||
|
}
|
||||||
|
remaining -= 1;
|
||||||
|
}
|
||||||
|
if remaining == 0 {
|
||||||
|
Some(line.len())
|
||||||
|
} else {
|
||||||
|
remaining -= 1;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let line = &self.lines[row];
|
||||||
|
let first_start = {
|
||||||
|
line.char_indices()
|
||||||
|
.nth(col)
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.unwrap_or(line.len())
|
||||||
|
};
|
||||||
|
|
||||||
|
// First line
|
||||||
|
if let Some(offset) = find_end(&line[first_start..]) {
|
||||||
|
let removed = self.lines[row]
|
||||||
|
.drain(first_start..first_start + offset)
|
||||||
|
.as_str()
|
||||||
|
.to_string();
|
||||||
|
self.yank = removed.clone().into();
|
||||||
|
self.push_history(EditKind::DeleteStr(removed, first_start), self.cursor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut r = row + 1;
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
while r < self.lines.len() {
|
||||||
|
let line = &self.lines[r];
|
||||||
|
if let Some(offset) = find_end(line) {
|
||||||
|
i = offset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
r += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut deleted = vec![self.lines[row].drain(first_start..).as_str().to_string()];
|
||||||
|
deleted.extend(self.lines.drain(row + 1..r));
|
||||||
|
if row + 1 < self.lines.len() {
|
||||||
|
let mut last_line = self.lines.remove(row + 1);
|
||||||
|
self.lines[row].push_str(&last_line[i..]);
|
||||||
|
last_line.truncate(i);
|
||||||
|
deleted.push(last_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.yank = YankText::Chunk(deleted.clone());
|
||||||
|
|
||||||
|
let edit = if deleted.len() == 1 {
|
||||||
|
EditKind::DeleteStr(deleted.remove(0), first_start)
|
||||||
|
} else {
|
||||||
|
EditKind::DeleteChunk(deleted, row, first_start)
|
||||||
|
};
|
||||||
|
self.push_history(edit, self.cursor);
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_piece(&mut self, col: usize, chars: usize) -> bool {
|
||||||
if chars == 0 {
|
if chars == 0 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -676,8 +819,8 @@ impl<'a> TextArea<'a> {
|
|||||||
line.replace_range(i..i + bytes, "");
|
line.replace_range(i..i + bytes, "");
|
||||||
|
|
||||||
self.cursor = (row, col);
|
self.cursor = (row, col);
|
||||||
self.push_history(EditKind::Remove(removed.clone(), i), cursor_before);
|
self.push_history(EditKind::DeleteStr(removed.clone(), i), cursor_before);
|
||||||
self.yank = removed;
|
self.yank = removed.into();
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@@ -704,7 +847,7 @@ impl<'a> TextArea<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.hard_tab_indent {
|
if self.hard_tab_indent {
|
||||||
return self.insert_str("\t");
|
return self.insert_piece("\t".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (row, col) = self.cursor;
|
let (row, col) = self.cursor;
|
||||||
@@ -714,7 +857,7 @@ impl<'a> TextArea<'a> {
|
|||||||
.map(|c| c.width().unwrap_or(0))
|
.map(|c| c.width().unwrap_or(0))
|
||||||
.sum();
|
.sum();
|
||||||
let len = self.tab_len - (width % self.tab_len as usize) as u8;
|
let len = self.tab_len - (width % self.tab_len as usize) as u8;
|
||||||
self.insert_str(spaces(len))
|
self.insert_piece(spaces(len).to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a newline at current cursor position.
|
/// Insert a newline at current cursor position.
|
||||||
@@ -833,7 +976,7 @@ impl<'a> TextArea<'a> {
|
|||||||
/// assert_eq!(textarea.lines(), ["ab"]);
|
/// assert_eq!(textarea.lines(), ["ab"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn delete_line_by_end(&mut self) -> bool {
|
pub fn delete_line_by_end(&mut self) -> bool {
|
||||||
if self.delete_str(self.cursor.1, usize::MAX) {
|
if self.delete_piece(self.cursor.1, usize::MAX) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
self.delete_next_char() // At the end of the line. Try to delete next line
|
self.delete_next_char() // At the end of the line. Try to delete next line
|
||||||
@@ -854,7 +997,7 @@ impl<'a> TextArea<'a> {
|
|||||||
/// assert_eq!(textarea.lines(), ["cde"]);
|
/// assert_eq!(textarea.lines(), ["cde"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn delete_line_by_head(&mut self) -> bool {
|
pub fn delete_line_by_head(&mut self) -> bool {
|
||||||
if self.delete_str(0, self.cursor.1) {
|
if self.delete_piece(0, self.cursor.1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
self.delete_newline()
|
self.delete_newline()
|
||||||
@@ -881,9 +1024,9 @@ impl<'a> TextArea<'a> {
|
|||||||
pub fn delete_word(&mut self) -> bool {
|
pub fn delete_word(&mut self) -> bool {
|
||||||
let (r, c) = self.cursor;
|
let (r, c) = self.cursor;
|
||||||
if let Some(col) = find_word_start_backward(&self.lines[r], c) {
|
if let Some(col) = find_word_start_backward(&self.lines[r], c) {
|
||||||
self.delete_str(col, c - col)
|
self.delete_piece(col, c - col)
|
||||||
} else if c > 0 {
|
} else if c > 0 {
|
||||||
self.delete_str(0, c)
|
self.delete_piece(0, c)
|
||||||
} else {
|
} else {
|
||||||
self.delete_newline()
|
self.delete_newline()
|
||||||
}
|
}
|
||||||
@@ -909,11 +1052,11 @@ impl<'a> TextArea<'a> {
|
|||||||
let (r, c) = self.cursor;
|
let (r, c) = self.cursor;
|
||||||
let line = &self.lines[r];
|
let line = &self.lines[r];
|
||||||
if let Some(col) = find_word_end_forward(line, c) {
|
if let Some(col) = find_word_end_forward(line, c) {
|
||||||
self.delete_str(c, col - c)
|
self.delete_piece(c, col - c)
|
||||||
} else {
|
} else {
|
||||||
let end_col = line.chars().count();
|
let end_col = line.chars().count();
|
||||||
if c < end_col {
|
if c < end_col {
|
||||||
self.delete_str(c, end_col - c)
|
self.delete_piece(c, end_col - c)
|
||||||
} else if r + 1 < self.lines.len() {
|
} else if r + 1 < self.lines.len() {
|
||||||
self.cursor = (r + 1, 0);
|
self.cursor = (r + 1, 0);
|
||||||
self.delete_newline()
|
self.delete_newline()
|
||||||
@@ -937,7 +1080,7 @@ impl<'a> TextArea<'a> {
|
|||||||
/// assert_eq!(textarea.lines(), [" bbb cccaaa"]);
|
/// assert_eq!(textarea.lines(), [" bbb cccaaa"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn paste(&mut self) -> bool {
|
pub fn paste(&mut self) -> bool {
|
||||||
self.insert_str(self.yank.to_string())
|
self.insert_piece(self.yank.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the cursor to the position specified by the [`CursorMove`] parameter. For each kind of cursor moves, see
|
/// Move the cursor to the position specified by the [`CursorMove`] parameter. For each kind of cursor moves, see
|
||||||
@@ -1481,7 +1624,8 @@ impl<'a> TextArea<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the yanked text. Text is automatically yanked when deleting strings by [`TextArea::delete_line_by_head`],
|
/// Get the yanked text. Text is automatically yanked when deleting strings by [`TextArea::delete_line_by_head`],
|
||||||
/// [`TextArea::delete_line_by_end`], [`TextArea::delete_word`], [`TextArea::delete_next_word`].
|
/// [`TextArea::delete_line_by_end`], [`TextArea::delete_word`], [`TextArea::delete_next_word`],
|
||||||
|
/// [`TextArea::delete_str`].
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::TextArea;
|
/// use tui_textarea::TextArea;
|
||||||
///
|
///
|
||||||
@@ -1490,8 +1634,8 @@ impl<'a> TextArea<'a> {
|
|||||||
/// textarea.delete_next_word();
|
/// textarea.delete_next_word();
|
||||||
/// assert_eq!(textarea.yank_text(), "abc");
|
/// assert_eq!(textarea.yank_text(), "abc");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn yank_text(&'a self) -> &'a str {
|
pub fn yank_text(&self) -> String {
|
||||||
&self.yank
|
self.yank.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a yanked text. The text can be inserted by [`TextArea::paste`]. The string passed to method must not contain
|
/// Set a yanked text. The text can be inserted by [`TextArea::paste`]. The string passed to method must not contain
|
||||||
@@ -1506,7 +1650,7 @@ impl<'a> TextArea<'a> {
|
|||||||
/// assert_eq!(textarea.lines(), ["hello, world"]);
|
/// assert_eq!(textarea.lines(), ["hello, world"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_yank_text(&mut self, text: impl Into<String>) {
|
pub fn set_yank_text(&mut self, text: impl Into<String>) {
|
||||||
self.yank = text.into();
|
self.yank = text.into().into();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a regular expression pattern for text search. Setting an empty string stops the text search.
|
/// Set a regular expression pattern for text search. Setting an empty string stops the text search.
|
||||||
|
|||||||
@@ -24,3 +24,777 @@ fn test_insert_soft_tab() {
|
|||||||
assert_eq!(line, expected, "{:?}", test);
|
assert_eq!(line, expected, "{:?}", test);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_insert_str_one_line() {
|
||||||
|
for i in 0..="ab".len() {
|
||||||
|
let mut t = TextArea::from(["ab"]);
|
||||||
|
t.move_cursor(CursorMove::Jump(0, i as u16));
|
||||||
|
assert!(t.insert_str("x"), "{}", i);
|
||||||
|
let have = &t.lines()[0];
|
||||||
|
|
||||||
|
let mut want = "ab".to_string();
|
||||||
|
want.insert(i, 'x');
|
||||||
|
assert_eq!(&want, have, "{}", i);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut t = TextArea::default();
|
||||||
|
assert!(t.insert_str("x"));
|
||||||
|
assert_eq!(t.lines(), ["x"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_insert_str_empty_line() {
|
||||||
|
let mut t = TextArea::from(["ab"]);
|
||||||
|
assert!(!t.insert_str(""));
|
||||||
|
assert_eq!(t.lines(), ["ab"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_insert_str_multiple_lines() {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let tests = [
|
||||||
|
// Positions
|
||||||
|
(
|
||||||
|
// Text before edit
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
// (row, offset) position before edit
|
||||||
|
(0, 0),
|
||||||
|
// String to be inserted
|
||||||
|
"x\ny",
|
||||||
|
// (row, offset) position after edit
|
||||||
|
(1, 1),
|
||||||
|
// Text after edit
|
||||||
|
&[
|
||||||
|
"x",
|
||||||
|
"yab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(0, 1),
|
||||||
|
"x\ny",
|
||||||
|
(1, 1),
|
||||||
|
&[
|
||||||
|
"ax",
|
||||||
|
"yb",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(0, 2),
|
||||||
|
"x\ny",
|
||||||
|
(1, 1),
|
||||||
|
&[
|
||||||
|
"abx",
|
||||||
|
"y",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 0),
|
||||||
|
"x\ny",
|
||||||
|
(2, 1),
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"x",
|
||||||
|
"ycd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 1),
|
||||||
|
"x\ny",
|
||||||
|
(2, 1),
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cx",
|
||||||
|
"yd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 2),
|
||||||
|
"x\ny",
|
||||||
|
(2, 1),
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cdx",
|
||||||
|
"y",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(2, 0),
|
||||||
|
"x\ny",
|
||||||
|
(3, 1),
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"x",
|
||||||
|
"yef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(2, 1),
|
||||||
|
"x\ny",
|
||||||
|
(3, 1),
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ex",
|
||||||
|
"yf",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(2, 2),
|
||||||
|
"x\ny",
|
||||||
|
(3, 1),
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"efx",
|
||||||
|
"y",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
// More than 2 lines
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 1),
|
||||||
|
"x\ny\nz\nw",
|
||||||
|
(4, 1),
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cx",
|
||||||
|
"y",
|
||||||
|
"z",
|
||||||
|
"wd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
// Empty lines
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
"x\ny\nz",
|
||||||
|
(2, 1),
|
||||||
|
&[
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"z",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
(1, 0),
|
||||||
|
"x\ny\nz",
|
||||||
|
(3, 1),
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"z",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
(2, 0),
|
||||||
|
"x\ny\nz",
|
||||||
|
(4, 1),
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"z",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
// Empty buffer
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
"x\ny\nz",
|
||||||
|
(2, 1),
|
||||||
|
&[
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"z",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
// Insert empty lines
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
"\n\n\n",
|
||||||
|
(2, 0),
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 0),
|
||||||
|
"\n\n\n",
|
||||||
|
(3, 0),
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 1),
|
||||||
|
"\n\n\n",
|
||||||
|
(3, 0),
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"c",
|
||||||
|
"",
|
||||||
|
"d",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 2),
|
||||||
|
"\n\n\n",
|
||||||
|
(3, 0),
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(2, 2),
|
||||||
|
"\n\n\n",
|
||||||
|
(4, 0),
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
// Multi-byte characters
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
"🐷\n🐼\n🐴",
|
||||||
|
(2, 1),
|
||||||
|
&[
|
||||||
|
"🐷",
|
||||||
|
"🐼",
|
||||||
|
"🐴🐶🐱",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
(0, 2),
|
||||||
|
"🐷\n🐼\n🐴",
|
||||||
|
(2, 1),
|
||||||
|
&[
|
||||||
|
"🐶🐱🐷",
|
||||||
|
"🐼",
|
||||||
|
"🐴",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
(1, 0),
|
||||||
|
"🐷\n🐼\n🐴",
|
||||||
|
(3, 1),
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐷",
|
||||||
|
"🐼",
|
||||||
|
"🐴🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
(1, 1),
|
||||||
|
"🐷\n🐼\n🐴",
|
||||||
|
(3, 1),
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐮🐷",
|
||||||
|
"🐼",
|
||||||
|
"🐴🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭",
|
||||||
|
][..],
|
||||||
|
(2, 2),
|
||||||
|
"🐷\n🐼\n🐴",
|
||||||
|
(4, 1),
|
||||||
|
&[
|
||||||
|
"🐶🐱",
|
||||||
|
"🐮🐰",
|
||||||
|
"🐧🐭🐷",
|
||||||
|
"🐼",
|
||||||
|
"🐴",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for test in tests {
|
||||||
|
let (before, before_pos, input, after_pos, expected) = test;
|
||||||
|
|
||||||
|
let mut t = TextArea::from(before.iter().map(|s| s.to_string()));
|
||||||
|
let (row, col) = before_pos;
|
||||||
|
t.move_cursor(CursorMove::Jump(row, col));
|
||||||
|
|
||||||
|
assert!(t.insert_str(input), "{:?}", test);
|
||||||
|
assert_eq!(t.cursor(), after_pos, "{:?}", test);
|
||||||
|
assert_eq!(t.lines(), expected, "{:?}", test);
|
||||||
|
|
||||||
|
assert!(t.undo(), "undo: {:?}", test);
|
||||||
|
assert_eq!(t.lines(), before, "content after undo: {:?}", test);
|
||||||
|
let before_pos = (row as _, col as _);
|
||||||
|
assert_eq!(t.cursor(), before_pos, "cursor after undo: {:?}", test);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_str_nothing() {
|
||||||
|
for i in 0..="ab".len() {
|
||||||
|
let mut t = TextArea::from(["ab"]);
|
||||||
|
assert!(!t.delete_str(0), "{}", i);
|
||||||
|
}
|
||||||
|
let mut t = TextArea::default();
|
||||||
|
assert!(!t.delete_str(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_str_within_line() {
|
||||||
|
for i in 0.."abc".len() {
|
||||||
|
for j in 1..="abc".len() - i {
|
||||||
|
let mut t = TextArea::from(["abc"]);
|
||||||
|
t.move_cursor(CursorMove::Jump(0, i as _));
|
||||||
|
assert!(t.delete_str(j), "at {}, size={}", i, j);
|
||||||
|
let have = &t.lines()[0];
|
||||||
|
|
||||||
|
let mut want = "abc".to_string();
|
||||||
|
want.drain(i..i + j);
|
||||||
|
assert_eq!(&want, have, "at {}, size={}", i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_str_multiple_lines() {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let tests = [
|
||||||
|
// Length
|
||||||
|
(
|
||||||
|
// Text before edit
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
// (row, offset) cursor position
|
||||||
|
(0, 0),
|
||||||
|
// Chars to be deleted
|
||||||
|
3,
|
||||||
|
// Deleted text
|
||||||
|
"ab\n",
|
||||||
|
// Text after edit
|
||||||
|
&[
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
4,
|
||||||
|
"ab\nc",
|
||||||
|
&[
|
||||||
|
"d",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
5,
|
||||||
|
"ab\ncd",
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
6,
|
||||||
|
"ab\ncd\n",
|
||||||
|
&[
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
7,
|
||||||
|
"ab\ncd\ne",
|
||||||
|
&[
|
||||||
|
"f",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
8,
|
||||||
|
"ab\ncd\nef",
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
9,
|
||||||
|
"ab\ncd\nef",
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
// Positions
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(0, 1),
|
||||||
|
3,
|
||||||
|
"b\nc",
|
||||||
|
&[
|
||||||
|
"ad",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(0, 2),
|
||||||
|
4,
|
||||||
|
"\ncd\n",
|
||||||
|
&[
|
||||||
|
"abef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(1, 0),
|
||||||
|
4,
|
||||||
|
"cd\ne",
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"f",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(2, 0),
|
||||||
|
3,
|
||||||
|
"ef",
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(2, 1),
|
||||||
|
2,
|
||||||
|
"f",
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"e",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
(2, 2),
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
&[
|
||||||
|
"ab",
|
||||||
|
"cd",
|
||||||
|
"ef",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
// Empty lines
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
1,
|
||||||
|
"\n",
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
2,
|
||||||
|
"\n\n",
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
3,
|
||||||
|
"\n\n",
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
(1, 0),
|
||||||
|
1,
|
||||||
|
"\n",
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
(2, 0),
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
// Empty buffer
|
||||||
|
(
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
(0, 0),
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
&[
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for test in tests {
|
||||||
|
let (before, (row, col), chars, deleted, after) = test;
|
||||||
|
|
||||||
|
let mut t = TextArea::from(before.iter().map(|s| s.to_string()));
|
||||||
|
t.move_cursor(CursorMove::Jump(row as _, col as _));
|
||||||
|
|
||||||
|
assert!(t.delete_str(chars), "did not modified: {:?}", test);
|
||||||
|
assert_eq!(t.cursor(), (row, col), "cursor position: {:?}", test);
|
||||||
|
assert_eq!(t.lines(), after, "text buffer content: {:?}", test);
|
||||||
|
assert_eq!(t.yank_text(), deleted, "yanked text: {:?}", test);
|
||||||
|
|
||||||
|
assert!(t.undo(), "undo: {:?}", test);
|
||||||
|
assert_eq!(t.lines(), before, "content after undo: {:?}", test);
|
||||||
|
assert_eq!(t.cursor(), (row, col), "cursor after undo: {:?}", test);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user