diff --git a/Cargo.lock b/Cargo.lock index 2d40ce558c..9c30960788 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2013,6 +2013,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "nonempty" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fa586da3e43cc7df44aae0e21ed2e743218b876de3f38035683d30bd8a3828e" + [[package]] name = "num-traits" version = "0.2.14" @@ -3034,6 +3040,7 @@ dependencies = [ "libc", "log", "maplit", + "nonempty", "page_size", "palette", "pest", diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 704462fa1c..0fa795d25c 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -48,6 +48,7 @@ confy = { git = 'https://github.com/rust-cli/confy', features = [ "yaml_conf" ], default-features = false } serde = { version = "1.0.123", features = ["derive"] } +nonempty = "0.6.0" [dependencies.bytemuck] version = "1.4" diff --git a/editor/src/editor/code_lines.rs b/editor/src/editor/code_lines.rs new file mode 100644 index 0000000000..af1ed3bfcd --- /dev/null +++ b/editor/src/editor/code_lines.rs @@ -0,0 +1,82 @@ + +use crate::ui::ui_error::UIResult; +use crate::ui::util::slice_get; +use bumpalo::collections::String as BumpString; +use bumpalo::Bump; +use crate::ui::text::lines::Lines; + +#[derive(Debug)] +pub struct CodeLines { + pub lines: Vec, + pub nr_of_chars: usize, +} + +impl CodeLines { + pub fn from_bump_str(code_str: &str) -> CodeLines { + CodeLines { + lines: split_inclusive(code_str), + nr_of_chars: code_str.len(), + } + } +} + +//TODO use rust's split_inclusive once it's no longer unstable +fn split_inclusive(code_str: &str) -> Vec { + let mut split_vec: Vec = Vec::new(); + let mut temp_str = String::new(); + let mut non_space_encountered = false; + + for token in code_str.chars() { + if token != ' ' && token != '\n' { + non_space_encountered = true; + temp_str.push(token); + } else if non_space_encountered { + split_vec.push(temp_str); + temp_str = String::new(); + temp_str.push(token); + non_space_encountered = false; + } else { + temp_str.push(token); + } + } + + if !temp_str.is_empty() { + split_vec.push(temp_str); + } + + split_vec +} + +impl Lines for CodeLines { + fn get_line(&self, line_nr: usize) -> UIResult<&str> { + let line_string = slice_get(line_nr, &self.lines)?; + + Ok(&line_string) + } + + fn line_len(&self, line_nr: usize) -> UIResult { + self.get_line(line_nr).map(|line| line.len()) + } + + fn nr_of_lines(&self) -> usize { + self.lines.len() + } + + fn nr_of_chars(&self) -> usize { + self.nr_of_chars + } + + fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a> { + let mut lines = BumpString::with_capacity_in(self.nr_of_chars(), arena); + + for line in &self.lines { + lines.push_str(&line); + } + + lines + } + + fn last_char(&self, line_nr: usize) -> UIResult> { + Ok(self.get_line(line_nr)?.chars().last()) + } +} \ No newline at end of file diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index ec06a8052e..e6866985dd 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -1,6 +1,6 @@ use super::keyboard_input; use super::style::CODE_TXT_XY; -use super::util::slice_get; +use crate::ui::util::slice_get; use crate::editor::ed_error::print_ui_err; use crate::editor::resources::strings::NOTHING_OPENED; use crate::editor::slow_pool::SlowPool; @@ -153,21 +153,27 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { let mut code_str = BumpString::from_str_in("", &code_arena); - if let Some(file_path) = file_path_opt { + let file_path = if let Some(file_path) = file_path_opt { match std::fs::read_to_string(file_path) { Ok(file_as_str) => { code_str = BumpString::from_str_in(&file_as_str, &code_arena); + file_path } - Err(e) => print_ui_err(&FileOpenFailed { - path_str: file_path.to_string_lossy().to_string(), - err_msg: e.to_string(), - }), + Err(e) => { + print_ui_err(&FileOpenFailed { + path_str: file_path.to_string_lossy().to_string(), + err_msg: e.to_string(), + }); + Path::new("") + }, } - } + } else { + Path::new("") + }; let ed_model_opt = { - let ed_model_res = ed_model::init_model(&code_str, env, &code_arena, &mut markup_node_pool); + let ed_model_res = ed_model::init_model(&code_str, file_path, env, &code_arena, &mut markup_node_pool); match ed_model_res { Ok(mut ed_model) => { diff --git a/editor/src/editor/markup/caret.rs b/editor/src/editor/markup/caret.rs index 0eff39e55a..b2b512c27a 100644 --- a/editor/src/editor/markup/caret.rs +++ b/editor/src/editor/markup/caret.rs @@ -1,4 +1,3 @@ -use crate::editor::mvc::ed_model::LeafIndex; use crate::editor::{ ed_error::{CaretNotFound, EdResult, NodeWithoutAttributes}, markup::attribute::Caret, @@ -7,90 +6,6 @@ use crate::editor::{ }; use snafu::ensure; -// Returns id of node that has Caret attribute -pub fn set_caret_at_start( - markup_node_id: SlowNodeId, - markup_node_pool: &mut SlowPool, -) -> EdResult { - let markup_node = markup_node_pool.get_mut(markup_node_id); - - match markup_node { - MarkupNode::Nested { - ast_node_id: _, - children_ids: _, - parent_id_opt: _, - } => NodeWithoutAttributes {}.fail(), - MarkupNode::Text { - content: _, - ast_node_id: _, - syn_high_style: _, - attributes, - parent_id_opt: _, - } => { - attributes.add(Caret::new_attr(0)); - Ok(markup_node_id) - } - MarkupNode::Blank { - ast_node_id: _, - attributes, - syn_high_style: _, - parent_id_opt: _, - } => { - attributes.add(Caret::new_attr(0)); - Ok(markup_node_id) - } - } -} - -// Returns nodes containing the carets after the move, as well as its position in a DFS ordered list of leaves. -pub fn move_carets_right_for_node( - node_with_caret_id: SlowNodeId, - caret_id_leaf_index: LeafIndex, - next_leaf_id_opt: Option, - markup_node_pool: &mut SlowPool, -) -> EdResult> { - let carets = get_carets(node_with_caret_id, markup_node_pool)?; - let node_content = markup_node_pool.get(node_with_caret_id).get_content()?; - - ensure!( - !carets.is_empty(), - CaretNotFound { - node_id: node_with_caret_id - } - ); - - let mut new_nodes_w_carets = Vec::new(); - - for caret in carets { - let (new_node, new_leaf_index) = if caret.offset_col + 1 < node_content.len() { - increase_caret_offset(node_with_caret_id, caret.offset_col, markup_node_pool)?; - - (node_with_caret_id, caret_id_leaf_index) - } else if let Some(next_child_id) = next_leaf_id_opt { - delete_caret(node_with_caret_id, caret.offset_col, markup_node_pool)?; - - let next_child = markup_node_pool.get_mut(next_child_id); - let child_attrs = next_child.get_mut_attributes()?; - child_attrs.add_caret(0); - - (next_child_id, caret_id_leaf_index + 1) - } else if caret.offset_col + 1 == node_content.len() { - // For last char in editor. - // In other places we jump to start of next node instead of going to end of - // this node, otherwise it would be like there is a space between every node. - increase_caret_offset(node_with_caret_id, caret.offset_col, markup_node_pool)?; - (node_with_caret_id, caret_id_leaf_index) - } else { - // Caret is at its end, keep it here. - (node_with_caret_id, caret_id_leaf_index) - }; - - new_nodes_w_carets.push((new_node, new_leaf_index)); - } - - Ok(new_nodes_w_carets) -} - fn get_carets(node_with_caret_id: SlowNodeId, markup_node_pool: &SlowPool) -> EdResult> { let curr_node = markup_node_pool.get(node_with_caret_id); let attributes = curr_node.get_attributes()?; diff --git a/editor/src/editor/mod.rs b/editor/src/editor/mod.rs index c0ac3ad56e..543112b5a0 100644 --- a/editor/src/editor/mod.rs +++ b/editor/src/editor/mod.rs @@ -11,3 +11,4 @@ mod style; mod syntax_highlight; mod theme; mod util; +mod code_lines; diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index 2784f8fb1c..5277e120b1 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -1,10 +1,23 @@ +use std::path::Path; +use crate::ui::text::selection::validate_raw_sel; +use crate::ui::text::selection::RawSelection; +use crate::ui::text::selection::Selection; +use crate::ui::ui_error::UIResult; +use crate::ui::text::lines::MoveCaretFun; +use crate::editor::code_lines::CodeLines; +use crate::ui::text::text_pos::TextPos; +use crate::ui::text::{ + lines, + lines::Lines, + lines::SelectableLines, +}; +use crate::ui::text::caret_w_select::CaretWSelect; use crate::editor::slow_pool::{SlowNodeId, SlowPool}; use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::{ ed_error::EdError::ParseError, ed_error::EdResult, markup::attribute::{Attributes, Caret}, - markup::caret::{move_carets_right_for_node, set_caret_at_start}, markup::nodes::{expr2_to_markup, set_parent_for_all, MarkupNode}, }; use crate::graphics::primitives::rect::Rect; @@ -15,25 +28,23 @@ use crate::window::keyboard_input::Modifiers; use bumpalo::collections::String as BumpString; use bumpalo::Bump; use roc_region::all::Region; -use std::collections::HashSet; use winit::event::VirtualKeyCode; - -pub type LeafIndex = usize; +use nonempty::NonEmpty; #[derive(Debug)] pub struct EdModel<'a> { pub module: EdModule<'a>, - pub code_as_str: &'a str, + pub code_lines: CodeLines, pub markup_root_id: SlowNodeId, pub glyph_dim_rect_opt: Option, pub has_focus: bool, - // This HashSet may have less elements than there are carets. There can be multiple carets for a single node. - caret_nodes: HashSet<(SlowNodeId, LeafIndex)>, - dfs_ordered_leaves: Vec, + caret_w_select_vec: NonEmpty<(CaretWSelect, Option)>, + // Option: MarkupNode that corresponds to caret position, Option because this SlowNodeId is only calculated when it needs to be used. } pub fn init_model<'a>( code_str: &'a BumpString, + file_path: &'a Path, env: Env<'a>, code_arena: &'a Bump, markup_node_pool: &mut SlowPool, @@ -65,62 +76,203 @@ pub fn init_model<'a>( temp_markup_root_id }; - let mut dfs_ordered_leaves = Vec::new(); - markup_node_pool.get(markup_root_id).get_dfs_leaves( - markup_root_id, - markup_node_pool, - &mut dfs_ordered_leaves, - ); - - // unwrap because it's not possible to only have a single Nested node without children. - let first_leaf_id = dfs_ordered_leaves.first().unwrap(); - let node_w_caret_id = set_caret_at_start(*first_leaf_id, markup_node_pool)?; - Ok(EdModel { module, - code_as_str: code_str, + code_lines: CodeLines::from_bump_str(code_str), markup_root_id, glyph_dim_rect_opt: None, has_focus: true, - caret_nodes: vec![(node_w_caret_id, 0)].into_iter().collect(), - dfs_ordered_leaves, + caret_w_select_vec: NonEmpty::new((CaretWSelect::default(), None)), }) } impl<'a> EdModel<'a> { pub fn handle_key_down( &mut self, - _modifiers: &Modifiers, + modifiers: &Modifiers, virtual_keycode: VirtualKeyCode, markup_node_pool: &mut SlowPool, ) -> EdResult<()> { match virtual_keycode { VirtualKeyCode::Right => { - let mut new_caret_nodes: Vec<(SlowNodeId, LeafIndex)> = Vec::new(); - - for (caret_node_id_ref, leaf_index) in self.caret_nodes.iter() { - let caret_node_id = *caret_node_id_ref; - let next_leaf_id_opt = self.get_next_leaf(*leaf_index); - - new_caret_nodes.extend(move_carets_right_for_node( - caret_node_id, - *leaf_index, - next_leaf_id_opt, - markup_node_pool, - )?); - } - - self.caret_nodes = new_caret_nodes.into_iter().collect(); + self.move_caret_right(modifiers)?; } - VirtualKeyCode::Left => unimplemented!("TODO"), + VirtualKeyCode::Left => { + self.move_caret_left(modifiers)?; + }, _ => (), }; Ok(()) } - pub fn get_next_leaf(&self, index: usize) -> Option { - self.dfs_ordered_leaves.get(index + 1).copied() + pub fn move_caret(&mut self, move_fun: MoveCaretFun, modifiers: &Modifiers) -> UIResult<()> { + for caret_tup in self.caret_w_select_vec.iter_mut() { + caret_tup.0 = move_fun(&self.code_lines, caret_tup.0, modifiers)?; + caret_tup.1 = None; + } + + Ok(()) + } +} + +impl<'a> SelectableLines for EdModel<'a> { + fn get_caret(self) -> TextPos { + self.caret_w_select_vec.first().0.caret_pos + } + + // keeps active selection + fn set_caret(&mut self, caret_pos: TextPos) { + let caret_tup = self.caret_w_select_vec.first_mut(); + caret_tup.0.caret_pos = caret_pos; + caret_tup.1 = None; + } + + fn move_caret_left(&mut self, modifiers: &Modifiers) -> UIResult<()> { + let move_fun: MoveCaretFun = lines::move_caret_left; + EdModel::move_caret(self, move_fun, modifiers)?; + + Ok(()) + } + + fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> { + let move_fun: MoveCaretFun = lines::move_caret_right; + EdModel::move_caret(self, move_fun, modifiers)?; + + Ok(()) + } + + fn move_caret_up(&mut self, modifiers: &Modifiers) -> UIResult<()> { + let move_fun: MoveCaretFun = lines::move_caret_up; + EdModel::move_caret(self, move_fun, modifiers)?; + + Ok(()) + } + + fn move_caret_down(&mut self, modifiers: &Modifiers) -> UIResult<()> { + let move_fun: MoveCaretFun = lines::move_caret_down; + EdModel::move_caret(self, move_fun, modifiers)?; + + Ok(()) + } + + fn move_caret_home(&mut self, modifiers: &Modifiers) -> UIResult<()> { + let move_fun: MoveCaretFun = lines::move_caret_home; + EdModel::move_caret(self, move_fun, modifiers)?; + + Ok(()) + } + + fn move_caret_end(&mut self, modifiers: &Modifiers) -> UIResult<()> { + let move_fun: MoveCaretFun = lines::move_caret_end; + EdModel::move_caret(self, move_fun, modifiers)?; + + Ok(()) + } + + fn get_selection(&self) -> Option { + self.caret_w_select_vec.first().0.selection_opt + } + + fn is_selection_active(&self) -> bool { + self.get_selection().is_some() + } + + fn get_selected_str(&self) -> UIResult> { + if let Some(selection) = self.get_selection() { + + + let start_line_index = selection.start_pos.line; + let start_col = selection.start_pos.column; + let end_line_index = selection.end_pos.line; + let end_col = selection.end_pos.column; + + if start_line_index == end_line_index { + let line_ref = self.code_lines.get_line(start_line_index)?; + + Ok( + Some( + line_ref[start_col..end_col].to_string() ) + ) + } else { + let full_str = String::new(); + + // TODO + Ok( + Some( + full_str + ) + ) + } + } else { + Ok(None) + } + } + + fn set_raw_sel(&mut self, raw_sel: RawSelection) -> UIResult<()> { + self.caret_w_select_vec.first_mut().0.selection_opt = Some(validate_raw_sel(raw_sel)?); + + Ok(()) + } + + fn set_sel_none(&mut self) { + self.caret_w_select_vec.first_mut().0.selection_opt = None; + } + + fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect) { + self.caret_w_select_vec.first_mut().0 = caret_w_sel; + } + + fn select_all(&mut self) -> UIResult<()> { + if self.code_lines.nr_of_chars() > 0 { + let last_pos = self.last_text_pos()?; + + self.set_raw_sel(RawSelection { + start_pos: TextPos { line: 0, column: 0 }, + end_pos: last_pos, + })?; + + self.set_caret(last_pos); + } + + Ok(()) + } + + fn last_text_pos(&self) -> UIResult { + let nr_of_lines = self.code_lines.lines.len(); + let last_line_index = nr_of_lines - 1; + let last_line = self.code_lines.get_line(last_line_index)?; + + Ok( + TextPos { + line: self.code_lines.lines.len() - 1, + column: last_line.len(), + } + ) + } + + fn handle_key_down( + &mut self, + modifiers: &Modifiers, + virtual_keycode: VirtualKeyCode, + ) -> UIResult<()> { + match virtual_keycode { + Left => self.move_caret_left(modifiers), + Up => self.move_caret_up(modifiers), + Right => self.move_caret_right(modifiers), + Down => self.move_caret_down(modifiers), + + A => { + if modifiers.ctrl { + self.select_all() + } else { + Ok(()) + } + } + Home => self.move_caret_home(modifiers), + End => self.move_caret_end(modifiers), + _ => Ok(()), + } } } diff --git a/editor/src/editor/mvc/ed_view.rs b/editor/src/editor/mvc/ed_view.rs index 5833b639a1..184f3f1e38 100644 --- a/editor/src/editor/mvc/ed_view.rs +++ b/editor/src/editor/mvc/ed_view.rs @@ -27,4 +27,13 @@ pub fn model_to_wgpu<'a>( glyph_dim_rect, markup_node_pool, ) + + //TODO implement method + build_selection_graphics( + ed_model.caret_w_select_vec + size, + txt_coords, + config, + glyph_dim_rect, + ) } diff --git a/editor/src/editor/render_ast.rs b/editor/src/editor/render_ast.rs index a48a2b1b46..981fd00381 100644 --- a/editor/src/editor/render_ast.rs +++ b/editor/src/editor/render_ast.rs @@ -1,3 +1,7 @@ +use crate::editor::slow_pool::SlowNodeId; +use crate::ui::text::text_pos::TextPos; +use nonempty::NonEmpty; +use crate::ui::text::caret_w_select::CaretWSelect; use super::markup::attribute::{Attribute, Attributes}; use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; use crate::editor::slow_pool::SlowPool; @@ -67,40 +71,6 @@ fn markup_to_wgpu<'a>( Ok((wgpu_texts, rects)) } -fn draw_attributes( - attributes: &Attributes, - txt_row_col: &(usize, usize), - code_style: &CodeStyle, -) -> Vec { - let char_width = code_style.glyph_dim_rect.width; - - attributes - .all - .iter() - .map(|attr| match attr { - Attribute::Caret { caret } => { - let caret_col = caret.offset_col as f32; - - let top_left_x = code_style.txt_coords.x - + (txt_row_col.1 as f32) * char_width - + caret_col * char_width; - - let top_left_y = code_style.txt_coords.y - + (txt_row_col.0 as f32) * char_width - + char_width * 0.2; - - make_caret_rect( - top_left_x, - top_left_y, - &code_style.glyph_dim_rect, - &code_style.ed_theme.ui_theme, - ) - } - rest => todo!("implement draw_attributes for {:?}", rest), - }) - .collect() -} - // TODO use text_row fn markup_to_wgpu_helper<'a>( markup_node: &'a MarkupNode, @@ -132,7 +102,7 @@ fn markup_to_wgpu_helper<'a>( content, ast_node_id: _, syn_high_style, - attributes, + attributes: _, parent_id_opt: _, } => { let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, &syn_high_style)?; @@ -141,7 +111,6 @@ fn markup_to_wgpu_helper<'a>( .with_color(colors::to_slice(*highlight_color)) .with_scale(code_style.font_size); - rects.extend(draw_attributes(attributes, txt_row_col, code_style)); txt_row_col.1 += content.len(); wgpu_texts.push(glyph_text); } @@ -171,8 +140,6 @@ fn markup_to_wgpu_helper<'a>( }; rects.push(hole_rect); - rects.extend(draw_attributes(attributes, txt_row_col, code_style)); - txt_row_col.1 += BLANK_PLACEHOLDER.len(); wgpu_texts.push(glyph_text); } diff --git a/editor/src/editor/util.rs b/editor/src/editor/util.rs index 1e7fa31c65..fe4d5c29ba 100644 --- a/editor/src/editor/util.rs +++ b/editor/src/editor/util.rs @@ -3,17 +3,6 @@ use snafu::OptionExt; use std::collections::HashMap; use std::slice::SliceIndex; -// replace vec methods that return Option with ones that return Result and proper Error -pub fn slice_get(index: usize, slice: &[T]) -> EdResult<&>::Output> { - let elt_ref = slice.get(index).context(OutOfBounds { - index, - collection_name: "Slice", - len: slice.len(), - })?; - - Ok(elt_ref) -} - // replace HashMap method that returns Option with one that returns Result and proper Error pub fn map_get<'a, K: ::std::fmt::Debug + std::hash::Hash + std::cmp::Eq, V>( hash_map: &'a HashMap, diff --git a/editor/src/ui/mod.rs b/editor/src/ui/mod.rs index e55ec08073..0f4852cdf8 100644 --- a/editor/src/ui/mod.rs +++ b/editor/src/ui/mod.rs @@ -1,4 +1,4 @@ pub mod text; pub mod theme; pub mod ui_error; -mod util; +pub mod util; diff --git a/editor/src/ui/text/big_selectable_text.rs b/editor/src/ui/text/big_text_area.rs similarity index 89% rename from editor/src/ui/text/big_selectable_text.rs rename to editor/src/ui/text/big_text_area.rs index 5b37e06886..79a3d8dd14 100644 --- a/editor/src/ui/text/big_selectable_text.rs +++ b/editor/src/ui/text/big_text_area.rs @@ -4,8 +4,9 @@ use crate::ui::text::{ caret_w_select::CaretWSelect, + lines, lines::{Lines, MutSelectableLines, SelectableLines}, - selection::{validate_raw_sel, validate_selection, RawSelection, Selection}, + selection::{validate_raw_sel, RawSelection, Selection}, text_pos::TextPos, }; use crate::ui::ui_error::{ @@ -20,7 +21,6 @@ use bumpalo::Bump; use ropey::Rope; use snafu::ensure; use std::{ - cmp::{max, min}, fmt, fs::File, io, @@ -28,14 +28,14 @@ use std::{ }; use winit::event::{VirtualKeyCode, VirtualKeyCode::*}; -pub struct BigSelectableText { +pub struct BigTextArea { pub caret_w_select: CaretWSelect, text_rope: Rope, pub path_str: String, arena: Bump, } -impl BigSelectableText { +impl BigTextArea { fn check_bounds(&self, char_indx: usize) -> UIResult<()> { ensure!( char_indx <= self.text_rope.len_chars(), @@ -71,17 +71,13 @@ impl BigSelectableText { } } -fn validate_sel_opt(start_pos: TextPos, end_pos: TextPos) -> UIResult> { - Ok(Some(validate_selection(start_pos, end_pos)?)) -} - -impl Lines for BigSelectableText { +impl Lines for BigTextArea { fn get_line(&self, line_nr: usize) -> UIResult<&str> { ensure!( line_nr < self.nr_of_lines(), OutOfBounds { index: line_nr, - collection_name: "BigSelectableText", + collection_name: "BigTextArea", len: self.nr_of_lines(), } ); @@ -121,9 +117,13 @@ impl Lines for BigSelectableText { lines } + + fn last_char(&self, line_nr: usize) -> UIResult> { + Ok(self.get_line(line_nr)?.chars().last()) + } } -impl SelectableLines for BigSelectableText { +impl SelectableLines for BigTextArea { fn get_caret(self) -> TextPos { self.caret_w_select.caret_pos } @@ -133,347 +133,39 @@ impl SelectableLines for BigSelectableText { } fn move_caret_left(&mut self, modifiers: &Modifiers) -> UIResult<()> { - let old_selection_opt = self.get_selection(); - let old_caret_pos = self.caret_w_select.caret_pos; - let old_line_nr = old_caret_pos.line; - let old_col_nr = old_caret_pos.column; - - let shift_pressed = modifiers.shift; - - let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed { - match old_selection_opt { - Some(old_selection) => { - (old_selection.start_pos.line, old_selection.start_pos.column) - } - None => unreachable!(), - } - } else if old_col_nr == 0 { - if old_line_nr == 0 { - (0, 0) - } else { - let curr_line_len = self.line_len(old_line_nr - 1)?; - - (old_line_nr - 1, curr_line_len - 1) - } - } else { - (old_line_nr, old_col_nr - 1) - }; - - let new_caret_pos = TextPos { - line: line_nr, - column: col_nr, - }; - - let new_selection_opt = if shift_pressed { - if let Some(old_selection) = old_selection_opt { - if old_caret_pos >= old_selection.end_pos { - if new_caret_pos == old_selection.start_pos { - None - } else { - validate_sel_opt(old_selection.start_pos, new_caret_pos)? - } - } else { - validate_sel_opt( - TextPos { - line: line_nr, - column: col_nr, - }, - old_selection.end_pos, - )? - } - } else if !(old_line_nr == line_nr && old_col_nr == col_nr) { - validate_sel_opt( - TextPos { - line: line_nr, - column: col_nr, - }, - TextPos { - line: old_line_nr, - column: old_col_nr, - }, - )? - } else { - None - } - } else { - None - }; - - self.caret_w_select = CaretWSelect::new(new_caret_pos, new_selection_opt); + self.caret_w_select = lines::move_caret_left(self, self.caret_w_select, modifiers)?; Ok(()) } fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> { - let old_selection_opt = self.get_selection(); - let old_caret_pos = self.caret_w_select.caret_pos; - let old_line_nr = old_caret_pos.line; - let old_col_nr = old_caret_pos.column; - - let shift_pressed = modifiers.shift; - - let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed { - match old_selection_opt { - Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column), - None => unreachable!(), - } - } else { - let curr_line = self.get_line(old_line_nr)?; - - if let Some(last_char) = curr_line.chars().last() { - if is_newline(&last_char) { - if old_col_nr + 1 > curr_line.len() - 1 { - (old_line_nr + 1, 0) - } else { - (old_line_nr, old_col_nr + 1) - } - } else if old_col_nr < curr_line.len() { - (old_line_nr, old_col_nr + 1) - } else { - (old_line_nr, old_col_nr) - } - } else { - (old_line_nr, old_col_nr) - } - }; - - let new_caret_pos = TextPos { - line: line_nr, - column: col_nr, - }; - - let new_selection_opt = if shift_pressed { - if let Some(old_selection) = old_selection_opt { - if old_caret_pos <= old_selection.start_pos { - if new_caret_pos == old_selection.end_pos { - None - } else { - validate_sel_opt(new_caret_pos, old_selection.end_pos)? - } - } else { - validate_sel_opt( - old_selection.start_pos, - TextPos { - line: line_nr, - column: col_nr, - }, - )? - } - } else if !(old_line_nr == line_nr && old_col_nr == col_nr) { - validate_sel_opt( - TextPos { - line: old_line_nr, - column: old_col_nr, - }, - TextPos { - line: line_nr, - column: col_nr, - }, - )? - } else { - None - } - } else { - None - }; - - self.caret_w_select = CaretWSelect::new(new_caret_pos, new_selection_opt); + self.caret_w_select = lines::move_caret_right(self, self.caret_w_select, modifiers)?; Ok(()) } fn move_caret_up(&mut self, modifiers: &Modifiers) -> UIResult<()> { - let old_selection_opt = self.get_selection(); - let old_caret_pos = self.caret_w_select.caret_pos; - let old_line_nr = old_caret_pos.line; - let old_col_nr = old_caret_pos.column; - - let shift_pressed = modifiers.shift; - - let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed { - match old_selection_opt { - Some(old_selection) => { - (old_selection.start_pos.line, old_selection.start_pos.column) - } - None => unreachable!(), - } - } else if old_line_nr == 0 { - (old_line_nr, 0) - } else { - let prev_line_len = self.line_len(old_line_nr - 1)?; - - if prev_line_len <= old_col_nr { - (old_line_nr - 1, prev_line_len - 1) - } else { - (old_line_nr - 1, old_col_nr) - } - }; - - let new_caret_pos = TextPos { - line: line_nr, - column: col_nr, - }; - - let new_selection_opt = if shift_pressed { - if let Some(old_selection) = old_selection_opt { - if old_selection.end_pos <= old_caret_pos { - if new_caret_pos == old_selection.start_pos { - None - } else { - validate_sel_opt( - min(old_selection.start_pos, new_caret_pos), - max(old_selection.start_pos, new_caret_pos), - )? - } - } else { - validate_sel_opt(new_caret_pos, old_selection.end_pos)? - } - } else if !(old_line_nr == line_nr && old_col_nr == col_nr) { - validate_sel_opt( - min(old_caret_pos, new_caret_pos), - max(old_caret_pos, new_caret_pos), - )? - } else { - None - } - } else { - None - }; - - self.caret_w_select = CaretWSelect::new(new_caret_pos, new_selection_opt); + self.caret_w_select = lines::move_caret_up(self, self.caret_w_select, modifiers)?; Ok(()) } fn move_caret_down(&mut self, modifiers: &Modifiers) -> UIResult<()> { - let old_selection_opt = self.get_selection(); - let old_caret_pos = self.caret_w_select.caret_pos; - let old_line_nr = old_caret_pos.line; - let old_col_nr = old_caret_pos.column; - - let shift_pressed = modifiers.shift; - - let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed { - match old_selection_opt { - Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column), - None => unreachable!(), - } - } else if old_line_nr + 1 >= self.nr_of_lines() { - let curr_line_len = self.line_len(old_line_nr)?; - - (old_line_nr, curr_line_len) - } else { - let next_line = self.get_line(old_line_nr + 1)?; - - if next_line.len() <= old_col_nr { - if let Some(last_char) = next_line.chars().last() { - if is_newline(&last_char) { - (old_line_nr + 1, next_line.len() - 1) - } else { - (old_line_nr + 1, next_line.len()) - } - } else { - (old_line_nr + 1, 0) - } - } else { - (old_line_nr + 1, old_col_nr) - } - }; - - let new_caret_pos = TextPos { - line: line_nr, - column: col_nr, - }; - - let new_selection_opt = if shift_pressed { - if let Some(old_selection) = old_selection_opt { - if old_caret_pos <= old_selection.start_pos { - if new_caret_pos == old_selection.end_pos { - None - } else { - validate_sel_opt( - min(old_selection.end_pos, new_caret_pos), - max(old_selection.end_pos, new_caret_pos), - )? - } - } else { - validate_sel_opt(old_selection.start_pos, new_caret_pos)? - } - } else if !(old_line_nr == line_nr && old_col_nr == col_nr) { - validate_sel_opt( - min(old_caret_pos, new_caret_pos), - max(old_caret_pos, new_caret_pos), - )? - } else { - None - } - } else { - None - }; - - self.caret_w_select = CaretWSelect::new(new_caret_pos, new_selection_opt); + self.caret_w_select = lines::move_caret_down(self, self.caret_w_select, modifiers)?; Ok(()) } fn move_caret_home(&mut self, modifiers: &Modifiers) -> UIResult<()> { - let curr_line_nr = self.caret_w_select.caret_pos.line; - let old_col_nr = self.caret_w_select.caret_pos.column; + self.caret_w_select = lines::move_caret_home(self, self.caret_w_select, modifiers)?; - let curr_line_str = self.get_line(curr_line_nr)?; - 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; - } - - let new_col_nr = if first_no_space_char_col == old_col_nr { - 0 - } else { - first_no_space_char_col - }; - - self.caret_w_select.move_caret_w_mods( - TextPos { - line: curr_line_nr, - column: new_col_nr, - }, - modifiers, - ) + Ok(()) } fn move_caret_end(&mut self, modifiers: &Modifiers) -> UIResult<()> { - let curr_line_nr = self.caret_w_select.caret_pos.line; - let curr_line_len = self.line_len(curr_line_nr)?; + self.caret_w_select = lines::move_caret_end(self, self.caret_w_select, modifiers)?; - let new_col = if let Some(last_char) = self.last_char(curr_line_nr)? { - if is_newline(&last_char) { - curr_line_len - 1 - } else { - curr_line_len - } - } else { - 0 - }; - - let new_pos = TextPos { - line: curr_line_nr, - column: new_col, - }; - - self.caret_w_select.move_caret_w_mods(new_pos, modifiers) + Ok(()) } fn get_selection(&self) -> Option { @@ -484,7 +176,7 @@ impl SelectableLines for BigSelectableText { self.get_selection().is_some() } - fn get_selected_str(&self) -> UIResult> { + fn get_selected_str(&self) -> UIResult> { if let Some(val_sel) = self.caret_w_select.selection_opt { let (start_char_indx, end_char_indx) = self.sel_to_tup(val_sel); @@ -493,12 +185,9 @@ impl SelectableLines for BigSelectableText { let rope_slice = self.text_rope.slice(start_char_indx..end_char_indx); if let Some(line_str_ref) = rope_slice.as_str() { - Ok(Some(line_str_ref)) + Ok(Some(line_str_ref.to_string())) } else { - // happens very rarely - let line_str = rope_slice.chunks().collect::(); - let arena_str_ref = self.arena.alloc(line_str); - Ok(Some(arena_str_ref)) + Ok(Some(rope_slice.chunks().collect::())) } } else { Ok(None) @@ -511,13 +200,17 @@ impl SelectableLines for BigSelectableText { Ok(()) } + fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect) { + self.caret_w_select = caret_w_sel; + } + fn set_sel_none(&mut self) { self.caret_w_select.selection_opt = None; } fn select_all(&mut self) -> UIResult<()> { if self.nr_of_chars() > 0 { - let last_pos = self.last_text_pos(); + let last_pos = self.last_text_pos()?; self.set_raw_sel(RawSelection { start_pos: TextPos { line: 0, column: 0 }, @@ -530,12 +223,10 @@ impl SelectableLines for BigSelectableText { Ok(()) } - fn last_text_pos(&self) -> TextPos { - self.char_indx_to_pos(self.nr_of_chars()) - } - - fn last_char(&self, line_nr: usize) -> UIResult> { - Ok(self.get_line(line_nr)?.chars().last()) + fn last_text_pos(&self) -> UIResult { + Ok( + self.char_indx_to_pos(self.nr_of_chars()) + ) } fn handle_key_down( @@ -563,7 +254,7 @@ impl SelectableLines for BigSelectableText { } } -impl MutSelectableLines for BigSelectableText { +impl MutSelectableLines for BigTextArea { fn insert_char(&mut self, new_char: &char) -> UIResult<()> { if self.is_selection_active() { self.del_selection()?; @@ -658,7 +349,7 @@ impl MutSelectableLines for BigSelectableText { } } -impl Default for BigSelectableText { +impl Default for BigTextArea { fn default() -> Self { let caret_w_select = CaretWSelect::default(); let text_rope = Rope::from_str(""); @@ -674,11 +365,11 @@ impl Default for BigSelectableText { } } -pub fn from_path(path: &Path) -> UIResult { +pub fn from_path(path: &Path) -> UIResult { let text_rope = rope_from_path(path)?; let path_str = path_to_string(path); - Ok(BigSelectableText { + Ok(BigTextArea { text_rope, path_str, ..Default::default() @@ -687,10 +378,10 @@ pub fn from_path(path: &Path) -> UIResult { #[allow(dead_code)] // used by tests but will also be used in the future -pub fn from_str(text: &str) -> BigSelectableText { +pub fn from_str(text: &str) -> BigTextArea { let text_rope = Rope::from_str(text); - BigSelectableText { + BigTextArea { text_rope, ..Default::default() } @@ -723,9 +414,9 @@ fn rope_from_path(path: &Path) -> UIResult { } // need to explicitly omit arena -impl fmt::Debug for BigSelectableText { +impl fmt::Debug for BigTextArea { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("BigSelectableText") + f.debug_struct("BigTextArea") .field("caret_w_select", &self.caret_w_select) .field("text_rope", &self.text_rope) .field("path_str", &self.path_str) @@ -736,8 +427,8 @@ impl fmt::Debug for BigSelectableText { #[cfg(test)] pub mod test_big_sel_text { use crate::ui::text::{ - big_selectable_text::from_str, - big_selectable_text::BigSelectableText, + big_text_area::from_str, + big_text_area::BigTextArea, caret_w_select::CaretWSelect, lines::{Lines, MutSelectableLines, SelectableLines}, selection::validate_selection, @@ -843,7 +534,7 @@ pub mod test_big_sel_text { Ok(elt_ref) } - pub fn big_text_from_dsl_str(lines: &[String]) -> BigSelectableText { + pub fn big_text_from_dsl_str(lines: &[String]) -> BigTextArea { from_str( &lines .iter() @@ -853,7 +544,7 @@ pub mod test_big_sel_text { ) } - pub fn all_lines_vec(big_sel_text: &BigSelectableText) -> Vec { + pub fn all_lines_vec(big_sel_text: &BigTextArea) -> Vec { let mut lines: Vec = Vec::new(); for i in 0..big_sel_text.nr_of_lines() { @@ -963,7 +654,7 @@ pub mod test_big_sel_text { } } - pub fn gen_big_text(lines: &[&str]) -> Result { + pub fn gen_big_text(lines: &[&str]) -> Result { let lines_string_slice: Vec = lines.iter().map(|l| l.to_string()).collect(); let mut big_text = big_text_from_dsl_str(&lines_string_slice); let caret_w_select = convert_dsl_to_selection(&lines_string_slice).unwrap(); @@ -1141,7 +832,7 @@ pub mod test_big_sel_text { Ok(()) } - type MoveCaretFun = fn(&mut BigSelectableText, &Modifiers) -> UIResult<()>; + type MoveCaretFun = fn(&mut BigTextArea, &Modifiers) -> UIResult<()>; // Convert nice string representations and compare results fn assert_move( @@ -1720,7 +1411,7 @@ pub mod test_big_sel_text { #[test] fn move_home() -> Result<(), String> { - let move_caret_home = BigSelectableText::move_caret_home; + let move_caret_home = BigTextArea::move_caret_home; assert_move(&["|"], &["|"], &no_mods(), move_caret_home)?; assert_move(&["a|"], &["|a"], &no_mods(), move_caret_home)?; assert_move(&["|a"], &["|a"], &no_mods(), move_caret_home)?; @@ -1834,7 +1525,7 @@ pub mod test_big_sel_text { #[test] fn move_end() -> Result<(), String> { - let move_caret_end = BigSelectableText::move_caret_end; + let move_caret_end = BigTextArea::move_caret_end; assert_move(&["|"], &["|"], &no_mods(), move_caret_end)?; assert_move(&["|a"], &["a|"], &no_mods(), move_caret_end)?; assert_move(&["a|"], &["a|"], &no_mods(), move_caret_end)?; @@ -2467,7 +2158,7 @@ pub mod test_big_sel_text { #[test] fn start_selection_home() -> Result<(), String> { - let move_caret_home = BigSelectableText::move_caret_home; + let move_caret_home = BigTextArea::move_caret_home; assert_move(&["|"], &["|"], &shift_pressed(), move_caret_home)?; assert_move(&["|a"], &["|a"], &shift_pressed(), move_caret_home)?; assert_move(&["a|"], &["|[a]"], &shift_pressed(), move_caret_home)?; @@ -2587,7 +2278,7 @@ pub mod test_big_sel_text { #[test] fn start_selection_end() -> Result<(), String> { - let move_caret_end = BigSelectableText::move_caret_end; + let move_caret_end = BigTextArea::move_caret_end; assert_move(&["|"], &["|"], &shift_pressed(), move_caret_end)?; assert_move(&["|a"], &["[a]|"], &shift_pressed(), move_caret_end)?; assert_move(&["a|"], &["a|"], &shift_pressed(), move_caret_end)?; @@ -3253,7 +2944,7 @@ pub mod test_big_sel_text { #[test] fn end_selection_home() -> Result<(), String> { - let move_caret_home = BigSelectableText::move_caret_home; + let move_caret_home = BigTextArea::move_caret_home; assert_move(&["[a]|"], &["|a"], &no_mods(), move_caret_home)?; assert_move(&["|[a]"], &["|a"], &no_mods(), move_caret_home)?; assert_move(&[" |[a]"], &["| a"], &no_mods(), move_caret_home)?; @@ -3318,7 +3009,7 @@ pub mod test_big_sel_text { #[test] fn end_selection_end() -> Result<(), String> { - let move_caret_end = BigSelectableText::move_caret_end; + let move_caret_end = BigTextArea::move_caret_end; assert_move(&["|[a]"], &["a|"], &no_mods(), move_caret_end)?; assert_move(&["[a]|"], &["a|"], &no_mods(), move_caret_end)?; assert_move(&[" a|[ ]"], &[" a |"], &no_mods(), move_caret_end)?; diff --git a/editor/src/ui/text/caret_w_select.rs b/editor/src/ui/text/caret_w_select.rs index 1a08830507..d9bbf15892 100644 --- a/editor/src/ui/text/caret_w_select.rs +++ b/editor/src/ui/text/caret_w_select.rs @@ -37,7 +37,7 @@ impl CaretWSelect { } } - pub fn move_caret_w_mods(&mut self, new_pos: TextPos, mods: &Modifiers) -> UIResult<()> { + pub fn move_caret_w_mods(&self, new_pos: TextPos, mods: &Modifiers) -> UIResult { let old_caret_pos = self.caret_pos; // one does not simply move the caret @@ -75,10 +75,7 @@ impl CaretWSelect { None }; - self.caret_pos = new_pos; - self.selection_opt = valid_sel_opt; - - Ok(()) + Ok(CaretWSelect::new(new_pos, valid_sel_opt)) } } diff --git a/editor/src/ui/text/lines.rs b/editor/src/ui/text/lines.rs index 25641e7c20..3e3205e374 100644 --- a/editor/src/ui/text/lines.rs +++ b/editor/src/ui/text/lines.rs @@ -1,5 +1,10 @@ // Adapted from https://github.com/cessen/ropey by Nathan Vegdahl, licensed under the MIT license +use std::cmp::max; +use std::cmp::min; +use crate::ui::util::is_newline; +use crate::ui::text::selection::validate_sel_opt; +use crate::ui::text::caret_w_select::CaretWSelect; use crate::ui::text::{ selection::{RawSelection, Selection}, text_pos::TextPos, @@ -19,8 +24,9 @@ pub trait Lines { fn nr_of_chars(&self) -> usize; - // TODO use pool allocation here fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a>; + + fn last_char(&self, line_nr: usize) -> UIResult>; } pub trait SelectableLines { @@ -44,17 +50,17 @@ pub trait SelectableLines { fn is_selection_active(&self) -> bool; - fn get_selected_str(&self) -> UIResult>; + fn get_selected_str(&self) -> UIResult>; fn set_raw_sel(&mut self, raw_sel: RawSelection) -> UIResult<()>; fn set_sel_none(&mut self); + fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect); + fn select_all(&mut self) -> UIResult<()>; - fn last_text_pos(&self) -> TextPos; - - fn last_char(&self, line_nr: usize) -> UIResult>; + fn last_text_pos(&self) -> UIResult; fn handle_key_down( &mut self, @@ -75,3 +81,342 @@ pub trait MutSelectableLines { fn del_selection(&mut self) -> UIResult<()>; } + +pub type MoveCaretFun = + fn(&T, CaretWSelect, &Modifiers) -> UIResult; + +pub fn move_caret_left(lines: &T, caret_w_select: CaretWSelect, modifiers: &Modifiers) -> UIResult { + let old_selection_opt = caret_w_select.selection_opt; + let old_caret_pos = caret_w_select.caret_pos; + let old_line_nr = old_caret_pos.line; + let old_col_nr = old_caret_pos.column; + + let shift_pressed = modifiers.shift; + + let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed { + match old_selection_opt { + Some(old_selection) => { + (old_selection.start_pos.line, old_selection.start_pos.column) + } + None => unreachable!(), + } + } else if old_col_nr == 0 { + if old_line_nr == 0 { + (0, 0) + } else { + let curr_line_len = lines.line_len(old_line_nr - 1)?; + + (old_line_nr - 1, curr_line_len - 1) + } + } else { + (old_line_nr, old_col_nr - 1) + }; + + let new_caret_pos = TextPos { + line: line_nr, + column: col_nr, + }; + + let new_selection_opt = if shift_pressed { + if let Some(old_selection) = old_selection_opt { + if old_caret_pos >= old_selection.end_pos { + if new_caret_pos == old_selection.start_pos { + None + } else { + validate_sel_opt(old_selection.start_pos, new_caret_pos)? + } + } else { + validate_sel_opt( + TextPos { + line: line_nr, + column: col_nr, + }, + old_selection.end_pos, + )? + } + } else if !(old_line_nr == line_nr && old_col_nr == col_nr) { + validate_sel_opt( + TextPos { + line: line_nr, + column: col_nr, + }, + TextPos { + line: old_line_nr, + column: old_col_nr, + }, + )? + } else { + None + } + } else { + None + }; + + Ok(CaretWSelect::new(new_caret_pos, new_selection_opt)) +} + +pub fn move_caret_right(lines: &T, caret_w_select: CaretWSelect, modifiers: &Modifiers) -> UIResult { + let old_selection_opt = caret_w_select.selection_opt; + let old_caret_pos = caret_w_select.caret_pos; + let old_line_nr = old_caret_pos.line; + let old_col_nr = old_caret_pos.column; + + let shift_pressed = modifiers.shift; + + let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed { + match old_selection_opt { + Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column), + None => unreachable!(), + } + } else { + let curr_line = lines.get_line(old_line_nr)?; + + if let Some(last_char) = curr_line.chars().last() { + if is_newline(&last_char) { + if old_col_nr + 1 > curr_line.len() - 1 { + (old_line_nr + 1, 0) + } else { + (old_line_nr, old_col_nr + 1) + } + } else if old_col_nr < curr_line.len() { + (old_line_nr, old_col_nr + 1) + } else { + (old_line_nr, old_col_nr) + } + } else { + (old_line_nr, old_col_nr) + } + }; + + let new_caret_pos = TextPos { + line: line_nr, + column: col_nr, + }; + + let new_selection_opt = if shift_pressed { + if let Some(old_selection) = old_selection_opt { + if old_caret_pos <= old_selection.start_pos { + if new_caret_pos == old_selection.end_pos { + None + } else { + validate_sel_opt(new_caret_pos, old_selection.end_pos)? + } + } else { + validate_sel_opt( + old_selection.start_pos, + TextPos { + line: line_nr, + column: col_nr, + }, + )? + } + } else if !(old_line_nr == line_nr && old_col_nr == col_nr) { + validate_sel_opt( + TextPos { + line: old_line_nr, + column: old_col_nr, + }, + TextPos { + line: line_nr, + column: col_nr, + }, + )? + } else { + None + } + } else { + None + }; + + Ok(CaretWSelect::new(new_caret_pos, new_selection_opt)) +} + +pub fn move_caret_up(lines: &T, caret_w_select: CaretWSelect, modifiers: &Modifiers) -> UIResult { + let old_selection_opt = caret_w_select.selection_opt; + let old_caret_pos = caret_w_select.caret_pos; + let old_line_nr = old_caret_pos.line; + let old_col_nr = old_caret_pos.column; + + let shift_pressed = modifiers.shift; + + let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed { + match old_selection_opt { + Some(old_selection) => { + (old_selection.start_pos.line, old_selection.start_pos.column) + } + None => unreachable!(), + } + } else if old_line_nr == 0 { + (old_line_nr, 0) + } else { + let prev_line_len = lines.line_len(old_line_nr - 1)?; + + if prev_line_len <= old_col_nr { + (old_line_nr - 1, prev_line_len - 1) + } else { + (old_line_nr - 1, old_col_nr) + } + }; + + let new_caret_pos = TextPos { + line: line_nr, + column: col_nr, + }; + + let new_selection_opt = if shift_pressed { + if let Some(old_selection) = old_selection_opt { + if old_selection.end_pos <= old_caret_pos { + if new_caret_pos == old_selection.start_pos { + None + } else { + validate_sel_opt( + min(old_selection.start_pos, new_caret_pos), + max(old_selection.start_pos, new_caret_pos), + )? + } + } else { + validate_sel_opt(new_caret_pos, old_selection.end_pos)? + } + } else if !(old_line_nr == line_nr && old_col_nr == col_nr) { + validate_sel_opt( + min(old_caret_pos, new_caret_pos), + max(old_caret_pos, new_caret_pos), + )? + } else { + None + } + } else { + None + }; + + Ok(CaretWSelect::new(new_caret_pos, new_selection_opt)) +} + +pub fn move_caret_down(lines: &T, caret_w_select: CaretWSelect, modifiers: &Modifiers) -> UIResult { + let old_selection_opt = caret_w_select.selection_opt; + let old_caret_pos = caret_w_select.caret_pos; + let old_line_nr = old_caret_pos.line; + let old_col_nr = old_caret_pos.column; + + let shift_pressed = modifiers.shift; + + let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed { + match old_selection_opt { + Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column), + None => unreachable!(), + } + } else if old_line_nr + 1 >= lines.nr_of_lines() { + let curr_line_len = lines.line_len(old_line_nr)?; + + (old_line_nr, curr_line_len) + } else { + let next_line = lines.get_line(old_line_nr + 1)?; + + if next_line.len() <= old_col_nr { + if let Some(last_char) = next_line.chars().last() { + if is_newline(&last_char) { + (old_line_nr + 1, next_line.len() - 1) + } else { + (old_line_nr + 1, next_line.len()) + } + } else { + (old_line_nr + 1, 0) + } + } else { + (old_line_nr + 1, old_col_nr) + } + }; + + let new_caret_pos = TextPos { + line: line_nr, + column: col_nr, + }; + + let new_selection_opt = if shift_pressed { + if let Some(old_selection) = old_selection_opt { + if old_caret_pos <= old_selection.start_pos { + if new_caret_pos == old_selection.end_pos { + None + } else { + validate_sel_opt( + min(old_selection.end_pos, new_caret_pos), + max(old_selection.end_pos, new_caret_pos), + )? + } + } else { + validate_sel_opt(old_selection.start_pos, new_caret_pos)? + } + } else if !(old_line_nr == line_nr && old_col_nr == col_nr) { + validate_sel_opt( + min(old_caret_pos, new_caret_pos), + max(old_caret_pos, new_caret_pos), + )? + } else { + None + } + } else { + None + }; + + Ok(CaretWSelect::new(new_caret_pos, new_selection_opt)) +} + +pub fn move_caret_home(lines: &T, caret_w_select: CaretWSelect, modifiers: &Modifiers) -> UIResult { + let curr_line_nr = caret_w_select.caret_pos.line; + let old_col_nr = caret_w_select.caret_pos.column; + + let curr_line_str = lines.get_line(curr_line_nr)?; + 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; + } + + let new_col_nr = if first_no_space_char_col == old_col_nr { + 0 + } else { + first_no_space_char_col + }; + + caret_w_select.move_caret_w_mods( + TextPos { + line: curr_line_nr, + column: new_col_nr, + }, + modifiers, + ) +} + +pub fn move_caret_end(lines: &T, caret_w_select: CaretWSelect, modifiers: &Modifiers) -> UIResult { + let curr_line_nr = caret_w_select.caret_pos.line; + let curr_line_len = lines.line_len(curr_line_nr)?; + + let new_col = if let Some(last_char) = lines.last_char(curr_line_nr)? { + if is_newline(&last_char) { + curr_line_len - 1 + } else { + curr_line_len + } + } else { + 0 + }; + + let new_pos = TextPos { + line: curr_line_nr, + column: new_col, + }; + + caret_w_select.move_caret_w_mods(new_pos, modifiers) +} \ No newline at end of file diff --git a/editor/src/ui/text/mod.rs b/editor/src/ui/text/mod.rs index 5dad412667..39616a47cd 100644 --- a/editor/src/ui/text/mod.rs +++ b/editor/src/ui/text/mod.rs @@ -1,4 +1,4 @@ -pub mod big_selectable_text; +pub mod big_text_area; pub mod caret_w_select; pub mod lines; pub mod selection; diff --git a/editor/src/ui/text/selection.rs b/editor/src/ui/text/selection.rs index 20a95f422e..80f7da0920 100644 --- a/editor/src/ui/text/selection.rs +++ b/editor/src/ui/text/selection.rs @@ -34,6 +34,11 @@ pub fn validate_raw_sel(raw_sel: RawSelection) -> UIResult { validate_selection(raw_sel.start_pos, raw_sel.end_pos) } + +pub fn validate_sel_opt(start_pos: TextPos, end_pos: TextPos) -> UIResult> { + Ok(Some(validate_selection(start_pos, end_pos)?)) +} + pub fn validate_selection(start_pos: TextPos, end_pos: TextPos) -> UIResult { ensure!( start_pos.line <= end_pos.line, diff --git a/editor/src/ui/util.rs b/editor/src/ui/util.rs index 8fba081a08..f62f62ee2d 100644 --- a/editor/src/ui/util.rs +++ b/editor/src/ui/util.rs @@ -1,5 +1,21 @@ + +use super::ui_error::{UIResult, OutOfBounds}; +use snafu::OptionExt; +use std::slice::SliceIndex; + pub fn is_newline(char_ref: &char) -> bool { let newline_codes = vec!['\u{d}', '\n']; newline_codes.contains(char_ref) } + +// replace vec method that return Option with one that return Result and proper Error +pub fn slice_get(index: usize, slice: &[T]) -> UIResult<&>::Output> { + let elt_ref = slice.get(index).context(OutOfBounds { + index, + collection_name: "Slice", + len: slice.len(), + })?; + + Ok(elt_ref) +}