diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index c9a89a6bba..13e49de1d3 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -1,9 +1,10 @@ use crate::{ - AnyWindowHandle, AppContext, Context, Handle, ModelContext, Result, ViewContext, WindowContext, + AnyWindowHandle, AppContext, Context, Handle, ModelContext, Result, Task, ViewContext, + WindowContext, }; use anyhow::anyhow; use parking_lot::Mutex; -use std::sync::Weak; +use std::{future::Future, sync::Weak}; #[derive(Clone)] pub struct AsyncAppContext(pub(crate) Weak>); @@ -99,6 +100,22 @@ impl AsyncAppContext { let mut app_context = app.lock(); app_context.update_window(handle.id, update) } + + pub fn spawn( + &self, + f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static, + ) -> Result> + where + Fut: Future + Send + 'static, + R: Send + 'static, + { + let app = self + .0 + .upgrade() + .ok_or_else(|| anyhow!("app was released"))?; + let app_context = app.lock(); + Ok(app_context.spawn(f)) + } } #[derive(Clone)] diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index 802e62d7b3..43a1afc3cc 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -1,7 +1,10 @@ use crate::{AppContext, PlatformDispatcher}; +use futures::channel::mpsc; use smol::prelude::*; use std::{ fmt::Debug, + marker::PhantomData, + mem, pin::Pin, sync::Arc, task::{Context, Poll}, @@ -133,7 +136,73 @@ impl Executor { futures::executor::block_on(future) } + pub async fn scoped<'scope, F>(&self, scheduler: F) + where + F: FnOnce(&mut Scope<'scope>), + { + let mut scope = Scope::new(self.clone()); + (scheduler)(&mut scope); + let spawned = mem::take(&mut scope.futures) + .into_iter() + .map(|f| self.spawn(f)) + .collect::>(); + for task in spawned { + task.await; + } + } + pub fn is_main_thread(&self) -> bool { self.dispatcher.is_main_thread() } } + +pub struct Scope<'a> { + executor: Executor, + futures: Vec + Send + 'static>>>, + tx: Option>, + rx: mpsc::Receiver<()>, + lifetime: PhantomData<&'a ()>, +} + +impl<'a> Scope<'a> { + fn new(executor: Executor) -> Self { + let (tx, rx) = mpsc::channel(1); + Self { + executor, + tx: Some(tx), + rx, + futures: Default::default(), + lifetime: PhantomData, + } + } + + pub fn spawn(&mut self, f: F) + where + F: Future + Send + 'a, + { + let tx = self.tx.clone().unwrap(); + + // Safety: The 'a lifetime is guaranteed to outlive any of these futures because + // dropping this `Scope` blocks until all of the futures have resolved. + let f = unsafe { + mem::transmute::< + Pin + Send + 'a>>, + Pin + Send + 'static>>, + >(Box::pin(async move { + f.await; + drop(tx); + })) + }; + self.futures.push(f); + } +} + +impl<'a> Drop for Scope<'a> { + fn drop(&mut self) { + self.tx.take().unwrap(); + + // Wait until the channel is closed, which means that all of the spawned + // futures have resolved. + self.executor.block(self.rx.next()); + } +} diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 1b6802ee5f..9580ad480c 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -9,7 +9,7 @@ use cli::{ }; use fs::RealFs; use futures::{channel::mpsc, SinkExt, StreamExt}; -use gpui2::{App, AsyncAppContext, Task}; +use gpui2::{App, AssetSource, AsyncAppContext, Task}; use log::LevelFilter; use parking_lot::Mutex; @@ -29,7 +29,10 @@ use std::{ }, thread, }; -use util::{channel::RELEASE_CHANNEL, http, paths, ResultExt}; +use util::{ + channel::{parse_zed_link, RELEASE_CHANNEL}, + http, paths, ResultExt, +}; use zed2::{ensure_only_instance, AppState, Assets, IsOnlyInstance}; // use zed2::{ // assets::Assets, @@ -59,8 +62,8 @@ fn main() { let fs = Arc::new(RealFs); let user_settings_file_rx = - watch_config_file(app.executor(), fs.clone(), paths::SETTINGS.clone()); - let user_keymap_file_rx = watch_config_file(app.executor(), fs.clone(), paths::KEYMAP.clone()); + watch_config_file(&app.executor(), fs.clone(), paths::SETTINGS.clone()); + let user_keymap_file_rx = watch_config_file(&app.executor(), fs.clone(), paths::KEYMAP.clone()); let login_shell_env_loaded = if stdout_is_a_pty() { Task::ready(()) @@ -194,7 +197,7 @@ fn main() { listener.open_urls(urls) } } else { - upload_previous_panics(http.clone(), cx); + // upload_previous_panics(http.clone(), cx); // TODO Development mode that forces the CLI mode usually runs Zed binary as is instead // of an *app, hence gets no specific callbacks run. Emulate them here, if needed. @@ -248,8 +251,15 @@ fn main() { // .detach(); } OpenRequest::CliConnection { connection } => { - cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) - .detach(); + if cx + .spawn(|cx| { + handle_cli_connection(connection, app_state.clone(), cx) + }) + .map(Task::detach) + .is_err() + { + break; + } } OpenRequest::JoinChannel { channel_id } => { // cx @@ -626,9 +636,10 @@ fn collect_url_args() -> Vec { } fn load_embedded_fonts(app: &App) { - let font_paths = Assets.list("fonts"); + let font_paths = Assets.list(&"fonts".into()).unwrap(); let embedded_fonts = Mutex::new(Vec::new()); - smol::block_on(app.background().scoped(|scope| { + let executor = app.executor(); + executor.block(executor.scoped(|scope| { for font_path in &font_paths { if !font_path.ends_with(".ttf") { continue; @@ -755,7 +766,7 @@ async fn handle_cli_connection( if let Some(request) = requests.next().await { match request { CliRequest::Open { paths, wait } => { - let mut caret_positions = HashMap::new(); + // let mut caret_positions = HashMap::new(); // todo!("workspace") // let paths = if paths.is_empty() {