markdown: Add to support LinkReference and Footnote. (#1201)
This commit is contained in:
parent
f4a292d975
commit
ee40f65fb5
6 changed files with 232 additions and 81 deletions
|
|
@ -2,13 +2,19 @@
|
||||||
|
|
||||||
Build Status [](https://github.com/longbridge/gpui-component/actions/workflows/ci.yml) of [GPUI Component](https://github.com/longbridge/gpui-component).
|
Build Status [](https://github.com/longbridge/gpui-component/actions/workflows/ci.yml) of [GPUI Component](https://github.com/longbridge/gpui-component).
|
||||||
|
|
||||||
This is first paragraph, there have **BOLD**, _italic_, and ~strikethrough~, `code` text.
|
This is first paragraph, there have **BOLD**, _italic_, and ~strikethrough~, `code` text [^1] [^2].
|
||||||
|
|
||||||
This is an additional demonstration paragraph in English demonstrating more content for [Markdown GFM](https://github.github.com/gfm/). It includes various stylistic elements and plain text.
|
This is an additional demonstration paragraph in English demonstrating more content for [Markdown GFM]. It includes various stylistic elements and plain text.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
这是一个中文演示段落,用于展示更多的 [Markdown GFM](https://github.github.com/gfm/) 内容。您可以在此尝试使用使用**粗体**、*斜体*和`代码`等样式。これは日本語のデモ段落です。Markdown の多言語サポートを示すためのテキストが含まれています。例えば、、**ボールド**、_イタリック_、および`コード`のスタイルなどを試すことができます。
|
这是一个中文演示段落,用于展示更多的 [Markdown GFM] 内容。您可以在此尝试使用使用**粗体**、*斜体*和`代码`等样式。これは日本語のデモ段落です。Markdown の多言語サポートを示すためのテキストが含まれています。例えば、、**ボールド**、_イタリック_、および`コード`のスタイルなどを試すことができます。
|
||||||
|
|
||||||
|
[Markdown GFM]: https://github.github.com/gfm/
|
||||||
|
|
||||||
|
[^1]: This is a footnote example.
|
||||||
|
|
||||||
|
[^2]: Here is another footnote.
|
||||||
|
|
||||||
## Basic formatting
|
## Basic formatting
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,7 @@ impl SyntaxHighlighter {
|
||||||
selected_range: &Range<usize>,
|
selected_range: &Range<usize>,
|
||||||
full_text: &SharedString,
|
full_text: &SharedString,
|
||||||
new_text: &str,
|
new_text: &str,
|
||||||
cx: &mut App,
|
cx: &App,
|
||||||
) {
|
) {
|
||||||
if &self.text == full_text {
|
if &self.text == full_text {
|
||||||
return;
|
return;
|
||||||
|
|
@ -324,7 +324,7 @@ impl SyntaxHighlighter {
|
||||||
/// NOTE: 10K lines, about 180ms
|
/// NOTE: 10K lines, about 180ms
|
||||||
/// FIXME: To improve the performance when there more than 5K lines, use partial update.
|
/// FIXME: To improve the performance when there more than 5K lines, use partial update.
|
||||||
/// Ref: https://github.com/longbridge/gpui-component/pull/1197
|
/// Ref: https://github.com/longbridge/gpui-component/pull/1197
|
||||||
fn build_styles(&mut self, cx: &mut App) {
|
fn build_styles(&mut self, cx: &App) {
|
||||||
let Some(tree) = &self.old_tree else {
|
let Some(tree) = &self.old_tree else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ use crate::text::TextViewState;
|
||||||
use crate::v_flex;
|
use crate::v_flex;
|
||||||
|
|
||||||
use crate::text::node::{
|
use crate::text::node::{
|
||||||
self, ImageNode, InlineNode, LinkMark, Paragraph, Table, TableRow, TextMark,
|
self, ImageNode, InlineNode, LinkMark, NodeContext, Paragraph, Table, TableRow, TextMark,
|
||||||
};
|
};
|
||||||
use crate::text::TextViewStyle;
|
use crate::text::TextViewStyle;
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ const BLOCK_ELEMENTS: [&str; 35] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Parse HTML into AST Node.
|
/// Parse HTML into AST Node.
|
||||||
pub(crate) fn parse(source: &str) -> Result<node::Node, SharedString> {
|
pub(crate) fn parse(source: &str, cx: &mut NodeContext) -> Result<node::Node, SharedString> {
|
||||||
let opts = ParseOpts {
|
let opts = ParseOpts {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
@ -77,7 +77,8 @@ pub(crate) fn parse(source: &str) -> Result<node::Node, SharedString> {
|
||||||
|
|
||||||
let mut paragraph = Paragraph::default();
|
let mut paragraph = Paragraph::default();
|
||||||
// NOTE: The outer paragraph is not used.
|
// NOTE: The outer paragraph is not used.
|
||||||
let node: node::Node = parse_node(&dom.document, &mut paragraph).unwrap_or(node::Node::Unknown);
|
let node: node::Node =
|
||||||
|
parse_node(&dom.document, &mut paragraph, cx).unwrap_or(node::Node::Unknown);
|
||||||
let node = node.compact();
|
let node = node.compact();
|
||||||
|
|
||||||
Ok(node)
|
Ok(node)
|
||||||
|
|
@ -131,8 +132,10 @@ impl RenderOnce for HtmlElement {
|
||||||
});
|
});
|
||||||
|
|
||||||
let root = self.state.read(cx).root();
|
let root = self.state.read(cx).root();
|
||||||
|
let node_cx = self.state.read(cx).node_cx.clone();
|
||||||
|
|
||||||
div().map(|this| match root {
|
div().map(|this| match root {
|
||||||
Ok(node) => this.child(node.render(None, true, true, &self.style, window, cx)),
|
Ok(node) => this.child(node.render(None, true, true, &node_cx, window, cx)),
|
||||||
Err(err) => this.child(
|
Err(err) => this.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
|
@ -363,6 +366,7 @@ fn parse_paragraph(
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.into(),
|
.into(),
|
||||||
title: attr_value(&attrs, local_name!("title")).map(Into::into),
|
title: attr_value(&attrs, local_name!("title")).map(Into::into),
|
||||||
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
paragraph.push(InlineNode::new(&text).marks(marks.clone()));
|
paragraph.push(InlineNode::new(&text).marks(marks.clone()));
|
||||||
|
|
@ -411,7 +415,11 @@ fn parse_paragraph(
|
||||||
(text, marks)
|
(text, marks)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<node::Node> {
|
fn parse_node(
|
||||||
|
node: &Rc<Node>,
|
||||||
|
paragraph: &mut Paragraph,
|
||||||
|
cx: &mut NodeContext,
|
||||||
|
) -> Option<node::Node> {
|
||||||
match node.data {
|
match node.data {
|
||||||
NodeData::Text { ref contents } => {
|
NodeData::Text { ref contents } => {
|
||||||
let text = contents.borrow().to_string();
|
let text = contents.borrow().to_string();
|
||||||
|
|
@ -495,7 +503,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<node::Node>
|
||||||
}
|
}
|
||||||
local_name!("ul") | local_name!("ol") => {
|
local_name!("ul") | local_name!("ol") => {
|
||||||
let ordered = name.local == local_name!("ol");
|
let ordered = name.local == local_name!("ol");
|
||||||
let children = consume_children_nodes(node, paragraph);
|
let children = consume_children_nodes(node, paragraph, cx);
|
||||||
Some(node::Node::List { children, ordered })
|
Some(node::Node::List { children, ordered })
|
||||||
}
|
}
|
||||||
local_name!("li") => {
|
local_name!("li") => {
|
||||||
|
|
@ -504,7 +512,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<node::Node>
|
||||||
|
|
||||||
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();
|
||||||
if let Some(child_node) = parse_node(child, &mut child_paragraph) {
|
if let Some(child_node) = parse_node(child, &mut child_paragraph, cx) {
|
||||||
children.push(child_node);
|
children.push(child_node);
|
||||||
}
|
}
|
||||||
if child_paragraph.text_len() > 0 {
|
if child_paragraph.text_len() > 0 {
|
||||||
|
|
@ -559,7 +567,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<node::Node>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
local_name!("blockquote") => {
|
local_name!("blockquote") => {
|
||||||
let children = consume_children_nodes(node, paragraph);
|
let children = consume_children_nodes(node, paragraph, cx);
|
||||||
Some(node::Node::Blockquote { children })
|
Some(node::Node::Blockquote { children })
|
||||||
}
|
}
|
||||||
local_name!("style") | local_name!("script") => None,
|
local_name!("style") | local_name!("script") => None,
|
||||||
|
|
@ -576,7 +584,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<node::Node>
|
||||||
|
|
||||||
// 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() {
|
||||||
if let Some(child_node) = parse_node(child, paragraph) {
|
if let Some(child_node) = parse_node(child, paragraph, cx) {
|
||||||
children.push(child_node);
|
children.push(child_node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -602,7 +610,7 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<node::Node>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
NodeData::Document => {
|
NodeData::Document => {
|
||||||
let children = consume_children_nodes(node, paragraph);
|
let children = consume_children_nodes(node, paragraph, cx);
|
||||||
Some(node::Node::Root { children })
|
Some(node::Node::Root { children })
|
||||||
}
|
}
|
||||||
NodeData::Doctype { .. }
|
NodeData::Doctype { .. }
|
||||||
|
|
@ -611,11 +619,15 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<node::Node>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn consume_children_nodes(node: &Node, paragraph: &mut Paragraph) -> Vec<node::Node> {
|
fn consume_children_nodes(
|
||||||
|
node: &Node,
|
||||||
|
paragraph: &mut Paragraph,
|
||||||
|
cx: &mut NodeContext,
|
||||||
|
) -> Vec<node::Node> {
|
||||||
let mut children = vec![];
|
let mut children = vec![];
|
||||||
consume_paragraph(&mut children, paragraph);
|
consume_paragraph(&mut children, paragraph);
|
||||||
for child in node.children.borrow().iter() {
|
for child in node.children.borrow().iter() {
|
||||||
if let Some(child_node) = parse_node(child, paragraph) {
|
if let Some(child_node) = parse_node(child, paragraph, cx) {
|
||||||
children.push(child_node);
|
children.push(child_node);
|
||||||
}
|
}
|
||||||
consume_paragraph(&mut children, paragraph);
|
consume_paragraph(&mut children, paragraph);
|
||||||
|
|
@ -637,7 +649,7 @@ fn consume_paragraph(children: &mut Vec<node::Node>, paragraph: &mut Paragraph)
|
||||||
mod tests {
|
mod tests {
|
||||||
use gpui::{px, relative};
|
use gpui::{px, relative};
|
||||||
|
|
||||||
use crate::text::node::{ImageNode, InlineNode, Node, Paragraph};
|
use crate::text::node::{ImageNode, InlineNode, Node, NodeContext, Paragraph};
|
||||||
|
|
||||||
use super::trim_text;
|
use super::trim_text;
|
||||||
|
|
||||||
|
|
@ -674,7 +686,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keep_spaces() {
|
fn test_keep_spaces() {
|
||||||
let html = r#"<p>and <code>code</code> text</p>"#;
|
let html = r#"<p>and <code>code</code> text</p>"#;
|
||||||
let node = super::parse(html).unwrap();
|
let mut cx = NodeContext::default();
|
||||||
|
let node = super::parse(html, &mut cx).unwrap();
|
||||||
assert_eq!(node.to_markdown(), "and `code` text");
|
assert_eq!(node.to_markdown(), "and `code` text");
|
||||||
|
|
||||||
let html = r#"
|
let html = r#"
|
||||||
|
|
@ -694,7 +707,7 @@ mod tests {
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
"#;
|
"#;
|
||||||
let node = super::parse(html).unwrap();
|
let node = super::parse(html, &mut cx).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
node.to_markdown(),
|
node.to_markdown(),
|
||||||
indoc::indoc! {r#"
|
indoc::indoc! {r#"
|
||||||
|
|
@ -720,7 +733,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_image() {
|
fn test_image() {
|
||||||
let html = r#"<img src="https://example.com/image.png" alt="Example" width="100" height="200" title="Example Image" />"#;
|
let html = r#"<img src="https://example.com/image.png" alt="Example" width="100" height="200" title="Example Image" />"#;
|
||||||
let node = super::parse(html).unwrap();
|
let mut cx = NodeContext::default();
|
||||||
|
let node = super::parse(html, &mut cx).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
node,
|
node,
|
||||||
Node::Paragraph(Paragraph {
|
Node::Paragraph(Paragraph {
|
||||||
|
|
@ -738,7 +752,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let html = r#"<img src="https://example.com/image.png" alt="Example" style="width: 80%" title="Example Image" />"#;
|
let html = r#"<img src="https://example.com/image.png" alt="Example" style="width: 80%" title="Example Image" />"#;
|
||||||
let node = super::parse(html).unwrap();
|
let node = super::parse(html, &mut cx).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
node,
|
node,
|
||||||
Node::Paragraph(Paragraph {
|
Node::Paragraph(Paragraph {
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ use markdown::{
|
||||||
use crate::{
|
use crate::{
|
||||||
text::{
|
text::{
|
||||||
node::{
|
node::{
|
||||||
self, CodeBlock, ImageNode, InlineNode, LinkMark, Paragraph, Span, Table, TableRow,
|
self, CodeBlock, ImageNode, InlineNode, LinkMark, NodeContext, Paragraph, Span, Table,
|
||||||
TextMark,
|
TableRow, TextMark,
|
||||||
},
|
},
|
||||||
TextViewState, TextViewStyle,
|
TextViewState, TextViewStyle,
|
||||||
},
|
},
|
||||||
|
|
@ -60,8 +60,10 @@ impl RenderOnce for MarkdownElement {
|
||||||
});
|
});
|
||||||
|
|
||||||
let root = self.state.read(cx).root();
|
let root = self.state.read(cx).root();
|
||||||
|
let node_cx = self.state.read(cx).node_cx.clone();
|
||||||
|
|
||||||
div().map(|this| match root {
|
div().map(|this| match root {
|
||||||
Ok(node) => this.child(node.render(None, true, true, &self.style, window, cx)),
|
Ok(node) => this.child(node.render(None, true, true, &node_cx, window, cx)),
|
||||||
Err(err) => this.child(
|
Err(err) => this.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
|
@ -76,19 +78,20 @@ impl RenderOnce for MarkdownElement {
|
||||||
pub(crate) fn parse(
|
pub(crate) fn parse(
|
||||||
raw: &str,
|
raw: &str,
|
||||||
style: &TextViewStyle,
|
style: &TextViewStyle,
|
||||||
cx: &mut App,
|
cx: &mut NodeContext,
|
||||||
|
app: &App,
|
||||||
) -> Result<node::Node, SharedString> {
|
) -> Result<node::Node, SharedString> {
|
||||||
markdown::to_mdast(&raw, &ParseOptions::gfm())
|
markdown::to_mdast(&raw, &ParseOptions::gfm())
|
||||||
.map(|n| ast_to_node(n, style, cx))
|
.map(|n| ast_to_node(n, style, cx, app))
|
||||||
.map_err(|e| e.to_string().into())
|
.map_err(|e| e.to_string().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_table_row(table: &mut Table, node: &mdast::TableRow) {
|
fn parse_table_row(table: &mut Table, node: &mdast::TableRow, cx: &mut NodeContext) {
|
||||||
let mut row = TableRow::default();
|
let mut row = TableRow::default();
|
||||||
node.children.iter().for_each(|c| {
|
node.children.iter().for_each(|c| {
|
||||||
match c {
|
match c {
|
||||||
Node::TableCell(cell) => {
|
Node::TableCell(cell) => {
|
||||||
parse_table_cell(&mut row, cell);
|
parse_table_cell(&mut row, cell, cx);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
@ -96,10 +99,10 @@ fn parse_table_row(table: &mut Table, node: &mdast::TableRow) {
|
||||||
table.children.push(row);
|
table.children.push(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_table_cell(row: &mut node::TableRow, node: &mdast::TableCell) {
|
fn parse_table_cell(row: &mut node::TableRow, node: &mdast::TableCell, cx: &mut NodeContext) {
|
||||||
let mut paragraph = Paragraph::default();
|
let mut paragraph = Paragraph::default();
|
||||||
node.children.iter().for_each(|c| {
|
node.children.iter().for_each(|c| {
|
||||||
parse_paragraph(&mut paragraph, c);
|
parse_paragraph(&mut paragraph, c, cx);
|
||||||
});
|
});
|
||||||
let table_cell = node::TableCell {
|
let table_cell = node::TableCell {
|
||||||
children: paragraph,
|
children: paragraph,
|
||||||
|
|
@ -108,7 +111,7 @@ fn parse_table_cell(row: &mut node::TableRow, node: &mdast::TableCell) {
|
||||||
row.children.push(table_cell);
|
row.children.push(table_cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_paragraph(paragraph: &mut Paragraph, node: &mdast::Node) -> String {
|
fn parse_paragraph(paragraph: &mut Paragraph, node: &mdast::Node, cx: &mut NodeContext) -> String {
|
||||||
let span = node.position().map(|pos| Span {
|
let span = node.position().map(|pos| Span {
|
||||||
start: pos.start.offset,
|
start: pos.start.offset,
|
||||||
end: pos.end.offset,
|
end: pos.end.offset,
|
||||||
|
|
@ -122,7 +125,7 @@ fn parse_paragraph(paragraph: &mut Paragraph, node: &mdast::Node) -> String {
|
||||||
match node {
|
match node {
|
||||||
Node::Paragraph(val) => {
|
Node::Paragraph(val) => {
|
||||||
val.children.iter().for_each(|c| {
|
val.children.iter().for_each(|c| {
|
||||||
text.push_str(&parse_paragraph(paragraph, c));
|
text.push_str(&parse_paragraph(paragraph, c, cx));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Node::Text(val) => {
|
Node::Text(val) => {
|
||||||
|
|
@ -132,7 +135,7 @@ fn parse_paragraph(paragraph: &mut Paragraph, node: &mdast::Node) -> String {
|
||||||
Node::Emphasis(val) => {
|
Node::Emphasis(val) => {
|
||||||
let mut child_paragraph = Paragraph::default();
|
let mut child_paragraph = Paragraph::default();
|
||||||
for child in val.children.iter() {
|
for child in val.children.iter() {
|
||||||
text.push_str(&parse_paragraph(&mut child_paragraph, &child));
|
text.push_str(&parse_paragraph(&mut child_paragraph, &child, cx));
|
||||||
}
|
}
|
||||||
paragraph.push(
|
paragraph.push(
|
||||||
InlineNode::new(&text).marks(vec![(0..text.len(), TextMark::default().italic())]),
|
InlineNode::new(&text).marks(vec![(0..text.len(), TextMark::default().italic())]),
|
||||||
|
|
@ -141,7 +144,7 @@ fn parse_paragraph(paragraph: &mut Paragraph, node: &mdast::Node) -> String {
|
||||||
Node::Strong(val) => {
|
Node::Strong(val) => {
|
||||||
let mut child_paragraph = Paragraph::default();
|
let mut child_paragraph = Paragraph::default();
|
||||||
for child in val.children.iter() {
|
for child in val.children.iter() {
|
||||||
text.push_str(&parse_paragraph(&mut child_paragraph, &child));
|
text.push_str(&parse_paragraph(&mut child_paragraph, &child, cx));
|
||||||
}
|
}
|
||||||
paragraph.push(
|
paragraph.push(
|
||||||
InlineNode::new(&text).marks(vec![(0..text.len(), TextMark::default().bold())]),
|
InlineNode::new(&text).marks(vec![(0..text.len(), TextMark::default().bold())]),
|
||||||
|
|
@ -150,7 +153,7 @@ fn parse_paragraph(paragraph: &mut Paragraph, node: &mdast::Node) -> String {
|
||||||
Node::Delete(val) => {
|
Node::Delete(val) => {
|
||||||
let mut child_paragraph = Paragraph::default();
|
let mut child_paragraph = Paragraph::default();
|
||||||
for child in val.children.iter() {
|
for child in val.children.iter() {
|
||||||
text.push_str(&parse_paragraph(&mut child_paragraph, &child));
|
text.push_str(&parse_paragraph(&mut child_paragraph, &child, cx));
|
||||||
}
|
}
|
||||||
paragraph.push(
|
paragraph.push(
|
||||||
InlineNode::new(&text)
|
InlineNode::new(&text)
|
||||||
|
|
@ -167,11 +170,12 @@ fn parse_paragraph(paragraph: &mut Paragraph, node: &mdast::Node) -> String {
|
||||||
let link_mark = Some(LinkMark {
|
let link_mark = Some(LinkMark {
|
||||||
url: val.url.clone().into(),
|
url: val.url.clone().into(),
|
||||||
title: val.title.clone().map(|s| s.into()),
|
title: val.title.clone().map(|s| s.into()),
|
||||||
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut child_paragraph = Paragraph::default();
|
let mut child_paragraph = Paragraph::default();
|
||||||
for child in val.children.iter() {
|
for child in val.children.iter() {
|
||||||
text.push_str(&parse_paragraph(&mut child_paragraph, &child));
|
text.push_str(&parse_paragraph(&mut child_paragraph, &child, cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: GPUI InteractiveText does not support inline images yet.
|
// FIXME: GPUI InteractiveText does not support inline images yet.
|
||||||
|
|
@ -211,7 +215,7 @@ fn parse_paragraph(paragraph: &mut Paragraph, node: &mdast::Node) -> String {
|
||||||
paragraph
|
paragraph
|
||||||
.push(InlineNode::new(&text).marks(vec![(0..text.len(), TextMark::default())]));
|
.push(InlineNode::new(&text).marks(vec![(0..text.len(), TextMark::default())]));
|
||||||
}
|
}
|
||||||
Node::Html(val) => match super::html::parse(&val.value) {
|
Node::Html(val) => match super::html::parse(&val.value, cx) {
|
||||||
Ok(el) => {
|
Ok(el) => {
|
||||||
if el.is_break() {
|
if el.is_break() {
|
||||||
text = "\n".to_owned();
|
text = "\n".to_owned();
|
||||||
|
|
@ -230,6 +234,37 @@ fn parse_paragraph(paragraph: &mut Paragraph, node: &mdast::Node) -> String {
|
||||||
text.push_str(&val.value);
|
text.push_str(&val.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Node::FootnoteReference(foot) => {
|
||||||
|
let prefix = format!("[{}]", foot.identifier);
|
||||||
|
paragraph.push(InlineNode::new(&prefix).marks(vec![(
|
||||||
|
0..prefix.len(),
|
||||||
|
TextMark {
|
||||||
|
italic: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)]));
|
||||||
|
}
|
||||||
|
Node::LinkReference(link) => {
|
||||||
|
let mut child_paragraph = Paragraph::default();
|
||||||
|
let mut child_text = String::new();
|
||||||
|
for child in link.children.iter() {
|
||||||
|
child_text.push_str(&parse_paragraph(&mut child_paragraph, child, cx));
|
||||||
|
}
|
||||||
|
|
||||||
|
let link_mark = LinkMark {
|
||||||
|
url: "".into(),
|
||||||
|
title: link.label.clone().map(Into::into),
|
||||||
|
identifier: Some(link.identifier.clone().into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
paragraph.push(InlineNode::new(&child_text).marks(vec![(
|
||||||
|
0..child_text.len(),
|
||||||
|
TextMark {
|
||||||
|
link: Some(link_mark),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)]));
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
tracing::warn!("unsupported inline node: {:#?}", node);
|
tracing::warn!("unsupported inline node: {:#?}", node);
|
||||||
|
|
@ -240,20 +275,25 @@ fn parse_paragraph(paragraph: &mut Paragraph, node: &mdast::Node) -> String {
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ast_to_node(value: mdast::Node, style: &TextViewStyle, cx: &mut App) -> node::Node {
|
fn ast_to_node(
|
||||||
|
value: mdast::Node,
|
||||||
|
style: &TextViewStyle,
|
||||||
|
cx: &mut NodeContext,
|
||||||
|
app: &App,
|
||||||
|
) -> node::Node {
|
||||||
match value {
|
match value {
|
||||||
Node::Root(val) => {
|
Node::Root(val) => {
|
||||||
let children = val
|
let children = val
|
||||||
.children
|
.children
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|c| ast_to_node(c, style, cx))
|
.map(|c| ast_to_node(c, style, cx, app))
|
||||||
.collect();
|
.collect();
|
||||||
node::Node::Root { children }
|
node::Node::Root { children }
|
||||||
}
|
}
|
||||||
Node::Paragraph(val) => {
|
Node::Paragraph(val) => {
|
||||||
let mut paragraph = Paragraph::default();
|
let mut paragraph = Paragraph::default();
|
||||||
val.children.iter().for_each(|c| {
|
val.children.iter().for_each(|c| {
|
||||||
parse_paragraph(&mut paragraph, c);
|
parse_paragraph(&mut paragraph, c, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
node::Node::Paragraph(paragraph)
|
node::Node::Paragraph(paragraph)
|
||||||
|
|
@ -262,7 +302,7 @@ fn ast_to_node(value: mdast::Node, style: &TextViewStyle, cx: &mut App) -> node:
|
||||||
let children = val
|
let children = val
|
||||||
.children
|
.children
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|c| ast_to_node(c, style, cx))
|
.map(|c| ast_to_node(c, style, cx, app))
|
||||||
.collect();
|
.collect();
|
||||||
node::Node::Blockquote { children }
|
node::Node::Blockquote { children }
|
||||||
}
|
}
|
||||||
|
|
@ -270,7 +310,7 @@ fn ast_to_node(value: mdast::Node, style: &TextViewStyle, cx: &mut App) -> node:
|
||||||
let children = list
|
let children = list
|
||||||
.children
|
.children
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|c| ast_to_node(c, style, cx))
|
.map(|c| ast_to_node(c, style, cx, app))
|
||||||
.collect();
|
.collect();
|
||||||
node::Node::List {
|
node::Node::List {
|
||||||
ordered: list.ordered,
|
ordered: list.ordered,
|
||||||
|
|
@ -281,7 +321,7 @@ fn ast_to_node(value: mdast::Node, style: &TextViewStyle, cx: &mut App) -> node:
|
||||||
let children = val
|
let children = val
|
||||||
.children
|
.children
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|c| ast_to_node(c, style, cx))
|
.map(|c| ast_to_node(c, style, cx, app))
|
||||||
.collect();
|
.collect();
|
||||||
node::Node::ListItem {
|
node::Node::ListItem {
|
||||||
children,
|
children,
|
||||||
|
|
@ -294,12 +334,12 @@ fn ast_to_node(value: mdast::Node, style: &TextViewStyle, cx: &mut App) -> node:
|
||||||
raw.value.into(),
|
raw.value.into(),
|
||||||
raw.lang.map(|s| s.into()),
|
raw.lang.map(|s| s.into()),
|
||||||
style,
|
style,
|
||||||
cx,
|
app,
|
||||||
)),
|
)),
|
||||||
Node::Heading(val) => {
|
Node::Heading(val) => {
|
||||||
let mut paragraph = Paragraph::default();
|
let mut paragraph = Paragraph::default();
|
||||||
val.children.iter().for_each(|c| {
|
val.children.iter().for_each(|c| {
|
||||||
parse_paragraph(&mut paragraph, c);
|
parse_paragraph(&mut paragraph, c, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
node::Node::Heading {
|
node::Node::Heading {
|
||||||
|
|
@ -307,8 +347,10 @@ fn ast_to_node(value: mdast::Node, style: &TextViewStyle, cx: &mut App) -> node:
|
||||||
children: paragraph,
|
children: paragraph,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Node::Math(val) => node::Node::CodeBlock(CodeBlock::new(val.value.into(), None, style, cx)),
|
Node::Math(val) => {
|
||||||
Node::Html(val) => match super::html::parse(&val.value) {
|
node::Node::CodeBlock(CodeBlock::new(val.value.into(), None, style, app))
|
||||||
|
}
|
||||||
|
Node::Html(val) => match super::html::parse(&val.value, cx) {
|
||||||
Ok(el) => el,
|
Ok(el) => el,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
|
|
@ -322,31 +364,31 @@ fn ast_to_node(value: mdast::Node, style: &TextViewStyle, cx: &mut App) -> node:
|
||||||
val.value.into(),
|
val.value.into(),
|
||||||
Some("mdx".into()),
|
Some("mdx".into()),
|
||||||
style,
|
style,
|
||||||
cx,
|
app,
|
||||||
)),
|
)),
|
||||||
Node::Yaml(val) => node::Node::CodeBlock(CodeBlock::new(
|
Node::Yaml(val) => node::Node::CodeBlock(CodeBlock::new(
|
||||||
val.value.into(),
|
val.value.into(),
|
||||||
Some("yml".into()),
|
Some("yml".into()),
|
||||||
style,
|
style,
|
||||||
cx,
|
app,
|
||||||
)),
|
)),
|
||||||
Node::Toml(val) => node::Node::CodeBlock(CodeBlock::new(
|
Node::Toml(val) => node::Node::CodeBlock(CodeBlock::new(
|
||||||
val.value.into(),
|
val.value.into(),
|
||||||
Some("toml".into()),
|
Some("toml".into()),
|
||||||
style,
|
style,
|
||||||
cx,
|
app,
|
||||||
)),
|
)),
|
||||||
Node::MdxJsxTextElement(val) => {
|
Node::MdxJsxTextElement(val) => {
|
||||||
let mut paragraph = Paragraph::default();
|
let mut paragraph = Paragraph::default();
|
||||||
val.children.iter().for_each(|c| {
|
val.children.iter().for_each(|c| {
|
||||||
parse_paragraph(&mut paragraph, c);
|
parse_paragraph(&mut paragraph, c, cx);
|
||||||
});
|
});
|
||||||
node::Node::Paragraph(paragraph)
|
node::Node::Paragraph(paragraph)
|
||||||
}
|
}
|
||||||
Node::MdxJsxFlowElement(val) => {
|
Node::MdxJsxFlowElement(val) => {
|
||||||
let mut paragraph = Paragraph::default();
|
let mut paragraph = Paragraph::default();
|
||||||
val.children.iter().for_each(|c| {
|
val.children.iter().for_each(|c| {
|
||||||
parse_paragraph(&mut paragraph, c);
|
parse_paragraph(&mut paragraph, c, cx);
|
||||||
});
|
});
|
||||||
node::Node::Paragraph(paragraph)
|
node::Node::Paragraph(paragraph)
|
||||||
}
|
}
|
||||||
|
|
@ -361,12 +403,44 @@ fn ast_to_node(value: mdast::Node, style: &TextViewStyle, cx: &mut App) -> node:
|
||||||
.collect();
|
.collect();
|
||||||
val.children.iter().for_each(|c| {
|
val.children.iter().for_each(|c| {
|
||||||
if let Node::TableRow(row) = c {
|
if let Node::TableRow(row) = c {
|
||||||
parse_table_row(&mut table, row);
|
parse_table_row(&mut table, row, cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
node::Node::Table(table)
|
node::Node::Table(table)
|
||||||
}
|
}
|
||||||
|
Node::FootnoteDefinition(def) => {
|
||||||
|
let mut paragraph = Paragraph::default();
|
||||||
|
let prefix = format!("[{}]: ", def.identifier);
|
||||||
|
paragraph.push(InlineNode::new(&prefix).marks(vec![(
|
||||||
|
0..prefix.len(),
|
||||||
|
TextMark {
|
||||||
|
italic: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)]));
|
||||||
|
|
||||||
|
def.children.iter().for_each(|c| {
|
||||||
|
parse_paragraph(&mut paragraph, c, cx);
|
||||||
|
});
|
||||||
|
node::Node::Paragraph(paragraph)
|
||||||
|
}
|
||||||
|
Node::Definition(def) => {
|
||||||
|
cx.add_ref(
|
||||||
|
def.identifier.clone().into(),
|
||||||
|
LinkMark {
|
||||||
|
url: def.url.clone().into(),
|
||||||
|
identifier: Some(def.identifier.clone().into()),
|
||||||
|
title: def.title.clone().map(Into::into),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
node::Node::Definition {
|
||||||
|
identifier: def.identifier.clone().into(),
|
||||||
|
url: def.url.clone().into(),
|
||||||
|
title: def.title.clone().map(|s| s.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
tracing::warn!("unsupported node: {:#?}", value);
|
tracing::warn!("unsupported node: {:#?}", value);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::ops::Range;
|
use std::{collections::HashMap, ops::Range};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, img, prelude::FluentBuilder as _, px, relative, rems, AnyElement, App, DefiniteLength,
|
div, img, prelude::FluentBuilder as _, px, relative, rems, AnyElement, App, DefiniteLength,
|
||||||
|
|
@ -22,6 +22,8 @@ use super::{utils::list_item_prefix, TextViewStyle};
|
||||||
#[derive(Debug, Default, Clone, PartialEq)]
|
#[derive(Debug, Default, Clone, PartialEq)]
|
||||||
pub struct LinkMark {
|
pub struct LinkMark {
|
||||||
pub url: SharedString,
|
pub url: SharedString,
|
||||||
|
/// Optional identifier for footnotes.
|
||||||
|
pub identifier: Option<SharedString>,
|
||||||
pub title: Option<SharedString>,
|
pub title: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,6 +142,10 @@ impl InlineNode {
|
||||||
pub(crate) struct Paragraph {
|
pub(crate) struct Paragraph {
|
||||||
pub(super) span: Option<Span>,
|
pub(super) span: Option<Span>,
|
||||||
pub(super) children: Vec<InlineNode>,
|
pub(super) children: Vec<InlineNode>,
|
||||||
|
/// The link references in this paragraph, used for reference links.
|
||||||
|
///
|
||||||
|
/// The key is the identifier, the value is the url.
|
||||||
|
pub(super) link_refs: HashMap<SharedString, SharedString>,
|
||||||
|
|
||||||
pub(crate) state: InlineState,
|
pub(crate) state: InlineState,
|
||||||
}
|
}
|
||||||
|
|
@ -149,6 +155,7 @@ impl Paragraph {
|
||||||
Self {
|
Self {
|
||||||
span: None,
|
span: None,
|
||||||
children: vec![InlineNode::new(&text)],
|
children: vec![InlineNode::new(&text)],
|
||||||
|
link_refs: HashMap::new(),
|
||||||
state: InlineState::default(),
|
state: InlineState::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -278,7 +285,7 @@ impl CodeBlock {
|
||||||
code: SharedString,
|
code: SharedString,
|
||||||
lang: Option<SharedString>,
|
lang: Option<SharedString>,
|
||||||
_: &TextViewStyle,
|
_: &TextViewStyle,
|
||||||
cx: &mut App,
|
cx: &App,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let theme = cx.theme().highlight_theme.clone();
|
let theme = cx.theme().highlight_theme.clone();
|
||||||
let mut styles = vec![];
|
let mut styles = vec![];
|
||||||
|
|
@ -331,6 +338,19 @@ impl CodeBlock {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A context for rendering nodes, contains link references.
|
||||||
|
#[derive(Default, Clone, PartialEq)]
|
||||||
|
pub(crate) struct NodeContext {
|
||||||
|
pub(crate) link_refs: HashMap<SharedString, LinkMark>,
|
||||||
|
pub(crate) style: TextViewStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeContext {
|
||||||
|
pub(super) fn add_ref(&mut self, identifier: SharedString, link: LinkMark) {
|
||||||
|
self.link_refs.insert(identifier, link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The AST Node of the rich text.
|
/// The AST Node of the rich text.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub(crate) enum Node {
|
pub(crate) enum Node {
|
||||||
|
|
@ -362,6 +382,12 @@ pub(crate) enum Node {
|
||||||
html: bool,
|
html: bool,
|
||||||
},
|
},
|
||||||
Divider,
|
Divider,
|
||||||
|
/// Use for to_markdown get raw definition
|
||||||
|
Definition {
|
||||||
|
identifier: SharedString,
|
||||||
|
url: SharedString,
|
||||||
|
title: Option<SharedString>,
|
||||||
|
},
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -464,7 +490,7 @@ impl Node {
|
||||||
text.push('\n');
|
text.push('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Node::Break { .. } | Node::Divider | Node::Unknown => {}
|
Node::Definition { .. } | Node::Break { .. } | Node::Divider | Node::Unknown => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
text
|
text
|
||||||
|
|
@ -472,7 +498,12 @@ impl Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Paragraph {
|
impl Paragraph {
|
||||||
fn render(&self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(
|
||||||
|
&self,
|
||||||
|
node_cx: &NodeContext,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> impl IntoElement {
|
||||||
let span = self.span;
|
let span = self.span;
|
||||||
let children = &self.children;
|
let children = &self.children;
|
||||||
|
|
||||||
|
|
@ -547,14 +578,21 @@ impl Paragraph {
|
||||||
highlight.background_color = Some(cx.theme().accent);
|
highlight.background_color = Some(cx.theme().accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(link_mark) = &style.link {
|
if let Some(mut link_mark) = style.link.clone() {
|
||||||
highlight.color = Some(cx.theme().link);
|
highlight.color = Some(cx.theme().link);
|
||||||
highlight.underline = Some(gpui::UnderlineStyle {
|
highlight.underline = Some(gpui::UnderlineStyle {
|
||||||
thickness: gpui::px(1.),
|
thickness: gpui::px(1.),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
links.push((inner_range.clone(), link_mark.clone()));
|
// convert link references, replace link
|
||||||
|
if let Some(identifier) = link_mark.identifier.as_ref() {
|
||||||
|
if let Some(mark) = node_cx.link_refs.get(identifier) {
|
||||||
|
link_mark = mark.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
links.push((inner_range.clone(), link_mark));
|
||||||
}
|
}
|
||||||
|
|
||||||
node_highlights.push((inner_range, highlight));
|
node_highlights.push((inner_range, highlight));
|
||||||
|
|
@ -589,7 +627,7 @@ impl Node {
|
||||||
item: &Node,
|
item: &Node,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
state: ListState,
|
state: ListState,
|
||||||
text_view_style: &TextViewStyle,
|
node_cx: &NodeContext,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
|
|
@ -617,7 +655,7 @@ impl Node {
|
||||||
}),
|
}),
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
text_view_style,
|
node_cx,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
@ -681,7 +719,7 @@ impl Node {
|
||||||
}),
|
}),
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
text_view_style,
|
node_cx,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)))
|
)))
|
||||||
|
|
@ -696,7 +734,12 @@ impl Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_table(item: &Node, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render_table(
|
||||||
|
item: &Node,
|
||||||
|
node_cx: &NodeContext,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> impl IntoElement {
|
||||||
const DEFAULT_LENGTH: usize = 5;
|
const DEFAULT_LENGTH: usize = 5;
|
||||||
const MAX_LENGTH: usize = 150;
|
const MAX_LENGTH: usize = 150;
|
||||||
let col_lens = match item {
|
let col_lens = match item {
|
||||||
|
|
@ -767,7 +810,7 @@ impl Node {
|
||||||
.border_color(cx.theme().border)
|
.border_color(cx.theme().border)
|
||||||
})
|
})
|
||||||
.truncate()
|
.truncate()
|
||||||
.child(cell.children.render(window, cx)),
|
.child(cell.children.render(node_cx, window, cx)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
cells
|
cells
|
||||||
|
|
@ -786,7 +829,7 @@ impl Node {
|
||||||
list_state: Option<ListState>,
|
list_state: Option<ListState>,
|
||||||
is_root: bool,
|
is_root: bool,
|
||||||
is_last_child: bool,
|
is_last_child: bool,
|
||||||
style: &TextViewStyle,
|
node_cx: &NodeContext,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
|
|
@ -794,7 +837,7 @@ impl Node {
|
||||||
let mb = if in_list || is_last_child {
|
let mb = if in_list || is_last_child {
|
||||||
rems(0.)
|
rems(0.)
|
||||||
} else {
|
} else {
|
||||||
style.paragraph_gap
|
node_cx.style.paragraph_gap
|
||||||
};
|
};
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -804,14 +847,14 @@ impl Node {
|
||||||
let children_len = children.len();
|
let children_len = children.len();
|
||||||
children.into_iter().enumerate().map(move |(index, c)| {
|
children.into_iter().enumerate().map(move |(index, c)| {
|
||||||
let is_last_child = is_root && index == children_len - 1;
|
let is_last_child = is_root && index == children_len - 1;
|
||||||
c.render(None, false, is_last_child, style, window, cx)
|
c.render(None, false, is_last_child, node_cx, window, cx)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
Node::Paragraph(paragraph) => div()
|
Node::Paragraph(paragraph) => div()
|
||||||
.id("p")
|
.id("p")
|
||||||
.mb(mb)
|
.mb(mb)
|
||||||
.child(paragraph.render(window, cx))
|
.child(paragraph.render(node_cx, window, cx))
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
Node::Heading { level, children } => {
|
Node::Heading { level, children } => {
|
||||||
let (text_size, font_weight) = match level {
|
let (text_size, font_weight) = match level {
|
||||||
|
|
@ -824,7 +867,7 @@ impl Node {
|
||||||
_ => (rems(1.), FontWeight::NORMAL),
|
_ => (rems(1.), FontWeight::NORMAL),
|
||||||
};
|
};
|
||||||
|
|
||||||
let text_size = text_size.to_pixels(style.heading_base_font_size);
|
let text_size = text_size.to_pixels(node_cx.style.heading_base_font_size);
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(("h", *level as usize))
|
.id(("h", *level as usize))
|
||||||
|
|
@ -832,7 +875,7 @@ impl Node {
|
||||||
.whitespace_normal()
|
.whitespace_normal()
|
||||||
.text_size(text_size)
|
.text_size(text_size)
|
||||||
.font_weight(font_weight)
|
.font_weight(font_weight)
|
||||||
.child(children.render(window, cx))
|
.child(children.render(node_cx, window, cx))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
Node::Blockquote { children } => div()
|
Node::Blockquote { children } => div()
|
||||||
|
|
@ -847,7 +890,7 @@ impl Node {
|
||||||
let children_len = children.len();
|
let children_len = children.len();
|
||||||
children.into_iter().enumerate().map(move |(index, c)| {
|
children.into_iter().enumerate().map(move |(index, c)| {
|
||||||
let is_last_child = is_root && index == children_len - 1;
|
let is_last_child = is_root && index == children_len - 1;
|
||||||
c.render(None, false, is_last_child, style, window, cx)
|
c.render(None, false, is_last_child, node_cx, window, cx)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
|
|
@ -869,7 +912,7 @@ impl Node {
|
||||||
todo: list_state.todo,
|
todo: list_state.todo,
|
||||||
depth: list_state.depth,
|
depth: list_state.depth,
|
||||||
},
|
},
|
||||||
style,
|
node_cx,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
));
|
));
|
||||||
|
|
@ -882,7 +925,7 @@ impl Node {
|
||||||
})
|
})
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
Node::CodeBlock(code_block) => code_block.render(mb, window, cx),
|
Node::CodeBlock(code_block) => code_block.render(mb, window, cx),
|
||||||
Node::Table { .. } => Self::render_table(&self, window, cx).into_any_element(),
|
Node::Table { .. } => Self::render_table(&self, node_cx, window, cx).into_any_element(),
|
||||||
Node::Divider => div()
|
Node::Divider => div()
|
||||||
.id("divider")
|
.id("divider")
|
||||||
.bg(cx.theme().border)
|
.bg(cx.theme().border)
|
||||||
|
|
@ -890,6 +933,7 @@ impl Node {
|
||||||
.mb(mb)
|
.mb(mb)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
Node::Break { .. } => div().id("break").into_any_element(),
|
Node::Break { .. } => div().id("break").into_any_element(),
|
||||||
|
Node::Unknown | Node::Definition { .. } => div().into_any_element(),
|
||||||
_ => {
|
_ => {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
tracing::warn!("unknown implementation: {:?}", self);
|
tracing::warn!("unknown implementation: {:?}", self);
|
||||||
|
|
@ -1065,6 +1109,17 @@ impl Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Node::Divider => "---".to_string(),
|
Node::Divider => "---".to_string(),
|
||||||
|
Node::Definition {
|
||||||
|
identifier,
|
||||||
|
url,
|
||||||
|
title,
|
||||||
|
} => {
|
||||||
|
if let Some(title) = title {
|
||||||
|
format!("[{}]: {} \"{}\"", identifier, url, title)
|
||||||
|
} else {
|
||||||
|
format!("[{}]: {}", identifier, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
Node::Unknown => "".to_string(),
|
Node::Unknown => "".to_string(),
|
||||||
}
|
}
|
||||||
.trim()
|
.trim()
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
global_state::GlobalState,
|
global_state::GlobalState,
|
||||||
highlighter::HighlightTheme,
|
highlighter::HighlightTheme,
|
||||||
input::{self},
|
input::{self},
|
||||||
text::node::{self},
|
text::node::{self, NodeContext},
|
||||||
};
|
};
|
||||||
|
|
||||||
const CONTEXT: &'static str = "TextView";
|
const CONTEXT: &'static str = "TextView";
|
||||||
|
|
@ -68,10 +68,10 @@ pub struct TextView {
|
||||||
#[derive(Default, Clone, PartialEq)]
|
#[derive(Default, Clone, PartialEq)]
|
||||||
pub(crate) struct TextViewState {
|
pub(crate) struct TextViewState {
|
||||||
root: Option<Result<Rc<node::Node>, SharedString>>,
|
root: Option<Result<Rc<node::Node>, SharedString>>,
|
||||||
|
pub(crate) node_cx: Rc<node::NodeContext>,
|
||||||
|
|
||||||
raw: SharedString,
|
raw: SharedString,
|
||||||
focus_handle: Option<FocusHandle>,
|
focus_handle: Option<FocusHandle>,
|
||||||
style: TextViewStyle,
|
|
||||||
|
|
||||||
/// The bounds of the text view
|
/// The bounds of the text view
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
|
|
@ -92,7 +92,7 @@ impl TextViewState {
|
||||||
raw: SharedString::default(),
|
raw: SharedString::default(),
|
||||||
focus_handle: Some(focus_handle),
|
focus_handle: Some(focus_handle),
|
||||||
root: None,
|
root: None,
|
||||||
style: TextViewStyle::default(),
|
node_cx: Rc::new(NodeContext::default()),
|
||||||
_last_parsed: None,
|
_last_parsed: None,
|
||||||
bounds: Bounds::default(),
|
bounds: Bounds::default(),
|
||||||
selection_positions: (None, None),
|
selection_positions: (None, None),
|
||||||
|
|
@ -116,7 +116,7 @@ impl TextViewState {
|
||||||
style: &TextViewStyle,
|
style: &TextViewStyle,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
let is_changed = self.raw != new_text || self.style != *style;
|
let is_changed = self.raw != new_text || self.node_cx.style != *style;
|
||||||
|
|
||||||
if self.root.is_some() && !is_changed {
|
if self.root.is_some() && !is_changed {
|
||||||
return;
|
return;
|
||||||
|
|
@ -128,20 +128,22 @@ impl TextViewState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut node_cx = NodeContext::default();
|
||||||
|
node_cx.style = style.clone();
|
||||||
self.raw = new_text;
|
self.raw = new_text;
|
||||||
// NOTE: About 100ms
|
// NOTE: About 100ms
|
||||||
// let measure = crate::Measure::new("parse_markdown");
|
// let measure = crate::Measure::new("parse_markdown");
|
||||||
self.root = Some(
|
self.root = Some(
|
||||||
if is_html {
|
if is_html {
|
||||||
super::format::html::parse(&self.raw)
|
super::format::html::parse(&self.raw, &mut node_cx)
|
||||||
} else {
|
} else {
|
||||||
super::format::markdown::parse(&self.raw, &style, cx)
|
super::format::markdown::parse(&self.raw, &style, &mut node_cx, cx)
|
||||||
}
|
}
|
||||||
.map(Rc::new),
|
.map(Rc::new),
|
||||||
);
|
);
|
||||||
|
self.node_cx = Rc::new(node_cx);
|
||||||
// measure.end();
|
// measure.end();
|
||||||
self._last_parsed = Some(Instant::now());
|
self._last_parsed = Some(Instant::now());
|
||||||
self.style = style.clone();
|
|
||||||
self.clear_selection();
|
self.clear_selection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue