Wait for language to load when parsing markdown
This commit is contained in:
parent
ea6f366d23
commit
a881b1f5fb
4 changed files with 137 additions and 92 deletions
|
@ -1018,7 +1018,6 @@ impl CompletionsMenu {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Do on background
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
|
let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
|
||||||
let Some(completion_item) = request.await.log_err() else {
|
let Some(completion_item) = request.await.log_err() else {
|
||||||
|
@ -1031,7 +1030,8 @@ impl CompletionsMenu {
|
||||||
&language_registry,
|
&language_registry,
|
||||||
None, // TODO: Try to reasonably work out which language the completion is for
|
None, // TODO: Try to reasonably work out which language the completion is for
|
||||||
&style,
|
&style,
|
||||||
);
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let mut completions = completions.write();
|
let mut completions = completions.write();
|
||||||
let completion = &mut completions[index];
|
let completion = &mut completions[index];
|
||||||
|
@ -1084,30 +1084,46 @@ impl CompletionsMenu {
|
||||||
let style = style.clone();
|
let style = style.clone();
|
||||||
move |_, range, items, cx| {
|
move |_, range, items, cx| {
|
||||||
let start_ix = range.start;
|
let start_ix = range.start;
|
||||||
let mut completions = completions.write();
|
let completions_guard = completions.read();
|
||||||
|
|
||||||
for (ix, mat) in matches[range].iter().enumerate() {
|
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 {
|
if let Some(lsp_docs) = &completion.lsp_completion.documentation {
|
||||||
let project = project
|
let project = project
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("It is impossible have LSP servers without a project");
|
.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();
|
||||||
|
|
||||||
completion.documentation = prepare_completion_documentation(
|
cx.spawn(|this, mut cx| async move {
|
||||||
lsp_docs,
|
let documentation = prepare_completion_documentation(
|
||||||
language_registry,
|
&lsp_docs,
|
||||||
|
&language_registry,
|
||||||
None,
|
None,
|
||||||
&style.theme,
|
&style,
|
||||||
);
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
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 documentation = &completion.documentation;
|
||||||
let item_ix = start_ix + ix;
|
|
||||||
|
|
||||||
items.push(
|
items.push(
|
||||||
MouseEventHandler::new::<CompletionTag, _>(
|
MouseEventHandler::new::<CompletionTag, _>(
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
actions,
|
||||||
elements::{Flex, MouseEventHandler, Padding, ParentElement, Text},
|
elements::{Empty, Flex, MouseEventHandler, Padding, ParentElement, Text},
|
||||||
fonts::HighlightStyle,
|
fonts::HighlightStyle,
|
||||||
platform::{CursorStyle, MouseButton},
|
platform::{CursorStyle, MouseButton},
|
||||||
AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext,
|
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),
|
symbol_range: DocumentRange::Inlay(inlay_hover.range),
|
||||||
blocks: vec![inlay_hover.tooltip],
|
blocks: vec![inlay_hover.tooltip],
|
||||||
language: None,
|
language: None,
|
||||||
rendered_content: None,
|
parsed_content: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
|
@ -332,7 +332,7 @@ fn show_hover(
|
||||||
symbol_range: DocumentRange::Text(range),
|
symbol_range: DocumentRange::Text(range),
|
||||||
blocks: hover_result.contents,
|
blocks: hover_result.contents,
|
||||||
language: hover_result.language,
|
language: hover_result.language,
|
||||||
rendered_content: None,
|
parsed_content: None,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -363,12 +363,12 @@ fn show_hover(
|
||||||
editor.hover_state.info_task = Some(task);
|
editor.hover_state.info_task = Some(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_blocks(
|
async fn render_blocks(
|
||||||
theme_id: usize,
|
theme_id: usize,
|
||||||
blocks: &[HoverBlock],
|
blocks: &[HoverBlock],
|
||||||
language_registry: &Arc<LanguageRegistry>,
|
language_registry: &Arc<LanguageRegistry>,
|
||||||
language: Option<Arc<Language>>,
|
language: Option<Arc<Language>>,
|
||||||
style: &EditorStyle,
|
style: &theme::Editor,
|
||||||
) -> ParsedInfo {
|
) -> ParsedInfo {
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
let mut highlights = Vec::new();
|
let mut highlights = Vec::new();
|
||||||
|
@ -382,7 +382,8 @@ fn render_blocks(
|
||||||
text.push_str(&block.text);
|
text.push_str(&block.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverBlockKind::Markdown => markdown::parse_markdown_block(
|
HoverBlockKind::Markdown => {
|
||||||
|
markdown::parse_markdown_block(
|
||||||
&block.text,
|
&block.text,
|
||||||
language_registry,
|
language_registry,
|
||||||
language.clone(),
|
language.clone(),
|
||||||
|
@ -391,7 +392,9 @@ fn render_blocks(
|
||||||
&mut highlights,
|
&mut highlights,
|
||||||
&mut region_ranges,
|
&mut region_ranges,
|
||||||
&mut regions,
|
&mut regions,
|
||||||
),
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
HoverBlockKind::Code { language } => {
|
HoverBlockKind::Code { language } => {
|
||||||
if let Some(language) = language_registry
|
if let Some(language) = language_registry
|
||||||
|
@ -482,7 +485,7 @@ pub struct InfoPopover {
|
||||||
symbol_range: DocumentRange,
|
symbol_range: DocumentRange,
|
||||||
pub blocks: Vec<HoverBlock>,
|
pub blocks: Vec<HoverBlock>,
|
||||||
language: Option<Arc<Language>>,
|
language: Option<Arc<Language>>,
|
||||||
rendered_content: Option<ParsedInfo>,
|
parsed_content: Option<ParsedInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -500,38 +503,25 @@ impl InfoPopover {
|
||||||
style: &EditorStyle,
|
style: &EditorStyle,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> AnyElement<Editor> {
|
) -> AnyElement<Editor> {
|
||||||
if let Some(rendered) = &self.rendered_content {
|
if let Some(parsed) = &self.parsed_content {
|
||||||
if rendered.theme_id != style.theme_id {
|
if parsed.theme_id != style.theme_id {
|
||||||
self.rendered_content = None;
|
self.parsed_content = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rendered_content = self.rendered_content.get_or_insert_with(|| {
|
let rendered = if let Some(parsed) = &self.parsed_content {
|
||||||
render_blocks(
|
|
||||||
style.theme_id,
|
|
||||||
&self.blocks,
|
|
||||||
self.project.read(cx).languages(),
|
|
||||||
self.language.clone(),
|
|
||||||
style,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| {
|
|
||||||
let mut region_id = 0;
|
|
||||||
let view_id = cx.view_id();
|
let view_id = cx.view_id();
|
||||||
|
let regions = parsed.regions.clone();
|
||||||
let code_span_background_color = style.document_highlight_read_background;
|
let code_span_background_color = style.document_highlight_read_background;
|
||||||
let regions = rendered_content.regions.clone();
|
|
||||||
Flex::column()
|
let mut region_id = 0;
|
||||||
.scrollable::<HoverBlock>(1, None, cx)
|
|
||||||
.with_child(
|
Text::new(parsed.text.clone(), style.text.clone())
|
||||||
Text::new(rendered_content.text.clone(), style.text.clone())
|
.with_highlights(parsed.highlights.clone())
|
||||||
.with_highlights(rendered_content.highlights.clone())
|
.with_custom_runs(parsed.region_ranges.clone(), move |ix, bounds, scene, _| {
|
||||||
.with_custom_runs(
|
|
||||||
rendered_content.region_ranges.clone(),
|
|
||||||
move |ix, bounds, scene, _| {
|
|
||||||
region_id += 1;
|
region_id += 1;
|
||||||
let region = regions[ix].clone();
|
let region = regions[ix].clone();
|
||||||
|
|
||||||
if let Some(url) = region.link_url {
|
if let Some(url) = region.link_url {
|
||||||
scene.push_cursor_region(CursorRegion {
|
scene.push_cursor_region(CursorRegion {
|
||||||
bounds,
|
bounds,
|
||||||
|
@ -539,12 +529,12 @@ impl InfoPopover {
|
||||||
});
|
});
|
||||||
scene.push_mouse_region(
|
scene.push_mouse_region(
|
||||||
MouseRegion::new::<Self>(view_id, region_id, bounds)
|
MouseRegion::new::<Self>(view_id, region_id, bounds)
|
||||||
.on_click::<Editor, _>(
|
.on_click::<Editor, _>(MouseButton::Left, move |_, _, cx| {
|
||||||
MouseButton::Left,
|
cx.platform().open_url(&url)
|
||||||
move |_, _, cx| cx.platform().open_url(&url),
|
}),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if region.code {
|
if region.code {
|
||||||
scene.push_quad(gpui::Quad {
|
scene.push_quad(gpui::Quad {
|
||||||
bounds,
|
bounds,
|
||||||
|
@ -553,10 +543,47 @@ impl InfoPopover {
|
||||||
corner_radii: (2.0).into(),
|
corner_radii: (2.0).into(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
)
|
.with_soft_wrap(true)
|
||||||
.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(rendered)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(style.hover_popover.container)
|
.with_style(style.hover_popover.container)
|
||||||
})
|
})
|
||||||
|
@ -877,7 +904,8 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let style = editor.style(cx);
|
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!(
|
assert_eq!(
|
||||||
rendered.text,
|
rendered.text,
|
||||||
code_str.trim(),
|
code_str.trim(),
|
||||||
|
@ -1069,7 +1097,8 @@ mod tests {
|
||||||
expected_styles,
|
expected_styles,
|
||||||
} in &rows[0..]
|
} 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_text, ranges) = marked_text_ranges(expected_marked_text, false);
|
||||||
let expected_highlights = ranges
|
let expected_highlights = ranges
|
||||||
|
@ -1339,7 +1368,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
popover
|
popover
|
||||||
.rendered_content
|
.parsed_content
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("should have label text for new type hint")
|
.expect("should have label text for new type hint")
|
||||||
.text,
|
.text,
|
||||||
|
@ -1403,7 +1432,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
popover
|
popover
|
||||||
.rendered_content
|
.parsed_content
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("should have label text for struct hint")
|
.expect("should have label text for struct hint")
|
||||||
.text,
|
.text,
|
||||||
|
|
|
@ -7,7 +7,7 @@ pub use crate::{
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
|
diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
|
||||||
language_settings::{language_settings, LanguageSettings},
|
language_settings::{language_settings, LanguageSettings},
|
||||||
markdown,
|
markdown::parse_markdown,
|
||||||
outline::OutlineItem,
|
outline::OutlineItem,
|
||||||
syntax_map::{
|
syntax_map::{
|
||||||
SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
|
SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
|
||||||
|
@ -145,7 +145,7 @@ pub struct Diagnostic {
|
||||||
pub is_unnecessary: bool,
|
pub is_unnecessary: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare_completion_documentation(
|
pub async fn prepare_completion_documentation(
|
||||||
documentation: &lsp::Documentation,
|
documentation: &lsp::Documentation,
|
||||||
language_registry: &Arc<LanguageRegistry>,
|
language_registry: &Arc<LanguageRegistry>,
|
||||||
language: Option<Arc<Language>>,
|
language: Option<Arc<Language>>,
|
||||||
|
@ -170,7 +170,7 @@ pub fn prepare_completion_documentation(
|
||||||
}
|
}
|
||||||
|
|
||||||
lsp::MarkupKind::Markdown => {
|
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))
|
Some(Documentation::MultiLineMarkdown(parsed))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,7 +2,6 @@ use std::ops::Range;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{Language, LanguageRegistry};
|
use crate::{Language, LanguageRegistry};
|
||||||
use futures::FutureExt;
|
|
||||||
use gpui::fonts::{HighlightStyle, Underline, Weight};
|
use gpui::fonts::{HighlightStyle, Underline, Weight};
|
||||||
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
|
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
|
||||||
|
|
||||||
|
@ -20,7 +19,7 @@ pub struct ParsedRegion {
|
||||||
pub link_url: Option<String>,
|
pub link_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_markdown(
|
pub async fn parse_markdown(
|
||||||
markdown: &str,
|
markdown: &str,
|
||||||
language_registry: &Arc<LanguageRegistry>,
|
language_registry: &Arc<LanguageRegistry>,
|
||||||
language: Option<Arc<Language>>,
|
language: Option<Arc<Language>>,
|
||||||
|
@ -40,7 +39,8 @@ pub fn parse_markdown(
|
||||||
&mut highlights,
|
&mut highlights,
|
||||||
&mut region_ranges,
|
&mut region_ranges,
|
||||||
&mut regions,
|
&mut regions,
|
||||||
);
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
ParsedMarkdown {
|
ParsedMarkdown {
|
||||||
text,
|
text,
|
||||||
|
@ -50,7 +50,7 @@ pub fn parse_markdown(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_markdown_block(
|
pub async fn parse_markdown_block(
|
||||||
markdown: &str,
|
markdown: &str,
|
||||||
language_registry: &Arc<LanguageRegistry>,
|
language_registry: &Arc<LanguageRegistry>,
|
||||||
language: Option<Arc<Language>>,
|
language: Option<Arc<Language>>,
|
||||||
|
@ -143,8 +143,8 @@ pub fn parse_markdown_block(
|
||||||
current_language = if let CodeBlockKind::Fenced(language) = kind {
|
current_language = if let CodeBlockKind::Fenced(language) = kind {
|
||||||
language_registry
|
language_registry
|
||||||
.language_for_name(language.as_ref())
|
.language_for_name(language.as_ref())
|
||||||
.now_or_never()
|
.await
|
||||||
.and_then(Result::ok)
|
.ok()
|
||||||
} else {
|
} else {
|
||||||
language.clone()
|
language.clone()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue