1
0
зеркало из https://github.com/glebtv/tui-textarea.git synced 2026-09-04 06:45:50 +03:00

move cursor forward/backward by word

Этот коммит содержится в:
rhysd
2022-06-11 01:34:49 +09:00
родитель 6e6a3183be
Коммит 17d7494197
2 изменённых файлов: 106 добавлений и 12 удалений

Просмотреть файл

@@ -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,