Merge pull request #2261 from zed-industries/async-language-loading

Antonio Scandurra created

Allow waiting for language to be loaded in LanguageRegistry APIs

Change summary

Cargo.lock                             |   1 
crates/client/src/telemetry.rs         |   4 
crates/collab_ui/src/contact_finder.rs |   2 
crates/editor/src/hover_popover.rs     |  11 
crates/feedback/src/feedback_editor.rs |  33 ++--
crates/journal/Cargo.toml              |   1 
crates/journal/src/journal.rs          |   2 
crates/language/src/buffer_tests.rs    |  54 +++++--
crates/language/src/language.rs        |  51 +++++--
crates/language/src/syntax_map.rs      |  30 ++-
crates/lsp/src/lsp.rs                  | 191 +++++++++++++++------------
crates/project/src/project.rs          |  24 ++
crates/util/src/util.rs                |  40 +++++
crates/zed/src/zed.rs                  | 126 ++++++++++--------
14 files changed, 352 insertions(+), 218 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3157,6 +3157,7 @@ dependencies = [
 name = "journal"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
  "chrono",
  "dirs 4.0.0",
  "editor",

crates/client/src/telemetry.rs 🔗

@@ -224,7 +224,7 @@ impl Telemetry {
                             .header("Content-Type", "application/json")
                             .body(json_bytes.into())?;
                         this.http_client.send(request).await?;
-                        Ok(())
+                        anyhow::Ok(())
                     }
                     .log_err(),
                 )
@@ -320,7 +320,7 @@ impl Telemetry {
                             .header("Content-Type", "application/json")
                             .body(json_bytes.into())?;
                         this.http_client.send(request).await?;
-                        Ok(())
+                        anyhow::Ok(())
                     }
                     .log_err(),
                 )

crates/collab_ui/src/contact_finder.rs 🔗

@@ -68,7 +68,7 @@ impl PickerDelegate for ContactFinder {
                     this.potential_contacts = potential_contacts.into();
                     cx.notify();
                 });
-                Ok(())
+                anyhow::Ok(())
             }
             .log_err()
             .await;

crates/editor/src/hover_popover.rs 🔗

@@ -1,3 +1,4 @@
+use futures::FutureExt;
 use gpui::{
     actions,
     elements::{Flex, MouseEventHandler, Padding, Text},
@@ -327,12 +328,10 @@ impl InfoPopover {
         MouseEventHandler::<InfoPopover>::new(0, cx, |_, cx| {
             let mut flex = Flex::new(Axis::Vertical).scrollable::<HoverBlock, _>(1, None, cx);
             flex.extend(self.contents.iter().map(|content| {
-                let project = self.project.read(cx);
-                if let Some(language) = content
-                    .language
-                    .clone()
-                    .and_then(|language| project.languages().language_for_name(&language))
-                {
+                let languages = self.project.read(cx).languages();
+                if let Some(language) = content.language.clone().and_then(|language| {
+                    languages.language_for_name(&language).now_or_never()?.ok()
+                }) {
                     let runs = language
                         .highlight_text(&content.text.as_str().into(), 0..content.text.len());
 

crates/feedback/src/feedback_editor.rs 🔗

@@ -20,6 +20,7 @@ use postage::prelude::Stream;
 
 use project::Project;
 use serde::Serialize;
+use util::ResultExt;
 use workspace::{
     item::{Item, ItemHandle},
     searchable::{SearchableItem, SearchableItemHandle},
@@ -200,24 +201,28 @@ impl FeedbackEditor {
 impl FeedbackEditor {
     pub fn deploy(
         system_specs: SystemSpecs,
-        workspace: &mut Workspace,
+        _: &mut Workspace,
         app_state: Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
     ) {
-        workspace
-            .with_local_workspace(&app_state, cx, |workspace, cx| {
-                let project = workspace.project().clone();
-                let markdown_language = project.read(cx).languages().language_for_name("Markdown");
-                let buffer = project
-                    .update(cx, |project, cx| {
-                        project.create_buffer("", markdown_language, cx)
+        let markdown = app_state.languages.language_for_name("Markdown");
+        cx.spawn(|workspace, mut cx| async move {
+            let markdown = markdown.await.log_err();
+            workspace
+                .update(&mut cx, |workspace, cx| {
+                    workspace.with_local_workspace(&app_state, cx, |workspace, cx| {
+                        let project = workspace.project().clone();
+                        let buffer = project
+                            .update(cx, |project, cx| project.create_buffer("", markdown, cx))
+                            .expect("creating buffers on a local workspace always succeeds");
+                        let feedback_editor = cx
+                            .add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx));
+                        workspace.add_item(Box::new(feedback_editor), cx);
                     })
-                    .expect("creating buffers on a local workspace always succeeds");
-                let feedback_editor =
-                    cx.add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx));
-                workspace.add_item(Box::new(feedback_editor), cx);
-            })
-            .detach();
+                })
+                .await;
+        })
+        .detach();
     }
 }
 

crates/journal/Cargo.toml 🔗

@@ -13,6 +13,7 @@ editor = { path = "../editor" }
 gpui = { path = "../gpui" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }
+anyhow = "1.0"
 chrono = "0.4"
 dirs = "4.0"
 log = { version = "0.4.16", features = ["kv_unstable_serde"] }

crates/journal/src/journal.rs 🔗

@@ -73,7 +73,7 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
                 }
             }
 
-            Ok(())
+            anyhow::Ok(())
         }
         .log_err()
     })

crates/language/src/buffer_tests.rs 🔗

@@ -80,31 +80,49 @@ fn test_select_language() {
 
     // matching file extension
     assert_eq!(
-        registry.language_for_path("zed/lib.rs").map(|l| l.name()),
+        registry
+            .language_for_path("zed/lib.rs")
+            .now_or_never()
+            .and_then(|l| Some(l.ok()?.name())),
         Some("Rust".into())
     );
     assert_eq!(
-        registry.language_for_path("zed/lib.mk").map(|l| l.name()),
+        registry
+            .language_for_path("zed/lib.mk")
+            .now_or_never()
+            .and_then(|l| Some(l.ok()?.name())),
         Some("Make".into())
     );
 
     // matching filename
     assert_eq!(
-        registry.language_for_path("zed/Makefile").map(|l| l.name()),
+        registry
+            .language_for_path("zed/Makefile")
+            .now_or_never()
+            .and_then(|l| Some(l.ok()?.name())),
         Some("Make".into())
     );
 
     // matching suffix that is not the full file extension or filename
     assert_eq!(
-        registry.language_for_path("zed/cars").map(|l| l.name()),
+        registry
+            .language_for_path("zed/cars")
+            .now_or_never()
+            .and_then(|l| Some(l.ok()?.name())),
         None
     );
     assert_eq!(
-        registry.language_for_path("zed/a.cars").map(|l| l.name()),
+        registry
+            .language_for_path("zed/a.cars")
+            .now_or_never()
+            .and_then(|l| Some(l.ok()?.name())),
         None
     );
     assert_eq!(
-        registry.language_for_path("zed/sumk").map(|l| l.name()),
+        registry
+            .language_for_path("zed/sumk")
+            .now_or_never()
+            .and_then(|l| Some(l.ok()?.name())),
         None
     );
 }
@@ -666,14 +684,14 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
         indoc! {"
             mod x {
                 moˇd y {
-                
+
                 }
             }
             let foo = 1;"},
         vec![indoc! {"
             mod x «{»
                 mod y {
-                
+
                 }
             «}»
             let foo = 1;"}],
@@ -683,7 +701,7 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
         indoc! {"
             mod x {
                 mod y ˇ{
-                
+
                 }
             }
             let foo = 1;"},
@@ -691,14 +709,14 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
             indoc! {"
                 mod x «{»
                     mod y {
-                    
+
                     }
                 «}»
                 let foo = 1;"},
             indoc! {"
                 mod x {
                     mod y «{»
-                    
+
                     «}»
                 }
                 let foo = 1;"},
@@ -709,7 +727,7 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
         indoc! {"
             mod x {
                 mod y {
-                
+
                 }ˇ
             }
             let foo = 1;"},
@@ -717,14 +735,14 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
             indoc! {"
                 mod x «{»
                     mod y {
-                    
+
                     }
                 «}»
                 let foo = 1;"},
             indoc! {"
                 mod x {
                     mod y «{»
-                    
+
                     «}»
                 }
                 let foo = 1;"},
@@ -735,14 +753,14 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
         indoc! {"
             mod x {
                 mod y {
-                
+
                 }
             ˇ}
             let foo = 1;"},
         vec![indoc! {"
             mod x «{»
                 mod y {
-                
+
                 }
             «}»
             let foo = 1;"}],
@@ -752,7 +770,7 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
         indoc! {"
             mod x {
                 mod y {
-                
+
                 }
             }
             let fˇoo = 1;"},
@@ -764,7 +782,7 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
         indoc! {"
             mod x {
                 mod y {
-                
+
                 }
             }
             let foo = 1;ˇ"},

crates/language/src/language.rs 🔗

@@ -13,8 +13,9 @@ use async_trait::async_trait;
 use client::http::HttpClient;
 use collections::HashMap;
 use futures::{
+    channel::oneshot,
     future::{BoxFuture, Shared},
-    FutureExt, TryFutureExt,
+    FutureExt, TryFutureExt as _,
 };
 use gpui::{executor::Background, MutableAppContext, Task};
 use highlight_map::HighlightMap;
@@ -43,7 +44,7 @@ use syntax_map::SyntaxSnapshot;
 use theme::{SyntaxTheme, Theme};
 use tree_sitter::{self, Query};
 use unicase::UniCase;
-use util::ResultExt;
+use util::{ResultExt, TryFutureExt as _, UnwrapFuture};
 
 #[cfg(any(test, feature = "test-support"))]
 use futures::channel::mpsc;
@@ -484,7 +485,7 @@ impl LanguageRegistry {
         let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16);
         Self {
             language_server_download_dir: None,
-            languages: Default::default(),
+            languages: RwLock::new(vec![PLAIN_TEXT.clone()]),
             available_languages: Default::default(),
             lsp_binary_statuses_tx,
             lsp_binary_statuses_rx,
@@ -568,12 +569,18 @@ impl LanguageRegistry {
         self.language_server_download_dir = Some(path.into());
     }
 
-    pub fn language_for_name(self: &Arc<Self>, name: &str) -> Option<Arc<Language>> {
+    pub fn language_for_name(
+        self: &Arc<Self>,
+        name: &str,
+    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
         let name = UniCase::new(name);
         self.get_or_load_language(|config| UniCase::new(config.name.as_ref()) == name)
     }
 
-    pub fn language_for_name_or_extension(self: &Arc<Self>, string: &str) -> Option<Arc<Language>> {
+    pub fn language_for_name_or_extension(
+        self: &Arc<Self>,
+        string: &str,
+    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
         let string = UniCase::new(string);
         self.get_or_load_language(|config| {
             UniCase::new(config.name.as_ref()) == string
@@ -584,7 +591,10 @@ impl LanguageRegistry {
         })
     }
 
-    pub fn language_for_path(self: &Arc<Self>, path: impl AsRef<Path>) -> Option<Arc<Language>> {
+    pub fn language_for_path(
+        self: &Arc<Self>,
+        path: impl AsRef<Path>,
+    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
         let path = path.as_ref();
         let filename = path.file_name().and_then(|name| name.to_str());
         let extension = path.extension().and_then(|name| name.to_str());
@@ -600,17 +610,17 @@ impl LanguageRegistry {
     fn get_or_load_language(
         self: &Arc<Self>,
         callback: impl Fn(&LanguageConfig) -> bool,
-    ) -> Option<Arc<Language>> {
+    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
+        let (tx, rx) = oneshot::channel();
+
         if let Some(language) = self
             .languages
             .read()
             .iter()
             .find(|language| callback(&language.config))
         {
-            return Some(language.clone());
-        }
-
-        if let Some(executor) = self.executor.clone() {
+            let _ = tx.send(Ok(language.clone()));
+        } else if let Some(executor) = self.executor.clone() {
             let mut available_languages = self.available_languages.write();
 
             if let Some(ix) = available_languages.iter().position(|l| callback(&l.config)) {
@@ -625,18 +635,29 @@ impl LanguageRegistry {
                             .with_lsp_adapter(language.lsp_adapter)
                             .await;
                         match language.with_queries(queries) {
-                            Ok(language) => this.add(Arc::new(language)),
+                            Ok(language) => {
+                                let language = Arc::new(language);
+                                this.add(language.clone());
+                                let _ = tx.send(Ok(language));
+                            }
                             Err(err) => {
-                                log::error!("failed  to load language {}: {}", name, err);
-                                return;
+                                let _ = tx.send(Err(anyhow!(
+                                    "failed to load language {}: {}",
+                                    name,
+                                    err
+                                )));
                             }
                         };
                     })
                     .detach();
+            } else {
+                let _ = tx.send(Err(anyhow!("language not found")));
             }
+        } else {
+            let _ = tx.send(Err(anyhow!("executor does not exist")));
         }
 
-        None
+        rx.unwrap()
     }
 
     pub fn to_vec(&self) -> Vec<Arc<Language>> {

crates/language/src/syntax_map.rs 🔗

@@ -1,5 +1,6 @@
 use crate::{Grammar, InjectionConfig, Language, LanguageRegistry};
 use collections::HashMap;
+use futures::FutureExt;
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
 use std::{
@@ -382,11 +383,11 @@ impl SyntaxSnapshot {
                 cursor.next(text);
                 while let Some(layer) = cursor.item() {
                     let SyntaxLayerContent::Pending { language_name } = &layer.content else { unreachable!() };
-                    if {
-                        let language_registry = &registry;
-                        language_registry.language_for_name_or_extension(language_name)
-                    }
-                    .is_some()
+                    if registry
+                        .language_for_name_or_extension(language_name)
+                        .now_or_never()
+                        .and_then(|language| language.ok())
+                        .is_some()
                     {
                         resolved_injection_ranges.push(layer.range.to_offset(text));
                     }
@@ -1116,7 +1117,10 @@ fn get_injections(
     combined_injection_ranges.clear();
     for pattern in &config.patterns {
         if let (Some(language_name), true) = (pattern.language.as_ref(), pattern.combined) {
-            if let Some(language) = language_registry.language_for_name_or_extension(language_name)
+            if let Some(language) = language_registry
+                .language_for_name_or_extension(language_name)
+                .now_or_never()
+                .and_then(|language| language.ok())
             {
                 combined_injection_ranges.insert(language, Vec::new());
             }
@@ -1162,10 +1166,10 @@ fn get_injections(
             };
 
             if let Some(language_name) = language_name {
-                let language = {
-                    let language_name: &str = &language_name;
-                    language_registry.language_for_name_or_extension(language_name)
-                };
+                let language = language_registry
+                    .language_for_name_or_extension(&language_name)
+                    .now_or_never()
+                    .and_then(|language| language.ok());
                 let range = text.anchor_before(step_range.start)..text.anchor_after(step_range.end);
                 if let Some(language) = language {
                     if combined {
@@ -2522,7 +2526,11 @@ mod tests {
         registry.add(Arc::new(html_lang()));
         registry.add(Arc::new(erb_lang()));
         registry.add(Arc::new(markdown_lang()));
-        let language = registry.language_for_name(language_name).unwrap();
+        let language = registry
+            .language_for_name(language_name)
+            .now_or_never()
+            .unwrap()
+            .unwrap();
         let mut buffer = Buffer::new(0, 0, Default::default());
 
         let mut mutated_syntax_map = SyntaxMap::new();

crates/lsp/src/lsp.rs 🔗

@@ -160,15 +160,13 @@ impl LanguageServer {
         server: Option<Child>,
         root_path: &Path,
         cx: AsyncAppContext,
-        mut on_unhandled_notification: F,
+        on_unhandled_notification: F,
     ) -> Self
     where
         Stdin: AsyncWrite + Unpin + Send + 'static,
         Stdout: AsyncRead + Unpin + Send + 'static,
         F: FnMut(AnyNotification) + 'static + Send,
     {
-        let mut stdin = BufWriter::new(stdin);
-        let mut stdout = BufReader::new(stdout);
         let (outbound_tx, outbound_rx) = channel::unbounded::<Vec<u8>>();
         let notification_handlers =
             Arc::new(Mutex::new(HashMap::<_, NotificationHandler>::default()));
@@ -177,89 +175,19 @@ impl LanguageServer {
         let input_task = cx.spawn(|cx| {
             let notification_handlers = notification_handlers.clone();
             let response_handlers = response_handlers.clone();
-            async move {
-                let _clear_response_handlers = util::defer({
-                    let response_handlers = response_handlers.clone();
-                    move || {
-                        response_handlers.lock().take();
-                    }
-                });
-                let mut buffer = Vec::new();
-                loop {
-                    buffer.clear();
-                    stdout.read_until(b'\n', &mut buffer).await?;
-                    stdout.read_until(b'\n', &mut buffer).await?;
-                    let message_len: usize = std::str::from_utf8(&buffer)?
-                        .strip_prefix(CONTENT_LEN_HEADER)
-                        .ok_or_else(|| anyhow!("invalid header"))?
-                        .trim_end()
-                        .parse()?;
-
-                    buffer.resize(message_len, 0);
-                    stdout.read_exact(&mut buffer).await?;
-                    log::trace!("incoming message:{}", String::from_utf8_lossy(&buffer));
-
-                    if let Ok(msg) = serde_json::from_slice::<AnyNotification>(&buffer) {
-                        if let Some(handler) = notification_handlers.lock().get_mut(msg.method) {
-                            handler(msg.id, msg.params.get(), cx.clone());
-                        } else {
-                            on_unhandled_notification(msg);
-                        }
-                    } else if let Ok(AnyResponse {
-                        id, error, result, ..
-                    }) = serde_json::from_slice(&buffer)
-                    {
-                        if let Some(handler) = response_handlers
-                            .lock()
-                            .as_mut()
-                            .and_then(|handlers| handlers.remove(&id))
-                        {
-                            if let Some(error) = error {
-                                handler(Err(error));
-                            } else if let Some(result) = result {
-                                handler(Ok(result.get()));
-                            } else {
-                                handler(Ok("null"));
-                            }
-                        }
-                    } else {
-                        warn!(
-                            "Failed to deserialize message:\n{}",
-                            std::str::from_utf8(&buffer)?
-                        );
-                    }
-
-                    // Don't starve the main thread when receiving lots of messages at once.
-                    smol::future::yield_now().await;
-                }
-            }
+            Self::handle_input(
+                stdout,
+                on_unhandled_notification,
+                notification_handlers,
+                response_handlers,
+                cx,
+            )
             .log_err()
         });
         let (output_done_tx, output_done_rx) = barrier::channel();
         let output_task = cx.background().spawn({
             let response_handlers = response_handlers.clone();
-            async move {
-                let _clear_response_handlers = util::defer({
-                    let response_handlers = response_handlers.clone();
-                    move || {
-                        response_handlers.lock().take();
-                    }
-                });
-                let mut content_len_buffer = Vec::new();
-                while let Ok(message) = outbound_rx.recv().await {
-                    log::trace!("outgoing message:{}", String::from_utf8_lossy(&message));
-                    content_len_buffer.clear();
-                    write!(content_len_buffer, "{}", message.len()).unwrap();
-                    stdin.write_all(CONTENT_LEN_HEADER.as_bytes()).await?;
-                    stdin.write_all(&content_len_buffer).await?;
-                    stdin.write_all("\r\n\r\n".as_bytes()).await?;
-                    stdin.write_all(&message).await?;
-                    stdin.flush().await?;
-                }
-                drop(output_done_tx);
-                Ok(())
-            }
-            .log_err()
+            Self::handle_output(stdin, outbound_rx, output_done_tx, response_handlers).log_err()
         });
 
         Self {
@@ -278,6 +206,105 @@ impl LanguageServer {
         }
     }
 
+    async fn handle_input<Stdout, F>(
+        stdout: Stdout,
+        mut on_unhandled_notification: F,
+        notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
+        response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
+        cx: AsyncAppContext,
+    ) -> anyhow::Result<()>
+    where
+        Stdout: AsyncRead + Unpin + Send + 'static,
+        F: FnMut(AnyNotification) + 'static + Send,
+    {
+        let mut stdout = BufReader::new(stdout);
+        let _clear_response_handlers = util::defer({
+            let response_handlers = response_handlers.clone();
+            move || {
+                response_handlers.lock().take();
+            }
+        });
+        let mut buffer = Vec::new();
+        loop {
+            buffer.clear();
+            stdout.read_until(b'\n', &mut buffer).await?;
+            stdout.read_until(b'\n', &mut buffer).await?;
+            let message_len: usize = std::str::from_utf8(&buffer)?
+                .strip_prefix(CONTENT_LEN_HEADER)
+                .ok_or_else(|| anyhow!("invalid header"))?
+                .trim_end()
+                .parse()?;
+
+            buffer.resize(message_len, 0);
+            stdout.read_exact(&mut buffer).await?;
+            log::trace!("incoming message:{}", String::from_utf8_lossy(&buffer));
+
+            if let Ok(msg) = serde_json::from_slice::<AnyNotification>(&buffer) {
+                if let Some(handler) = notification_handlers.lock().get_mut(msg.method) {
+                    handler(msg.id, msg.params.get(), cx.clone());
+                } else {
+                    on_unhandled_notification(msg);
+                }
+            } else if let Ok(AnyResponse {
+                id, error, result, ..
+            }) = serde_json::from_slice(&buffer)
+            {
+                if let Some(handler) = response_handlers
+                    .lock()
+                    .as_mut()
+                    .and_then(|handlers| handlers.remove(&id))
+                {
+                    if let Some(error) = error {
+                        handler(Err(error));
+                    } else if let Some(result) = result {
+                        handler(Ok(result.get()));
+                    } else {
+                        handler(Ok("null"));
+                    }
+                }
+            } else {
+                warn!(
+                    "Failed to deserialize message:\n{}",
+                    std::str::from_utf8(&buffer)?
+                );
+            }
+
+            // Don't starve the main thread when receiving lots of messages at once.
+            smol::future::yield_now().await;
+        }
+    }
+
+    async fn handle_output<Stdin>(
+        stdin: Stdin,
+        outbound_rx: channel::Receiver<Vec<u8>>,
+        output_done_tx: barrier::Sender,
+        response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
+    ) -> anyhow::Result<()>
+    where
+        Stdin: AsyncWrite + Unpin + Send + 'static,
+    {
+        let mut stdin = BufWriter::new(stdin);
+        let _clear_response_handlers = util::defer({
+            let response_handlers = response_handlers.clone();
+            move || {
+                response_handlers.lock().take();
+            }
+        });
+        let mut content_len_buffer = Vec::new();
+        while let Ok(message) = outbound_rx.recv().await {
+            log::trace!("outgoing message:{}", String::from_utf8_lossy(&message));
+            content_len_buffer.clear();
+            write!(content_len_buffer, "{}", message.len()).unwrap();
+            stdin.write_all(CONTENT_LEN_HEADER.as_bytes()).await?;
+            stdin.write_all(&content_len_buffer).await?;
+            stdin.write_all("\r\n\r\n".as_bytes()).await?;
+            stdin.write_all(&message).await?;
+            stdin.flush().await?;
+        }
+        drop(output_done_tx);
+        Ok(())
+    }
+
     /// Initializes a language server.
     /// Note that `options` is used directly to construct [`InitializeParams`],
     /// which is why it is owned.
@@ -389,7 +416,7 @@ impl LanguageServer {
                     output_done.recv().await;
                     log::debug!("language server shutdown finished");
                     drop(tasks);
-                    Ok(())
+                    anyhow::Ok(())
                 }
                 .log_err(),
             )

crates/project/src/project.rs 🔗

@@ -1838,7 +1838,11 @@ impl Project {
     ) -> Option<()> {
         // If the buffer has a language, set it and start the language server if we haven't already.
         let full_path = buffer.read(cx).file()?.full_path(cx);
-        let new_language = self.languages.language_for_path(&full_path)?;
+        let new_language = self
+            .languages
+            .language_for_path(&full_path)
+            .now_or_never()?
+            .ok()?;
         buffer.update(cx, |buffer, cx| {
             if buffer.language().map_or(true, |old_language| {
                 !Arc::ptr_eq(old_language, &new_language)
@@ -2248,8 +2252,14 @@ impl Project {
             })
             .collect();
         for (worktree_id, worktree_abs_path, full_path) in language_server_lookup_info {
-            let language = self.languages.language_for_path(&full_path)?;
-            self.restart_language_server(worktree_id, worktree_abs_path, language, cx);
+            if let Some(language) = self
+                .languages
+                .language_for_path(&full_path)
+                .now_or_never()
+                .and_then(|language| language.ok())
+            {
+                self.restart_language_server(worktree_id, worktree_abs_path, language, cx);
+            }
         }
 
         None
@@ -3278,12 +3288,14 @@ impl Project {
                                 path: path.into(),
                             };
                             let signature = this.symbol_signature(&project_path);
+                            let adapter_language = adapter_language.clone();
                             let language = this
                                 .languages
                                 .language_for_path(&project_path.path)
-                                .unwrap_or(adapter_language.clone());
+                                .unwrap_or_else(move |_| adapter_language);
                             let language_server_name = adapter.name.clone();
                             Some(async move {
+                                let language = language.await;
                                 let label = language
                                     .label_for_symbol(&lsp_symbol.name, lsp_symbol.kind)
                                     .await;
@@ -5831,7 +5843,7 @@ impl Project {
                                 })?;
                             }
 
-                            Ok(())
+                            anyhow::Ok(())
                         }
                         .log_err(),
                     )
@@ -6060,7 +6072,7 @@ impl Project {
                 worktree_id,
                 path: PathBuf::from(serialized_symbol.path).into(),
             };
-            let language = languages.language_for_path(&path.path);
+            let language = languages.language_for_path(&path.path).await.log_err();
             Ok(Symbol {
                 language_server_name: LanguageServerName(
                     serialized_symbol.language_server_name.into(),

crates/util/src/util.rs 🔗

@@ -124,11 +124,15 @@ pub trait TryFutureExt {
     fn warn_on_err(self) -> LogErrorFuture<Self>
     where
         Self: Sized;
+    fn unwrap(self) -> UnwrapFuture<Self>
+    where
+        Self: Sized;
 }
 
-impl<F, T> TryFutureExt for F
+impl<F, T, E> TryFutureExt for F
 where
-    F: Future<Output = anyhow::Result<T>>,
+    F: Future<Output = Result<T, E>>,
+    E: std::fmt::Debug,
 {
     fn log_err(self) -> LogErrorFuture<Self>
     where
@@ -143,17 +147,25 @@ where
     {
         LogErrorFuture(self, log::Level::Warn)
     }
+
+    fn unwrap(self) -> UnwrapFuture<Self>
+    where
+        Self: Sized,
+    {
+        UnwrapFuture(self)
+    }
 }
 
 pub struct LogErrorFuture<F>(F, log::Level);
 
-impl<F, T> Future for LogErrorFuture<F>
+impl<F, T, E> Future for LogErrorFuture<F>
 where
-    F: Future<Output = anyhow::Result<T>>,
+    F: Future<Output = Result<T, E>>,
+    E: std::fmt::Debug,
 {
     type Output = Option<T>;
 
-    fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
         let level = self.1;
         let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
         match inner.poll(cx) {
@@ -169,6 +181,24 @@ where
     }
 }
 
+pub struct UnwrapFuture<F>(F);
+
+impl<F, T, E> Future for UnwrapFuture<F>
+where
+    F: Future<Output = Result<T, E>>,
+    E: std::fmt::Debug,
+{
+    type Output = T;
+
+    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+        let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
+        match inner.poll(cx) {
+            Poll::Ready(result) => Poll::Ready(result.unwrap()),
+            Poll::Pending => Poll::Pending,
+        }
+    }
+}
+
 struct Defer<F: FnOnce()>(Option<F>);
 
 impl<F: FnOnce()> Drop for Defer<F> {

crates/zed/src/zed.rs 🔗

@@ -167,9 +167,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
     });
     cx.add_action({
         let app_state = app_state.clone();
-        move |workspace: &mut Workspace, _: &OpenLicenses, cx: &mut ViewContext<Workspace>| {
+        move |_: &mut Workspace, _: &OpenLicenses, cx: &mut ViewContext<Workspace>| {
             open_bundled_file(
-                workspace,
                 app_state.clone(),
                 "licenses.md",
                 "Open Source License Attribution",
@@ -192,9 +191,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
     });
     cx.add_action({
         let app_state = app_state.clone();
-        move |workspace: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext<Workspace>| {
+        move |_: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext<Workspace>| {
             open_bundled_file(
-                workspace,
                 app_state.clone(),
                 "keymaps/default.json",
                 "Default Key Bindings",
@@ -205,11 +203,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
     });
     cx.add_action({
         let app_state = app_state.clone();
-        move |workspace: &mut Workspace,
-              _: &OpenDefaultSettings,
-              cx: &mut ViewContext<Workspace>| {
+        move |_: &mut Workspace, _: &OpenDefaultSettings, cx: &mut ViewContext<Workspace>| {
             open_bundled_file(
-                workspace,
                 app_state.clone(),
                 "settings/default.json",
                 "Default Settings",
@@ -218,32 +213,41 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
             );
         }
     });
-    cx.add_action(
-        |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| {
+    cx.add_action({
+        let app_state = app_state.clone();
+        move |_: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| {
+            let app_state = app_state.clone();
+            let markdown = app_state.languages.language_for_name("JSON");
             let content = to_string_pretty(&cx.debug_elements()).unwrap();
-            let project = workspace.project().clone();
-            let json_language = project
-                .read(cx)
-                .languages()
-                .language_for_name("JSON")
-                .unwrap();
-            if project.read(cx).is_remote() {
-                cx.propagate_action();
-            } else if let Some(buffer) = project
-                .update(cx, |project, cx| {
-                    project.create_buffer(&content, Some(json_language), cx)
-                })
-                .log_err()
-            {
-                workspace.add_item(
-                    Box::new(
-                        cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
-                    ),
-                    cx,
-                );
-            }
-        },
-    );
+            cx.spawn(|workspace, mut cx| async move {
+                let markdown = markdown.await.log_err();
+                workspace
+                    .update(&mut cx, |workspace, cx| {
+                        workspace.with_local_workspace(&app_state, cx, move |workspace, cx| {
+                            let project = workspace.project().clone();
+
+                            let buffer = project
+                                .update(cx, |project, cx| {
+                                    project.create_buffer(&content, markdown, cx)
+                                })
+                                .expect("creating buffers on a local workspace always succeeds");
+                            let buffer = cx.add_model(|cx| {
+                                MultiBuffer::singleton(buffer, cx)
+                                    .with_title("Debug Elements".into())
+                            });
+                            workspace.add_item(
+                                Box::new(cx.add_view(|cx| {
+                                    Editor::for_multibuffer(buffer, Some(project.clone()), cx)
+                                })),
+                                cx,
+                            );
+                        })
+                    })
+                    .await;
+            })
+            .detach();
+        }
+    });
     cx.add_action(
         |workspace: &mut Workspace,
          _: &project_panel::ToggleFocus,
@@ -628,6 +632,7 @@ fn open_telemetry_log_file(
                 start_offset += newline_offset + 1;
             }
             let log_suffix = &log[start_offset..];
+            let json = app_state.languages.language_for_name("JSON").await.log_err();
 
             workspace.update(&mut cx, |workspace, cx| {
                 let project = workspace.project().clone();
@@ -635,7 +640,7 @@ fn open_telemetry_log_file(
                     .update(cx, |project, cx| project.create_buffer("", None, cx))
                     .expect("creating buffers on a local workspace always succeeds");
                 buffer.update(cx, |buffer, cx| {
-                    buffer.set_language(app_state.languages.language_for_name("JSON"), cx);
+                    buffer.set_language(json, cx);
                     buffer.edit(
                         [(
                             0..0,
@@ -668,35 +673,42 @@ fn open_telemetry_log_file(
 }
 
 fn open_bundled_file(
-    workspace: &mut Workspace,
     app_state: Arc<AppState>,
     asset_path: &'static str,
     title: &'static str,
     language: &'static str,
     cx: &mut ViewContext<Workspace>,
 ) {
-    workspace
-        .with_local_workspace(&app_state, cx, |workspace, cx| {
-            let project = workspace.project().clone();
-            let buffer = project.update(cx, |project, cx| {
-                let text = Assets::get(asset_path)
-                    .map(|f| f.data)
-                    .unwrap_or_else(|| Cow::Borrowed(b"File not found"));
-                let text = str::from_utf8(text.as_ref()).unwrap();
-                project
-                    .create_buffer(text, project.languages().language_for_name(language), cx)
-                    .expect("creating buffers on a local workspace always succeeds")
-            });
-            let buffer =
-                cx.add_model(|cx| MultiBuffer::singleton(buffer, cx).with_title(title.into()));
-            workspace.add_item(
-                Box::new(
-                    cx.add_view(|cx| Editor::for_multibuffer(buffer, Some(project.clone()), cx)),
-                ),
-                cx,
-            );
-        })
-        .detach();
+    let language = app_state.languages.language_for_name(language);
+    cx.spawn(|workspace, mut cx| async move {
+        let language = language.await.log_err();
+        workspace
+            .update(&mut cx, |workspace, cx| {
+                workspace.with_local_workspace(&app_state, cx, |workspace, cx| {
+                    let project = workspace.project();
+                    let buffer = project.update(cx, |project, cx| {
+                        let text = Assets::get(asset_path)
+                            .map(|f| f.data)
+                            .unwrap_or_else(|| Cow::Borrowed(b"File not found"));
+                        let text = str::from_utf8(text.as_ref()).unwrap();
+                        project
+                            .create_buffer(text, language, cx)
+                            .expect("creating buffers on a local workspace always succeeds")
+                    });
+                    let buffer = cx.add_model(|cx| {
+                        MultiBuffer::singleton(buffer, cx).with_title(title.into())
+                    });
+                    workspace.add_item(
+                        Box::new(cx.add_view(|cx| {
+                            Editor::for_multibuffer(buffer, Some(project.clone()), cx)
+                        })),
+                        cx,
+                    );
+                })
+            })
+            .await;
+    })
+    .detach();
 }
 
 fn schema_file_match(path: &Path) -> &Path {