diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 00b1ef2de57f4faeeda3852aabda848791cd116d..dcfb00617d600a2db2feeb94c4aca99d1428b49f 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -85,6 +85,8 @@ pub trait UpgradeModelHandle { handle: &WeakModelHandle, ) -> Option>; + fn model_handle_is_upgradable(&self, handle: &WeakModelHandle) -> bool; + fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option; } @@ -608,6 +610,10 @@ impl UpgradeModelHandle for AsyncAppContext { self.0.borrow().upgrade_model_handle(handle) } + fn model_handle_is_upgradable(&self, handle: &WeakModelHandle) -> bool { + self.0.borrow().model_handle_is_upgradable(handle) + } + fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option { self.0.borrow().upgrade_any_model_handle(handle) } @@ -763,6 +769,7 @@ impl MutableAppContext { models: Default::default(), views: Default::default(), windows: Default::default(), + app_states: Default::default(), element_states: Default::default(), ref_counts: Arc::new(Mutex::new(RefCounts::default())), background, @@ -1306,6 +1313,27 @@ impl MutableAppContext { Ok(pending) } + pub fn add_app_state(&mut self, state: T) { + self.cx + .app_states + .insert(TypeId::of::(), Box::new(state)); + } + + pub fn update_app_state(&mut self, update: F) -> U + where + F: FnOnce(&mut T, &mut MutableAppContext) -> U, + { + let type_id = TypeId::of::(); + let mut state = self + .cx + .app_states + .remove(&type_id) + .expect("no app state has been added for this type"); + let result = update(state.downcast_mut().unwrap(), self); + self.cx.app_states.insert(type_id, state); + result + } + pub fn add_model(&mut self, build_model: F) -> ModelHandle where T: Entity, @@ -1828,6 +1856,10 @@ impl UpgradeModelHandle for MutableAppContext { self.cx.upgrade_model_handle(handle) } + fn model_handle_is_upgradable(&self, handle: &WeakModelHandle) -> bool { + self.cx.model_handle_is_upgradable(handle) + } + fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option { self.cx.upgrade_any_model_handle(handle) } @@ -1898,6 +1930,7 @@ pub struct AppContext { models: HashMap>, views: HashMap<(usize, usize), Box>, windows: HashMap, + app_states: HashMap>, element_states: HashMap>, background: Arc, ref_counts: Arc>, @@ -1929,6 +1962,14 @@ impl AppContext { pub fn platform(&self) -> &Arc { &self.platform } + + pub fn app_state(&self) -> &T { + self.app_states + .get(&TypeId::of::()) + .expect("no app state has been added for this type") + .downcast_ref() + .unwrap() + } } impl ReadModel for AppContext { @@ -1956,6 +1997,10 @@ impl UpgradeModelHandle for AppContext { } } + fn model_handle_is_upgradable(&self, handle: &WeakModelHandle) -> bool { + self.models.contains_key(&handle.model_id) + } + fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option { if self.models.contains_key(&handle.model_id) { self.ref_counts.lock().inc_model(handle.model_id); @@ -2361,6 +2406,10 @@ impl UpgradeModelHandle for ModelContext<'_, M> { self.cx.upgrade_model_handle(handle) } + fn model_handle_is_upgradable(&self, handle: &WeakModelHandle) -> bool { + self.cx.model_handle_is_upgradable(handle) + } + fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option { self.cx.upgrade_any_model_handle(handle) } @@ -2699,6 +2748,10 @@ impl UpgradeModelHandle for ViewContext<'_, V> { self.cx.upgrade_model_handle(handle) } + fn model_handle_is_upgradable(&self, handle: &WeakModelHandle) -> bool { + self.cx.model_handle_is_upgradable(handle) + } + fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option { self.cx.upgrade_any_model_handle(handle) } @@ -2941,6 +2994,12 @@ impl PartialEq for ModelHandle { impl Eq for ModelHandle {} +impl PartialEq> for ModelHandle { + fn eq(&self, other: &WeakModelHandle) -> bool { + self.model_id == other.model_id + } +} + impl Hash for ModelHandle { fn hash(&self, state: &mut H) { self.model_id.hash(state); @@ -3013,6 +3072,10 @@ impl WeakModelHandle { self.model_id } + pub fn is_upgradable(&self, cx: &impl UpgradeModelHandle) -> bool { + cx.model_handle_is_upgradable(self) + } + pub fn upgrade(&self, cx: &impl UpgradeModelHandle) -> Option> { cx.upgrade_model_handle(self) } diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index ba59e14a30b38fe4b551b96f3b119020752b30ec..8a41a76e714bc352593c20a87e18edb5758b8e0b 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -281,6 +281,10 @@ impl<'a> UpgradeModelHandle for LayoutContext<'a> { self.app.upgrade_model_handle(handle) } + fn model_handle_is_upgradable(&self, handle: &WeakModelHandle) -> bool { + self.app.model_handle_is_upgradable(handle) + } + fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option { self.app.upgrade_any_model_handle(handle) } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index be2ac6a51a86f6cd9f2dc271e0b034df64d59c79..d1db3f0d2bd92060eea7370053fa0a46ed7cb12f 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1,9 +1,10 @@ use crate::{Direction, SearchOption, SelectMatch, ToggleSearchOption}; +use collections::HashMap; use editor::{Anchor, Autoscroll, Editor, MultiBuffer, SelectAll}; use gpui::{ action, elements::*, keymap::Binding, platform::CursorStyle, AppContext, ElementBox, Entity, ModelContext, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, - ViewHandle, + ViewHandle, WeakModelHandle, }; use postage::watch; use project::{search::SearchQuery, Project}; @@ -14,7 +15,9 @@ use std::{ path::PathBuf, }; use util::ResultExt as _; -use workspace::{Item, ItemHandle, ItemNavHistory, ItemView, Settings, Workspace}; +use workspace::{ + Item, ItemHandle, ItemNavHistory, ItemView, Settings, WeakItemViewHandle, Workspace, +}; action!(Deploy); action!(Search); @@ -23,7 +26,11 @@ action!(ToggleFocus); const MAX_TAB_TITLE_LEN: usize = 24; +#[derive(Default)] +struct ActiveSearches(HashMap, WeakModelHandle>); + pub fn init(cx: &mut MutableAppContext) { + cx.add_app_state(ActiveSearches::default()); cx.add_bindings([ Binding::new("cmd-shift-F", ToggleFocus, Some("ProjectSearchView")), Binding::new("cmd-f", ToggleFocus, Some("ProjectSearchView")), @@ -194,6 +201,13 @@ impl View for ProjectSearchView { } fn on_focus(&mut self, cx: &mut ViewContext) { + cx.update_app_state(|state: &mut ActiveSearches, cx| { + state.0.insert( + self.model.read(cx).project.downgrade(), + self.model.downgrade(), + ) + }); + if self.model.read(cx).match_ranges.is_empty() { cx.focus(&self.query_editor); } else { @@ -383,11 +397,28 @@ impl ProjectSearchView { this } + // Re-activate the most recently activated search or the most recent if it has been closed. + // If no search exists in the workspace, create a new one. fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext) { - if let Some(existing) = workspace - .items_of_type::(cx) - .max_by_key(|existing| existing.id()) - { + // Clean up entries for dropped projects + cx.update_app_state(|state: &mut ActiveSearches, cx| { + state.0.retain(|project, _| project.is_upgradable(cx)) + }); + + let active_search = cx + .app_state::() + .0 + .get(&workspace.project().downgrade()); + + let existing = active_search + .and_then(|active_search| { + workspace + .items_of_type::(cx) + .find(|search| search == active_search) + }) + .or_else(|| workspace.item_of_type::(cx)); + + if let Some(existing) = existing { workspace.activate_item(&existing, cx); } else { let model = cx.add_model(|cx| ProjectSearch::new(workspace.project().clone(), cx)); @@ -515,10 +546,7 @@ impl ProjectSearchView { self.focus_results_editor(cx); } } else { - self.query_editor.update(cx, |query_editor, cx| { - query_editor.select_all(&SelectAll, cx); - }); - cx.focus(&self.query_editor); + self.focus_query_editor(cx); } } @@ -532,6 +560,13 @@ impl ProjectSearchView { } } + fn focus_query_editor(&self, cx: &mut ViewContext) { + self.query_editor.update(cx, |query_editor, cx| { + query_editor.select_all(&SelectAll, cx); + }); + cx.focus(&self.query_editor); + } + fn focus_results_editor(&self, cx: &mut ViewContext) { self.query_editor.update(cx, |query_editor, cx| { let cursor = query_editor.newest_anchor_selection().head(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 585695c520c757b3dddc851571a13c519373598e..0ad3a1e5e35ad0cce16c2f6bf2c40f5cf03293db 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -9,7 +9,7 @@ mod status_bar; use anyhow::{anyhow, Result}; use client::{Authenticate, ChannelList, Client, User, UserStore}; use clock::ReplicaId; -use collections::HashSet; +use collections::BTreeMap; use gpui::{ action, color::Color, @@ -36,6 +36,7 @@ pub use status_bar::StatusItemView; use std::{ any::{Any, TypeId}, cell::RefCell, + cmp::Reverse, future::Future, hash::{Hash, Hasher}, path::{Path, PathBuf}, @@ -569,7 +570,7 @@ pub struct Workspace { status_bar: ViewHandle, project: ModelHandle, path_openers: Arc<[Box]>, - items: HashSet>, + items: BTreeMap, Box>, _observe_current_user: Task<()>, } @@ -815,14 +816,14 @@ impl Workspace { fn item_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option> { self.items - .iter() + .values() .filter_map(|i| i.upgrade(cx)) .find(|i| i.project_path(cx).as_ref() == Some(path)) } pub fn item_of_type(&self, cx: &AppContext) -> Option> { self.items - .iter() + .values() .find_map(|i| i.upgrade(cx).and_then(|i| i.to_any().downcast())) } @@ -831,7 +832,7 @@ impl Workspace { cx: &'a AppContext, ) -> impl 'a + Iterator> { self.items - .iter() + .values() .filter_map(|i| i.upgrade(cx).and_then(|i| i.to_any().downcast())) } @@ -974,7 +975,8 @@ impl Workspace { where T: 'static + ItemHandle, { - self.items.insert(item_handle.downgrade()); + self.items + .insert(Reverse(item_handle.id()), item_handle.downgrade()); pane.update(cx, |pane, cx| pane.open_item(item_handle, self, cx)) } @@ -1068,7 +1070,8 @@ impl Workspace { if let Some(item) = pane.read(cx).active_item() { let nav_history = new_pane.read(cx).nav_history().clone(); if let Some(clone) = item.clone_on_split(nav_history, cx.as_mut()) { - self.items.insert(clone.item(cx).downgrade()); + let item = clone.item(cx).downgrade(); + self.items.insert(Reverse(item.id()), item); new_pane.update(cx, |new_pane, cx| new_pane.add_item_view(clone, cx)); } }