103 lines
3.9 KiB
Rust
103 lines
3.9 KiB
Rust
use {
|
|
super::util::{find_word_start_backward, find_word_start_forward},
|
|
crate::tui::widgets::textarea::textarea::widget::Viewport,
|
|
std::cmp,
|
|
};
|
|
|
|
/// Specify how to move the cursor.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum CursorMove {
|
|
/// Move cursor forward by one character. When the cursor is at the end of line, it moves to the
|
|
/// head of next line.
|
|
Forward,
|
|
/// Move cursor backward by one character. When the cursor is at the head of line, it moves to
|
|
/// the end of previous line.
|
|
Back,
|
|
/// Move cursor up by one line.
|
|
Up,
|
|
/// Move cursor down by one line.
|
|
Down,
|
|
/// Move cursor to the head of line. When the cursor is at the head of line, it moves to the end of previous line.
|
|
Head,
|
|
/// Move cursor to the end of line. When the cursor is at the end of line, it moves to the head of next line.
|
|
End,
|
|
/// Move cursor forward by one word. Word boundary appears at spaces, punctuations, and others. For example
|
|
/// `fn foo(a)` consists of words `fn`, `foo`, `(`, `a`, `)`. When the cursor is at the end of line, it moves to the
|
|
/// head of next line.
|
|
WordForward,
|
|
/// Move cursor backward by one word. Word boundary appears at spaces, punctuations, and others. For example
|
|
/// `fn foo(a)` consists of words `fn`, `foo`, `(`, `a`, `)`.When the cursor is at the head of line, it moves to
|
|
/// the end of previous line.
|
|
WordBack,
|
|
/// Move cursor to keep it within the viewport. For example, when a viewport displays line 8 to line 16:
|
|
///
|
|
/// - cursor at line 4 is moved to line 8
|
|
/// - cursor at line 20 is moved to line 16
|
|
/// - cursor at line 12 is not moved
|
|
///
|
|
/// This is useful when you moved a cursor but you don't want to move the viewport.
|
|
InViewport,
|
|
}
|
|
|
|
impl CursorMove {
|
|
pub(crate) fn next_cursor(
|
|
&self,
|
|
(row, col): (usize, usize),
|
|
lines: &[String],
|
|
viewport: &Viewport,
|
|
) -> Option<(usize, usize)> {
|
|
use CursorMove::*;
|
|
|
|
fn fit_col(col: usize, line: &str) -> usize {
|
|
cmp::min(col, line.chars().count())
|
|
}
|
|
|
|
match self {
|
|
Forward if col >= lines[row].chars().count() => {
|
|
(row + 1 < lines.len()).then(|| (row + 1, 0))
|
|
}
|
|
Forward => Some((row, col + 1)),
|
|
Back if col == 0 => {
|
|
let row = row.checked_sub(1)?;
|
|
Some((row, lines[row].chars().count()))
|
|
}
|
|
Back => Some((row, col - 1)),
|
|
Up => {
|
|
let row = row.checked_sub(1)?;
|
|
Some((row, fit_col(col, &lines[row])))
|
|
}
|
|
Down => Some((row + 1, fit_col(col, lines.get(row + 1)?))),
|
|
Head => Some((row, 0)),
|
|
End => Some((row, lines[row].chars().count())),
|
|
WordForward => {
|
|
if let Some(col) = find_word_start_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 => {
|
|
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()))
|
|
} else {
|
|
Some((row, 0))
|
|
}
|
|
}
|
|
InViewport => {
|
|
let (row_top, col_top, row_bottom, col_bottom) = viewport.position();
|
|
|
|
let row = row.clamp(row_top as usize, row_bottom as usize);
|
|
let row = cmp::min(row, lines.len() - 1);
|
|
let col = col.clamp(col_top as usize, col_bottom as usize);
|
|
let col = fit_col(col, &lines[row]);
|
|
|
|
Some((row, col))
|
|
}
|
|
}
|
|
}
|
|
}
|