text_view: Fix nested Blockquote render in Markdown and HTML. (#1053)
<img width="986" height="271" alt="image" src="https://github.com/user-attachments/assets/1de04629-2bdc-48f2-ae69-45c33482aeff" /> - Also fixed short language name in code block, e.g.: `rs` -> `rust`.
This commit is contained in:
parent
3b759cd448
commit
82b87d92ab
6 changed files with 103 additions and 99 deletions
|
|
@ -16,8 +16,19 @@
|
|||
<p>This is a text before blockquote.</p>
|
||||
<blockquote>
|
||||
This is before paragraph in blockquote.
|
||||
<p>This is a second blockquote paragraph.</p>
|
||||
<p>This is after paragraph in blockquote.</p>
|
||||
<p>This is first blockquote paragraph.</p>
|
||||
<blockquote>
|
||||
<p>This is second level</p>
|
||||
<blockquote>
|
||||
<p>This is third level</p>
|
||||
</blockquote>
|
||||
|
||||
<ul>
|
||||
<li>List item in a blockquote.</li>
|
||||
<li>Second list item</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
This is after paragraph in blockquote.
|
||||
</blockquote>
|
||||
<style type="text/css">
|
||||
.highlight {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,11 @@ And this is next blockquote
|
|||
> First level
|
||||
>
|
||||
> > Second level
|
||||
> > Third level
|
||||
>
|
||||
> ```rs
|
||||
> const FOO: &str = "bar";
|
||||
> ```
|
||||
|
||||
## Code block
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ use std::{
|
|||
};
|
||||
|
||||
use super::LanguageConfig;
|
||||
use crate::{highlighter::languages, ThemeMode};
|
||||
use crate::{
|
||||
highlighter::{languages, Language},
|
||||
ThemeMode,
|
||||
};
|
||||
|
||||
pub(super) fn init(cx: &mut App) {
|
||||
let mut register = LanguageRegistry::new();
|
||||
for language in languages::Language::all() {
|
||||
register.register(language.name(), &language.config());
|
||||
}
|
||||
let register = LanguageRegistry::new();
|
||||
|
||||
cx.set_global(register);
|
||||
}
|
||||
|
|
@ -422,12 +422,19 @@ impl LanguageRegistry {
|
|||
cx.global_mut::<LanguageRegistry>()
|
||||
}
|
||||
|
||||
/// Create a new language registry with default languages and themes.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
let mut registry = Self {
|
||||
languages: HashMap::new(),
|
||||
light_theme: Arc::new(HighlightTheme::default_light()),
|
||||
dark_theme: Arc::new(HighlightTheme::default_dark()),
|
||||
};
|
||||
|
||||
for language in languages::Language::all() {
|
||||
registry.register(language.name(), &language.config());
|
||||
}
|
||||
|
||||
registry
|
||||
}
|
||||
|
||||
pub fn register(&mut self, lang: &str, config: &LanguageConfig) {
|
||||
|
|
@ -455,7 +462,8 @@ impl LanguageRegistry {
|
|||
|
||||
/// Returns the language configuration for the given language name.
|
||||
pub fn language(&self, name: &str) -> Option<&LanguageConfig> {
|
||||
self.languages.get(name)
|
||||
let language = Language::from_str(name);
|
||||
self.languages.get(language.name())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -475,4 +483,15 @@ mod tests {
|
|||
assert_eq!(syntax.style("keyword.repeat"), Some(rgb(0x0433ff).into()));
|
||||
assert_eq!(syntax.style("foo"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registry() {
|
||||
use super::LanguageRegistry;
|
||||
let registry = LanguageRegistry::new();
|
||||
|
||||
assert!(registry.language("rust").is_some());
|
||||
assert!(registry.language("rs").is_some());
|
||||
assert!(registry.language("javascript").is_some());
|
||||
assert!(registry.language("js").is_some());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -263,7 +263,9 @@ pub enum Node {
|
|||
level: u8,
|
||||
children: Paragraph,
|
||||
},
|
||||
Blockquote(Paragraph),
|
||||
Blockquote {
|
||||
children: Vec<Node>,
|
||||
},
|
||||
List {
|
||||
/// Only contains ListItem, others will be ignored
|
||||
children: Vec<Node>,
|
||||
|
|
@ -670,14 +672,20 @@ impl Node {
|
|||
.child(children)
|
||||
.into_any_element()
|
||||
}
|
||||
Node::Blockquote(children) => div()
|
||||
Node::Blockquote { children } => div()
|
||||
.w_full()
|
||||
.mb(mb)
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.border_l_3()
|
||||
.border_color(cx.theme().secondary_active)
|
||||
.px_4()
|
||||
.child(children)
|
||||
.children({
|
||||
let children_len = children.len();
|
||||
children.into_iter().enumerate().map(move |(index, c)| {
|
||||
let is_last_child = is_root && index == children_len - 1;
|
||||
c.render(None, false, is_last_child, style, window, cx)
|
||||
})
|
||||
})
|
||||
.into_any_element(),
|
||||
Node::List { children, ordered } => v_flex()
|
||||
.mb(mb)
|
||||
|
|
@ -789,8 +797,13 @@ impl Node {
|
|||
let hashes = "#".repeat(*level as usize);
|
||||
format!("{} {}", hashes, children.to_markdown())
|
||||
}
|
||||
Node::Blockquote(paragraph) => {
|
||||
let content = paragraph.to_markdown();
|
||||
Node::Blockquote { children } => {
|
||||
let content = children
|
||||
.iter()
|
||||
.map(|child| child.to_markdown())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n\n");
|
||||
|
||||
content
|
||||
.lines()
|
||||
.map(|line| format!("> {}", line))
|
||||
|
|
|
|||
|
|
@ -562,10 +562,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
|||
| local_name!("h5")
|
||||
| local_name!("h6") => {
|
||||
let mut children = vec![];
|
||||
if !paragraph.is_empty() {
|
||||
children.push(element::Node::Paragraph(paragraph.clone()));
|
||||
paragraph.clear();
|
||||
}
|
||||
consume_paragraph(&mut children, paragraph);
|
||||
|
||||
let level = name
|
||||
.local
|
||||
|
|
@ -594,10 +591,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
|||
}
|
||||
local_name!("img") => {
|
||||
let mut children = vec![];
|
||||
if !paragraph.is_empty() {
|
||||
children.push(element::Node::Paragraph(paragraph.clone()));
|
||||
paragraph.clear();
|
||||
}
|
||||
consume_paragraph(&mut children, paragraph);
|
||||
|
||||
let Some(src) = attr_value(attrs, local_name!("src")) else {
|
||||
if cfg!(debug_assertions) {
|
||||
|
|
@ -629,36 +623,13 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
|||
}
|
||||
}
|
||||
local_name!("ul") | local_name!("ol") => {
|
||||
let mut children = vec![];
|
||||
if !paragraph.is_empty() {
|
||||
children.push(element::Node::Paragraph(paragraph.clone()));
|
||||
paragraph.clear();
|
||||
}
|
||||
|
||||
let ordered = name.local == local_name!("ol");
|
||||
|
||||
let mut list_children = vec![];
|
||||
|
||||
for child in node.children.borrow().iter() {
|
||||
let mut child_paragraph = Paragraph::default();
|
||||
if let Some(child_node) = parse_node(child, &mut child_paragraph) {
|
||||
list_children.push(child_node);
|
||||
}
|
||||
}
|
||||
|
||||
let list = element::Node::List {
|
||||
children: list_children,
|
||||
ordered,
|
||||
};
|
||||
if children.len() > 0 {
|
||||
children.push(list);
|
||||
Some(element::Node::Root { children })
|
||||
} else {
|
||||
Some(list)
|
||||
}
|
||||
let children = consume_children_nodes(node, paragraph);
|
||||
Some(element::Node::List { children, ordered })
|
||||
}
|
||||
local_name!("li") => {
|
||||
let mut children = vec![];
|
||||
consume_paragraph(&mut children, paragraph);
|
||||
|
||||
for child in node.children.borrow().iter() {
|
||||
let mut child_paragraph = Paragraph::default();
|
||||
|
|
@ -679,10 +650,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
|||
}
|
||||
}
|
||||
|
||||
if !paragraph.is_empty() {
|
||||
children.push(element::Node::Paragraph(paragraph.clone()));
|
||||
paragraph.clear();
|
||||
}
|
||||
consume_paragraph(&mut children, paragraph);
|
||||
|
||||
Some(element::Node::ListItem {
|
||||
children,
|
||||
|
|
@ -692,10 +660,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
|||
}
|
||||
local_name!("table") => {
|
||||
let mut children = vec![];
|
||||
if !paragraph.is_empty() {
|
||||
children.push(element::Node::Paragraph(paragraph.clone()));
|
||||
paragraph.clear();
|
||||
}
|
||||
consume_paragraph(&mut children, paragraph);
|
||||
|
||||
let mut table = Table::default();
|
||||
for child in node.children.borrow().iter() {
|
||||
|
|
@ -713,6 +678,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
|||
}
|
||||
}
|
||||
}
|
||||
consume_paragraph(&mut children, paragraph);
|
||||
|
||||
let table = element::Node::Table(table);
|
||||
if children.len() > 0 {
|
||||
|
|
@ -723,22 +689,8 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
|||
}
|
||||
}
|
||||
local_name!("blockquote") => {
|
||||
let mut children = vec![];
|
||||
if !paragraph.is_empty() {
|
||||
children.push(element::Node::Paragraph(paragraph.clone()));
|
||||
paragraph.clear();
|
||||
}
|
||||
|
||||
let mut blockquote = Paragraph::default();
|
||||
for (i, child) in node.children.borrow().iter().enumerate() {
|
||||
if i > 0 {
|
||||
blockquote.push_str("\n");
|
||||
}
|
||||
parse_paragraph(&mut blockquote, child);
|
||||
}
|
||||
children.push(element::Node::Blockquote(blockquote));
|
||||
|
||||
Some(element::Node::Root { children: children })
|
||||
let children = consume_children_nodes(node, paragraph);
|
||||
Some(element::Node::Blockquote { children })
|
||||
}
|
||||
local_name!("style") | local_name!("script") => None,
|
||||
_ => {
|
||||
|
|
@ -750,10 +702,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
|||
// Hello <p>Inner text of block element</p> World
|
||||
|
||||
// Insert before text as a node -- The "Hello"
|
||||
if !paragraph.is_empty() {
|
||||
children.push(element::Node::Paragraph(paragraph.clone()));
|
||||
paragraph.clear();
|
||||
}
|
||||
consume_paragraph(&mut children, paragraph);
|
||||
|
||||
// Inner of the block element -- The "Inner text of block element"
|
||||
for child in node.children.borrow().iter() {
|
||||
|
|
@ -761,11 +710,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
|||
children.push(child_node);
|
||||
}
|
||||
}
|
||||
|
||||
// if !paragraph.is_empty() {
|
||||
// children.push(element::Node::Paragraph(paragraph.clone()));
|
||||
// paragraph.clear();
|
||||
// }
|
||||
consume_paragraph(&mut children, paragraph);
|
||||
|
||||
if children.is_empty() {
|
||||
None
|
||||
|
|
@ -787,18 +732,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
|||
}
|
||||
},
|
||||
NodeData::Document => {
|
||||
let mut children = vec![];
|
||||
for child in node.children.borrow().iter() {
|
||||
if let Some(child_node) = parse_node(child, paragraph) {
|
||||
children.push(child_node);
|
||||
}
|
||||
}
|
||||
|
||||
if !paragraph.is_empty() {
|
||||
children.push(element::Node::Paragraph(paragraph.clone()));
|
||||
paragraph.clear();
|
||||
}
|
||||
|
||||
let children = consume_children_nodes(node, paragraph);
|
||||
Some(element::Node::Root { children })
|
||||
}
|
||||
NodeData::Doctype { .. }
|
||||
|
|
@ -807,6 +741,28 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
|||
}
|
||||
}
|
||||
|
||||
fn consume_children_nodes(node: &Node, paragraph: &mut Paragraph) -> Vec<element::Node> {
|
||||
let mut children = vec![];
|
||||
consume_paragraph(&mut children, paragraph);
|
||||
for child in node.children.borrow().iter() {
|
||||
if let Some(child_node) = parse_node(child, paragraph) {
|
||||
children.push(child_node);
|
||||
}
|
||||
consume_paragraph(&mut children, paragraph);
|
||||
}
|
||||
|
||||
children
|
||||
}
|
||||
|
||||
fn consume_paragraph(children: &mut Vec<element::Node>, paragraph: &mut Paragraph) {
|
||||
if paragraph.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
children.push(element::Node::Paragraph(paragraph.clone()));
|
||||
paragraph.clear();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::{px, relative};
|
||||
|
|
|
|||
|
|
@ -382,12 +382,12 @@ fn ast_to_node(value: mdast::Node, style: &TextViewStyle, cx: &mut App) -> eleme
|
|||
element::Node::Paragraph(paragraph)
|
||||
}
|
||||
Node::Blockquote(val) => {
|
||||
let mut paragraph = Paragraph::default();
|
||||
val.children.iter().for_each(|c| {
|
||||
parse_paragraph(&mut paragraph, c);
|
||||
});
|
||||
|
||||
element::Node::Blockquote(paragraph)
|
||||
let children = val
|
||||
.children
|
||||
.into_iter()
|
||||
.map(|c| ast_to_node(c, style, cx))
|
||||
.collect();
|
||||
element::Node::Blockquote { children }
|
||||
}
|
||||
Node::List(list) => {
|
||||
let children = list
|
||||
|
|
|
|||
Loading…
Reference in a new issue