Introduce workspace::register_project_item

This lets downstream crates like `editor` define how project items should be
opened.

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2022-03-17 15:54:34 +01:00
parent bff414cfbc
commit 5d14c9abdf
7 changed files with 102 additions and 35 deletions

2
Cargo.lock generated
View File

@ -1782,7 +1782,9 @@ dependencies = [
name = "file_finder"
version = "0.1.0"
dependencies = [
"ctor",
"editor",
"env_logger",
"fuzzy",
"gpui",
"postage",

View File

@ -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)
});
}

View File

@ -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"

View File

@ -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| {

View File

@ -1364,12 +1364,22 @@ 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>();
@ -1377,7 +1387,23 @@ impl MutableAppContext {
.cx
.globals
.remove(&type_id)
.expect("no app state has been added for this type");
.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>();
let mut state = self
.cx
.globals
.remove(&type_id)
.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 {

View File

@ -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>,

View File

@ -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))
})
}