Re-activate the most recently-activated project search on cmd-shift-F

This commits adds the beginnings of an application state facility as a non-static place to store the most recently-activated search for each project.

I also store workspace items by descending order of their entity id so that we always fetch the newest item of a given type when calling `Workspace::item_of_type`.
This commit is contained in:
Nathan Sobo 2022-02-27 18:07:46 -07:00
parent 1ddae2adfd
commit cb230ad574
4 changed files with 122 additions and 17 deletions

View file

@ -85,6 +85,8 @@ pub trait UpgradeModelHandle {
handle: &WeakModelHandle<T>, handle: &WeakModelHandle<T>,
) -> Option<ModelHandle<T>>; ) -> Option<ModelHandle<T>>;
fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool;
fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle>; fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle>;
} }
@ -608,6 +610,10 @@ impl UpgradeModelHandle for AsyncAppContext {
self.0.borrow().upgrade_model_handle(handle) self.0.borrow().upgrade_model_handle(handle)
} }
fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool {
self.0.borrow().model_handle_is_upgradable(handle)
}
fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> { fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> {
self.0.borrow().upgrade_any_model_handle(handle) self.0.borrow().upgrade_any_model_handle(handle)
} }
@ -763,6 +769,7 @@ impl MutableAppContext {
models: Default::default(), models: Default::default(),
views: Default::default(), views: Default::default(),
windows: Default::default(), windows: Default::default(),
app_states: Default::default(),
element_states: Default::default(), element_states: Default::default(),
ref_counts: Arc::new(Mutex::new(RefCounts::default())), ref_counts: Arc::new(Mutex::new(RefCounts::default())),
background, background,
@ -1306,6 +1313,27 @@ impl MutableAppContext {
Ok(pending) Ok(pending)
} }
pub fn add_app_state<T: 'static>(&mut self, state: T) {
self.cx
.app_states
.insert(TypeId::of::<T>(), Box::new(state));
}
pub fn update_app_state<T: 'static, F, U>(&mut self, update: F) -> U
where
F: FnOnce(&mut T, &mut MutableAppContext) -> U,
{
let type_id = TypeId::of::<T>();
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<T, F>(&mut self, build_model: F) -> ModelHandle<T> pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
where where
T: Entity, T: Entity,
@ -1828,6 +1856,10 @@ impl UpgradeModelHandle for MutableAppContext {
self.cx.upgrade_model_handle(handle) self.cx.upgrade_model_handle(handle)
} }
fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool {
self.cx.model_handle_is_upgradable(handle)
}
fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> { fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> {
self.cx.upgrade_any_model_handle(handle) self.cx.upgrade_any_model_handle(handle)
} }
@ -1898,6 +1930,7 @@ pub struct AppContext {
models: HashMap<usize, Box<dyn AnyModel>>, models: HashMap<usize, Box<dyn AnyModel>>,
views: HashMap<(usize, usize), Box<dyn AnyView>>, views: HashMap<(usize, usize), Box<dyn AnyView>>,
windows: HashMap<usize, Window>, windows: HashMap<usize, Window>,
app_states: HashMap<TypeId, Box<dyn Any>>,
element_states: HashMap<ElementStateId, Box<dyn Any>>, element_states: HashMap<ElementStateId, Box<dyn Any>>,
background: Arc<executor::Background>, background: Arc<executor::Background>,
ref_counts: Arc<Mutex<RefCounts>>, ref_counts: Arc<Mutex<RefCounts>>,
@ -1929,6 +1962,14 @@ impl AppContext {
pub fn platform(&self) -> &Arc<dyn Platform> { pub fn platform(&self) -> &Arc<dyn Platform> {
&self.platform &self.platform
} }
pub fn app_state<T: 'static>(&self) -> &T {
self.app_states
.get(&TypeId::of::<T>())
.expect("no app state has been added for this type")
.downcast_ref()
.unwrap()
}
} }
impl ReadModel for AppContext { impl ReadModel for AppContext {
@ -1956,6 +1997,10 @@ impl UpgradeModelHandle for AppContext {
} }
} }
fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool {
self.models.contains_key(&handle.model_id)
}
fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> { fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> {
if self.models.contains_key(&handle.model_id) { if self.models.contains_key(&handle.model_id) {
self.ref_counts.lock().inc_model(handle.model_id); self.ref_counts.lock().inc_model(handle.model_id);
@ -2361,6 +2406,10 @@ impl<M> UpgradeModelHandle for ModelContext<'_, M> {
self.cx.upgrade_model_handle(handle) self.cx.upgrade_model_handle(handle)
} }
fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool {
self.cx.model_handle_is_upgradable(handle)
}
fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> { fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> {
self.cx.upgrade_any_model_handle(handle) self.cx.upgrade_any_model_handle(handle)
} }
@ -2699,6 +2748,10 @@ impl<V> UpgradeModelHandle for ViewContext<'_, V> {
self.cx.upgrade_model_handle(handle) self.cx.upgrade_model_handle(handle)
} }
fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool {
self.cx.model_handle_is_upgradable(handle)
}
fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> { fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> {
self.cx.upgrade_any_model_handle(handle) self.cx.upgrade_any_model_handle(handle)
} }
@ -2941,6 +2994,12 @@ impl<T> PartialEq for ModelHandle<T> {
impl<T> Eq for ModelHandle<T> {} impl<T> Eq for ModelHandle<T> {}
impl<T> PartialEq<WeakModelHandle<T>> for ModelHandle<T> {
fn eq(&self, other: &WeakModelHandle<T>) -> bool {
self.model_id == other.model_id
}
}
impl<T> Hash for ModelHandle<T> { impl<T> Hash for ModelHandle<T> {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.model_id.hash(state); self.model_id.hash(state);
@ -3013,6 +3072,10 @@ impl<T: Entity> WeakModelHandle<T> {
self.model_id 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<ModelHandle<T>> { pub fn upgrade(&self, cx: &impl UpgradeModelHandle) -> Option<ModelHandle<T>> {
cx.upgrade_model_handle(self) cx.upgrade_model_handle(self)
} }

View file

@ -281,6 +281,10 @@ impl<'a> UpgradeModelHandle for LayoutContext<'a> {
self.app.upgrade_model_handle(handle) self.app.upgrade_model_handle(handle)
} }
fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool {
self.app.model_handle_is_upgradable(handle)
}
fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> { fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> {
self.app.upgrade_any_model_handle(handle) self.app.upgrade_any_model_handle(handle)
} }

View file

@ -1,9 +1,10 @@
use crate::{Direction, SearchOption, SelectMatch, ToggleSearchOption}; use crate::{Direction, SearchOption, SelectMatch, ToggleSearchOption};
use collections::HashMap;
use editor::{Anchor, Autoscroll, Editor, MultiBuffer, SelectAll}; use editor::{Anchor, Autoscroll, Editor, MultiBuffer, SelectAll};
use gpui::{ use gpui::{
action, elements::*, keymap::Binding, platform::CursorStyle, AppContext, ElementBox, Entity, action, elements::*, keymap::Binding, platform::CursorStyle, AppContext, ElementBox, Entity,
ModelContext, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ModelContext, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext,
ViewHandle, ViewHandle, WeakModelHandle,
}; };
use postage::watch; use postage::watch;
use project::{search::SearchQuery, Project}; use project::{search::SearchQuery, Project};
@ -14,7 +15,9 @@ use std::{
path::PathBuf, path::PathBuf,
}; };
use util::ResultExt as _; use util::ResultExt as _;
use workspace::{Item, ItemHandle, ItemNavHistory, ItemView, Settings, Workspace}; use workspace::{
Item, ItemHandle, ItemNavHistory, ItemView, Settings, WeakItemViewHandle, Workspace,
};
action!(Deploy); action!(Deploy);
action!(Search); action!(Search);
@ -23,7 +26,11 @@ action!(ToggleFocus);
const MAX_TAB_TITLE_LEN: usize = 24; const MAX_TAB_TITLE_LEN: usize = 24;
#[derive(Default)]
struct ActiveSearches(HashMap<WeakModelHandle<Project>, WeakModelHandle<ProjectSearch>>);
pub fn init(cx: &mut MutableAppContext) { pub fn init(cx: &mut MutableAppContext) {
cx.add_app_state(ActiveSearches::default());
cx.add_bindings([ cx.add_bindings([
Binding::new("cmd-shift-F", ToggleFocus, Some("ProjectSearchView")), Binding::new("cmd-shift-F", ToggleFocus, Some("ProjectSearchView")),
Binding::new("cmd-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<Self>) { fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
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() { if self.model.read(cx).match_ranges.is_empty() {
cx.focus(&self.query_editor); cx.focus(&self.query_editor);
} else { } else {
@ -383,11 +397,28 @@ impl ProjectSearchView {
this 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<Workspace>) { fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
if let Some(existing) = workspace // Clean up entries for dropped projects
.items_of_type::<ProjectSearch>(cx) cx.update_app_state(|state: &mut ActiveSearches, cx| {
.max_by_key(|existing| existing.id()) state.0.retain(|project, _| project.is_upgradable(cx))
{ });
let active_search = cx
.app_state::<ActiveSearches>()
.0
.get(&workspace.project().downgrade());
let existing = active_search
.and_then(|active_search| {
workspace
.items_of_type::<ProjectSearch>(cx)
.find(|search| search == active_search)
})
.or_else(|| workspace.item_of_type::<ProjectSearch>(cx));
if let Some(existing) = existing {
workspace.activate_item(&existing, cx); workspace.activate_item(&existing, cx);
} else { } else {
let model = cx.add_model(|cx| ProjectSearch::new(workspace.project().clone(), cx)); let model = cx.add_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
@ -515,10 +546,7 @@ impl ProjectSearchView {
self.focus_results_editor(cx); self.focus_results_editor(cx);
} }
} else { } else {
self.query_editor.update(cx, |query_editor, cx| { self.focus_query_editor(cx);
query_editor.select_all(&SelectAll, cx);
});
cx.focus(&self.query_editor);
} }
} }
@ -532,6 +560,13 @@ impl ProjectSearchView {
} }
} }
fn focus_query_editor(&self, cx: &mut ViewContext<Self>) {
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>) { fn focus_results_editor(&self, cx: &mut ViewContext<Self>) {
self.query_editor.update(cx, |query_editor, cx| { self.query_editor.update(cx, |query_editor, cx| {
let cursor = query_editor.newest_anchor_selection().head(); let cursor = query_editor.newest_anchor_selection().head();

View file

@ -9,7 +9,7 @@ mod status_bar;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use client::{Authenticate, ChannelList, Client, User, UserStore}; use client::{Authenticate, ChannelList, Client, User, UserStore};
use clock::ReplicaId; use clock::ReplicaId;
use collections::HashSet; use collections::BTreeMap;
use gpui::{ use gpui::{
action, action,
color::Color, color::Color,
@ -36,6 +36,7 @@ pub use status_bar::StatusItemView;
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
cell::RefCell, cell::RefCell,
cmp::Reverse,
future::Future, future::Future,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -569,7 +570,7 @@ pub struct Workspace {
status_bar: ViewHandle<StatusBar>, status_bar: ViewHandle<StatusBar>,
project: ModelHandle<Project>, project: ModelHandle<Project>,
path_openers: Arc<[Box<dyn PathOpener>]>, path_openers: Arc<[Box<dyn PathOpener>]>,
items: HashSet<Box<dyn WeakItemHandle>>, items: BTreeMap<Reverse<usize>, Box<dyn WeakItemHandle>>,
_observe_current_user: Task<()>, _observe_current_user: Task<()>,
} }
@ -815,14 +816,14 @@ impl Workspace {
fn item_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<Box<dyn ItemHandle>> { fn item_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
self.items self.items
.iter() .values()
.filter_map(|i| i.upgrade(cx)) .filter_map(|i| i.upgrade(cx))
.find(|i| i.project_path(cx).as_ref() == Some(path)) .find(|i| i.project_path(cx).as_ref() == Some(path))
} }
pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ModelHandle<T>> { pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ModelHandle<T>> {
self.items self.items
.iter() .values()
.find_map(|i| i.upgrade(cx).and_then(|i| i.to_any().downcast())) .find_map(|i| i.upgrade(cx).and_then(|i| i.to_any().downcast()))
} }
@ -831,7 +832,7 @@ impl Workspace {
cx: &'a AppContext, cx: &'a AppContext,
) -> impl 'a + Iterator<Item = ModelHandle<T>> { ) -> impl 'a + Iterator<Item = ModelHandle<T>> {
self.items self.items
.iter() .values()
.filter_map(|i| i.upgrade(cx).and_then(|i| i.to_any().downcast())) .filter_map(|i| i.upgrade(cx).and_then(|i| i.to_any().downcast()))
} }
@ -974,7 +975,8 @@ impl Workspace {
where where
T: 'static + ItemHandle, 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)) 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() { if let Some(item) = pane.read(cx).active_item() {
let nav_history = new_pane.read(cx).nav_history().clone(); let nav_history = new_pane.read(cx).nav_history().clone();
if let Some(clone) = item.clone_on_split(nav_history, cx.as_mut()) { 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)); new_pane.update(cx, |new_pane, cx| new_pane.add_item_view(clone, cx));
} }
} }