From 510559691803aa7aad8debb43e82585128d3e522 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 Oct 2021 10:37:40 +0200 Subject: [PATCH] Move sidebar-specific code out of Workspace --- Cargo.lock | 4 + crates/server/src/rpc.rs | 2 +- crates/workspace/Cargo.toml | 4 + crates/workspace/src/lib.rs | 0 crates/zed/src/lib.rs | 230 ++++++++++++++++++++++++++++- crates/zed/src/main.rs | 7 +- crates/zed/src/menus.rs | 2 +- crates/zed/src/people_panel.rs | 53 ++++++- crates/zed/src/workspace.rs | 259 ++------------------------------- 9 files changed, 305 insertions(+), 256 deletions(-) create mode 100644 crates/workspace/Cargo.toml create mode 100644 crates/workspace/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 173ffeb260..2e1c1d2dd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6013,6 +6013,10 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "workspace" +version = "0.1.0" + [[package]] name = "wyz" version = "0.2.0" diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 1261671c85..119da770f7 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1095,7 +1095,7 @@ mod tests { #[gpui::test] async fn test_unshare_worktree(mut cx_a: TestAppContext, mut cx_b: TestAppContext) { - cx_b.update(zed::workspace::init); + cx_b.update(zed::people_panel::init); let lang_registry = Arc::new(LanguageRegistry::new()); // Connect to a server as 2 clients. diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml new file mode 100644 index 0000000000..c8d25c0dba --- /dev/null +++ b/crates/workspace/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "workspace" +version = "0.1.0" +edition = "2018" diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/zed/src/lib.rs b/crates/zed/src/lib.rs index 6d5be2f0a6..05343713c9 100644 --- a/crates/zed/src/lib.rs +++ b/crates/zed/src/lib.rs @@ -14,17 +14,30 @@ pub mod workspace; pub use buffer; use buffer::LanguageRegistry; +use chat_panel::ChatPanel; pub use client; pub use editor; -use gpui::{action, keymap::Binding, ModelHandle}; +use gpui::{ + action, + geometry::{rect::RectF, vector::vec2f}, + keymap::Binding, + platform::WindowOptions, + ModelHandle, MutableAppContext, PathPromptOptions, Task, ViewContext, +}; use parking_lot::Mutex; +use people_panel::PeoplePanel; use postage::watch; pub use project::{self, fs}; +use project_panel::ProjectPanel; pub use settings::Settings; -use std::sync::Arc; +use std::{path::PathBuf, sync::Arc}; use util::TryFutureExt; +use crate::workspace::Workspace; + action!(About); +action!(Open, Arc); +action!(OpenPaths, OpenParams); action!(Quit); action!(Authenticate); action!(AdjustBufferFontSize, f32); @@ -42,7 +55,18 @@ pub struct AppState { pub channel_list: ModelHandle, } +#[derive(Clone)] +pub struct OpenParams { + pub paths: Vec, + pub app_state: Arc, +} + pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { + cx.add_global_action(open); + cx.add_global_action(|action: &OpenPaths, cx: &mut MutableAppContext| { + open_paths(action, cx).detach() + }); + cx.add_global_action(open_new); cx.add_global_action(quit); cx.add_global_action({ @@ -71,6 +95,208 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { ]) } +fn open(action: &Open, cx: &mut MutableAppContext) { + let app_state = action.0.clone(); + cx.prompt_for_paths( + PathPromptOptions { + files: true, + directories: true, + multiple: true, + }, + move |paths, cx| { + if let Some(paths) = paths { + cx.dispatch_global_action(OpenPaths(OpenParams { paths, app_state })); + } + }, + ); +} + +fn open_paths(action: &OpenPaths, cx: &mut MutableAppContext) -> Task<()> { + log::info!("open paths {:?}", action.0.paths); + + // Open paths in existing workspace if possible + for window_id in cx.window_ids().collect::>() { + if let Some(handle) = cx.root_view::(window_id) { + let task = handle.update(cx, |view, cx| { + if view.contains_paths(&action.0.paths, cx.as_ref()) { + log::info!("open paths on existing workspace"); + Some(view.open_paths(&action.0.paths, cx)) + } else { + None + } + }); + + if let Some(task) = task { + return task; + } + } + } + + log::info!("open new workspace"); + + // Add a new workspace if necessary + let (_, workspace) = cx.add_window(window_options(), |cx| { + build_workspace(&action.0.app_state, cx) + }); + workspace.update(cx, |workspace, cx| { + workspace.open_paths(&action.0.paths, cx) + }) +} + +fn open_new(action: &workspace::OpenNew, cx: &mut MutableAppContext) { + cx.add_window(window_options(), |cx| { + let mut workspace = build_workspace(action.0.as_ref(), cx); + workspace.open_new_file(&action, cx); + workspace + }); +} + +fn build_workspace(app_state: &AppState, cx: &mut ViewContext) -> Workspace { + let mut workspace = Workspace::new(app_state, cx); + let project = workspace.project().clone(); + workspace.left_sidebar_mut().add_item( + "icons/folder-tree-16.svg", + ProjectPanel::new(project, app_state.settings.clone(), cx).into(), + ); + workspace.right_sidebar_mut().add_item( + "icons/user-16.svg", + cx.add_view(|cx| { + PeoplePanel::new(app_state.user_store.clone(), app_state.settings.clone(), cx) + }) + .into(), + ); + workspace.right_sidebar_mut().add_item( + "icons/comment-16.svg", + cx.add_view(|cx| { + ChatPanel::new( + app_state.client.clone(), + app_state.channel_list.clone(), + app_state.settings.clone(), + cx, + ) + }) + .into(), + ); + workspace +} + +fn window_options() -> WindowOptions<'static> { + WindowOptions { + bounds: RectF::new(vec2f(0., 0.), vec2f(1024., 768.)), + title: None, + titlebar_appears_transparent: true, + traffic_light_position: Some(vec2f(8., 8.)), + } +} + fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { cx.platform().quit(); } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{test::test_app_state, workspace::ItemView}; + use serde_json::json; + use util::test::temp_tree; + + #[gpui::test] + async fn test_open_paths_action(mut cx: gpui::TestAppContext) { + let app_state = cx.update(test_app_state); + let dir = temp_tree(json!({ + "a": { + "aa": null, + "ab": null, + }, + "b": { + "ba": null, + "bb": null, + }, + "c": { + "ca": null, + "cb": null, + }, + })); + + cx.update(|cx| { + open_paths( + &OpenPaths(OpenParams { + paths: vec![ + dir.path().join("a").to_path_buf(), + dir.path().join("b").to_path_buf(), + ], + app_state: app_state.clone(), + }), + cx, + ) + }) + .await; + assert_eq!(cx.window_ids().len(), 1); + + cx.update(|cx| { + open_paths( + &OpenPaths(OpenParams { + paths: vec![dir.path().join("a").to_path_buf()], + app_state: app_state.clone(), + }), + cx, + ) + }) + .await; + assert_eq!(cx.window_ids().len(), 1); + let workspace_1 = cx.root_view::(cx.window_ids()[0]).unwrap(); + workspace_1.read_with(&cx, |workspace, cx| { + assert_eq!(workspace.worktrees(cx).len(), 2) + }); + + cx.update(|cx| { + open_paths( + &OpenPaths(OpenParams { + paths: vec![ + dir.path().join("b").to_path_buf(), + dir.path().join("c").to_path_buf(), + ], + app_state: app_state.clone(), + }), + cx, + ) + }) + .await; + assert_eq!(cx.window_ids().len(), 2); + } + + #[gpui::test] + async fn test_new_empty_workspace(mut cx: gpui::TestAppContext) { + let app_state = cx.update(test_app_state); + cx.update(|cx| init(&app_state, cx)); + cx.dispatch_global_action(workspace::OpenNew(app_state.clone())); + let window_id = *cx.window_ids().first().unwrap(); + let workspace = cx.root_view::(window_id).unwrap(); + let editor = workspace.update(&mut cx, |workspace, cx| { + workspace + .active_item(cx) + .unwrap() + .to_any() + .downcast::() + .unwrap() + }); + + editor.update(&mut cx, |editor, cx| { + assert!(editor.text(cx).is_empty()); + }); + + workspace.update(&mut cx, |workspace, cx| { + workspace.save_active_item(&workspace::Save, cx) + }); + + app_state.fs.as_fake().insert_dir("/root").await.unwrap(); + cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name"))); + + editor + .condition(&cx, |editor, cx| editor.title(cx) == "the-new-name") + .await; + editor.update(&mut cx, |editor, cx| { + assert!(!editor.is_dirty(cx)); + }); + } +} diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 95b53bbde3..020a011301 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -14,9 +14,9 @@ use zed::{ client::{http, ChannelList, UserStore}, editor, file_finder, fs::RealFs, - language, menus, project_panel, settings, theme_selector, - workspace::{self, OpenNew, OpenParams, OpenPaths}, - AppState, + language, menus, people_panel, project_panel, settings, theme_selector, + workspace::{self, OpenNew}, + AppState, OpenParams, OpenPaths, }; fn main() { @@ -55,6 +55,7 @@ fn main() { 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, cx); diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 82dc24af1e..fcb315692e 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -33,7 +33,7 @@ pub fn menus(state: &Arc) -> Vec> { MenuItem::Action { name: "Open…", keystroke: Some("cmd-o"), - action: Box::new(workspace::Open(state.clone())), + action: Box::new(crate::Open(state.clone())), }, ], }, diff --git a/crates/zed/src/people_panel.rs b/crates/zed/src/people_panel.rs index d59ec5c2b2..9ea408d5b7 100644 --- a/crates/zed/src/people_panel.rs +++ b/crates/zed/src/people_panel.rs @@ -1,12 +1,12 @@ -use crate::{theme::Theme, Settings}; +use crate::{theme::Theme, workspace::Workspace, Settings}; use client::{Collaborator, UserStore}; use gpui::{ action, elements::*, geometry::{rect::RectF, vector::vec2f}, platform::CursorStyle, - Element, ElementBox, Entity, LayoutContext, ModelHandle, RenderContext, Subscription, View, - ViewContext, + Element, ElementBox, Entity, LayoutContext, ModelHandle, MutableAppContext, RenderContext, + Subscription, View, ViewContext, }; use postage::watch; @@ -15,6 +15,13 @@ action!(LeaveWorktree, u64); action!(ShareWorktree, u64); action!(UnshareWorktree, u64); +pub fn init(cx: &mut MutableAppContext) { + cx.add_action(PeoplePanel::share_worktree); + cx.add_action(PeoplePanel::unshare_worktree); + cx.add_action(PeoplePanel::join_worktree); + cx.add_action(PeoplePanel::leave_worktree); +} + pub struct PeoplePanel { collaborators: ListState, user_store: ModelHandle, @@ -55,6 +62,46 @@ impl PeoplePanel { } } + fn share_worktree( + workspace: &mut Workspace, + action: &ShareWorktree, + cx: &mut ViewContext, + ) { + workspace + .project() + .update(cx, |p, cx| p.share_worktree(action.0, cx)); + } + + fn unshare_worktree( + workspace: &mut Workspace, + action: &UnshareWorktree, + cx: &mut ViewContext, + ) { + workspace + .project() + .update(cx, |p, cx| p.unshare_worktree(action.0, cx)); + } + + fn join_worktree( + workspace: &mut Workspace, + action: &JoinWorktree, + cx: &mut ViewContext, + ) { + workspace + .project() + .update(cx, |p, cx| p.add_remote_worktree(action.0, cx).detach()); + } + + fn leave_worktree( + workspace: &mut Workspace, + action: &LeaveWorktree, + cx: &mut ViewContext, + ) { + workspace + .project() + .update(cx, |p, cx| p.close_remote_worktree(action.0, cx)); + } + fn update_collaborators(&mut self, _: ModelHandle, cx: &mut ViewContext) { self.collaborators .reset(self.user_store.read(cx).collaborators().len()); diff --git a/crates/zed/src/workspace.rs b/crates/zed/src/workspace.rs index 770bc838ae..8c5fb36a71 100644 --- a/crates/zed/src/workspace.rs +++ b/crates/zed/src/workspace.rs @@ -4,11 +4,8 @@ pub mod pane_group; pub mod sidebar; use crate::{ - chat_panel::ChatPanel, fs::Fs, - people_panel::{JoinWorktree, LeaveWorktree, PeoplePanel, ShareWorktree, UnshareWorktree}, project::{Project, ProjectPath}, - project_panel::ProjectPanel, settings::Settings, workspace::sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItemFocus}, AppState, Authenticate, @@ -17,15 +14,9 @@ use anyhow::Result; use buffer::Buffer; use client::Client; use gpui::{ - action, - elements::*, - geometry::{rect::RectF, vector::vec2f}, - json::to_string_pretty, - keymap::Binding, - platform::{CursorStyle, WindowOptions}, - AnyViewHandle, AppContext, ClipboardItem, Entity, ModelHandle, MutableAppContext, - PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, - WeakModelHandle, + action, elements::*, json::to_string_pretty, keymap::Binding, platform::CursorStyle, + AnyViewHandle, AppContext, ClipboardItem, Entity, ModelHandle, MutableAppContext, PromptLevel, + RenderContext, Task, View, ViewContext, ViewHandle, WeakModelHandle, }; use log::error; pub use pane::*; @@ -39,27 +30,16 @@ use std::{ sync::Arc, }; -action!(Open, Arc); -action!(OpenPaths, OpenParams); action!(OpenNew, Arc); action!(Save); action!(DebugElements); pub fn init(cx: &mut MutableAppContext) { - cx.add_global_action(open); - cx.add_global_action(|action: &OpenPaths, cx: &mut MutableAppContext| { - open_paths(action, cx).detach() - }); - cx.add_global_action(open_new); cx.add_action(Workspace::save_active_item); cx.add_action(Workspace::debug_elements); cx.add_action(Workspace::open_new_file); cx.add_action(Workspace::toggle_sidebar_item); cx.add_action(Workspace::toggle_sidebar_item_focus); - cx.add_action(Workspace::share_worktree); - cx.add_action(Workspace::unshare_worktree); - cx.add_action(Workspace::join_worktree); - cx.add_action(Workspace::leave_worktree); cx.add_bindings(vec![ Binding::new("cmd-s", Save, None), Binding::new("cmd-alt-i", DebugElements, None), @@ -83,78 +63,6 @@ pub fn init(cx: &mut MutableAppContext) { pane::init(cx); } -#[derive(Clone)] -pub struct OpenParams { - pub paths: Vec, - pub app_state: Arc, -} - -fn open(action: &Open, cx: &mut MutableAppContext) { - let app_state = action.0.clone(); - cx.prompt_for_paths( - PathPromptOptions { - files: true, - directories: true, - multiple: true, - }, - move |paths, cx| { - if let Some(paths) = paths { - cx.dispatch_global_action(OpenPaths(OpenParams { paths, app_state })); - } - }, - ); -} - -fn open_paths(action: &OpenPaths, cx: &mut MutableAppContext) -> Task<()> { - log::info!("open paths {:?}", action.0.paths); - - // Open paths in existing workspace if possible - for window_id in cx.window_ids().collect::>() { - if let Some(handle) = cx.root_view::(window_id) { - let task = handle.update(cx, |view, cx| { - if view.contains_paths(&action.0.paths, cx.as_ref()) { - log::info!("open paths on existing workspace"); - Some(view.open_paths(&action.0.paths, cx)) - } else { - None - } - }); - - if let Some(task) = task { - return task; - } - } - } - - log::info!("open new workspace"); - - // Add a new workspace if necessary - - let (_, workspace) = cx.add_window(window_options(), |cx| { - Workspace::new(&action.0.app_state, cx) - }); - workspace.update(cx, |workspace, cx| { - workspace.open_paths(&action.0.paths, cx) - }) -} - -fn open_new(action: &OpenNew, cx: &mut MutableAppContext) { - cx.add_window(window_options(), |cx| { - let mut view = Workspace::new(action.0.as_ref(), cx); - view.open_new_file(&action, cx); - view - }); -} - -fn window_options() -> WindowOptions<'static> { - WindowOptions { - bounds: RectF::new(vec2f(0., 0.), vec2f(1024., 768.)), - title: None, - titlebar_appears_transparent: true, - traffic_light_position: Some(vec2f(8., 8.)), - } -} - pub trait Item: Entity + Sized { type View: ItemView; @@ -398,33 +306,6 @@ impl Workspace { .detach(); cx.focus(&pane); - let mut left_sidebar = Sidebar::new(Side::Left); - left_sidebar.add_item( - "icons/folder-tree-16.svg", - ProjectPanel::new(project.clone(), app_state.settings.clone(), cx).into(), - ); - - let mut right_sidebar = Sidebar::new(Side::Right); - right_sidebar.add_item( - "icons/user-16.svg", - cx.add_view(|cx| { - PeoplePanel::new(app_state.user_store.clone(), app_state.settings.clone(), cx) - }) - .into(), - ); - right_sidebar.add_item( - "icons/comment-16.svg", - cx.add_view(|cx| { - ChatPanel::new( - app_state.client.clone(), - app_state.channel_list.clone(), - app_state.settings.clone(), - cx, - ) - }) - .into(), - ); - let mut current_user = app_state.user_store.read(cx).watch_current_user().clone(); let mut connection_status = app_state.client.status().clone(); let _observe_current_user = cx.spawn_weak(|this, mut cx| async move { @@ -451,8 +332,8 @@ impl Workspace { client: app_state.client.clone(), user_store: app_state.user_store.clone(), fs: app_state.fs.clone(), - left_sidebar, - right_sidebar, + left_sidebar: Sidebar::new(Side::Left), + right_sidebar: Sidebar::new(Side::Right), project, items: Default::default(), loading_items: Default::default(), @@ -460,6 +341,14 @@ impl Workspace { } } + pub fn left_sidebar_mut(&mut self) -> &mut Sidebar { + &mut self.left_sidebar + } + + pub fn right_sidebar_mut(&mut self) -> &mut Sidebar { + &mut self.right_sidebar + } + pub fn project(&self) -> &ModelHandle { &self.project } @@ -876,26 +765,6 @@ impl Workspace { }; } - fn share_worktree(&mut self, action: &ShareWorktree, cx: &mut ViewContext) { - self.project - .update(cx, |p, cx| p.share_worktree(action.0, cx)); - } - - fn unshare_worktree(&mut self, action: &UnshareWorktree, cx: &mut ViewContext) { - self.project - .update(cx, |p, cx| p.unshare_worktree(action.0, cx)); - } - - fn join_worktree(&mut self, action: &JoinWorktree, cx: &mut ViewContext) { - self.project - .update(cx, |p, cx| p.add_remote_worktree(action.0, cx).detach()); - } - - fn leave_worktree(&mut self, action: &LeaveWorktree, cx: &mut ViewContext) { - self.project - .update(cx, |p, cx| p.close_remote_worktree(action.0, cx)); - } - fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { let pane = cx.add_view(|_| Pane::new(self.settings.clone())); let pane_id = pane.id(); @@ -1166,72 +1035,6 @@ mod tests { use editor::{Editor, Insert}; use serde_json::json; use std::collections::HashSet; - use util::test::temp_tree; - - #[gpui::test] - async fn test_open_paths_action(mut cx: gpui::TestAppContext) { - let app_state = cx.update(test_app_state); - let dir = temp_tree(json!({ - "a": { - "aa": null, - "ab": null, - }, - "b": { - "ba": null, - "bb": null, - }, - "c": { - "ca": null, - "cb": null, - }, - })); - - cx.update(|cx| { - open_paths( - &OpenPaths(OpenParams { - paths: vec![ - dir.path().join("a").to_path_buf(), - dir.path().join("b").to_path_buf(), - ], - app_state: app_state.clone(), - }), - cx, - ) - }) - .await; - assert_eq!(cx.window_ids().len(), 1); - - cx.update(|cx| { - open_paths( - &OpenPaths(OpenParams { - paths: vec![dir.path().join("a").to_path_buf()], - app_state: app_state.clone(), - }), - cx, - ) - }) - .await; - assert_eq!(cx.window_ids().len(), 1); - let workspace_1 = cx.root_view::(cx.window_ids()[0]).unwrap(); - workspace_1.read_with(&cx, |workspace, cx| { - assert_eq!(workspace.worktrees(cx).len(), 2) - }); - - cx.update(|cx| { - open_paths( - &OpenPaths(OpenParams { - paths: vec![ - dir.path().join("b").to_path_buf(), - dir.path().join("c").to_path_buf(), - ], - app_state: app_state.clone(), - }), - cx, - ) - }) - .await; - assert_eq!(cx.window_ids().len(), 2); - } #[gpui::test] async fn test_open_entry(mut cx: gpui::TestAppContext) { @@ -1620,42 +1423,6 @@ mod tests { }); } - #[gpui::test] - async fn test_new_empty_workspace(mut cx: gpui::TestAppContext) { - cx.update(init); - - let app_state = cx.update(test_app_state); - cx.dispatch_global_action(OpenNew(app_state.clone())); - let window_id = *cx.window_ids().first().unwrap(); - let workspace = cx.root_view::(window_id).unwrap(); - let editor = workspace.update(&mut cx, |workspace, cx| { - workspace - .active_item(cx) - .unwrap() - .to_any() - .downcast::() - .unwrap() - }); - - editor.update(&mut cx, |editor, cx| { - assert!(editor.text(cx).is_empty()); - }); - - workspace.update(&mut cx, |workspace, cx| { - workspace.save_active_item(&Save, cx) - }); - - app_state.fs.as_fake().insert_dir("/root").await.unwrap(); - cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name"))); - - editor - .condition(&cx, |editor, cx| editor.title(cx) == "the-new-name") - .await; - editor.update(&mut cx, |editor, cx| { - assert!(!editor.is_dirty(cx)); - }); - } - #[gpui::test] async fn test_pane_actions(mut cx: gpui::TestAppContext) { cx.update(|cx| pane::init(cx));