diff --git a/Cargo.lock b/Cargo.lock index 3d1af6fbb4..3b186a2893 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,17 +633,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "blake2b_simd" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "constant_time_eq", -] - [[package]] name = "blake3" version = "0.3.8" @@ -1452,6 +1441,15 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -1464,12 +1462,12 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" dependencies = [ "libc", - "redox_users 0.3.5", + "redox_users", "winapi", ] @@ -1480,7 +1478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users 0.4.0", + "redox_users", "winapi", ] @@ -2516,6 +2514,19 @@ dependencies = [ "libc", ] +[[package]] +name = "journal" +version = "0.1.0" +dependencies = [ + "chrono", + "dirs 4.0.0", + "editor", + "gpui", + "log", + "util", + "workspace", +] + [[package]] name = "jpeg-decoder" version = "0.1.22" @@ -3152,7 +3163,7 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.2.5", + "redox_syscall", "smallvec", "winapi", ] @@ -3720,12 +3731,6 @@ dependencies = [ "rand_core 0.3.1", ] -[[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - [[package]] name = "redox_syscall" version = "0.2.5" @@ -3735,17 +3740,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_users" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" -dependencies = [ - "getrandom 0.1.16", - "redox_syscall 0.1.57", - "rust-argon2", -] - [[package]] name = "redox_users" version = "0.4.0" @@ -3753,7 +3747,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ "getrandom 0.2.2", - "redox_syscall 0.2.5", + "redox_syscall", ] [[package]] @@ -3881,18 +3875,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rust-argon2" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" -dependencies = [ - "base64 0.13.0", - "blake2b_simd", - "constant_time_eq", - "crossbeam-utils", -] - [[package]] name = "rust-embed" version = "6.2.0" @@ -4545,7 +4527,7 @@ dependencies = [ "crossbeam-channel", "crossbeam-queue", "crossbeam-utils", - "dirs", + "dirs 3.0.1", "either", "futures-channel", "futures-core", @@ -4850,7 +4832,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "rand 0.8.3", - "redox_syscall 0.2.5", + "redox_syscall", "remove_dir_all", "winapi", ] @@ -5673,6 +5655,7 @@ dependencies = [ "gpui", "language", "log", + "parking_lot", "postage", "project", "serde_json", @@ -5718,7 +5701,7 @@ dependencies = [ "crossbeam-channel", "ctor", "diagnostics", - "dirs", + "dirs 3.0.1", "easy-parallel", "editor", "env_logger", @@ -5732,6 +5715,7 @@ dependencies = [ "ignore", "image", "indexmap", + "journal", "language", "lazy_static", "libc", diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 26a1fcd494..8992772577 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1257,7 +1257,7 @@ impl Editor { } } - fn insert(&mut self, text: &str, cx: &mut ViewContext) { + pub fn insert(&mut self, text: &str, cx: &mut ViewContext) { self.start_transaction(cx); let old_selections = self.local_selections::(cx); let new_selections = self.buffer.update(cx, |buffer, cx| { diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 38c3aba045..dd9e09f663 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -667,7 +667,7 @@ pub struct MutableAppContext { assets: Arc, cx: AppContext, actions: HashMap>>>, - global_actions: HashMap>>, + global_actions: HashMap>, keystroke_matcher: keymap::Matcher, next_entity_id: usize, next_window_id: usize, @@ -838,10 +838,13 @@ impl MutableAppContext { handler(action, cx); }); - self.global_actions - .entry(TypeId::of::()) - .or_default() - .push(handler); + if self + .global_actions + .insert(TypeId::of::(), handler) + .is_some() + { + panic!("registered multiple global handlers for the same action type"); + } } pub fn window_ids(&self) -> impl Iterator + '_ { @@ -1125,7 +1128,7 @@ impl MutableAppContext { } if !halted_dispatch { - this.dispatch_global_action_any(action); + halted_dispatch = this.dispatch_global_action_any(action); } halted_dispatch }) @@ -1135,13 +1138,14 @@ impl MutableAppContext { self.dispatch_global_action_any(&action); } - fn dispatch_global_action_any(&mut self, action: &dyn AnyAction) { + fn dispatch_global_action_any(&mut self, action: &dyn AnyAction) -> bool { self.update(|this| { - if let Some((name, mut handlers)) = this.global_actions.remove_entry(&action.id()) { - for handler in handlers.iter_mut().rev() { - handler(action, this); - } - this.global_actions.insert(name, handlers); + if let Some((name, mut handler)) = this.global_actions.remove_entry(&action.id()) { + handler(action, this); + this.global_actions.insert(name, handler); + true + } else { + false } }) } @@ -3988,12 +3992,7 @@ mod tests { let actions_clone = actions.clone(); cx.add_global_action(move |_: &Action, _: &mut MutableAppContext| { - actions_clone.borrow_mut().push("global a".to_string()); - }); - - let actions_clone = actions.clone(); - cx.add_global_action(move |_: &Action, _: &mut MutableAppContext| { - actions_clone.borrow_mut().push("global b".to_string()); + actions_clone.borrow_mut().push("global".to_string()); }); let actions_clone = actions.clone(); @@ -4049,7 +4048,7 @@ mod tests { assert_eq!( *actions.borrow(), - vec!["4 d", "4 c", "3 b", "3 a", "2 d", "2 c", "global b", "global a"] + vec!["4 d", "4 c", "3 b", "3 a", "2 d", "2 c", "global"] ); } diff --git a/crates/journal/Cargo.toml b/crates/journal/Cargo.toml new file mode 100644 index 0000000000..b2c470f4c5 --- /dev/null +++ b/crates/journal/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "journal" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/journal.rs" + +[dependencies] +editor = { path = "../editor" } +gpui = { path = "../gpui" } +util = { path = "../util" } +workspace = { path = "../workspace" } +chrono = "0.4" +dirs = "4.0" +log = "0.4" \ No newline at end of file diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs new file mode 100644 index 0000000000..8adda8e1d3 --- /dev/null +++ b/crates/journal/src/journal.rs @@ -0,0 +1,75 @@ +use chrono::{Datelike, Local, Timelike}; +use editor::{Autoscroll, Editor}; +use gpui::{action, keymap::Binding, MutableAppContext}; +use std::{fs::OpenOptions, sync::Arc}; +use util::TryFutureExt as _; +use workspace::AppState; + +action!(NewJournalEntry); + +pub fn init(app_state: Arc, cx: &mut MutableAppContext) { + cx.add_bindings(vec![Binding::new("ctrl-alt-cmd-j", NewJournalEntry, None)]); + cx.add_global_action(move |_: &NewJournalEntry, cx| new_journal_entry(app_state.clone(), cx)); +} + +pub fn new_journal_entry(app_state: Arc, cx: &mut MutableAppContext) { + let now = Local::now(); + let home_dir = match dirs::home_dir() { + Some(home_dir) => home_dir, + None => { + log::error!("can't determine home directory"); + return; + } + }; + + let journal_dir = home_dir.join("journal"); + let month_dir = journal_dir + .join(format!("{:02}", now.year())) + .join(format!("{:02}", now.month())); + let entry_path = month_dir.join(format!("{:02}.md", now.day())); + let now = now.time(); + let (pm, hour) = now.hour12(); + let am_or_pm = if pm { "PM" } else { "AM" }; + let entry_heading = format!("# {}:{:02} {}\n\n", hour, now.minute(), am_or_pm); + + let create_entry = cx.background().spawn(async move { + std::fs::create_dir_all(month_dir)?; + OpenOptions::new() + .create(true) + .write(true) + .open(&entry_path)?; + Ok::<_, std::io::Error>((journal_dir, entry_path)) + }); + + cx.spawn(|mut cx| { + async move { + let (journal_dir, entry_path) = create_entry.await?; + let workspace = cx + .update(|cx| workspace::open_paths(&[journal_dir], &app_state, cx)) + .await; + + let opened = workspace + .update(&mut cx, |workspace, cx| { + workspace.open_paths(&[entry_path], cx) + }) + .await; + + if let Some(Some(Ok(item))) = opened.first() { + if let Some(editor) = item.to_any().downcast::() { + editor.update(&mut cx, |editor, cx| { + let len = editor.buffer().read(cx).read(cx).len(); + editor.select_ranges([len..len], Some(Autoscroll::Center), cx); + if len > 0 { + editor.insert("\n\n", cx); + } + editor.insert(&entry_heading, cx); + }); + } + } + + Ok(()) + } + .log_err() + }) + .detach(); +} diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index b0b07e7bae..faed6e5a39 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1174,7 +1174,8 @@ mod tests { ) }) .unwrap() - .await; + .await + .unwrap(); workspace_b.read_with(&cx_b, |workspace, cx| { let active_pane = workspace.active_pane().read(cx); assert!(active_pane.active_item().is_some()); diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 005d66ad41..67ad7899ea 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -11,7 +11,7 @@ use parking_lot::Mutex; use postage::watch; use std::{cmp, sync::Arc}; use theme::ThemeRegistry; -use workspace::{Settings, Workspace}; +use workspace::{Settings, Workspace, AppState}; #[derive(Clone)] pub struct ThemeSelectorParams { @@ -317,3 +317,13 @@ impl View for ThemeSelector { cx } } + +impl<'a> From<&'a AppState> for ThemeSelectorParams { + fn from(state: &'a AppState) -> Self { + Self { + settings_tx: state.settings_tx.clone(), + settings: state.settings.clone(), + themes: state.themes.clone(), + } + } +} diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index 66a36889f5..b0c66b005b 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -43,7 +43,10 @@ pub trait ResultExt { fn warn_on_err(self) -> Option; } -impl ResultExt for anyhow::Result { +impl ResultExt for Result +where + E: std::fmt::Debug, +{ type Ok = T; fn log_err(self) -> Option { diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index a5ca3c91e9..497254074e 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -17,6 +17,7 @@ project = { path = "../project" } theme = { path = "../theme" } anyhow = "1.0.38" log = "0.4" +parking_lot = "0.11.1" postage = { version = "0.4.1", features = ["futures-traits"] } [dev-dependencies] diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index a13602016a..5a3411c926 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -107,15 +107,15 @@ impl Pane { &mut self, project_path: ProjectPath, cx: &mut ViewContext, - ) -> bool { + ) -> Option> { if let Some(index) = self.items.iter().position(|item| { item.project_path(cx.as_ref()) .map_or(false, |item_path| item_path == project_path) }) { self.activate_item(index, cx); - true + self.items.get(index).map(|handle| handle.boxed_clone()) } else { - false + None } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 090aae5567..747a914ffe 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4,7 +4,7 @@ pub mod settings; pub mod sidebar; mod status_bar; -use anyhow::Result; +use anyhow::{anyhow, Result}; use client::{Authenticate, ChannelList, Client, User, UserStore}; use gpui::{ action, @@ -13,16 +13,18 @@ use gpui::{ geometry::{vector::vec2f, PathBuilder}, json::{self, to_string_pretty, ToJson}, keymap::Binding, - platform::CursorStyle, + platform::{CursorStyle, WindowOptions}, AnyViewHandle, AppContext, ClipboardItem, Entity, ModelContext, ModelHandle, MutableAppContext, - PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, WeakModelHandle, + PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, + WeakModelHandle, }; use language::LanguageRegistry; use log::error; pub use pane::*; pub use pane_group::*; +use parking_lot::Mutex; use postage::{prelude::Stream, watch}; -use project::{Fs, Project, ProjectPath, Worktree}; +use project::{fs, Fs, Project, ProjectPath, Worktree}; pub use settings::Settings; use sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItemFocus}; use status_bar::StatusBar; @@ -32,13 +34,23 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use theme::Theme; +use theme::{Theme, ThemeRegistry}; -action!(OpenNew, WorkspaceParams); +action!(Open, Arc); +action!(OpenNew, Arc); +action!(OpenPaths, OpenParams); action!(Save); action!(DebugElements); pub fn init(cx: &mut MutableAppContext) { + cx.add_global_action(open); + cx.add_global_action(move |action: &OpenPaths, cx: &mut MutableAppContext| { + open_paths(&action.0.paths, &action.0.app_state, cx).detach() + }); + cx.add_global_action(move |action: &OpenNew, cx: &mut MutableAppContext| { + open_new(&action.0, cx) + }); + cx.add_action(Workspace::save_active_item); cx.add_action(Workspace::debug_elements); cx.add_action(Workspace::toggle_sidebar_item); @@ -66,6 +78,27 @@ pub fn init(cx: &mut MutableAppContext) { pane::init(cx); } +pub struct AppState { + pub settings_tx: Arc>>, + pub settings: watch::Receiver, + pub languages: Arc, + pub themes: Arc, + pub client: Arc, + pub user_store: ModelHandle, + pub fs: Arc, + pub channel_list: ModelHandle, + pub entry_openers: Arc<[Box]>, + pub build_window_options: &'static dyn Fn() -> WindowOptions<'static>, + pub build_workspace: + &'static dyn Fn(&WorkspaceParams, &mut ViewContext) -> Workspace, +} + +#[derive(Clone)] +pub struct OpenParams { + pub paths: Vec, + pub app_state: Arc, +} + pub trait EntryOpener { fn open( &self, @@ -468,7 +501,11 @@ impl Workspace { } } - pub fn open_paths(&mut self, abs_paths: &[PathBuf], cx: &mut ViewContext) -> Task<()> { + pub fn open_paths( + &mut self, + abs_paths: &[PathBuf], + cx: &mut ViewContext, + ) -> Task, Arc>>>> { let entries = abs_paths .iter() .cloned() @@ -484,26 +521,26 @@ impl Workspace { cx.spawn(|this, mut cx| { let fs = fs.clone(); async move { - let project_path = project_path.await?; + let project_path = project_path.await.ok()?; if fs.is_file(&abs_path).await { if let Some(entry) = this.update(&mut cx, |this, cx| this.open_entry(project_path, cx)) { - entry.await; + return Some(entry.await); } } - Ok(()) + None } }) }) - .collect::>>>(); + .collect::>(); cx.foreground().spawn(async move { + let mut items = Vec::new(); for task in tasks { - if let Err(error) = task.await { - log::error!("error opening paths {}", error); - } + items.push(task.await); } + items }) } @@ -595,10 +632,12 @@ impl Workspace { &mut self, project_path: ProjectPath, cx: &mut ViewContext, - ) -> Option> { + ) -> Option, Arc>>> { let pane = self.active_pane().clone(); - if self.activate_or_open_existing_entry(project_path.clone(), &pane, cx) { - return None; + if let Some(existing_item) = + self.activate_or_open_existing_entry(project_path.clone(), &pane, cx) + { + return Some(cx.foreground().spawn(async move { Ok(existing_item) })); } let worktree = match self @@ -629,20 +668,20 @@ impl Workspace { Some(cx.spawn(|this, mut cx| async move { let load_result = task.await; this.update(&mut cx, |this, cx| { - if let Some(pane) = pane.upgrade(&cx) { - match load_result { - Ok(item) => { - // By the time loading finishes, the entry could have been already added - // to the pane. If it was, we activate it, otherwise we'll store the - // item and add a new view for it. - if !this.activate_or_open_existing_entry(project_path, &pane, cx) { - this.add_item(item, cx); - } - } - Err(error) => { - log::error!("error opening item: {}", error); - } - } + let pane = pane + .upgrade(&cx) + .ok_or_else(|| anyhow!("could not upgrade pane reference"))?; + let item = load_result?; + + // By the time loading finishes, the entry could have been already added + // to the pane. If it was, we activate it, otherwise we'll store the + // item and add a new view for it. + if let Some(existing) = + this.activate_or_open_existing_entry(project_path, &pane, cx) + { + Ok(existing) + } else { + Ok(this.add_item(item, cx)) } }) })) @@ -653,11 +692,13 @@ impl Workspace { project_path: ProjectPath, pane: &ViewHandle, cx: &mut ViewContext, - ) -> bool { + ) -> Option> { // If the pane contains a view for this file, then activate // that item view. - if pane.update(cx, |pane, cx| pane.activate_entry(project_path.clone(), cx)) { - return true; + if let Some(existing_item_view) = + pane.update(cx, |pane, cx| pane.activate_entry(project_path.clone(), cx)) + { + return Some(existing_item_view); } // Otherwise, if this file is already open somewhere in the workspace, @@ -680,10 +721,10 @@ impl Workspace { } }); if let Some(view) = view_for_existing_item { - pane.add_item_view(view, cx.as_mut()); - true + pane.add_item_view(view.boxed_clone(), cx.as_mut()); + Some(view) } else { - false + None } } @@ -830,13 +871,19 @@ impl Workspace { pane } - pub fn add_item(&mut self, item_handle: T, cx: &mut ViewContext) + pub fn add_item( + &mut self, + item_handle: T, + cx: &mut ViewContext, + ) -> Box where T: ItemHandle, { let view = item_handle.add_view(cx.window_id(), self.settings.clone(), cx); self.items.push(item_handle.downgrade()); - self.active_pane().add_item_view(view, cx.as_mut()); + self.active_pane() + .add_item_view(view.boxed_clone(), cx.as_mut()); + view } fn activate_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { @@ -1215,3 +1262,86 @@ impl Element for AvatarRibbon { }) } } + +impl std::fmt::Debug for OpenParams { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("OpenParams") + .field("paths", &self.paths) + .finish() + } +} + +impl<'a> From<&'a AppState> for WorkspaceParams { + fn from(state: &'a AppState) -> Self { + Self { + client: state.client.clone(), + fs: state.fs.clone(), + languages: state.languages.clone(), + settings: state.settings.clone(), + user_store: state.user_store.clone(), + channel_list: state.channel_list.clone(), + entry_openers: state.entry_openers.clone(), + } + } +} + +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 })); + } + }, + ); +} + +pub fn open_paths( + abs_paths: &[PathBuf], + app_state: &Arc, + cx: &mut MutableAppContext, +) -> Task> { + log::info!("open paths {:?}", abs_paths); + + // Open paths in existing workspace if possible + let mut existing = None; + for window_id in cx.window_ids().collect::>() { + if let Some(workspace) = cx.root_view::(window_id) { + if workspace.update(cx, |view, cx| { + if view.contains_paths(abs_paths, cx.as_ref()) { + existing = Some(workspace.clone()); + true + } else { + false + } + }) { + break; + } + } + } + + let workspace = existing.unwrap_or_else(|| { + cx.add_window((app_state.build_window_options)(), |cx| { + (app_state.build_workspace)(&WorkspaceParams::from(app_state.as_ref()), cx) + }) + .1 + }); + + let task = workspace.update(cx, |workspace, cx| workspace.open_paths(abs_paths, cx)); + cx.spawn(|_| async move { + task.await; + workspace + }) +} + +fn open_new(app_state: &Arc, cx: &mut MutableAppContext) { + let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| { + (app_state.build_workspace)(&app_state.as_ref().into(), cx) + }); + cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone())); +} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 275f8b0deb..452122d55a 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -28,24 +28,25 @@ test-support = [ ] [dependencies] -text = { path = "../text" } chat_panel = { path = "../chat_panel" } client = { path = "../client" } clock = { path = "../clock" } +contacts_panel = { path = "../contacts_panel" } diagnostics = { path = "../diagnostics" } -fsevent = { path = "../fsevent" } -fuzzy = { path = "../fuzzy" } editor = { path = "../editor" } file_finder = { path = "../file_finder" } +fsevent = { path = "../fsevent" } +fuzzy = { path = "../fuzzy" } go_to_line = { path = "../go_to_line" } gpui = { path = "../gpui" } +journal = { path = "../journal" } language = { path = "../language" } lsp = { path = "../lsp" } -contacts_panel = { path = "../contacts_panel" } project = { path = "../project" } project_panel = { path = "../project_panel" } rpc = { path = "../rpc" } sum_tree = { path = "../sum_tree" } +text = { path = "../text" } theme = { path = "../theme" } theme_selector = { path = "../theme_selector" } util = { path = "../util" } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 3847244ba6..1b578598d1 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -9,8 +9,10 @@ use parking_lot::Mutex; use simplelog::SimpleLogger; use std::{fs, path::PathBuf, sync::Arc}; use theme::{ThemeRegistry, DEFAULT_THEME_NAME}; -use workspace::{self, settings, OpenNew, Settings}; -use zed::{self, assets::Assets, fs::RealFs, language, menus, AppState, OpenParams, OpenPaths}; +use workspace::{self, settings, AppState, OpenNew, OpenParams, OpenPaths, Settings}; +use zed::{ + self, assets::Assets, build_window_options, build_workspace, fs::RealFs, language, menus, +}; fn main() { init_logger(); @@ -72,7 +74,10 @@ fn main() { user_store, fs: Arc::new(RealFs), entry_openers: Arc::from(entry_openers), + build_window_options: &build_window_options, + build_workspace: &build_workspace, }); + journal::init(app_state.clone(), cx); zed::init(&app_state, cx); theme_selector::init(app_state.as_ref().into(), cx); @@ -84,7 +89,7 @@ fn main() { let paths = collect_path_args(); if paths.is_empty() { - cx.dispatch_global_action(OpenNew(app_state.as_ref().into())); + cx.dispatch_global_action(OpenNew(app_state.clone())); } else { cx.dispatch_global_action(OpenPaths(OpenParams { paths, app_state })); } diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 25a4b5e6f9..33ac76e63c 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -1,19 +1,9 @@ -use crate::{AppState, WorkspaceParams}; +use crate::AppState; use gpui::{Menu, MenuItem}; use std::sync::Arc; #[cfg(target_os = "macos")] pub fn menus(state: &Arc) -> Vec> { - let workspace_params = WorkspaceParams { - client: state.client.clone(), - fs: state.fs.clone(), - languages: state.languages.clone(), - settings: state.settings.clone(), - user_store: state.user_store.clone(), - channel_list: state.channel_list.clone(), - entry_openers: state.entry_openers.clone(), - }; - vec![ Menu { name: "Zed", @@ -37,13 +27,13 @@ pub fn menus(state: &Arc) -> Vec> { MenuItem::Action { name: "New", keystroke: Some("cmd-n"), - action: Box::new(workspace::OpenNew(workspace_params)), + action: Box::new(workspace::OpenNew(state.clone())), }, MenuItem::Separator, MenuItem::Action { name: "Open…", keystroke: Some("cmd-o"), - action: Box::new(crate::Open(state.clone())), + action: Box::new(workspace::Open(state.clone())), }, ], }, diff --git a/crates/zed/src/test.rs b/crates/zed/src/test.rs index 91f1c165ee..fa540ae72a 100644 --- a/crates/zed/src/test.rs +++ b/crates/zed/src/test.rs @@ -1,4 +1,4 @@ -use crate::{assets::Assets, AppState}; +use crate::{assets::Assets, build_window_options, build_workspace, AppState}; use client::{http::ServerResponse, test::FakeHttpClient, ChannelList, Client, UserStore}; use gpui::{AssetSource, MutableAppContext}; use language::LanguageRegistry; @@ -42,6 +42,8 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { user_store, fs: Arc::new(FakeFs::new()), entry_openers: Arc::from(entry_openers), + build_window_options: &build_window_options, + build_workspace: &build_workspace, }) } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 16f67fa171..ad543ee166 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -4,7 +4,6 @@ pub mod menus; #[cfg(any(test, feature = "test-support"))] pub mod test; -use self::language::LanguageRegistry; use chat_panel::ChatPanel; pub use client; pub use contacts_panel; @@ -15,53 +14,23 @@ use gpui::{ geometry::vector::vec2f, keymap::Binding, platform::{WindowBounds, WindowOptions}, - ModelHandle, MutableAppContext, PathPromptOptions, Task, ViewContext, + ViewContext, }; pub use lsp; -use parking_lot::Mutex; -use postage::watch; pub use project::{self, fs}; use project_panel::ProjectPanel; -use std::{path::PathBuf, sync::Arc}; -use theme::ThemeRegistry; -use theme_selector::ThemeSelectorParams; +use std::sync::Arc; pub use workspace; -use workspace::{OpenNew, Settings, Workspace, WorkspaceParams}; +use workspace::{AppState, Workspace, WorkspaceParams}; action!(About); -action!(Open, Arc); -action!(OpenPaths, OpenParams); action!(Quit); action!(AdjustBufferFontSize, f32); const MIN_FONT_SIZE: f32 = 6.0; -pub struct AppState { - pub settings_tx: Arc>>, - pub settings: watch::Receiver, - pub languages: Arc, - pub themes: Arc, - pub client: Arc, - pub user_store: ModelHandle, - pub fs: Arc, - pub channel_list: ModelHandle, - pub entry_openers: Arc<[Box]>, -} - -#[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({ let settings_tx = app_state.settings_tx.clone(); @@ -79,63 +48,7 @@ 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 app_state = &action.0.app_state; - let (_, workspace) = cx.add_window(window_options(), |cx| { - build_workspace(&WorkspaceParams::from(app_state.as_ref()), cx) - }); - // cx.resize_window(window_id); - workspace.update(cx, |workspace, cx| { - workspace.open_paths(&action.0.paths, cx) - }) -} - -fn open_new(action: &OpenNew, cx: &mut MutableAppContext) { - let (window_id, workspace) = - cx.add_window(window_options(), |cx| build_workspace(&action.0, cx)); - cx.dispatch_action(window_id, vec![workspace.id()], action); -} - -fn build_workspace(params: &WorkspaceParams, cx: &mut ViewContext) -> Workspace { +pub fn build_workspace(params: &WorkspaceParams, cx: &mut ViewContext) -> Workspace { let mut workspace = Workspace::new(params, cx); let project = workspace.project().clone(); workspace.left_sidebar_mut().add_item( @@ -174,7 +87,7 @@ fn build_workspace(params: &WorkspaceParams, cx: &mut ViewContext) -> workspace } -fn window_options() -> WindowOptions<'static> { +pub fn build_window_options() -> WindowOptions<'static> { WindowOptions { bounds: WindowBounds::Maximized, title: None, @@ -187,41 +100,23 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { cx.platform().quit(); } -impl<'a> From<&'a AppState> for WorkspaceParams { - fn from(state: &'a AppState) -> Self { - Self { - client: state.client.clone(), - fs: state.fs.clone(), - languages: state.languages.clone(), - settings: state.settings.clone(), - user_store: state.user_store.clone(), - channel_list: state.channel_list.clone(), - entry_openers: state.entry_openers.clone(), - } - } -} - -impl<'a> From<&'a AppState> for ThemeSelectorParams { - fn from(state: &'a AppState) -> Self { - Self { - settings_tx: state.settings_tx.clone(), - settings: state.settings.clone(), - themes: state.themes.clone(), - } - } -} - #[cfg(test)] mod tests { use super::*; use editor::Editor; + use gpui::MutableAppContext; use project::ProjectPath; use serde_json::json; - use std::{collections::HashSet, path::Path}; + use std::{ + collections::HashSet, + path::{Path, PathBuf}, + }; use test::test_app_state; use theme::DEFAULT_THEME_NAME; use util::test::temp_tree; - use workspace::{pane, ItemView, ItemViewHandle, SplitDirection, WorkspaceHandle}; + use workspace::{ + open_paths, pane, ItemView, ItemViewHandle, OpenNew, SplitDirection, WorkspaceHandle, + }; #[gpui::test] async fn test_open_paths_action(mut cx: gpui::TestAppContext) { @@ -243,29 +138,19 @@ mod tests { 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(), - }), + &[ + dir.path().join("a").to_path_buf(), + dir.path().join("b").to_path_buf(), + ], + &app_state, 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; + cx.update(|cx| open_paths(&[dir.path().join("a").to_path_buf()], &app_state, 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| { @@ -274,13 +159,11 @@ mod tests { 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(), - }), + &[ + dir.path().join("b").to_path_buf(), + dir.path().join("c").to_path_buf(), + ], + &app_state, cx, ) }) @@ -291,8 +174,10 @@ mod tests { #[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.as_ref().into())); + cx.update(|cx| { + workspace::init(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| { @@ -357,10 +242,11 @@ mod tests { let file3 = entries[2].clone(); // Open the first entry - workspace + let entry_1 = workspace .update(&mut cx, |w, cx| w.open_entry(file1.clone(), cx)) .unwrap() - .await; + .await + .unwrap(); cx.read(|cx| { let pane = workspace.read(cx).active_pane().read(cx); assert_eq!( @@ -374,7 +260,8 @@ mod tests { workspace .update(&mut cx, |w, cx| w.open_entry(file2.clone(), cx)) .unwrap() - .await; + .await + .unwrap(); cx.read(|cx| { let pane = workspace.read(cx).active_pane().read(cx); assert_eq!( @@ -385,9 +272,12 @@ mod tests { }); // Open the first entry again. The existing pane item is activated. - workspace.update(&mut cx, |w, cx| { - assert!(w.open_entry(file1.clone(), cx).is_none()) - }); + let entry_1b = workspace + .update(&mut cx, |w, cx| w.open_entry(file1.clone(), cx).unwrap()) + .await + .unwrap(); + assert_eq!(entry_1.id(), entry_1b.id()); + cx.read(|cx| { let pane = workspace.read(cx).active_pane().read(cx); assert_eq!( @@ -398,9 +288,15 @@ mod tests { }); // Split the pane with the first entry, then open the second entry again. - workspace.update(&mut cx, |w, cx| { - w.split_pane(w.active_pane().clone(), SplitDirection::Right, cx); - assert!(w.open_entry(file2.clone(), cx).is_none()); + workspace + .update(&mut cx, |w, cx| { + w.split_pane(w.active_pane().clone(), SplitDirection::Right, cx); + w.open_entry(file2.clone(), cx).unwrap() + }) + .await + .unwrap(); + + workspace.read_with(&cx, |w, cx| { assert_eq!( w.active_pane() .read(cx) @@ -418,8 +314,8 @@ mod tests { w.open_entry(file3.clone(), cx).unwrap(), ) }); - t1.await; - t2.await; + t1.await.unwrap(); + t2.await.unwrap(); cx.read(|cx| { let pane = workspace.read(cx).active_pane().read(cx); assert_eq!( @@ -576,7 +472,7 @@ mod tests { }); // Create a new untitled buffer - cx.dispatch_action(window_id, vec![workspace.id()], OpenNew(params.clone())); + cx.dispatch_action(window_id, vec![workspace.id()], OpenNew(app_state.clone())); let editor = workspace.read_with(&cx, |workspace, cx| { workspace .active_item(cx) @@ -639,19 +535,22 @@ mod tests { // Open the same newly-created file in another pane item. The new editor should reuse // the same buffer. - cx.dispatch_action(window_id, vec![workspace.id()], OpenNew(params.clone())); - workspace.update(&mut cx, |workspace, cx| { - workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx); - assert!(workspace - .open_entry( - ProjectPath { - worktree_id: worktree.id(), - path: Path::new("the-new-name.rs").into() - }, - cx - ) - .is_none()); - }); + cx.dispatch_action(window_id, vec![workspace.id()], OpenNew(app_state.clone())); + workspace + .update(&mut cx, |workspace, cx| { + workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx); + workspace + .open_entry( + ProjectPath { + worktree_id: worktree.id(), + path: Path::new("the-new-name.rs").into(), + }, + cx, + ) + .unwrap() + }) + .await + .unwrap(); let editor2 = workspace.update(&mut cx, |workspace, cx| { workspace .active_item(cx) @@ -675,7 +574,7 @@ mod tests { let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); // Create a new untitled buffer - cx.dispatch_action(window_id, vec![workspace.id()], OpenNew(params.clone())); + cx.dispatch_action(window_id, vec![workspace.id()], OpenNew(app_state.clone())); let editor = workspace.read_with(&cx, |workspace, cx| { workspace .active_item(cx) @@ -747,7 +646,8 @@ mod tests { workspace .update(&mut cx, |w, cx| w.open_entry(file1.clone(), cx)) .unwrap() - .await; + .await + .unwrap(); cx.read(|cx| { assert_eq!( pane_1.read(cx).active_item().unwrap().project_path(cx),