| @@ -61,12 +61,12 @@ struct ActiveSearches(HashMap<WeakModel<Project>, WeakView<ProjectSearchView>>); · 60 unmodified lines |
| 61 | struct ActiveSettings(HashMap<WeakModel<Project>, ProjectSearchSettings>); | 61 | struct ActiveSettings(HashMap<WeakModel<Project>, ProjectSearchSettings>); |
| 62 | | 62 | |
| 63 | pub fn init(cx: &mut AppContext) { | 63 | pub fn init(cx: &mut AppContext) { |
| 64 | // todo!() po | | |
| 65 | cx.set_global(ActiveSearches::default()); | 64 | cx.set_global(ActiveSearches::default()); |
| 66 | cx.set_global(ActiveSettings::default()); | 65 | cx.set_global(ActiveSettings::default()); |
| 67 | cx.observe_new_views(|workspace: &mut Workspace, _cx| { | 66 | cx.observe_new_views(|workspace: &mut Workspace, _cx| { |
| 68 | workspace | 67 | workspace |
| 69 | .register_action(ProjectSearchView::deploy) | 68 | .register_action(ProjectSearchView::new_search) |
| | 69 | .register_action(ProjectSearchView::deploy_search) |
| 70 | .register_action(ProjectSearchBar::search_in_new); | 70 | .register_action(ProjectSearchBar::search_in_new); |
| 71 | }) | 71 | }) |
| 72 | .detach(); | 72 | .detach(); |
| @@ -941,11 +941,41 @@ impl ProjectSearchView { · 868 unmodified lines |
| 941 | }); | 941 | }); |
| 942 | } | 942 | } |
| 943 | | 943 | |
| | 944 | // Re-activate the most recently activated search or the most recent if it has been closed. |
| | 945 | // If no search exists in the workspace, create a new one. |
| | 946 | fn deploy_search( |
| | 947 | workspace: &mut Workspace, |
| | 948 | _: &workspace::DeploySearch, |
| | 949 | cx: &mut ViewContext<Workspace>, |
| | 950 | ) { |
| | 951 | let active_search = cx |
| | 952 | .global::<ActiveSearches>() |
| | 953 | .0 |
| | 954 | .get(&workspace.project().downgrade()); |
| | 955 | let existing = active_search |
| | 956 | .and_then(|active_search| { |
| | 957 | workspace |
| | 958 | .items_of_type::<ProjectSearchView>(cx) |
| | 959 | .filter(|search| &search.downgrade() == active_search) |
| | 960 | .last() |
| | 961 | }) |
| | 962 | .or_else(|| workspace.item_of_type::<ProjectSearchView>(cx)); |
| | 963 | Self::existing_or_new_search(workspace, existing, cx) |
| | 964 | } |
| | 965 | |
| 944 | // Add another search tab to the workspace. | 966 | // Add another search tab to the workspace. |
| 945 | fn deploy( | 967 | fn new_search( |
| 946 | workspace: &mut Workspace, | 968 | workspace: &mut Workspace, |
| 947 | _: &workspace::NewSearch, | 969 | _: &workspace::NewSearch, |
| 948 | cx: &mut ViewContext<Workspace>, | 970 | cx: &mut ViewContext<Workspace>, |
| | 971 | ) { |
| | 972 | Self::existing_or_new_search(workspace, None, cx) |
| | 973 | } |
| | 974 | |
| | 975 | fn existing_or_new_search( |
| | 976 | workspace: &mut Workspace, |
| | 977 | existing: Option<View<ProjectSearchView>>, |
| | 978 | cx: &mut ViewContext<Workspace>, |
| 949 | ) { | 979 | ) { |
| 950 | // Clean up entries for dropped projects | 980 | // Clean up entries for dropped projects |
| 951 | cx.update_global(|state: &mut ActiveSearches, _cx| { | 981 | cx.update_global(|state: &mut ActiveSearches, _cx| { |
| @@ -962,19 +992,27 @@ impl ProjectSearchView { · 10 unmodified lines |
| 962 | } | 992 | } |
| 963 | }); | 993 | }); |
| 964 | | 994 | |
| 965 | let settings = cx | 995 | let search = if let Some(existing) = existing { |
| 966 | .global::<ActiveSettings>() | 996 | workspace.activate_item(&existing, cx); |
| 967 | .0 | 997 | existing |
| 968 | .get(&workspace.project().downgrade()); | | |
| 969 | | | |
| 970 | let settings = if let Some(settings) = settings { | | |
| 971 | Some(settings.clone()) | | |
| 972 | } else { | 998 | } else { |
| 973 | None | 999 | let settings = cx |
| 974 | }; | 1000 | .global::<ActiveSettings>() |
| | 1001 | .0 |
| | 1002 | .get(&workspace.project().downgrade()); |
| 975 | | 1003 | |
| 976 | let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx)); | 1004 | let settings = if let Some(settings) = settings { |
| 977 | let search = cx.new_view(|cx| ProjectSearchView::new(model, cx, settings)); | 1005 | Some(settings.clone()) |
| | 1006 | } else { |
| | 1007 | None |
| | 1008 | }; |
| | 1009 | |
| | 1010 | let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx)); |
| | 1011 | let view = cx.new_view(|cx| ProjectSearchView::new(model, cx, settings)); |
| | 1012 | |
| | 1013 | workspace.add_item(Box::new(view.clone()), cx); |
| | 1014 | view |
| | 1015 | }; |
| 978 | | 1016 | |
| 979 | workspace.add_item(Box::new(search.clone()), cx); | 1017 | workspace.add_item(Box::new(search.clone()), cx); |
| 980 | | 1018 | |
| @@ -2060,7 +2098,237 @@ pub mod tests { · 1079 unmodified lines |
| 2060 | } | 2098 | } |
| 2061 | | 2099 | |
| 2062 | #[gpui::test] | 2100 | #[gpui::test] |
| 2063 | async fn test_project_search_focus(cx: &mut TestAppContext) { | 2101 | async fn test_deploy_project_search_focus(cx: &mut TestAppContext) { |
| | 2102 | init_test(cx); |
| | 2103 | |
| | 2104 | let fs = FakeFs::new(cx.background_executor.clone()); |
| | 2105 | fs.insert_tree( |
| | 2106 | "/dir", |
| | 2107 | json!({ |
| | 2108 | "one.rs": "const ONE: usize = 1;", |
| | 2109 | "two.rs": "const TWO: usize = one::ONE + one::ONE;", |
| | 2110 | "three.rs": "const THREE: usize = one::ONE + two::TWO;", |
| | 2111 | "four.rs": "const FOUR: usize = one::ONE + three::THREE;", |
| | 2112 | }), |
| | 2113 | ) |
| | 2114 | .await; |
| | 2115 | let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; |
| | 2116 | let window = cx.add_window(|cx| Workspace::test_new(project, cx)); |
| | 2117 | let workspace = window.clone(); |
| | 2118 | let search_bar = window.build_view(cx, |_| ProjectSearchBar::new()); |
| | 2119 | |
| | 2120 | let active_item = cx.read(|cx| { |
| | 2121 | workspace |
| | 2122 | .read(cx) |
| | 2123 | .unwrap() |
| | 2124 | .active_pane() |
| | 2125 | .read(cx) |
| | 2126 | .active_item() |
| | 2127 | .and_then(|item| item.downcast::<ProjectSearchView>()) |
| | 2128 | }); |
| | 2129 | assert!( |
| | 2130 | active_item.is_none(), |
| | 2131 | "Expected no search panel to be active" |
| | 2132 | ); |
| | 2133 | |
| | 2134 | window |
| | 2135 | .update(cx, move |workspace, cx| { |
| | 2136 | assert_eq!(workspace.panes().len(), 1); |
| | 2137 | workspace.panes()[0].update(cx, move |pane, cx| { |
| | 2138 | pane.toolbar() |
| | 2139 | .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) |
| | 2140 | }); |
| | 2141 | |
| | 2142 | ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch, cx) |
| | 2143 | }) |
| | 2144 | .unwrap(); |
| | 2145 | |
| | 2146 | let Some(search_view) = cx.read(|cx| { |
| | 2147 | workspace |
| | 2148 | .read(cx) |
| | 2149 | .unwrap() |
| | 2150 | .active_pane() |
| | 2151 | .read(cx) |
| | 2152 | .active_item() |
| | 2153 | .and_then(|item| item.downcast::<ProjectSearchView>()) |
| | 2154 | }) else { |
| | 2155 | panic!("Search view expected to appear after new search event trigger") |
| | 2156 | }; |
| | 2157 | |
| | 2158 | cx.spawn(|mut cx| async move { |
| | 2159 | window |
| | 2160 | .update(&mut cx, |_, cx| { |
| | 2161 | cx.dispatch_action(ToggleFocus.boxed_clone()) |
| | 2162 | }) |
| | 2163 | .unwrap(); |
| | 2164 | }) |
| | 2165 | .detach(); |
| | 2166 | cx.background_executor.run_until_parked(); |
| | 2167 | window |
| | 2168 | .update(cx, |_, cx| { |
| | 2169 | search_view.update(cx, |search_view, cx| { |
| | 2170 | assert!( |
| | 2171 | search_view.query_editor.focus_handle(cx).is_focused(cx), |
| | 2172 | "Empty search view should be focused after the toggle focus event: no results panel to focus on", |
| | 2173 | ); |
| | 2174 | }); |
| | 2175 | }).unwrap(); |
| | 2176 | |
| | 2177 | window |
| | 2178 | .update(cx, |_, cx| { |
| | 2179 | search_view.update(cx, |search_view, cx| { |
| | 2180 | let query_editor = &search_view.query_editor; |
| | 2181 | assert!( |
| | 2182 | query_editor.focus_handle(cx).is_focused(cx), |
| | 2183 | "Search view should be focused after the new search view is activated", |
| | 2184 | ); |
| | 2185 | let query_text = query_editor.read(cx).text(cx); |
| | 2186 | assert!( |
| | 2187 | query_text.is_empty(), |
| | 2188 | "New search query should be empty but got '{query_text}'", |
| | 2189 | ); |
| | 2190 | let results_text = search_view |
| | 2191 | .results_editor |
| | 2192 | .update(cx, |editor, cx| editor.display_text(cx)); |
| | 2193 | assert!( |
| | 2194 | results_text.is_empty(), |
| | 2195 | "Empty search view should have no results but got '{results_text}'" |
| | 2196 | ); |
| | 2197 | }); |
| | 2198 | }) |
| | 2199 | .unwrap(); |
| | 2200 | |
| | 2201 | window |
| | 2202 | .update(cx, |_, cx| { |
| | 2203 | search_view.update(cx, |search_view, cx| { |
| | 2204 | search_view.query_editor.update(cx, |query_editor, cx| { |
| | 2205 | query_editor.set_text("sOMETHINGtHATsURELYdOESnOTeXIST", cx) |
| | 2206 | }); |
| | 2207 | search_view.search(cx); |
| | 2208 | }); |
| | 2209 | }) |
| | 2210 | .unwrap(); |
| | 2211 | cx.background_executor.run_until_parked(); |
| | 2212 | window |
| | 2213 | .update(cx, |_, cx| { |
| | 2214 | search_view.update(cx, |search_view, cx| { |
| | 2215 | let results_text = search_view |
| | 2216 | .results_editor |
| | 2217 | .update(cx, |editor, cx| editor.display_text(cx)); |
| | 2218 | assert!( |
| | 2219 | results_text.is_empty(), |
| | 2220 | "Search view for mismatching query should have no results but got '{results_text}'" |
| | 2221 | ); |
| | 2222 | assert!( |
| | 2223 | search_view.query_editor.focus_handle(cx).is_focused(cx), |
| | 2224 | "Search view should be focused after mismatching query had been used in search", |
| | 2225 | ); |
| | 2226 | }); |
| | 2227 | }).unwrap(); |
| | 2228 | |
| | 2229 | cx.spawn(|mut cx| async move { |
| | 2230 | window.update(&mut cx, |_, cx| { |
| | 2231 | cx.dispatch_action(ToggleFocus.boxed_clone()) |
| | 2232 | }) |
| | 2233 | }) |
| | 2234 | .detach(); |
| | 2235 | cx.background_executor.run_until_parked(); |
| | 2236 | window.update(cx, |_, cx| { |
| | 2237 | search_view.update(cx, |search_view, cx| { |
| | 2238 | assert!( |
| | 2239 | search_view.query_editor.focus_handle(cx).is_focused(cx), |
| | 2240 | "Search view with mismatching query should be focused after the toggle focus event: still no results panel to focus on", |
| | 2241 | ); |
| | 2242 | }); |
| | 2243 | }).unwrap(); |
| | 2244 | |
| | 2245 | window |
| | 2246 | .update(cx, |_, cx| { |
| | 2247 | search_view.update(cx, |search_view, cx| { |
| | 2248 | search_view |
| | 2249 | .query_editor |
| | 2250 | .update(cx, |query_editor, cx| query_editor.set_text("TWO", cx)); |
| | 2251 | search_view.search(cx); |
| | 2252 | }); |
| | 2253 | }) |
| | 2254 | .unwrap(); |
| | 2255 | cx.background_executor.run_until_parked(); |
| | 2256 | window.update(cx, |_, cx| { |
| | 2257 | search_view.update(cx, |search_view, cx| { |
| | 2258 | assert_eq!( |
| | 2259 | search_view |
| | 2260 | .results_editor |
| | 2261 | .update(cx, |editor, cx| editor.display_text(cx)), |
| | 2262 | "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;", |
| | 2263 | "Search view results should match the query" |
| | 2264 | ); |
| | 2265 | assert!( |
| | 2266 | search_view.results_editor.focus_handle(cx).is_focused(cx), |
| | 2267 | "Search view with mismatching query should be focused after search results are available", |
| | 2268 | ); |
| | 2269 | }); |
| | 2270 | }).unwrap(); |
| | 2271 | cx.spawn(|mut cx| async move { |
| | 2272 | window |
| | 2273 | .update(&mut cx, |_, cx| { |
| | 2274 | cx.dispatch_action(ToggleFocus.boxed_clone()) |
| | 2275 | }) |
| | 2276 | .unwrap(); |
| | 2277 | }) |
| | 2278 | .detach(); |
| | 2279 | cx.background_executor.run_until_parked(); |
| | 2280 | window.update(cx, |_, cx| { |
| | 2281 | search_view.update(cx, |search_view, cx| { |
| | 2282 | assert!( |
| | 2283 | search_view.results_editor.focus_handle(cx).is_focused(cx), |
| | 2284 | "Search view with matching query should still have its results editor focused after the toggle focus event", |
| | 2285 | ); |
| | 2286 | }); |
| | 2287 | }).unwrap(); |
| | 2288 | |
| | 2289 | workspace |
| | 2290 | .update(cx, |workspace, cx| { |
| | 2291 | ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch, cx) |
| | 2292 | }) |
| | 2293 | .unwrap(); |
| | 2294 | window.update(cx, |_, cx| { |
| | 2295 | search_view.update(cx, |search_view, cx| { |
| | 2296 | assert_eq!(search_view.query_editor.read(cx).text(cx), "two", "Query should be updated to first search result after search view 2nd open in a row"); |
| | 2297 | assert_eq!( |
| | 2298 | search_view |
| | 2299 | .results_editor |
| | 2300 | .update(cx, |editor, cx| editor.display_text(cx)), |
| | 2301 | "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;", |
| | 2302 | "Results should be unchanged after search view 2nd open in a row" |
| | 2303 | ); |
| | 2304 | assert!( |
| | 2305 | search_view.query_editor.focus_handle(cx).is_focused(cx), |
| | 2306 | "Focus should be moved into query editor again after search view 2nd open in a row" |
| | 2307 | ); |
| | 2308 | }); |
| | 2309 | }).unwrap(); |
| | 2310 | |
| | 2311 | cx.spawn(|mut cx| async move { |
| | 2312 | window |
| | 2313 | .update(&mut cx, |_, cx| { |
| | 2314 | cx.dispatch_action(ToggleFocus.boxed_clone()) |
| | 2315 | }) |
| | 2316 | .unwrap(); |
| | 2317 | }) |
| | 2318 | .detach(); |
| | 2319 | cx.background_executor.run_until_parked(); |
| | 2320 | window.update(cx, |_, cx| { |
| | 2321 | search_view.update(cx, |search_view, cx| { |
| | 2322 | assert!( |
| | 2323 | search_view.results_editor.focus_handle(cx).is_focused(cx), |
| | 2324 | "Search view with matching query should switch focus to the results editor after the toggle focus event", |
| | 2325 | ); |
| | 2326 | }); |
| | 2327 | }).unwrap(); |
| | 2328 | } |
| | 2329 | |
| | 2330 | #[gpui::test] |
| | 2331 | async fn test_new_project_search_focus(cx: &mut TestAppContext) { |
| 2064 | init_test(cx); | 2332 | init_test(cx); |
| 2065 | | 2333 | |
| 2066 | let fs = FakeFs::new(cx.background_executor.clone()); | 2334 | let fs = FakeFs::new(cx.background_executor.clone()); |
| @@ -2101,7 +2369,7 @@ pub mod tests { · 34 unmodified lines |
| 2101 | .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) | 2369 | .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) |
| 2102 | }); | 2370 | }); |
| 2103 | | 2371 | |
| 2104 | ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx) | 2372 | ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx) |
| 2105 | }) | 2373 | }) |
| 2106 | .unwrap(); | 2374 | .unwrap(); |
| 2107 | | 2375 | |
| @@ -2250,7 +2518,7 @@ pub mod tests { · 142 unmodified lines |
| 2250 | | 2518 | |
| 2251 | workspace | 2519 | workspace |
| 2252 | .update(cx, |workspace, cx| { | 2520 | .update(cx, |workspace, cx| { |
| 2253 | ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx) | 2521 | ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx) |
| 2254 | }) | 2522 | }) |
| 2255 | .unwrap(); | 2523 | .unwrap(); |
| 2256 | cx.background_executor.run_until_parked(); | 2524 | cx.background_executor.run_until_parked(); |
| @@ -2536,7 +2804,7 @@ pub mod tests { · 279 unmodified lines |
| 2536 | .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) | 2804 | .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) |
| 2537 | }); | 2805 | }); |
| 2538 | | 2806 | |
| 2539 | ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx) | 2807 | ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx) |
| 2540 | } | 2808 | } |
| 2541 | }) | 2809 | }) |
| 2542 | .unwrap(); | 2810 | .unwrap(); |