Cargo.lock 🔗
@@ -1782,7 +1782,9 @@ dependencies = [
name = "file_finder"
version = "0.1.0"
dependencies = [
+ "ctor",
"editor",
+ "env_logger",
"fuzzy",
"gpui",
"postage",
Antonio Scandurra and Nathan Sobo created
This lets downstream crates like `editor` define how project items should be
opened.
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Cargo.lock | 2
crates/editor/src/editor.rs | 2
crates/file_finder/Cargo.toml | 2
crates/file_finder/src/file_finder.rs | 7 ++
crates/gpui/src/app.rs | 34 +++++++++++++
crates/project/src/project.rs | 21 +++++++
crates/workspace/src/workspace.rs | 69 ++++++++++++++++------------
7 files changed, 102 insertions(+), 35 deletions(-)
@@ -1782,7 +1782,9 @@ dependencies = [
name = "file_finder"
version = "0.1.0"
dependencies = [
+ "ctor",
"editor",
+ "env_logger",
"fuzzy",
"gpui",
"postage",
@@ -340,7 +340,7 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_async_action(Editor::confirm_rename);
cx.add_async_action(Editor::find_all_references);
- workspace::register_editor_builder(cx, |project, buffer, cx| {
+ workspace::register_project_item(cx, |project, buffer, cx| {
Editor::for_buffer(buffer, Some(project), cx)
});
}
@@ -21,3 +21,5 @@ postage = { version = "0.4.1", features = ["futures-traits"] }
gpui = { path = "../gpui", features = ["test-support"] }
serde_json = { version = "1.0.64", features = ["preserve_order"] }
workspace = { path = "../workspace", features = ["test-support"] }
+ctor = "0.1"
+env_logger = "0.8"
@@ -407,6 +407,13 @@ mod tests {
use std::path::PathBuf;
use workspace::{Workspace, WorkspaceParams};
+ #[ctor::ctor]
+ fn init_logger() {
+ if std::env::var("RUST_LOG").is_ok() {
+ env_logger::init();
+ }
+ }
+
#[gpui::test]
async fn test_matching_paths(cx: &mut gpui::TestAppContext) {
cx.update(|cx| {
@@ -1364,12 +1364,38 @@ impl MutableAppContext {
Ok(pending)
}
+ pub fn default_global<T: 'static + Default>(&mut self) -> &T {
+ self.cx
+ .globals
+ .entry(TypeId::of::<T>())
+ .or_insert_with(|| Box::new(T::default()))
+ .downcast_ref()
+ .unwrap()
+ }
+
pub fn set_global<T: 'static>(&mut self, state: T) {
self.cx.globals.insert(TypeId::of::<T>(), Box::new(state));
}
- pub fn update_global<T: 'static, F, U>(&mut self, update: F) -> U
+ pub fn update_default_global<T, F, U>(&mut self, update: F) -> U
+ where
+ T: 'static + Default,
+ F: FnOnce(&mut T, &mut MutableAppContext) -> U,
+ {
+ let type_id = TypeId::of::<T>();
+ let mut state = self
+ .cx
+ .globals
+ .remove(&type_id)
+ .unwrap_or_else(|| Box::new(T::default()));
+ let result = update(state.downcast_mut().unwrap(), self);
+ self.cx.globals.insert(type_id, state);
+ result
+ }
+
+ pub fn update_global<T, F, U>(&mut self, update: F) -> U
where
+ T: 'static,
F: FnOnce(&mut T, &mut MutableAppContext) -> U,
{
let type_id = TypeId::of::<T>();
@@ -1377,7 +1403,7 @@ impl MutableAppContext {
.cx
.globals
.remove(&type_id)
- .expect("no app state has been added for this type");
+ .expect("no global has been added for this type");
let result = update(state.downcast_mut().unwrap(), self);
self.cx.globals.insert(type_id, state);
result
@@ -3715,6 +3741,10 @@ impl AnyModelHandle {
pub fn is<T: Entity>(&self) -> bool {
self.model_type == TypeId::of::<T>()
}
+
+ pub fn model_type(&self) -> TypeId {
+ self.model_type
+ }
}
impl<T: Entity> From<ModelHandle<T>> for AnyModelHandle {
@@ -11,8 +11,8 @@ use collections::{hash_map, BTreeMap, HashMap, HashSet};
use futures::{future::Shared, Future, FutureExt, StreamExt, TryFutureExt};
use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
use gpui::{
- AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
- UpgradeModelHandle, WeakModelHandle,
+ AnyModelHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
+ MutableAppContext, Task, UpgradeModelHandle, WeakModelHandle,
};
use language::{
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
@@ -822,6 +822,23 @@ impl Project {
Ok(buffer)
}
+ pub fn open_path(
+ &mut self,
+ path: impl Into<ProjectPath>,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<(ProjectEntryId, AnyModelHandle)>> {
+ let task = self.open_buffer(path, cx);
+ cx.spawn_weak(|_, cx| async move {
+ let buffer = task.await?;
+ let project_entry_id = buffer
+ .read_with(&cx, |buffer, cx| {
+ File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
+ })
+ .ok_or_else(|| anyhow!("no project entry"))?;
+ Ok((project_entry_id, buffer.into()))
+ })
+ }
+
pub fn open_buffer(
&mut self,
path: impl Into<ProjectPath>,
@@ -9,6 +9,7 @@ mod status_bar;
use anyhow::{anyhow, Result};
use client::{Authenticate, ChannelList, Client, User, UserStore};
use clock::ReplicaId;
+use collections::HashMap;
use gpui::{
action,
color::Color,
@@ -17,11 +18,11 @@ use gpui::{
json::{self, to_string_pretty, ToJson},
keymap::Binding,
platform::{CursorStyle, WindowOptions},
- AnyViewHandle, AppContext, ClipboardItem, Entity, ImageData, ModelHandle, MutableAppContext,
- PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle,
- WeakViewHandle,
+ AnyModelHandle, AnyViewHandle, AppContext, ClipboardItem, Entity, ImageData, ModelHandle,
+ MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext,
+ ViewHandle, WeakViewHandle,
};
-use language::{Buffer, LanguageRegistry};
+use language::LanguageRegistry;
use log::error;
pub use pane::*;
pub use pane_group::*;
@@ -41,13 +42,16 @@ use std::{
};
use theme::{Theme, ThemeRegistry};
-pub type BuildEditor = Arc<
- dyn Fn(
- usize,
- ModelHandle<Project>,
- ModelHandle<Buffer>,
- &mut MutableAppContext,
- ) -> Box<dyn ItemHandle>,
+type ItemBuilders = HashMap<
+ TypeId,
+ Arc<
+ dyn Fn(
+ usize,
+ ModelHandle<Project>,
+ AnyModelHandle,
+ &mut MutableAppContext,
+ ) -> Box<dyn ItemHandle>,
+ >,
>;
action!(Open, Arc<AppState>);
@@ -104,14 +108,20 @@ pub fn init(cx: &mut MutableAppContext) {
]);
}
-pub fn register_editor_builder<F, V>(cx: &mut MutableAppContext, build_editor: F)
+pub fn register_project_item<F, V>(cx: &mut MutableAppContext, build_item: F)
where
- V: Item,
- F: 'static + Fn(ModelHandle<Project>, ModelHandle<Buffer>, &mut ViewContext<V>) -> V,
+ V: ProjectItem,
+ F: 'static + Fn(ModelHandle<Project>, ModelHandle<V::Item>, &mut ViewContext<V>) -> V,
{
- cx.set_global::<BuildEditor>(Arc::new(move |window_id, project, model, cx| {
- Box::new(cx.add_view(window_id, |cx| build_editor(project, model, cx)))
- }));
+ cx.update_default_global(|builders: &mut ItemBuilders, _| {
+ builders.insert(
+ TypeId::of::<V::Item>(),
+ Arc::new(move |window_id, project, model, cx| {
+ let model = model.downcast::<V::Item>().unwrap();
+ Box::new(cx.add_view(window_id, |cx| build_item(project, model, cx)))
+ }),
+ );
+ });
}
pub struct AppState {
@@ -826,20 +836,19 @@ impl Workspace {
)>,
> {
let project = self.project().clone();
- let buffer = project.update(cx, |project, cx| project.open_buffer(path, cx));
- cx.spawn(|this, mut cx| async move {
- let buffer = buffer.await?;
- let project_entry_id = buffer.read_with(&cx, |buffer, cx| {
- project::File::from_dyn(buffer.file())
- .and_then(|file| file.project_entry_id(cx))
- .ok_or_else(|| anyhow!("buffer has no entry"))
+ let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
+ let window_id = cx.window_id();
+ cx.as_mut().spawn(|mut cx| async move {
+ let (project_entry_id, project_item) = project_item.await?;
+ let build_item = cx.update(|cx| {
+ cx.default_global::<ItemBuilders>()
+ .get(&project_item.model_type())
+ .ok_or_else(|| anyhow!("no item builder for project item"))
+ .cloned()
})?;
- let (window_id, build_editor) = this.update(&mut cx, |_, cx| {
- (cx.window_id(), cx.global::<BuildEditor>().clone())
- });
- let build_editor =
- move |cx: &mut MutableAppContext| build_editor(window_id, project, buffer, cx);
- Ok((project_entry_id, build_editor))
+ let build_item =
+ move |cx: &mut MutableAppContext| build_item(window_id, project, project_item, cx);
+ Ok((project_entry_id, build_item))
})
}