From 5d14c9abdf9397aef8605937288dd649132081c1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 17 Mar 2022 15:54:34 +0100 Subject: [PATCH] Introduce `workspace::register_project_item` This lets downstream crates like `editor` define how project items should be opened. Co-Authored-By: Nathan Sobo --- 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(-) diff --git a/Cargo.lock b/Cargo.lock index 28c433c43f85e7df2de479d96eaf99b3d1a453d0..4cfb831a58cd49ea148afd07382d7fd983795e58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1782,7 +1782,9 @@ dependencies = [ name = "file_finder" version = "0.1.0" dependencies = [ + "ctor", "editor", + "env_logger", "fuzzy", "gpui", "postage", diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 40d589bea71c6894422fd4cc985a9d3f9d0034d7..30888d8a408ada78170e6dd6a39979392e085fe4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -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) }); } diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index c5300dbcd9f47279aac127315e2d03f354aaae04..b946ea48fbbf58a7196f56949ececfe23fa1fd75 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -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" diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 808f7e47031b9c08ba79e89330a990a2c8ed5440..ca41eb74a11cb2d08103c22e90eebff3ccef7d53 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -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| { diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 097f9d7199fea07871db8522e0c2e1900bcdbb4c..5488d31416368a19f91f88c1d3bc51e2e7a6c264 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1364,12 +1364,38 @@ impl MutableAppContext { Ok(pending) } + pub fn default_global(&mut self) -> &T { + self.cx + .globals + .entry(TypeId::of::()) + .or_insert_with(|| Box::new(T::default())) + .downcast_ref() + .unwrap() + } + pub fn set_global(&mut self, state: T) { self.cx.globals.insert(TypeId::of::(), Box::new(state)); } - pub fn update_global(&mut self, update: F) -> U + pub fn update_default_global(&mut self, update: F) -> U + where + T: 'static + Default, + F: FnOnce(&mut T, &mut MutableAppContext) -> U, + { + let type_id = TypeId::of::(); + 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(&mut self, update: F) -> U where + T: 'static, F: FnOnce(&mut T, &mut MutableAppContext) -> U, { let type_id = TypeId::of::(); @@ -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(&self) -> bool { self.model_type == TypeId::of::() } + + pub fn model_type(&self) -> TypeId { + self.model_type + } } impl From> for AnyModelHandle { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c999b8ce4eb93228769fa782785af8e6b1b0f571..317cf1ba028d19b920d04c4a70094adddb932b97 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -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, + cx: &mut ModelContext, + ) -> Task> { + 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, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index d220dbc0fac755d2620577246f9b28bc6dcf895a..33155b5d4f4710338d6c4d4570af129f21e92624 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -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, - ModelHandle, - &mut MutableAppContext, - ) -> Box, +type ItemBuilders = HashMap< + TypeId, + Arc< + dyn Fn( + usize, + ModelHandle, + AnyModelHandle, + &mut MutableAppContext, + ) -> Box, + >, >; action!(Open, Arc); @@ -104,14 +108,20 @@ pub fn init(cx: &mut MutableAppContext) { ]); } -pub fn register_editor_builder(cx: &mut MutableAppContext, build_editor: F) +pub fn register_project_item(cx: &mut MutableAppContext, build_item: F) where - V: Item, - F: 'static + Fn(ModelHandle, ModelHandle, &mut ViewContext) -> V, + V: ProjectItem, + F: 'static + Fn(ModelHandle, ModelHandle, &mut ViewContext) -> V, { - cx.set_global::(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::(), + Arc::new(move |window_id, project, model, cx| { + let model = model.downcast::().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::() + .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::().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)) }) }