206 lines
6.3 KiB
Rust
206 lines
6.3 KiB
Rust
|
|
//! Inline AST nodes.
|
||
|
|
//!
|
||
|
|
//! The link-related variants (`WikiLink`, `ExternalLink`,
|
||
|
|
//! `Transclusion`, `RawUrl`) wrap structs defined in `super::link`.
|
||
|
|
|
||
|
|
use super::link::{ExternalLinkNode, RawUrlNode, TransclusionNode, WikiLinkNode};
|
||
|
|
use super::span::Span;
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||
|
|
pub enum Keyword {
|
||
|
|
Todo,
|
||
|
|
Done,
|
||
|
|
Started,
|
||
|
|
Fixme,
|
||
|
|
Fixed,
|
||
|
|
Xxx,
|
||
|
|
Stopped,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Keyword {
|
||
|
|
/// The literal source text of the keyword (e.g. `"TODO"`). Used both for
|
||
|
|
/// the rendered span and for anchor/id derivation via [`inline_text`].
|
||
|
|
pub fn label(self) -> &'static str {
|
||
|
|
match self {
|
||
|
|
Keyword::Todo => "TODO",
|
||
|
|
Keyword::Done => "DONE",
|
||
|
|
Keyword::Started => "STARTED",
|
||
|
|
Keyword::Fixme => "FIXME",
|
||
|
|
Keyword::Fixed => "FIXED",
|
||
|
|
Keyword::Xxx => "XXX",
|
||
|
|
Keyword::Stopped => "STOPPED",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub enum InlineNode {
|
||
|
|
Text(TextNode),
|
||
|
|
Bold(BoldNode),
|
||
|
|
Italic(ItalicNode),
|
||
|
|
BoldItalic(BoldItalicNode),
|
||
|
|
Strikethrough(StrikethroughNode),
|
||
|
|
Code(CodeNode),
|
||
|
|
Superscript(SuperscriptNode),
|
||
|
|
Subscript(SubscriptNode),
|
||
|
|
MathInline(MathInlineNode),
|
||
|
|
Keyword(KeywordNode),
|
||
|
|
Color(ColorNode),
|
||
|
|
WikiLink(WikiLinkNode),
|
||
|
|
ExternalLink(ExternalLinkNode),
|
||
|
|
Transclusion(TransclusionNode),
|
||
|
|
RawUrl(RawUrlNode),
|
||
|
|
/// A soft line break joining two content lines within a paragraph or
|
||
|
|
/// list item. Consumers treat it as whitespace; the HTML renderer emits
|
||
|
|
/// a space (vimwiki `*_ignore_newline = 1`, the default) or `<br />`
|
||
|
|
/// (`= 0`).
|
||
|
|
SoftBreak(SoftBreakNode),
|
||
|
|
}
|
||
|
|
|
||
|
|
impl InlineNode {
|
||
|
|
/// The source [`Span`] this node covers. Every variant wraps a node with
|
||
|
|
/// its own `span` field; this is the one place that maps variant → span.
|
||
|
|
pub fn span(&self) -> Span {
|
||
|
|
match self {
|
||
|
|
InlineNode::Text(n) => n.span,
|
||
|
|
InlineNode::Bold(n) => n.span,
|
||
|
|
InlineNode::Italic(n) => n.span,
|
||
|
|
InlineNode::BoldItalic(n) => n.span,
|
||
|
|
InlineNode::Strikethrough(n) => n.span,
|
||
|
|
InlineNode::Code(n) => n.span,
|
||
|
|
InlineNode::Superscript(n) => n.span,
|
||
|
|
InlineNode::Subscript(n) => n.span,
|
||
|
|
InlineNode::MathInline(n) => n.span,
|
||
|
|
InlineNode::Keyword(n) => n.span,
|
||
|
|
InlineNode::Color(n) => n.span,
|
||
|
|
InlineNode::WikiLink(n) => n.span,
|
||
|
|
InlineNode::ExternalLink(n) => n.span,
|
||
|
|
InlineNode::Transclusion(n) => n.span,
|
||
|
|
InlineNode::RawUrl(n) => n.span,
|
||
|
|
InlineNode::SoftBreak(n) => n.span,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Concatenate the plain-text content of a run of inlines, descending into
|
||
|
|
/// formatting containers. Links/external links use their description, falling
|
||
|
|
/// back to the target path / URL. This is the canonical heading/anchor text —
|
||
|
|
/// the HTML renderer uses it for heading `id`s and the LSP for anchor matching,
|
||
|
|
/// so the two never drift.
|
||
|
|
pub fn inline_text(inlines: &[InlineNode]) -> String {
|
||
|
|
let mut out = String::new();
|
||
|
|
push_inline_text(inlines, &mut out);
|
||
|
|
out.trim().to_string()
|
||
|
|
}
|
||
|
|
|
||
|
|
fn push_inline_text(inlines: &[InlineNode], out: &mut String) {
|
||
|
|
for n in inlines {
|
||
|
|
match n {
|
||
|
|
InlineNode::Text(t) => out.push_str(&t.content),
|
||
|
|
InlineNode::Bold(b) => push_inline_text(&b.children, out),
|
||
|
|
InlineNode::Italic(i) => push_inline_text(&i.children, out),
|
||
|
|
InlineNode::BoldItalic(bi) => push_inline_text(&bi.children, out),
|
||
|
|
InlineNode::Strikethrough(s) => push_inline_text(&s.children, out),
|
||
|
|
InlineNode::Code(c) => out.push_str(&c.content),
|
||
|
|
InlineNode::Superscript(s) => push_inline_text(&s.children, out),
|
||
|
|
InlineNode::Subscript(s) => push_inline_text(&s.children, out),
|
||
|
|
InlineNode::Color(c) => push_inline_text(&c.children, out),
|
||
|
|
InlineNode::Keyword(k) => out.push_str(k.keyword.label()),
|
||
|
|
InlineNode::WikiLink(w) => match &w.description {
|
||
|
|
Some(d) => push_inline_text(d, out),
|
||
|
|
None => {
|
||
|
|
if let Some(p) = &w.target.path {
|
||
|
|
out.push_str(p);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
InlineNode::ExternalLink(e) => match &e.description {
|
||
|
|
Some(d) => push_inline_text(d, out),
|
||
|
|
None => out.push_str(&e.url),
|
||
|
|
},
|
||
|
|
_ => {}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub struct TextNode {
|
||
|
|
pub span: Span,
|
||
|
|
pub content: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub struct SoftBreakNode {
|
||
|
|
pub span: Span,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub struct BoldNode {
|
||
|
|
pub span: Span,
|
||
|
|
pub children: Vec<InlineNode>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub struct ItalicNode {
|
||
|
|
pub span: Span,
|
||
|
|
pub children: Vec<InlineNode>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub struct BoldItalicNode {
|
||
|
|
pub span: Span,
|
||
|
|
pub children: Vec<InlineNode>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub struct StrikethroughNode {
|
||
|
|
pub span: Span,
|
||
|
|
pub children: Vec<InlineNode>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub struct CodeNode {
|
||
|
|
pub span: Span,
|
||
|
|
pub content: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub struct SuperscriptNode {
|
||
|
|
pub span: Span,
|
||
|
|
pub children: Vec<InlineNode>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub struct SubscriptNode {
|
||
|
|
pub span: Span,
|
||
|
|
pub children: Vec<InlineNode>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub struct MathInlineNode {
|
||
|
|
pub span: Span,
|
||
|
|
pub content: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub struct KeywordNode {
|
||
|
|
pub span: Span,
|
||
|
|
pub keyword: Keyword,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// A named colour span (`color` is a `color_dic` key, `children` the wrapped
|
||
|
|
/// inline content).
|
||
|
|
///
|
||
|
|
/// This is an **extension point**, not a node the vimwiki parser emits: in
|
||
|
|
/// the editor, colour comes from the `:Colorize` family writing literal
|
||
|
|
/// `<span style="color:…">` markup, which round-trips through export via
|
||
|
|
/// `valid_html_tags`. `ColorNode` exists so an alternate syntax (or a future
|
||
|
|
/// `[color]` markup) can feed the renderer's `color_dic` / `color_tag_template`
|
||
|
|
/// path directly; see `HtmlRenderer::render_color`.
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub struct ColorNode {
|
||
|
|
pub span: Span,
|
||
|
|
pub color: String,
|
||
|
|
pub children: Vec<InlineNode>,
|
||
|
|
}
|