Major clean up and improvements to vimwiki compatibility and documentation.
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
//! Block-level AST nodes and their supporting types.
|
||||
//!
|
||||
//! See SPEC.md §6.4.
|
||||
|
||||
use super::inline::InlineNode;
|
||||
use super::span::Span;
|
||||
@@ -17,7 +15,7 @@ pub enum BlockNode {
|
||||
DefinitionList(DefinitionListNode),
|
||||
Table(TableNode),
|
||||
Comment(CommentNode),
|
||||
/// Vimwiki tag line — `:tag1:tag2:`. Phase 12 (v1.1). The placement
|
||||
/// 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),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Inline AST nodes.
|
||||
//!
|
||||
//! See SPEC.md §6.4. The link-related variants (`WikiLink`, `ExternalLink`,
|
||||
//! The link-related variants (`WikiLink`, `ExternalLink`,
|
||||
//! `Transclusion`, `RawUrl`) wrap structs defined in `super::link`.
|
||||
|
||||
use super::link::{ExternalLinkNode, RawUrlNode, TransclusionNode, WikiLinkNode};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Link-related AST nodes and supporting types.
|
||||
//!
|
||||
//! See SPEC.md §6.4. All nodes here are inline (`InlineNode` variants);
|
||||
//! 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.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! AST types for nuwiki documents.
|
||||
//!
|
||||
//! Types here are syntax-agnostic — both vimwiki and (eventually) markdown
|
||||
//! produce the same node shapes. See SPEC.md §6.4.
|
||||
//! produce the same node shapes.
|
||||
|
||||
pub mod block;
|
||||
pub mod inline;
|
||||
@@ -28,7 +28,7 @@ pub use visit::{
|
||||
walk_table, walk_table_row, Visitor,
|
||||
};
|
||||
|
||||
/// Root of an AST. See SPEC.md §6.4.
|
||||
/// Root of an AST.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct DocumentNode {
|
||||
pub span: Span,
|
||||
@@ -37,8 +37,7 @@ pub struct DocumentNode {
|
||||
}
|
||||
|
||||
/// Document-level placeholders parsed from `%title` / `%nohtml` / `%template`
|
||||
/// / `%date` directives, plus the v1.1 file-level tag accumulator
|
||||
/// (SPEC §12.3). See SPEC.md §9.
|
||||
/// / `%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.
|
||||
@@ -48,7 +47,7 @@ pub struct PageMetadata {
|
||||
pub nohtml: bool,
|
||||
pub template: Option<String>,
|
||||
pub date: Option<String>,
|
||||
/// File-scope tags parsed by the v1.1 tag lexer (Phase 12). Convenience
|
||||
/// File-scope tags parsed by the tag lexer. Convenience
|
||||
/// projection of the `BlockNode::Tag` nodes whose scope is `File`.
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Source positions and spans carried on every AST node.
|
||||
//!
|
||||
//! See SPEC.md §6.5. Positions are 0-indexed; `column` is a byte offset
|
||||
//! 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)]
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
//! 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. See SPEC.md §6.4 + P4.
|
||||
//! Pattern follows `rustc`'s and `syn`'s visitors.
|
||||
//!
|
||||
//! Read-only for now. A `VisitorMut` flavor can be added when transformations
|
||||
//! become relevant (post-Phase 5).
|
||||
//! become relevant.
|
||||
|
||||
use super::block::{
|
||||
BlockNode, BlockquoteNode, CommentNode, DefinitionItemNode, DefinitionListNode, ErrorNode,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Date primitives for the diary subsystem (Phase 16).
|
||||
//! Date primitives for the diary subsystem.
|
||||
//!
|
||||
//! v1.1 deliberately doesn't pull in `chrono` or `time`. The diary feature
|
||||
//! This crate deliberately doesn't pull in `chrono` or `time`. The diary feature
|
||||
//! only needs `YYYY-MM-DD` parsing/formatting and ±1 day arithmetic, which
|
||||
//! is small enough that the dependency cost outweighs the convenience.
|
||||
//!
|
||||
@@ -56,7 +56,7 @@ impl DiaryDate {
|
||||
format!("{:04}-{:02}-{:02}", self.year, self.month, self.day)
|
||||
}
|
||||
|
||||
/// UTC "today" computed from `SystemTime::now()`. Phase 16's diary
|
||||
/// UTC "today" computed from `SystemTime::now()`. The diary
|
||||
/// commands intentionally use UTC — local-time semantics depend on a
|
||||
/// timezone DB we don't ship and would surprise users crossing DST
|
||||
/// boundaries.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Core parser, AST, and renderer for nuwiki.
|
||||
//!
|
||||
//! This crate is editor-independent and must never depend on `nuwiki-lsp` or
|
||||
//! `nuwiki-ls`. See SPEC.md §6.2 for the dependency rules.
|
||||
//! `nuwiki-ls`.
|
||||
|
||||
pub mod ast;
|
||||
pub mod date;
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
//! Walks a `DocumentNode` and writes HTML5 fragments. The renderer is
|
||||
//! configured with a link resolver (callback that turns a `LinkTarget` into
|
||||
//! a URL string) and, optionally, an enclosing template with substitution
|
||||
//! placeholders (SPEC.md §6.8 + §12.8).
|
||||
//! placeholders.
|
||||
//!
|
||||
//! Substitution model: `{{content}}` and `{{title}}` are computed from
|
||||
//! the rendered body and the document's metadata; `with_var(k, v)` /
|
||||
//! `with_vars(map)` add arbitrary key→value pairs (Phase 17 ships
|
||||
//! `{{date}}`, `{{root_path}}`, `{{toc}}`). Order: `{{content}}` is
|
||||
//! `with_vars(map)` add arbitrary key→value pairs (`{{date}}`,
|
||||
//! `{{root_path}}`, `{{toc}}`). Order: `{{content}}` is
|
||||
//! substituted first so a body that happens to contain a literal
|
||||
//! `{{title}}` isn't itself rewritten in the next pass.
|
||||
//!
|
||||
@@ -44,7 +44,7 @@ pub struct HtmlRenderer {
|
||||
/// `color_dic`-style override: vimwiki colour-tag names → CSS
|
||||
/// values (`"red"` / `"#ffe119"` / `"oklch(…)"`). Unspecified
|
||||
/// names fall through to the default `class="color-<name>"`
|
||||
/// rendering. Matches SPEC §12.11 `color_dic`.
|
||||
/// rendering. Matches vimwiki's `color_dic`.
|
||||
colors: HashMap<String, String>,
|
||||
}
|
||||
|
||||
@@ -662,7 +662,7 @@ fn table_spans(table: &TableNode) -> Vec<Vec<CellLayout>> {
|
||||
|
||||
// ===== Default link resolver =====
|
||||
|
||||
/// Default `LinkResolver`. Mirrors the conventions in SPEC.md §9:
|
||||
/// Default `LinkResolver`. Mirrors the vimwiki link conventions:
|
||||
/// wiki pages become `path.html`; interwiki links land in sibling
|
||||
/// directories; diary entries land under `diary/`; file/local schemes
|
||||
/// use their literal path; anchor-only links collapse to a fragment.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Document renderers.
|
||||
//!
|
||||
//! SPEC.md §6.8: a `Renderer` is writer-based, takes a `DocumentNode`, and
|
||||
//! A `Renderer` is writer-based, takes a `DocumentNode`, and
|
||||
//! emits its representation into any `Write`. Errors propagate through
|
||||
//! `io::Result` — there's no separate per-renderer error type so the trait
|
||||
//! stays object-safe (`Box<dyn Renderer>` is useful for the LSP later).
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
//! a `Lexer` produces a `TokenStream`, a `Parser` consumes it and produces a
|
||||
//! `DocumentNode`. A `SyntaxPlugin` is the type-erased facade that the LSP
|
||||
//! layer holds in a `SyntaxRegistry`.
|
||||
//!
|
||||
//! See SPEC.md §6.3 (interface) and §6.6 (lexer strategy).
|
||||
|
||||
pub mod registry;
|
||||
pub mod vimwiki;
|
||||
@@ -15,7 +13,7 @@ pub use registry::SyntaxRegistry;
|
||||
use crate::ast::DocumentNode;
|
||||
|
||||
/// Eager token buffer produced by a `Lexer`. The newtype keeps the door
|
||||
/// open to a lazy/streaming variant later (SPEC.md §6.6) without breaking
|
||||
/// open to a lazy/streaming variant later without breaking
|
||||
/// callers.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct TokenStream<T> {
|
||||
@@ -89,7 +87,7 @@ pub trait Lexer {
|
||||
}
|
||||
|
||||
/// Parser half of a syntax plugin. Consumes a `TokenStream` and produces a
|
||||
/// `DocumentNode`. Per SPEC.md §6.7 parsing is resilient — malformed input
|
||||
/// `DocumentNode`. Parsing is resilient — malformed input
|
||||
/// becomes `BlockNode::Error(ErrorNode)`, never a hard failure.
|
||||
pub trait Parser {
|
||||
type Token;
|
||||
@@ -104,13 +102,13 @@ pub trait Parser {
|
||||
/// hold heterogeneous plugins behind a single trait object.
|
||||
///
|
||||
/// `Send + Sync` is required — the LSP server stores plugins behind an `Arc`
|
||||
/// and shares them across request handler tasks (SPEC.md §6.3).
|
||||
/// and shares them across request handler tasks.
|
||||
pub trait SyntaxPlugin: Send + Sync {
|
||||
fn id(&self) -> &str;
|
||||
fn display_name(&self) -> &str;
|
||||
|
||||
/// File extensions associated with this syntax. Each entry includes the
|
||||
/// leading dot, e.g. `[".wiki"]`. See SPEC.md §6.3.
|
||||
/// leading dot, e.g. `[".wiki"]`.
|
||||
fn file_extensions(&self) -> &[&str];
|
||||
|
||||
fn parse(&self, text: &str) -> DocumentNode;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Registry of syntax plugins. See SPEC.md §6.3.
|
||||
//! Registry of syntax plugins.
|
||||
//!
|
||||
//! Plugins are stored behind `Arc<dyn SyntaxPlugin>` so the LSP layer can
|
||||
//! hand a cheap clone to per-document tasks without re-locking the registry.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Vimwiki lexer.
|
||||
//!
|
||||
//! Hand-rolled, two-pass per SPEC.md §6.6:
|
||||
//! Hand-rolled, two-pass:
|
||||
//!
|
||||
//! - **Block pass:** scan the source line by line, recognise structural
|
||||
//! constructs (headings, lists, tables, fences, comments, etc.), emit
|
||||
@@ -12,7 +12,7 @@
|
||||
//! Both passes share one `Vec<VimwikiToken>` so the parser sees a single,
|
||||
//! ordered stream. The lexer is permissive: every delimiter is emitted; the
|
||||
//! parser pairs them and falls back to literal text on mismatches. Spans are
|
||||
//! stored as 0-indexed byte offsets per SPEC.md §6.5.
|
||||
//! stored as 0-indexed byte offsets.
|
||||
//!
|
||||
//! Multi-line constructs (`{{{ }}}`, `{{$ }}$`, `%%+ +%%`) flip a
|
||||
//! `BlockMode` so subsequent lines are treated as raw content until the
|
||||
@@ -97,13 +97,13 @@ pub enum VimwikiTokenKind {
|
||||
CommentMultiClose,
|
||||
CommentMultiLine(String),
|
||||
|
||||
// ----- Tags (SPEC §12.3) -----
|
||||
// ----- Tags -----
|
||||
/// `:tag1:tag2:` — colon-delimited tag line. The lexer treats this as
|
||||
/// block-level (must be the entire trimmed content of the line).
|
||||
/// Scope (file / heading / standalone) is the parser's job.
|
||||
Tag(Vec<String>),
|
||||
|
||||
// ----- Page placeholders (SPEC §9) -----
|
||||
// ----- Page placeholders -----
|
||||
PlaceholderTitle(Option<String>),
|
||||
PlaceholderNohtml,
|
||||
PlaceholderTemplate(Option<String>),
|
||||
@@ -129,7 +129,7 @@ pub enum VimwikiTokenKind {
|
||||
TransclusionSep,
|
||||
RawUrl(String),
|
||||
|
||||
/// Lex error — never aborts the whole document (SPEC §6.7).
|
||||
/// Lex error — never aborts the whole document.
|
||||
Error(String),
|
||||
}
|
||||
|
||||
@@ -481,7 +481,7 @@ impl<'src> LexState<'src> {
|
||||
if trimmed.len() < 4 || !trimmed.bytes().all(|b| b == b'-') {
|
||||
return false;
|
||||
}
|
||||
// SPEC §9: four or more dashes. Allow surrounding whitespace.
|
||||
// Four or more dashes. Allow surrounding whitespace.
|
||||
let span = Span::new(self.line_start_pos(), self.line_end_pos(line));
|
||||
self.push(VimwikiTokenKind::HorizontalRule, span);
|
||||
self.emit_newline(line);
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::ast::DocumentNode;
|
||||
use crate::syntax::{Lexer, Parser, SyntaxPlugin};
|
||||
|
||||
/// SyntaxPlugin facade: chains `VimwikiLexer` + `VimwikiParser` so the
|
||||
/// registry can hand out a single trait object per SPEC.md §6.3.
|
||||
/// registry can hand out a single trait object.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct VimwikiSyntax;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Vimwiki parser.
|
||||
//!
|
||||
//! Hand-rolled recursive-descent over `Vec<VimwikiToken>` (SPEC.md §6.7).
|
||||
//! Hand-rolled recursive-descent over `Vec<VimwikiToken>`.
|
||||
//! Resilient: never aborts the document. Anything the dispatcher can't
|
||||
//! claim becomes a `BlockNode::Error(ErrorNode)` so progress is
|
||||
//! guaranteed.
|
||||
@@ -28,7 +28,7 @@
|
||||
//!
|
||||
//! `[[ ]]` payloads are inspected to choose between `WikiLinkNode`
|
||||
//! (the default) and `ExternalLinkNode` (when the target is a URL). The
|
||||
//! `LinkTarget` enum follows SPEC §9: leading `/` is root-relative,
|
||||
//! `LinkTarget` enum follows the vimwiki conventions: leading `/` is root-relative,
|
||||
//! `//` is filesystem-absolute, `wiki<N>:` / `wn.<Name>:` are interwiki,
|
||||
//! `diary:` / `file:` / `local:` are their own kinds, and a trailing `/`
|
||||
//! marks a directory link.
|
||||
@@ -69,10 +69,10 @@ impl Parser for VimwikiParser {
|
||||
struct ParseState<'a> {
|
||||
toks: &'a [VimwikiToken],
|
||||
pos: usize,
|
||||
/// Phase 12: count of `HeadingNode`s already pushed to `children`.
|
||||
/// Count of `HeadingNode`s already pushed to `children`.
|
||||
/// `headings_seen - 1` is the most recent heading's index.
|
||||
headings_seen: usize,
|
||||
/// Phase 12: source line of the most recently-emitted heading.
|
||||
/// Source line of the most recently-emitted heading.
|
||||
/// `tag_line - last_heading_line ∈ {1, 2}` is a header-scope tag.
|
||||
last_heading_line: Option<u32>,
|
||||
}
|
||||
@@ -141,7 +141,7 @@ impl<'a> ParseState<'a> {
|
||||
}
|
||||
let before = self.pos;
|
||||
let block = self.parse_block();
|
||||
// Phase 12: file-scope tags get accumulated into metadata so
|
||||
// File-scope tags get accumulated into metadata so
|
||||
// callers can read page-level tag membership without walking
|
||||
// the AST. The TagNode itself stays in `children` so renderers
|
||||
// see it too.
|
||||
@@ -280,7 +280,7 @@ impl<'a> ParseState<'a> {
|
||||
let inline = parse_inline_seq(&self.toks[inline_start..self.pos]);
|
||||
self.advance();
|
||||
self.eat_newline();
|
||||
// Record for tag-scope resolution (Phase 12).
|
||||
// Record for tag-scope resolution.
|
||||
self.headings_seen += 1;
|
||||
self.last_heading_line = Some(span_end.line);
|
||||
return BlockNode::Heading(HeadingNode {
|
||||
|
||||
@@ -136,7 +136,7 @@ fn registry_can_parse_through_trait_object() {
|
||||
}
|
||||
|
||||
/// Compile-time check: SyntaxRegistry must be Send + Sync so the LSP server
|
||||
/// can share it across handler tasks (SPEC.md §6.3).
|
||||
/// can share it across handler tasks.
|
||||
#[test]
|
||||
fn registry_is_send_and_sync() {
|
||||
fn assert_send_sync<T: Send + Sync>() {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Phase 12 tag tests: lexer + parser + renderer behaviour on
|
||||
//! Tag tests: lexer + parser + renderer behaviour on
|
||||
//! `:tag:` lines, plus the scope-detection rules.
|
||||
|
||||
use nuwiki_core::ast::{BlockNode, TagScope};
|
||||
|
||||
Reference in New Issue
Block a user