feat: initial nuwiki Rust workspace (core, lsp, ls)
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
//! Block-level AST nodes and their supporting types.
|
||||
|
||||
use super::inline::InlineNode;
|
||||
use super::span::Span;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum BlockNode {
|
||||
Heading(HeadingNode),
|
||||
Paragraph(ParagraphNode),
|
||||
HorizontalRule(HorizontalRuleNode),
|
||||
Blockquote(BlockquoteNode),
|
||||
Preformatted(PreformattedNode),
|
||||
MathBlock(MathBlockNode),
|
||||
List(ListNode),
|
||||
DefinitionList(DefinitionListNode),
|
||||
Table(TableNode),
|
||||
Comment(CommentNode),
|
||||
/// Vimwiki tag line — `:tag1:tag2:`. The placement
|
||||
/// rule (file / heading / standalone) is captured in `TagScope` so
|
||||
/// LSP handlers and renderers can treat the three differently.
|
||||
Tag(TagNode),
|
||||
Error(ErrorNode),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct HeadingNode {
|
||||
pub span: Span,
|
||||
/// 1..=6 — invariant enforced by the parser, not the type.
|
||||
pub level: u8,
|
||||
pub children: Vec<InlineNode>,
|
||||
pub centered: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ParagraphNode {
|
||||
pub span: Span,
|
||||
pub children: Vec<InlineNode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct HorizontalRuleNode {
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BlockquoteNode {
|
||||
pub span: Span,
|
||||
pub children: Vec<BlockNode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PreformattedNode {
|
||||
pub span: Span,
|
||||
pub content: String,
|
||||
pub language: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MathBlockNode {
|
||||
pub span: Span,
|
||||
pub content: String,
|
||||
pub environment: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ListNode {
|
||||
pub span: Span,
|
||||
pub ordered: bool,
|
||||
pub symbol: ListSymbol,
|
||||
pub items: Vec<ListItemNode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ListItemNode {
|
||||
pub span: Span,
|
||||
pub symbol: ListSymbol,
|
||||
pub level: usize,
|
||||
pub checkbox: Option<CheckboxState>,
|
||||
pub children: Vec<InlineNode>,
|
||||
pub sublist: Option<ListNode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ListSymbol {
|
||||
Dash,
|
||||
Star,
|
||||
Hash,
|
||||
Numeric,
|
||||
NumericParen,
|
||||
AlphaParen,
|
||||
AlphaUpperParen,
|
||||
RomanParen,
|
||||
RomanUpperParen,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum CheckboxState {
|
||||
Empty,
|
||||
Quarter,
|
||||
Half,
|
||||
ThreeQuarters,
|
||||
Done,
|
||||
Rejected,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DefinitionListNode {
|
||||
pub span: Span,
|
||||
pub items: Vec<DefinitionItemNode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DefinitionItemNode {
|
||||
pub span: Span,
|
||||
pub term: Option<Vec<InlineNode>>,
|
||||
pub definitions: Vec<Vec<InlineNode>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TableAlign {
|
||||
Default,
|
||||
Left,
|
||||
Right,
|
||||
Center,
|
||||
}
|
||||
|
||||
impl Default for TableAlign {
|
||||
fn default() -> Self {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TableNode {
|
||||
pub span: Span,
|
||||
pub rows: Vec<TableRowNode>,
|
||||
pub has_header: bool,
|
||||
/// One entry per column, taken from a Markdown-style header
|
||||
/// separator row (`|:--|--:|:--:|`). Empty when no alignment was
|
||||
/// specified; cells past the end inherit `Default`.
|
||||
pub alignments: Vec<TableAlign>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TableRowNode {
|
||||
pub span: Span,
|
||||
pub cells: Vec<TableCellNode>,
|
||||
pub is_header: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TableCellNode {
|
||||
pub span: Span,
|
||||
pub children: Vec<InlineNode>,
|
||||
pub col_span: bool,
|
||||
pub row_span: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CommentNode {
|
||||
pub span: Span,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
/// Where a `TagNode` falls on the page, matching vimwiki's placement rules.
|
||||
///
|
||||
/// - `File` — line 0 or 1 of the document; tags the entire page.
|
||||
/// - `Heading(i)` — within two lines below the i-th `HeadingNode` in the
|
||||
/// document's `children`; tags that heading.
|
||||
/// - `Standalone` — anywhere else; the tag is its own anchor on the source
|
||||
/// line.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum TagScope {
|
||||
File,
|
||||
Heading(usize),
|
||||
Standalone,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TagNode {
|
||||
pub span: Span,
|
||||
pub tags: Vec<String>,
|
||||
pub scope: TagScope,
|
||||
}
|
||||
|
||||
/// Resilient parse-failure node — emitted instead of aborting the document.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ErrorNode {
|
||||
pub span: Span,
|
||||
pub raw: String,
|
||||
pub message: String,
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
//! 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,
|
||||
}
|
||||
|
||||
#[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::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>,
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//! Link-related AST nodes and supporting types.
|
||||
//!
|
||||
//! All nodes here are inline (`InlineNode` variants);
|
||||
//! they live in their own module because the link model has enough surface
|
||||
//! area (kinds, anchors, interwiki indirection) to warrant separation.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::inline::InlineNode;
|
||||
use super::span::Span;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum LinkKind {
|
||||
Wiki,
|
||||
Interwiki,
|
||||
Diary,
|
||||
File,
|
||||
Local,
|
||||
Raw,
|
||||
AnchorOnly,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
|
||||
pub struct LinkTarget {
|
||||
pub kind: LinkKind,
|
||||
pub path: Option<String>,
|
||||
pub wiki_index: Option<usize>,
|
||||
pub wiki_name: Option<String>,
|
||||
pub anchor: Option<String>,
|
||||
pub is_absolute: bool,
|
||||
pub is_directory: bool,
|
||||
}
|
||||
|
||||
impl Default for LinkKind {
|
||||
fn default() -> Self {
|
||||
Self::Wiki
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct WikiLinkNode {
|
||||
pub span: Span,
|
||||
pub target: LinkTarget,
|
||||
pub description: Option<Vec<InlineNode>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExternalLinkNode {
|
||||
pub span: Span,
|
||||
pub url: String,
|
||||
pub description: Option<Vec<InlineNode>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TransclusionNode {
|
||||
pub span: Span,
|
||||
pub url: String,
|
||||
pub alt: Option<String>,
|
||||
pub attrs: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RawUrlNode {
|
||||
pub span: Span,
|
||||
pub url: String,
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//! AST types for nuwiki documents.
|
||||
//!
|
||||
//! Types here are syntax-agnostic — both vimwiki and (eventually) markdown
|
||||
//! produce the same node shapes.
|
||||
|
||||
pub mod block;
|
||||
pub mod inline;
|
||||
pub mod link;
|
||||
pub mod span;
|
||||
pub mod visit;
|
||||
|
||||
pub use block::{
|
||||
BlockNode, BlockquoteNode, CheckboxState, CommentNode, DefinitionItemNode, DefinitionListNode,
|
||||
ErrorNode, HeadingNode, HorizontalRuleNode, ListItemNode, ListNode, ListSymbol, MathBlockNode,
|
||||
ParagraphNode, PreformattedNode, TableAlign, TableCellNode, TableNode, TableRowNode, TagNode,
|
||||
TagScope,
|
||||
};
|
||||
pub use inline::{
|
||||
inline_text, BoldItalicNode, BoldNode, CodeNode, ColorNode, InlineNode, ItalicNode, Keyword,
|
||||
KeywordNode, MathInlineNode, SoftBreakNode, StrikethroughNode, SubscriptNode, SuperscriptNode,
|
||||
TextNode,
|
||||
};
|
||||
pub use link::{
|
||||
ExternalLinkNode, LinkKind, LinkTarget, RawUrlNode, TransclusionNode, WikiLinkNode,
|
||||
};
|
||||
pub use span::{Position, Span};
|
||||
pub use visit::{
|
||||
walk_block, walk_definition_item, walk_document, walk_inline, walk_list, walk_list_item,
|
||||
walk_table, walk_table_row, Visitor,
|
||||
};
|
||||
|
||||
/// Root of an AST.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct DocumentNode {
|
||||
pub span: Span,
|
||||
pub children: Vec<BlockNode>,
|
||||
pub metadata: PageMetadata,
|
||||
}
|
||||
|
||||
/// Document-level placeholders parsed from `%title` / `%nohtml` / `%template`
|
||||
/// / `%date` directives, plus the file-level tag accumulator.
|
||||
///
|
||||
/// New fields are added at the bottom; consumers should construct with
|
||||
/// `..Default::default()` to stay forward-compatible.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct PageMetadata {
|
||||
pub title: Option<String>,
|
||||
pub nohtml: bool,
|
||||
pub template: Option<String>,
|
||||
pub date: Option<String>,
|
||||
/// File-scope tags parsed by the tag lexer. Convenience
|
||||
/// projection of the `BlockNode::Tag` nodes whose scope is `File`.
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//! Source positions and spans carried on every AST node.
|
||||
//!
|
||||
//! Positions are 0-indexed; `column` is a byte offset
|
||||
//! within a line; `offset` is a byte offset from the start of the document.
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Position {
|
||||
pub line: u32,
|
||||
pub column: u32,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
impl Position {
|
||||
pub const fn new(line: u32, column: u32, offset: usize) -> Self {
|
||||
Self {
|
||||
line,
|
||||
column,
|
||||
offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
pub struct Span {
|
||||
pub start: Position,
|
||||
pub end: Position,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub const fn new(start: Position, end: Position) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
//! Open-recursion AST visitor.
|
||||
//!
|
||||
//! Each `visit_*` method has a default body that calls the corresponding
|
||||
//! `walk_*` free function, which in turn dispatches back into the trait.
|
||||
//! Override any method to short-circuit or augment traversal — call the
|
||||
//! matching `walk_*` from your override to keep descending.
|
||||
//!
|
||||
//! Pattern follows `rustc`'s and `syn`'s visitors.
|
||||
//!
|
||||
//! Read-only for now. A `VisitorMut` flavor can be added when transformations
|
||||
//! become relevant.
|
||||
|
||||
use super::block::{
|
||||
BlockNode, BlockquoteNode, CommentNode, DefinitionItemNode, DefinitionListNode, ErrorNode,
|
||||
HeadingNode, HorizontalRuleNode, ListItemNode, ListNode, MathBlockNode, ParagraphNode,
|
||||
PreformattedNode, TableCellNode, TableNode, TableRowNode, TagNode,
|
||||
};
|
||||
use super::inline::{
|
||||
BoldItalicNode, BoldNode, CodeNode, ColorNode, InlineNode, ItalicNode, KeywordNode,
|
||||
MathInlineNode, StrikethroughNode, SubscriptNode, SuperscriptNode, TextNode,
|
||||
};
|
||||
use super::link::{ExternalLinkNode, RawUrlNode, TransclusionNode, WikiLinkNode};
|
||||
use super::DocumentNode;
|
||||
|
||||
pub trait Visitor {
|
||||
// Top level
|
||||
fn visit_document(&mut self, node: &DocumentNode) {
|
||||
walk_document(self, node);
|
||||
}
|
||||
|
||||
// Block dispatch
|
||||
fn visit_block(&mut self, node: &BlockNode) {
|
||||
walk_block(self, node);
|
||||
}
|
||||
|
||||
// Inline dispatch
|
||||
fn visit_inline(&mut self, node: &InlineNode) {
|
||||
walk_inline(self, node);
|
||||
}
|
||||
|
||||
// Block leaves and recursive block nodes
|
||||
fn visit_heading(&mut self, node: &HeadingNode) {
|
||||
for child in &node.children {
|
||||
self.visit_inline(child);
|
||||
}
|
||||
}
|
||||
fn visit_paragraph(&mut self, node: &ParagraphNode) {
|
||||
for child in &node.children {
|
||||
self.visit_inline(child);
|
||||
}
|
||||
}
|
||||
fn visit_horizontal_rule(&mut self, _node: &HorizontalRuleNode) {}
|
||||
fn visit_blockquote(&mut self, node: &BlockquoteNode) {
|
||||
for child in &node.children {
|
||||
self.visit_block(child);
|
||||
}
|
||||
}
|
||||
fn visit_preformatted(&mut self, _node: &PreformattedNode) {}
|
||||
fn visit_math_block(&mut self, _node: &MathBlockNode) {}
|
||||
fn visit_list(&mut self, node: &ListNode) {
|
||||
walk_list(self, node);
|
||||
}
|
||||
fn visit_list_item(&mut self, node: &ListItemNode) {
|
||||
walk_list_item(self, node);
|
||||
}
|
||||
fn visit_definition_list(&mut self, node: &DefinitionListNode) {
|
||||
for item in &node.items {
|
||||
self.visit_definition_item(item);
|
||||
}
|
||||
}
|
||||
fn visit_definition_item(&mut self, node: &DefinitionItemNode) {
|
||||
walk_definition_item(self, node);
|
||||
}
|
||||
fn visit_table(&mut self, node: &TableNode) {
|
||||
walk_table(self, node);
|
||||
}
|
||||
fn visit_table_row(&mut self, node: &TableRowNode) {
|
||||
walk_table_row(self, node);
|
||||
}
|
||||
fn visit_table_cell(&mut self, node: &TableCellNode) {
|
||||
for child in &node.children {
|
||||
self.visit_inline(child);
|
||||
}
|
||||
}
|
||||
fn visit_comment(&mut self, _node: &CommentNode) {}
|
||||
fn visit_tag(&mut self, _node: &TagNode) {}
|
||||
fn visit_error(&mut self, _node: &ErrorNode) {}
|
||||
|
||||
// Inline leaves and recursive inline nodes
|
||||
fn visit_text(&mut self, _node: &TextNode) {}
|
||||
fn visit_bold(&mut self, node: &BoldNode) {
|
||||
for child in &node.children {
|
||||
self.visit_inline(child);
|
||||
}
|
||||
}
|
||||
fn visit_italic(&mut self, node: &ItalicNode) {
|
||||
for child in &node.children {
|
||||
self.visit_inline(child);
|
||||
}
|
||||
}
|
||||
fn visit_bold_italic(&mut self, node: &BoldItalicNode) {
|
||||
for child in &node.children {
|
||||
self.visit_inline(child);
|
||||
}
|
||||
}
|
||||
fn visit_strikethrough(&mut self, node: &StrikethroughNode) {
|
||||
for child in &node.children {
|
||||
self.visit_inline(child);
|
||||
}
|
||||
}
|
||||
fn visit_code(&mut self, _node: &CodeNode) {}
|
||||
fn visit_superscript(&mut self, node: &SuperscriptNode) {
|
||||
for child in &node.children {
|
||||
self.visit_inline(child);
|
||||
}
|
||||
}
|
||||
fn visit_subscript(&mut self, node: &SubscriptNode) {
|
||||
for child in &node.children {
|
||||
self.visit_inline(child);
|
||||
}
|
||||
}
|
||||
fn visit_math_inline(&mut self, _node: &MathInlineNode) {}
|
||||
fn visit_keyword(&mut self, _node: &KeywordNode) {}
|
||||
fn visit_color(&mut self, node: &ColorNode) {
|
||||
for child in &node.children {
|
||||
self.visit_inline(child);
|
||||
}
|
||||
}
|
||||
fn visit_wiki_link(&mut self, node: &WikiLinkNode) {
|
||||
if let Some(desc) = &node.description {
|
||||
for child in desc {
|
||||
self.visit_inline(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn visit_external_link(&mut self, node: &ExternalLinkNode) {
|
||||
if let Some(desc) = &node.description {
|
||||
for child in desc {
|
||||
self.visit_inline(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn visit_transclusion(&mut self, _node: &TransclusionNode) {}
|
||||
fn visit_raw_url(&mut self, _node: &RawUrlNode) {}
|
||||
}
|
||||
|
||||
pub fn walk_document<V: Visitor + ?Sized>(v: &mut V, node: &DocumentNode) {
|
||||
for child in &node.children {
|
||||
v.visit_block(child);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_block<V: Visitor + ?Sized>(v: &mut V, node: &BlockNode) {
|
||||
match node {
|
||||
BlockNode::Heading(n) => v.visit_heading(n),
|
||||
BlockNode::Paragraph(n) => v.visit_paragraph(n),
|
||||
BlockNode::HorizontalRule(n) => v.visit_horizontal_rule(n),
|
||||
BlockNode::Blockquote(n) => v.visit_blockquote(n),
|
||||
BlockNode::Preformatted(n) => v.visit_preformatted(n),
|
||||
BlockNode::MathBlock(n) => v.visit_math_block(n),
|
||||
BlockNode::List(n) => v.visit_list(n),
|
||||
BlockNode::DefinitionList(n) => v.visit_definition_list(n),
|
||||
BlockNode::Table(n) => v.visit_table(n),
|
||||
BlockNode::Comment(n) => v.visit_comment(n),
|
||||
BlockNode::Tag(n) => v.visit_tag(n),
|
||||
BlockNode::Error(n) => v.visit_error(n),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_inline<V: Visitor + ?Sized>(v: &mut V, node: &InlineNode) {
|
||||
match node {
|
||||
InlineNode::Text(n) => v.visit_text(n),
|
||||
InlineNode::Bold(n) => v.visit_bold(n),
|
||||
InlineNode::Italic(n) => v.visit_italic(n),
|
||||
InlineNode::BoldItalic(n) => v.visit_bold_italic(n),
|
||||
InlineNode::Strikethrough(n) => v.visit_strikethrough(n),
|
||||
InlineNode::Code(n) => v.visit_code(n),
|
||||
InlineNode::Superscript(n) => v.visit_superscript(n),
|
||||
InlineNode::Subscript(n) => v.visit_subscript(n),
|
||||
InlineNode::MathInline(n) => v.visit_math_inline(n),
|
||||
InlineNode::Keyword(n) => v.visit_keyword(n),
|
||||
InlineNode::Color(n) => v.visit_color(n),
|
||||
InlineNode::WikiLink(n) => v.visit_wiki_link(n),
|
||||
InlineNode::ExternalLink(n) => v.visit_external_link(n),
|
||||
InlineNode::Transclusion(n) => v.visit_transclusion(n),
|
||||
InlineNode::RawUrl(n) => v.visit_raw_url(n),
|
||||
// A soft break carries no content/children — treated as whitespace.
|
||||
InlineNode::SoftBreak(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_list<V: Visitor + ?Sized>(v: &mut V, node: &ListNode) {
|
||||
for item in &node.items {
|
||||
v.visit_list_item(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_list_item<V: Visitor + ?Sized>(v: &mut V, node: &ListItemNode) {
|
||||
for child in &node.children {
|
||||
v.visit_inline(child);
|
||||
}
|
||||
if let Some(sublist) = &node.sublist {
|
||||
v.visit_list(sublist);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_definition_item<V: Visitor + ?Sized>(v: &mut V, node: &DefinitionItemNode) {
|
||||
if let Some(term) = &node.term {
|
||||
for child in term {
|
||||
v.visit_inline(child);
|
||||
}
|
||||
}
|
||||
for definition in &node.definitions {
|
||||
for child in definition {
|
||||
v.visit_inline(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_table<V: Visitor + ?Sized>(v: &mut V, node: &TableNode) {
|
||||
for row in &node.rows {
|
||||
v.visit_table_row(row);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_table_row<V: Visitor + ?Sized>(v: &mut V, node: &TableRowNode) {
|
||||
for cell in &node.cells {
|
||||
v.visit_table_cell(cell);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user