зеркало из
https://github.com/glebtv/tui-textarea.git
synced 2026-09-03 22:35:50 +03:00
delete previous/next word
Этот коммит содержится в:
@@ -1,3 +1,4 @@
|
||||
use crate::word::{find_word_start_backward, find_word_start_forward};
|
||||
use std::cmp;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -16,25 +17,6 @@ pub enum CursorMove {
|
||||
ParagraphBack,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
enum CharKind {
|
||||
Space,
|
||||
Punct,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl CharKind {
|
||||
fn new(c: char) -> Self {
|
||||
if c.is_whitespace() {
|
||||
Self::Space
|
||||
} else if c.is_ascii_punctuation() {
|
||||
Self::Punct
|
||||
} else {
|
||||
Self::Other
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorMove {
|
||||
pub fn next_cursor(
|
||||
&self,
|
||||
@@ -76,19 +58,7 @@ impl CursorMove {
|
||||
Some((row, fit_col(col, &lines[row])))
|
||||
}
|
||||
WordForward => {
|
||||
fn find_word_forward(line: &str, col: usize) -> Option<usize> {
|
||||
let mut it = line.chars().enumerate().skip(col);
|
||||
let mut prev = CharKind::new(it.next()?.1);
|
||||
for (col, c) in it {
|
||||
let cur = CharKind::new(c);
|
||||
if cur != CharKind::Space && prev != cur {
|
||||
return Some(col);
|
||||
}
|
||||
prev = cur;
|
||||
}
|
||||
None
|
||||
}
|
||||
if let Some(col) = find_word_forward(&lines[row], col) {
|
||||
if let Some(col) = find_word_start_forward(&lines[row], col) {
|
||||
Some((row, col))
|
||||
} else if row + 1 < lines.len() {
|
||||
Some((row + 1, 0))
|
||||
@@ -97,28 +67,7 @@ impl CursorMove {
|
||||
}
|
||||
}
|
||||
WordBack => {
|
||||
fn find_word_back(line: &str, col: usize) -> Option<usize> {
|
||||
let idx = line
|
||||
.char_indices()
|
||||
.nth(col)
|
||||
.map(|(i, _)| i)
|
||||
.unwrap_or(line.len());
|
||||
let mut it = line[..idx].chars().rev().enumerate();
|
||||
let mut cur = CharKind::new(it.next()?.1);
|
||||
for (i, c) in it {
|
||||
let next = CharKind::new(c);
|
||||
if cur != CharKind::Space && next != cur {
|
||||
return Some(col - i);
|
||||
}
|
||||
cur = next;
|
||||
}
|
||||
if cur != CharKind::Space {
|
||||
Some(0)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
if let Some(col) = find_word_back(&lines[row], col) {
|
||||
if let Some(col) = find_word_start_backward(&lines[row], col) {
|
||||
Some((row, col))
|
||||
} else if row > 0 {
|
||||
Some((row - 1, lines[row - 1].chars().count()))
|
||||
|
||||
@@ -4,6 +4,7 @@ mod cursor;
|
||||
mod history;
|
||||
mod input;
|
||||
mod textarea;
|
||||
mod word;
|
||||
|
||||
pub use cursor::CursorMove;
|
||||
pub use input::{Input, Key};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::cursor::CursorMove;
|
||||
use crate::history::{Edit, EditKind, History};
|
||||
use crate::input::{Input, Key};
|
||||
use crate::word::{find_word_end_forward, find_word_start_backward};
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
use tui::buffer::Buffer;
|
||||
use tui::layout::Rect;
|
||||
@@ -74,7 +75,8 @@ impl<'a> TextArea<'a> {
|
||||
}
|
||||
| Input {
|
||||
key: Key::Backspace,
|
||||
..
|
||||
ctrl: false,
|
||||
alt: false,
|
||||
} => self.delete_char(),
|
||||
Input {
|
||||
key: Key::Char('d'),
|
||||
@@ -82,7 +84,9 @@ impl<'a> TextArea<'a> {
|
||||
alt: false,
|
||||
}
|
||||
| Input {
|
||||
key: Key::Delete, ..
|
||||
key: Key::Delete,
|
||||
ctrl: false,
|
||||
alt: false,
|
||||
} => self.delete_next_char(),
|
||||
Input {
|
||||
key: Key::Char('m'),
|
||||
@@ -102,6 +106,31 @@ impl<'a> TextArea<'a> {
|
||||
ctrl: true,
|
||||
alt: false,
|
||||
} => self.delete_line_by_head(),
|
||||
Input {
|
||||
key: Key::Char('w'),
|
||||
ctrl: true,
|
||||
alt: false,
|
||||
}
|
||||
| Input {
|
||||
key: Key::Char('h'),
|
||||
ctrl: false,
|
||||
alt: true,
|
||||
}
|
||||
| Input {
|
||||
key: Key::Backspace,
|
||||
ctrl: false,
|
||||
alt: true,
|
||||
} => self.delete_word(),
|
||||
Input {
|
||||
key: Key::Delete,
|
||||
ctrl: false,
|
||||
alt: true,
|
||||
}
|
||||
| Input {
|
||||
key: Key::Char('d'),
|
||||
ctrl: false,
|
||||
alt: true,
|
||||
} => self.delete_next_word(),
|
||||
Input {
|
||||
key: Key::Char('n'),
|
||||
ctrl: true,
|
||||
@@ -336,8 +365,6 @@ impl<'a> TextArea<'a> {
|
||||
let row = self.cursor.0;
|
||||
let line = &mut self.lines[row];
|
||||
if let Some((i, _)) = line.char_indices().nth(col) {
|
||||
// push/pop ' ' to preserve ' ' at the end of line
|
||||
line.pop();
|
||||
let bytes = line[i..]
|
||||
.char_indices()
|
||||
.nth(chars)
|
||||
@@ -345,7 +372,6 @@ impl<'a> TextArea<'a> {
|
||||
.unwrap_or_else(|| line[i..].len());
|
||||
let removed = line[i..i + bytes].to_string();
|
||||
line.replace_range(i..i + bytes, "");
|
||||
line.push(' ');
|
||||
|
||||
self.cursor = (row, col);
|
||||
self.push_history(EditKind::Remove(removed.clone(), i), (row, col));
|
||||
@@ -376,18 +402,25 @@ impl<'a> TextArea<'a> {
|
||||
self.push_history(EditKind::InsertNewline(idx), (row, col));
|
||||
}
|
||||
|
||||
pub fn delete_newline(&mut self) {
|
||||
let (row, col) = self.cursor;
|
||||
if row == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let line = self.lines.remove(row);
|
||||
let prev_line = &mut self.lines[row - 1];
|
||||
let prev_line_end = prev_line.len();
|
||||
|
||||
self.cursor = (row - 1, prev_line.chars().count());
|
||||
prev_line.push_str(&line);
|
||||
self.push_history(EditKind::DeleteNewline(prev_line_end), (row, col));
|
||||
}
|
||||
|
||||
pub fn delete_char(&mut self) {
|
||||
let (row, col) = self.cursor;
|
||||
if col == 0 {
|
||||
if row > 0 {
|
||||
let line = self.lines.remove(row);
|
||||
let prev_line = &mut self.lines[row - 1];
|
||||
let prev_line_end = prev_line.len();
|
||||
|
||||
self.cursor = (row - 1, prev_line.chars().count());
|
||||
prev_line.push_str(&line);
|
||||
self.push_history(EditKind::DeleteNewline(prev_line_end), (row, col));
|
||||
}
|
||||
self.delete_newline();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -416,6 +449,33 @@ impl<'a> TextArea<'a> {
|
||||
self.delete_str(0, self.cursor.1);
|
||||
}
|
||||
|
||||
pub fn delete_word(&mut self) {
|
||||
let (r, c) = self.cursor;
|
||||
if let Some(col) = find_word_start_backward(&self.lines[r], c) {
|
||||
self.delete_str(col, c - col);
|
||||
} else if c > 0 {
|
||||
self.delete_str(0, c);
|
||||
} else {
|
||||
self.delete_newline();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_next_word(&mut self) {
|
||||
let (r, c) = self.cursor;
|
||||
let line = &self.lines[r];
|
||||
if let Some(col) = find_word_end_forward(line, c) {
|
||||
self.delete_str(c, col - c);
|
||||
} else {
|
||||
let end_col = line.chars().count();
|
||||
if c < end_col {
|
||||
self.delete_str(c, end_col - c);
|
||||
} else if r + 1 < self.lines.len() {
|
||||
self.cursor = (r + 1, 0);
|
||||
self.delete_newline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paste(&mut self) {
|
||||
let yank = std::mem::take(&mut self.yank);
|
||||
self.insert_str(yank);
|
||||
|
||||
62
src/word.rs
Обычный файл
62
src/word.rs
Обычный файл
@@ -0,0 +1,62 @@
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
enum CharKind {
|
||||
Space,
|
||||
Punct,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl CharKind {
|
||||
fn new(c: char) -> Self {
|
||||
if c.is_whitespace() {
|
||||
Self::Space
|
||||
} else if c.is_ascii_punctuation() {
|
||||
Self::Punct
|
||||
} else {
|
||||
Self::Other
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_word_start_forward(line: &str, start_col: usize) -> Option<usize> {
|
||||
let mut it = line.chars().enumerate().skip(start_col);
|
||||
let mut prev = CharKind::new(it.next()?.1);
|
||||
for (col, c) in it {
|
||||
let cur = CharKind::new(c);
|
||||
if cur != CharKind::Space && prev != cur {
|
||||
return Some(col);
|
||||
}
|
||||
prev = cur;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn find_word_end_forward(line: &str, start_col: usize) -> Option<usize> {
|
||||
let mut it = line.chars().enumerate().skip(start_col);
|
||||
let mut prev = CharKind::new(it.next()?.1);
|
||||
for (col, c) in it {
|
||||
let cur = CharKind::new(c);
|
||||
if prev != CharKind::Space && prev != cur {
|
||||
return Some(col);
|
||||
}
|
||||
prev = cur;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn find_word_start_backward(line: &str, start_col: usize) -> Option<usize> {
|
||||
let idx = line
|
||||
.char_indices()
|
||||
.nth(start_col)
|
||||
.map(|(i, _)| i)
|
||||
.unwrap_or(line.len());
|
||||
let mut it = line[..idx].chars().rev().enumerate();
|
||||
let mut cur = CharKind::new(it.next()?.1);
|
||||
for (i, c) in it {
|
||||
let next = CharKind::new(c);
|
||||
if cur != CharKind::Space && next != cur {
|
||||
return Some(start_col - i);
|
||||
}
|
||||
cur = next;
|
||||
}
|
||||
(cur != CharKind::Space).then(|| 0)
|
||||
}
|
||||
Ссылка в новой задаче
Block a user