Don't scroll the editor on select all matches (#28435)

Part of https://github.com/zed-industries/zed/issues/9309

Release Notes:

- Improved scroll behavior of `editor: select all matches`

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
neunato 2025-04-09 19:50:14 +02:00 committed by GitHub
parent 6aa0248ab3
commit 64de6bd2a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 45 additions and 47 deletions

View file

@ -11573,7 +11573,7 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Editor>,
) {
this.unfold_ranges(&[range.clone()], false, true, cx);
this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
this.change_selections(auto_scroll, window, cx, |s| {
if replace_newest {
s.delete(s.newest_anchor().id);
@ -11748,16 +11748,21 @@ impl Editor {
return Ok(());
}
let mut new_selections = self.selections.all::<usize>(cx);
let mut new_selections = Vec::new();
let reversed = self.selections.oldest::<usize>(cx).reversed;
let buffer = &display_map.buffer_snapshot;
let query_matches = select_next_state
.query
.stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
for query_match in query_matches {
let query_match = query_match.unwrap(); // can only fail due to I/O
let offset_range = query_match.start()..query_match.end();
for query_match in query_matches.into_iter() {
let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
let offset_range = if reversed {
query_match.end()..query_match.start()
} else {
query_match.start()..query_match.end()
};
let display_range = offset_range.start.to_display_point(&display_map)
..offset_range.end.to_display_point(&display_map);
@ -11765,52 +11770,14 @@ impl Editor {
|| (!movement::is_inside_word(&display_map, display_range.start)
&& !movement::is_inside_word(&display_map, display_range.end))
{
self.selections.change_with(cx, |selections| {
new_selections.push(Selection {
id: selections.new_selection_id(),
start: offset_range.start,
end: offset_range.end,
reversed: false,
goal: SelectionGoal::None,
});
});
new_selections.push(offset_range.start..offset_range.end);
}
}
new_selections.sort_by_key(|selection| selection.start);
let mut ix = 0;
while ix + 1 < new_selections.len() {
let current_selection = &new_selections[ix];
let next_selection = &new_selections[ix + 1];
if current_selection.range().overlaps(&next_selection.range()) {
if current_selection.id < next_selection.id {
new_selections.remove(ix + 1);
} else {
new_selections.remove(ix);
}
} else {
ix += 1;
}
}
let reversed = self.selections.oldest::<usize>(cx).reversed;
for selection in new_selections.iter_mut() {
selection.reversed = reversed;
}
select_next_state.done = true;
self.unfold_ranges(
&new_selections
.iter()
.map(|selection| selection.range())
.collect::<Vec<_>>(),
false,
false,
cx,
);
self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
selections.select(new_selections)
self.unfold_ranges(&new_selections.clone(), false, false, cx);
self.change_selections(None, window, cx, |selections| {
selections.select_ranges(new_selections)
});
Ok(())

View file

@ -5842,6 +5842,37 @@ async fn test_select_all_matches(cx: &mut TestAppContext) {
cx.assert_editor_state("abc\n« ˇ»abc\nabc");
}
#[gpui::test]
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);
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(|e, window, cx| e.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"
);
}
#[gpui::test]
async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
init_test(cx, |_| {});