зеркало из
https://github.com/glebtv/tui-textarea.git
synced 2026-09-04 06:45:50 +03:00
move cursor forward/backward by word
Этот коммит содержится в:
@@ -10,6 +10,27 @@ pub enum CursorMove {
|
||||
End,
|
||||
Top,
|
||||
Bottom,
|
||||
WordForward,
|
||||
WordBack,
|
||||
}
|
||||
|
||||
#[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 {
|
||||
@@ -18,38 +39,91 @@ impl CursorMove {
|
||||
(row, col): (usize, usize),
|
||||
lines: &[String],
|
||||
) -> Option<(usize, usize)> {
|
||||
use CursorMove::*;
|
||||
|
||||
fn fit_col(col: usize, line: &str) -> usize {
|
||||
cmp::min(col, line.chars().count())
|
||||
}
|
||||
|
||||
match self {
|
||||
CursorMove::Forward if col >= lines[row].chars().count() => {
|
||||
Forward if col >= lines[row].chars().count() => {
|
||||
if row + 1 < lines.len() {
|
||||
Some((row + 1, 0))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
CursorMove::Forward => Some((row, col + 1)),
|
||||
CursorMove::Back if col == 0 => {
|
||||
Forward => Some((row, col + 1)),
|
||||
Back if col == 0 => {
|
||||
if row > 0 {
|
||||
Some((row - 1, lines[row - 1].chars().count()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
CursorMove::Back => Some((row, col - 1)),
|
||||
CursorMove::Up if row == 0 => None,
|
||||
CursorMove::Up => Some((row - 1, fit_col(col, &lines[row - 1]))),
|
||||
CursorMove::Down if row + 1 >= lines.len() => None,
|
||||
CursorMove::Down => (Some((row + 1, fit_col(col, &lines[row + 1])))),
|
||||
CursorMove::Head => Some((row, 0)),
|
||||
CursorMove::End => Some((row, lines[row].chars().count())),
|
||||
CursorMove::Top => Some((0, fit_col(col, &lines[0]))),
|
||||
CursorMove::Bottom => {
|
||||
Back => Some((row, col - 1)),
|
||||
Up if row == 0 => None,
|
||||
Up => Some((row - 1, fit_col(col, &lines[row - 1]))),
|
||||
Down if row + 1 >= lines.len() => None,
|
||||
Down => (Some((row + 1, fit_col(col, &lines[row + 1])))),
|
||||
Head => Some((row, 0)),
|
||||
End => Some((row, lines[row].chars().count())),
|
||||
Top => Some((0, fit_col(col, &lines[0]))),
|
||||
Bottom => {
|
||||
let row = lines.len() - 1;
|
||||
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) {
|
||||
Some((row, col))
|
||||
} else if row + 1 < lines.len() {
|
||||
Some((row + 1, 0))
|
||||
} else {
|
||||
Some((row, lines[row].chars().count()))
|
||||
}
|
||||
}
|
||||
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) {
|
||||
Some((row, col))
|
||||
} else if row > 0 {
|
||||
Some((row - 1, lines[row - 1].chars().count()))
|
||||
} else {
|
||||
Some((row, 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +158,26 @@ impl<'a> TextArea<'a> {
|
||||
ctrl: true,
|
||||
alt: true,
|
||||
} => self.move_cursor(CursorMove::Bottom),
|
||||
Input {
|
||||
key: Key::Char('f'),
|
||||
ctrl: false,
|
||||
alt: true,
|
||||
}
|
||||
| Input {
|
||||
key: Key::Right,
|
||||
ctrl: true,
|
||||
alt: false,
|
||||
} => self.move_cursor(CursorMove::WordForward),
|
||||
Input {
|
||||
key: Key::Char('b'),
|
||||
ctrl: false,
|
||||
alt: true,
|
||||
}
|
||||
| Input {
|
||||
key: Key::Left,
|
||||
ctrl: true,
|
||||
alt: false,
|
||||
} => self.move_cursor(CursorMove::WordBack),
|
||||
Input {
|
||||
key: Key::Char('u'),
|
||||
ctrl: true,
|
||||
|
||||
Ссылка в новой задаче
Block a user