Wait for language to load when parsing markdown

Julia created

Change summary

crates/editor/src/editor.rs        |  42 +++++--
crates/editor/src/hover_popover.rs | 167 ++++++++++++++++++-------------
crates/language/src/buffer.rs      |   6 
crates/language/src/markdown.rs    |  12 +-
4 files changed, 136 insertions(+), 91 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -1018,7 +1018,6 @@ impl CompletionsMenu {
             return;
         }
 
-        // TODO: Do on background
         cx.spawn(|this, mut cx| async move {
             let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
             let Some(completion_item) = request.await.log_err() else {
@@ -1031,7 +1030,8 @@ impl CompletionsMenu {
                     &language_registry,
                     None, // TODO: Try to reasonably work out which language the completion is for
                     &style,
-                );
+                )
+                .await;
 
                 let mut completions = completions.write();
                 let completion = &mut completions[index];
@@ -1084,30 +1084,46 @@ impl CompletionsMenu {
             let style = style.clone();
             move |_, range, items, cx| {
                 let start_ix = range.start;
-                let mut completions = completions.write();
+                let completions_guard = completions.read();
 
                 for (ix, mat) in matches[range].iter().enumerate() {
-                    let completion = &mut completions[mat.candidate_id];
+                    let item_ix = start_ix + ix;
+                    let candidate_id = mat.candidate_id;
+                    let completion = &completions_guard[candidate_id];
 
-                    if completion.documentation.is_none() {
+                    if item_ix == selected_item && completion.documentation.is_none() {
                         if let Some(lsp_docs) = &completion.lsp_completion.documentation {
                             let project = project
                                 .as_ref()
                                 .expect("It is impossible have LSP servers without a project");
 
-                            let language_registry = project.read(cx).languages();
+                            let lsp_docs = lsp_docs.clone();
+                            let lsp_docs = lsp_docs.clone();
+                            let language_registry = project.read(cx).languages().clone();
+                            let style = style.theme.clone();
+                            let completions = completions.clone();
+
+                            cx.spawn(|this, mut cx| async move {
+                                let documentation = prepare_completion_documentation(
+                                    &lsp_docs,
+                                    &language_registry,
+                                    None,
+                                    &style,
+                                )
+                                .await;
 
-                            completion.documentation = prepare_completion_documentation(
-                                lsp_docs,
-                                language_registry,
-                                None,
-                                &style.theme,
-                            );
+                                this.update(&mut cx, |_, cx| {
+                                    let mut completions = completions.write();
+                                    completions[candidate_id].documentation = documentation;
+                                    drop(completions);
+                                    cx.notify();
+                                })
+                            })
+                            .detach();
                         }
                     }
 
                     let documentation = &completion.documentation;
-                    let item_ix = start_ix + ix;
 
                     items.push(
                         MouseEventHandler::new::<CompletionTag, _>(

crates/editor/src/hover_popover.rs 🔗

@@ -7,7 +7,7 @@ use crate::{
 use futures::FutureExt;
 use gpui::{
     actions,
-    elements::{Flex, MouseEventHandler, Padding, ParentElement, Text},
+    elements::{Empty, Flex, MouseEventHandler, Padding, ParentElement, Text},
     fonts::HighlightStyle,
     platform::{CursorStyle, MouseButton},
     AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext,
@@ -128,7 +128,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
                     symbol_range: DocumentRange::Inlay(inlay_hover.range),
                     blocks: vec![inlay_hover.tooltip],
                     language: None,
-                    rendered_content: None,
+                    parsed_content: None,
                 };
 
                 this.update(&mut cx, |this, cx| {
@@ -332,7 +332,7 @@ fn show_hover(
                     symbol_range: DocumentRange::Text(range),
                     blocks: hover_result.contents,
                     language: hover_result.language,
-                    rendered_content: None,
+                    parsed_content: None,
                 })
             });
 
@@ -363,12 +363,12 @@ fn show_hover(
     editor.hover_state.info_task = Some(task);
 }
 
-fn render_blocks(
+async fn render_blocks(
     theme_id: usize,
     blocks: &[HoverBlock],
     language_registry: &Arc<LanguageRegistry>,
     language: Option<Arc<Language>>,
-    style: &EditorStyle,
+    style: &theme::Editor,
 ) -> ParsedInfo {
     let mut text = String::new();
     let mut highlights = Vec::new();
@@ -382,16 +382,19 @@ fn render_blocks(
                 text.push_str(&block.text);
             }
 
-            HoverBlockKind::Markdown => markdown::parse_markdown_block(
-                &block.text,
-                language_registry,
-                language.clone(),
-                style,
-                &mut text,
-                &mut highlights,
-                &mut region_ranges,
-                &mut regions,
-            ),
+            HoverBlockKind::Markdown => {
+                markdown::parse_markdown_block(
+                    &block.text,
+                    language_registry,
+                    language.clone(),
+                    style,
+                    &mut text,
+                    &mut highlights,
+                    &mut region_ranges,
+                    &mut regions,
+                )
+                .await
+            }
 
             HoverBlockKind::Code { language } => {
                 if let Some(language) = language_registry
@@ -482,7 +485,7 @@ pub struct InfoPopover {
     symbol_range: DocumentRange,
     pub blocks: Vec<HoverBlock>,
     language: Option<Arc<Language>>,
-    rendered_content: Option<ParsedInfo>,
+    parsed_content: Option<ParsedInfo>,
 }
 
 #[derive(Debug, Clone)]
@@ -500,63 +503,87 @@ impl InfoPopover {
         style: &EditorStyle,
         cx: &mut ViewContext<Editor>,
     ) -> AnyElement<Editor> {
-        if let Some(rendered) = &self.rendered_content {
-            if rendered.theme_id != style.theme_id {
-                self.rendered_content = None;
+        if let Some(parsed) = &self.parsed_content {
+            if parsed.theme_id != style.theme_id {
+                self.parsed_content = None;
             }
         }
 
-        let rendered_content = self.rendered_content.get_or_insert_with(|| {
-            render_blocks(
-                style.theme_id,
-                &self.blocks,
-                self.project.read(cx).languages(),
-                self.language.clone(),
-                style,
-            )
-        });
+        let rendered = if let Some(parsed) = &self.parsed_content {
+            let view_id = cx.view_id();
+            let regions = parsed.regions.clone();
+            let code_span_background_color = style.document_highlight_read_background;
 
-        MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| {
             let mut region_id = 0;
-            let view_id = cx.view_id();
 
-            let code_span_background_color = style.document_highlight_read_background;
-            let regions = rendered_content.regions.clone();
+            Text::new(parsed.text.clone(), style.text.clone())
+                .with_highlights(parsed.highlights.clone())
+                .with_custom_runs(parsed.region_ranges.clone(), move |ix, bounds, scene, _| {
+                    region_id += 1;
+                    let region = regions[ix].clone();
+
+                    if let Some(url) = region.link_url {
+                        scene.push_cursor_region(CursorRegion {
+                            bounds,
+                            style: CursorStyle::PointingHand,
+                        });
+                        scene.push_mouse_region(
+                            MouseRegion::new::<Self>(view_id, region_id, bounds)
+                                .on_click::<Editor, _>(MouseButton::Left, move |_, _, cx| {
+                                    cx.platform().open_url(&url)
+                                }),
+                        );
+                    }
+
+                    if region.code {
+                        scene.push_quad(gpui::Quad {
+                            bounds,
+                            background: Some(code_span_background_color),
+                            border: Default::default(),
+                            corner_radii: (2.0).into(),
+                        });
+                    }
+                })
+                .with_soft_wrap(true)
+                .into_any()
+        } else {
+            let theme_id = style.theme_id;
+            let language_registry = self.project.read(cx).languages().clone();
+            let blocks = self.blocks.clone();
+            let language = self.language.clone();
+            let style = style.theme.clone();
+            cx.spawn(|this, mut cx| async move {
+                let blocks =
+                    render_blocks(theme_id, &blocks, &language_registry, language, &style).await;
+                _ = this.update(&mut cx, |_, cx| cx.notify());
+                blocks
+            })
+            .detach();
+
+            Empty::new().into_any()
+        };
+
+        // let rendered_content = self.parsed_content.get_or_insert_with(|| {
+        //     let language_registry = self.project.read(cx).languages().clone();
+        //     cx.spawn(|this, mut cx| async move {
+        //         let blocks = render_blocks(
+        //             style.theme_id,
+        //             &self.blocks,
+        //             &language_registry,
+        //             self.language.clone(),
+        //             style,
+        //         )
+        //         .await;
+        //         this.update(&mut cx, |_, cx| cx.notify());
+        //         blocks
+        //     })
+        //     .shared()
+        // });
+
+        MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| {
             Flex::column()
                 .scrollable::<HoverBlock>(1, None, cx)
-                .with_child(
-                    Text::new(rendered_content.text.clone(), style.text.clone())
-                        .with_highlights(rendered_content.highlights.clone())
-                        .with_custom_runs(
-                            rendered_content.region_ranges.clone(),
-                            move |ix, bounds, scene, _| {
-                                region_id += 1;
-                                let region = regions[ix].clone();
-                                if let Some(url) = region.link_url {
-                                    scene.push_cursor_region(CursorRegion {
-                                        bounds,
-                                        style: CursorStyle::PointingHand,
-                                    });
-                                    scene.push_mouse_region(
-                                        MouseRegion::new::<Self>(view_id, region_id, bounds)
-                                            .on_click::<Editor, _>(
-                                                MouseButton::Left,
-                                                move |_, _, cx| cx.platform().open_url(&url),
-                                            ),
-                                    );
-                                }
-                                if region.code {
-                                    scene.push_quad(gpui::Quad {
-                                        bounds,
-                                        background: Some(code_span_background_color),
-                                        border: Default::default(),
-                                        corner_radii: (2.0).into(),
-                                    });
-                                }
-                            },
-                        )
-                        .with_soft_wrap(true),
-                )
+                .with_child(rendered)
                 .contained()
                 .with_style(style.hover_popover.container)
         })
@@ -877,7 +904,8 @@ mod tests {
             );
 
             let style = editor.style(cx);
-            let rendered = render_blocks(0, &blocks, &Default::default(), None, &style);
+            let rendered =
+                smol::block_on(render_blocks(0, &blocks, &Default::default(), None, &style));
             assert_eq!(
                 rendered.text,
                 code_str.trim(),
@@ -1069,7 +1097,8 @@ mod tests {
                 expected_styles,
             } in &rows[0..]
             {
-                let rendered = render_blocks(0, &blocks, &Default::default(), None, &style);
+                let rendered =
+                    smol::block_on(render_blocks(0, &blocks, &Default::default(), None, &style));
 
                 let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
                 let expected_highlights = ranges
@@ -1339,7 +1368,7 @@ mod tests {
             );
             assert_eq!(
                 popover
-                    .rendered_content
+                    .parsed_content
                     .as_ref()
                     .expect("should have label text for new type hint")
                     .text,
@@ -1403,7 +1432,7 @@ mod tests {
             );
             assert_eq!(
                 popover
-                    .rendered_content
+                    .parsed_content
                     .as_ref()
                     .expect("should have label text for struct hint")
                     .text,

crates/language/src/buffer.rs 🔗

@@ -7,7 +7,7 @@ pub use crate::{
 use crate::{
     diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
     language_settings::{language_settings, LanguageSettings},
-    markdown,
+    markdown::parse_markdown,
     outline::OutlineItem,
     syntax_map::{
         SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
@@ -145,7 +145,7 @@ pub struct Diagnostic {
     pub is_unnecessary: bool,
 }
 
-pub fn prepare_completion_documentation(
+pub async fn prepare_completion_documentation(
     documentation: &lsp::Documentation,
     language_registry: &Arc<LanguageRegistry>,
     language: Option<Arc<Language>>,
@@ -170,7 +170,7 @@ pub fn prepare_completion_documentation(
             }
 
             lsp::MarkupKind::Markdown => {
-                let parsed = markdown::parse_markdown(value, language_registry, language, style);
+                let parsed = parse_markdown(value, language_registry, language, style).await;
                 Some(Documentation::MultiLineMarkdown(parsed))
             }
         },

crates/language/src/markdown.rs 🔗

@@ -2,7 +2,6 @@ use std::ops::Range;
 use std::sync::Arc;
 
 use crate::{Language, LanguageRegistry};
-use futures::FutureExt;
 use gpui::fonts::{HighlightStyle, Underline, Weight};
 use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
 
@@ -20,7 +19,7 @@ pub struct ParsedRegion {
     pub link_url: Option<String>,
 }
 
-pub fn parse_markdown(
+pub async fn parse_markdown(
     markdown: &str,
     language_registry: &Arc<LanguageRegistry>,
     language: Option<Arc<Language>>,
@@ -40,7 +39,8 @@ pub fn parse_markdown(
         &mut highlights,
         &mut region_ranges,
         &mut regions,
-    );
+    )
+    .await;
 
     ParsedMarkdown {
         text,
@@ -50,7 +50,7 @@ pub fn parse_markdown(
     }
 }
 
-pub fn parse_markdown_block(
+pub async fn parse_markdown_block(
     markdown: &str,
     language_registry: &Arc<LanguageRegistry>,
     language: Option<Arc<Language>>,
@@ -143,8 +143,8 @@ pub fn parse_markdown_block(
                     current_language = if let CodeBlockKind::Fenced(language) = kind {
                         language_registry
                             .language_for_name(language.as_ref())
-                            .now_or_never()
-                            .and_then(Result::ok)
+                            .await
+                            .ok()
                     } else {
                         language.clone()
                     }