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>
|
<p>This is a text before blockquote.</p>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
This is before paragraph in blockquote.
|
This is before paragraph in blockquote.
|
||||||
<p>This is a second blockquote paragraph.</p>
|
<p>This is first blockquote paragraph.</p>
|
||||||
<p>This is after paragraph in blockquote.</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>
|
</blockquote>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.highlight {
|
.highlight {
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,11 @@ And this is next blockquote
|
||||||
> First level
|
> First level
|
||||||
>
|
>
|
||||||
> > Second level
|
> > Second level
|
||||||
|
> > Third level
|
||||||
|
>
|
||||||
|
> ```rs
|
||||||
|
> const FOO: &str = "bar";
|
||||||
|
> ```
|
||||||
|
|
||||||
## Code block
|
## Code block
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::LanguageConfig;
|
use super::LanguageConfig;
|
||||||
use crate::{highlighter::languages, ThemeMode};
|
use crate::{
|
||||||
|
highlighter::{languages, Language},
|
||||||
|
ThemeMode,
|
||||||
|
};
|
||||||
|
|
||||||
pub(super) fn init(cx: &mut App) {
|
pub(super) fn init(cx: &mut App) {
|
||||||
let mut register = LanguageRegistry::new();
|
let register = LanguageRegistry::new();
|
||||||
for language in languages::Language::all() {
|
|
||||||
register.register(language.name(), &language.config());
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.set_global(register);
|
cx.set_global(register);
|
||||||
}
|
}
|
||||||
|
|
@ -422,12 +422,19 @@ impl LanguageRegistry {
|
||||||
cx.global_mut::<LanguageRegistry>()
|
cx.global_mut::<LanguageRegistry>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new language registry with default languages and themes.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
let mut registry = Self {
|
||||||
languages: HashMap::new(),
|
languages: HashMap::new(),
|
||||||
light_theme: Arc::new(HighlightTheme::default_light()),
|
light_theme: Arc::new(HighlightTheme::default_light()),
|
||||||
dark_theme: Arc::new(HighlightTheme::default_dark()),
|
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) {
|
pub fn register(&mut self, lang: &str, config: &LanguageConfig) {
|
||||||
|
|
@ -455,7 +462,8 @@ impl LanguageRegistry {
|
||||||
|
|
||||||
/// Returns the language configuration for the given language name.
|
/// Returns the language configuration for the given language name.
|
||||||
pub fn language(&self, name: &str) -> Option<&LanguageConfig> {
|
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("keyword.repeat"), Some(rgb(0x0433ff).into()));
|
||||||
assert_eq!(syntax.style("foo"), None);
|
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,
|
level: u8,
|
||||||
children: Paragraph,
|
children: Paragraph,
|
||||||
},
|
},
|
||||||
Blockquote(Paragraph),
|
Blockquote {
|
||||||
|
children: Vec<Node>,
|
||||||
|
},
|
||||||
List {
|
List {
|
||||||
/// Only contains ListItem, others will be ignored
|
/// Only contains ListItem, others will be ignored
|
||||||
children: Vec<Node>,
|
children: Vec<Node>,
|
||||||
|
|
@ -670,14 +672,20 @@ impl Node {
|
||||||
.child(children)
|
.child(children)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
Node::Blockquote(children) => div()
|
Node::Blockquote { children } => div()
|
||||||
.w_full()
|
.w_full()
|
||||||
.mb(mb)
|
.mb(mb)
|
||||||
.text_color(cx.theme().muted_foreground)
|
.text_color(cx.theme().muted_foreground)
|
||||||
.border_l_3()
|
.border_l_3()
|
||||||
.border_color(cx.theme().secondary_active)
|
.border_color(cx.theme().secondary_active)
|
||||||
.px_4()
|
.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(),
|
.into_any_element(),
|
||||||
Node::List { children, ordered } => v_flex()
|
Node::List { children, ordered } => v_flex()
|
||||||
.mb(mb)
|
.mb(mb)
|
||||||
|
|
@ -789,8 +797,13 @@ impl Node {
|
||||||
let hashes = "#".repeat(*level as usize);
|
let hashes = "#".repeat(*level as usize);
|
||||||
format!("{} {}", hashes, children.to_markdown())
|
format!("{} {}", hashes, children.to_markdown())
|
||||||
}
|
}
|
||||||
Node::Blockquote(paragraph) => {
|
Node::Blockquote { children } => {
|
||||||
let content = paragraph.to_markdown();
|
let content = children
|
||||||
|
.iter()
|
||||||
|
.map(|child| child.to_markdown())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n\n");
|
||||||
|
|
||||||
content
|
content
|
||||||
.lines()
|
.lines()
|
||||||
.map(|line| format!("> {}", line))
|
.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!("h5")
|
||||||
| local_name!("h6") => {
|
| local_name!("h6") => {
|
||||||
let mut children = vec![];
|
let mut children = vec![];
|
||||||
if !paragraph.is_empty() {
|
consume_paragraph(&mut children, paragraph);
|
||||||
children.push(element::Node::Paragraph(paragraph.clone()));
|
|
||||||
paragraph.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
let level = name
|
let level = name
|
||||||
.local
|
.local
|
||||||
|
|
@ -594,10 +591,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
||||||
}
|
}
|
||||||
local_name!("img") => {
|
local_name!("img") => {
|
||||||
let mut children = vec![];
|
let mut children = vec![];
|
||||||
if !paragraph.is_empty() {
|
consume_paragraph(&mut children, paragraph);
|
||||||
children.push(element::Node::Paragraph(paragraph.clone()));
|
|
||||||
paragraph.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(src) = attr_value(attrs, local_name!("src")) else {
|
let Some(src) = attr_value(attrs, local_name!("src")) else {
|
||||||
if cfg!(debug_assertions) {
|
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") => {
|
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 ordered = name.local == local_name!("ol");
|
||||||
|
let children = consume_children_nodes(node, paragraph);
|
||||||
let mut list_children = vec![];
|
Some(element::Node::List { children, ordered })
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
local_name!("li") => {
|
local_name!("li") => {
|
||||||
let mut children = vec![];
|
let mut children = vec![];
|
||||||
|
consume_paragraph(&mut children, paragraph);
|
||||||
|
|
||||||
for child in node.children.borrow().iter() {
|
for child in node.children.borrow().iter() {
|
||||||
let mut child_paragraph = Paragraph::default();
|
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() {
|
consume_paragraph(&mut children, paragraph);
|
||||||
children.push(element::Node::Paragraph(paragraph.clone()));
|
|
||||||
paragraph.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(element::Node::ListItem {
|
Some(element::Node::ListItem {
|
||||||
children,
|
children,
|
||||||
|
|
@ -692,10 +660,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
||||||
}
|
}
|
||||||
local_name!("table") => {
|
local_name!("table") => {
|
||||||
let mut children = vec![];
|
let mut children = vec![];
|
||||||
if !paragraph.is_empty() {
|
consume_paragraph(&mut children, paragraph);
|
||||||
children.push(element::Node::Paragraph(paragraph.clone()));
|
|
||||||
paragraph.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut table = Table::default();
|
let mut table = Table::default();
|
||||||
for child in node.children.borrow().iter() {
|
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);
|
let table = element::Node::Table(table);
|
||||||
if children.len() > 0 {
|
if children.len() > 0 {
|
||||||
|
|
@ -723,22 +689,8 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
local_name!("blockquote") => {
|
local_name!("blockquote") => {
|
||||||
let mut children = vec![];
|
let children = consume_children_nodes(node, paragraph);
|
||||||
if !paragraph.is_empty() {
|
Some(element::Node::Blockquote { children })
|
||||||
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 })
|
|
||||||
}
|
}
|
||||||
local_name!("style") | local_name!("script") => None,
|
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
|
// Hello <p>Inner text of block element</p> World
|
||||||
|
|
||||||
// Insert before text as a node -- The "Hello"
|
// Insert before text as a node -- The "Hello"
|
||||||
if !paragraph.is_empty() {
|
consume_paragraph(&mut children, paragraph);
|
||||||
children.push(element::Node::Paragraph(paragraph.clone()));
|
|
||||||
paragraph.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inner of the block element -- The "Inner text of block element"
|
// Inner of the block element -- The "Inner text of block element"
|
||||||
for child in node.children.borrow().iter() {
|
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);
|
children.push(child_node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
consume_paragraph(&mut children, paragraph);
|
||||||
// if !paragraph.is_empty() {
|
|
||||||
// children.push(element::Node::Paragraph(paragraph.clone()));
|
|
||||||
// paragraph.clear();
|
|
||||||
// }
|
|
||||||
|
|
||||||
if children.is_empty() {
|
if children.is_empty() {
|
||||||
None
|
None
|
||||||
|
|
@ -787,18 +732,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Nod
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
NodeData::Document => {
|
NodeData::Document => {
|
||||||
let mut children = vec![];
|
let children = consume_children_nodes(node, paragraph);
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(element::Node::Root { children })
|
Some(element::Node::Root { children })
|
||||||
}
|
}
|
||||||
NodeData::Doctype { .. }
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use gpui::{px, relative};
|
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)
|
element::Node::Paragraph(paragraph)
|
||||||
}
|
}
|
||||||
Node::Blockquote(val) => {
|
Node::Blockquote(val) => {
|
||||||
let mut paragraph = Paragraph::default();
|
let children = val
|
||||||
val.children.iter().for_each(|c| {
|
.children
|
||||||
parse_paragraph(&mut paragraph, c);
|
.into_iter()
|
||||||
});
|
.map(|c| ast_to_node(c, style, cx))
|
||||||
|
.collect();
|
||||||
element::Node::Blockquote(paragraph)
|
element::Node::Blockquote { children }
|
||||||
}
|
}
|
||||||
Node::List(list) => {
|
Node::List(list) => {
|
||||||
let children = list
|
let children = list
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue