Detailed changes
@@ -239,12 +239,7 @@ pub async fn parse_markdown_block(
Event::Start(tag) => match tag {
Tag::Paragraph => new_paragraph(text, &mut list_stack),
- Tag::Heading {
- level: _,
- id: _,
- classes: _,
- attrs: _,
- } => {
+ Tag::Heading { .. } => {
new_paragraph(text, &mut list_stack);
bold_depth += 1;
}
@@ -267,12 +262,7 @@ pub async fn parse_markdown_block(
Tag::Strikethrough => strikethrough_depth += 1,
- Tag::Link {
- link_type: _,
- dest_url,
- title: _,
- id: _,
- } => link_url = Some(dest_url.to_string()),
+ Tag::Link { dest_url, .. } => link_url = Some(dest_url.to_string()),
Tag::List(number) => {
list_stack.push((number, false));
@@ -13,7 +13,7 @@ pub enum ParsedMarkdownElement {
BlockQuote(ParsedMarkdownBlockQuote),
CodeBlock(ParsedMarkdownCodeBlock),
/// A paragraph of text and other inline elements.
- Paragraph(ParsedMarkdownText),
+ Paragraph(MarkdownParagraph),
HorizontalRule(Range<usize>),
}
@@ -25,7 +25,13 @@ impl ParsedMarkdownElement {
Self::Table(table) => table.source_range.clone(),
Self::BlockQuote(block_quote) => block_quote.source_range.clone(),
Self::CodeBlock(code_block) => code_block.source_range.clone(),
- Self::Paragraph(text) => text.source_range.clone(),
+ Self::Paragraph(text) => match &text[0] {
+ MarkdownParagraphChunk::Text(t) => t.source_range.clone(),
+ MarkdownParagraphChunk::Image(image) => match image {
+ Image::Web { source_range, .. } => source_range.clone(),
+ Image::Path { source_range, .. } => source_range.clone(),
+ },
+ },
Self::HorizontalRule(range) => range.clone(),
}
}
@@ -35,6 +41,15 @@ impl ParsedMarkdownElement {
}
}
+pub type MarkdownParagraph = Vec<MarkdownParagraphChunk>;
+
+#[derive(Debug)]
+#[cfg_attr(test, derive(PartialEq))]
+pub enum MarkdownParagraphChunk {
+ Text(ParsedMarkdownText),
+ Image(Image),
+}
+
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ParsedMarkdown {
@@ -73,7 +88,7 @@ pub struct ParsedMarkdownCodeBlock {
pub struct ParsedMarkdownHeading {
pub source_range: Range<usize>,
pub level: HeadingLevel,
- pub contents: ParsedMarkdownText,
+ pub contents: MarkdownParagraph,
}
#[derive(Debug, PartialEq)]
@@ -107,7 +122,7 @@ pub enum ParsedMarkdownTableAlignment {
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ParsedMarkdownTableRow {
- pub children: Vec<ParsedMarkdownText>,
+ pub children: Vec<MarkdownParagraph>,
}
impl Default for ParsedMarkdownTableRow {
@@ -123,7 +138,7 @@ impl ParsedMarkdownTableRow {
}
}
- pub fn with_children(children: Vec<ParsedMarkdownText>) -> Self {
+ pub fn with_children(children: Vec<MarkdownParagraph>) -> Self {
Self { children }
}
}
@@ -135,7 +150,7 @@ pub struct ParsedMarkdownBlockQuote {
pub children: Vec<ParsedMarkdownElement>,
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct ParsedMarkdownText {
/// Where the text is located in the source Markdown document.
pub source_range: Range<usize>,
@@ -266,10 +281,112 @@ impl Display for Link {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Link::Web { url } => write!(f, "{}", url),
- Link::Path {
+ Link::Path { display_path, .. } => write!(f, "{}", display_path.display()),
+ }
+ }
+}
+
+/// A Markdown Image
+#[derive(Debug, Clone)]
+#[cfg_attr(test, derive(PartialEq))]
+pub enum Image {
+ Web {
+ source_range: Range<usize>,
+ /// The URL of the Image.
+ url: String,
+ /// Link URL if exists.
+ link: Option<Link>,
+ /// alt text if it exists
+ alt_text: Option<ParsedMarkdownText>,
+ },
+ /// Image path on the filesystem.
+ Path {
+ source_range: Range<usize>,
+ /// The path as provided in the Markdown document.
+ display_path: PathBuf,
+ /// The absolute path to the item.
+ path: PathBuf,
+ /// Link URL if exists.
+ link: Option<Link>,
+ /// alt text if it exists
+ alt_text: Option<ParsedMarkdownText>,
+ },
+}
+
+impl Image {
+ pub fn identify(
+ source_range: Range<usize>,
+ file_location_directory: Option<PathBuf>,
+ text: String,
+ link: Option<Link>,
+ ) -> Option<Image> {
+ if text.starts_with("http") {
+ return Some(Image::Web {
+ source_range,
+ url: text,
+ link,
+ alt_text: None,
+ });
+ }
+ let path = PathBuf::from(&text);
+ if path.is_absolute() {
+ return Some(Image::Path {
+ source_range,
+ display_path: path.clone(),
+ path,
+ link,
+ alt_text: None,
+ });
+ }
+ if let Some(file_location_directory) = file_location_directory {
+ let display_path = path;
+ let path = file_location_directory.join(text);
+ return Some(Image::Path {
+ source_range,
display_path,
- path: _,
- } => write!(f, "{}", display_path.display()),
+ path,
+ link,
+ alt_text: None,
+ });
+ }
+ None
+ }
+
+ pub fn with_alt_text(&self, alt_text: ParsedMarkdownText) -> Self {
+ match self {
+ Image::Web {
+ ref source_range,
+ ref url,
+ ref link,
+ ..
+ } => Image::Web {
+ source_range: source_range.clone(),
+ url: url.clone(),
+ link: link.clone(),
+ alt_text: Some(alt_text),
+ },
+ Image::Path {
+ ref source_range,
+ ref display_path,
+ ref path,
+ ref link,
+ ..
+ } => Image::Path {
+ source_range: source_range.clone(),
+ display_path: display_path.clone(),
+ path: path.clone(),
+ link: link.clone(),
+ alt_text: Some(alt_text),
+ },
+ }
+ }
+}
+
+impl Display for Image {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Image::Web { url, .. } => write!(f, "{}", url),
+ Image::Path { display_path, .. } => write!(f, "{}", display_path.display()),
}
}
}
@@ -4,7 +4,7 @@ use collections::FxHashMap;
use gpui::FontWeight;
use language::LanguageRegistry;
use pulldown_cmark::{Alignment, Event, Options, Parser, Tag, TagEnd};
-use std::{ops::Range, path::PathBuf, sync::Arc};
+use std::{ops::Range, path::PathBuf, sync::Arc, vec};
pub async fn parse_markdown(
markdown_input: &str,
@@ -101,11 +101,11 @@ impl<'a> MarkdownParser<'a> {
| Event::Code(_)
| Event::Html(_)
| Event::FootnoteReference(_)
- | Event::Start(Tag::Link { link_type: _, dest_url: _, title: _, id: _ })
+ | Event::Start(Tag::Link { .. })
| Event::Start(Tag::Emphasis)
| Event::Start(Tag::Strong)
| Event::Start(Tag::Strikethrough)
- | Event::Start(Tag::Image { link_type: _, dest_url: _, title: _, id: _ }) => {
+ | Event::Start(Tag::Image { .. }) => {
true
}
_ => false,
@@ -134,12 +134,7 @@ impl<'a> MarkdownParser<'a> {
let text = self.parse_text(false, Some(source_range));
Some(vec![ParsedMarkdownElement::Paragraph(text)])
}
- Tag::Heading {
- level,
- id: _,
- classes: _,
- attrs: _,
- } => {
+ Tag::Heading { level, .. } => {
let level = *level;
self.cursor += 1;
let heading = self.parse_heading(level);
@@ -194,22 +189,23 @@ impl<'a> MarkdownParser<'a> {
&mut self,
should_complete_on_soft_break: bool,
source_range: Option<Range<usize>>,
- ) -> ParsedMarkdownText {
+ ) -> MarkdownParagraph {
let source_range = source_range.unwrap_or_else(|| {
self.current()
.map(|(_, range)| range.clone())
.unwrap_or_default()
});
+ let mut markdown_text_like = Vec::new();
let mut text = String::new();
let mut bold_depth = 0;
let mut italic_depth = 0;
let mut strikethrough_depth = 0;
let mut link: Option<Link> = None;
+ let mut image: Option<Image> = None;
let mut region_ranges: Vec<Range<usize>> = vec![];
let mut regions: Vec<ParsedRegion> = vec![];
let mut highlights: Vec<(Range<usize>, MarkdownHighlight)> = vec![];
-
let mut link_urls: Vec<String> = vec![];
let mut link_ranges: Vec<Range<usize>> = vec![];
@@ -225,8 +221,6 @@ impl<'a> MarkdownParser<'a> {
if should_complete_on_soft_break {
break;
}
-
- // `Some text\nSome more text` should be treated as a single line.
text.push(' ');
}
@@ -240,7 +234,6 @@ impl<'a> MarkdownParser<'a> {
Event::Text(t) => {
text.push_str(t.as_ref());
-
let mut style = MarkdownHighlightStyle::default();
if bold_depth > 0 {
@@ -299,7 +292,6 @@ impl<'a> MarkdownParser<'a> {
url: link.as_str().to_string(),
}),
});
-
last_link_len = end;
}
last_link_len
@@ -316,13 +308,63 @@ impl<'a> MarkdownParser<'a> {
}
}
if new_highlight {
- highlights
- .push((last_run_len..text.len(), MarkdownHighlight::Style(style)));
+ highlights.push((
+ last_run_len..text.len(),
+ MarkdownHighlight::Style(style.clone()),
+ ));
}
}
- }
+ if let Some(mut image) = image.clone() {
+ let is_valid_image = match image.clone() {
+ Image::Path { display_path, .. } => {
+ gpui::ImageSource::try_from(display_path).is_ok()
+ }
+ Image::Web { url, .. } => gpui::ImageSource::try_from(url).is_ok(),
+ };
+ if is_valid_image {
+ text.truncate(text.len() - t.len());
+ if !t.is_empty() {
+ let alt_text = ParsedMarkdownText {
+ source_range: source_range.clone(),
+ contents: t.to_string(),
+ highlights: highlights.clone(),
+ region_ranges: region_ranges.clone(),
+ regions: regions.clone(),
+ };
+ image = image.with_alt_text(alt_text);
+ } else {
+ let alt_text = ParsedMarkdownText {
+ source_range: source_range.clone(),
+ contents: "img".to_string(),
+ highlights: highlights.clone(),
+ region_ranges: region_ranges.clone(),
+ regions: regions.clone(),
+ };
+ image = image.with_alt_text(alt_text);
+ }
+ if !text.is_empty() {
+ let parsed_regions =
+ MarkdownParagraphChunk::Text(ParsedMarkdownText {
+ source_range: source_range.clone(),
+ contents: text.clone(),
+ highlights: highlights.clone(),
+ region_ranges: region_ranges.clone(),
+ regions: regions.clone(),
+ });
+ text = String::new();
+ highlights = vec![];
+ region_ranges = vec![];
+ regions = vec![];
+ markdown_text_like.push(parsed_regions);
+ }
- // Note: This event means "inline code" and not "code block"
+ let parsed_image = MarkdownParagraphChunk::Image(image.clone());
+ markdown_text_like.push(parsed_image);
+ style = MarkdownHighlightStyle::default();
+ }
+ style.underline = true;
+ };
+ }
Event::Code(t) => {
text.push_str(t.as_ref());
region_ranges.push(prev_len..text.len());
@@ -336,46 +378,44 @@ impl<'a> MarkdownParser<'a> {
}),
));
}
-
regions.push(ParsedRegion {
code: true,
link: link.clone(),
});
}
-
Event::Start(tag) => match tag {
Tag::Emphasis => italic_depth += 1,
Tag::Strong => bold_depth += 1,
Tag::Strikethrough => strikethrough_depth += 1,
- Tag::Link {
- link_type: _,
- dest_url,
- title: _,
- id: _,
- } => {
+ Tag::Link { dest_url, .. } => {
link = Link::identify(
self.file_location_directory.clone(),
dest_url.to_string(),
);
}
+ Tag::Image { dest_url, .. } => {
+ image = Image::identify(
+ source_range.clone(),
+ self.file_location_directory.clone(),
+ dest_url.to_string(),
+ link.clone(),
+ );
+ }
_ => {
break;
}
},
Event::End(tag) => match tag {
- TagEnd::Emphasis => {
- italic_depth -= 1;
- }
- TagEnd::Strong => {
- bold_depth -= 1;
- }
- TagEnd::Strikethrough => {
- strikethrough_depth -= 1;
- }
+ TagEnd::Emphasis => italic_depth -= 1,
+ TagEnd::Strong => bold_depth -= 1,
+ TagEnd::Strikethrough => strikethrough_depth -= 1,
TagEnd::Link => {
link = None;
}
+ TagEnd::Image => {
+ image = None;
+ }
TagEnd::Paragraph => {
self.cursor += 1;
break;
@@ -384,7 +424,6 @@ impl<'a> MarkdownParser<'a> {
break;
}
},
-
_ => {
break;
}
@@ -392,14 +431,16 @@ impl<'a> MarkdownParser<'a> {
self.cursor += 1;
}
-
- ParsedMarkdownText {
- source_range,
- contents: text,
- highlights,
- regions,
- region_ranges,
+ if !text.is_empty() {
+ markdown_text_like.push(MarkdownParagraphChunk::Text(ParsedMarkdownText {
+ source_range: source_range.clone(),
+ contents: text,
+ highlights,
+ regions,
+ region_ranges,
+ }));
}
+ markdown_text_like
}
fn parse_heading(&mut self, level: pulldown_cmark::HeadingLevel) -> ParsedMarkdownHeading {
@@ -708,7 +749,6 @@ impl<'a> MarkdownParser<'a> {
}
}
}
-
let highlights = if let Some(language) = &language {
if let Some(registry) = &self.language_registry {
let rope: language::Rope = code.as_str().into();
@@ -735,10 +775,14 @@ impl<'a> MarkdownParser<'a> {
#[cfg(test)]
mod tests {
+ use core::panic;
+
use super::*;
use gpui::BackgroundExecutor;
- use language::{tree_sitter_rust, HighlightId, Language, LanguageConfig, LanguageMatcher};
+ use language::{
+ tree_sitter_rust, HighlightId, Language, LanguageConfig, LanguageMatcher, LanguageRegistry,
+ };
use pretty_assertions::assert_eq;
use ParsedMarkdownListItemType::*;
@@ -810,20 +854,29 @@ mod tests {
assert_eq!(parsed.children.len(), 1);
assert_eq!(
parsed.children[0],
- ParsedMarkdownElement::Paragraph(ParsedMarkdownText {
- source_range: 0..35,
- contents: "Some bostrikethroughld text".to_string(),
- highlights: Vec::new(),
- region_ranges: Vec::new(),
- regions: Vec::new(),
- })
+ ParsedMarkdownElement::Paragraph(vec![MarkdownParagraphChunk::Text(
+ ParsedMarkdownText {
+ source_range: 0..35,
+ contents: "Some bostrikethroughld text".to_string(),
+ highlights: Vec::new(),
+ region_ranges: Vec::new(),
+ regions: Vec::new(),
+ }
+ )])
);
- let paragraph = if let ParsedMarkdownElement::Paragraph(text) = &parsed.children[0] {
+ let new_text = if let ParsedMarkdownElement::Paragraph(text) = &parsed.children[0] {
text
} else {
panic!("Expected a paragraph");
};
+
+ let paragraph = if let MarkdownParagraphChunk::Text(text) = &new_text[0] {
+ text
+ } else {
+ panic!("Expected a text");
+ };
+
assert_eq!(
paragraph.highlights,
vec![
@@ -871,6 +924,11 @@ mod tests {
parsed.children,
vec![p("Checkout this https://zed.dev link", 0..34)]
);
+ }
+
+ #[gpui::test]
+ async fn test_image_links_detection() {
+ let parsed = parse("").await;
let paragraph = if let ParsedMarkdownElement::Paragraph(text) = &parsed.children[0] {
text
@@ -878,25 +936,22 @@ mod tests {
panic!("Expected a paragraph");
};
assert_eq!(
- paragraph.highlights,
- vec![(
- 14..29,
- MarkdownHighlight::Style(MarkdownHighlightStyle {
- underline: true,
- ..Default::default()
- }),
- )]
+ paragraph[0],
+ MarkdownParagraphChunk::Image(Image::Web {
+ source_range: 0..111,
+ url: "https://blog.logrocket.com/wp-content/uploads/2024/04/exploring-zed-open-source-code-editor-rust-2.png".to_string(),
+ link: None,
+ alt_text: Some(
+ ParsedMarkdownText {
+ source_range: 0..111,
+ contents: "test".to_string(),
+ highlights: vec![],
+ region_ranges: vec![],
+ regions: vec![],
+ },
+ ),
+ },)
);
- assert_eq!(
- paragraph.regions,
- vec![ParsedRegion {
- code: false,
- link: Some(Link::Web {
- url: "https://zed.dev".to_string()
- }),
- }]
- );
- assert_eq!(paragraph.region_ranges, vec![14..29]);
}
#[gpui::test]
@@ -1169,7 +1224,7 @@ Some other content
vec![
list_item(0..8, 1, Unordered, vec![p("code", 2..8)]),
list_item(9..19, 1, Unordered, vec![p("bold", 11..19)]),
- list_item(20..49, 1, Unordered, vec![p("link", 22..49)],)
+ list_item(20..49, 1, Unordered, vec![p("link", 22..49)],),
],
);
}
@@ -1312,7 +1367,7 @@ fn main() {
))
}
- fn h1(contents: ParsedMarkdownText, source_range: Range<usize>) -> ParsedMarkdownElement {
+ fn h1(contents: MarkdownParagraph, source_range: Range<usize>) -> ParsedMarkdownElement {
ParsedMarkdownElement::Heading(ParsedMarkdownHeading {
source_range,
level: HeadingLevel::H1,
@@ -1320,7 +1375,7 @@ fn main() {
})
}
- fn h2(contents: ParsedMarkdownText, source_range: Range<usize>) -> ParsedMarkdownElement {
+ fn h2(contents: MarkdownParagraph, source_range: Range<usize>) -> ParsedMarkdownElement {
ParsedMarkdownElement::Heading(ParsedMarkdownHeading {
source_range,
level: HeadingLevel::H2,
@@ -1328,7 +1383,7 @@ fn main() {
})
}
- fn h3(contents: ParsedMarkdownText, source_range: Range<usize>) -> ParsedMarkdownElement {
+ fn h3(contents: MarkdownParagraph, source_range: Range<usize>) -> ParsedMarkdownElement {
ParsedMarkdownElement::Heading(ParsedMarkdownHeading {
source_range,
level: HeadingLevel::H3,
@@ -1340,14 +1395,14 @@ fn main() {
ParsedMarkdownElement::Paragraph(text(contents, source_range))
}
- fn text(contents: &str, source_range: Range<usize>) -> ParsedMarkdownText {
- ParsedMarkdownText {
+ fn text(contents: &str, source_range: Range<usize>) -> MarkdownParagraph {
+ vec![MarkdownParagraphChunk::Text(ParsedMarkdownText {
highlights: Vec::new(),
region_ranges: Vec::new(),
regions: Vec::new(),
source_range,
contents: contents.to_string(),
- }
+ })]
}
fn block_quote(
@@ -1401,7 +1456,7 @@ fn main() {
}
}
- fn row(children: Vec<ParsedMarkdownText>) -> ParsedMarkdownTableRow {
+ fn row(children: Vec<MarkdownParagraph>) -> ParsedMarkdownTableRow {
ParsedMarkdownTableRow { children }
}
@@ -1,29 +1,33 @@
use crate::markdown_elements::{
- HeadingLevel, Link, ParsedMarkdown, ParsedMarkdownBlockQuote, ParsedMarkdownCodeBlock,
- ParsedMarkdownElement, ParsedMarkdownHeading, ParsedMarkdownListItem,
- ParsedMarkdownListItemType, ParsedMarkdownTable, ParsedMarkdownTableAlignment,
- ParsedMarkdownTableRow, ParsedMarkdownText,
+ HeadingLevel, Image, Link, MarkdownParagraph, MarkdownParagraphChunk, ParsedMarkdown,
+ ParsedMarkdownBlockQuote, ParsedMarkdownCodeBlock, ParsedMarkdownElement,
+ ParsedMarkdownHeading, ParsedMarkdownListItem, ParsedMarkdownListItemType, ParsedMarkdownTable,
+ ParsedMarkdownTableAlignment, ParsedMarkdownTableRow, ParsedMarkdownText,
};
use gpui::{
- div, px, rems, AbsoluteLength, AnyElement, ClipboardItem, DefiniteLength, Div, Element,
- ElementId, HighlightStyle, Hsla, InteractiveText, IntoElement, Keystroke, Length, Modifiers,
- ParentElement, SharedString, Styled, StyledText, TextStyle, WeakView, WindowContext,
+ div, img, px, rems, AbsoluteLength, AnyElement, ClipboardItem, DefiniteLength, Div, Element,
+ ElementId, HighlightStyle, Hsla, ImageSource, InteractiveText, IntoElement, Keystroke, Length,
+ Modifiers, ParentElement, Resource, SharedString, Styled, StyledText, TextStyle, WeakView,
+ WindowContext,
};
use settings::Settings;
use std::{
ops::{Mul, Range},
+ path::Path,
sync::Arc,
+ vec,
};
use theme::{ActiveTheme, SyntaxTheme, ThemeSettings};
use ui::{
h_flex, relative, v_flex, Checkbox, Clickable, FluentBuilder, IconButton, IconName, IconSize,
- InteractiveElement, LinkPreview, Selection, StatefulInteractiveElement, StyledExt, Tooltip,
- VisibleOnHover,
+ InteractiveElement, LinkPreview, Selection, StatefulInteractiveElement, StyledExt, StyledImage,
+ Tooltip, VisibleOnHover,
};
use workspace::Workspace;
type CheckboxClickedCallback = Arc<Box<dyn Fn(bool, Range<usize>, &mut WindowContext)>>;
+#[derive(Clone)]
pub struct RenderContext {
workspace: Option<WeakView<Workspace>>,
next_id: usize,
@@ -153,7 +157,7 @@ fn render_markdown_heading(parsed: &ParsedMarkdownHeading, cx: &mut RenderContex
.text_color(color)
.pt(rems(0.15))
.pb_1()
- .child(render_markdown_text(&parsed.contents, cx))
+ .children(render_markdown_text(&parsed.contents, cx))
.whitespace_normal()
.into_any()
}
@@ -231,17 +235,29 @@ fn render_markdown_list_item(
cx.with_common_p(item).into_any()
}
+fn paragraph_len(paragraphs: &MarkdownParagraph) -> usize {
+ paragraphs
+ .iter()
+ .map(|paragraph| match paragraph {
+ MarkdownParagraphChunk::Text(text) => text.contents.len(),
+ // TODO: Scale column width based on image size
+ MarkdownParagraphChunk::Image(_) => 1,
+ })
+ .sum()
+}
+
fn render_markdown_table(parsed: &ParsedMarkdownTable, cx: &mut RenderContext) -> AnyElement {
let mut max_lengths: Vec<usize> = vec![0; parsed.header.children.len()];
for (index, cell) in parsed.header.children.iter().enumerate() {
- let length = cell.contents.len();
+ let length = paragraph_len(&cell);
max_lengths[index] = length;
}
for row in &parsed.body {
for (index, cell) in row.children.iter().enumerate() {
- let length = cell.contents.len();
+ let length = paragraph_len(&cell);
+
if length > max_lengths[index] {
max_lengths[index] = length;
}
@@ -307,11 +323,10 @@ fn render_markdown_table_row(
};
let max_width = max_column_widths.get(index).unwrap_or(&0.0);
-
let mut cell = container
.w(Length::Definite(relative(*max_width)))
.h_full()
- .child(contents)
+ .children(contents)
.px_2()
.py_1()
.border_color(cx.border_color);
@@ -398,18 +413,219 @@ fn render_markdown_code_block(
.into_any()
}
-fn render_markdown_paragraph(parsed: &ParsedMarkdownText, cx: &mut RenderContext) -> AnyElement {
+fn render_markdown_paragraph(parsed: &MarkdownParagraph, cx: &mut RenderContext) -> AnyElement {
cx.with_common_p(div())
- .child(render_markdown_text(parsed, cx))
+ .children(render_markdown_text(parsed, cx))
+ .flex()
.into_any_element()
}
-fn render_markdown_text(parsed: &ParsedMarkdownText, cx: &mut RenderContext) -> AnyElement {
- let element_id = cx.next_id(&parsed.source_range);
+fn render_markdown_text(parsed_new: &MarkdownParagraph, cx: &mut RenderContext) -> Vec<AnyElement> {
+ let mut any_element = vec![];
+ // these values are cloned in-order satisfy borrow checker
+ let syntax_theme = cx.syntax_theme.clone();
+ let workspace_clone = cx.workspace.clone();
+ let code_span_bg_color = cx.code_span_background_color;
+ let text_style = cx.text_style.clone();
+
+ for parsed_region in parsed_new {
+ match parsed_region {
+ MarkdownParagraphChunk::Text(parsed) => {
+ let element_id = cx.next_id(&parsed.source_range);
+
+ let highlights = gpui::combine_highlights(
+ parsed.highlights.iter().filter_map(|(range, highlight)| {
+ highlight
+ .to_highlight_style(&syntax_theme)
+ .map(|style| (range.clone(), style))
+ }),
+ parsed.regions.iter().zip(&parsed.region_ranges).filter_map(
+ |(region, range)| {
+ if region.code {
+ Some((
+ range.clone(),
+ HighlightStyle {
+ background_color: Some(code_span_bg_color),
+ ..Default::default()
+ },
+ ))
+ } else {
+ None
+ }
+ },
+ ),
+ );
+ let mut links = Vec::new();
+ let mut link_ranges = Vec::new();
+ for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
+ if let Some(link) = region.link.clone() {
+ links.push(link);
+ link_ranges.push(range.clone());
+ }
+ }
+ let workspace = workspace_clone.clone();
+ let element = div()
+ .child(
+ InteractiveText::new(
+ element_id,
+ StyledText::new(parsed.contents.clone())
+ .with_highlights(&text_style, highlights),
+ )
+ .tooltip({
+ let links = links.clone();
+ let link_ranges = link_ranges.clone();
+ move |idx, cx| {
+ for (ix, range) in link_ranges.iter().enumerate() {
+ if range.contains(&idx) {
+ return Some(LinkPreview::new(&links[ix].to_string(), cx));
+ }
+ }
+ None
+ }
+ })
+ .on_click(
+ link_ranges,
+ move |clicked_range_ix, window_cx| match &links[clicked_range_ix] {
+ Link::Web { url } => window_cx.open_url(url),
+ Link::Path { path, .. } => {
+ if let Some(workspace) = &workspace {
+ _ = workspace.update(window_cx, |workspace, cx| {
+ workspace
+ .open_abs_path(path.clone(), false, cx)
+ .detach();
+ });
+ }
+ }
+ },
+ ),
+ )
+ .into_any();
+ any_element.push(element);
+ }
+
+ MarkdownParagraphChunk::Image(image) => {
+ let (link, source_range, image_source, alt_text) = match image {
+ Image::Web {
+ link,
+ source_range,
+ url,
+ alt_text,
+ } => (
+ link,
+ source_range,
+ Resource::Uri(url.clone().into()),
+ alt_text,
+ ),
+ Image::Path {
+ link,
+ source_range,
+ path,
+ alt_text,
+ ..
+ } => {
+ let image_path = Path::new(path.to_str().unwrap());
+ (
+ link,
+ source_range,
+ Resource::Path(Arc::from(image_path)),
+ alt_text,
+ )
+ }
+ };
+
+ let element_id = cx.next_id(source_range);
+
+ match link {
+ None => {
+ let fallback_workspace = workspace_clone.clone();
+ let fallback_syntax_theme = syntax_theme.clone();
+ let fallback_text_style = text_style.clone();
+ let fallback_alt_text = alt_text.clone();
+ let element_id_new = element_id.clone();
+ let element = div()
+ .child(img(ImageSource::Resource(image_source)).with_fallback({
+ move || {
+ fallback_text(
+ fallback_alt_text.clone().unwrap(),
+ element_id.clone(),
+ &fallback_syntax_theme,
+ code_span_bg_color,
+ fallback_workspace.clone(),
+ &fallback_text_style,
+ )
+ }
+ }))
+ .id(element_id_new)
+ .into_any();
+ any_element.push(element);
+ }
+ Some(link) => {
+ let link_click = link.clone();
+ let link_tooltip = link.clone();
+ let fallback_workspace = workspace_clone.clone();
+ let fallback_syntax_theme = syntax_theme.clone();
+ let fallback_text_style = text_style.clone();
+ let fallback_alt_text = alt_text.clone();
+ let element_id_new = element_id.clone();
+ let image_element = div()
+ .child(img(ImageSource::Resource(image_source)).with_fallback({
+ move || {
+ fallback_text(
+ fallback_alt_text.clone().unwrap(),
+ element_id.clone(),
+ &fallback_syntax_theme,
+ code_span_bg_color,
+ fallback_workspace.clone(),
+ &fallback_text_style,
+ )
+ }
+ }))
+ .id(element_id_new)
+ .tooltip(move |cx| LinkPreview::new(&link_tooltip.to_string(), cx))
+ .on_click({
+ let workspace = workspace_clone.clone();
+ move |_event, window_cx| match &link_click {
+ Link::Web { url } => window_cx.open_url(url),
+ Link::Path { path, .. } => {
+ if let Some(workspace) = &workspace {
+ _ = workspace.update(window_cx, |workspace, cx| {
+ workspace
+ .open_abs_path(path.clone(), false, cx)
+ .detach();
+ });
+ }
+ }
+ }
+ })
+ .into_any();
+ any_element.push(image_element);
+ }
+ }
+ }
+ }
+ }
+
+ any_element
+}
+
+fn render_markdown_rule(cx: &mut RenderContext) -> AnyElement {
+ let rule = div().w_full().h(px(2.)).bg(cx.border_color);
+ div().pt_3().pb_3().child(rule).into_any()
+}
+
+fn fallback_text(
+ parsed: ParsedMarkdownText,
+ source_range: ElementId,
+ syntax_theme: &theme::SyntaxTheme,
+ code_span_bg_color: Hsla,
+ workspace: Option<WeakView<Workspace>>,
+ text_style: &TextStyle,
+) -> AnyElement {
+ let element_id = source_range;
let highlights = gpui::combine_highlights(
parsed.highlights.iter().filter_map(|(range, highlight)| {
- let highlight = highlight.to_highlight_style(&cx.syntax_theme)?;
+ let highlight = highlight.to_highlight_style(syntax_theme)?;
Some((range.clone(), highlight))
}),
parsed
@@ -421,7 +637,7 @@ fn render_markdown_text(parsed: &ParsedMarkdownText, cx: &mut RenderContext) ->
Some((
range.clone(),
HighlightStyle {
- background_color: Some(cx.code_span_background_color),
+ background_color: Some(code_span_bg_color),
..Default::default()
},
))
@@ -430,7 +646,6 @@ fn render_markdown_text(parsed: &ParsedMarkdownText, cx: &mut RenderContext) ->
}
}),
);
-
let mut links = Vec::new();
let mut link_ranges = Vec::new();
for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
@@ -439,45 +654,38 @@ fn render_markdown_text(parsed: &ParsedMarkdownText, cx: &mut RenderContext) ->
link_ranges.push(range.clone());
}
}
-
- let workspace = cx.workspace.clone();
-
- InteractiveText::new(
- element_id,
- StyledText::new(parsed.contents.clone()).with_highlights(&cx.text_style, highlights),
- )
- .tooltip({
- let links = links.clone();
- let link_ranges = link_ranges.clone();
- move |idx, cx| {
- for (ix, range) in link_ranges.iter().enumerate() {
- if range.contains(&idx) {
- return Some(LinkPreview::new(&links[ix].to_string(), cx));
- }
- }
- None
- }
- })
- .on_click(
- link_ranges,
- move |clicked_range_ix, window_cx| match &links[clicked_range_ix] {
- Link::Web { url } => window_cx.open_url(url),
- Link::Path {
- path,
- display_path: _,
- } => {
- if let Some(workspace) = &workspace {
- _ = workspace.update(window_cx, |workspace, cx| {
- workspace.open_abs_path(path.clone(), false, cx).detach();
- });
+ let element = div()
+ .child(
+ InteractiveText::new(
+ element_id,
+ StyledText::new(parsed.contents.clone()).with_highlights(text_style, highlights),
+ )
+ .tooltip({
+ let links = links.clone();
+ let link_ranges = link_ranges.clone();
+ move |idx, cx| {
+ for (ix, range) in link_ranges.iter().enumerate() {
+ if range.contains(&idx) {
+ return Some(LinkPreview::new(&links[ix].to_string(), cx));
+ }
+ }
+ None
}
- }
- },
- )
- .into_any_element()
-}
-
-fn render_markdown_rule(cx: &mut RenderContext) -> AnyElement {
- let rule = div().w_full().h(px(2.)).bg(cx.border_color);
- div().pt_3().pb_3().child(rule).into_any()
+ })
+ .on_click(
+ link_ranges,
+ move |clicked_range_ix, window_cx| match &links[clicked_range_ix] {
+ Link::Web { url } => window_cx.open_url(url),
+ Link::Path { path, .. } => {
+ if let Some(workspace) = &workspace {
+ _ = workspace.update(window_cx, |workspace, cx| {
+ workspace.open_abs_path(path.clone(), false, cx).detach();
+ });
+ }
+ }
+ },
+ ),
+ )
+ .into_any();
+ return element;
}
@@ -238,11 +238,8 @@ impl NotificationStore {
) -> Result<()> {
this.update(&mut cx, |this, cx| {
if let Some(notification) = envelope.payload.notification {
- if let Some(rpc::Notification::ChannelMessageMention {
- message_id,
- sender_id: _,
- channel_id: _,
- }) = Notification::from_proto(¬ification)
+ if let Some(rpc::Notification::ChannelMessageMention { message_id, .. }) =
+ Notification::from_proto(¬ification)
{
let fetch_message_task = this.channel_store.update(cx, |this, cx| {
this.fetch_channel_messages(vec![message_id], cx)
@@ -114,7 +114,7 @@ impl Cell {
id,
metadata,
source,
- attachments: _,
+ ..
} => {
let source = source.join("");
@@ -310,12 +310,7 @@ pub fn render_markdown_mut(
}
Event::Start(tag) => match tag {
Tag::Paragraph => new_paragraph(text, &mut list_stack),
- Tag::Heading {
- level: _,
- id: _,
- classes: _,
- attrs: _,
- } => {
+ Tag::Heading { .. } => {
new_paragraph(text, &mut list_stack);
bold_depth += 1;
}
@@ -333,12 +328,7 @@ pub fn render_markdown_mut(
Tag::Emphasis => italic_depth += 1,
Tag::Strong => bold_depth += 1,
Tag::Strikethrough => strikethrough_depth += 1,
- Tag::Link {
- link_type: _,
- dest_url,
- title: _,
- id: _,
- } => link_url = Some(dest_url.to_string()),
+ Tag::Link { dest_url, .. } => link_url = Some(dest_url.to_string()),
Tag::List(number) => {
list_stack.push((number, false));
}