From aeed9d0d8b5300836419f6a1c7f9836a947ab9e1 Mon Sep 17 00:00:00 2001 From: Andrew Lygin Date: Fri, 1 Mar 2024 12:40:57 +0300 Subject: [PATCH] Scroll project search results to the top (#8329) Scroll project search results to the top after every new search. Release Notes: - Fixed autoscrolling of the project search results ([8237](https://github.com/zed-industries/zed/issues/8237)) --- crates/search/src/project_search.rs | 115 ++++++++++++++++++++++++---- 1 file changed, 99 insertions(+), 16 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 3d00c283e6..b3c52476fe 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -7,16 +7,18 @@ use crate::{ use anyhow::{Context as _, Result}; use collections::HashMap; use editor::{ - actions::SelectAll, items::active_match_index, scroll::Autoscroll, Anchor, Editor, EditorEvent, - MultiBuffer, MAX_TAB_TITLE_LEN, + actions::SelectAll, + items::active_match_index, + scroll::{Autoscroll, Axis}, + Anchor, Editor, EditorEvent, MultiBuffer, MAX_TAB_TITLE_LEN, }; use editor::{EditorElement, EditorStyle}; use gpui::{ actions, div, Action, AnyElement, AnyView, AppContext, Context as _, Element, EntityId, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, Global, Hsla, - InteractiveElement, IntoElement, KeyContext, Model, ModelContext, ParentElement, PromptLevel, - Render, SharedString, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext, - WeakModel, WeakView, WhiteSpace, WindowContext, + InteractiveElement, IntoElement, KeyContext, Model, ModelContext, ParentElement, Point, + PromptLevel, Render, SharedString, Styled, Subscription, Task, TextStyle, View, ViewContext, + VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext, }; use menu::Confirm; use project::{ @@ -1302,6 +1304,7 @@ impl ProjectSearchView { editor.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges(range_to_select) }); + editor.scroll(Point::default(), Some(Axis::Vertical), cx); } editor.highlight_background::( match_ranges, @@ -2094,11 +2097,12 @@ fn register_workspace_action_for_present_search( pub mod tests { use super::*; use editor::DisplayPoint; - use gpui::{Action, TestAppContext}; + use gpui::{Action, TestAppContext, WindowHandle}; use project::FakeFs; use semantic_index::semantic_index_settings::SemanticIndexSettings; use serde_json::json; use settings::{Settings, SettingsStore}; + use std::sync::Arc; use workspace::DeploySearch; #[gpui::test] @@ -2120,15 +2124,7 @@ pub mod tests { let search = cx.new_model(|cx| ProjectSearch::new(project, cx)); let search_view = cx.add_window(|cx| ProjectSearchView::new(search.clone(), cx, None)); - search_view - .update(cx, |search_view, cx| { - search_view - .query_editor - .update(cx, |query_editor, cx| query_editor.set_text("TWO", cx)); - search_view.search(cx); - }) - .unwrap(); - cx.background_executor.run_until_parked(); + perform_search(search_view, "TWO", cx); search_view.update(cx, |search_view, cx| { assert_eq!( search_view @@ -3377,7 +3373,78 @@ pub mod tests { .unwrap(); } - pub fn init_test(cx: &mut TestAppContext) { + #[gpui::test] + async fn test_scroll_search_results_to_top(cx: &mut TestAppContext) { + init_test(cx); + + // We need many lines in the search results to be able to scroll the window + let fs = FakeFs::new(cx.background_executor.clone()); + fs.insert_tree( + "/dir", + json!({ + "1.txt": "\n\n\n\n\n A \n\n\n\n\n", + "2.txt": "\n\n\n\n\n A \n\n\n\n\n", + "3.rs": "\n\n\n\n\n A \n\n\n\n\n", + "4.rs": "\n\n\n\n\n A \n\n\n\n\n", + "5.rs": "\n\n\n\n\n A \n\n\n\n\n", + "6.rs": "\n\n\n\n\n A \n\n\n\n\n", + "7.rs": "\n\n\n\n\n A \n\n\n\n\n", + "8.rs": "\n\n\n\n\n A \n\n\n\n\n", + "9.rs": "\n\n\n\n\n A \n\n\n\n\n", + "a.rs": "\n\n\n\n\n A \n\n\n\n\n", + "b.rs": "\n\n\n\n\n B \n\n\n\n\n", + "c.rs": "\n\n\n\n\n B \n\n\n\n\n", + "d.rs": "\n\n\n\n\n B \n\n\n\n\n", + "e.rs": "\n\n\n\n\n B \n\n\n\n\n", + "f.rs": "\n\n\n\n\n B \n\n\n\n\n", + "g.rs": "\n\n\n\n\n B \n\n\n\n\n", + "h.rs": "\n\n\n\n\n B \n\n\n\n\n", + "i.rs": "\n\n\n\n\n B \n\n\n\n\n", + "j.rs": "\n\n\n\n\n B \n\n\n\n\n", + "k.rs": "\n\n\n\n\n B \n\n\n\n\n", + }), + ) + .await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + let search = cx.new_model(|cx| ProjectSearch::new(project, cx)); + let search_view = cx.add_window(|cx| ProjectSearchView::new(search.clone(), cx, None)); + + // First search + perform_search(search_view.clone(), "A", cx); + search_view + .update(cx, |search_view, cx| { + search_view.results_editor.update(cx, |results_editor, cx| { + // Results are correct and scrolled to the top + assert_eq!( + results_editor.display_text(cx).match_indices(" A ").count(), + 10 + ); + assert_eq!(results_editor.scroll_position(cx), Point::default()); + + // Scroll results all the way down + results_editor.scroll(Point::new(0., f32::MAX), Some(Axis::Vertical), cx); + }); + }) + .expect("unable to update search view"); + + // Second search + perform_search(search_view.clone(), "B", cx); + search_view + .update(cx, |search_view, cx| { + search_view.results_editor.update(cx, |results_editor, cx| { + // Results are correct... + assert_eq!( + results_editor.display_text(cx).match_indices(" B ").count(), + 10 + ); + // ...and scrolled back to the top + assert_eq!(results_editor.scroll_position(cx), Point::default()); + }); + }) + .expect("unable to update search view"); + } + + fn init_test(cx: &mut TestAppContext) { cx.update(|cx| { let settings = SettingsStore::test(cx); cx.set_global(settings); @@ -3394,4 +3461,20 @@ pub mod tests { super::init(cx); }); } + + fn perform_search( + search_view: WindowHandle, + text: impl Into>, + cx: &mut TestAppContext, + ) { + search_view + .update(cx, |search_view, cx| { + search_view + .query_editor + .update(cx, |query_editor, cx| query_editor.set_text(text, cx)); + search_view.search(cx); + }) + .unwrap(); + cx.background_executor.run_until_parked(); + } }