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