From cbd9e186b5924dba83f1c28b01ced4ba590fadc1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 27 Dec 2021 15:33:57 -0800 Subject: [PATCH] Store selections with a right start bias so that autoindent moves them Previously, cursors at column 0 had to be explicitly moved when those lines were autoindented. This behavior was lost when we moved selections from the buffer to the editor. Now, with the right bias, we get this behavior automatically. Co-Authored-By: Nathan Sobo --- crates/editor/src/editor.rs | 168 ++++++++++++++++++++++++----------- crates/language/src/tests.rs | 57 ------------ 2 files changed, 118 insertions(+), 107 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5763d97c47..4fb1f1c741 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -487,25 +487,14 @@ impl Editor { cx.observe(&display_map, Self::on_display_map_changed) .detach(); - let mut next_selection_id = 0; - let selections = Arc::from( - &[Selection { - id: post_inc(&mut next_selection_id), - start: Anchor::min(), - end: Anchor::min(), - reversed: false, - goal: SelectionGoal::None, - }][..], - ); - - Self { + let mut this = Self { handle: cx.weak_handle(), buffer, display_map, - selections, + selections: Arc::from([]), pending_selection: None, columnar_selection_tail: None, - next_selection_id, + next_selection_id: 0, add_selections_state: None, select_next_state: None, selection_history: Default::default(), @@ -523,7 +512,16 @@ impl Editor { mode: EditorMode::Full, placeholder_text: None, highlighted_row: None, - } + }; + let selection = Selection { + id: post_inc(&mut this.next_selection_id), + start: 0, + end: 0, + reversed: false, + goal: SelectionGoal::None, + }; + this.update_selections(vec![selection], None, cx); + this } pub fn open_new( @@ -1263,36 +1261,33 @@ impl Editor { pub fn insert(&mut self, text: &str, cx: &mut ViewContext) { self.start_transaction(cx); let old_selections = self.local_selections::(cx); - let new_selections = self.buffer.update(cx, |buffer, cx| { - let snapshot = buffer.read(cx); - let new_selections = old_selections - .iter() - .map(|selection| Selection { - id: selection.id, - start: snapshot.anchor_after(selection.start), - end: snapshot.anchor_after(selection.end), - reversed: false, - goal: SelectionGoal::None, - }) - .collect::>(); - - drop(snapshot); + self.buffer.update(cx, |buffer, cx| { let edit_ranges = old_selections.iter().map(|s| s.start..s.end); buffer.edit_with_autoindent(edit_ranges, text, cx); - - let snapshot = buffer.read(cx); - self.resolve_selections::(new_selections.iter(), &snapshot) - .collect() }); - self.update_selections(new_selections, Some(Autoscroll::Fit), cx); + let selections = self.local_selections::(cx); + self.update_selections(selections, Some(Autoscroll::Fit), cx); self.end_transaction(cx); } fn autoclose_pairs(&mut self, cx: &mut ViewContext) { let selections = self.local_selections::(cx); - let new_autoclose_pair = self.buffer.update(cx, |buffer, cx| { - let snapshot = buffer.snapshot(cx); + let mut bracket_pair_state = None; + let mut new_selections = None; + self.buffer.update(cx, |buffer, cx| { + let mut snapshot = buffer.snapshot(cx); + let left_biased_selections = selections + .iter() + .map(|selection| Selection { + id: selection.id, + start: snapshot.anchor_before(selection.start), + end: snapshot.anchor_before(selection.end), + reversed: selection.reversed, + goal: selection.goal, + }) + .collect::>(); + let autoclose_pair = snapshot.language().and_then(|language| { let first_selection_start = selections.first().unwrap().start; let pair = language.brackets().iter().find(|pair| { @@ -1317,7 +1312,7 @@ impl Editor { }) }); - autoclose_pair.and_then(|pair| { + if let Some(pair) = autoclose_pair { let selection_ranges = selections .iter() .map(|selection| { @@ -1327,11 +1322,16 @@ impl Editor { .collect::>(); buffer.edit(selection_ranges, &pair.end, cx); - let snapshot = buffer.snapshot(cx); + snapshot = buffer.snapshot(cx); + + new_selections = Some( + self.resolve_selections::(left_biased_selections.iter(), &snapshot) + .collect::>(), + ); if pair.end.len() == 1 { let mut delta = 0; - Some(BracketPairState { + bracket_pair_state = Some(BracketPairState { ranges: selections .iter() .map(move |selection| { @@ -1341,13 +1341,17 @@ impl Editor { }) .collect(), pair, - }) - } else { - None + }); } - }) + } }); - self.autoclose_stack.extend(new_autoclose_pair); + + if let Some(new_selections) = new_selections { + self.update_selections(new_selections, None, cx); + } + if let Some(bracket_pair_state) = bracket_pair_state { + self.autoclose_stack.push(bracket_pair_state); + } } fn skip_autoclose_end(&mut self, text: &str, cx: &mut ViewContext) -> bool { @@ -3265,12 +3269,19 @@ impl Editor { self.pause_cursor_blinking(cx); self.set_selections( - Arc::from_iter(selections.into_iter().map(|selection| Selection { - id: selection.id, - start: buffer.anchor_before(selection.start), - end: buffer.anchor_before(selection.end), - reversed: selection.reversed, - goal: selection.goal, + Arc::from_iter(selections.into_iter().map(|selection| { + let end_bias = if selection.end > selection.start { + Bias::Left + } else { + Bias::Right + }; + Selection { + id: selection.id, + start: buffer.anchor_after(selection.start), + end: buffer.anchor_at(selection.end, end_bias), + reversed: selection.reversed, + goal: selection.goal, + } })), cx, ); @@ -5730,6 +5741,63 @@ mod tests { ); } + #[gpui::test] + async fn test_autoindent_selections(mut cx: gpui::TestAppContext) { + let settings = cx.read(EditorSettings::test); + let language = Some(Arc::new( + Language::new( + LanguageConfig { + brackets: vec![ + BracketPair { + start: "{".to_string(), + end: "}".to_string(), + close: false, + newline: true, + }, + BracketPair { + start: "(".to_string(), + end: ")".to_string(), + close: false, + newline: true, + }, + ], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ) + .with_indents_query( + r#" + (_ "(" ")" @end) @indent + (_ "{" "}" @end) @indent + "#, + ) + .unwrap(), + )); + + let text = "fn a() {}"; + + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx)); + let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); + let (_, editor) = cx.add_window(|cx| build_editor(buffer, settings, cx)); + editor + .condition(&cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx)) + .await; + + editor.update(&mut cx, |editor, cx| { + editor.select_ranges([5..5, 8..8, 9..9], None, cx); + editor.newline(&Newline, cx); + assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n"); + assert_eq!( + editor.selected_ranges(cx), + &[ + Point::new(1, 4)..Point::new(1, 4), + Point::new(3, 4)..Point::new(3, 4), + Point::new(5, 0)..Point::new(5, 0) + ] + ); + }); + } + #[gpui::test] async fn test_autoclose_pairs(mut cx: gpui::TestAppContext) { let settings = cx.read(EditorSettings::test); diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 73ac9266da..4db5e788f8 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -330,63 +330,6 @@ fn test_edit_with_autoindent(cx: &mut MutableAppContext) { }); } -// We need another approach to managing selections with auto-indent - -// #[gpui::test] -// fn test_autoindent_moves_selections(cx: &mut MutableAppContext) { -// cx.add_model(|cx| { -// let text = "fn a() {}"; - -// let mut buffer = -// Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx); - -// let selection_set_id = buffer.add_selection_set::(&[], cx); -// buffer.start_transaction(); -// buffer.edit_with_autoindent([5..5, 9..9], "\n\n", cx); -// buffer -// .update_selection_set( -// selection_set_id, -// &[ -// Selection { -// id: 0, -// start: Point::new(1, 0), -// end: Point::new(1, 0), -// reversed: false, -// goal: SelectionGoal::None, -// }, -// Selection { -// id: 1, -// start: Point::new(4, 0), -// end: Point::new(4, 0), -// reversed: false, -// goal: SelectionGoal::None, -// }, -// ], -// cx, -// ) -// .unwrap(); -// assert_eq!(buffer.text(), "fn a(\n\n) {}\n\n"); - -// // TODO! Come up with a different approach to moving selections now that we don't manage selection sets in the buffer - -// // Ending the transaction runs the auto-indent. The selection -// // at the start of the auto-indented row is pushed to the right. -// buffer.end_transaction(cx); -// assert_eq!(buffer.text(), "fn a(\n \n) {}\n\n"); -// let selection_ranges = buffer -// .selection_set(selection_set_id) -// .unwrap() -// .selections::(&buffer) -// .map(|selection| selection.start.to_point(&buffer)..selection.end.to_point(&buffer)) -// .collect::>(); - -// assert_eq!(selection_ranges[0], empty(Point::new(1, 4))); -// assert_eq!(selection_ranges[1], empty(Point::new(4, 0))); - -// buffer -// }); -// } - #[gpui::test] fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) { cx.add_model(|cx| {