From f7055c2accb22fbba4397afd0d194206d482902b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 20 Apr 2022 12:54:34 +0200 Subject: [PATCH] Implement `zed --wait` --- crates/cli/src/cli.rs | 1 + crates/cli/src/main.rs | 3 +- crates/gpui/src/app.rs | 8 +-- crates/journal/src/journal.rs | 2 +- crates/workspace/src/workspace.rs | 82 ++++++++++++++++++------------- crates/zed/src/main.rs | 80 ++++++++++++++++++++++++++++-- 6 files changed, 132 insertions(+), 44 deletions(-) diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index af7e78ea31..7cad42b534 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -15,6 +15,7 @@ pub enum CliRequest { #[derive(Debug, Serialize, Deserialize)] pub enum CliResponse { + Ping, Stdout { message: String }, Stderr { message: String }, Exit { status: i32 }, diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 159462fe57..9aac438724 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -34,11 +34,12 @@ fn main() -> Result<()> { .into_iter() .map(|path| fs::canonicalize(path).map_err(|error| anyhow!(error))) .collect::>>()?, - wait: false, + wait: args.wait, })?; while let Ok(response) = rx.recv() { match response { + CliResponse::Ping => {} CliResponse::Stdout { message } => println!("{message}"), CliResponse::Stderr { message } => eprintln!("{message}"), CliResponse::Exit { status } => std::process::exit(status), diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 57df8879f7..0f9a3041b3 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -733,7 +733,7 @@ type GlobalSubscriptionCallback = Box bool>; type FocusObservationCallback = Box bool>; type GlobalObservationCallback = Box; -type ReleaseObservationCallback = Box; +type ReleaseObservationCallback = Box; type DeserializeActionCallback = fn(json: &str) -> anyhow::Result>; pub struct MutableAppContext { @@ -1259,12 +1259,12 @@ impl MutableAppContext { } } - pub fn observe_release(&mut self, handle: &H, mut callback: F) -> Subscription + pub fn observe_release(&mut self, handle: &H, callback: F) -> Subscription where E: Entity, E::Event: 'static, H: Handle, - F: 'static + FnMut(&E, &mut Self), + F: 'static + FnOnce(&E, &mut Self), { let id = post_inc(&mut self.next_subscription_id); self.release_observations @@ -2211,7 +2211,7 @@ impl MutableAppContext { fn handle_entity_release_effect(&mut self, entity_id: usize, entity: &dyn Any) { let callbacks = self.release_observations.lock().remove(&entity_id); if let Some(callbacks) = callbacks { - for (_, mut callback) in callbacks { + for (_, callback) in callbacks { callback(entity, self); } } diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 910c2947b4..7aa8be4d97 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -43,7 +43,7 @@ pub fn new_journal_entry(app_state: Arc, cx: &mut MutableAppContext) { cx.spawn(|mut cx| { async move { let (journal_dir, entry_path) = create_entry.await?; - let workspace = cx + let (workspace, _) = cx .update(|cx| workspace::open_paths(&[journal_dir], &app_state, cx)) .await; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index cfff617ff4..62a79ff706 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -376,6 +376,11 @@ pub trait ItemHandle: 'static + fmt::Debug { -> Task>; fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option; fn to_followable_item_handle(&self, cx: &AppContext) -> Option>; + fn on_release( + &self, + cx: &mut MutableAppContext, + callback: Box, + ) -> gpui::Subscription; } pub trait WeakItemHandle { @@ -411,6 +416,12 @@ impl ItemHandle for ViewHandle { Box::new(self.clone()) } + fn set_nav_history(&self, nav_history: Rc>, cx: &mut MutableAppContext) { + self.update(cx, |item, cx| { + item.set_nav_history(ItemNavHistory::new(nav_history, &cx.handle()), cx); + }) + } + fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option> { self.update(cx, |item, cx| { cx.add_option_view(|cx| item.clone_on_split(cx)) @@ -418,12 +429,6 @@ impl ItemHandle for ViewHandle { .map(|handle| Box::new(handle) as Box) } - fn set_nav_history(&self, nav_history: Rc>, cx: &mut MutableAppContext) { - self.update(cx, |item, cx| { - item.set_nav_history(ItemNavHistory::new(nav_history, &cx.handle()), cx); - }) - } - fn added_to_pane( &self, workspace: &mut Workspace, @@ -512,6 +517,30 @@ impl ItemHandle for ViewHandle { self.update(cx, |this, cx| this.navigate(data, cx)) } + fn id(&self) -> usize { + self.id() + } + + fn to_any(&self) -> AnyViewHandle { + self.into() + } + + fn is_dirty(&self, cx: &AppContext) -> bool { + self.read(cx).is_dirty(cx) + } + + fn has_conflict(&self, cx: &AppContext) -> bool { + self.read(cx).has_conflict(cx) + } + + fn can_save(&self, cx: &AppContext) -> bool { + self.read(cx).can_save(cx) + } + + fn can_save_as(&self, cx: &AppContext) -> bool { + self.read(cx).can_save_as(cx) + } + fn save(&self, project: ModelHandle, cx: &mut MutableAppContext) -> Task> { self.update(cx, |item, cx| item.save(project, cx)) } @@ -533,30 +562,6 @@ impl ItemHandle for ViewHandle { self.update(cx, |item, cx| item.reload(project, cx)) } - fn is_dirty(&self, cx: &AppContext) -> bool { - self.read(cx).is_dirty(cx) - } - - fn has_conflict(&self, cx: &AppContext) -> bool { - self.read(cx).has_conflict(cx) - } - - fn id(&self) -> usize { - self.id() - } - - fn to_any(&self) -> AnyViewHandle { - self.into() - } - - fn can_save(&self, cx: &AppContext) -> bool { - self.read(cx).can_save(cx) - } - - fn can_save_as(&self, cx: &AppContext) -> bool { - self.read(cx).can_save_as(cx) - } - fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option { self.read(cx).act_as_type(type_id, self, cx) } @@ -570,6 +575,14 @@ impl ItemHandle for ViewHandle { None } } + + fn on_release( + &self, + cx: &mut MutableAppContext, + callback: Box, + ) -> gpui::Subscription { + cx.observe_release(self, move |_, cx| callback(cx)) + } } impl Into for Box { @@ -2102,7 +2115,10 @@ pub fn open_paths( abs_paths: &[PathBuf], app_state: &Arc, cx: &mut MutableAppContext, -) -> Task> { +) -> Task<( + ViewHandle, + Vec, Arc>>>, +)> { log::info!("open paths {:?}", abs_paths); // Open paths in existing workspace if possible @@ -2139,8 +2155,8 @@ pub fn open_paths( let task = workspace.update(cx, |workspace, cx| workspace.open_paths(abs_paths, cx)); cx.spawn(|_| async move { - task.await; - workspace + let items = task.await; + (workspace, items) }) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 6a6dc5ef49..5fbf634262 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -11,7 +11,7 @@ use client::{self, http, ChannelList, UserStore}; use fs::OpenOptions; use futures::{ channel::{mpsc, oneshot}, - SinkExt, StreamExt, + FutureExt, SinkExt, StreamExt, }; use gpui::{App, AssetSource, AsyncAppContext, Task}; use log::LevelFilter; @@ -19,7 +19,7 @@ use parking_lot::Mutex; use project::Fs; use settings::{self, KeymapFile, Settings, SettingsFileContent}; use smol::process::Command; -use std::{env, fs, path::PathBuf, sync::Arc, thread}; +use std::{env, fs, path::PathBuf, sync::Arc, thread, time::Duration}; use theme::{ThemeRegistry, DEFAULT_THEME_NAME}; use util::ResultExt; use workspace::{self, AppState, OpenNew, OpenPaths}; @@ -360,9 +360,79 @@ async fn handle_cli_connection( ) { if let Some(request) = requests.next().await { match request { - CliRequest::Open { paths, .. } => { - cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths, app_state })); - responses.send(CliResponse::Exit { status: 0 }).log_err(); + CliRequest::Open { paths, wait } => { + let (workspace, items) = cx + .update(|cx| workspace::open_paths(&paths, &app_state, cx)) + .await; + + let mut errored = false; + let mut futures = Vec::new(); + cx.update(|cx| { + for (item, path) in items.into_iter().zip(&paths) { + match item { + Some(Ok(item)) => { + let released = oneshot::channel(); + item.on_release( + cx, + Box::new(move |_| { + let _ = released.0.send(()); + }), + ) + .detach(); + futures.push(released.1); + } + Some(Err(err)) => { + responses + .send(CliResponse::Stderr { + message: format!("error opening {:?}: {}", path, err), + }) + .log_err(); + errored = true; + } + None => {} + } + } + }); + + if wait { + let background = cx.background(); + let wait = async move { + if paths.is_empty() { + let (done_tx, done_rx) = oneshot::channel(); + let _subscription = cx.update(|cx| { + cx.observe_release(&workspace, move |_, _| { + let _ = done_tx.send(()); + }) + }); + drop(workspace); + let _ = done_rx.await; + } else { + let _ = futures::future::try_join_all(futures).await; + }; + } + .fuse(); + futures::pin_mut!(wait); + + loop { + // Repeatedly check if CLI is still open to avoid wasting resources + // waiting for files or workspaces to close. + let mut timer = background.timer(Duration::from_secs(1)).fuse(); + futures::select_biased! { + _ = wait => break, + _ = timer => { + if responses.send(CliResponse::Ping).is_err() { + break; + } + } + } + } + } + + responses + .send(CliResponse::Exit { + status: if errored { 1 } else { 0 }, + }) + .log_err(); } } }