WIP

Antonio Scandurra created

Change summary

crates/gpui2/src/app/async_context.rs | 21 +++++++
crates/gpui2/src/executor.rs          | 69 +++++++++++++++++++++++++++++
crates/zed2/src/main.rs               | 31 ++++++++----
3 files changed, 109 insertions(+), 12 deletions(-)

Detailed changes

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<Mutex<AppContext>>);
@@ -99,6 +100,22 @@ impl AsyncAppContext {
         let mut app_context = app.lock();
         app_context.update_window(handle.id, update)
     }
+
+    pub fn spawn<Fut, R>(
+        &self,
+        f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static,
+    ) -> Result<Task<R>>
+    where
+        Fut: Future<Output = R> + 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)]

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::<Vec<_>>();
+        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<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
+    tx: Option<mpsc::Sender<()>>,
+    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<F>(&mut self, f: F)
+    where
+        F: Future<Output = ()> + 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<Box<dyn Future<Output = ()> + Send + 'a>>,
+                Pin<Box<dyn Future<Output = ()> + 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());
+    }
+}

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<String> {
 }
 
 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() {