diff --git a/editor/src/keyboard_input.rs b/editor/src/keyboard_input.rs index 457f27d50c..9013e1b9e9 100644 --- a/editor/src/keyboard_input.rs +++ b/editor/src/keyboard_input.rs @@ -15,10 +15,7 @@ pub fn handle_keydown( } match virtual_keycode { - Left => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model), - Up => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model), - Right => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model), - Down => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model), + Left | Up | Right | Down => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model), Copy => handle_copy(app_model)?, Paste => handle_paste(app_model)?, @@ -39,7 +36,7 @@ pub fn handle_keydown( } } - A => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model), + A | Home | End => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model), _ => (), } diff --git a/editor/src/lib.rs b/editor/src/lib.rs index fd154fa768..f5234d23a0 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -53,6 +53,7 @@ mod keyboard_input; pub mod lang; mod mvc; //pub mod mvc; // for benchmarking +mod ui; mod resources; mod selection; mod text_buffer; diff --git a/editor/src/mvc/ed_model.rs b/editor/src/mvc/ed_model.rs index a9fa4f9733..696805c36b 100644 --- a/editor/src/mvc/ed_model.rs +++ b/editor/src/mvc/ed_model.rs @@ -2,8 +2,6 @@ use crate::error::EdResult; use crate::graphics::primitives::rect::Rect; use crate::text_buffer; use crate::text_buffer::TextBuffer; -use std::cmp::Ordering; -use std::fmt; use std::path::Path; #[derive(Debug)] @@ -47,45 +45,3 @@ impl EdModel { Ok(()) } } - -#[derive(Debug, Copy, Clone)] -pub struct Position { - pub line: usize, - pub column: usize, -} - -impl Ord for Position { - fn cmp(&self, other: &Self) -> Ordering { - (self.line, self.column).cmp(&(other.line, other.column)) - } -} - -impl PartialOrd for Position { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl PartialEq for Position { - fn eq(&self, other: &Self) -> bool { - (self.line, self.column) == (other.line, other.column) - } -} - -impl Eq for Position {} - -#[derive(Debug, Copy, Clone)] -pub struct RawSelection { - pub start_pos: Position, - pub end_pos: Position, -} - -impl std::fmt::Display for RawSelection { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "RawSelection: start_pos: line:{} col:{}, end_pos: line:{} col:{}", - self.start_pos.line, self.start_pos.column, self.end_pos.line, self.end_pos.column - ) - } -} diff --git a/editor/src/mvc/ed_update.rs b/editor/src/mvc/ed_update.rs index 27247c11fe..28f218fb2c 100644 --- a/editor/src/mvc/ed_update.rs +++ b/editor/src/mvc/ed_update.rs @@ -332,6 +332,83 @@ pub fn handle_select_all(ed_model: &mut EdModel) { } } +impl EdModel { + pub fn move_caret_w_mods(&mut self, new_pos: Position, mods: &ModifiersState) { + let caret_pos = self.caret_pos; + + // one does not simply move the caret + if new_pos != caret_pos { + if mods.shift() { + if let Some(selection) = self.selection_opt { + if new_pos < selection.start_pos { + if caret_pos > selection.start_pos { + self.set_selection( + new_pos, + selection.start_pos + ) + } else { + self.set_selection( + new_pos, + selection.end_pos + ) + } + } else if new_pos > selection.end_pos { + if caret_pos < selection.end_pos { + self.set_selection( + selection.end_pos, + new_pos + ) + } else { + self.set_selection( + selection.start_pos, + new_pos + ) + } + } else if new_pos > caret_pos { + self.set_selection( + new_pos, + selection.end_pos + ) + } else if new_pos < caret_pos { + self.set_selection( + selection.start_pos, + new_pos + ) + } + } else if new_pos < self.caret_pos { + self.set_selection( + new_pos, + caret_pos + ) + } else { + self.set_selection( + caret_pos, + new_pos + ) + } + } else { + self.selection_opt = None; + } + + self.caret_pos = new_pos; + } + } + + pub fn set_selection(&mut self, start_pos: Position, end_pos: Position) { + self.selection_opt = + if start_pos != end_pos { + Some( + RawSelection { + start_pos, + end_pos + } + ) + } else { + None + } + } +} + pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult<()> { let old_caret_pos = ed_model.caret_pos; @@ -418,6 +495,53 @@ pub fn handle_key_down( handle_select_all(ed_model) } } + Home => { + let curr_line_nr = ed_model.caret_pos.line; + // TODO no unwrap + let curr_line_str = ed_model.text_buf.line(curr_line_nr).unwrap(); + let line_char_iter = curr_line_str.chars(); + + let mut first_no_space_char_col = 0; + let mut non_space_found = false; + + for c in line_char_iter { + if !c.is_whitespace() { + non_space_found = true; + break; + } else { + first_no_space_char_col += 1; + } + } + + if !non_space_found { + first_no_space_char_col = 0; + } + + ed_model.move_caret_w_mods( + Position { + line: ed_model.caret_pos.line, + column: first_no_space_char_col + }, + modifiers + ) + } + End => { + let curr_line = ed_model.caret_pos.line; + // TODO no unwrap + let new_col = + max( + 0, + ed_model.text_buf.line_len(curr_line).unwrap() - 1 + ); + + let new_pos = + Position { + line: curr_line, + column: new_col + }; + + ed_model.move_caret_w_mods(new_pos, modifiers); + } _ => {} } } @@ -622,4 +746,8 @@ pub mod test_ed_update { Ok(()) } + + // TODO hometest + + // TODO endtest } diff --git a/editor/src/ui/error.rs b/editor/src/ui/error.rs new file mode 100644 index 0000000000..615539328f --- /dev/null +++ b/editor/src/ui/error.rs @@ -0,0 +1,20 @@ +use snafu::{Backtrace, ErrorCompat, Snafu}; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum UIError { + #[snafu(display( + "OutOfBounds: index {} was out of bounds for {} with length {}.", + index, + collection_name, + len + ))] + OutOfBounds { + index: usize, + collection_name: String, + len: usize, + backtrace: Backtrace, + }, +} + +pub type UIResult = std::result::Result; \ No newline at end of file diff --git a/editor/src/ui/mod.rs b/editor/src/ui/mod.rs new file mode 100644 index 0000000000..371cc35a37 --- /dev/null +++ b/editor/src/ui/mod.rs @@ -0,0 +1,3 @@ +pub mod error; +pub mod model; +pub mod update; \ No newline at end of file diff --git a/editor/src/ui/model/mod.rs b/editor/src/ui/model/mod.rs new file mode 100644 index 0000000000..71b75d5eb3 --- /dev/null +++ b/editor/src/ui/model/mod.rs @@ -0,0 +1 @@ +pub mod text; \ No newline at end of file diff --git a/editor/src/ui/model/text/mod.rs b/editor/src/ui/model/text/mod.rs new file mode 100644 index 0000000000..c7060966e3 --- /dev/null +++ b/editor/src/ui/model/text/mod.rs @@ -0,0 +1,3 @@ +pub mod selectable_text_m; +pub mod selection; +pub mod txt_pos; \ No newline at end of file diff --git a/editor/src/ui/model/text/selectable_text_m.rs b/editor/src/ui/model/text/selectable_text_m.rs new file mode 100644 index 0000000000..f7281401fa --- /dev/null +++ b/editor/src/ui/model/text/selectable_text_m.rs @@ -0,0 +1,48 @@ + +use crate::ui::error::UIResult; +use super::txt_pos::TxtPos; +use std::fmt; + +impl std::fmt::Display for RawSelection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "RawSelection: start_pos: line:{} col:{}, end_pos: line:{} col:{}", + self.start_pos.line, self.start_pos.column, self.end_pos.line, self.end_pos.column + ) + } +} + +trait SelectableLines { + fn get_line(&self, line_nr: usize) -> UIResult<&str>; + + fn line_len(&self, line_nr: usize) -> UIResult; + + fn nr_of_lines(&self) -> usize; + + fn nr_of_chars(&self) -> usize; + + // TODO use pool allocation here + fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a>; + + fn last_TxtPos(&self) -> TxtPos; + + fn get_selection(&self, raw_sel: RawSelection) -> UIResult<&str>; +} + +trait MutSelectableLines { + fn insert_char(&mut self, caret_pos: TxtPos, new_char: &char) -> UIResult<()>; + + fn insert_str(&mut self, caret_pos: TxtPos, new_str: &str) -> UIResult<()>; + + fn pop_char(&mut self, caret_pos: TxtPos); + + fn del_selection(&mut self, raw_sel: RawSelection) -> UIResult<()>; +} + +#[derive(Debug)] +pub struct SelectableText { + pub lines: SelectableLines, + pub caret_pos: TxtPos, + pub selection_opt: Option, +} \ No newline at end of file diff --git a/editor/src/ui/model/text/selection.rs b/editor/src/ui/model/text/selection.rs new file mode 100644 index 0000000000..bc26e5e878 --- /dev/null +++ b/editor/src/ui/model/text/selection.rs @@ -0,0 +1,2193 @@ +use crate::ui::error::{UIResult, InvalidSelection}; +use bumpalo::collections::Vec as BumpVec; +use bumpalo::Bump; +use snafu::ensure; + +#[derive(Debug, Copy, Clone)] +pub struct RawSelection { + pub start_pos: TxtPos, + pub end_pos: TxtPos, +} + +//using the "parse don't validate" pattern +pub struct ValidSelection { + pub selection: RawSelection, +} + +pub fn validate_selection(selection: RawSelection) -> EdResult { + let RawSelection { start_pos, end_pos } = selection; + + ensure!( + start_pos.line <= end_pos.line, + InvalidSelection { + err_msg: format!( + "start_pos.line ({}) should be smaller than or equal to end_pos.line ({})", + start_pos.line, end_pos.line + ) + } + ); + + ensure!( + !(start_pos.line == end_pos.line && start_pos.column > end_pos.column), + InvalidSelection { + err_msg: format!( + "start_pos.column ({}) should be smaller than or equal to end_pos.column ({}) when start_pos.line equals end_pos.line", + start_pos.column, + end_pos.column + ) + } + ); + + Ok(ValidSelection { + selection: RawSelection { start_pos, end_pos }, + }) +} + +pub fn create_selection_rects<'a>( + raw_sel: RawSelection, + text_buf: &TextBuffer, + glyph_dim_rect: &Rect, + arena: &'a Bump, +) -> EdResult> { + let valid_sel = validate_selection(raw_sel)?; + let RawSelection { start_pos, end_pos } = valid_sel.selection; + + let mut all_rects: BumpVec = BumpVec::new_in(arena); + + let height = glyph_dim_rect.height; + let start_y = glyph_dim_rect.top_left_coords.y + height * (start_pos.line as f32); + let line_start_x = glyph_dim_rect.top_left_coords.x; + + if start_pos.line == end_pos.line { + let width = ((end_pos.column as f32) * glyph_dim_rect.width) + - ((start_pos.column as f32) * glyph_dim_rect.width); + let sel_rect_x = line_start_x + ((start_pos.column as f32) * glyph_dim_rect.width); + + all_rects.push(Rect { + top_left_coords: (sel_rect_x, start_y).into(), + width, + height, + color: colors::SELECT_COLOR, + }); + + Ok(all_rects) + } else { + // first line + let end_col = text_buf.line_len_res(start_pos.line)?; + let width = ((end_col as f32) * glyph_dim_rect.width) + - ((start_pos.column as f32) * glyph_dim_rect.width); + + let sel_rect_x = line_start_x + ((start_pos.column as f32) * glyph_dim_rect.width); + + all_rects.push(Rect { + top_left_coords: (sel_rect_x, start_y).into(), + width, + height, + color: colors::SELECT_COLOR, + }); + + //middle lines + let nr_mid_lines = (end_pos.line - start_pos.line) - 1; + let first_mid_line = start_pos.line + 1; + + for i in first_mid_line..(first_mid_line + nr_mid_lines) { + let mid_line_len = text_buf.line_len_res(i)?; + + let width = (mid_line_len as f32) * glyph_dim_rect.width; + + let sel_rect_y = start_y + ((i - start_pos.line) as f32) * glyph_dim_rect.height; + + all_rects.push(Rect { + top_left_coords: (line_start_x, sel_rect_y).into(), + width, + height, + color: colors::SELECT_COLOR, + }); + } + + //last line + if end_pos.column > 0 { + let sel_rect_y = + start_y + ((end_pos.line - start_pos.line) as f32) * glyph_dim_rect.height; + + let width = (end_pos.column as f32) * glyph_dim_rect.width; + + all_rects.push(Rect { + top_left_coords: (line_start_x, sel_rect_y).into(), + width, + height, + color: colors::SELECT_COLOR, + }); + } + + Ok(all_rects) + } +} + +#[cfg(test)] +pub mod test_selection { + use crate::error::{EdResult, OutOfBounds}; + use crate::mvc::ed_model::{Position, RawSelection}; + use crate::mvc::ed_update::{ + move_caret_down, move_caret_left, move_caret_right, move_caret_up, MoveCaretFun, + }; + use crate::text_buffer::TextBuffer; + use crate::vec_result::get_res; + use core::cmp::Ordering; + use pest::Parser; + use ropey::Rope; + use snafu::OptionExt; + use std::collections::HashMap; + use std::slice::SliceIndex; + + #[derive(Parser)] + #[grammar = "../tests/selection.pest"] + pub struct LineParser; + + // show selection and caret position as symbols in lines for easy testing + pub fn convert_selection_to_dsl( + raw_sel_opt: Option, + caret_pos: Position, + lines: &mut [String], + ) -> EdResult<&[String]> { + if let Some(raw_sel) = raw_sel_opt { + let mut to_insert = vec![ + (raw_sel.start_pos, '['), + (raw_sel.end_pos, ']'), + (caret_pos, '|'), + ]; + let symbol_map: HashMap = + [('[', 2), (']', 0), ('|', 1)].iter().cloned().collect(); + + // sort for nice printing + to_insert.sort_by(|a, b| { + let pos_cmp = a.0.cmp(&b.0); + if pos_cmp == Ordering::Equal { + symbol_map.get(&a.1).cmp(&symbol_map.get(&b.1)) + } else { + pos_cmp + } + }); + + // insert symbols into text lines + for i in 0..to_insert.len() { + let (pos, insert_char) = *get_res(i, &to_insert)?; + + insert_at_pos(lines, pos, insert_char)?; + + // shift position of following symbols now that symbol is inserted + for j in i..to_insert.len() { + let (old_pos, _) = get_mut_res(j, &mut to_insert)?; + + if old_pos.line == pos.line { + old_pos.column += 1; + } + } + } + } else { + insert_at_pos(lines, caret_pos, '|')?; + } + + Ok(lines) + } + + fn insert_at_pos(lines: &mut [String], pos: Position, insert_char: char) -> EdResult<()> { + let line = get_mut_res(pos.line, lines)?; + line.insert(pos.column, insert_char); + + Ok(()) + } + + // It's much nicer to have get_mut return a Result with clear error than an Option + fn get_mut_res( + index: usize, + vec: &mut [T], + ) -> EdResult<&mut >::Output> { + let vec_len = vec.len(); + + let elt_ref = vec.get_mut(index).context(OutOfBounds { + index, + collection_name: "Slice", + len: vec_len, + })?; + + Ok(elt_ref) + } + + fn text_buffer_from_str(lines_str: &str) -> TextBuffer { + TextBuffer { + text_rope: Rope::from_str(lines_str), + path_str: "".to_owned(), + arena: bumpalo::Bump::new(), + } + } + + pub fn text_buffer_from_dsl_str(lines: &[String]) -> TextBuffer { + text_buffer_from_str( + &lines + .iter() + .map(|line| line.replace(&['[', ']', '|'][..], "")) + .collect::>() + .join(""), + ) + } + + pub fn all_lines_vec(text_buf: &TextBuffer) -> Vec { + let mut lines: Vec = Vec::new(); + + for line in text_buf.text_rope.lines() { + lines.push( + line + .as_str() + .expect( + "Failed to get str from RopeSlice. See https://docs.rs/ropey/1.2.0/ropey/struct.RopeSlice.html#method.as_str" + ) + .to_owned() + ); + } + + lines + } + + // Retrieve selection and position from formatted string + pub fn convert_dsl_to_selection( + lines: &[String], + ) -> Result<(Option, Position), String> { + let lines_str: String = lines.join(""); + + let parsed = LineParser::parse(Rule::linesWithSelect, &lines_str) + .expect("Selection test DSL parsing failed"); + + let mut caret_opt: Option<(usize, usize)> = None; + let mut sel_start_opt: Option<(usize, usize)> = None; + let mut sel_end_opt: Option<(usize, usize)> = None; + let mut line_nr = 0; + let mut col_nr = 0; + + for line in parsed { + for elt in line.into_inner() { + match elt.as_rule() { + Rule::optCaret => { + if elt.as_span().as_str() == "|" { + if caret_opt.is_some() { + return Err( + "Multiple carets found, there should be only one".to_owned() + ); + } else { + caret_opt = Some((line_nr, col_nr)); + } + } + } + Rule::optSelStart => { + if sel_start_opt.is_some() { + if elt.as_span().as_str() == "[" { + return Err("Found start of selection more than once, there should be only one".to_owned()); + } + } else if elt.as_span().as_str() == "[" { + sel_start_opt = Some((line_nr, col_nr)); + } + } + Rule::optSelEnd => { + if sel_end_opt.is_some() { + if elt.as_span().as_str() == "]" { + return Err("Found end of selection more than once, there should be only one".to_owned()); + } + } else if elt.as_span().as_str() == "]" { + sel_end_opt = Some((line_nr, col_nr)); + } + } + Rule::text => { + let split_str = elt + .as_span() + .as_str() + .split('\n') + .into_iter() + .collect::>(); + + if split_str.len() > 1 { + line_nr += split_str.len() - 1; + col_nr = 0 + } + if let Some(last_str) = split_str.last() { + col_nr += last_str.len() + } + } + _ => {} + } + } + } + + // Make sure return makes sense + if let Some((line, column)) = caret_opt { + let caret_pos = Position { line, column }; + if sel_start_opt.is_none() && sel_end_opt.is_none() { + Ok((None, caret_pos)) + } else if let Some((start_line, start_column)) = sel_start_opt { + if let Some((end_line, end_column)) = sel_end_opt { + Ok(( + Some(RawSelection { + start_pos: Position { + line: start_line, + column: start_column, + }, + end_pos: Position { + line: end_line, + column: end_column, + }, + }), + caret_pos, + )) + } else { + Err("Selection end ']' was not found, but selection start '[' was. Bad input string.".to_owned()) + } + } else { + Err("Selection start '[' was not found, but selection end ']' was. Bad input string.".to_owned()) + } + } else { + Err("No caret was found in lines.".to_owned()) + } + } + + // Convert nice string representations and compare results + fn assert_move( + pre_lines_str: &[&str], + expected_post_lines_str: &[&str], + shift_pressed: bool, + move_fun: MoveCaretFun, + ) -> Result<(), String> { + let pre_lines: Vec = pre_lines_str.iter().map(|l| l.to_string()).collect(); + let expected_post_lines: Vec = expected_post_lines_str + .iter() + .map(|l| l.to_string()) + .collect(); + + let (sel_opt, caret_pos) = convert_dsl_to_selection(&pre_lines)?; + + let clean_text_buf = text_buffer_from_dsl_str(&pre_lines); + + let (new_caret_pos, new_sel_opt) = + move_fun(caret_pos, sel_opt, shift_pressed, &clean_text_buf); + + let mut lines_vec = all_lines_vec(&clean_text_buf); + let post_lines_res = convert_selection_to_dsl(new_sel_opt, new_caret_pos, &mut lines_vec); + + match post_lines_res { + Ok(post_lines) => { + assert_eq!(expected_post_lines, post_lines); + Ok(()) + } + Err(e) => Err(format!("{:?}", e)), + } + } + + #[test] + fn move_right() -> Result<(), String> { + assert_move(&["|"], &["|"], false, move_caret_right)?; + assert_move(&["a|"], &["a|"], false, move_caret_right)?; + assert_move(&["|A"], &["A|"], false, move_caret_right)?; + assert_move(&["|abc"], &["a|bc"], false, move_caret_right)?; + assert_move(&["a|bc"], &["ab|c"], false, move_caret_right)?; + assert_move(&["abc|"], &["abc|"], false, move_caret_right)?; + assert_move(&["| abc"], &[" |abc"], false, move_caret_right)?; + assert_move(&["abc| "], &["abc |"], false, move_caret_right)?; + assert_move(&["abc|\n", "d"], &["abc\n", "|d"], false, move_caret_right)?; + assert_move(&["abc|\n", ""], &["abc\n", "|"], false, move_caret_right)?; + assert_move( + &["abc\n", "|def"], + &["abc\n", "d|ef"], + false, + move_caret_right, + )?; + assert_move( + &["abc\n", "def| "], + &["abc\n", "def |"], + false, + move_caret_right, + )?; + assert_move( + &["abc\n", "def |\n", "ghi"], + &["abc\n", "def \n", "|ghi"], + false, + move_caret_right, + )?; + assert_move( + &["abc\n", "def|\n", ""], + &["abc\n", "def\n", "|"], + false, + move_caret_right, + )?; + assert_move( + &["abc\n", "def\n", "ghi|\n", "jkl"], + &["abc\n", "def\n", "ghi\n", "|jkl"], + false, + move_caret_right, + )?; + assert_move( + &["abc\n", "def\n", "|ghi\n", "jkl"], + &["abc\n", "def\n", "g|hi\n", "jkl"], + false, + move_caret_right, + )?; + assert_move( + &["abc\n", "def\n", "g|hi\n", "jkl"], + &["abc\n", "def\n", "gh|i\n", "jkl"], + false, + move_caret_right, + )?; + + Ok(()) + } + + #[test] + fn move_left() -> Result<(), String> { + assert_move(&["|"], &["|"], false, move_caret_left)?; + assert_move(&["|a"], &["|a"], false, move_caret_left)?; + assert_move(&["|A"], &["|A"], false, move_caret_left)?; + assert_move(&["a|bc"], &["|abc"], false, move_caret_left)?; + assert_move(&["ab|c"], &["a|bc"], false, move_caret_left)?; + assert_move(&["abc|"], &["ab|c"], false, move_caret_left)?; + assert_move(&[" |abc"], &["| abc"], false, move_caret_left)?; + assert_move(&["abc |"], &["abc| "], false, move_caret_left)?; + assert_move(&["abc\n", "|d"], &["abc|\n", "d"], false, move_caret_left)?; + assert_move(&["abc\n", "|"], &["abc|\n", ""], false, move_caret_left)?; + assert_move( + &["abc\n", "d|ef"], + &["abc\n", "|def"], + false, + move_caret_left, + )?; + assert_move( + &["abc\n", "def |"], + &["abc\n", "def| "], + false, + move_caret_left, + )?; + assert_move( + &["abc\n", "def \n", "|ghi"], + &["abc\n", "def |\n", "ghi"], + false, + move_caret_left, + )?; + assert_move( + &["abc\n", "def\n", "|"], + &["abc\n", "def|\n", ""], + false, + move_caret_left, + )?; + assert_move( + &["abc\n", "def\n", "ghi\n", "|jkl"], + &["abc\n", "def\n", "ghi|\n", "jkl"], + false, + move_caret_left, + )?; + assert_move( + &["abc\n", "def\n", "g|hi\n", "jkl"], + &["abc\n", "def\n", "|ghi\n", "jkl"], + false, + move_caret_left, + )?; + assert_move( + &["abc\n", "def\n", "gh|i\n", "jkl"], + &["abc\n", "def\n", "g|hi\n", "jkl"], + false, + move_caret_left, + )?; + + Ok(()) + } + + #[test] + fn move_up() -> Result<(), String> { + assert_move(&["|"], &["|"], false, move_caret_up)?; + assert_move(&["|a"], &["|a"], false, move_caret_up)?; + assert_move(&["A|"], &["|A"], false, move_caret_up)?; + assert_move(&["a|bc"], &["|abc"], false, move_caret_up)?; + assert_move(&["ab|c"], &["|abc"], false, move_caret_up)?; + assert_move(&["abc|"], &["|abc"], false, move_caret_up)?; + assert_move(&["|abc\n", "def"], &["|abc\n", "def"], false, move_caret_up)?; + assert_move(&["abc\n", "|def"], &["|abc\n", "def"], false, move_caret_up)?; + assert_move(&["abc\n", "d|ef"], &["a|bc\n", "def"], false, move_caret_up)?; + assert_move(&["abc\n", "de|f"], &["ab|c\n", "def"], false, move_caret_up)?; + assert_move(&["abc\n", "def|"], &["abc|\n", "def"], false, move_caret_up)?; + assert_move( + &["abc\n", "def \n", "|ghi"], + &["abc\n", "|def \n", "ghi"], + false, + move_caret_up, + )?; + assert_move( + &["abc\n", "def \n", "g|hi"], + &["abc\n", "d|ef \n", "ghi"], + false, + move_caret_up, + )?; + assert_move( + &["abc\n", "def \n", "gh|i"], + &["abc\n", "de|f \n", "ghi"], + false, + move_caret_up, + )?; + assert_move( + &["abc\n", "def \n", "ghi|"], + &["abc\n", "def| \n", "ghi"], + false, + move_caret_up, + )?; + assert_move( + &["abc\n", "de\n", "ghi|"], + &["abc\n", "de|\n", "ghi"], + false, + move_caret_up, + )?; + assert_move(&["abc\n", "de|"], &["ab|c\n", "de"], false, move_caret_up)?; + assert_move(&["abc\n", "d|e"], &["a|bc\n", "de"], false, move_caret_up)?; + assert_move(&["abc\n", "|de"], &["|abc\n", "de"], false, move_caret_up)?; + assert_move( + &["ab\n", "cdef\n", "ghijkl\n", "mnopqrst|"], + &["ab\n", "cdef\n", "ghijkl|\n", "mnopqrst"], + false, + move_caret_up, + )?; + assert_move( + &["ab\n", "cdef\n", "ghijkl|\n", "mnopqrst"], + &["ab\n", "cdef|\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_up, + )?; + assert_move( + &["ab\n", "cdef\n", "ghijkl\n", "|mnopqrst"], + &["ab\n", "cdef\n", "|ghijkl\n", "mnopqrst"], + false, + move_caret_up, + )?; + assert_move( + &[" ab\n", " |cdef\n", "ghijkl\n", "mnopqrst"], + &[" |ab\n", " cdef\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_up, + )?; + assert_move( + &["ab\n", "cdef\n", "ghijkl\n", "mnopqr|st"], + &["ab\n", "cdef\n", "ghijkl|\n", "mnopqrst"], + false, + move_caret_up, + )?; + assert_move( + &["ab\n", "cde|f\n", "ghijkl\n", "mnopqrst"], + &["ab|\n", "cdef\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_up, + )?; + assert_move( + &["abcdefgh\n", "ijklmn\n", "opqr\n", "st|"], + &["abcdefgh\n", "ijklmn\n", "op|qr\n", "st"], + false, + move_caret_up, + )?; + assert_move( + &["abcdefgh\n", "ijklmn\n", "opqr|\n", "st"], + &["abcdefgh\n", "ijkl|mn\n", "opqr\n", "st"], + false, + move_caret_up, + )?; + assert_move( + &["abcdefgh\n", "ijklmn|\n", "opqr\n", "st"], + &["abcdef|gh\n", "ijklmn\n", "opqr\n", "st"], + false, + move_caret_up, + )?; + assert_move( + &["abcdefgh|\n", "ijklmn\n", "opqr\n", "st"], + &["|abcdefgh\n", "ijklmn\n", "opqr\n", "st"], + false, + move_caret_up, + )?; + assert_move( + &["abcdefg|h\n", "ijklmn\n", "opqr\n", "st"], + &["|abcdefgh\n", "ijklmn\n", "opqr\n", "st"], + false, + move_caret_up, + )?; + assert_move( + &["a|bcdefgh\n", "ijklmn\n", "opqr\n", "st"], + &["|abcdefgh\n", "ijklmn\n", "opqr\n", "st"], + false, + move_caret_up, + )?; + assert_move( + &["|abcdefgh\n", "ijklmn\n", "opqr\n", "st"], + &["|abcdefgh\n", "ijklmn\n", "opqr\n", "st"], + false, + move_caret_up, + )?; + assert_move(&["abc def gh |"], &["|abc def gh "], false, move_caret_up)?; + assert_move(&["abc de|f gh "], &["|abc def gh "], false, move_caret_up)?; + assert_move(&["ab|c def gh "], &["|abc def gh "], false, move_caret_up)?; + assert_move(&["a|bc def gh "], &["|abc def gh "], false, move_caret_up)?; + + Ok(()) + } + + #[test] + fn move_down() -> Result<(), String> { + assert_move(&["|"], &["|"], false, move_caret_down)?; + assert_move(&["|a"], &["a|"], false, move_caret_down)?; + assert_move(&["A|"], &["A|"], false, move_caret_down)?; + assert_move(&["a|bc"], &["abc|"], false, move_caret_down)?; + assert_move(&["ab|c"], &["abc|"], false, move_caret_down)?; + assert_move(&["abc|"], &["abc|"], false, move_caret_down)?; + assert_move(&["abc| "], &["abc |"], false, move_caret_down)?; + assert_move( + &["abc\n", "|def"], + &["abc\n", "def|"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "d|ef"], + &["abc\n", "def|"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "de|f"], + &["abc\n", "def|"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "def|"], + &["abc\n", "def|"], + false, + move_caret_down, + )?; + assert_move( + &["|abc\n", "def"], + &["abc\n", "|def"], + false, + move_caret_down, + )?; + assert_move( + &["a|bc\n", "def"], + &["abc\n", "d|ef"], + false, + move_caret_down, + )?; + assert_move( + &["ab|c\n", "def"], + &["abc\n", "de|f"], + false, + move_caret_down, + )?; + assert_move( + &["abc|\n", "def"], + &["abc\n", "def|"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "|def \n", "ghi"], + &["abc\n", "def \n", "|ghi"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "d|ef \n", "ghi"], + &["abc\n", "def \n", "g|hi"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "de|f \n", "ghi"], + &["abc\n", "def \n", "gh|i"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "def| \n", "ghi"], + &["abc\n", "def \n", "ghi|"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "def |\n", "ghi"], + &["abc\n", "def \n", "ghi|"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "de|\n", "ghi"], + &["abc\n", "de\n", "gh|i"], + false, + move_caret_down, + )?; + assert_move(&["abc|\n", "de"], &["abc\n", "de|"], false, move_caret_down)?; + assert_move(&["ab|c\n", "de"], &["abc\n", "de|"], false, move_caret_down)?; + assert_move(&["a|bc\n", "de"], &["abc\n", "d|e"], false, move_caret_down)?; + assert_move(&["|abc\n", "de"], &["abc\n", "|de"], false, move_caret_down)?; + assert_move( + &["ab|\n", "cdef\n", "ghijkl\n", "mnopqrst"], + &["ab\n", "cd|ef\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_down, + )?; + assert_move( + &["ab\n", "cdef|\n", "ghijkl\n", "mnopqrst"], + &["ab\n", "cdef\n", "ghij|kl\n", "mnopqrst"], + false, + move_caret_down, + )?; + assert_move( + &["ab\n", "cdef\n", "ghijkl|\n", "mnopqrst"], + &["ab\n", "cdef\n", "ghijkl\n", "mnopqr|st"], + false, + move_caret_down, + )?; + assert_move( + &[" |ab\n", " cdef\n", "ghijkl\n", "mnopqrst"], + &[" ab\n", " |cdef\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_down, + )?; + assert_move( + &["ab\n", "|cdef\n", "ghijkl\n", "mnopqrst"], + &["ab\n", "cdef\n", "|ghijkl\n", "mnopqrst"], + false, + move_caret_down, + )?; + assert_move( + &["ab\n", "cdef\n", "|ghijkl\n", "mnopqrst"], + &["ab\n", "cdef\n", "ghijkl\n", "|mnopqrst"], + false, + move_caret_down, + )?; + assert_move( + &["abcdefgh|\n", "ijklmn\n", "opqr\n", "st"], + &["abcdefgh\n", "ijklmn|\n", "opqr\n", "st"], + false, + move_caret_down, + )?; + assert_move( + &["abcdefgh\n", "ijklmn|\n", "opqr\n", "st"], + &["abcdefgh\n", "ijklmn\n", "opqr|\n", "st"], + false, + move_caret_down, + )?; + assert_move( + &["abcdefgh\n", "ijklmn\n", "opqr|\n", "st"], + &["abcdefgh\n", "ijklmn\n", "opqr\n", "st|"], + false, + move_caret_down, + )?; + assert_move( + &["abcdefgh\n", "ijklmn\n", "opqr\n", "|st"], + &["abcdefgh\n", "ijklmn\n", "opqr\n", "st|"], + false, + move_caret_down, + )?; + assert_move(&["abc def gh |"], &["abc def gh |"], false, move_caret_down)?; + assert_move(&["abc de|f gh "], &["abc def gh |"], false, move_caret_down)?; + assert_move(&["ab|c def gh "], &["abc def gh |"], false, move_caret_down)?; + assert_move(&["a|bc def gh "], &["abc def gh |"], false, move_caret_down)?; + assert_move(&["|abc def gh "], &["abc def gh |"], false, move_caret_down)?; + + Ok(()) + } + + #[test] + fn start_selection_right() -> Result<(), String> { + assert_move(&["|"], &["|"], true, move_caret_right)?; + assert_move(&["a|"], &["a|"], true, move_caret_right)?; + assert_move(&["|A"], &["[A]|"], true, move_caret_right)?; + assert_move(&["|abc"], &["[a]|bc"], true, move_caret_right)?; + assert_move(&["a|bc"], &["a[b]|c"], true, move_caret_right)?; + assert_move(&["abc|"], &["abc|"], true, move_caret_right)?; + assert_move(&["| abc"], &["[ ]|abc"], true, move_caret_right)?; + assert_move(&["abc| "], &["abc[ ]|"], true, move_caret_right)?; + assert_move(&["abc|\n", "d"], &["abc[\n", "]|d"], true, move_caret_right)?; + assert_move(&["abc|\n", ""], &["abc[\n", "]|"], true, move_caret_right)?; + assert_move( + &["abc\n", "|def"], + &["abc\n", "[d]|ef"], + true, + move_caret_right, + )?; + assert_move( + &["abc\n", "def| "], + &["abc\n", "def[ ]|"], + true, + move_caret_right, + )?; + assert_move( + &["abc\n", "def |\n", "ghi"], + &["abc\n", "def [\n", "]|ghi"], + true, + move_caret_right, + )?; + assert_move( + &["abc\n", "def|\n", ""], + &["abc\n", "def[\n", "]|"], + true, + move_caret_right, + )?; + assert_move( + &["abc\n", "def\n", "ghi|\n", "jkl"], + &["abc\n", "def\n", "ghi[\n", "]|jkl"], + true, + move_caret_right, + )?; + assert_move( + &["abc\n", "def\n", "|ghi\n", "jkl"], + &["abc\n", "def\n", "[g]|hi\n", "jkl"], + true, + move_caret_right, + )?; + assert_move( + &["abc\n", "def\n", "g|hi\n", "jkl"], + &["abc\n", "def\n", "g[h]|i\n", "jkl"], + true, + move_caret_right, + )?; + + Ok(()) + } + + #[test] + fn start_selection_left() -> Result<(), String> { + assert_move(&["|"], &["|"], true, move_caret_left)?; + assert_move(&["a|"], &["|[a]"], true, move_caret_left)?; + assert_move(&["|A"], &["|A"], true, move_caret_left)?; + assert_move(&["|abc"], &["|abc"], true, move_caret_left)?; + assert_move(&["a|bc"], &["|[a]bc"], true, move_caret_left)?; + assert_move(&["abc|"], &["ab|[c]"], true, move_caret_left)?; + assert_move(&[" |abc"], &["|[ ]abc"], true, move_caret_left)?; + assert_move(&["abc |"], &["abc|[ ]"], true, move_caret_left)?; + assert_move(&["abc|\n", "d"], &["ab|[c]\n", "d"], true, move_caret_left)?; + assert_move(&["abc\n", "|d"], &["abc|[\n", "]d"], true, move_caret_left)?; + assert_move(&["abc\n", "|"], &["abc|[\n", "]"], true, move_caret_left)?; + assert_move( + &["abc\n", " |def"], + &["abc\n", "|[ ]def"], + true, + move_caret_left, + )?; + assert_move( + &["abc\n", "d|ef"], + &["abc\n", "|[d]ef"], + true, + move_caret_left, + )?; + assert_move( + &["abc\n", "de|f "], + &["abc\n", "d|[e]f "], + true, + move_caret_left, + )?; + assert_move( + &["abc\n", "def\n", "|"], + &["abc\n", "def|[\n", "]"], + true, + move_caret_left, + )?; + assert_move( + &["abc\n", "def\n", "|ghi\n", "jkl"], + &["abc\n", "def|[\n", "]ghi\n", "jkl"], + true, + move_caret_left, + )?; + assert_move( + &["abc\n", "def\n", "g|hi\n", "jkl"], + &["abc\n", "def\n", "|[g]hi\n", "jkl"], + true, + move_caret_left, + )?; + assert_move( + &["abc\n", "def\n", "gh|i\n", "jkl"], + &["abc\n", "def\n", "g|[h]i\n", "jkl"], + true, + move_caret_left, + )?; + assert_move( + &["abc\n", "def\n", "ghi|\n", "jkl"], + &["abc\n", "def\n", "gh|[i]\n", "jkl"], + true, + move_caret_left, + )?; + + Ok(()) + } + + #[test] + fn start_selection_down() -> Result<(), String> { + assert_move(&["|"], &["|"], true, move_caret_down)?; + assert_move(&["|a"], &["[a]|"], true, move_caret_down)?; + assert_move(&["A|"], &["A|"], true, move_caret_down)?; + assert_move(&["a|bc"], &["a[bc]|"], true, move_caret_down)?; + assert_move(&["ab|c"], &["ab[c]|"], true, move_caret_down)?; + assert_move(&["abc|"], &["abc|"], true, move_caret_down)?; + assert_move(&["abc| "], &["abc[ ]|"], true, move_caret_down)?; + assert_move( + &["abc\n", "|def"], + &["abc\n", "[def]|"], + true, + move_caret_down, + )?; + assert_move( + &["abc\n", "d|ef"], + &["abc\n", "d[ef]|"], + true, + move_caret_down, + )?; + assert_move( + &["abc\n", "de|f"], + &["abc\n", "de[f]|"], + true, + move_caret_down, + )?; + assert_move( + &["abc\n", "def|"], + &["abc\n", "def|"], + true, + move_caret_down, + )?; + assert_move( + &["|abc\n", "def"], + &["[abc\n", "]|def"], + true, + move_caret_down, + )?; + assert_move( + &["a|bc\n", "def"], + &["a[bc\n", "d]|ef"], + true, + move_caret_down, + )?; + assert_move( + &["ab|c\n", "def"], + &["ab[c\n", "de]|f"], + true, + move_caret_down, + )?; + assert_move( + &["abc|\n", "def"], + &["abc[\n", "def]|"], + true, + move_caret_down, + )?; + assert_move( + &["abc\n", "|def \n", "ghi"], + &["abc\n", "[def \n", "]|ghi"], + true, + move_caret_down, + )?; + assert_move( + &["abc\n", "d|ef \n", "ghi"], + &["abc\n", "d[ef \n", "g]|hi"], + true, + move_caret_down, + )?; + assert_move( + &["abc\n", "de|f \n", "ghi"], + &["abc\n", "de[f \n", "gh]|i"], + true, + move_caret_down, + )?; + assert_move( + &["abc\n", "def| \n", "ghi"], + &["abc\n", "def[ \n", "ghi]|"], + true, + move_caret_down, + )?; + assert_move( + &["abc\n", "def |\n", "ghi"], + &["abc\n", "def [\n", "ghi]|"], + true, + move_caret_down, + )?; + assert_move( + &["abc\n", "de|\n", "ghi"], + &["abc\n", "de[\n", "gh]|i"], + true, + move_caret_down, + )?; + assert_move( + &["abc|\n", "de"], + &["abc[\n", "de]|"], + true, + move_caret_down, + )?; + assert_move( + &["ab|c\n", "de"], + &["ab[c\n", "de]|"], + true, + move_caret_down, + )?; + assert_move( + &["a|bc\n", "de"], + &["a[bc\n", "d]|e"], + true, + move_caret_down, + )?; + assert_move( + &["|abc\n", "de"], + &["[abc\n", "]|de"], + true, + move_caret_down, + )?; + assert_move( + &["ab|\n", "cdef\n", "ghijkl\n", "mnopqrst"], + &["ab[\n", "cd]|ef\n", "ghijkl\n", "mnopqrst"], + true, + move_caret_down, + )?; + assert_move( + &["ab\n", "cdef|\n", "ghijkl\n", "mnopqrst"], + &["ab\n", "cdef[\n", "ghij]|kl\n", "mnopqrst"], + true, + move_caret_down, + )?; + assert_move( + &["ab\n", "cdef\n", "ghijkl|\n", "mnopqrst"], + &["ab\n", "cdef\n", "ghijkl[\n", "mnopqr]|st"], + true, + move_caret_down, + )?; + assert_move( + &[" |ab\n", " cdef\n", "ghijkl\n", "mnopqrst"], + &[" [ab\n", " ]|cdef\n", "ghijkl\n", "mnopqrst"], + true, + move_caret_down, + )?; + assert_move( + &["ab\n", "|cdef\n", "ghijkl\n", "mnopqrst"], + &["ab\n", "[cdef\n", "]|ghijkl\n", "mnopqrst"], + true, + move_caret_down, + )?; + assert_move( + &["ab\n", "cdef\n", "|ghijkl\n", "mnopqrst"], + &["ab\n", "cdef\n", "[ghijkl\n", "]|mnopqrst"], + true, + move_caret_down, + )?; + assert_move( + &["abcdefgh|\n", "ijklmn\n", "opqr\n", "st"], + &["abcdefgh[\n", "ijklmn]|\n", "opqr\n", "st"], + true, + move_caret_down, + )?; + assert_move( + &["abcdefgh\n", "ijklmn|\n", "opqr\n", "st"], + &["abcdefgh\n", "ijklmn[\n", "opqr]|\n", "st"], + true, + move_caret_down, + )?; + assert_move( + &["abcdefgh\n", "ijklmn\n", "opqr|\n", "st"], + &["abcdefgh\n", "ijklmn\n", "opqr[\n", "st]|"], + true, + move_caret_down, + )?; + assert_move( + &["abcdefgh\n", "ijklmn\n", "opqr\n", "|st"], + &["abcdefgh\n", "ijklmn\n", "opqr\n", "[st]|"], + true, + move_caret_down, + )?; + assert_move(&["abc def gh |"], &["abc def gh |"], true, move_caret_down)?; + assert_move( + &["abc de|f gh "], + &["abc de[f gh ]|"], + true, + move_caret_down, + )?; + assert_move( + &["ab|c def gh "], + &["ab[c def gh ]|"], + true, + move_caret_down, + )?; + assert_move( + &["a|bc def gh "], + &["a[bc def gh ]|"], + true, + move_caret_down, + )?; + assert_move( + &["|abc def gh "], + &["[abc def gh ]|"], + true, + move_caret_down, + )?; + + Ok(()) + } + + #[test] + fn start_selection_up() -> Result<(), String> { + assert_move(&["|"], &["|"], true, move_caret_up)?; + assert_move(&["|a"], &["|a"], true, move_caret_up)?; + assert_move(&["A|"], &["|[A]"], true, move_caret_up)?; + assert_move(&["a|bc"], &["|[a]bc"], true, move_caret_up)?; + assert_move(&["ab|c"], &["|[ab]c"], true, move_caret_up)?; + assert_move(&["abc|"], &["|[abc]"], true, move_caret_up)?; + assert_move(&["|abc\n", "def"], &["|abc\n", "def"], true, move_caret_up)?; + assert_move( + &["abc\n", "|def"], + &["|[abc\n", "]def"], + true, + move_caret_up, + )?; + assert_move( + &["abc\n", "d|ef"], + &["a|[bc\n", "d]ef"], + true, + move_caret_up, + )?; + assert_move( + &["abc\n", "de|f"], + &["ab|[c\n", "de]f"], + true, + move_caret_up, + )?; + assert_move( + &["abc\n", "def|"], + &["abc|[\n", "def]"], + true, + move_caret_up, + )?; + assert_move( + &["abc\n", "def \n", "|ghi"], + &["abc\n", "|[def \n", "]ghi"], + true, + move_caret_up, + )?; + assert_move( + &["abc\n", "def \n", "g|hi"], + &["abc\n", "d|[ef \n", "g]hi"], + true, + move_caret_up, + )?; + assert_move( + &["abc\n", "def \n", "gh|i"], + &["abc\n", "de|[f \n", "gh]i"], + true, + move_caret_up, + )?; + assert_move( + &["abc\n", "def \n", "ghi|"], + &["abc\n", "def|[ \n", "ghi]"], + true, + move_caret_up, + )?; + assert_move( + &["abc\n", "de\n", "ghi|"], + &["abc\n", "de|[\n", "ghi]"], + true, + move_caret_up, + )?; + assert_move(&["abc\n", "de|"], &["ab|[c\n", "de]"], true, move_caret_up)?; + assert_move(&["abc\n", "d|e"], &["a|[bc\n", "d]e"], true, move_caret_up)?; + assert_move(&["abc\n", "|de"], &["|[abc\n", "]de"], true, move_caret_up)?; + assert_move( + &["ab\n", "cdef\n", "ghijkl\n", "mnopqrst|"], + &["ab\n", "cdef\n", "ghijkl|[\n", "mnopqrst]"], + true, + move_caret_up, + )?; + assert_move( + &["ab\n", "cdef\n", "ghijkl|\n", "mnopqrst"], + &["ab\n", "cdef|[\n", "ghijkl]\n", "mnopqrst"], + true, + move_caret_up, + )?; + assert_move( + &["ab\n", "cdef\n", "ghijkl\n", "|mnopqrst"], + &["ab\n", "cdef\n", "|[ghijkl\n", "]mnopqrst"], + true, + move_caret_up, + )?; + assert_move( + &[" ab\n", " |cdef\n", "ghijkl\n", "mnopqrst"], + &[" |[ab\n", " ]cdef\n", "ghijkl\n", "mnopqrst"], + true, + move_caret_up, + )?; + assert_move( + &["ab\n", "cdef\n", "ghijkl\n", "mnopqr|st"], + &["ab\n", "cdef\n", "ghijkl|[\n", "mnopqr]st"], + true, + move_caret_up, + )?; + assert_move( + &["ab\n", "cde|f\n", "ghijkl\n", "mnopqrst"], + &["ab|[\n", "cde]f\n", "ghijkl\n", "mnopqrst"], + true, + move_caret_up, + )?; + assert_move( + &["abcdefgh\n", "ijklmn\n", "opqr\n", "st|"], + &["abcdefgh\n", "ijklmn\n", "op|[qr\n", "st]"], + true, + move_caret_up, + )?; + assert_move( + &["abcdefgh\n", "ijklmn\n", "opqr|\n", "st"], + &["abcdefgh\n", "ijkl|[mn\n", "opqr]\n", "st"], + true, + move_caret_up, + )?; + assert_move( + &["abcdefgh\n", "ijklmn|\n", "opqr\n", "st"], + &["abcdef|[gh\n", "ijklmn]\n", "opqr\n", "st"], + true, + move_caret_up, + )?; + assert_move( + &["abcdefgh|\n", "ijklmn\n", "opqr\n", "st"], + &["|[abcdefgh]\n", "ijklmn\n", "opqr\n", "st"], + true, + move_caret_up, + )?; + assert_move( + &["abcdefg|h\n", "ijklmn\n", "opqr\n", "st"], + &["|[abcdefg]h\n", "ijklmn\n", "opqr\n", "st"], + true, + move_caret_up, + )?; + assert_move( + &["a|bcdefgh\n", "ijklmn\n", "opqr\n", "st"], + &["|[a]bcdefgh\n", "ijklmn\n", "opqr\n", "st"], + true, + move_caret_up, + )?; + assert_move( + &["|abcdefgh\n", "ijklmn\n", "opqr\n", "st"], + &["|abcdefgh\n", "ijklmn\n", "opqr\n", "st"], + true, + move_caret_up, + )?; + assert_move(&["abc def gh |"], &["|[abc def gh ]"], true, move_caret_up)?; + assert_move(&["abc de|f gh "], &["|[abc de]f gh "], true, move_caret_up)?; + assert_move(&["ab|c def gh "], &["|[ab]c def gh "], true, move_caret_up)?; + assert_move(&["a|bc def gh "], &["|[a]bc def gh "], true, move_caret_up)?; + + Ok(()) + } + + #[test] + fn end_selection_right() -> Result<(), String> { + assert_move(&["[A]|"], &["A|"], false, move_caret_right)?; + assert_move(&["[a]|bc"], &["a|bc"], false, move_caret_right)?; + assert_move(&["a[b]|c"], &["ab|c"], false, move_caret_right)?; + assert_move(&["ab[c]|"], &["abc|"], false, move_caret_right)?; + assert_move(&["[ ]|abc"], &[" |abc"], false, move_caret_right)?; + assert_move(&["|[ ]abc"], &[" |abc"], false, move_caret_right)?; + assert_move(&["a|[b]c"], &["ab|c"], false, move_caret_right)?; + assert_move( + &["abc[\n", "]|d"], + &["abc\n", "|d"], + false, + move_caret_right, + )?; + assert_move( + &["abc|[\n", "]d"], + &["abc\n", "|d"], + false, + move_caret_right, + )?; + assert_move(&["abc|[\n", "]"], &["abc\n", "|"], false, move_caret_right)?; + assert_move( + &["abc\n", "[d]|ef"], + &["abc\n", "d|ef"], + false, + move_caret_right, + )?; + assert_move( + &["abc\n", "def\n", "ghi[\n", "]|jkl"], + &["abc\n", "def\n", "ghi\n", "|jkl"], + false, + move_caret_right, + )?; + assert_move(&["[ab]|c"], &["ab|c"], false, move_caret_right)?; + assert_move(&["[abc]|"], &["abc|"], false, move_caret_right)?; + assert_move( + &["ab|[c\n", "]def\n", "ghi"], + &["abc\n", "|def\n", "ghi"], + false, + move_caret_right, + )?; + assert_move( + &["ab[c\n", "]|def\n", "ghi"], + &["abc\n", "|def\n", "ghi"], + false, + move_caret_right, + )?; + assert_move( + &["a|[bc\n", "]def\n", "ghi"], + &["abc\n", "|def\n", "ghi"], + false, + move_caret_right, + )?; + assert_move( + &["|[abc\n", "]def\n", "ghi"], + &["abc\n", "|def\n", "ghi"], + false, + move_caret_right, + )?; + assert_move( + &["a|[bc\n", "d]ef\n", "ghi"], + &["abc\n", "d|ef\n", "ghi"], + false, + move_caret_right, + )?; + assert_move( + &["|[abc\n", "def]\n", "ghi"], + &["abc\n", "def|\n", "ghi"], + false, + move_caret_right, + )?; + assert_move( + &["[ab\n", "cdef\n", "ghijkl\n", "mnopqrst]|"], + &["ab\n", "cdef\n", "ghijkl\n", "mnopqrst|"], + false, + move_caret_right, + )?; + assert_move( + &["|[ab\n", "cdef\n", "ghijkl\n", "mnopqrst]"], + &["ab\n", "cdef\n", "ghijkl\n", "mnopqrst|"], + false, + move_caret_right, + )?; + assert_move( + &["ab\n", "c[def\n", "ghijkl\n", "mno]|pqrst"], + &["ab\n", "cdef\n", "ghijkl\n", "mno|pqrst"], + false, + move_caret_right, + )?; + assert_move( + &["ab\n", "c|[def\n", "ghijkl\n", "mno]pqrst"], + &["ab\n", "cdef\n", "ghijkl\n", "mno|pqrst"], + false, + move_caret_right, + )?; + + Ok(()) + } + + #[test] + fn end_selection_left() -> Result<(), String> { + assert_move(&["[A]|"], &["|A"], false, move_caret_left)?; + assert_move(&["[a]|bc"], &["|abc"], false, move_caret_left)?; + assert_move(&["a[b]|c"], &["a|bc"], false, move_caret_left)?; + assert_move(&["ab[c]|"], &["ab|c"], false, move_caret_left)?; + assert_move(&["[ ]|abc"], &["| abc"], false, move_caret_left)?; + assert_move(&["|[ ]abc"], &["| abc"], false, move_caret_left)?; + assert_move(&["a|[b]c"], &["a|bc"], false, move_caret_left)?; + assert_move(&["abc[\n", "]|d"], &["abc|\n", "d"], false, move_caret_left)?; + assert_move(&["abc|[\n", "]d"], &["abc|\n", "d"], false, move_caret_left)?; + assert_move(&["abc|[\n", "]"], &["abc|\n", ""], false, move_caret_left)?; + assert_move( + &["abc\n", "[d]|ef"], + &["abc\n", "|def"], + false, + move_caret_left, + )?; + assert_move( + &["abc\n", "def\n", "ghi[\n", "]|jkl"], + &["abc\n", "def\n", "ghi|\n", "jkl"], + false, + move_caret_left, + )?; + assert_move(&["[ab]|c"], &["|abc"], false, move_caret_left)?; + assert_move(&["[abc]|"], &["|abc"], false, move_caret_left)?; + assert_move( + &["ab|[c\n", "]def\n", "ghi"], + &["ab|c\n", "def\n", "ghi"], + false, + move_caret_left, + )?; + assert_move( + &["ab[c\n", "]|def\n", "ghi"], + &["ab|c\n", "def\n", "ghi"], + false, + move_caret_left, + )?; + assert_move( + &["a|[bc\n", "]def\n", "ghi"], + &["a|bc\n", "def\n", "ghi"], + false, + move_caret_left, + )?; + assert_move( + &["|[abc\n", "]def\n", "ghi"], + &["|abc\n", "def\n", "ghi"], + false, + move_caret_left, + )?; + assert_move( + &["a|[bc\n", "d]ef\n", "ghi"], + &["a|bc\n", "def\n", "ghi"], + false, + move_caret_left, + )?; + assert_move( + &["|[abc\n", "def]\n", "ghi"], + &["|abc\n", "def\n", "ghi"], + false, + move_caret_left, + )?; + assert_move( + &["[ab\n", "cdef\n", "ghijkl\n", "mnopqrst]|"], + &["|ab\n", "cdef\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_left, + )?; + assert_move( + &["|[ab\n", "cdef\n", "ghijkl\n", "mnopqrst]"], + &["|ab\n", "cdef\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_left, + )?; + assert_move( + &["ab\n", "c[def\n", "ghijkl\n", "mno]|pqrst"], + &["ab\n", "c|def\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_left, + )?; + assert_move( + &["ab\n", "c|[def\n", "ghijkl\n", "mno]pqrst"], + &["ab\n", "c|def\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_left, + )?; + + Ok(()) + } + + #[test] + fn end_selection_down() -> Result<(), String> { + assert_move(&["[a]|"], &["a|"], false, move_caret_down)?; + assert_move(&["|[a]"], &["a|"], false, move_caret_down)?; + assert_move(&["a|[bc]"], &["abc|"], false, move_caret_down)?; + assert_move(&["ab[c]|"], &["abc|"], false, move_caret_down)?; + assert_move(&["abc|[ ]"], &["abc |"], false, move_caret_down)?; + assert_move( + &["abc\n", "|[def]"], + &["abc\n", "def|"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "d|[ef]"], + &["abc\n", "def|"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "de|[f]"], + &["abc\n", "def|"], + false, + move_caret_down, + )?; + assert_move( + &["[abc\n", "]|def"], + &["abc\n", "|def"], + false, + move_caret_down, + )?; + assert_move( + &["a[bc\n", "d]|ef"], + &["abc\n", "d|ef"], + false, + move_caret_down, + )?; + assert_move( + &["ab|[c\n", "de]f"], + &["abc\n", "de|f"], + false, + move_caret_down, + )?; + assert_move( + &["abc[\n", "def]|"], + &["abc\n", "def|"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "|[def \n", "]ghi"], + &["abc\n", "def \n", "|ghi"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "d[ef \n", "g]|hi"], + &["abc\n", "def \n", "g|hi"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "de[f \n", "gh]|i"], + &["abc\n", "def \n", "gh|i"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "def[ \n", "ghi]|"], + &["abc\n", "def \n", "ghi|"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "def [\n", "ghi]|"], + &["abc\n", "def \n", "ghi|"], + false, + move_caret_down, + )?; + assert_move( + &["abc\n", "de[\n", "gh]|i"], + &["abc\n", "de\n", "gh|i"], + false, + move_caret_down, + )?; + assert_move( + &["abc|[\n", "de]"], + &["abc\n", "de|"], + false, + move_caret_down, + )?; + assert_move( + &["ab[c\n", "de]|"], + &["abc\n", "de|"], + false, + move_caret_down, + )?; + assert_move( + &["a|[bc\n", "d]e"], + &["abc\n", "d|e"], + false, + move_caret_down, + )?; + assert_move( + &["[abc\n", "]|de"], + &["abc\n", "|de"], + false, + move_caret_down, + )?; + assert_move( + &["ab[\n", "cd]|ef\n", "ghijkl\n", "mnopqrst"], + &["ab\n", "cd|ef\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_down, + )?; + assert_move( + &["ab\n", "cdef|[\n", "ghij]kl\n", "mnopqrst"], + &["ab\n", "cdef\n", "ghij|kl\n", "mnopqrst"], + false, + move_caret_down, + )?; + assert_move( + &["ab\n", "cdef\n", "ghijkl[\n", "mnopqr]|st"], + &["ab\n", "cdef\n", "ghijkl\n", "mnopqr|st"], + false, + move_caret_down, + )?; + assert_move( + &[" [ab\n", " ]|cdef\n", "ghijkl\n", "mnopqrst"], + &[" ab\n", " |cdef\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_down, + )?; + assert_move( + &["ab\n", "|[cdef\n", "]ghijkl\n", "mnopqrst"], + &["ab\n", "cdef\n", "|ghijkl\n", "mnopqrst"], + false, + move_caret_down, + )?; + assert_move( + &["ab\n", "cdef\n", "[ghijkl\n", "]|mnopqrst"], + &["ab\n", "cdef\n", "ghijkl\n", "|mnopqrst"], + false, + move_caret_down, + )?; + assert_move( + &["abcdefgh[\n", "ijklmn]|\n", "opqr\n", "st"], + &["abcdefgh\n", "ijklmn|\n", "opqr\n", "st"], + false, + move_caret_down, + )?; + assert_move( + &["abcdefgh\n", "ijklmn[\n", "opqr]|\n", "st"], + &["abcdefgh\n", "ijklmn\n", "opqr|\n", "st"], + false, + move_caret_down, + )?; + assert_move( + &["abcdefgh\n", "ijklmn\n", "opqr[\n", "st]|"], + &["abcdefgh\n", "ijklmn\n", "opqr\n", "st|"], + false, + move_caret_down, + )?; + assert_move( + &["abcdefgh\n", "ijklmn\n", "opqr\n", "[st]|"], + &["abcdefgh\n", "ijklmn\n", "opqr\n", "st|"], + false, + move_caret_down, + )?; + assert_move( + &["abc de[f gh ]|"], + &["abc def gh |"], + false, + move_caret_down, + )?; + assert_move( + &["ab|[c def gh ]"], + &["abc def gh |"], + false, + move_caret_down, + )?; + assert_move( + &["a[bc def gh ]|"], + &["abc def gh |"], + false, + move_caret_down, + )?; + assert_move( + &["[abc def gh ]|"], + &["abc def gh |"], + false, + move_caret_down, + )?; + + Ok(()) + } + + #[test] + fn end_selection_up() -> Result<(), String> { + assert_move(&["[a]|"], &["|a"], false, move_caret_up)?; + assert_move(&["|[a]"], &["|a"], false, move_caret_up)?; + assert_move(&["a|[bc]"], &["a|bc"], false, move_caret_up)?; + assert_move(&["ab[c]|"], &["ab|c"], false, move_caret_up)?; + assert_move(&["abc|[ ]"], &["abc| "], false, move_caret_up)?; + assert_move( + &["abc\n", "|[def]"], + &["abc\n", "|def"], + false, + move_caret_up, + )?; + assert_move( + &["abc\n", "d|[ef]"], + &["abc\n", "d|ef"], + false, + move_caret_up, + )?; + assert_move( + &["abc\n", "de|[f]"], + &["abc\n", "de|f"], + false, + move_caret_up, + )?; + assert_move( + &["[abc\n", "]|def"], + &["|abc\n", "def"], + false, + move_caret_up, + )?; + assert_move( + &["a[bc\n", "d]|ef"], + &["a|bc\n", "def"], + false, + move_caret_up, + )?; + assert_move( + &["ab|[c\n", "de]f"], + &["ab|c\n", "def"], + false, + move_caret_up, + )?; + assert_move( + &["abc[\n", "def]|"], + &["abc|\n", "def"], + false, + move_caret_up, + )?; + assert_move( + &["abc\n", "|[def \n", "]ghi"], + &["abc\n", "|def \n", "ghi"], + false, + move_caret_up, + )?; + assert_move( + &["abc\n", "d[ef \n", "g]|hi"], + &["abc\n", "d|ef \n", "ghi"], + false, + move_caret_up, + )?; + assert_move( + &["abc\n", "de|[f \n", "gh]i"], + &["abc\n", "de|f \n", "ghi"], + false, + move_caret_up, + )?; + assert_move( + &["abc\n", "def[ \n", "ghi]|"], + &["abc\n", "def| \n", "ghi"], + false, + move_caret_up, + )?; + assert_move( + &["abc\n", "def [\n", "ghi]|"], + &["abc\n", "def |\n", "ghi"], + false, + move_caret_up, + )?; + assert_move( + &["abc\n", "de[\n", "gh]|i"], + &["abc\n", "de|\n", "ghi"], + false, + move_caret_up, + )?; + assert_move(&["abc|[\n", "de]"], &["abc|\n", "de"], false, move_caret_up)?; + assert_move(&["ab[c\n", "de]|"], &["ab|c\n", "de"], false, move_caret_up)?; + assert_move(&["a|[bc\n", "d]e"], &["a|bc\n", "de"], false, move_caret_up)?; + assert_move(&["[abc\n", "]|de"], &["|abc\n", "de"], false, move_caret_up)?; + assert_move( + &["ab[\n", "cd]|ef\n", "ghijkl\n", "mnopqrst"], + &["ab|\n", "cdef\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_up, + )?; + assert_move( + &["ab\n", "cdef|[\n", "ghij]kl\n", "mnopqrst"], + &["ab\n", "cdef|\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_up, + )?; + assert_move( + &["ab\n", "cdef\n", "ghijkl[\n", "mnopqr]|st"], + &["ab\n", "cdef\n", "ghijkl|\n", "mnopqrst"], + false, + move_caret_up, + )?; + assert_move( + &[" [ab\n", " ]|cdef\n", "ghijkl\n", "mnopqrst"], + &[" |ab\n", " cdef\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_up, + )?; + assert_move( + &["ab\n", "|[cdef\n", "]ghijkl\n", "mnopqrst"], + &["ab\n", "|cdef\n", "ghijkl\n", "mnopqrst"], + false, + move_caret_up, + )?; + assert_move( + &["ab\n", "cdef\n", "[ghijkl\n", "]|mnopqrst"], + &["ab\n", "cdef\n", "|ghijkl\n", "mnopqrst"], + false, + move_caret_up, + )?; + assert_move( + &["abcdefgh[\n", "ijklmn]|\n", "opqr\n", "st"], + &["abcdefgh|\n", "ijklmn\n", "opqr\n", "st"], + false, + move_caret_up, + )?; + assert_move( + &["abcdefgh\n", "ijklmn[\n", "opqr]|\n", "st"], + &["abcdefgh\n", "ijklmn|\n", "opqr\n", "st"], + false, + move_caret_up, + )?; + assert_move( + &["abcdefgh\n", "ijklmn\n", "opqr[\n", "st]|"], + &["abcdefgh\n", "ijklmn\n", "opqr|\n", "st"], + false, + move_caret_up, + )?; + assert_move( + &["abcdefgh\n", "ijklmn\n", "opqr\n", "[st]|"], + &["abcdefgh\n", "ijklmn\n", "opqr\n", "|st"], + false, + move_caret_up, + )?; + assert_move(&["abc de[f gh ]|"], &["abc de|f gh "], false, move_caret_up)?; + assert_move(&["ab|[c def gh ]"], &["ab|c def gh "], false, move_caret_up)?; + assert_move(&["a[bc def gh ]|"], &["a|bc def gh "], false, move_caret_up)?; + assert_move(&["[abc def gh ]|"], &["|abc def gh "], false, move_caret_up)?; + + Ok(()) + } + + #[test] + fn extend_selection_right() -> Result<(), String> { + assert_move(&["[a]|bc"], &["[ab]|c"], true, move_caret_right)?; + assert_move(&["a[b]|c"], &["a[bc]|"], true, move_caret_right)?; + assert_move(&["[ab]|c"], &["[abc]|"], true, move_caret_right)?; + assert_move(&["[ ]|abc"], &["[ a]|bc"], true, move_caret_right)?; + assert_move(&["[abc]|"], &["[abc]|"], true, move_caret_right)?; + assert_move(&["a[bc]|"], &["a[bc]|"], true, move_caret_right)?; + assert_move(&["ab[c]|"], &["ab[c]|"], true, move_caret_right)?; + assert_move( + &["abc[\n", "]|d"], + &["abc[\n", "d]|"], + true, + move_caret_right, + )?; + assert_move(&["ab[c]|\n", ""], &["ab[c\n", "]|"], true, move_caret_right)?; + assert_move( + &["ab[c]|\n", "d"], + &["ab[c\n", "]|d"], + true, + move_caret_right, + )?; + assert_move( + &["abc\n", "def\n", "ghi[\n", "]|jkl"], + &["abc\n", "def\n", "ghi[\n", "j]|kl"], + true, + move_caret_right, + )?; + assert_move( + &["ab[c\n", "def\n", "ghi\n", "]|jkl"], + &["ab[c\n", "def\n", "ghi\n", "j]|kl"], + true, + move_caret_right, + )?; + assert_move( + &["ab[c\n", "def\n", "]|ghi\n", "jkl"], + &["ab[c\n", "def\n", "g]|hi\n", "jkl"], + true, + move_caret_right, + )?; + assert_move( + &["[abc\n", "def\n", "ghi\n", "jk]|l"], + &["[abc\n", "def\n", "ghi\n", "jkl]|"], + true, + move_caret_right, + )?; + assert_move( + &["[abc\n", "def\n", "ghi\n", "jkl]|"], + &["[abc\n", "def\n", "ghi\n", "jkl]|"], + true, + move_caret_right, + )?; + + Ok(()) + } + + #[test] + fn extend_selection_left() -> Result<(), String> { + assert_move(&["ab|[c]"], &["a|[bc]"], true, move_caret_left)?; + assert_move(&["a|[bc]"], &["|[abc]"], true, move_caret_left)?; + assert_move(&["|[abc]"], &["|[abc]"], true, move_caret_left)?; + assert_move(&["|[ab]c"], &["|[ab]c"], true, move_caret_left)?; + assert_move(&["|[a]bc"], &["|[a]bc"], true, move_caret_left)?; + assert_move(&[" |[a]bc"], &["|[ a]bc"], true, move_caret_left)?; + assert_move( + &["abc|[\n", "]d"], + &["ab|[c\n", "]d"], + true, + move_caret_left, + )?; + assert_move( + &["abc\n", "|[d]"], + &["abc|[\n", "d]"], + true, + move_caret_left, + )?; + assert_move(&["ab|[c\n", "]"], &["a|[bc\n", "]"], true, move_caret_left)?; + assert_move( + &["abc\n", "def|[\n", "ghi\n", "j]kl"], + &["abc\n", "de|[f\n", "ghi\n", "j]kl"], + true, + move_caret_left, + )?; + assert_move( + &["a|[bc\n", "def\n", "ghi\n", "jkl]"], + &["|[abc\n", "def\n", "ghi\n", "jkl]"], + true, + move_caret_left, + )?; + assert_move( + &["abc\n", "def\n", "ghi\n", "|[jkl]"], + &["abc\n", "def\n", "ghi|[\n", "jkl]"], + true, + move_caret_left, + )?; + + Ok(()) + } + + #[test] + fn extend_selection_up() -> Result<(), String> { + assert_move(&["ab|[c]"], &["|[abc]"], true, move_caret_up)?; + assert_move(&["a|[bc]"], &["|[abc]"], true, move_caret_up)?; + assert_move(&["|[abc]"], &["|[abc]"], true, move_caret_up)?; + assert_move(&["|[ab]c"], &["|[ab]c"], true, move_caret_up)?; + assert_move(&["|[a]bc"], &["|[a]bc"], true, move_caret_up)?; + assert_move(&[" |[a]bc"], &["|[ a]bc"], true, move_caret_up)?; + assert_move(&["ab[c]|"], &["|[ab]c"], true, move_caret_up)?; + assert_move(&["[a]|"], &["|a"], true, move_caret_up)?; + assert_move(&["[a]|bc"], &["|abc"], true, move_caret_up)?; + assert_move(&["[a]|bc\n", "d"], &["|abc\n", "d"], true, move_caret_up)?; + assert_move( + &["abc\n", "de[f]|"], + &["abc|[\n", "de]f"], + true, + move_caret_up, + )?; + assert_move( + &["abc\n", "de|[f]"], + &["ab|[c\n", "def]"], + true, + move_caret_up, + )?; + assert_move( + &["ab|[c\n", "def]"], + &["|[abc\n", "def]"], + true, + move_caret_up, + )?; + assert_move( + &["ab\n", "cdef\n", "ghijkl\n", "[mnopqr]|st"], + &["ab\n", "cdef\n", "ghijkl|[\n", "]mnopqrst"], + true, + move_caret_up, + )?; + assert_move( + &["ab\n", "cdef\n", "ghijkl\n", "[mnopqrs]|t"], + &["ab\n", "cdef\n", "ghijkl|[\n", "]mnopqrst"], + true, + move_caret_up, + )?; + assert_move( + &["abcdefgh\n", "ijklmn\n", "|[o]pqr\n", "st"], + &["abcdefgh\n", "|[ijklmn\n", "o]pqr\n", "st"], + true, + move_caret_up, + )?; + + Ok(()) + } + + #[test] + fn extend_selection_down() -> Result<(), String> { + assert_move(&["[ab]|c"], &["[abc]|"], true, move_caret_down)?; + assert_move(&["[a]|bc"], &["[abc]|"], true, move_caret_down)?; + assert_move(&["[abc]|"], &["[abc]|"], true, move_caret_down)?; + assert_move(&["|[ab]c"], &["ab[c]|"], true, move_caret_down)?; + assert_move(&["|[a]bc"], &["a[bc]|"], true, move_caret_down)?; + assert_move( + &["[a]|bc\n", "d"], + &["[abc\n", "d]|"], + true, + move_caret_down, + )?; + assert_move( + &["[a]|bc\n", "de"], + &["[abc\n", "d]|e"], + true, + move_caret_down, + )?; + assert_move( + &["[abc\n", "d]|e"], + &["[abc\n", "de]|"], + true, + move_caret_down, + )?; + assert_move(&["[a]|bc\n", ""], &["[abc\n", "]|"], true, move_caret_down)?; + assert_move( + &["ab\n", "cdef\n", "ghijkl\n", "[mnopqr]|st"], + &["ab\n", "cdef\n", "ghijkl\n", "[mnopqrst]|"], + true, + move_caret_down, + )?; + assert_move( + &["a[b\n", "cdef\n", "ghijkl\n", "mnopqr]|st"], + &["a[b\n", "cdef\n", "ghijkl\n", "mnopqrst]|"], + true, + move_caret_down, + )?; + assert_move( + &["[ab\n", "cdef\n", "ghijkl\n", "mnopqrst]|"], + &["[ab\n", "cdef\n", "ghijkl\n", "mnopqrst]|"], + true, + move_caret_down, + )?; + assert_move( + &["abcd[efgh]|\n", "ijklmn\n", "opqr\n", "st"], + &["abcd[efgh\n", "ijklmn]|\n", "opqr\n", "st"], + true, + move_caret_down, + )?; + assert_move( + &["abcd[e]|fgh\n", "ijklmn\n", "opqr\n", "st"], + &["abcd[efgh\n", "ijklm]|n\n", "opqr\n", "st"], + true, + move_caret_down, + )?; + + Ok(()) + } + + #[test] + fn shrink_selection_right() -> Result<(), String> { + assert_move(&["ab|[c]"], &["abc|"], true, move_caret_right)?; + assert_move(&["a|[bc]"], &["ab|[c]"], true, move_caret_right)?; + assert_move(&["|[abc]"], &["a|[bc]"], true, move_caret_right)?; + assert_move( + &["|[abc\n", "def\n", "ghi\n", "jkl]"], + &["a|[bc\n", "def\n", "ghi\n", "jkl]"], + true, + move_caret_right, + )?; + assert_move( + &["abc\n", "d|[ef\n", "]ghi\n", "jkl"], + &["abc\n", "de|[f\n", "]ghi\n", "jkl"], + true, + move_caret_right, + )?; + assert_move( + &["abc\n", "de|[f]\n", "ghi\n", "jkl"], + &["abc\n", "def|\n", "ghi\n", "jkl"], + true, + move_caret_right, + )?; + + Ok(()) + } + + #[test] + fn shrink_selection_left() -> Result<(), String> { + assert_move(&["ab[c]|"], &["ab|c"], true, move_caret_left)?; + assert_move(&["a[bc]|"], &["a[b]|c"], true, move_caret_left)?; + assert_move(&["[abc]|"], &["[ab]|c"], true, move_caret_left)?; + assert_move( + &["[abc\n", "def\n", "ghi\n", "jkl]|"], + &["[abc\n", "def\n", "ghi\n", "jk]|l"], + true, + move_caret_left, + )?; + assert_move( + &["|[abc\n", "def\n", "ghi\n", "jkl]"], + &["|[abc\n", "def\n", "ghi\n", "jkl]"], + true, + move_caret_left, + )?; + assert_move( + &["abc\n", "def[\n", "]|ghi\n", "jkl"], + &["abc\n", "def|\n", "ghi\n", "jkl"], + true, + move_caret_left, + )?; + assert_move( + &["abc\n", "d[ef\n", "gh]|i\n", "jkl"], + &["abc\n", "d[ef\n", "g]|hi\n", "jkl"], + true, + move_caret_left, + )?; + + Ok(()) + } + + #[test] + fn shrink_selection_up() -> Result<(), String> { + assert_move(&["[abc]|"], &["|abc"], true, move_caret_up)?; + assert_move(&["[ab]|c"], &["|abc"], true, move_caret_up)?; + assert_move(&["[a]|bc"], &["|abc"], true, move_caret_up)?; + assert_move(&["|abc"], &["|abc"], true, move_caret_up)?; + assert_move( + &["[abc\n", "def]|"], + &["[abc]|\n", "def"], + true, + move_caret_up, + )?; + assert_move( + &["[abc\n", "de]|f"], + &["[ab]|c\n", "def"], + true, + move_caret_up, + )?; + assert_move( + &["[abc\n", "def\n", "ghi\n", "jkl]|"], + &["[abc\n", "def\n", "ghi]|\n", "jkl"], + true, + move_caret_up, + )?; + assert_move( + &["abc\n", "def\n", "ghi[\n", "jkl]|"], + &["abc\n", "def\n", "ghi|\n", "jkl"], + true, + move_caret_up, + )?; + assert_move( + &["abc\n", "d[ef\n", "ghi\n", "jk]|l"], + &["abc\n", "d[ef\n", "gh]|i\n", "jkl"], + true, + move_caret_up, + )?; + assert_move( + &["[abc\n", "d]|ef\n", "ghi\n", "jkl"], + &["[a]|bc\n", "def\n", "ghi\n", "jkl"], + true, + move_caret_up, + )?; + + Ok(()) + } + + #[test] + fn shrink_selection_down() -> Result<(), String> { + assert_move(&["|[abc]"], &["abc|"], true, move_caret_down)?; + assert_move( + &["|[abc\n", "def]"], + &["abc\n", "|[def]"], + true, + move_caret_down, + )?; + assert_move( + &["a|[bc\n", "def]"], + &["abc\n", "d|[ef]"], + true, + move_caret_down, + )?; + assert_move( + &["|[abc\n", "def\n", "ghi]"], + &["abc\n", "|[def\n", "ghi]"], + true, + move_caret_down, + )?; + assert_move( + &["ab|[c\n", "def\n", "ghi]"], + &["abc\n", "de|[f\n", "ghi]"], + true, + move_caret_down, + )?; + assert_move( + &["abc\n", "de|[f\n", "ghi]"], + &["abc\n", "def\n", "gh|[i]"], + true, + move_caret_down, + )?; + assert_move( + &["abcdef|[\n", "ghij\n", "kl]"], + &["abcdef\n", "ghij|[\n", "kl]"], + true, + move_caret_down, + )?; + assert_move( + &["abcde|[f\n", "ghij\n", "kl]"], + &["abcdef\n", "ghij|[\n", "kl]"], + true, + move_caret_down, + )?; + assert_move( + &["ab|[cdef\n", "ghij\n", "kl]"], + &["abcdef\n", "gh|[ij\n", "kl]"], + true, + move_caret_down, + )?; + + Ok(()) + } +} diff --git a/editor/src/ui/model/text/txt_pos.rs b/editor/src/ui/model/text/txt_pos.rs new file mode 100644 index 0000000000..0c023b6ad4 --- /dev/null +++ b/editor/src/ui/model/text/txt_pos.rs @@ -0,0 +1,28 @@ + +use std::cmp::Ordering; + +#[derive(Debug, Copy, Clone)] +pub struct TxtPos { + pub line: usize, + pub column: usize, +} + +impl Ord for TxtPos { + fn cmp(&self, other: &Self) -> Ordering { + (self.line, self.column).cmp(&(other.line, other.column)) + } +} + +impl PartialOrd for TxtPos { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for TxtPos { + fn eq(&self, other: &Self) -> bool { + (self.line, self.column) == (other.line, other.column) + } +} + +impl Eq for TxtPos {} \ No newline at end of file diff --git a/editor/src/ui/update/mod.rs b/editor/src/ui/update/mod.rs new file mode 100644 index 0000000000..33a595ed27 --- /dev/null +++ b/editor/src/ui/update/mod.rs @@ -0,0 +1 @@ +pub mod SelectableTextU; \ No newline at end of file diff --git a/editor/src/ui/update/selectable_text_u.rs b/editor/src/ui/update/selectable_text_u.rs new file mode 100644 index 0000000000..9e43a2f002 --- /dev/null +++ b/editor/src/ui/update/selectable_text_u.rs @@ -0,0 +1,62 @@ +impl SelectableText { + pub fn move_caret_w_mods(&mut self, new_pos: Position, mods: &ModifiersState) { + let caret_pos = self.caret_pos; + + // one does not simply move the caret + if new_pos != caret_pos { + if mods.shift() { + if let Some(selection) = self.selection_opt { + if new_pos < selection.start_pos { + if caret_pos > selection.start_pos { + self.set_selection( + new_pos, + selection.start_pos + ) + } else { + self.set_selection( + new_pos, + selection.end_pos + ) + } + } else if new_pos > selection.end_pos { + if caret_pos < selection.end_pos { + self.set_selection( + selection.end_pos, + new_pos + ) + } else { + self.set_selection( + selection.start_pos, + new_pos + ) + } + } else if new_pos > caret_pos { + self.set_selection( + new_pos, + selection.end_pos + ) + } else if new_pos < caret_pos { + self.set_selection( + selection.start_pos, + new_pos + ) + } + } else if new_pos < self.caret_pos { + self.set_selection( + new_pos, + caret_pos + ) + } else { + self.set_selection( + caret_pos, + new_pos + ) + } + } else { + self.selection_opt = None; + } + + self.caret_pos = new_pos; + } + } +} \ No newline at end of file