зеркало из
https://github.com/glebtv/tui-textarea.git
synced 2026-09-08 00:15:50 +03:00
allow TextArea::delete_str to delete across newlines
Этот коммит содержится в:
@@ -681,17 +681,93 @@ impl<'a> TextArea<'a> {
|
|||||||
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();
|
||||||
|
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 = deleted.join("\n"); // TODO
|
||||||
|
self.push_history(EditKind::DeleteChunk(deleted, first_start), self.cursor);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_piece(&mut self, col: usize, chars: usize) -> bool {
|
||||||
if chars == 0 {
|
if chars == 0 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -866,7 +942,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
|
||||||
@@ -887,7 +963,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()
|
||||||
@@ -914,9 +990,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()
|
||||||
}
|
}
|
||||||
@@ -942,11 +1018,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()
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ fn test_insert_str_one_line() {
|
|||||||
want.insert(i, 'x');
|
want.insert(i, 'x');
|
||||||
assert_eq!(&want, have, "{}", i);
|
assert_eq!(&want, have, "{}", i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut t = TextArea::default();
|
||||||
|
assert!(t.insert_str("x"));
|
||||||
|
assert_eq!(t.lines(), ["x"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -385,3 +389,317 @@ fn test_insert_str_multiple_lines() {
|
|||||||
assert_eq!(t.lines(), expected, "{:?}", test);
|
assert_eq!(t.lines(), expected, "{:?}", test);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_str_delete_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_delete_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_delete_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user