use std::cmp::max;
use crate::selection::{HorizPos, SelRegion, Selection};
use crate::view::View;
use crate::word_boundaries::WordCursor;
use xi_rope::{Cursor, LinesMetric, Rope};
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Movement {
Left,
Right,
LeftWord,
RightWord,
LeftOfLine,
RightOfLine,
Up,
Down,
UpPage,
DownPage,
UpExactPosition,
DownExactPosition,
StartOfParagraph,
EndOfParagraph,
EndOfParagraphKill,
StartOfDocument,
EndOfDocument,
}
fn vertical_motion(
r: SelRegion,
view: &View,
text: &Rope,
line_delta: isize,
modify: bool,
) -> (usize, Option<HorizPos>) {
let (col, line) = selection_position(r, view, text, line_delta < 0, modify);
let n_lines = view.line_of_offset(text, text.len());
if line_delta < 0 && (-line_delta as usize) > line {
return (0, Some(col));
}
let line = if line_delta < 0 {
line - (-line_delta as usize)
} else {
line.saturating_add(line_delta as usize)
};
if line > n_lines {
return (text.len(), Some(col));
}
let new_offset = view.line_col_to_offset(text, line, col);
(new_offset, Some(col))
}
fn vertical_motion_exact_pos(
r: SelRegion,
view: &View,
text: &Rope,
move_up: bool,
modify: bool,
) -> (usize, Option<HorizPos>) {
let (col, init_line) = selection_position(r, view, text, move_up, modify);
let n_lines = view.line_of_offset(text, text.len());
let mut line_length = view.offset_of_line(text, init_line.saturating_add(1))
- view.offset_of_line(text, init_line);
if move_up && init_line == 0 {
return (view.line_col_to_offset(text, init_line, col), Some(col));
}
let mut line = if move_up { init_line - 1 } else { init_line.saturating_add(1) };
let col = if line_length < col { line_length - 1 } else { col };
loop {
line_length = view.offset_of_line(text, line + 1) - view.offset_of_line(text, line);
if line_length > col {
break;
}
if line >= n_lines || (line == 0 && move_up) {
line = init_line;
break;
}
line = if move_up { line - 1 } else { line.saturating_add(1) };
}
(view.line_col_to_offset(text, line, col), Some(col))
}
fn selection_position(
r: SelRegion,
view: &View,
text: &Rope,
move_up: bool,
modify: bool,
) -> (HorizPos, usize) {
let active = if modify {
r.end
} else if move_up {
r.min()
} else {
r.max()
};
let col = if let Some(col) = r.horiz { col } else { view.offset_to_line_col(text, active).1 };
let line = view.line_of_offset(text, active);
(col, line)
}
const SCROLL_OVERLAP: isize = 2;
fn scroll_height(view: &View) -> isize {
max(view.scroll_height() as isize - SCROLL_OVERLAP, 1)
}
pub fn region_movement(
m: Movement,
r: SelRegion,
view: &View,
text: &Rope,
modify: bool,
) -> SelRegion {
let (offset, horiz) = match m {
Movement::Left => {
if r.is_caret() || modify {
if let Some(offset) = text.prev_grapheme_offset(r.end) {
(offset, None)
} else {
(0, r.horiz)
}
} else {
(r.min(), None)
}
}
Movement::Right => {
if r.is_caret() || modify {
if let Some(offset) = text.next_grapheme_offset(r.end) {
(offset, None)
} else {
(r.end, r.horiz)
}
} else {
(r.max(), None)
}
}
Movement::LeftWord => {
let mut word_cursor = WordCursor::new(text, r.end);
let offset = word_cursor.prev_boundary().unwrap_or(0);
(offset, None)
}
Movement::RightWord => {
let mut word_cursor = WordCursor::new(text, r.end);
let offset = word_cursor.next_boundary().unwrap_or_else(|| text.len());
(offset, None)
}
Movement::LeftOfLine => {
let line = view.line_of_offset(text, r.end);
let offset = view.offset_of_line(text, line);
(offset, None)
}
Movement::RightOfLine => {
let line = view.line_of_offset(text, r.end);
let mut offset = text.len();
let next_line_offset = view.offset_of_line(text, line + 1);
if line < view.line_of_offset(text, offset) {
if let Some(prev) = text.prev_grapheme_offset(next_line_offset) {
offset = prev;
}
}
(offset, None)
}
Movement::Up => vertical_motion(r, view, text, -1, modify),
Movement::Down => vertical_motion(r, view, text, 1, modify),
Movement::UpExactPosition => vertical_motion_exact_pos(r, view, text, true, modify),
Movement::DownExactPosition => vertical_motion_exact_pos(r, view, text, false, modify),
Movement::StartOfParagraph => {
let mut cursor = Cursor::new(&text, r.end);
let offset = cursor.prev::<LinesMetric>().unwrap_or(0);
(offset, None)
}
Movement::EndOfParagraph => {
let mut offset = r.end;
let mut cursor = Cursor::new(&text, offset);
if let Some(next_para_offset) = cursor.next::<LinesMetric>() {
if cursor.is_boundary::<LinesMetric>() {
if let Some(eol) = text.prev_grapheme_offset(next_para_offset) {
offset = eol;
}
} else if cursor.pos() == text.len() {
offset = text.len();
}
(offset, None)
} else {
(text.len(), None)
}
}
Movement::EndOfParagraphKill => {
let mut offset = r.end;
let mut cursor = Cursor::new(&text, offset);
if let Some(next_para_offset) = cursor.next::<LinesMetric>() {
offset = next_para_offset;
if cursor.is_boundary::<LinesMetric>() {
if let Some(eol) = text.prev_grapheme_offset(next_para_offset) {
if eol != r.end {
offset = eol;
}
}
}
}
(offset, None)
}
Movement::UpPage => vertical_motion(r, view, text, -scroll_height(view), modify),
Movement::DownPage => vertical_motion(r, view, text, scroll_height(view), modify),
Movement::StartOfDocument => (0, None),
Movement::EndOfDocument => (text.len(), None),
};
SelRegion::new(if modify { r.start } else { offset }, offset).with_horiz(horiz)
}
pub fn selection_movement(
m: Movement,
s: &Selection,
view: &View,
text: &Rope,
modify: bool,
) -> Selection {
let mut result = Selection::new();
for &r in s.iter() {
let new_region = region_movement(m, r, view, text, modify);
result.add_region(new_region);
}
result
}