diff --git a/editor/src/keyboard_input.rs b/editor/src/keyboard_input.rs index 0a976a1a0f..457f27d50c 100644 --- a/editor/src/keyboard_input.rs +++ b/editor/src/keyboard_input.rs @@ -38,6 +38,9 @@ pub fn handle_keydown( handle_cut(app_model)? } } + + A => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model), + _ => (), } diff --git a/editor/src/mvc/ed_update.rs b/editor/src/mvc/ed_update.rs index 83cb701aec..27247c11fe 100644 --- a/editor/src/mvc/ed_update.rs +++ b/editor/src/mvc/ed_update.rs @@ -318,6 +318,20 @@ fn del_selection(selection: RawSelection, ed_model: &mut EdModel) -> EdResult<() Ok(()) } +// TODO move this to impl EdModel +pub fn handle_select_all(ed_model: &mut EdModel) { + if ed_model.text_buf.nr_of_chars() > 0 { + let last_pos = ed_model.text_buf.last_position(); + + ed_model.selection_opt = Some(RawSelection { + start_pos: Position { line: 0, column: 0 }, + end_pos: last_pos, + }); + + ed_model.caret_pos = last_pos; + } +} + pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult<()> { let old_caret_pos = ed_model.caret_pos; @@ -351,7 +365,8 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult ed_model.selection_opt = None; } - '\u{3}' // Ctrl + C + '\u{1}' // Ctrl + A + | '\u{3}' // Ctrl + C | '\u{16}' // Ctrl + V | '\u{18}' // Ctrl + X | '\u{e000}'..='\u{f8ff}' // http://www.unicode.org/faq/private_use.html @@ -397,6 +412,12 @@ pub fn handle_key_down( Up => handle_arrow(move_caret_up, modifiers, ed_model), Right => handle_arrow(move_caret_right, modifiers, ed_model), Down => handle_arrow(move_caret_down, modifiers, ed_model), + + A => { + if modifiers.ctrl() { + handle_select_all(ed_model) + } + } _ => {} } } @@ -405,7 +426,7 @@ pub fn handle_key_down( pub mod test_ed_update { use crate::mvc::app_update::test_app_update::mock_app_model; use crate::mvc::ed_model::{Position, RawSelection}; - use crate::mvc::ed_update::handle_new_char; + use crate::mvc::ed_update::{handle_new_char, handle_select_all}; use crate::selection::test_selection::{ all_lines_vec, convert_dsl_to_selection, convert_selection_to_dsl, text_buffer_from_dsl_str, }; @@ -554,4 +575,51 @@ pub mod test_ed_update { Ok(()) } + + fn assert_select_all( + pre_lines_str: &[&str], + expected_post_lines_str: &[&str], + ) -> Result<(), String> { + let (caret_pos, selection_opt, pre_text_buf) = gen_caret_text_buf(pre_lines_str)?; + + let app_model = mock_app_model(pre_text_buf, caret_pos, selection_opt, None); + let mut ed_model = app_model.ed_model_opt.unwrap(); + + handle_select_all(&mut ed_model); + + let mut text_buf_lines = all_lines_vec(&ed_model.text_buf); + let post_lines_str = convert_selection_to_dsl( + ed_model.selection_opt, + ed_model.caret_pos, + &mut text_buf_lines, + )?; + + assert_eq!(post_lines_str, expected_post_lines_str); + + Ok(()) + } + + #[test] + fn select_all() -> Result<(), String> { + assert_select_all(&["|"], &["|"])?; + assert_select_all(&["|a"], &["[a]|"])?; + assert_select_all(&["a|"], &["[a]|"])?; + assert_select_all(&["abc d|ef ghi"], &["[abc def ghi]|"])?; + assert_select_all(&["[a]|"], &["[a]|"])?; + assert_select_all(&["|[a]"], &["[a]|"])?; + assert_select_all(&["|[abc def ghi]"], &["[abc def ghi]|"])?; + assert_select_all(&["a\n", "[b\n", "]|"], &["[a\n", "b\n", "]|"])?; + assert_select_all(&["a\n", "[b]|\n", ""], &["[a\n", "b\n", "]|"])?; + assert_select_all(&["a\n", "|[b\n", "]"], &["[a\n", "b\n", "]|"])?; + assert_select_all( + &["abc\n", "def\n", "gh|i\n", "jkl"], + &["[abc\n", "def\n", "ghi\n", "jkl]|"], + )?; + assert_select_all( + &["|[abc\n", "def\n", "ghi\n", "jkl]"], + &["[abc\n", "def\n", "ghi\n", "jkl]|"], + )?; + + Ok(()) + } } diff --git a/editor/src/text_buffer.rs b/editor/src/text_buffer.rs index f43d0260ff..a0a512883c 100644 --- a/editor/src/text_buffer.rs +++ b/editor/src/text_buffer.rs @@ -116,6 +116,10 @@ impl TextBuffer { self.text_rope.len_lines() } + pub fn nr_of_chars(&self) -> usize { + self.text_rope.len_chars() + } + // expensive function, don't use it if it can be done with a specialized, more efficient function // TODO use pool allocation here pub fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a> { @@ -132,6 +136,16 @@ impl TextBuffer { self.text_rope.line_to_char(pos.line) + pos.column } + fn char_indx_to_pos(&self, char_indx: usize) -> Position { + let line = self.text_rope.char_to_line(char_indx); + + let char_idx_line_start = self.pos_to_char_indx(Position { line, column: 0 }); + + let column = char_indx - char_idx_line_start; + + Position { line, column } + } + fn sel_to_tup(&self, raw_sel: RawSelection) -> EdResult<(usize, usize)> { let valid_sel = validate_selection(raw_sel)?; let start_char_indx = self.pos_to_char_indx(valid_sel.selection.start_pos); @@ -139,6 +153,10 @@ impl TextBuffer { Ok((start_char_indx, end_char_indx)) } + + pub fn last_position(&self) -> Position { + self.char_indx_to_pos(self.nr_of_chars()) + } } pub fn from_path(path: &Path) -> EdResult {