Introduce "entry openers" but still register editors in `workspace`

Antonio Scandurra created

Change summary

Cargo.lock                  |  1 
crates/editor/Cargo.toml    |  1 
crates/workspace/src/lib.rs | 63 ++++++++++++++++++++++++++++++--------
crates/zed/src/lib.rs       |  2 +
crates/zed/src/main.rs      | 19 ++++++----
crates/zed/src/menus.rs     |  1 
crates/zed/src/test.rs      |  1 
7 files changed, 67 insertions(+), 21 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1532,6 +1532,7 @@ dependencies = [
  "log",
  "parking_lot",
  "postage",
+ "project",
  "rand 0.8.3",
  "serde",
  "smallvec",

crates/editor/Cargo.toml 🔗

@@ -15,6 +15,7 @@ buffer = { path = "../buffer" }
 clock = { path = "../clock" }
 gpui = { path = "../gpui" }
 language = { path = "../language" }
+project = { path = "../project" }
 sum_tree = { path = "../sum_tree" }
 theme = { path = "../theme" }
 util = { path = "../util" }

crates/workspace/src/lib.rs 🔗

@@ -5,12 +5,12 @@ pub mod settings;
 pub mod sidebar;
 mod status_bar;
 
-use anyhow::Result;
+use anyhow::{anyhow, Result};
 use client::{Authenticate, ChannelList, Client, UserStore};
 use gpui::{
     action, elements::*, json::to_string_pretty, keymap::Binding, platform::CursorStyle,
-    AnyViewHandle, AppContext, ClipboardItem, Entity, ModelHandle, MutableAppContext, PromptLevel,
-    RenderContext, Task, View, ViewContext, ViewHandle, WeakModelHandle,
+    AnyViewHandle, AppContext, ClipboardItem, Entity, ModelContext, ModelHandle, MutableAppContext,
+    PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, WeakModelHandle,
 };
 use language::{Buffer, LanguageRegistry};
 use log::error;
@@ -33,7 +33,28 @@ action!(OpenNew, WorkspaceParams);
 action!(Save);
 action!(DebugElements);
 
-pub fn init(cx: &mut MutableAppContext) {
+struct BufferOpener;
+
+impl EntryOpener for BufferOpener {
+    fn open(
+        &self,
+        worktree: &mut Worktree,
+        project_path: ProjectPath,
+        cx: &mut ModelContext<Worktree>,
+    ) -> Option<Task<Result<Box<dyn ItemHandle>>>> {
+        let buffer = worktree.open_buffer(project_path.path, cx);
+        let task = cx.spawn(|_, _| async move {
+            buffer
+                .await
+                .map(|buffer| Box::new(buffer) as Box<dyn ItemHandle>)
+        });
+        Some(task)
+    }
+}
+
+pub fn init(cx: &mut MutableAppContext, entry_openers: &mut Vec<Box<dyn EntryOpener>>) {
+    entry_openers.push(Box::new(BufferOpener));
+
     cx.add_action(Workspace::save_active_item);
     cx.add_action(Workspace::debug_elements);
     cx.add_action(Workspace::open_new_file);
@@ -62,6 +83,15 @@ pub fn init(cx: &mut MutableAppContext) {
     pane::init(cx);
 }
 
+pub trait EntryOpener {
+    fn open(
+        &self,
+        worktree: &mut Worktree,
+        path: ProjectPath,
+        cx: &mut ModelContext<Worktree>,
+    ) -> Option<Task<Result<Box<dyn ItemHandle>>>>;
+}
+
 pub trait Item: Entity + Sized {
     type View: ItemView;
 
@@ -268,6 +298,7 @@ pub struct WorkspaceParams {
     pub settings: watch::Receiver<Settings>,
     pub user_store: ModelHandle<UserStore>,
     pub channel_list: ModelHandle<ChannelList>,
+    pub entry_openers: Arc<[Box<dyn EntryOpener>]>,
 }
 
 impl WorkspaceParams {
@@ -299,6 +330,7 @@ impl WorkspaceParams {
             languages: Arc::new(languages),
             settings: watch::channel_with(settings).1,
             user_store,
+            entry_openers: Arc::from([]),
         }
     }
 }
@@ -316,6 +348,7 @@ pub struct Workspace {
     active_pane: ViewHandle<Pane>,
     status_bar: ViewHandle<StatusBar>,
     project: ModelHandle<Project>,
+    entry_openers: Arc<[Box<dyn EntryOpener>]>,
     items: Vec<Box<dyn WeakItemHandle>>,
     loading_items: HashMap<
         ProjectPath,
@@ -388,6 +421,7 @@ impl Workspace {
             left_sidebar: Sidebar::new(Side::Left),
             right_sidebar: Sidebar::new(Side::Right),
             project,
+            entry_openers: params.entry_openers.clone(),
             items: Default::default(),
             loading_items: Default::default(),
             _observe_current_user,
@@ -600,18 +634,21 @@ impl Workspace {
             entry.insert(rx);
 
             let project_path = project_path.clone();
+            let entry_openers = self.entry_openers.clone();
             cx.as_mut()
                 .spawn(|mut cx| async move {
-                    let buffer = worktree
-                        .update(&mut cx, |worktree, cx| {
-                            worktree.open_buffer(project_path.path.as_ref(), cx)
+                    let item = worktree.update(&mut cx, move |worktree, cx| {
+                        for opener in entry_openers.iter() {
+                            if let Some(task) = opener.open(worktree, project_path.clone(), cx) {
+                                return task;
+                            }
+                        }
+
+                        cx.spawn(|_, _| async move {
+                            Err(anyhow!("no opener for path {:?} found", project_path))
                         })
-                        .await;
-                    *tx.borrow_mut() = Some(
-                        buffer
-                            .map(|buffer| Box::new(buffer) as Box<dyn ItemHandle>)
-                            .map_err(Arc::new),
-                    );
+                    });
+                    *tx.borrow_mut() = Some(item.await.map_err(Arc::new));
                 })
                 .detach();
         }

crates/zed/src/lib.rs 🔗

@@ -45,6 +45,7 @@ pub struct AppState {
     pub user_store: ModelHandle<client::UserStore>,
     pub fs: Arc<dyn fs::Fs>,
     pub channel_list: ModelHandle<client::ChannelList>,
+    pub entry_openers: Arc<[Box<dyn workspace::EntryOpener>]>,
 }
 
 #[derive(Clone)]
@@ -185,6 +186,7 @@ impl<'a> From<&'a AppState> for WorkspaceParams {
             settings: state.settings.clone(),
             user_store: state.user_store.clone(),
             channel_list: state.channel_list.clone(),
+            entry_openers: state.entry_openers.clone(),
         }
     }
 }

crates/zed/src/main.rs 🔗

@@ -33,6 +33,16 @@ fn main() {
         let client = client::Client::new();
         let http = http::client();
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
+        let mut entry_openers = Vec::new();
+
+        client::init(client.clone(), cx);
+        workspace::init(cx, &mut entry_openers);
+        editor::init(cx);
+        file_finder::init(cx);
+        people_panel::init(cx);
+        chat_panel::init(cx);
+        project_panel::init(cx);
+
         let app_state = Arc::new(AppState {
             languages: languages.clone(),
             settings_tx: Arc::new(Mutex::new(settings_tx)),
@@ -43,16 +53,9 @@ fn main() {
             client,
             user_store,
             fs: Arc::new(RealFs),
+            entry_openers: Arc::from(entry_openers),
         });
-
         zed::init(&app_state, cx);
-        client::init(app_state.client.clone(), cx);
-        workspace::init(cx);
-        editor::init(cx);
-        file_finder::init(cx);
-        people_panel::init(cx);
-        chat_panel::init(cx);
-        project_panel::init(cx);
         theme_selector::init(app_state.as_ref().into(), cx);
 
         cx.set_menus(menus::menus(&app_state.clone()));

crates/zed/src/menus.rs 🔗

@@ -11,6 +11,7 @@ pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
         settings: state.settings.clone(),
         user_store: state.user_store.clone(),
         channel_list: state.channel_list.clone(),
+        entry_openers: state.entry_openers.clone(),
     };
 
     vec![

crates/zed/src/test.rs 🔗

@@ -30,6 +30,7 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
         client,
         user_store,
         fs: Arc::new(FakeFs::new()),
+        entry_openers: Arc::from([]),
     })
 }