зеркало из
https://github.com/glebtv/tui-textarea.git
synced 2026-09-08 00:15:50 +03:00
add EditKind::InsertChunks and EditKind::DeleteChunk edit kinds
Этот коммит содержится в:
414
src/history.rs
414
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),
|
||||||
|
DeleteChunk(Vec<String>, 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,36 @@ 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, i) => {
|
||||||
|
debug_assert!(c.len() > 1, "Chunk size must be > 1: {:?}", c);
|
||||||
|
|
||||||
|
// 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, i) => {
|
||||||
|
debug_assert!(c.len() > 1, "Chunk size must be > 1: {:?}", c);
|
||||||
|
|
||||||
|
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 +73,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, i) => DeleteChunk(c, i),
|
||||||
|
DeleteChunk(c, i) => InsertChunk(c, i),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,3 +175,376 @@ 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",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
][..],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
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(), offset);
|
||||||
|
edit.apply(row, &mut lines);
|
||||||
|
assert_eq!(
|
||||||
|
&lines, expected,
|
||||||
|
"{:?} at {:?} with {:?}",
|
||||||
|
before, pos, input,
|
||||||
|
);
|
||||||
|
|
||||||
|
let edit = EditKind::DeleteChunk(chunk, offset);
|
||||||
|
edit.apply(row, &mut lines);
|
||||||
|
assert_eq!(
|
||||||
|
&lines, &before,
|
||||||
|
"{:?} at {:?} with {:?}",
|
||||||
|
before, pos, input,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use crate::widget::{Renderer, Viewport};
|
|||||||
use crate::word::{find_word_end_forward, find_word_start_backward};
|
use crate::word::{find_word_end_forward, find_word_start_backward};
|
||||||
#[cfg(feature = "ratatui")]
|
#[cfg(feature = "ratatui")]
|
||||||
use ratatui::text::Line;
|
use ratatui::text::Line;
|
||||||
|
use std::mem;
|
||||||
#[cfg(feature = "tuirs")]
|
#[cfg(feature = "tuirs")]
|
||||||
use tui::text::Spans as Line;
|
use tui::text::Spans as Line;
|
||||||
use unicode_width::UnicodeWidthChar as _;
|
use unicode_width::UnicodeWidthChar as _;
|
||||||
@@ -612,8 +613,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 +621,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(&mut self, s: &str) -> bool {
|
||||||
let s = s.into();
|
let mut lines: Vec<_> = s.lines().map(ToString::to_string).collect();
|
||||||
|
match lines.len() {
|
||||||
|
0 => false,
|
||||||
|
1 => self.insert_piece(mem::take(&mut lines[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,
|
||||||
|
col + chunk[chunk.len() - 1].chars().count(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let edit = EditKind::InsertChunk(chunk, 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 +665,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,7 +678,7 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,7 +710,7 @@ 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;
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -704,7 +738,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 +748,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.
|
||||||
@@ -937,7 +971,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
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user