mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 10:29:35 +03:00
Support rendering strikethrough text in markdown (#8287)
Just noticed strikethrough text handling was not implemented for the following: Chat ![image](https://github.com/zed-industries/zed/assets/53836821/ddd98272-d4d4-4a94-bd79-77e967f3ca15) Markdown Preview ![image](https://github.com/zed-industries/zed/assets/53836821/9087635c-5b89-40e6-8e4d-2785a43ef318) Code Documentation ![image](https://github.com/zed-industries/zed/assets/53836821/5ed55c60-3e5e-4fc2-86c2-a81fac7de038) It looks like there are three different markdown parsing/rendering implementations, might be worth to investigate if any of these can be combined into a single crate (looks like a lot of work though). Release Notes: - Added support for rendering strikethrough text in markdown elements
This commit is contained in:
parent
cd8ede542b
commit
43163a0154
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -7042,11 +7042,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pulldown-cmark"
|
name = "pulldown-cmark"
|
||||||
version = "0.9.3"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
|
checksum = "dce76ce678ffc8e5675b22aa1405de0b7037e2fdf8913fea40d1926c6fe1e6e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 2.4.1",
|
||||||
"memchr",
|
"memchr",
|
||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
@ -218,7 +218,7 @@ profiling = "1"
|
|||||||
postage = { version = "0.5", features = ["futures-traits"] }
|
postage = { version = "0.5", features = ["futures-traits"] }
|
||||||
pretty_assertions = "1.3.0"
|
pretty_assertions = "1.3.0"
|
||||||
prost = "0.8"
|
prost = "0.8"
|
||||||
pulldown-cmark = { version = "0.9.2", default-features = false }
|
pulldown-cmark = { version = "0.10.0", default-features = false }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
refineable = { path = "./crates/refineable" }
|
refineable = { path = "./crates/refineable" }
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
|
@ -4,8 +4,8 @@ use std::sync::Arc;
|
|||||||
use std::{ops::Range, path::PathBuf};
|
use std::{ops::Range, path::PathBuf};
|
||||||
|
|
||||||
use crate::{HighlightId, Language, LanguageRegistry};
|
use crate::{HighlightId, Language, LanguageRegistry};
|
||||||
use gpui::{px, FontStyle, FontWeight, HighlightStyle, UnderlineStyle};
|
use gpui::{px, FontStyle, FontWeight, HighlightStyle, StrikethroughStyle, UnderlineStyle};
|
||||||
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
|
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd};
|
||||||
|
|
||||||
/// Parsed Markdown content.
|
/// Parsed Markdown content.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -47,6 +47,13 @@ impl MarkdownHighlight {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if style.strikethrough {
|
||||||
|
highlight.strikethrough = Some(StrikethroughStyle {
|
||||||
|
thickness: px(1.),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if style.weight != FontWeight::default() {
|
if style.weight != FontWeight::default() {
|
||||||
highlight.font_weight = Some(style.weight);
|
highlight.font_weight = Some(style.weight);
|
||||||
}
|
}
|
||||||
@ -66,6 +73,8 @@ pub struct MarkdownHighlightStyle {
|
|||||||
pub italic: bool,
|
pub italic: bool,
|
||||||
/// Whether the text should be underlined.
|
/// Whether the text should be underlined.
|
||||||
pub underline: bool,
|
pub underline: bool,
|
||||||
|
/// Whether the text should be struck through.
|
||||||
|
pub strikethrough: bool,
|
||||||
/// The weight of the text.
|
/// The weight of the text.
|
||||||
pub weight: FontWeight,
|
pub weight: FontWeight,
|
||||||
}
|
}
|
||||||
@ -151,6 +160,7 @@ pub async fn parse_markdown_block(
|
|||||||
) {
|
) {
|
||||||
let mut bold_depth = 0;
|
let mut bold_depth = 0;
|
||||||
let mut italic_depth = 0;
|
let mut italic_depth = 0;
|
||||||
|
let mut strikethrough_depth = 0;
|
||||||
let mut link_url = None;
|
let mut link_url = None;
|
||||||
let mut current_language = None;
|
let mut current_language = None;
|
||||||
let mut list_stack = Vec::new();
|
let mut list_stack = Vec::new();
|
||||||
@ -174,6 +184,10 @@ pub async fn parse_markdown_block(
|
|||||||
style.italic = true;
|
style.italic = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strikethrough_depth > 0 {
|
||||||
|
style.strikethrough = true;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(link) = link_url.clone().and_then(|u| Link::identify(u)) {
|
if let Some(link) = link_url.clone().and_then(|u| Link::identify(u)) {
|
||||||
region_ranges.push(prev_len..text.len());
|
region_ranges.push(prev_len..text.len());
|
||||||
regions.push(ParsedRegion {
|
regions.push(ParsedRegion {
|
||||||
@ -221,7 +235,12 @@ pub async fn parse_markdown_block(
|
|||||||
Event::Start(tag) => match tag {
|
Event::Start(tag) => match tag {
|
||||||
Tag::Paragraph => new_paragraph(text, &mut list_stack),
|
Tag::Paragraph => new_paragraph(text, &mut list_stack),
|
||||||
|
|
||||||
Tag::Heading(_, _, _) => {
|
Tag::Heading {
|
||||||
|
level: _,
|
||||||
|
id: _,
|
||||||
|
classes: _,
|
||||||
|
attrs: _,
|
||||||
|
} => {
|
||||||
new_paragraph(text, &mut list_stack);
|
new_paragraph(text, &mut list_stack);
|
||||||
bold_depth += 1;
|
bold_depth += 1;
|
||||||
}
|
}
|
||||||
@ -242,7 +261,14 @@ pub async fn parse_markdown_block(
|
|||||||
|
|
||||||
Tag::Strong => bold_depth += 1,
|
Tag::Strong => bold_depth += 1,
|
||||||
|
|
||||||
Tag::Link(_, url, _) => link_url = Some(url.to_string()),
|
Tag::Strikethrough => strikethrough_depth += 1,
|
||||||
|
|
||||||
|
Tag::Link {
|
||||||
|
link_type: _,
|
||||||
|
dest_url,
|
||||||
|
title: _,
|
||||||
|
id: _,
|
||||||
|
} => link_url = Some(dest_url.to_string()),
|
||||||
|
|
||||||
Tag::List(number) => {
|
Tag::List(number) => {
|
||||||
list_stack.push((number, false));
|
list_stack.push((number, false));
|
||||||
@ -272,12 +298,13 @@ pub async fn parse_markdown_block(
|
|||||||
},
|
},
|
||||||
|
|
||||||
Event::End(tag) => match tag {
|
Event::End(tag) => match tag {
|
||||||
Tag::Heading(_, _, _) => bold_depth -= 1,
|
TagEnd::Heading(_) => bold_depth -= 1,
|
||||||
Tag::CodeBlock(_) => current_language = None,
|
TagEnd::CodeBlock => current_language = None,
|
||||||
Tag::Emphasis => italic_depth -= 1,
|
TagEnd::Emphasis => italic_depth -= 1,
|
||||||
Tag::Strong => bold_depth -= 1,
|
TagEnd::Strong => bold_depth -= 1,
|
||||||
Tag::Link(_, _, _) => link_url = None,
|
TagEnd::Strikethrough => strikethrough_depth -= 1,
|
||||||
Tag::List(_) => drop(list_stack.pop()),
|
TagEnd::Link => link_url = None,
|
||||||
|
TagEnd::List(_) => drop(list_stack.pop()),
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use gpui::{px, FontStyle, FontWeight, HighlightStyle, SharedString, UnderlineStyle};
|
use gpui::{
|
||||||
|
px, FontStyle, FontWeight, HighlightStyle, SharedString, StrikethroughStyle, UnderlineStyle,
|
||||||
|
};
|
||||||
use language::HighlightId;
|
use language::HighlightId;
|
||||||
use std::{ops::Range, path::PathBuf};
|
use std::{ops::Range, path::PathBuf};
|
||||||
|
|
||||||
@ -170,6 +172,13 @@ impl MarkdownHighlight {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if style.strikethrough {
|
||||||
|
highlight.strikethrough = Some(StrikethroughStyle {
|
||||||
|
thickness: px(1.),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if style.weight != FontWeight::default() {
|
if style.weight != FontWeight::default() {
|
||||||
highlight.font_weight = Some(style.weight);
|
highlight.font_weight = Some(style.weight);
|
||||||
}
|
}
|
||||||
@ -189,6 +198,8 @@ pub struct MarkdownHighlightStyle {
|
|||||||
pub italic: bool,
|
pub italic: bool,
|
||||||
/// Whether the text should be underlined.
|
/// Whether the text should be underlined.
|
||||||
pub underline: bool,
|
pub underline: bool,
|
||||||
|
/// Whether the text should be struck through.
|
||||||
|
pub strikethrough: bool,
|
||||||
/// The weight of the text.
|
/// The weight of the text.
|
||||||
pub weight: FontWeight,
|
pub weight: FontWeight,
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::markdown_elements::*;
|
use crate::markdown_elements::*;
|
||||||
use gpui::FontWeight;
|
use gpui::FontWeight;
|
||||||
use pulldown_cmark::{Alignment, Event, Options, Parser, Tag};
|
use pulldown_cmark::{Alignment, Event, Options, Parser, Tag, TagEnd};
|
||||||
use std::{ops::Range, path::PathBuf};
|
use std::{ops::Range, path::PathBuf};
|
||||||
|
|
||||||
pub fn parse_markdown(
|
pub fn parse_markdown(
|
||||||
@ -70,11 +70,11 @@ impl<'a> MarkdownParser<'a> {
|
|||||||
| Event::Code(_)
|
| Event::Code(_)
|
||||||
| Event::Html(_)
|
| Event::Html(_)
|
||||||
| Event::FootnoteReference(_)
|
| Event::FootnoteReference(_)
|
||||||
| Event::Start(Tag::Link(_, _, _))
|
| Event::Start(Tag::Link { link_type: _, dest_url: _, title: _, id: _ })
|
||||||
| Event::Start(Tag::Emphasis)
|
| Event::Start(Tag::Emphasis)
|
||||||
| Event::Start(Tag::Strong)
|
| Event::Start(Tag::Strong)
|
||||||
| Event::Start(Tag::Strikethrough)
|
| Event::Start(Tag::Strikethrough)
|
||||||
| Event::Start(Tag::Image(_, _, _)) => {
|
| Event::Start(Tag::Image { link_type: _, dest_url: _, title: _, id: _ }) => {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
_ => return false,
|
_ => return false,
|
||||||
@ -99,15 +99,21 @@ impl<'a> MarkdownParser<'a> {
|
|||||||
let text = self.parse_text(false);
|
let text = self.parse_text(false);
|
||||||
Some(ParsedMarkdownElement::Paragraph(text))
|
Some(ParsedMarkdownElement::Paragraph(text))
|
||||||
}
|
}
|
||||||
Tag::Heading(level, _, _) => {
|
Tag::Heading {
|
||||||
|
level,
|
||||||
|
id: _,
|
||||||
|
classes: _,
|
||||||
|
attrs: _,
|
||||||
|
} => {
|
||||||
let level = level.clone();
|
let level = level.clone();
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
let heading = self.parse_heading(level);
|
let heading = self.parse_heading(level);
|
||||||
Some(ParsedMarkdownElement::Heading(heading))
|
Some(ParsedMarkdownElement::Heading(heading))
|
||||||
}
|
}
|
||||||
Tag::Table(_) => {
|
Tag::Table(alignment) => {
|
||||||
|
let alignment = alignment.clone();
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
let table = self.parse_table();
|
let table = self.parse_table(alignment);
|
||||||
Some(ParsedMarkdownElement::Table(table))
|
Some(ParsedMarkdownElement::Table(table))
|
||||||
}
|
}
|
||||||
Tag::List(order) => {
|
Tag::List(order) => {
|
||||||
@ -162,6 +168,7 @@ impl<'a> MarkdownParser<'a> {
|
|||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
let mut bold_depth = 0;
|
let mut bold_depth = 0;
|
||||||
let mut italic_depth = 0;
|
let mut italic_depth = 0;
|
||||||
|
let mut strikethrough_depth = 0;
|
||||||
let mut link: Option<Link> = None;
|
let mut link: Option<Link> = None;
|
||||||
let mut region_ranges: Vec<Range<usize>> = vec![];
|
let mut region_ranges: Vec<Range<usize>> = vec![];
|
||||||
let mut regions: Vec<ParsedRegion> = vec![];
|
let mut regions: Vec<ParsedRegion> = vec![];
|
||||||
@ -201,6 +208,10 @@ impl<'a> MarkdownParser<'a> {
|
|||||||
style.italic = true;
|
style.italic = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strikethrough_depth > 0 {
|
||||||
|
style.strikethrough = true;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(link) = link.clone() {
|
if let Some(link) = link.clone() {
|
||||||
region_ranges.push(prev_len..text.len());
|
region_ranges.push(prev_len..text.len());
|
||||||
regions.push(ParsedRegion {
|
regions.push(ParsedRegion {
|
||||||
@ -248,39 +259,40 @@ impl<'a> MarkdownParser<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::Start(tag) => {
|
Event::Start(tag) => match tag {
|
||||||
match tag {
|
|
||||||
Tag::Emphasis => italic_depth += 1,
|
Tag::Emphasis => italic_depth += 1,
|
||||||
Tag::Strong => bold_depth += 1,
|
Tag::Strong => bold_depth += 1,
|
||||||
Tag::Link(_type, url, _title) => {
|
Tag::Strikethrough => strikethrough_depth += 1,
|
||||||
|
Tag::Link {
|
||||||
|
link_type: _,
|
||||||
|
dest_url,
|
||||||
|
title: _,
|
||||||
|
id: _,
|
||||||
|
} => {
|
||||||
link = Link::identify(
|
link = Link::identify(
|
||||||
self.file_location_directory.clone(),
|
self.file_location_directory.clone(),
|
||||||
url.to_string(),
|
dest_url.to_string(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Tag::Strikethrough => {
|
|
||||||
// TODO: Confirm that gpui currently doesn't support strikethroughs
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
|
||||||
|
|
||||||
Event::End(tag) => match tag {
|
Event::End(tag) => match tag {
|
||||||
Tag::Emphasis => {
|
TagEnd::Emphasis => {
|
||||||
italic_depth -= 1;
|
italic_depth -= 1;
|
||||||
}
|
}
|
||||||
Tag::Strong => {
|
TagEnd::Strong => {
|
||||||
bold_depth -= 1;
|
bold_depth -= 1;
|
||||||
}
|
}
|
||||||
Tag::Link(_, _, _) => {
|
TagEnd::Strikethrough => {
|
||||||
|
strikethrough_depth -= 1;
|
||||||
|
}
|
||||||
|
TagEnd::Link => {
|
||||||
link = None;
|
link = None;
|
||||||
}
|
}
|
||||||
Tag::Strikethrough => {
|
TagEnd::Paragraph => {
|
||||||
// TODO: Confirm that gpui currently doesn't support strikethroughs
|
|
||||||
}
|
|
||||||
Tag::Paragraph => {
|
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -328,14 +340,17 @@ impl<'a> MarkdownParser<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_table(&mut self) -> ParsedMarkdownTable {
|
fn parse_table(&mut self, alignment: Vec<Alignment>) -> ParsedMarkdownTable {
|
||||||
let (_event, source_range) = self.previous().unwrap();
|
let (_event, source_range) = self.previous().unwrap();
|
||||||
let source_range = source_range.clone();
|
let source_range = source_range.clone();
|
||||||
let mut header = ParsedMarkdownTableRow::new();
|
let mut header = ParsedMarkdownTableRow::new();
|
||||||
let mut body = vec![];
|
let mut body = vec![];
|
||||||
let mut current_row = vec![];
|
let mut current_row = vec![];
|
||||||
let mut in_header = true;
|
let mut in_header = true;
|
||||||
let mut alignment: Vec<ParsedMarkdownTableAlignment> = vec![];
|
let column_alignments = alignment
|
||||||
|
.iter()
|
||||||
|
.map(|a| Self::convert_alignment(a))
|
||||||
|
.collect();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if self.eof() {
|
if self.eof() {
|
||||||
@ -346,7 +361,7 @@ impl<'a> MarkdownParser<'a> {
|
|||||||
match current {
|
match current {
|
||||||
Event::Start(Tag::TableHead)
|
Event::Start(Tag::TableHead)
|
||||||
| Event::Start(Tag::TableRow)
|
| Event::Start(Tag::TableRow)
|
||||||
| Event::End(Tag::TableCell) => {
|
| Event::End(TagEnd::TableCell) => {
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
}
|
}
|
||||||
Event::Start(Tag::TableCell) => {
|
Event::Start(Tag::TableCell) => {
|
||||||
@ -354,7 +369,7 @@ impl<'a> MarkdownParser<'a> {
|
|||||||
let cell_contents = self.parse_text(false);
|
let cell_contents = self.parse_text(false);
|
||||||
current_row.push(cell_contents);
|
current_row.push(cell_contents);
|
||||||
}
|
}
|
||||||
Event::End(Tag::TableHead) | Event::End(Tag::TableRow) => {
|
Event::End(TagEnd::TableHead) | Event::End(TagEnd::TableRow) => {
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
let new_row = std::mem::replace(&mut current_row, vec![]);
|
let new_row = std::mem::replace(&mut current_row, vec![]);
|
||||||
if in_header {
|
if in_header {
|
||||||
@ -365,11 +380,7 @@ impl<'a> MarkdownParser<'a> {
|
|||||||
body.push(row);
|
body.push(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::End(Tag::Table(table_alignment)) => {
|
Event::End(TagEnd::Table) => {
|
||||||
alignment = table_alignment
|
|
||||||
.iter()
|
|
||||||
.map(|a| Self::convert_alignment(a))
|
|
||||||
.collect();
|
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -383,7 +394,7 @@ impl<'a> MarkdownParser<'a> {
|
|||||||
source_range,
|
source_range,
|
||||||
header,
|
header,
|
||||||
body,
|
body,
|
||||||
column_alignments: alignment,
|
column_alignments,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,7 +428,7 @@ impl<'a> MarkdownParser<'a> {
|
|||||||
let block = ParsedMarkdownElement::List(inner_list);
|
let block = ParsedMarkdownElement::List(inner_list);
|
||||||
current_list_items.push(Box::new(block));
|
current_list_items.push(Box::new(block));
|
||||||
}
|
}
|
||||||
Event::End(Tag::List(_)) => {
|
Event::End(TagEnd::List(_)) => {
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -451,7 +462,7 @@ impl<'a> MarkdownParser<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::End(Tag::Item) => {
|
Event::End(TagEnd::Item) => {
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
|
|
||||||
let item_type = if let Some(checked) = task_item {
|
let item_type = if let Some(checked) = task_item {
|
||||||
@ -525,7 +536,7 @@ impl<'a> MarkdownParser<'a> {
|
|||||||
Event::Start(Tag::BlockQuote) => {
|
Event::Start(Tag::BlockQuote) => {
|
||||||
nested_depth += 1;
|
nested_depth += 1;
|
||||||
}
|
}
|
||||||
Event::End(Tag::BlockQuote) => {
|
Event::End(TagEnd::BlockQuote) => {
|
||||||
nested_depth -= 1;
|
nested_depth -= 1;
|
||||||
if nested_depth == 0 {
|
if nested_depth == 0 {
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
@ -554,7 +565,7 @@ impl<'a> MarkdownParser<'a> {
|
|||||||
code.push_str(&text);
|
code.push_str(&text);
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
}
|
}
|
||||||
Event::End(Tag::CodeBlock(_)) => {
|
Event::End(TagEnd::CodeBlock) => {
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -642,6 +653,56 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nested_bold_strikethrough_text() {
|
||||||
|
let parsed = parse("Some **bo~~strikethrough~~ld** text");
|
||||||
|
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let paragraph = if let ParsedMarkdownElement::Paragraph(text) = &parsed.children[0] {
|
||||||
|
text
|
||||||
|
} else {
|
||||||
|
panic!("Expected a paragraph");
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
paragraph.highlights,
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
5..7,
|
||||||
|
MarkdownHighlight::Style(MarkdownHighlightStyle {
|
||||||
|
weight: FontWeight::BOLD,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
7..20,
|
||||||
|
MarkdownHighlight::Style(MarkdownHighlightStyle {
|
||||||
|
weight: FontWeight::BOLD,
|
||||||
|
strikethrough: true,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
20..22,
|
||||||
|
MarkdownHighlight::Style(MarkdownHighlightStyle {
|
||||||
|
weight: FontWeight::BOLD,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_header_only_table() {
|
fn test_header_only_table() {
|
||||||
let markdown = "\
|
let markdown = "\
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText, IntoElement,
|
AnyElement, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText, IntoElement,
|
||||||
SharedString, StyledText, UnderlineStyle, WindowContext,
|
SharedString, StrikethroughStyle, StyledText, UnderlineStyle, WindowContext,
|
||||||
};
|
};
|
||||||
use language::{HighlightId, Language, LanguageRegistry};
|
use language::{HighlightId, Language, LanguageRegistry};
|
||||||
use std::{ops::Range, sync::Arc};
|
use std::{ops::Range, sync::Arc};
|
||||||
@ -134,10 +134,11 @@ pub fn render_markdown_mut(
|
|||||||
link_ranges: &mut Vec<Range<usize>>,
|
link_ranges: &mut Vec<Range<usize>>,
|
||||||
link_urls: &mut Vec<String>,
|
link_urls: &mut Vec<String>,
|
||||||
) {
|
) {
|
||||||
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
|
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd};
|
||||||
|
|
||||||
let mut bold_depth = 0;
|
let mut bold_depth = 0;
|
||||||
let mut italic_depth = 0;
|
let mut italic_depth = 0;
|
||||||
|
let mut strikethrough_depth = 0;
|
||||||
let mut link_url = None;
|
let mut link_url = None;
|
||||||
let mut current_language = None;
|
let mut current_language = None;
|
||||||
let mut list_stack = Vec::new();
|
let mut list_stack = Vec::new();
|
||||||
@ -175,6 +176,12 @@ pub fn render_markdown_mut(
|
|||||||
if italic_depth > 0 {
|
if italic_depth > 0 {
|
||||||
style.font_style = Some(FontStyle::Italic);
|
style.font_style = Some(FontStyle::Italic);
|
||||||
}
|
}
|
||||||
|
if strikethrough_depth > 0 {
|
||||||
|
style.strikethrough = Some(StrikethroughStyle {
|
||||||
|
thickness: 1.0.into(),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
let last_run_len = if let Some(link_url) = link_url.clone() {
|
let last_run_len = if let Some(link_url) = link_url.clone() {
|
||||||
link_ranges.push(prev_len..text.len());
|
link_ranges.push(prev_len..text.len());
|
||||||
link_urls.push(link_url);
|
link_urls.push(link_url);
|
||||||
@ -249,7 +256,12 @@ pub fn render_markdown_mut(
|
|||||||
}
|
}
|
||||||
Event::Start(tag) => match tag {
|
Event::Start(tag) => match tag {
|
||||||
Tag::Paragraph => new_paragraph(text, &mut list_stack),
|
Tag::Paragraph => new_paragraph(text, &mut list_stack),
|
||||||
Tag::Heading(_, _, _) => {
|
Tag::Heading {
|
||||||
|
level: _,
|
||||||
|
id: _,
|
||||||
|
classes: _,
|
||||||
|
attrs: _,
|
||||||
|
} => {
|
||||||
new_paragraph(text, &mut list_stack);
|
new_paragraph(text, &mut list_stack);
|
||||||
bold_depth += 1;
|
bold_depth += 1;
|
||||||
}
|
}
|
||||||
@ -266,7 +278,13 @@ pub fn render_markdown_mut(
|
|||||||
}
|
}
|
||||||
Tag::Emphasis => italic_depth += 1,
|
Tag::Emphasis => italic_depth += 1,
|
||||||
Tag::Strong => bold_depth += 1,
|
Tag::Strong => bold_depth += 1,
|
||||||
Tag::Link(_, url, _) => link_url = Some(url.to_string()),
|
Tag::Strikethrough => strikethrough_depth += 1,
|
||||||
|
Tag::Link {
|
||||||
|
link_type: _,
|
||||||
|
dest_url,
|
||||||
|
title: _,
|
||||||
|
id: _,
|
||||||
|
} => link_url = Some(dest_url.to_string()),
|
||||||
Tag::List(number) => {
|
Tag::List(number) => {
|
||||||
list_stack.push((number, false));
|
list_stack.push((number, false));
|
||||||
}
|
}
|
||||||
@ -292,12 +310,13 @@ pub fn render_markdown_mut(
|
|||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
Event::End(tag) => match tag {
|
Event::End(tag) => match tag {
|
||||||
Tag::Heading(_, _, _) => bold_depth -= 1,
|
TagEnd::Heading(_) => bold_depth -= 1,
|
||||||
Tag::CodeBlock(_) => current_language = None,
|
TagEnd::CodeBlock => current_language = None,
|
||||||
Tag::Emphasis => italic_depth -= 1,
|
TagEnd::Emphasis => italic_depth -= 1,
|
||||||
Tag::Strong => bold_depth -= 1,
|
TagEnd::Strong => bold_depth -= 1,
|
||||||
Tag::Link(_, _, _) => link_url = None,
|
TagEnd::Strikethrough => strikethrough_depth -= 1,
|
||||||
Tag::List(_) => drop(list_stack.pop()),
|
TagEnd::Link => link_url = None,
|
||||||
|
TagEnd::List(_) => drop(list_stack.pop()),
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
Event::HardBreak => text.push('\n'),
|
Event::HardBreak => text.push('\n'),
|
||||||
|
Loading…
Reference in New Issue
Block a user