Cargo.lock ๐
@@ -3605,6 +3605,7 @@ dependencies = [
"linkify",
"log",
"lsp",
+ "markdown",
"multi_buffer",
"ordered-float 2.10.0",
"parking_lot",
Ephram , Conrad Irwin , and Antonio created
Release Notes:
- Fixed #5236
- Added the ability to select and copy text from information popovers
https://github.com/zed-industries/zed/assets/50590465/d5c86623-342b-474b-913e-d07cc3f76de4
---------
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Antonio <ascii@zed.dev>
Cargo.lock | 1
crates/editor/Cargo.toml | 1
crates/editor/src/editor.rs | 5
crates/editor/src/element.rs | 3
crates/editor/src/hover_popover.rs | 571 +++++++++-----------
crates/gpui/src/text_system/line.rs | 8
crates/markdown/examples/markdown.rs | 82 +-
crates/markdown/examples/markdown_as_child.rs | 120 ++++
crates/markdown/src/markdown.rs | 164 +++++
crates/markdown/src/parser.rs | 15
crates/project/src/project.rs | 2
crates/recent_projects/src/dev_servers.rs | 24
12 files changed, 596 insertions(+), 400 deletions(-)
@@ -3605,6 +3605,7 @@ dependencies = [
"linkify",
"log",
"lsp",
+ "markdown",
"multi_buffer",
"ordered-float 2.10.0",
"parking_lot",
@@ -49,6 +49,7 @@ lazy_static.workspace = true
linkify.workspace = true
log.workspace = true
lsp.workspace = true
+markdown.workspace = true
multi_buffer.workspace = true
ordered-float.workspace = true
parking_lot.workspace = true
@@ -11604,8 +11604,11 @@ impl Editor {
if let Some(blame) = self.blame.as_ref() {
blame.update(cx, GitBlame::blur)
}
+ if !self.hover_state.focused(cx) {
+ hide_hover(self, cx);
+ }
+
self.hide_context_menu(cx);
- hide_hover(self, cx);
cx.emit(EditorEvent::Blurred);
cx.notify();
}
@@ -3740,6 +3740,9 @@ impl EditorElement {
move |event: &MouseMoveEvent, phase, cx| {
if phase == DispatchPhase::Bubble {
editor.update(cx, |editor, cx| {
+ if editor.hover_state.focused(cx) {
+ return;
+ }
if event.pressed_button == Some(MouseButton::Left)
|| event.pressed_button == Some(MouseButton::Middle)
{
@@ -5,24 +5,26 @@ use crate::{
Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,
EditorStyle, Hover, RangeToAnchorExt,
};
-use futures::{stream::FuturesUnordered, FutureExt};
use gpui::{
- div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, MouseButton,
- ParentElement, Pixels, ScrollHandle, SharedString, Size, StatefulInteractiveElement, Styled,
- Task, ViewContext, WeakView,
+ div, px, AnyElement, AsyncWindowContext, CursorStyle, FontWeight, Hsla, InteractiveElement,
+ IntoElement, MouseButton, ParentElement, Pixels, ScrollHandle, SharedString, Size,
+ StatefulInteractiveElement, StyleRefinement, Styled, Task, TextStyleRefinement, View,
+ ViewContext, WeakView,
};
-use language::{markdown, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown};
-
+use itertools::Itertools;
+use language::{DiagnosticEntry, Language, LanguageRegistry};
use lsp::DiagnosticSeverity;
+use markdown::{Markdown, MarkdownStyle};
use multi_buffer::ToOffset;
-use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
+use project::{HoverBlock, InlayHintLabelPart};
use settings::Settings;
-use smol::stream::StreamExt;
+use std::rc::Rc;
+use std::{borrow::Cow, cell::RefCell};
use std::{ops::Range, sync::Arc, time::Duration};
+use theme::ThemeSettings;
use ui::{prelude::*, window_is_transparent, Tooltip};
use util::TryFutureExt;
use workspace::Workspace;
-
pub const HOVER_DELAY_MILLIS: u64 = 350;
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
@@ -40,6 +42,9 @@ pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
/// depending on whether a point to hover over is provided.
pub fn hover_at(editor: &mut Editor, anchor: Option<Anchor>, cx: &mut ViewContext<Editor>) {
if EditorSettings::get_global(cx).hover_popover_enabled {
+ if show_keyboard_hover(editor, cx) {
+ return;
+ }
if let Some(anchor) = anchor {
show_hover(editor, anchor, false, cx);
} else {
@@ -48,6 +53,20 @@ pub fn hover_at(editor: &mut Editor, anchor: Option<Anchor>, cx: &mut ViewContex
}
}
+pub fn show_keyboard_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
+ let info_popovers = editor.hover_state.info_popovers.clone();
+ for p in info_popovers {
+ let keyboard_grace = p.keyboard_grace.borrow();
+ if *keyboard_grace {
+ if let Some(anchor) = p.anchor {
+ show_hover(editor, anchor, false, cx);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
pub struct InlayHover {
pub range: InlayHighlight,
pub tooltip: HoverBlock,
@@ -113,12 +132,14 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;
let blocks = vec![inlay_hover.tooltip];
- let parsed_content = parse_blocks(&blocks, &language_registry, None).await;
+ let parsed_content = parse_blocks(&blocks, &language_registry, None, &mut cx).await;
let hover_popover = InfoPopover {
symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
parsed_content,
scroll_handle: ScrollHandle::new(),
+ keyboard_grace: Rc::new(RefCell::new(false)),
+ anchor: None,
};
this.update(&mut cx, |this, cx| {
@@ -291,39 +312,40 @@ fn show_hover(
let snapshot = this.update(&mut cx, |this, cx| this.snapshot(cx))?;
let mut hover_highlights = Vec::with_capacity(hovers_response.len());
let mut info_popovers = Vec::with_capacity(hovers_response.len());
- let mut info_popover_tasks = hovers_response
- .into_iter()
- .map(|hover_result| async {
- // Create symbol range of anchors for highlighting and filtering of future requests.
- let range = hover_result
- .range
- .and_then(|range| {
- let start = snapshot
- .buffer_snapshot
- .anchor_in_excerpt(excerpt_id, range.start)?;
- let end = snapshot
- .buffer_snapshot
- .anchor_in_excerpt(excerpt_id, range.end)?;
-
- Some(start..end)
- })
- .unwrap_or_else(|| anchor..anchor);
-
- let blocks = hover_result.contents;
- let language = hover_result.language;
- let parsed_content = parse_blocks(&blocks, &language_registry, language).await;
-
- (
- range.clone(),
- InfoPopover {
- symbol_range: RangeInEditor::Text(range),
- parsed_content,
- scroll_handle: ScrollHandle::new(),
- },
- )
- })
- .collect::<FuturesUnordered<_>>();
- while let Some((highlight_range, info_popover)) = info_popover_tasks.next().await {
+ let mut info_popover_tasks = Vec::with_capacity(hovers_response.len());
+
+ for hover_result in hovers_response {
+ // Create symbol range of anchors for highlighting and filtering of future requests.
+ let range = hover_result
+ .range
+ .and_then(|range| {
+ let start = snapshot
+ .buffer_snapshot
+ .anchor_in_excerpt(excerpt_id, range.start)?;
+ let end = snapshot
+ .buffer_snapshot
+ .anchor_in_excerpt(excerpt_id, range.end)?;
+
+ Some(start..end)
+ })
+ .unwrap_or_else(|| anchor..anchor);
+
+ let blocks = hover_result.contents;
+ let language = hover_result.language;
+ let parsed_content =
+ parse_blocks(&blocks, &language_registry, language, &mut cx).await;
+ info_popover_tasks.push((
+ range.clone(),
+ InfoPopover {
+ symbol_range: RangeInEditor::Text(range),
+ parsed_content,
+ scroll_handle: ScrollHandle::new(),
+ keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
+ anchor: Some(anchor),
+ },
+ ));
+ }
+ for (highlight_range, info_popover) in info_popover_tasks {
hover_highlights.push(highlight_range);
info_popovers.push(info_popover);
}
@@ -357,72 +379,81 @@ async fn parse_blocks(
blocks: &[HoverBlock],
language_registry: &Arc<LanguageRegistry>,
language: Option<Arc<Language>>,
-) -> markdown::ParsedMarkdown {
- let mut text = String::new();
- let mut highlights = Vec::new();
- let mut region_ranges = Vec::new();
- let mut regions = Vec::new();
-
- for block in blocks {
- match &block.kind {
- HoverBlockKind::PlainText => {
- markdown::new_paragraph(&mut text, &mut Vec::new());
- text.push_str(&block.text.replace("\\n", "\n"));
- }
+ cx: &mut AsyncWindowContext,
+) -> Option<View<Markdown>> {
+ let fallback_language_name = if let Some(ref l) = language {
+ let l = Arc::clone(l);
+ Some(l.lsp_id().clone())
+ } else {
+ None
+ };
- HoverBlockKind::Markdown => {
- markdown::parse_markdown_block(
- &block.text.replace("\\n", "\n"),
- language_registry,
- language.clone(),
- &mut text,
- &mut highlights,
- &mut region_ranges,
- &mut regions,
- )
- .await
+ let combined_text = blocks
+ .iter()
+ .map(|block| match &block.kind {
+ project::HoverBlockKind::PlainText | project::HoverBlockKind::Markdown => {
+ Cow::Borrowed(block.text.trim())
}
-
- HoverBlockKind::Code { language } => {
- if let Some(language) = language_registry
- .language_for_name(language)
- .now_or_never()
- .and_then(Result::ok)
- {
- markdown::highlight_code(&mut text, &mut highlights, &block.text, &language);
- } else {
- text.push_str(&block.text);
- }
+ project::HoverBlockKind::Code { language } => {
+ Cow::Owned(format!("```{}\n{}\n```", language, block.text.trim()))
}
- }
- }
+ })
+ .join("\n\n");
+
+ let rendered_block = cx
+ .new_view(|cx| {
+ let settings = ThemeSettings::get_global(cx);
+ let buffer_font_family = settings.buffer_font.family.clone();
+ let mut base_style = cx.text_style();
+ base_style.refine(&TextStyleRefinement {
+ font_family: Some(buffer_font_family.clone()),
+ color: Some(cx.theme().colors().editor_foreground),
+ ..Default::default()
+ });
- let leading_space = text.chars().take_while(|c| c.is_whitespace()).count();
- if leading_space > 0 {
- highlights = highlights
- .into_iter()
- .map(|(range, style)| {
- (
- range.start.saturating_sub(leading_space)
- ..range.end.saturating_sub(leading_space),
- style,
- )
- })
- .collect();
- region_ranges = region_ranges
- .into_iter()
- .map(|range| {
- range.start.saturating_sub(leading_space)..range.end.saturating_sub(leading_space)
- })
- .collect();
- }
+ let markdown_style = MarkdownStyle {
+ base_text_style: base_style,
+ code_block: StyleRefinement::default().mt(rems(1.)).mb(rems(1.)),
+ inline_code: TextStyleRefinement {
+ background_color: Some(cx.theme().colors().background),
+ ..Default::default()
+ },
+ rule_color: Color::Muted.color(cx),
+ block_quote_border_color: Color::Muted.color(cx),
+ block_quote: TextStyleRefinement {
+ color: Some(Color::Muted.color(cx)),
+ ..Default::default()
+ },
+ link: TextStyleRefinement {
+ color: Some(cx.theme().colors().editor_foreground),
+ underline: Some(gpui::UnderlineStyle {
+ thickness: px(1.),
+ color: Some(cx.theme().colors().editor_foreground),
+ wavy: false,
+ }),
+ ..Default::default()
+ },
+ syntax: cx.theme().syntax().clone(),
+ selection_background_color: { cx.theme().players().local().selection },
+ break_style: Default::default(),
+ heading: StyleRefinement::default()
+ .font_weight(FontWeight::BOLD)
+ .text_base()
+ .mt(rems(1.))
+ .mb_0(),
+ };
- ParsedMarkdown {
- text: text.trim().to_string(),
- highlights,
- region_ranges,
- regions,
- }
+ Markdown::new(
+ combined_text,
+ markdown_style.clone(),
+ Some(language_registry.clone()),
+ cx,
+ fallback_language_name,
+ )
+ })
+ .ok();
+
+ rendered_block
}
#[derive(Default, Debug)]
@@ -444,7 +475,7 @@ impl HoverState {
style: &EditorStyle,
visible_rows: Range<DisplayRow>,
max_size: Size<Pixels>,
- workspace: Option<WeakView<Workspace>>,
+ _workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> Option<(DisplayPoint, Vec<AnyElement>)> {
// If there is a diagnostic, position the popovers based on that.
@@ -482,29 +513,39 @@ impl HoverState {
elements.push(diagnostic_popover.render(style, max_size, cx));
}
for info_popover in &mut self.info_popovers {
- elements.push(info_popover.render(style, max_size, workspace.clone(), cx));
+ elements.push(info_popover.render(max_size, cx));
}
Some((point, elements))
}
+
+ pub fn focused(&self, cx: &mut ViewContext<Editor>) -> bool {
+ let mut hover_popover_is_focused = false;
+ for info_popover in &self.info_popovers {
+ for markdown_view in &info_popover.parsed_content {
+ if markdown_view.focus_handle(cx).is_focused(cx) {
+ hover_popover_is_focused = true;
+ }
+ }
+ }
+ return hover_popover_is_focused;
+ }
}
-#[derive(Clone, Debug)]
+#[derive(Debug, Clone)]
+
pub struct InfoPopover {
pub symbol_range: RangeInEditor,
- pub parsed_content: ParsedMarkdown,
+ pub parsed_content: Option<View<Markdown>>,
pub scroll_handle: ScrollHandle,
+ pub keyboard_grace: Rc<RefCell<bool>>,
+ pub anchor: Option<Anchor>,
}
impl InfoPopover {
- pub fn render(
- &mut self,
- style: &EditorStyle,
- max_size: Size<Pixels>,
- workspace: Option<WeakView<Workspace>>,
- cx: &mut ViewContext<Editor>,
- ) -> AnyElement {
- div()
+ pub fn render(&mut self, max_size: Size<Pixels>, cx: &mut ViewContext<Editor>) -> AnyElement {
+ let keyboard_grace = Rc::clone(&self.keyboard_grace);
+ let mut d = div()
.id("info_popover")
.elevation_2(cx)
.overflow_y_scroll()
@@ -514,15 +555,17 @@ impl InfoPopover {
// Prevent a mouse down/move on the popover from being propagated to the editor,
// because that would dismiss the popover.
.on_mouse_move(|_, cx| cx.stop_propagation())
- .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
- .child(div().p_2().child(crate::render_parsed_markdown(
- "content",
- &self.parsed_content,
- style,
- workspace,
- cx,
- )))
- .into_any_element()
+ .on_mouse_down(MouseButton::Left, move |_, cx| {
+ let mut keyboard_grace = keyboard_grace.borrow_mut();
+ *keyboard_grace = false;
+ cx.stop_propagation();
+ })
+ .p_2();
+
+ if let Some(markdown) = &self.parsed_content {
+ d = d.child(markdown.clone());
+ }
+ d.into_any_element()
}
pub fn scroll(&self, amount: &ScrollAmount, cx: &mut ViewContext<Editor>) {
@@ -642,17 +685,33 @@ mod tests {
InlayId, PointForPosition,
};
use collections::BTreeSet;
- use gpui::{FontWeight, HighlightStyle, UnderlineStyle};
use indoc::indoc;
use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet};
use lsp::LanguageServerId;
- use project::{HoverBlock, HoverBlockKind};
+ use markdown::parser::MarkdownEvent;
use smol::stream::StreamExt;
use std::sync::atomic;
use std::sync::atomic::AtomicUsize;
use text::Bias;
- use unindent::Unindent;
- use util::test::marked_text_ranges;
+
+ impl InfoPopover {
+ fn get_rendered_text(&self, cx: &gpui::AppContext) -> String {
+ let mut rendered_text = String::new();
+ if let Some(parsed_content) = self.parsed_content.clone() {
+ let markdown = parsed_content.read(cx);
+ let text = markdown.parsed_markdown().source().to_string();
+ let data = markdown.parsed_markdown().events();
+ let slice = data;
+
+ for (range, event) in slice.iter() {
+ if [MarkdownEvent::Text, MarkdownEvent::Code].contains(event) {
+ rendered_text.push_str(&text[range.clone()])
+ }
+ }
+ }
+ rendered_text
+ }
+ }
#[gpui::test]
async fn test_mouse_hover_info_popover_with_autocomplete_popover(
@@ -736,7 +795,7 @@ mod tests {
.advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
requests.next().await;
- cx.editor(|editor, _| {
+ cx.editor(|editor, cx| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.info_popovers.len(),
@@ -744,14 +803,13 @@ mod tests {
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
- let rendered = editor
+ let rendered_text = editor
.hover_state
.info_popovers
.first()
- .cloned()
.unwrap()
- .parsed_content;
- assert_eq!(rendered.text, "some basic docs".to_string())
+ .get_rendered_text(cx);
+ assert_eq!(rendered_text, "some basic docs".to_string())
});
// check that the completion menu is still visible and that there still has only been 1 completion request
@@ -777,7 +835,7 @@ mod tests {
assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
//verify the information popover is still visible and unchanged
- cx.editor(|editor, _| {
+ cx.editor(|editor, cx| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.info_popovers.len(),
@@ -785,14 +843,14 @@ mod tests {
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
- let rendered = editor
+ let rendered_text = editor
.hover_state
.info_popovers
.first()
- .cloned()
.unwrap()
- .parsed_content;
- assert_eq!(rendered.text, "some basic docs".to_string())
+ .get_rendered_text(cx);
+
+ assert_eq!(rendered_text, "some basic docs".to_string())
});
// Mouse moved with no hover response dismisses
@@ -870,7 +928,7 @@ mod tests {
.advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
requests.next().await;
- cx.editor(|editor, _| {
+ cx.editor(|editor, cx| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.info_popovers.len(),
@@ -878,14 +936,14 @@ mod tests {
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
- let rendered = editor
+ let rendered_text = editor
.hover_state
.info_popovers
.first()
- .cloned()
.unwrap()
- .parsed_content;
- assert_eq!(rendered.text, "some basic docs".to_string())
+ .get_rendered_text(cx);
+
+ assert_eq!(rendered_text, "some basic docs".to_string())
});
// Mouse moved with no hover response dismisses
@@ -931,34 +989,49 @@ mod tests {
let symbol_range = cx.lsp_range(indoc! {"
ยซfnยป test() { println!(); }
"});
- cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
- Ok(Some(lsp::Hover {
- contents: lsp::HoverContents::Markup(lsp::MarkupContent {
- kind: lsp::MarkupKind::Markdown,
- value: "some other basic docs".to_string(),
- }),
- range: Some(symbol_range),
- }))
- })
- .next()
- .await;
+
+ cx.editor(|editor, _cx| {
+ assert!(!editor.hover_state.visible());
+
+ assert_eq!(
+ editor.hover_state.info_popovers.len(),
+ 0,
+ "Expected no hovers but got but got: {:?}",
+ editor.hover_state.info_popovers
+ );
+ });
+
+ let mut requests =
+ cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
+ Ok(Some(lsp::Hover {
+ contents: lsp::HoverContents::Markup(lsp::MarkupContent {
+ kind: lsp::MarkupKind::Markdown,
+ value: "some other basic docs".to_string(),
+ }),
+ range: Some(symbol_range),
+ }))
+ });
+
+ requests.next().await;
+ cx.dispatch_action(Hover);
cx.condition(|editor, _| editor.hover_state.visible()).await;
- cx.editor(|editor, _| {
+ cx.editor(|editor, cx| {
assert_eq!(
editor.hover_state.info_popovers.len(),
1,
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
- let rendered = editor
+
+ let rendered_text = editor
.hover_state
.info_popovers
.first()
- .cloned()
.unwrap()
- .parsed_content;
- assert_eq!(rendered.text, "some other basic docs".to_string())
+ .get_rendered_text(cx);
+
+ assert_eq!(rendered_text, "some other basic docs".to_string())
});
}
@@ -998,24 +1071,25 @@ mod tests {
})
.next()
.await;
+ cx.dispatch_action(Hover);
cx.condition(|editor, _| editor.hover_state.visible()).await;
- cx.editor(|editor, _| {
+ cx.editor(|editor, cx| {
assert_eq!(
editor.hover_state.info_popovers.len(),
1,
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
- let rendered = editor
+ let rendered_text = editor
.hover_state
.info_popovers
.first()
- .cloned()
.unwrap()
- .parsed_content;
+ .get_rendered_text(cx);
+
assert_eq!(
- rendered.text,
+ rendered_text,
"regular text for hover to show".to_string(),
"No empty string hovers should be shown"
);
@@ -1063,24 +1137,25 @@ mod tests {
.next()
.await;
+ cx.dispatch_action(Hover);
+
cx.condition(|editor, _| editor.hover_state.visible()).await;
- cx.editor(|editor, _| {
+ cx.editor(|editor, cx| {
assert_eq!(
editor.hover_state.info_popovers.len(),
1,
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
- let rendered = editor
+ let rendered_text = editor
.hover_state
.info_popovers
.first()
- .cloned()
.unwrap()
- .parsed_content;
+ .get_rendered_text(cx);
+
assert_eq!(
- rendered.text,
- code_str.trim(),
+ rendered_text, code_str,
"Should not have extra line breaks at end of rendered hover"
);
});
@@ -1156,153 +1231,6 @@ mod tests {
});
}
- #[gpui::test]
- fn test_render_blocks(cx: &mut gpui::TestAppContext) {
- init_test(cx, |_| {});
-
- let languages = Arc::new(LanguageRegistry::test(cx.executor()));
- let editor = cx.add_window(|cx| Editor::single_line(cx));
- editor
- .update(cx, |editor, _cx| {
- let style = editor.style.clone().unwrap();
-
- struct Row {
- blocks: Vec<HoverBlock>,
- expected_marked_text: String,
- expected_styles: Vec<HighlightStyle>,
- }
-
- let rows = &[
- // Strong emphasis
- Row {
- blocks: vec![HoverBlock {
- text: "one **two** three".to_string(),
- kind: HoverBlockKind::Markdown,
- }],
- expected_marked_text: "one ยซtwoยป three".to_string(),
- expected_styles: vec![HighlightStyle {
- font_weight: Some(FontWeight::BOLD),
- ..Default::default()
- }],
- },
- // Links
- Row {
- blocks: vec three".to_string(),
- kind: HoverBlockKind::Markdown,
- }],
- expected_marked_text: "one ยซtwoยป three".to_string(),
- expected_styles: vec![HighlightStyle {
- underline: Some(UnderlineStyle {
- thickness: 1.0.into(),
- ..Default::default()
- }),
- ..Default::default()
- }],
- },
- // Lists
- Row {
- blocks: vec
- - d"
- .unindent(),
- kind: HoverBlockKind::Markdown,
- }],
- expected_marked_text: "
- lists:
- - one
- - a
- - b
- - two
- - ยซcยป
- - d"
- .unindent(),
- expected_styles: vec![HighlightStyle {
- underline: Some(UnderlineStyle {
- thickness: 1.0.into(),
- ..Default::default()
- }),
- ..Default::default()
- }],
- },
- // Multi-paragraph list items
- Row {
- blocks: vec![HoverBlock {
- text: "
- * one two
- three
-
- * four five
- * six seven
- eight
-
- nine
- * ten
- * six"
- .unindent(),
- kind: HoverBlockKind::Markdown,
- }],
- expected_marked_text: "
- - one two three
- - four five
- - six seven eight
-
- nine
- - ten
- - six"
- .unindent(),
- expected_styles: vec![HighlightStyle {
- underline: Some(UnderlineStyle {
- thickness: 1.0.into(),
- ..Default::default()
- }),
- ..Default::default()
- }],
- },
- ];
-
- for Row {
- blocks,
- expected_marked_text,
- expected_styles,
- } in &rows[0..]
- {
- let rendered = smol::block_on(parse_blocks(&blocks, &languages, None));
-
- let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
- let expected_highlights = ranges
- .into_iter()
- .zip(expected_styles.iter().cloned())
- .collect::<Vec<_>>();
- assert_eq!(
- rendered.text, expected_text,
- "wrong text for input {blocks:?}"
- );
-
- let rendered_highlights: Vec<_> = rendered
- .highlights
- .iter()
- .filter_map(|(range, highlight)| {
- let highlight = highlight.to_highlight_style(&style.syntax)?;
- Some((range.clone(), highlight))
- })
- .collect();
-
- assert_eq!(
- rendered_highlights, expected_highlights,
- "wrong highlights for input {blocks:?}"
- );
- }
- })
- .unwrap();
- }
-
#[gpui::test]
async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) {
init_test(cx, |settings| {
@@ -1546,9 +1474,8 @@ mod tests {
"Popover range should match the new type label part"
);
assert_eq!(
- popover.parsed_content.text,
- format!("A tooltip for `{new_type_label}`"),
- "Rendered text should not anyhow alter backticks"
+ popover.get_rendered_text(cx),
+ format!("A tooltip for {new_type_label}"),
);
});
@@ -1602,7 +1529,7 @@ mod tests {
"Popover range should match the struct label part"
);
assert_eq!(
- popover.parsed_content.text,
+ popover.get_rendered_text(cx),
format!("A tooltip for {struct_label}"),
"Rendered markdown element should remove backticks from text"
);
@@ -109,7 +109,13 @@ fn paint_line(
wrap_boundaries: &[WrapBoundary],
cx: &mut WindowContext,
) -> Result<()> {
- let line_bounds = Bounds::new(origin, size(layout.width, line_height));
+ let line_bounds = Bounds::new(
+ origin,
+ size(
+ layout.width,
+ line_height * (wrap_boundaries.len() as f32 + 1.),
+ ),
+ );
cx.paint_layer(line_bounds, |cx| {
let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
let baseline_offset = point(px(0.), padding_top + layout.ascent);
@@ -1,5 +1,5 @@
use assets::Assets;
-use gpui::{prelude::*, App, KeyBinding, Task, View, WindowOptions};
+use gpui::{prelude::*, rgb, App, KeyBinding, StyleRefinement, Task, View, WindowOptions};
use language::{language_settings::AllLanguageSettings, LanguageRegistry};
use markdown::{Markdown, MarkdownStyle};
use node_runtime::FakeNodeRuntime;
@@ -105,44 +105,49 @@ pub fn main() {
cx.activate(true);
cx.open_window(WindowOptions::default(), |cx| {
cx.new_view(|cx| {
- MarkdownExample::new(
- MARKDOWN_EXAMPLE.to_string(),
- MarkdownStyle {
- code_block: gpui::TextStyleRefinement {
- font_family: Some("Zed Plex Mono".into()),
- color: Some(cx.theme().colors().editor_foreground),
- background_color: Some(cx.theme().colors().editor_background),
- ..Default::default()
- },
- inline_code: gpui::TextStyleRefinement {
- font_family: Some("Zed Plex Mono".into()),
- // @nate: Could we add inline-code specific styles to the theme?
- color: Some(cx.theme().colors().editor_foreground),
- background_color: Some(cx.theme().colors().editor_background),
- ..Default::default()
- },
- rule_color: Color::Muted.color(cx),
- block_quote_border_color: Color::Muted.color(cx),
- block_quote: gpui::TextStyleRefinement {
- color: Some(Color::Muted.color(cx)),
- ..Default::default()
- },
- link: gpui::TextStyleRefinement {
+ let markdown_style = MarkdownStyle {
+ base_text_style: gpui::TextStyle {
+ font_family: "Zed Plex Sans".into(),
+ color: cx.theme().colors().terminal_ansi_black,
+ ..Default::default()
+ },
+ code_block: StyleRefinement::default()
+ .font_family("Zed Plex Mono")
+ .m(rems(1.))
+ .bg(rgb(0xAAAAAAA)),
+ inline_code: gpui::TextStyleRefinement {
+ font_family: Some("Zed Mono".into()),
+ color: Some(cx.theme().colors().editor_foreground),
+ background_color: Some(cx.theme().colors().editor_background),
+ ..Default::default()
+ },
+ rule_color: Color::Muted.color(cx),
+ block_quote_border_color: Color::Muted.color(cx),
+ block_quote: gpui::TextStyleRefinement {
+ color: Some(Color::Muted.color(cx)),
+ ..Default::default()
+ },
+ link: gpui::TextStyleRefinement {
+ color: Some(Color::Accent.color(cx)),
+ underline: Some(gpui::UnderlineStyle {
+ thickness: px(1.),
color: Some(Color::Accent.color(cx)),
- underline: Some(gpui::UnderlineStyle {
- thickness: px(1.),
- color: Some(Color::Accent.color(cx)),
- wavy: false,
- }),
- ..Default::default()
- },
- syntax: cx.theme().syntax().clone(),
- selection_background_color: {
- let mut selection = cx.theme().players().local().selection;
- selection.fade_out(0.7);
- selection
- },
+ wavy: false,
+ }),
+ ..Default::default()
},
+ syntax: cx.theme().syntax().clone(),
+ selection_background_color: {
+ let mut selection = cx.theme().players().local().selection;
+ selection.fade_out(0.7);
+ selection
+ },
+ ..Default::default()
+ };
+
+ MarkdownExample::new(
+ MARKDOWN_EXAMPLE.to_string(),
+ markdown_style,
language_registry,
cx,
)
@@ -163,7 +168,8 @@ impl MarkdownExample {
language_registry: Arc<LanguageRegistry>,
cx: &mut WindowContext,
) -> Self {
- let markdown = cx.new_view(|cx| Markdown::new(text, style, Some(language_registry), cx));
+ let markdown =
+ cx.new_view(|cx| Markdown::new(text, style, Some(language_registry), cx, None));
Self { markdown }
}
}
@@ -0,0 +1,120 @@
+use assets::Assets;
+use gpui::*;
+use language::{language_settings::AllLanguageSettings, LanguageRegistry};
+use markdown::{Markdown, MarkdownStyle};
+use node_runtime::FakeNodeRuntime;
+use settings::SettingsStore;
+use std::sync::Arc;
+use theme::LoadThemes;
+use ui::div;
+use ui::prelude::*;
+
+const MARKDOWN_EXAMPLE: &'static str = r#"
+this text should be selectable
+
+wow so cool
+
+## Heading 2
+"#;
+pub fn main() {
+ env_logger::init();
+
+ App::new().with_assets(Assets).run(|cx| {
+ let store = SettingsStore::test(cx);
+ cx.set_global(store);
+ language::init(cx);
+ SettingsStore::update(cx, |store, cx| {
+ store.update_user_settings::<AllLanguageSettings>(cx, |_| {});
+ });
+ cx.bind_keys([KeyBinding::new("cmd-c", markdown::Copy, None)]);
+
+ let node_runtime = FakeNodeRuntime::new();
+ let language_registry = Arc::new(LanguageRegistry::new(
+ Task::ready(()),
+ cx.background_executor().clone(),
+ ));
+ languages::init(language_registry.clone(), node_runtime, cx);
+ theme::init(LoadThemes::JustBase, cx);
+ Assets.load_fonts(cx).unwrap();
+
+ cx.activate(true);
+ let _ = cx.open_window(WindowOptions::default(), |cx| {
+ cx.new_view(|cx| {
+ let markdown_style = MarkdownStyle {
+ base_text_style: gpui::TextStyle {
+ font_family: "Zed Mono".into(),
+ color: cx.theme().colors().text,
+ ..Default::default()
+ },
+ code_block: StyleRefinement {
+ text: Some(gpui::TextStyleRefinement {
+ font_family: Some("Zed Mono".into()),
+ background_color: Some(cx.theme().colors().editor_background),
+ ..Default::default()
+ }),
+ margin: gpui::EdgesRefinement {
+ top: Some(Length::Definite(rems(4.).into())),
+ left: Some(Length::Definite(rems(4.).into())),
+ right: Some(Length::Definite(rems(4.).into())),
+ bottom: Some(Length::Definite(rems(4.).into())),
+ },
+ ..Default::default()
+ },
+ inline_code: gpui::TextStyleRefinement {
+ font_family: Some("Zed Mono".into()),
+ background_color: Some(cx.theme().colors().editor_background),
+ ..Default::default()
+ },
+ rule_color: Color::Muted.color(cx),
+ block_quote_border_color: Color::Muted.color(cx),
+ block_quote: gpui::TextStyleRefinement {
+ color: Some(Color::Muted.color(cx)),
+ ..Default::default()
+ },
+ link: gpui::TextStyleRefinement {
+ color: Some(Color::Accent.color(cx)),
+ underline: Some(gpui::UnderlineStyle {
+ thickness: px(1.),
+ color: Some(Color::Accent.color(cx)),
+ wavy: false,
+ }),
+ ..Default::default()
+ },
+ syntax: cx.theme().syntax().clone(),
+ selection_background_color: {
+ let mut selection = cx.theme().players().local().selection;
+ selection.fade_out(0.7);
+ selection
+ },
+ break_style: Default::default(),
+ heading: Default::default(),
+ };
+ let markdown = cx.new_view(|cx| {
+ Markdown::new(MARKDOWN_EXAMPLE.into(), markdown_style, None, cx, None)
+ });
+
+ HelloWorld { markdown }
+ })
+ });
+ });
+}
+struct HelloWorld {
+ markdown: View<Markdown>,
+}
+
+impl Render for HelloWorld {
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+ div()
+ .flex()
+ .bg(rgb(0x2e7d32))
+ .size(Length::Definite(Pixels(700.0).into()))
+ .justify_center()
+ .items_center()
+ .shadow_lg()
+ .border_1()
+ .border_color(rgb(0x0000ff))
+ .text_xl()
+ .text_color(rgb(0xffffff))
+ .child(div().child(self.markdown.clone()).p_20())
+ }
+}
@@ -1,16 +1,17 @@
-mod parser;
+pub mod parser;
use crate::parser::CodeBlockKind;
use futures::FutureExt;
use gpui::{
actions, point, quad, AnyElement, AppContext, Bounds, ClipboardItem, CursorStyle,
DispatchPhase, Edges, FocusHandle, FocusableView, FontStyle, FontWeight, GlobalElementId,
- Hitbox, Hsla, KeyContext, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent, Point,
- Render, StrikethroughStyle, Style, StyledText, Task, TextLayout, TextRun, TextStyle,
- TextStyleRefinement, View,
+ Hitbox, Hsla, KeyContext, Length, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent,
+ Point, Render, StrikethroughStyle, StyleRefinement, StyledText, Task, TextLayout, TextRun,
+ TextStyle, TextStyleRefinement, View,
};
use language::{Language, LanguageRegistry, Rope};
use parser::{parse_markdown, MarkdownEvent, MarkdownTag, MarkdownTagEnd};
+
use std::{iter, mem, ops::Range, rc::Rc, sync::Arc};
use theme::SyntaxTheme;
use ui::prelude::*;
@@ -18,7 +19,8 @@ use util::{ResultExt, TryFutureExt};
#[derive(Clone)]
pub struct MarkdownStyle {
- pub code_block: TextStyleRefinement,
+ pub base_text_style: TextStyle,
+ pub code_block: StyleRefinement,
pub inline_code: TextStyleRefinement,
pub block_quote: TextStyleRefinement,
pub link: TextStyleRefinement,
@@ -26,8 +28,27 @@ pub struct MarkdownStyle {
pub block_quote_border_color: Hsla,
pub syntax: Arc<SyntaxTheme>,
pub selection_background_color: Hsla,
+ pub break_style: StyleRefinement,
+ pub heading: StyleRefinement,
}
+impl Default for MarkdownStyle {
+ fn default() -> Self {
+ Self {
+ base_text_style: Default::default(),
+ code_block: Default::default(),
+ inline_code: Default::default(),
+ block_quote: Default::default(),
+ link: Default::default(),
+ rule_color: Default::default(),
+ block_quote_border_color: Default::default(),
+ syntax: Arc::new(SyntaxTheme::default()),
+ selection_background_color: Default::default(),
+ break_style: Default::default(),
+ heading: Default::default(),
+ }
+ }
+}
pub struct Markdown {
source: String,
selection: Selection,
@@ -39,6 +60,7 @@ pub struct Markdown {
pending_parse: Option<Task<Option<()>>>,
focus_handle: FocusHandle,
language_registry: Option<Arc<LanguageRegistry>>,
+ fallback_code_block_language: Option<String>,
}
actions!(markdown, [Copy]);
@@ -49,6 +71,7 @@ impl Markdown {
style: MarkdownStyle,
language_registry: Option<Arc<LanguageRegistry>>,
cx: &mut ViewContext<Self>,
+ fallback_code_block_language: Option<String>,
) -> Self {
let focus_handle = cx.focus_handle();
let mut this = Self {
@@ -62,6 +85,7 @@ impl Markdown {
pending_parse: None,
focus_handle,
language_registry,
+ fallback_code_block_language,
};
this.parse(cx);
this
@@ -89,7 +113,14 @@ impl Markdown {
&self.source
}
+ pub fn parsed_markdown(&self) -> &ParsedMarkdown {
+ &self.parsed_markdown
+ }
+
fn copy(&self, text: &RenderedText, cx: &mut ViewContext<Self>) {
+ if self.selection.end <= self.selection.start {
+ return;
+ }
let text = text.text_for_range(self.selection.start..self.selection.end);
cx.write_to_clipboard(ClipboardItem::new(text));
}
@@ -140,6 +171,7 @@ impl Render for Markdown {
cx.view().clone(),
self.style.clone(),
self.language_registry.clone(),
+ self.fallback_code_block_language.clone(),
)
}
}
@@ -185,11 +217,21 @@ impl Selection {
}
#[derive(Clone)]
-struct ParsedMarkdown {
+pub struct ParsedMarkdown {
source: SharedString,
events: Arc<[(Range<usize>, MarkdownEvent)]>,
}
+impl ParsedMarkdown {
+ pub fn source(&self) -> &SharedString {
+ &self.source
+ }
+
+ pub fn events(&self) -> &Arc<[(Range<usize>, MarkdownEvent)]> {
+ return &self.events;
+ }
+}
+
impl Default for ParsedMarkdown {
fn default() -> Self {
Self {
@@ -203,6 +245,7 @@ pub struct MarkdownElement {
markdown: View<Markdown>,
style: MarkdownStyle,
language_registry: Option<Arc<LanguageRegistry>>,
+ fallback_code_block_language: Option<String>,
}
impl MarkdownElement {
@@ -210,19 +253,31 @@ impl MarkdownElement {
markdown: View<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, cx: &mut WindowContext) -> 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(name)
+ .language_for_name(language_name.as_str())
.map(|language| language.ok())
.shared();
@@ -417,7 +472,7 @@ impl MarkdownElement {
.update(cx, |markdown, _| markdown.autoscroll_request.take())?;
let (position, line_height) = rendered_text.position_for_source_index(autoscroll_index)?;
- let text_style = cx.text_style();
+ let text_style = self.style.base_text_style.clone();
let font_id = cx.text_system().resolve_font(&text_style.font());
let font_size = text_style.font_size.to_pixels(cx.rem_size());
let em_width = cx
@@ -462,14 +517,26 @@ impl Element for MarkdownElement {
_id: Option<&GlobalElementId>,
cx: &mut WindowContext,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
- let mut builder = MarkdownElementBuilder::new(cx.text_style(), self.style.syntax.clone());
+ let mut builder = MarkdownElementBuilder::new(
+ self.style.base_text_style.clone(),
+ self.style.syntax.clone(),
+ );
let parsed_markdown = self.markdown.read(cx).parsed_markdown.clone();
+ let markdown_end = if let Some(last) = parsed_markdown.events.last() {
+ last.0.end
+ } else {
+ 0
+ };
for (range, event) in parsed_markdown.events.iter() {
match event {
MarkdownEvent::Start(tag) => {
match tag {
MarkdownTag::Paragraph => {
- builder.push_div(div().mb_2().line_height(rems(1.3)));
+ builder.push_div(
+ div().mb_2().line_height(rems(1.3)),
+ range,
+ markdown_end,
+ );
}
MarkdownTag::Heading { level, .. } => {
let mut heading = div().mb_2();
@@ -480,7 +547,11 @@ impl Element for MarkdownElement {
pulldown_cmark::HeadingLevel::H4 => heading.text_lg(),
_ => heading,
};
- builder.push_div(heading);
+ heading.style().refine(&self.style.heading);
+ builder.push_text_style(
+ self.style.heading.text_style().clone().unwrap_or_default(),
+ );
+ builder.push_div(heading, range, markdown_end);
}
MarkdownTag::BlockQuote => {
builder.push_text_style(self.style.block_quote.clone());
@@ -490,6 +561,8 @@ impl Element for MarkdownElement {
.mb_2()
.border_l_4()
.border_color(self.style.block_quote_border_color),
+ range,
+ markdown_end,
);
}
MarkdownTag::CodeBlock(kind) => {
@@ -499,17 +572,18 @@ impl Element for MarkdownElement {
None
};
+ let mut d = div().w_full().rounded_lg();
+ d.style().refine(&self.style.code_block);
+ if let Some(code_block_text_style) = &self.style.code_block.text {
+ builder.push_text_style(code_block_text_style.to_owned());
+ }
builder.push_code_block(language);
- builder.push_text_style(self.style.code_block.clone());
- builder.push_div(div().rounded_lg().p_4().mb_2().w_full().when_some(
- self.style.code_block.background_color,
- |div, color| div.bg(color),
- ));
+ builder.push_div(d, range, markdown_end);
}
- MarkdownTag::HtmlBlock => builder.push_div(div()),
+ MarkdownTag::HtmlBlock => builder.push_div(div(), range, markdown_end),
MarkdownTag::List(bullet_index) => {
builder.push_list(*bullet_index);
- builder.push_div(div().pl_4());
+ builder.push_div(div().pl_4(), range, markdown_end);
}
MarkdownTag::Item => {
let bullet = if let Some(bullet_index) = builder.next_bullet_index() {
@@ -525,9 +599,11 @@ impl Element for MarkdownElement {
.items_start()
.gap_1()
.child(bullet),
+ range,
+ markdown_end,
);
// Without `w_0`, text doesn't wrap to the width of the container.
- builder.push_div(div().flex_1().w_0());
+ builder.push_div(div().flex_1().w_0(), range, markdown_end);
}
MarkdownTag::Emphasis => builder.push_text_style(TextStyleRefinement {
font_style: Some(FontStyle::Italic),
@@ -552,6 +628,7 @@ impl Element for MarkdownElement {
builder.push_text_style(self.style.link.clone())
}
}
+ MarkdownTag::MetadataBlock(_) => {}
_ => log::error!("unsupported markdown tag {:?}", tag),
}
}
@@ -559,7 +636,10 @@ impl Element for MarkdownElement {
MarkdownTagEnd::Paragraph => {
builder.pop_div();
}
- MarkdownTagEnd::Heading(_) => builder.pop_div(),
+ MarkdownTagEnd::Heading(_) => {
+ builder.pop_div();
+ builder.pop_text_style()
+ }
MarkdownTagEnd::BlockQuote => {
builder.pop_text_style();
builder.pop_div()
@@ -567,8 +647,10 @@ impl Element for MarkdownElement {
MarkdownTagEnd::CodeBlock => {
builder.trim_trailing_newline();
builder.pop_div();
- builder.pop_text_style();
builder.pop_code_block();
+ if self.style.code_block.text.is_some() {
+ builder.pop_text_style();
+ }
}
MarkdownTagEnd::HtmlBlock => builder.pop_div(),
MarkdownTagEnd::List(_) => {
@@ -609,18 +691,24 @@ impl Element for MarkdownElement {
.border_b_1()
.my_2()
.border_color(self.style.rule_color),
+ range,
+ markdown_end,
);
builder.pop_div()
}
- MarkdownEvent::SoftBreak => builder.push_text("\n", range.start),
- MarkdownEvent::HardBreak => builder.push_text("\n", range.start),
+ MarkdownEvent::SoftBreak => builder.push_text(" ", range.start),
+ MarkdownEvent::HardBreak => {
+ let mut d = div().py_3();
+ d.style().refine(&self.style.break_style);
+ builder.push_div(d, range, markdown_end);
+ builder.pop_div()
+ }
_ => log::error!("unsupported markdown event {:?}", event),
}
}
-
let mut rendered_markdown = builder.build();
let child_layout_id = rendered_markdown.element.request_layout(cx);
- let layout_id = cx.request_layout(Style::default(), [child_layout_id]);
+ let layout_id = cx.request_layout(gpui::Style::default(), [child_layout_id]);
(layout_id, rendered_markdown)
}
@@ -732,8 +820,32 @@ impl MarkdownElementBuilder {
self.text_style_stack.pop();
}
- fn push_div(&mut self, div: Div) {
+ fn push_div(&mut self, mut div: Div, range: &Range<usize>, markdown_end: usize) {
self.flush_text();
+
+ if range.start == 0 {
+ //first element, remove top margin
+ div.style().refine(&StyleRefinement {
+ margin: gpui::EdgesRefinement {
+ top: Some(Length::Definite(px(0.).into())),
+ left: None,
+ right: None,
+ bottom: None,
+ },
+ ..Default::default()
+ });
+ }
+ if range.end == markdown_end {
+ div.style().refine(&StyleRefinement {
+ margin: gpui::EdgesRefinement {
+ top: None,
+ left: None,
+ right: None,
+ bottom: Some(Length::Definite(rems(0.).into())),
+ },
+ ..Default::default()
+ });
+ }
self.div_stack.push(div);
}
@@ -7,11 +7,22 @@ use std::ops::Range;
pub fn parse_markdown(text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
let mut events = Vec::new();
let mut within_link = false;
+ let mut within_metadata = false;
for (pulldown_event, mut range) in Parser::new_ext(text, Options::all()).into_offset_iter() {
+ if within_metadata {
+ if let pulldown_cmark::Event::End(pulldown_cmark::TagEnd::MetadataBlock { .. }) =
+ pulldown_event
+ {
+ within_metadata = false;
+ }
+ continue;
+ }
match pulldown_event {
pulldown_cmark::Event::Start(tag) => {
- if let pulldown_cmark::Tag::Link { .. } = tag {
- within_link = true;
+ match tag {
+ pulldown_cmark::Tag::Link { .. } => within_link = true,
+ pulldown_cmark::Tag::MetadataBlock { .. } => within_metadata = true,
+ _ => {}
}
events.push((range, MarkdownEvent::Start(tag.into())))
}
@@ -5801,7 +5801,7 @@ impl Project {
.await
.into_iter()
.filter_map(|hover| remove_empty_hover_blocks(hover?))
- .collect()
+ .collect::<Vec<Hover>>()
})
} else if let Some(project_id) = self.remote_id() {
let request_task = self.client().request(proto::MultiLspQuery {
@@ -114,25 +114,31 @@ impl DevServerProjects {
cx.notify();
});
+ let mut base_style = cx.text_style();
+ base_style.refine(&gpui::TextStyleRefinement {
+ color: Some(cx.theme().colors().editor_foreground),
+ ..Default::default()
+ });
+
let markdown_style = MarkdownStyle {
- code_block: gpui::TextStyleRefinement {
- font_family: Some("Zed Plex Mono".into()),
- color: Some(cx.theme().colors().editor_foreground),
- background_color: Some(cx.theme().colors().editor_background),
+ base_text_style: base_style,
+ code_block: gpui::StyleRefinement {
+ text: Some(gpui::TextStyleRefinement {
+ font_family: Some("Zed Plex Mono".into()),
+ ..Default::default()
+ }),
..Default::default()
},
- inline_code: Default::default(),
- block_quote: Default::default(),
link: gpui::TextStyleRefinement {
color: Some(Color::Accent.color(cx)),
..Default::default()
},
- rule_color: Default::default(),
- block_quote_border_color: Default::default(),
syntax: cx.theme().syntax().clone(),
selection_background_color: cx.theme().players().local().selection,
+ ..Default::default()
};
- let markdown = cx.new_view(|cx| Markdown::new("".to_string(), markdown_style, None, cx));
+ let markdown =
+ cx.new_view(|cx| Markdown::new("".to_string(), markdown_style, None, cx, None));
Self {
mode: Mode::Default(None),