Detailed changes
@@ -6471,42 +6471,79 @@ impl Editor {
}
self.select_next_state = Some(select_next_state);
- } else if selections.len() == 1 {
- let selection = selections.last_mut().unwrap();
- if selection.start == selection.end {
- let word_range = movement::surrounding_word(
- &display_map,
- selection.start.to_display_point(&display_map),
- );
- selection.start = word_range.start.to_offset(&display_map, Bias::Left);
- selection.end = word_range.end.to_offset(&display_map, Bias::Left);
- selection.goal = SelectionGoal::None;
- selection.reversed = false;
-
- let query = buffer
- .text_for_range(selection.start..selection.end)
- .collect::<String>();
-
- let is_empty = query.is_empty();
- let select_state = SelectNextState {
- query: AhoCorasick::new(&[query])?,
- wordwise: true,
- done: is_empty,
- };
- select_next_match_ranges(
- self,
- selection.start..selection.end,
- replace_newest,
- autoscroll,
- cx,
- );
- self.select_next_state = Some(select_state);
- } else {
- let query = buffer
- .text_for_range(selection.start..selection.end)
- .collect::<String>();
+ } else {
+ let mut only_carets = true;
+ let mut same_text_selected = true;
+ let mut selected_text = None;
+
+ let mut selections_iter = selections.iter().peekable();
+ while let Some(selection) = selections_iter.next() {
+ if selection.start != selection.end {
+ only_carets = false;
+ }
+
+ if same_text_selected {
+ if selected_text.is_none() {
+ selected_text =
+ Some(buffer.text_for_range(selection.range()).collect::<String>());
+ }
+
+ if let Some(next_selection) = selections_iter.peek() {
+ if next_selection.range().len() == selection.range().len() {
+ let next_selected_text = buffer
+ .text_for_range(next_selection.range())
+ .collect::<String>();
+ if Some(next_selected_text) != selected_text {
+ same_text_selected = false;
+ selected_text = None;
+ }
+ } else {
+ same_text_selected = false;
+ selected_text = None;
+ }
+ }
+ }
+ }
+
+ if only_carets {
+ for selection in &mut selections {
+ let word_range = movement::surrounding_word(
+ &display_map,
+ selection.start.to_display_point(&display_map),
+ );
+ selection.start = word_range.start.to_offset(&display_map, Bias::Left);
+ selection.end = word_range.end.to_offset(&display_map, Bias::Left);
+ selection.goal = SelectionGoal::None;
+ selection.reversed = false;
+ select_next_match_ranges(
+ self,
+ selection.start..selection.end,
+ replace_newest,
+ autoscroll,
+ cx,
+ );
+ }
+
+ if selections.len() == 1 {
+ let selection = selections
+ .last()
+ .expect("ensured that there's only one selection");
+ let query = buffer
+ .text_for_range(selection.start..selection.end)
+ .collect::<String>();
+ let is_empty = query.is_empty();
+ let select_state = SelectNextState {
+ query: AhoCorasick::new(&[query])?,
+ wordwise: true,
+ done: is_empty,
+ };
+ self.select_next_state = Some(select_state);
+ } else {
+ self.select_next_state = None;
+ }
+ } else if let Some(selected_text) = selected_text {
self.select_next_state = Some(SelectNextState {
- query: AhoCorasick::new(&[query])?,
+ query: AhoCorasick::new(&[selected_text])?,
wordwise: false,
done: false,
});
@@ -6610,39 +6647,81 @@ impl Editor {
}
self.select_prev_state = Some(select_prev_state);
- } else if selections.len() == 1 {
- let selection = selections.last_mut().unwrap();
- if selection.start == selection.end {
- let word_range = movement::surrounding_word(
- &display_map,
- selection.start.to_display_point(&display_map),
+ } else {
+ let mut only_carets = true;
+ let mut same_text_selected = true;
+ let mut selected_text = None;
+
+ let mut selections_iter = selections.iter().peekable();
+ while let Some(selection) = selections_iter.next() {
+ if selection.start != selection.end {
+ only_carets = false;
+ }
+
+ if same_text_selected {
+ if selected_text.is_none() {
+ selected_text =
+ Some(buffer.text_for_range(selection.range()).collect::<String>());
+ }
+
+ if let Some(next_selection) = selections_iter.peek() {
+ if next_selection.range().len() == selection.range().len() {
+ let next_selected_text = buffer
+ .text_for_range(next_selection.range())
+ .collect::<String>();
+ if Some(next_selected_text) != selected_text {
+ same_text_selected = false;
+ selected_text = None;
+ }
+ } else {
+ same_text_selected = false;
+ selected_text = None;
+ }
+ }
+ }
+ }
+
+ if only_carets {
+ for selection in &mut selections {
+ let word_range = movement::surrounding_word(
+ &display_map,
+ selection.start.to_display_point(&display_map),
+ );
+ selection.start = word_range.start.to_offset(&display_map, Bias::Left);
+ selection.end = word_range.end.to_offset(&display_map, Bias::Left);
+ selection.goal = SelectionGoal::None;
+ selection.reversed = false;
+ }
+ if selections.len() == 1 {
+ let selection = selections
+ .last()
+ .expect("ensured that there's only one selection");
+ let query = buffer
+ .text_for_range(selection.start..selection.end)
+ .collect::<String>();
+ let is_empty = query.is_empty();
+ let select_state = SelectNextState {
+ query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
+ wordwise: true,
+ done: is_empty,
+ };
+ self.select_prev_state = Some(select_state);
+ } else {
+ self.select_prev_state = None;
+ }
+
+ self.unfold_ranges(
+ selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
+ false,
+ true,
+ cx,
);
- selection.start = word_range.start.to_offset(&display_map, Bias::Left);
- selection.end = word_range.end.to_offset(&display_map, Bias::Left);
- selection.goal = SelectionGoal::None;
- selection.reversed = false;
-
- let query = buffer
- .text_for_range(selection.start..selection.end)
- .collect::<String>();
- let query = query.chars().rev().collect::<String>();
- let select_state = SelectNextState {
- query: AhoCorasick::new(&[query])?,
- wordwise: true,
- done: false,
- };
- self.unfold_ranges([selection.start..selection.end], false, true, cx);
self.change_selections(Some(Autoscroll::newest()), cx, |s| {
s.select(selections);
});
- self.select_prev_state = Some(select_state);
- } else {
- let query = buffer
- .text_for_range(selection.start..selection.end)
- .collect::<String>();
- let query = query.chars().rev().collect::<String>();
+ } else if let Some(selected_text) = selected_text {
self.select_prev_state = Some(SelectNextState {
- query: AhoCorasick::new(&[query])?,
+ query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
wordwise: false,
done: false,
});
@@ -3821,62 +3821,137 @@ async fn test_select_next(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-async fn test_select_previous(cx: &mut gpui::TestAppContext) {
+async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
- {
- // `Select previous` without a selection (selects wordwise)
- let mut cx = EditorTestContext::new(cx).await;
- cx.set_state("abc\nˇabc abc\ndefabc\nabc");
- cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
- .unwrap();
- cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
+ let mut cx = EditorTestContext::new(cx).await;
+ cx.set_state(
+ r#"let foo = 2;
+lˇet foo = 2;
+let fooˇ = 2;
+let foo = 2;
+let foo = ˇ2;"#,
+ );
- cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
- .unwrap();
- cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
+ cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
+ .unwrap();
+ cx.assert_editor_state(
+ r#"let foo = 2;
+«letˇ» foo = 2;
+let «fooˇ» = 2;
+let foo = 2;
+let foo = «2ˇ»;"#,
+ );
- cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
- cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
+ // noop for multiple selections with different contents
+ cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
+ .unwrap();
+ cx.assert_editor_state(
+ r#"let foo = 2;
+«letˇ» foo = 2;
+let «fooˇ» = 2;
+let foo = 2;
+let foo = «2ˇ»;"#,
+ );
+}
- cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
- cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
+#[gpui::test]
+async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |_| {});
- cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
- .unwrap();
- cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
+ let mut cx = EditorTestContext::new(cx).await;
+ cx.set_state("abc\nˇabc abc\ndefabc\nabc");
- cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
- .unwrap();
- cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
- }
- {
- // `Select previous` with a selection
- let mut cx = EditorTestContext::new(cx).await;
- cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
+ cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+ .unwrap();
+ cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
- cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
- .unwrap();
- cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
+ cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+ .unwrap();
+ cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
- cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
- .unwrap();
- cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
+ cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
+ cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
- cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
- cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
+ cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
+ cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
- cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
- cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
+ cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+ .unwrap();
+ cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
- cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
- .unwrap();
- cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
+ cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+ .unwrap();
+ cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
- cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
- .unwrap();
- cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
- }
+ cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+ .unwrap();
+ cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
+}
+
+#[gpui::test]
+async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+ cx.set_state(
+ r#"let foo = 2;
+lˇet foo = 2;
+let fooˇ = 2;
+let foo = 2;
+let foo = ˇ2;"#,
+ );
+
+ cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+ .unwrap();
+ cx.assert_editor_state(
+ r#"let foo = 2;
+«letˇ» foo = 2;
+let «fooˇ» = 2;
+let foo = 2;
+let foo = «2ˇ»;"#,
+ );
+
+ // noop for multiple selections with different contents
+ cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+ .unwrap();
+ cx.assert_editor_state(
+ r#"let foo = 2;
+«letˇ» foo = 2;
+let «fooˇ» = 2;
+let foo = 2;
+let foo = «2ˇ»;"#,
+ );
+}
+
+#[gpui::test]
+async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+ cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
+
+ cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+ .unwrap();
+ cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
+
+ cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+ .unwrap();
+ cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
+
+ cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
+ cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
+
+ cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
+ cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
+
+ cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+ .unwrap();
+ cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
+
+ cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+ .unwrap();
+ cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
}
#[gpui::test]
@@ -5,7 +5,7 @@ use language::Point;
use crate::{display_map::ToDisplayPoint, Editor, EditorMode, LineWithInvisibles};
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Autoscroll {
Next,
Strategy(AutoscrollStrategy),
@@ -25,7 +25,7 @@ impl Autoscroll {
}
}
-#[derive(PartialEq, Eq, Default)]
+#[derive(PartialEq, Eq, Default, Clone, Copy)]
pub enum AutoscrollStrategy {
Fit,
Newest,