| @@ -61,12 +61,12 @@ struct ActiveSearches(HashMap<WeakModel<Project>, WeakView<ProjectSearchView>>); · 60 unmodified lines |
| 61 | 61 | struct ActiveSettings(HashMap<WeakModel<Project>, ProjectSearchSettings>); |
| 62 | 62 | |
| 63 | 63 | pub fn init(cx: &mut AppContext) { |
| 64 | | − // todo!() po |
| 65 | 64 | cx.set_global(ActiveSearches::default()); |
| 66 | 65 | cx.set_global(ActiveSettings::default()); |
| 67 | 66 | cx.observe_new_views(|workspace: &mut Workspace, _cx| { |
| 68 | 67 | workspace |
| 69 | | − .register_action(ProjectSearchView::deploy) |
| 68 | + .register_action(ProjectSearchView::new_search) |
| 69 | + .register_action(ProjectSearchView::deploy_search) |
| 70 | 70 | .register_action(ProjectSearchBar::search_in_new); |
| 71 | 71 | }) |
| 72 | 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 | 966 | // Add another search tab to the workspace. |
| 945 | | − fn deploy( |
| 967 | + fn new_search( |
| 946 | 968 | workspace: &mut Workspace, |
| 947 | 969 | _: &workspace::NewSearch, |
| 948 | 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 | 980 | // Clean up entries for dropped projects |
| 951 | 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 |
| 966 | | − .global::<ActiveSettings>() |
| 967 | | − .0 |
| 968 | | − .get(&workspace.project().downgrade()); |
| 969 | | − |
| 970 | | − let settings = if let Some(settings) = settings { |
| 971 | | − Some(settings.clone()) |
| 995 | + let search = if let Some(existing) = existing { |
| 996 | + workspace.activate_item(&existing, cx); |
| 997 | + existing |
| 972 | 998 | } else { |
| 973 | | − None |
| 974 | | − }; |
| 999 | + let settings = cx |
| 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)); |
| 977 | | − let search = cx.new_view(|cx| ProjectSearchView::new(model, cx, settings)); |
| 1004 | + let settings = if let Some(settings) = 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 | 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 | 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 | 2332 | init_test(cx); |
| 2065 | 2333 | |
| 2066 | 2334 | let fs = FakeFs::new(cx.background_executor.clone()); |
| @@ -2101,7 +2369,7 @@ pub mod tests { · 34 unmodified lines |
| 2101 | 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 | 2374 | .unwrap(); |
| 2107 | 2375 | |
| @@ -2250,7 +2518,7 @@ pub mod tests { · 142 unmodified lines |
| 2250 | 2518 | |
| 2251 | 2519 | workspace |
| 2252 | 2520 | .update(cx, |workspace, cx| { |
| 2253 | | − ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx) |
| 2521 | + ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx) |
| 2254 | 2522 | }) |
| 2255 | 2523 | .unwrap(); |
| 2256 | 2524 | cx.background_executor.run_until_parked(); |
| @@ -2536,7 +2804,7 @@ pub mod tests { · 279 unmodified lines |
| 2536 | 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 | 2810 | .unwrap(); |