@@ -16119,11 +16119,8 @@ impl Editor {
};
let mut new_selections = Vec::new();
-
- let reversed = self
- .selections
- .oldest::<MultiBufferOffset>(&display_map)
- .reversed;
+ let initial_selection = self.selections.oldest::<MultiBufferOffset>(&display_map);
+ let reversed = initial_selection.reversed;
let buffer = display_map.buffer_snapshot();
let query_matches = select_next_state
.query
@@ -16137,21 +16134,33 @@ impl Editor {
MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
};
- if !select_next_state.wordwise
- || (!buffer.is_inside_word(offset_range.start, None)
- && !buffer.is_inside_word(offset_range.end, None))
- {
- new_selections.push(offset_range.start..offset_range.end);
- }
- }
+ let is_partial_word_match = select_next_state.wordwise
+ && (buffer.is_inside_word(offset_range.start, None)
+ || buffer.is_inside_word(offset_range.end, None));
- select_next_state.done = true;
+ let is_initial_selection = MultiBufferOffset(query_match.start())
+ == initial_selection.start
+ && MultiBufferOffset(query_match.end()) == initial_selection.end;
- if new_selections.is_empty() {
- log::error!("bug: new_selections is empty in select_all_matches");
- return Ok(());
+ if !is_partial_word_match && !is_initial_selection {
+ new_selections.push(offset_range);
+ }
}
+ // Ensure that the initial range is the last selection, as
+ // `MutableSelectionsCollection::select_ranges` makes the last selection
+ // the newest selection, which the editor then relies on as the primary
+ // cursor for scroll targeting. Without this, the last match would then
+ // be automatically focused when the user started editing the selected
+ // matches.
+ let initial_directed_range = if reversed {
+ initial_selection.end..initial_selection.start
+ } else {
+ initial_selection.start..initial_selection.end
+ };
+ new_selections.push(initial_directed_range);
+
+ select_next_state.done = true;
self.unfold_ranges(&new_selections, false, false, cx);
self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
selections.select_ranges(new_selections)
@@ -9968,7 +9968,6 @@ async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
-
let large_body_1 = "\nd".repeat(200);
let large_body_2 = "\ne".repeat(200);
@@ -9981,17 +9980,62 @@ async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
scroll_position
});
- cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
+ cx.update_editor(|editor, window, cx| editor.select_all_matches(&SelectAllMatches, window, cx))
.unwrap();
cx.assert_editor_state(&format!(
"«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
));
- let scroll_position_after_selection =
- cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
- assert_eq!(
- initial_scroll_position, scroll_position_after_selection,
- "Scroll position should not change after selecting all matches"
- );
+ cx.update_editor(|editor, _, cx| {
+ assert_eq!(
+ editor.scroll_position(cx),
+ initial_scroll_position,
+ "Scroll position should not change after selecting all matches"
+ )
+ });
+
+ // Simulate typing while the selections are active, as that is where the
+ // editor would attempt to actually scroll to the newest selection, which
+ // should have been set as the original selection to avoid scrolling to the
+ // last match.
+ cx.simulate_keystroke("x");
+ cx.update_editor(|editor, _, cx| {
+ assert_eq!(
+ editor.scroll_position(cx),
+ initial_scroll_position,
+ "Scroll position should not change after editing all matches"
+ )
+ });
+
+ cx.set_state(&format!(
+ "abc\nabc{large_body_1} «aˇ»bc{large_body_2}\nefabc\nabc"
+ ));
+ let initial_scroll_position = cx.update_editor(|editor, _, cx| {
+ let scroll_position = editor.scroll_position(cx);
+ assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
+ scroll_position
+ });
+
+ cx.update_editor(|editor, window, cx| editor.select_all_matches(&SelectAllMatches, window, cx))
+ .unwrap();
+ cx.assert_editor_state(&format!(
+ "«aˇ»bc\n«aˇ»bc{large_body_1} «aˇ»bc{large_body_2}\nef«aˇ»bc\n«aˇ»bc"
+ ));
+ cx.update_editor(|editor, _, cx| {
+ assert_eq!(
+ editor.scroll_position(cx),
+ initial_scroll_position,
+ "Scroll position should not change after selecting all matches"
+ )
+ });
+
+ cx.simulate_keystroke("x");
+ cx.update_editor(|editor, _, cx| {
+ assert_eq!(
+ editor.scroll_position(cx),
+ initial_scroll_position,
+ "Scroll position should not change after editing all matches"
+ )
+ });
}
#[gpui::test]