Fix clicking on file links in editor (#25117)
Closes #18641 Contributes: #13194 Release Notes: - Open LSP documentation file links in Zed not the system opener - Render completion documentation markdown consistently with documentation markdown
This commit is contained in:
parent
ebbc6a9752
commit
1678e3cbf1
16 changed files with 353 additions and 352 deletions
|
@ -1,7 +1,6 @@
|
|||
pub mod parser;
|
||||
|
||||
use crate::parser::CodeBlockKind;
|
||||
use futures::FutureExt;
|
||||
use gpui::{
|
||||
actions, point, quad, AnyElement, App, Bounds, ClipboardItem, CursorStyle, DispatchPhase,
|
||||
Edges, Entity, FocusHandle, Focusable, FontStyle, FontWeight, GlobalElementId, Hitbox, Hsla,
|
||||
|
@ -12,7 +11,7 @@ use gpui::{
|
|||
use language::{Language, LanguageRegistry, Rope};
|
||||
use parser::{parse_links_only, parse_markdown, MarkdownEvent, MarkdownTag, MarkdownTagEnd};
|
||||
|
||||
use std::{iter, mem, ops::Range, rc::Rc, sync::Arc};
|
||||
use std::{collections::HashMap, iter, mem, ops::Range, rc::Rc, sync::Arc};
|
||||
use theme::SyntaxTheme;
|
||||
use ui::{prelude::*, Tooltip};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
@ -49,7 +48,7 @@ impl Default for MarkdownStyle {
|
|||
}
|
||||
|
||||
pub struct Markdown {
|
||||
source: String,
|
||||
source: SharedString,
|
||||
selection: Selection,
|
||||
pressed_link: Option<RenderedLink>,
|
||||
autoscroll_request: Option<usize>,
|
||||
|
@ -60,6 +59,7 @@ pub struct Markdown {
|
|||
focus_handle: FocusHandle,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
fallback_code_block_language: Option<String>,
|
||||
open_url: Option<Box<dyn Fn(SharedString, &mut Window, &mut App)>>,
|
||||
options: Options,
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ actions!(markdown, [Copy]);
|
|||
|
||||
impl Markdown {
|
||||
pub fn new(
|
||||
source: String,
|
||||
source: SharedString,
|
||||
style: MarkdownStyle,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
fallback_code_block_language: Option<String>,
|
||||
|
@ -97,13 +97,24 @@ impl Markdown {
|
|||
parse_links_only: false,
|
||||
copy_code_block_buttons: true,
|
||||
},
|
||||
open_url: None,
|
||||
};
|
||||
this.parse(window, cx);
|
||||
this
|
||||
}
|
||||
|
||||
pub fn open_url(
|
||||
self,
|
||||
open_url: impl Fn(SharedString, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
open_url: Some(Box::new(open_url)),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_text(
|
||||
source: String,
|
||||
source: SharedString,
|
||||
style: MarkdownStyle,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
fallback_code_block_language: Option<String>,
|
||||
|
@ -127,6 +138,7 @@ impl Markdown {
|
|||
parse_links_only: true,
|
||||
copy_code_block_buttons: true,
|
||||
},
|
||||
open_url: None,
|
||||
};
|
||||
this.parse(window, cx);
|
||||
this
|
||||
|
@ -137,11 +149,11 @@ impl Markdown {
|
|||
}
|
||||
|
||||
pub fn append(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.source.push_str(text);
|
||||
self.source = SharedString::new(self.source.to_string() + text);
|
||||
self.parse(window, cx);
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, source: String, window: &mut Window, cx: &mut Context<Self>) {
|
||||
pub fn reset(&mut self, source: SharedString, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if source == self.source() {
|
||||
return;
|
||||
}
|
||||
|
@ -176,17 +188,38 @@ impl Markdown {
|
|||
return;
|
||||
}
|
||||
|
||||
let text = self.source.clone();
|
||||
let source = self.source.clone();
|
||||
let parse_text_only = self.options.parse_links_only;
|
||||
let language_registry = self.language_registry.clone();
|
||||
let fallback = self.fallback_code_block_language.clone();
|
||||
let parsed = cx.background_spawn(async move {
|
||||
let text = SharedString::from(text);
|
||||
let events = match parse_text_only {
|
||||
true => Arc::from(parse_links_only(text.as_ref())),
|
||||
false => Arc::from(parse_markdown(text.as_ref())),
|
||||
};
|
||||
if parse_text_only {
|
||||
return anyhow::Ok(ParsedMarkdown {
|
||||
events: Arc::from(parse_links_only(source.as_ref())),
|
||||
source,
|
||||
languages: HashMap::default(),
|
||||
});
|
||||
}
|
||||
let (events, language_names) = parse_markdown(&source);
|
||||
let mut languages = HashMap::with_capacity(language_names.len());
|
||||
for name in language_names {
|
||||
if let Some(registry) = language_registry.as_ref() {
|
||||
let language = if !name.is_empty() {
|
||||
registry.language_for_name(&name)
|
||||
} else if let Some(fallback) = &fallback {
|
||||
registry.language_for_name(fallback)
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
if let Ok(language) = language.await {
|
||||
languages.insert(name, language);
|
||||
}
|
||||
}
|
||||
}
|
||||
anyhow::Ok(ParsedMarkdown {
|
||||
source: text,
|
||||
events,
|
||||
source,
|
||||
events: Arc::from(events),
|
||||
languages,
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -217,12 +250,7 @@ impl Markdown {
|
|||
|
||||
impl Render for Markdown {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
MarkdownElement::new(
|
||||
cx.entity().clone(),
|
||||
self.style.clone(),
|
||||
self.language_registry.clone(),
|
||||
self.fallback_code_block_language.clone(),
|
||||
)
|
||||
MarkdownElement::new(cx.entity().clone(), self.style.clone())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,6 +298,7 @@ impl Selection {
|
|||
pub struct ParsedMarkdown {
|
||||
source: SharedString,
|
||||
events: Arc<[(Range<usize>, MarkdownEvent)]>,
|
||||
languages: HashMap<SharedString, Arc<Language>>,
|
||||
}
|
||||
|
||||
impl ParsedMarkdown {
|
||||
|
@ -285,61 +314,11 @@ impl ParsedMarkdown {
|
|||
pub struct MarkdownElement {
|
||||
markdown: Entity<Markdown>,
|
||||
style: MarkdownStyle,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
fallback_code_block_language: Option<String>,
|
||||
}
|
||||
|
||||
impl MarkdownElement {
|
||||
fn new(
|
||||
markdown: Entity<Markdown>,
|
||||
style: MarkdownStyle,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
fallback_code_block_language: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
markdown,
|
||||
style,
|
||||
language_registry,
|
||||
fallback_code_block_language,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_language(
|
||||
&self,
|
||||
name: &str,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<Arc<Language>> {
|
||||
let language_test = self.language_registry.as_ref()?.language_for_name(name);
|
||||
|
||||
let language_name = match language_test.now_or_never() {
|
||||
Some(Ok(_)) => String::from(name),
|
||||
Some(Err(_)) if !name.is_empty() && self.fallback_code_block_language.is_some() => {
|
||||
self.fallback_code_block_language.clone().unwrap()
|
||||
}
|
||||
_ => String::new(),
|
||||
};
|
||||
|
||||
let language = self
|
||||
.language_registry
|
||||
.as_ref()?
|
||||
.language_for_name(language_name.as_str())
|
||||
.map(|language| language.ok())
|
||||
.shared();
|
||||
|
||||
match language.clone().now_or_never() {
|
||||
Some(language) => language,
|
||||
None => {
|
||||
let markdown = self.markdown.downgrade();
|
||||
window
|
||||
.spawn(cx, |mut cx| async move {
|
||||
language.await;
|
||||
markdown.update(&mut cx, |_, cx| cx.notify())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
None
|
||||
}
|
||||
}
|
||||
fn new(markdown: Entity<Markdown>, style: MarkdownStyle) -> Self {
|
||||
Self { markdown, style }
|
||||
}
|
||||
|
||||
fn paint_selection(
|
||||
|
@ -452,7 +431,7 @@ impl MarkdownElement {
|
|||
pending: true,
|
||||
};
|
||||
window.focus(&markdown.focus_handle);
|
||||
window.prevent_default()
|
||||
window.prevent_default();
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
|
@ -492,11 +471,15 @@ impl MarkdownElement {
|
|||
});
|
||||
self.on_mouse_event(window, cx, {
|
||||
let rendered_text = rendered_text.clone();
|
||||
move |markdown, event: &MouseUpEvent, phase, _, cx| {
|
||||
move |markdown, event: &MouseUpEvent, phase, window, cx| {
|
||||
if phase.bubble() {
|
||||
if let Some(pressed_link) = markdown.pressed_link.take() {
|
||||
if Some(&pressed_link) == rendered_text.link_for_position(event.position) {
|
||||
cx.open_url(&pressed_link.destination_url);
|
||||
if let Some(open_url) = markdown.open_url.as_mut() {
|
||||
open_url(pressed_link.destination_url, window, cx);
|
||||
} else {
|
||||
cx.open_url(&pressed_link.destination_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if markdown.selection.pending {
|
||||
|
@ -617,7 +600,7 @@ impl Element for MarkdownElement {
|
|||
}
|
||||
MarkdownTag::CodeBlock(kind) => {
|
||||
let language = if let CodeBlockKind::Fenced(language) = kind {
|
||||
self.load_language(language.as_ref(), window, cx)
|
||||
parsed_markdown.languages.get(language).cloned()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue