Remove stale phase/spec references from code comments

Strip "Phase N", "SPEC §X.Y", and "v1.x" planning labels from comments
across the Rust crates and editor layers now that those tracking
artifacts are gone. Comments only — no logic, strings, or test names
touched.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-30 15:25:51 -03:00
parent 5135840f05
commit 21b485c91b
55 changed files with 199 additions and 210 deletions
+5 -5
View File
@@ -1,5 +1,5 @@
" autoload/nuwiki/commands.vim — Vim-side wrappers around the LSP " autoload/nuwiki/commands.vim — Vim-side wrappers around the LSP
" commands the server advertises (Phase 19). " commands the server advertises.
" "
" Lives in autoload/ so the cost is paid only when a `:Vimwiki*` / " Lives in autoload/ so the cost is paid only when a `:Vimwiki*` /
" `:Nuwiki*` command is actually invoked. Dispatches via vim-lsp's " `:Nuwiki*` command is actually invoked. Dispatches via vim-lsp's
@@ -634,9 +634,9 @@ function! nuwiki#commands#rename_file() abort
endif endif
endfunction endfunction
" ===== §13.1 deferred ===== " ===== Deferred =====
" §13.1 Cluster A — list rewriters. " Cluster A — list rewriters.
" `gl<Space>` — remove done items from the current list only. Passing the " `gl<Space>` — remove done items from the current list only. Passing the
" cursor position scopes the server to the contiguous list block under it. " cursor position scopes the server to the contiguous list block under it.
@@ -737,7 +737,7 @@ function! nuwiki#commands#list_toggle_or_add_checkbox() abort
let l:new = strpart(l:line, 0, l:end) . '[ ] ' . strpart(l:line, l:end) let l:new = strpart(l:line, 0, l:end) . '[ ] ' . strpart(l:line, l:end)
call setline('.', l:new) call setline('.', l:new)
endfunction endfunction
" §13.1 Cluster B — table rewriters. " Cluster B — table rewriters.
function! nuwiki#commands#table_insert(...) abort function! nuwiki#commands#table_insert(...) abort
let l:cols = a:0 >= 1 ? str2nr(a:1) : 3 let l:cols = a:0 >= 1 ? str2nr(a:1) : 3
@@ -815,7 +815,7 @@ function! nuwiki#commands#colorize(...) abort
\ . strpart(l:line, l:right - 1) \ . strpart(l:line, l:right - 1)
call setline('.', l:new) call setline('.', l:new)
endfunction endfunction
" §13.1 Cluster C — link helpers. " Cluster C — link helpers.
function! nuwiki#commands#paste_link() abort function! nuwiki#commands#paste_link() abort
let l:p = s:cursor_position() let l:p = s:cursor_position()
+1 -1
View File
@@ -1,6 +1,6 @@
" autoload/nuwiki/lsp.vim — Vim (non-Neovim) LSP client wiring. " autoload/nuwiki/lsp.vim — Vim (non-Neovim) LSP client wiring.
" "
" Spec §7.4 preference order: " Preference order:
" 1. vim-lsp (matoto/vim-lsp) " 1. vim-lsp (matoto/vim-lsp)
" 2. coc.nvim " 2. coc.nvim
" 3. Print an error if neither is available. " 3. Print an error if neither is available.
+1 -3
View File
@@ -1,6 +1,4 @@
//! Block-level AST nodes and their supporting types. //! Block-level AST nodes and their supporting types.
//!
//! See SPEC.md §6.4.
use super::inline::InlineNode; use super::inline::InlineNode;
use super::span::Span; use super::span::Span;
@@ -17,7 +15,7 @@ pub enum BlockNode {
DefinitionList(DefinitionListNode), DefinitionList(DefinitionListNode),
Table(TableNode), Table(TableNode),
Comment(CommentNode), 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 /// rule (file / heading / standalone) is captured in `TagScope` so
/// LSP handlers and renderers can treat the three differently. /// LSP handlers and renderers can treat the three differently.
Tag(TagNode), Tag(TagNode),
+1 -1
View File
@@ -1,6 +1,6 @@
//! Inline AST nodes. //! 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`. //! `Transclusion`, `RawUrl`) wrap structs defined in `super::link`.
use super::link::{ExternalLinkNode, RawUrlNode, TransclusionNode, WikiLinkNode}; use super::link::{ExternalLinkNode, RawUrlNode, TransclusionNode, WikiLinkNode};
+1 -1
View File
@@ -1,6 +1,6 @@
//! Link-related AST nodes and supporting types. //! 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 //! they live in their own module because the link model has enough surface
//! area (kinds, anchors, interwiki indirection) to warrant separation. //! area (kinds, anchors, interwiki indirection) to warrant separation.
+4 -5
View File
@@ -1,7 +1,7 @@
//! AST types for nuwiki documents. //! AST types for nuwiki documents.
//! //!
//! Types here are syntax-agnostic — both vimwiki and (eventually) markdown //! 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 block;
pub mod inline; pub mod inline;
@@ -28,7 +28,7 @@ pub use visit::{
walk_table, walk_table_row, Visitor, 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)] #[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DocumentNode { pub struct DocumentNode {
pub span: Span, pub span: Span,
@@ -37,8 +37,7 @@ pub struct DocumentNode {
} }
/// Document-level placeholders parsed from `%title` / `%nohtml` / `%template` /// Document-level placeholders parsed from `%title` / `%nohtml` / `%template`
/// / `%date` directives, plus the v1.1 file-level tag accumulator /// / `%date` directives, plus the file-level tag accumulator.
/// (SPEC §12.3). See SPEC.md §9.
/// ///
/// New fields are added at the bottom; consumers should construct with /// New fields are added at the bottom; consumers should construct with
/// `..Default::default()` to stay forward-compatible. /// `..Default::default()` to stay forward-compatible.
@@ -48,7 +47,7 @@ pub struct PageMetadata {
pub nohtml: bool, pub nohtml: bool,
pub template: Option<String>, pub template: Option<String>,
pub date: 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`. /// projection of the `BlockNode::Tag` nodes whose scope is `File`.
pub tags: Vec<String>, pub tags: Vec<String>,
} }
+1 -1
View File
@@ -1,6 +1,6 @@
//! Source positions and spans carried on every AST node. //! 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. //! within a line; `offset` is a byte offset from the start of the document.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
+2 -2
View File
@@ -5,10 +5,10 @@
//! Override any method to short-circuit or augment traversal — call the //! Override any method to short-circuit or augment traversal — call the
//! matching `walk_*` from your override to keep descending. //! 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 //! Read-only for now. A `VisitorMut` flavor can be added when transformations
//! become relevant (post-Phase 5). //! become relevant.
use super::block::{ use super::block::{
BlockNode, BlockquoteNode, CommentNode, DefinitionItemNode, DefinitionListNode, ErrorNode, BlockNode, BlockquoteNode, CommentNode, DefinitionItemNode, DefinitionListNode, ErrorNode,
+3 -3
View File
@@ -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 //! only needs `YYYY-MM-DD` parsing/formatting and ±1 day arithmetic, which
//! is small enough that the dependency cost outweighs the convenience. //! 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) 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 /// commands intentionally use UTC — local-time semantics depend on a
/// timezone DB we don't ship and would surprise users crossing DST /// timezone DB we don't ship and would surprise users crossing DST
/// boundaries. /// boundaries.
+1 -1
View File
@@ -1,7 +1,7 @@
//! Core parser, AST, and renderer for nuwiki. //! Core parser, AST, and renderer for nuwiki.
//! //!
//! This crate is editor-independent and must never depend on `nuwiki-lsp` or //! 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 ast;
pub mod date; pub mod date;
+5 -5
View File
@@ -3,12 +3,12 @@
//! Walks a `DocumentNode` and writes HTML5 fragments. The renderer is //! Walks a `DocumentNode` and writes HTML5 fragments. The renderer is
//! configured with a link resolver (callback that turns a `LinkTarget` into //! configured with a link resolver (callback that turns a `LinkTarget` into
//! a URL string) and, optionally, an enclosing template with substitution //! 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 //! Substitution model: `{{content}}` and `{{title}}` are computed from
//! the rendered body and the document's metadata; `with_var(k, v)` / //! the rendered body and the document's metadata; `with_var(k, v)` /
//! `with_vars(map)` add arbitrary key→value pairs (Phase 17 ships //! `with_vars(map)` add arbitrary key→value pairs (`{{date}}`,
//! `{{date}}`, `{{root_path}}`, `{{toc}}`). Order: `{{content}}` is //! `{{root_path}}`, `{{toc}}`). Order: `{{content}}` is
//! substituted first so a body that happens to contain a literal //! substituted first so a body that happens to contain a literal
//! `{{title}}` isn't itself rewritten in the next pass. //! `{{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 /// `color_dic`-style override: vimwiki colour-tag names → CSS
/// values (`"red"` / `"#ffe119"` / `"oklch(…)"`). Unspecified /// values (`"red"` / `"#ffe119"` / `"oklch(…)"`). Unspecified
/// names fall through to the default `class="color-<name>"` /// 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>, colors: HashMap<String, String>,
} }
@@ -662,7 +662,7 @@ fn table_spans(table: &TableNode) -> Vec<Vec<CellLayout>> {
// ===== Default link resolver ===== // ===== 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 /// wiki pages become `path.html`; interwiki links land in sibling
/// directories; diary entries land under `diary/`; file/local schemes /// directories; diary entries land under `diary/`; file/local schemes
/// use their literal path; anchor-only links collapse to a fragment. /// use their literal path; anchor-only links collapse to a fragment.
+1 -1
View File
@@ -1,6 +1,6 @@
//! Document renderers. //! 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 //! emits its representation into any `Write`. Errors propagate through
//! `io::Result` — there's no separate per-renderer error type so the trait //! `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). //! stays object-safe (`Box<dyn Renderer>` is useful for the LSP later).
+4 -6
View File
@@ -4,8 +4,6 @@
//! a `Lexer` produces a `TokenStream`, a `Parser` consumes it and produces a //! a `Lexer` produces a `TokenStream`, a `Parser` consumes it and produces a
//! `DocumentNode`. A `SyntaxPlugin` is the type-erased facade that the LSP //! `DocumentNode`. A `SyntaxPlugin` is the type-erased facade that the LSP
//! layer holds in a `SyntaxRegistry`. //! layer holds in a `SyntaxRegistry`.
//!
//! See SPEC.md §6.3 (interface) and §6.6 (lexer strategy).
pub mod registry; pub mod registry;
pub mod vimwiki; pub mod vimwiki;
@@ -15,7 +13,7 @@ pub use registry::SyntaxRegistry;
use crate::ast::DocumentNode; use crate::ast::DocumentNode;
/// Eager token buffer produced by a `Lexer`. The newtype keeps the door /// 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. /// callers.
#[derive(Debug, Clone, Default, PartialEq, Eq)] #[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TokenStream<T> { pub struct TokenStream<T> {
@@ -89,7 +87,7 @@ pub trait Lexer {
} }
/// Parser half of a syntax plugin. Consumes a `TokenStream` and produces a /// 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. /// becomes `BlockNode::Error(ErrorNode)`, never a hard failure.
pub trait Parser { pub trait Parser {
type Token; type Token;
@@ -104,13 +102,13 @@ pub trait Parser {
/// hold heterogeneous plugins behind a single trait object. /// hold heterogeneous plugins behind a single trait object.
/// ///
/// `Send + Sync` is required — the LSP server stores plugins behind an `Arc` /// `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 { pub trait SyntaxPlugin: Send + Sync {
fn id(&self) -> &str; fn id(&self) -> &str;
fn display_name(&self) -> &str; fn display_name(&self) -> &str;
/// File extensions associated with this syntax. Each entry includes the /// 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 file_extensions(&self) -> &[&str];
fn parse(&self, text: &str) -> DocumentNode; fn parse(&self, text: &str) -> DocumentNode;
+1 -1
View File
@@ -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 //! 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. //! hand a cheap clone to per-document tasks without re-locking the registry.
@@ -1,6 +1,6 @@
//! Vimwiki lexer. //! 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 //! - **Block pass:** scan the source line by line, recognise structural
//! constructs (headings, lists, tables, fences, comments, etc.), emit //! constructs (headings, lists, tables, fences, comments, etc.), emit
@@ -12,7 +12,7 @@
//! Both passes share one `Vec<VimwikiToken>` so the parser sees a single, //! Both passes share one `Vec<VimwikiToken>` so the parser sees a single,
//! ordered stream. The lexer is permissive: every delimiter is emitted; the //! ordered stream. The lexer is permissive: every delimiter is emitted; the
//! parser pairs them and falls back to literal text on mismatches. Spans are //! 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 //! Multi-line constructs (`{{{ }}}`, `{{$ }}$`, `%%+ +%%`) flip a
//! `BlockMode` so subsequent lines are treated as raw content until the //! `BlockMode` so subsequent lines are treated as raw content until the
@@ -97,13 +97,13 @@ pub enum VimwikiTokenKind {
CommentMultiClose, CommentMultiClose,
CommentMultiLine(String), CommentMultiLine(String),
// ----- Tags (SPEC §12.3) ----- // ----- Tags -----
/// `:tag1:tag2:` — colon-delimited tag line. The lexer treats this as /// `:tag1:tag2:` — colon-delimited tag line. The lexer treats this as
/// block-level (must be the entire trimmed content of the line). /// block-level (must be the entire trimmed content of the line).
/// Scope (file / heading / standalone) is the parser's job. /// Scope (file / heading / standalone) is the parser's job.
Tag(Vec<String>), Tag(Vec<String>),
// ----- Page placeholders (SPEC §9) ----- // ----- Page placeholders -----
PlaceholderTitle(Option<String>), PlaceholderTitle(Option<String>),
PlaceholderNohtml, PlaceholderNohtml,
PlaceholderTemplate(Option<String>), PlaceholderTemplate(Option<String>),
@@ -129,7 +129,7 @@ pub enum VimwikiTokenKind {
TransclusionSep, TransclusionSep,
RawUrl(String), RawUrl(String),
/// Lex error — never aborts the whole document (SPEC §6.7). /// Lex error — never aborts the whole document.
Error(String), Error(String),
} }
@@ -481,7 +481,7 @@ impl<'src> LexState<'src> {
if trimmed.len() < 4 || !trimmed.bytes().all(|b| b == b'-') { if trimmed.len() < 4 || !trimmed.bytes().all(|b| b == b'-') {
return false; 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)); let span = Span::new(self.line_start_pos(), self.line_end_pos(line));
self.push(VimwikiTokenKind::HorizontalRule, span); self.push(VimwikiTokenKind::HorizontalRule, span);
self.emit_newline(line); self.emit_newline(line);
+1 -1
View File
@@ -10,7 +10,7 @@ use crate::ast::DocumentNode;
use crate::syntax::{Lexer, Parser, SyntaxPlugin}; use crate::syntax::{Lexer, Parser, SyntaxPlugin};
/// SyntaxPlugin facade: chains `VimwikiLexer` + `VimwikiParser` so the /// 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)] #[derive(Debug, Default, Clone)]
pub struct VimwikiSyntax; pub struct VimwikiSyntax;
@@ -1,6 +1,6 @@
//! Vimwiki parser. //! 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 //! Resilient: never aborts the document. Anything the dispatcher can't
//! claim becomes a `BlockNode::Error(ErrorNode)` so progress is //! claim becomes a `BlockNode::Error(ErrorNode)` so progress is
//! guaranteed. //! guaranteed.
@@ -28,7 +28,7 @@
//! //!
//! `[[ ]]` payloads are inspected to choose between `WikiLinkNode` //! `[[ ]]` payloads are inspected to choose between `WikiLinkNode`
//! (the default) and `ExternalLinkNode` (when the target is a URL). The //! (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, //! `//` is filesystem-absolute, `wiki<N>:` / `wn.<Name>:` are interwiki,
//! `diary:` / `file:` / `local:` are their own kinds, and a trailing `/` //! `diary:` / `file:` / `local:` are their own kinds, and a trailing `/`
//! marks a directory link. //! marks a directory link.
@@ -69,10 +69,10 @@ impl Parser for VimwikiParser {
struct ParseState<'a> { struct ParseState<'a> {
toks: &'a [VimwikiToken], toks: &'a [VimwikiToken],
pos: usize, 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 - 1` is the most recent heading's index.
headings_seen: usize, 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. /// `tag_line - last_heading_line ∈ {1, 2}` is a header-scope tag.
last_heading_line: Option<u32>, last_heading_line: Option<u32>,
} }
@@ -141,7 +141,7 @@ impl<'a> ParseState<'a> {
} }
let before = self.pos; let before = self.pos;
let block = self.parse_block(); 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 // callers can read page-level tag membership without walking
// the AST. The TagNode itself stays in `children` so renderers // the AST. The TagNode itself stays in `children` so renderers
// see it too. // see it too.
@@ -280,7 +280,7 @@ impl<'a> ParseState<'a> {
let inline = parse_inline_seq(&self.toks[inline_start..self.pos]); let inline = parse_inline_seq(&self.toks[inline_start..self.pos]);
self.advance(); self.advance();
self.eat_newline(); self.eat_newline();
// Record for tag-scope resolution (Phase 12). // Record for tag-scope resolution.
self.headings_seen += 1; self.headings_seen += 1;
self.last_heading_line = Some(span_end.line); self.last_heading_line = Some(span_end.line);
return BlockNode::Heading(HeadingNode { return BlockNode::Heading(HeadingNode {
+1 -1
View File
@@ -136,7 +136,7 @@ fn registry_can_parse_through_trait_object() {
} }
/// Compile-time check: SyntaxRegistry must be Send + Sync so the LSP server /// 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] #[test]
fn registry_is_send_and_sync() { fn registry_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {} fn assert_send_sync<T: Send + Sync>() {}
+1 -1
View File
@@ -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. //! `:tag:` lines, plus the scope-detection rules.
use nuwiki_core::ast::{BlockNode, TagScope}; use nuwiki_core::ast::{BlockNode, TagScope};
+17 -17
View File
@@ -1,10 +1,10 @@
//! `workspace/executeCommand` dispatcher. //! `workspace/executeCommand` dispatcher.
//! //!
//! Phase 13 introduced the router + `nuwiki.file.delete`. Phase 14 added //! The router handles `nuwiki.file.delete`, the cheap surgical edits —
//! the cheap surgical edits — checkbox toggle/cycle/reject, heading //! checkbox toggle/cycle/reject, heading promote/demote, "next task"
//! promote/demote, "next task" navigation. Phase 15 layers on the //! navigation — and the generation + workspace-query commands:
//! generation + workspace-query commands: `toc.generate`, //! `toc.generate`, `links.generate`, `workspace.checkLinks`,
//! `links.generate`, `workspace.checkLinks`, `workspace.findOrphans`. //! `workspace.findOrphans`.
//! //!
//! Every command resolves to either a `WorkspaceEdit` (the server then //! Every command resolves to either a `WorkspaceEdit` (the server then
//! calls `apply_edit`) or a JSON `Value` (returned to the client) — see //! calls `apply_edit`) or a JSON `Value` (returned to the client) — see
@@ -663,7 +663,7 @@ fn tags_rebuild(backend: &Backend, args: Vec<Value>) -> Result<Option<Value>, St
}))) })))
} }
// ===== Phase 18: multi-wiki picker commands ===== // ===== multi-wiki picker commands =====
fn wiki_list_all(backend: &Backend) -> Result<Option<Value>, String> { fn wiki_list_all(backend: &Backend) -> Result<Option<Value>, String> {
let wikis = backend.wikis_snapshot(); let wikis = backend.wikis_snapshot();
@@ -821,7 +821,7 @@ fn wiki_goto_page(backend: &Backend, args: Vec<Value>) -> Result<Option<Value>,
}))) })))
} }
// ===== §13.1 Cluster A: list rewriters ===== // ===== Cluster A: list rewriters =====
fn list_remove_done(backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceEdit>, String> { fn list_remove_done(backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceEdit>, String> {
let p = parse_optional_uri_arg(args.clone())?; let p = parse_optional_uri_arg(args.clone())?;
@@ -954,7 +954,7 @@ fn list_change_level(backend: &Backend, args: Vec<Value>) -> Result<Option<Works
)) ))
} }
// ===== §13.1 Cluster B: table rewriters ===== // ===== Cluster B: table rewriters =====
fn table_insert(_backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceEdit>, String> { fn table_insert(_backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceEdit>, String> {
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -1030,7 +1030,7 @@ fn table_move_column(backend: &Backend, args: Vec<Value>) -> Result<Option<Works
)) ))
} }
// ===== Phase 17: HTML export commands ===== // ===== HTML export commands =====
fn export_current( fn export_current(
backend: &Backend, backend: &Backend,
@@ -1671,7 +1671,7 @@ pub mod ops {
None None
} }
// ===== Phase 15: TOC + Links generation, workspace queries ===== // ===== TOC + Links generation, workspace queries =====
use serde::Serialize; use serde::Serialize;
@@ -1997,7 +1997,7 @@ pub mod ops {
/// on the diagnostics module's internals. /// on the diagnostics module's internals.
pub use crate::diagnostics::collect_wiki_links; pub use crate::diagnostics::collect_wiki_links;
// ===== §13.1 Cluster A: list rewriters ===== // ===== Cluster A: list rewriters =====
use crate::edits::text_edit_delete; use crate::edits::text_edit_delete;
use nuwiki_core::ast::ListSymbol; use nuwiki_core::ast::ListSymbol;
@@ -2544,7 +2544,7 @@ pub mod ops {
Some((span, marker_str, i)) Some((span, marker_str, i))
} }
// ===== §13.1 Cluster B: table rewriters ===== // ===== Cluster B: table rewriters =====
use nuwiki_core::ast::{TableNode, TableRowNode}; use nuwiki_core::ast::{TableNode, TableRowNode};
@@ -2802,7 +2802,7 @@ pub mod ops {
Some(out) Some(out)
} }
// ===== Phase 16: diary helpers ===== // ===== diary helpers =====
use crate::edits::op_create; use crate::edits::op_create;
@@ -2856,10 +2856,10 @@ pub mod ops {
b.build() b.build()
} }
// ===== Tag ops (Phase 12 commands) ===== // ===== Tag ops =====
// //
// SPEC §12.3 calls for three tag-driven commands beyond the indexing // Three tag-driven commands beyond the indexing itself.
// that Phase 12 already shipped. `tag_hits` powers `nuwiki.tags.search`; // `tag_hits` powers `nuwiki.tags.search`;
// `tag_links_edit` powers `nuwiki.tags.generateLinks`; the rebuild // `tag_links_edit` powers `nuwiki.tags.generateLinks`; the rebuild
// command is a direct re-walk of the wiki root in the dispatcher. // command is a direct re-walk of the wiki root in the dispatcher.
@@ -3048,7 +3048,7 @@ pub mod ops {
} }
} }
/// Disk-touching helpers for the Phase 17 `nuwiki.export.*` commands. /// Disk-touching helpers for the `nuwiki.export.*` commands.
/// Kept out of `ops` because everything here is side-effecting — pure /// Kept out of `ops` because everything here is side-effecting — pure
/// rendering / templating lives in [`crate::export`]. /// rendering / templating lives in [`crate::export`].
pub mod export_ops { pub mod export_ops {
+17 -19
View File
@@ -1,14 +1,13 @@
//! Server-side runtime config. //! Server-side runtime config.
//! //!
//! Populated from `InitializeParams.initialization_options` at boot and //! Populated from `InitializeParams.initialization_options` at boot and
//! refreshed via `workspace/didChangeConfiguration` (P18). v1.0 single-wiki //! refreshed via `workspace/didChangeConfiguration`. Legacy single-wiki
//! shapes are accepted and desugared into the v1.1 `wikis = [...]` form so //! shapes are accepted and desugared into the `wikis = [...]` form so
//! existing setups keep working (P16). //! existing setups keep working.
//! //!
//! Only the fields actually consumed in Phase 11 land here. Phases 1319 //! `WikiConfig` (diary path, html path, template options, …) and the
//! extend `WikiConfig` (diary path, html path, template options, …) and //! top-level `Config` (diagnostic severity, mappings opt-in, …) carry the
//! the top-level `Config` (diagnostic severity, mappings opt-in, …) as //! fields actually consumed by the server.
//! they need them.
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -39,9 +38,8 @@ pub struct WikiConfig {
/// The full path is `<root>/<diary_rel_path>/<diary_index>.wiki`. /// The full path is `<root>/<diary_rel_path>/<diary_index>.wiki`.
pub diary_index: String, pub diary_index: String,
/// `daily`/`weekly`/`monthly`/`yearly`. Picks the date format the /// `daily`/`weekly`/`monthly`/`yearly`. Picks the date format the
/// diary commands use. v1.1 ships `daily`; the others fall through /// diary commands use. `daily` is supported; the others fall through
/// to vimwiki-style names but the commands are no-ops until /// to vimwiki-style names but the commands are no-ops for them.
/// Cluster 4 lands.
pub diary_frequency: String, pub diary_frequency: String,
/// First day of the week (`monday`..`sunday`). Used by weekly /// First day of the week (`monday`..`sunday`). Used by weekly
/// diary entry naming. /// diary entry naming.
@@ -74,7 +72,7 @@ pub struct WikiConfig {
pub nested_syntaxes: std::collections::HashMap<String, String>, pub nested_syntaxes: std::collections::HashMap<String, String>,
/// Rebuild the page's TOC on save when set. /// Rebuild the page's TOC on save when set.
pub auto_toc: bool, pub auto_toc: bool,
/// Phase 17 HTML export options. See SPEC §12.8. /// HTML export options.
pub html: HtmlConfig, pub html: HtmlConfig,
} }
@@ -104,7 +102,7 @@ pub struct HtmlConfig {
/// to skip during `allToHtml*`. /// to skip during `allToHtml*`.
pub exclude_files: Vec<String>, pub exclude_files: Vec<String>,
/// `color_dic`: vimwiki colour-tag name → CSS value mapping used /// `color_dic`: vimwiki colour-tag name → CSS value mapping used
/// by the HTML renderer (SPEC §12.11). Empty by default; the /// by the HTML renderer. Empty by default; the
/// renderer falls back to `class="color-<name>"` when a name /// renderer falls back to `class="color-<name>"` when a name
/// isn't in the dict. /// isn't in the dict.
pub color_dic: std::collections::HashMap<String, String>, pub color_dic: std::collections::HashMap<String, String>,
@@ -130,7 +128,7 @@ impl Default for HtmlConfig {
impl HtmlConfig { impl HtmlConfig {
/// Synthesise default paths under `root` when the user hasn't /// Synthesise default paths under `root` when the user hasn't
/// specified explicit ones. `html_path` defaults to `<root>/_html` /// specified explicit ones. `html_path` defaults to `<root>/_html`
/// and `template_path` to `<root>/_templates` per SPEC §12.8. /// and `template_path` to `<root>/_templates`.
pub fn from_root(root: &Path) -> Self { pub fn from_root(root: &Path) -> Self {
Self { Self {
html_path: root.join("_html"), html_path: root.join("_html"),
@@ -284,7 +282,7 @@ fn default_links_space_char() -> String {
" ".to_string() " ".to_string()
} }
/// Fill in the v1.1 per-wiki keys that none of the constructors care /// Fill in the per-wiki keys that none of the constructors care
/// about. Centralising the defaults here keeps `empty()`, `from_root()`, /// about. Centralising the defaults here keeps `empty()`, `from_root()`,
/// the legacy single-wiki path, and `From<RawWiki>` in sync. /// the legacy single-wiki path, and `From<RawWiki>` in sync.
fn wiki_defaults() -> WikiDefaults { fn wiki_defaults() -> WikiDefaults {
@@ -329,9 +327,9 @@ impl Config {
/// Build a `Config` from `InitializeParams`. /// Build a `Config` from `InitializeParams`.
/// ///
/// Priority for the wikis list: /// Priority for the wikis list:
/// 1. `initialization_options.wikis = [...]` (v1.1 shape) /// 1. `initialization_options.wikis = [...]` (multi-wiki shape)
/// 2. `initialization_options.wiki_root + file_extension + syntax` /// 2. `initialization_options.wiki_root + file_extension + syntax`
/// (v1.0 single-wiki shape, P16) /// (legacy single-wiki shape)
/// 3. `workspace_folders[0]` if present /// 3. `workspace_folders[0]` if present
/// 4. deprecated `root_uri` if present /// 4. deprecated `root_uri` if present
/// 5. empty list (server stays alive but won't index anything) /// 5. empty list (server stays alive but won't index anything)
@@ -445,7 +443,7 @@ mod opt_bool_or_int {
#[serde(default, rename_all = "snake_case")] #[serde(default, rename_all = "snake_case")]
struct InitOptions { struct InitOptions {
wikis: Option<Vec<RawWiki>>, wikis: Option<Vec<RawWiki>>,
// v1.0 single-wiki compat fields // legacy single-wiki compat fields
wiki_root: Option<String>, wiki_root: Option<String>,
file_extension: Option<String>, file_extension: Option<String>,
syntax: Option<String>, syntax: Option<String>,
@@ -466,7 +464,7 @@ struct RawWiki {
diary_rel_path: Option<String>, diary_rel_path: Option<String>,
#[serde(default)] #[serde(default)]
diary_index: Option<String>, diary_index: Option<String>,
// Phase 17 HTML keys — all optional, fall back to per-root defaults. // HTML keys — all optional, fall back to per-root defaults.
#[serde(default)] #[serde(default)]
html_path: Option<String>, html_path: Option<String>,
#[serde(default)] #[serde(default)]
@@ -487,7 +485,7 @@ struct RawWiki {
exclude_files: Option<Vec<String>>, exclude_files: Option<Vec<String>>,
#[serde(default)] #[serde(default)]
color_dic: Option<std::collections::HashMap<String, String>>, color_dic: Option<std::collections::HashMap<String, String>>,
// v1.1 per-wiki keys mirroring vimwiki globals. All optional so // per-wiki keys mirroring vimwiki globals. All optional so
// existing single-wiki configs migrate without touching anything. // existing single-wiki configs migrate without touching anything.
#[serde(default)] #[serde(default)]
index: Option<String>, index: Option<String>,
+3 -4
View File
@@ -1,6 +1,6 @@
//! Diagnostic sources beyond parse-time `ErrorNode`s. //! Diagnostic sources beyond parse-time `ErrorNode`s.
//! //!
//! Phase 15 lands the `nuwiki.link` source broken-link warnings. Walks //! The `nuwiki.link` source emits broken-link warnings. Walks
//! every `WikiLinkNode` in the AST and emits one diagnostic per: //! every `WikiLinkNode` in the AST and emits one diagnostic per:
//! - `Wiki` target that doesn't resolve to a page in the workspace index, //! - `Wiki` target that doesn't resolve to a page in the workspace index,
//! - `Wiki`/`AnchorOnly` target with an anchor that matches neither a //! - `Wiki`/`AnchorOnly` target with an anchor that matches neither a
@@ -8,7 +8,7 @@
//! - `File`/`Local` target whose resolved path doesn't exist on disk. //! - `File`/`Local` target whose resolved path doesn't exist on disk.
//! //!
//! Interwiki, Diary, Raw, and external links are not diagnosed — //! Interwiki, Diary, Raw, and external links are not diagnosed —
//! interwiki resolution is Phase 18's job, diary is Phase 16's, and we //! interwiki resolution and diary handling live elsewhere, and we
//! don't fetch URLs. //! don't fetch URLs.
use std::path::PathBuf; use std::path::PathBuf;
@@ -332,8 +332,7 @@ fn resolve_file_path(
index.root.as_ref().map(|r| r.join(&candidate)) index.root.as_ref().map(|r| r.join(&candidate))
} }
/// `nuwiki.link` diagnostics for a single document. Phase 11 left this as /// `nuwiki.link` diagnostics for a single document.
/// a stub; Phase 15 fills it in.
pub fn link_health( pub fn link_health(
ast: &DocumentNode, ast: &DocumentNode,
text: &str, text: &str,
+1 -1
View File
@@ -1,4 +1,4 @@
//! Diary subsystem (Phase 16) — pure helpers used by the //! Diary subsystem — pure helpers used by the
//! `nuwiki.diary.*` `executeCommand` handlers. //! `nuwiki.diary.*` `executeCommand` handlers.
//! //!
//! All path/URI math lives here so the command dispatcher stays a thin //! All path/URI math lives here so the command dispatcher stays a thin
+1 -1
View File
@@ -1,6 +1,6 @@
//! Helpers for building `WorkspaceEdit`s. //! Helpers for building `WorkspaceEdit`s.
//! //!
//! Phase 13+ (rename, list/table edits, link/TOC generation) all return //! Rename, list/table edits, and link/TOC generation all return
//! `WorkspaceEdit`. The builder centralises the byte-offset → LSP-position //! `WorkspaceEdit`. The builder centralises the byte-offset → LSP-position
//! conversion (UTF-8 or UTF-16, matching whatever was negotiated in //! conversion (UTF-8 or UTF-16, matching whatever was negotiated in
//! `initialize`) so each call site doesn't have to redo it. //! `initialize`) so each call site doesn't have to redo it.
+1 -1
View File
@@ -1,4 +1,4 @@
//! HTML export — pure helpers shared by the Phase 17 `nuwiki.export.*` //! HTML export — pure helpers shared by the `nuwiki.export.*`
//! command handlers. Side-effecting work (reading the template from //! command handlers. Side-effecting work (reading the template from
//! disk, writing the rendered HTML, copying CSS) lives in `commands.rs` //! disk, writing the rendered HTML, copying CSS) lives in `commands.rs`
//! since the dispatcher is the one with a `&Backend` and async context. //! since the dispatcher is the one with a `&Backend` and async context.
+1 -1
View File
@@ -1,4 +1,4 @@
//! `textDocument/foldingRange` provider — SPEC §12.10 / P14. //! `textDocument/foldingRange` provider.
//! //!
//! Strategy: one fold per heading (line → line before the next heading //! Strategy: one fold per heading (line → line before the next heading
//! of same-or-higher level) plus one fold per top-level list block. //! of same-or-higher level) plus one fold per top-level list block.
+10 -10
View File
@@ -1,7 +1,7 @@
//! Workspace index — the in-memory model of every `.wiki` page nuwiki //! Workspace index — the in-memory model of every `.wiki` page nuwiki
//! has seen, used to power Phase 8 nav features. //! has seen, used to power nav features.
//! //!
//! Per P8 (SPEC §11), the initial scan runs as a background tokio task so //! The initial scan runs as a background tokio task so
//! the server stays responsive on startup. Per-document updates land here //! the server stays responsive on startup. Per-document updates land here
//! synchronously via `upsert` whenever the LSP backend re-parses on //! synchronously via `upsert` whenever the LSP backend re-parses on
//! `didOpen` / `didChange`. //! `didOpen` / `didChange`.
@@ -29,10 +29,10 @@ pub struct IndexedPage {
pub title: Option<String>, pub title: Option<String>,
pub headings: Vec<HeadingInfo>, pub headings: Vec<HeadingInfo>,
pub outgoing: Vec<OutgoingLink>, pub outgoing: Vec<OutgoingLink>,
/// Phase 12: every `TagNode` on the page. `tags_by_name` on the /// Every `TagNode` on the page. `tags_by_name` on the
/// containing `WorkspaceIndex` is the reverse map. /// containing `WorkspaceIndex` is the reverse map.
pub tags: Vec<TagInfo>, pub tags: Vec<TagInfo>,
/// Phase 16: `Some(date)` when this page is a *daily* diary entry — /// `Some(date)` when this page is a *daily* diary entry —
/// stem parses as `YYYY-MM-DD`. Equivalent to /// stem parses as `YYYY-MM-DD`. Equivalent to
/// `diary_period.and_then(|p| if let DiaryPeriod::Day(d) = p { Some(d) } else { None })`. /// `diary_period.and_then(|p| if let DiaryPeriod::Day(d) = p { Some(d) } else { None })`.
/// Kept as its own field for back-compat with prev/next/list callers /// Kept as its own field for back-compat with prev/next/list callers
@@ -97,7 +97,7 @@ pub struct Backlink {
} }
/// A tag's location, used by `WorkspaceIndex::tags_for` for cross-page /// A tag's location, used by `WorkspaceIndex::tags_for` for cross-page
/// `[[Page#tag]]` resolution and `nuwiki.tags.search` (Phase 13). /// `[[Page#tag]]` resolution and `nuwiki.tags.search`.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TagOccurrence { pub struct TagOccurrence {
pub uri: Url, pub uri: Url,
@@ -108,7 +108,7 @@ pub struct TagOccurrence {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct WorkspaceIndex { pub struct WorkspaceIndex {
pub root: Option<PathBuf>, pub root: Option<PathBuf>,
/// Phase 16: subdir relative to `root` that holds diary entries. /// Subdir relative to `root` that holds diary entries.
/// Mirrors `WikiConfig::diary_rel_path`; stored here so `upsert` can /// Mirrors `WikiConfig::diary_rel_path`; stored here so `upsert` can
/// classify each indexed URI without a back-reference to the config. /// classify each indexed URI without a back-reference to the config.
pub diary_rel_path: Option<String>, pub diary_rel_path: Option<String>,
@@ -119,9 +119,9 @@ pub struct WorkspaceIndex {
pub pages_by_uri: HashMap<Url, IndexedPage>, pub pages_by_uri: HashMap<Url, IndexedPage>,
pub pages_by_name: HashMap<String, Url>, pub pages_by_name: HashMap<String, Url>,
pub backlinks: HashMap<String, Vec<Backlink>>, pub backlinks: HashMap<String, Vec<Backlink>>,
/// Phase 12: tag name → every occurrence across the workspace. Used by /// Tag name → every occurrence across the workspace. Used by
/// the v1.1 nav handlers (tag-as-anchor `definition`, `workspace/symbol` /// the nav handlers (tag-as-anchor `definition`, `workspace/symbol`
/// inclusion) and the Phase 13 `nuwiki.tags.search` command. /// inclusion) and the `nuwiki.tags.search` command.
pub tags_by_name: HashMap<String, Vec<TagOccurrence>>, pub tags_by_name: HashMap<String, Vec<TagOccurrence>>,
} }
@@ -187,7 +187,7 @@ impl WorkspaceIndex {
} }
} }
// Phase 12: maintain the reverse tag map alongside the per-page list. // Maintain the reverse tag map alongside the per-page list.
for tag in &page.tags { for tag in &page.tags {
self.tags_by_name self.tags_by_name
.entry(tag.name.clone()) .entry(tag.name.clone())
+26 -27
View File
@@ -1,8 +1,7 @@
//! LSP protocol bridge for nuwiki. //! LSP protocol bridge for nuwiki.
//! //!
//! `tower-lsp` server that maintains a `DocumentStore` (SPEC §6.10) plus a //! `tower-lsp` server that maintains a `DocumentStore` plus a
//! workspace-wide index (SPEC §6.10 + P8) and exposes every LSP method //! workspace-wide index and exposes every LSP method:
//! through Phase 8:
//! //!
//! - `initialize` / `initialized` / `shutdown` //! - `initialize` / `initialized` / `shutdown`
//! - `textDocument/didOpen`, `didChange` (full sync), `didClose` //! - `textDocument/didOpen`, `didChange` (full sync), `didClose`
@@ -14,12 +13,12 @@
//! - `workspace/symbol` — search across indexed headings //! - `workspace/symbol` — search across indexed headings
//! //!
//! Position encoding is negotiated as UTF-8 when the client supports LSP //! Position encoding is negotiated as UTF-8 when the client supports LSP
//! 3.17+ encodings (SPEC §6.11). When the client only supports the legacy //! 3.17+ encodings. When the client only supports the legacy
//! UTF-16 default, positions get translated through a per-line lookup. //! UTF-16 default, positions get translated through a per-line lookup.
//! //!
//! Re-parses are full per change (incremental parsing deferred post-v1). //! Re-parses are full per change (no incremental parsing).
//! The workspace scan runs in a background tokio task spawned from //! The workspace scan runs in a background tokio task spawned from
//! `initialize` (P8) with `window/workDoneProgress` begin/end events. //! `initialize` with `window/workDoneProgress` begin/end events.
pub mod commands; pub mod commands;
pub mod config; pub mod config;
@@ -111,8 +110,8 @@ pub async fn run_stdio() -> io::Result<()> {
struct DocumentState { struct DocumentState {
text: String, text: String,
ast: DocumentNode, ast: DocumentNode,
/// Last version we observed from the client. Held per SPEC §6.10 even /// Last version we observed from the client. Held even
/// though the foundation phase doesn't yet need to read it back. /// though nothing reads it back yet.
#[allow(dead_code)] #[allow(dead_code)]
version: i32, version: i32,
} }
@@ -124,8 +123,8 @@ struct Backend {
/// True after `initialize` if the client opted into UTF-8 encoding. /// True after `initialize` if the client opted into UTF-8 encoding.
/// Otherwise we keep the LSP 3.16 default of UTF-16. /// Otherwise we keep the LSP 3.16 default of UTF-16.
use_utf8: Arc<AtomicBool>, use_utf8: Arc<AtomicBool>,
/// Per-wiki state (P11). Vector holds one entry until Phase 18 /// Per-wiki state. Vector holds one entry until multi-wiki
/// (multi-wiki) lets users add more. /// support lets users add more.
wikis: Arc<RwLock<Vec<Wiki>>>, wikis: Arc<RwLock<Vec<Wiki>>>,
/// Server config received via `initializationOptions` and refreshed /// Server config received via `initializationOptions` and refreshed
/// via `workspace/didChangeConfiguration` (P18). /// via `workspace/didChangeConfiguration` (P18).
@@ -151,7 +150,7 @@ impl Backend {
wikis.iter().find(|w| w.id == id).cloned() wikis.iter().find(|w| w.id == id).cloned()
} }
/// Used by Phase 15 workspace-level commands (`checkLinks`, `findOrphans`) /// Used by workspace-level commands (`checkLinks`, `findOrphans`)
/// when no URI is supplied — they fall back to the first registered wiki. /// when no URI is supplied — they fall back to the first registered wiki.
fn default_wiki(&self) -> Option<Wiki> { fn default_wiki(&self) -> Option<Wiki> {
let wikis = self.wikis.read().ok()?; let wikis = self.wikis.read().ok()?;
@@ -290,7 +289,7 @@ impl Backend {
/// Return live state if `uri` is open in the editor, otherwise read /// Return live state if `uri` is open in the editor, otherwise read
/// the file from disk and re-parse. The closed-doc path is used by /// the file from disk and re-parse. The closed-doc path is used by
/// cross-document operations (Phase 13 `workspace/rename` and link /// cross-document operations (`workspace/rename` and link
/// health) that need accurate spans + text without trusting cached /// health) that need accurate spans + text without trusting cached
/// `WorkspaceIndex` data. /// `WorkspaceIndex` data.
pub async fn read_or_open(&self, uri: &Url) -> io::Result<DocSnapshot> { pub async fn read_or_open(&self, uri: &Url) -> io::Result<DocSnapshot> {
@@ -318,7 +317,7 @@ impl Backend {
} }
async fn update_document(&self, uri: Url, text: String, version: i32) { async fn update_document(&self, uri: Url, text: String, version: i32) {
// For Phase 6 we always treat unknown buffers as vimwiki — if/when // We always treat unknown buffers as vimwiki — if/when
// multi-syntax dispatch lands the pick should follow the URI's ext. // multi-syntax dispatch lands the pick should follow the URI's ext.
let plugin = match self.registry.get("vimwiki") { let plugin = match self.registry.get("vimwiki") {
Some(p) => p, Some(p) => p,
@@ -373,7 +372,7 @@ impl Backend {
#[tower_lsp::async_trait] #[tower_lsp::async_trait]
impl LanguageServer for Backend { impl LanguageServer for Backend {
async fn initialize(&self, params: InitializeParams) -> LspResult<InitializeResult> { async fn initialize(&self, params: InitializeParams) -> LspResult<InitializeResult> {
// SPEC §6.11: prefer UTF-8 if the client advertises support. // Prefer UTF-8 if the client advertises support.
let supports_utf8 = params let supports_utf8 = params
.capabilities .capabilities
.general .general
@@ -404,7 +403,7 @@ impl LanguageServer for Backend {
change: Some(TextDocumentSyncKind::FULL), change: Some(TextDocumentSyncKind::FULL),
will_save: None, will_save: None,
will_save_wait_until: None, will_save_wait_until: None,
// Phase 17 `auto_export` listens on `did_save`. Asking // `auto_export` listens on `did_save`. Asking
// for `include_text: false` since the document store // for `include_text: false` since the document store
// already mirrors the live buffer. // already mirrors the live buffer.
save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions { save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions {
@@ -502,9 +501,9 @@ impl LanguageServer for Backend {
async fn did_change_configuration(&self, params: DidChangeConfigurationParams) { async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
// Apply the new settings to `Config`. Re-indexing on root changes // Apply the new settings to `Config`. Re-indexing on root changes
// is out of scope for Phase 11; for now we just accept the new // is out of scope here; for now we just accept the new
// values so later commands see the right diagnostic settings etc. // values so later commands see the right diagnostic settings etc.
// Phase 18 (multi-wiki) revisits this with proper rebuild semantics. // Multi-wiki support revisits this with proper rebuild semantics.
let mut cfg = self.config.write().expect("config lock poisoned"); let mut cfg = self.config.write().expect("config lock poisoned");
cfg.apply_change(&params.settings); cfg.apply_change(&params.settings);
} }
@@ -530,7 +529,7 @@ impl LanguageServer for Backend {
} }
async fn did_rename_files(&self, params: RenameFilesParams) { async fn did_rename_files(&self, params: RenameFilesParams) {
// Phase 13 advertises this capability so the editor pushes // We advertise this capability so the editor pushes
// out-of-band renames (file explorer drags, `:VimwikiRenameFile` // out-of-band renames (file explorer drags, `:VimwikiRenameFile`
// when the client opts to surface the new URI). Update the index // when the client opts to surface the new URI). Update the index
// so backlinks/headings track the new URI without waiting for // so backlinks/headings track the new URI without waiting for
@@ -576,7 +575,7 @@ impl LanguageServer for Backend {
} }
async fn did_save(&self, params: DidSaveTextDocumentParams) { async fn did_save(&self, params: DidSaveTextDocumentParams) {
// Phase 17: when `auto_export` is set on the resolved wiki, run // When `auto_export` is set on the resolved wiki, run
// the equivalent of `nuwiki.export.currentToHtml` after the file // the equivalent of `nuwiki.export.currentToHtml` after the file
// hits disk. Best-effort — failures are logged but do not bubble // hits disk. Best-effort — failures are logged but do not bubble
// to the client. Skips pages with the `%nohtml` directive. // to the client. Skips pages with the `%nohtml` directive.
@@ -934,7 +933,7 @@ impl LanguageServer for Backend {
}); });
} }
} }
// Phase 12: surface tags too, named as `:tag:` so editors // Surface tags too, named as `:tag:` so editors
// can distinguish them from headings at a glance. // can distinguish them from headings at a glance.
for t in &page.tags { for t in &page.tags {
if q.is_empty() || t.name.to_lowercase().contains(&q) { if q.is_empty() || t.name.to_lowercase().contains(&q) {
@@ -1121,9 +1120,9 @@ fn utf16_column(text: &str, line: u32, byte_col: u32) -> u32 {
.sum::<usize>() as u32 .sum::<usize>() as u32
} }
/// Backward-compat wrapper kept for v1.0 test surface. New call sites /// Backward-compat wrapper kept for the legacy test surface. New call sites
/// should use `collect_diagnostics` so they can opt into link-health /// should use `collect_diagnostics` so they can opt into link-health
/// (Phase 15) and other sources. /// and other sources.
pub fn ast_diagnostics(ast: &DocumentNode, text: &str, utf8: bool) -> Vec<Diagnostic> { pub fn ast_diagnostics(ast: &DocumentNode, text: &str, utf8: bool) -> Vec<Diagnostic> {
collect_diagnostics( collect_diagnostics(
ast, ast,
@@ -1136,12 +1135,12 @@ pub fn ast_diagnostics(ast: &DocumentNode, text: &str, utf8: bool) -> Vec<Diagno
) )
} }
/// Composable diagnostic collector (P11 §12.2.4). /// Composable diagnostic collector.
/// ///
/// Sources, each gated by `cfg`: /// Sources, each gated by `cfg`:
/// ///
/// 1. Parse errors (always on). /// 1. Parse errors (always on).
/// 2. Broken-link warnings (Phase 15) — emitted when an index, source /// 2. Broken-link warnings — emitted when an index, source
/// page name, and `link_severity != Off` are all available. /// page name, and `link_severity != Off` are all available.
pub fn collect_diagnostics( pub fn collect_diagnostics(
ast: &DocumentNode, ast: &DocumentNode,
@@ -1317,8 +1316,8 @@ fn keyword_str(k: nuwiki_core::ast::Keyword) -> &'static str {
// ===== Workspace + navigation helpers ===== // ===== Workspace + navigation helpers =====
// //
// `workspace_root_from_params` lived here through Phase 10; Phase 11 lifted // `workspace_root_from_params` logic now lives in
// that logic into `Config::from_init_params` (see `config.rs`) so workspace // `Config::from_init_params` (see `config.rs`) so workspace
// folders and `root_uri` are handled alongside `initializationOptions`. // folders and `root_uri` are handled alongside `initializationOptions`.
/// Background workspace scan (P8). Walks every `.wiki` file under `root`, /// Background workspace scan (P8). Walks every `.wiki` file under `root`,
@@ -1469,7 +1468,7 @@ fn heading_range_in(idx: &WorkspaceIndex, uri: &Url, anchor: &str, _utf8: bool)
if let Some(h) = page.find_heading_by_anchor(anchor) { if let Some(h) = page.find_heading_by_anchor(anchor) {
return Some(span_to_lsp_range_no_text(&h.span)); return Some(span_to_lsp_range_no_text(&h.span));
} }
// Phase 12: tags-as-anchors. `[[Page#tag-name]]` lands on the // Tags-as-anchors. `[[Page#tag-name]]` lands on the
// matching `TagNode` on the target page. // matching `TagNode` on the target page.
let t = page.tags.iter().find(|t| t.name == anchor)?; let t = page.tags.iter().find(|t| t.name == anchor)?;
Some(span_to_lsp_range_no_text(&t.span)) Some(span_to_lsp_range_no_text(&t.span))
+1 -1
View File
@@ -1,4 +1,4 @@
//! Helpers shared by the Phase 8 navigation handlers (definition, //! Helpers shared by the navigation handlers (definition,
//! references, hover, completion). //! references, hover, completion).
//! //!
//! Two responsibilities: //! Two responsibilities:
+2 -2
View File
@@ -1,12 +1,12 @@
//! `textDocument/rename` — server-side rename with cross-document link //! `textDocument/rename` — server-side rename with cross-document link
//! rewriting. //! rewriting.
//! //!
//! Phase 13 implementation: //! Implementation:
//! 1. Resolve the rename target — cursor on a wikilink renames the linked //! 1. Resolve the rename target — cursor on a wikilink renames the linked
//! page; otherwise rename the current file. //! page; otherwise rename the current file.
//! 2. Build the new URI from the wiki root + the user-supplied path. //! 2. Build the new URI from the wiki root + the user-supplied path.
//! 3. For every page in the wiki's index with an outgoing link to the //! 3. For every page in the wiki's index with an outgoing link to the
//! old page name, read its source via the Phase 11 closed-doc loader //! old page name, read its source via the closed-doc loader
//! so cached spans are validated against current text, then emit a //! so cached spans are validated against current text, then emit a
//! `TextEdit` that swaps the `[[old]]` for `[[new]]` while preserving //! `TextEdit` that swaps the `[[old]]` for `[[new]]` while preserving
//! anchors and descriptions. //! anchors and descriptions.
+2 -3
View File
@@ -1,8 +1,7 @@
//! Semantic-token emission for the vimwiki AST. //! Semantic-token emission for the vimwiki AST.
//! //!
//! Token-type scheme settled in P7: custom `vimwiki*` types plus //! Token-type scheme: custom `vimwiki*` types plus
//! `level1`..`level6` + `centered` modifiers (SPEC §11). Default highlight //! `level1`..`level6` + `centered` modifiers.
//! groups for these arrive in Phase 9.
//! //!
//! Pipeline: //! Pipeline:
//! //!
+4 -4
View File
@@ -1,9 +1,9 @@
//! `Wiki` aggregate — per-wiki state, even when there's only one. //! `Wiki` aggregate — per-wiki state, even when there's only one.
//! //!
//! v1.0 shipped with a single `Arc<RwLock<WorkspaceIndex>>` on `Backend`. //! The single `Arc<RwLock<WorkspaceIndex>>` on `Backend` is lifted into a
//! Phase 11 lifts that into a `Wiki` aggregate so Phase 18 (multi-wiki) //! `Wiki` aggregate so multi-wiki support only changes config shape, not
//! only changes config shape, not data flow. Phases 1217 always operate //! data flow. Handlers always operate on a `Wiki`; in practice the `wikis`
//! on a `Wiki`; in practice the `wikis` `Vec` has one entry until 18. //! `Vec` has one entry until multi-wiki support lands.
use std::path::Path; use std::path::Path;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
+2 -2
View File
@@ -1,9 +1,9 @@
//! §13.1 §13.3 — `color_dic` → `HtmlRenderer` integration. //! `color_dic` → `HtmlRenderer` integration.
//! //!
//! Drives the renderer directly so we don't have to spin up the full //! Drives the renderer directly so we don't have to spin up the full
//! export pipeline. The end-to-end path (export command → renderer //! export pipeline. The end-to-end path (export command → renderer
//! with `cfg.color_dic` plumbed through) is integration-tested via the //! with `cfg.color_dic` plumbed through) is integration-tested via the
//! Phase 17 export tests. //! export tests.
use std::collections::HashMap; use std::collections::HashMap;
+2 -2
View File
@@ -1,6 +1,6 @@
//! Command-coverage suite: at least one behavioural test for every //! Command-coverage suite: at least one behavioural test for every
//! documented `:Nuwiki*` / `:Vimwiki*` command (see `doc/nuwiki.txt` //! documented `:Nuwiki*` / `:Vimwiki*` command (see the COMMANDS section
//! §5 COMMANDS). //! of `doc/nuwiki.txt`).
//! //!
//! The user-facing commands funnel through `workspace/executeCommand` //! The user-facing commands funnel through `workspace/executeCommand`
//! into the Backend dispatcher, or — for follow/backlinks/rename — through //! into the Backend dispatcher, or — for follow/backlinks/rename — through
+3 -3
View File
@@ -1,6 +1,6 @@
//! Phase 13: pure-function tests for the rename helper + the file-delete //! Pure-function tests for the rename helper + the file-delete
//! command. The async `Backend::rename` end-to-end (with `read_or_open` //! command. The async `Backend::rename` end-to-end (with `read_or_open`
//! hitting disk) is exercised by Phase 14+ integration but covered here //! hitting disk) is exercised by integration tests but covered here
//! at the building-block level — same logic, no async client. //! at the building-block level — same logic, no async client.
use std::path::PathBuf; use std::path::PathBuf;
@@ -95,7 +95,7 @@ async fn file_delete_returns_workspace_edit_with_delete_op() {
// //
// Easier: shape the test as an integration test over the public // Easier: shape the test as an integration test over the public
// module surface — call `commands::execute` once we expose a thin // module surface — call `commands::execute` once we expose a thin
// builder. For Phase 13 we test the JSON-shaped contract using the // builder. We test the JSON-shaped contract using the
// edits module directly, since that's what `file_delete` returns. // edits module directly, since that's what `file_delete` returns.
let uri = Url::parse("file:///tmp/note.wiki").unwrap(); let uri = Url::parse("file:///tmp/note.wiki").unwrap();
let mut b = nuwiki_lsp::edits::WorkspaceEditBuilder::new(); let mut b = nuwiki_lsp::edits::WorkspaceEditBuilder::new();
+1 -1
View File
@@ -1,4 +1,4 @@
//! §13.1 Cluster A — list rewriters: `removeDone`, `renumber`, //! Cluster A — list rewriters: `removeDone`, `renumber`,
//! `changeSymbol`, `changeLevel`. //! `changeSymbol`, `changeLevel`.
use nuwiki_core::ast::ListSymbol; use nuwiki_core::ast::ListSymbol;
+1 -1
View File
@@ -1,4 +1,4 @@
//! §13.1 Cluster B — table rewriters: `insert`, `align`, `moveColumn`. //! Cluster B — table rewriters: `insert`, `align`, `moveColumn`.
use nuwiki_core::syntax::vimwiki::VimwikiSyntax; use nuwiki_core::syntax::vimwiki::VimwikiSyntax;
use nuwiki_core::syntax::SyntaxPlugin; use nuwiki_core::syntax::SyntaxPlugin;
+1 -1
View File
@@ -1,4 +1,4 @@
//! Phase 19: LSP folding-range provider. //! LSP folding-range provider.
//! //!
//! Drives `folding::folding_ranges` directly. The handler wiring is //! Drives `folding::folding_ranges` directly. The handler wiring is
//! shallow — it just calls into this function — so we verify the //! shallow — it just calls into this function — so we verify the
+2 -2
View File
@@ -1,7 +1,7 @@
//! Tests for the pure helpers that the LSP backend uses to translate //! Tests for the pure helpers that the LSP backend uses to translate
//! between `nuwiki-core` ASTs and the LSP wire format. The async //! between `nuwiki-core` ASTs and the LSP wire format. The async
//! request-handling loop is exercised at integration time in Phase 8+ //! request-handling loop is exercised at integration time by the
//! (navigation), so this file stays focused on conversion correctness. //! navigation tests, so this file stays focused on conversion correctness.
use nuwiki_core::ast::{ use nuwiki_core::ast::{
inline::InlineNode, BlockNode, BlockquoteNode, DocumentNode, ErrorNode, ParagraphNode, inline::InlineNode, BlockNode, BlockquoteNode, DocumentNode, ErrorNode, ParagraphNode,
+1 -1
View File
@@ -1,4 +1,4 @@
//! Phase 17: HTML export commands. //! HTML export commands.
//! //!
//! Drives the pure `export::*` helpers directly. The side-effecting //! Drives the pure `export::*` helpers directly. The side-effecting
//! `export_ops::write_page` / `write_rss` paths get exercised via a //! `export_ops::write_page` / `write_rss` paths get exercised via a
+1 -1
View File
@@ -1,4 +1,4 @@
//! Phase 15: link-health diagnostics + TOC/links/orphans queries. //! Link-health diagnostics + TOC/links/orphans queries.
use std::path::PathBuf; use std::path::PathBuf;
+4 -4
View File
@@ -1,8 +1,8 @@
//! Phase 18: multi-wiki — interwiki resolution + picker commands. //! Multi-wiki — interwiki resolution + picker commands.
//! //!
//! The cross-wiki resolver methods on `Backend` are exercised indirectly //! The cross-wiki resolver methods on `Backend` are exercised indirectly
//! through `tower_lsp::LspService`'s test harness via a synthetic //! through `tower_lsp::LspService`'s test harness via a synthetic
//! `Backend` (the same pattern Phase 13 used). To keep this test suite //! `Backend`. To keep this test suite
//! lightweight we drive the *pure* resolution primitives — the parser //! lightweight we drive the *pure* resolution primitives — the parser
//! producing `LinkKind::Interwiki` targets, then the `WorkspaceIndex` //! producing `LinkKind::Interwiki` targets, then the `WorkspaceIndex`
//! lookup-by-name — and verify the multi-wiki config shape end-to-end. //! lookup-by-name — and verify the multi-wiki config shape end-to-end.
@@ -80,7 +80,7 @@ fn multi_wiki_config_loads_two_entries() {
#[test] #[test]
fn v1_0_single_root_still_works_alongside_multi_wiki_shape() { fn v1_0_single_root_still_works_alongside_multi_wiki_shape() {
// Single-wiki shorthand is the v1.0 form; ensure it still desugars. // Single-wiki shorthand is the legacy form; ensure it still desugars.
let cfg = config_from_json(serde_json::json!({ let cfg = config_from_json(serde_json::json!({
"wiki_root": "/tmp/legacy", "wiki_root": "/tmp/legacy",
})); }));
@@ -135,7 +135,7 @@ fn nested_root_picks_longest_prefix_match() {
// ===== Cross-wiki resolution (uses WorkspaceIndex per wiki) ===== // ===== Cross-wiki resolution (uses WorkspaceIndex per wiki) =====
/// Verify that the cross-wiki resolution semantics match the SPEC: /// Verify the cross-wiki resolution semantics:
/// `[[wiki1:Page]]` looks up `Page` in the *second* wiki's index, not /// `[[wiki1:Page]]` looks up `Page` in the *second* wiki's index, not
/// the source wiki's. The pure-data check here mirrors what /// the source wiki's. The pure-data check here mirrors what
/// `Backend::resolve_target_uri` does. /// `Backend::resolve_target_uri` does.
+2 -2
View File
@@ -1,7 +1,7 @@
//! Tests for the Phase 8 navigation pieces: WorkspaceIndex queries, //! Tests for the navigation pieces: WorkspaceIndex queries,
//! page-name derivation, anchor slugify, position lookup, and link //! page-name derivation, anchor slugify, position lookup, and link
//! resolution. The async LSP handlers are exercised through these //! resolution. The async LSP handlers are exercised through these
//! helpers; end-to-end JSON-RPC drive lives in a follow-up phase. //! helpers; end-to-end JSON-RPC drive lives elsewhere.
use std::path::PathBuf; use std::path::PathBuf;
+4 -4
View File
@@ -95,10 +95,10 @@ else
call s:record(0, 'cmd.NuwikiIndex_defined', ':NuwikiIndex not registered') call s:record(0, 'cmd.NuwikiIndex_defined', ':NuwikiIndex not registered')
endif endif
" ===== Documented command surface (doc/nuwiki.txt §5) ===== " ===== Documented command surface =====
" Every command must be reachable in BOTH the canonical :Nuwiki* form and " Every command must be reachable in BOTH the canonical :Nuwiki* form and
" the vimwiki-compatible :Vimwiki* form. The suffixes below are the §5 list " the vimwiki-compatible :Vimwiki* form. The suffixes below are the command
" minus the prefix. :NuwikiInstall is the one exception — it's a nuwiki-only " list minus the prefix. :NuwikiInstall is the one exception — it's a nuwiki-only
" maintenance command with no :Vimwiki* alias, so it's checked separately. " maintenance command with no :Vimwiki* alias, so it's checked separately.
let s:both_forms = [ let s:both_forms = [
@@ -151,7 +151,7 @@ call s:record(
\ 'invoke.VimwikiNextLink_jumps_to_first_link', \ 'invoke.VimwikiNextLink_jumps_to_first_link',
\ 'col=' . col('.')) \ 'col=' . col('.'))
" ===== Documented mapping surface (doc §6/§7/§8) ===== " ===== Documented mapping surface =====
" Every documented mapping must resolve to a buffer-local mapping in the " Every documented mapping must resolve to a buffer-local mapping in the
" mode(s) the doc lists it under. The mouse group is opt-in — the shell " mode(s) the doc lists it under. The mouse group is opt-in — the shell
" wrapper sets g:nuwiki_mouse_mappings before the ftplugin loads. " wrapper sets g:nuwiki_mouse_mappings before the ftplugin loads.
+12 -12
View File
@@ -123,10 +123,10 @@ vim.defer_fn(function()
end end
record(true, 'lsp.attached', 'client=' .. client.name) record(true, 'lsp.attached', 'client=' .. client.name)
-- ===== Documented command surface (doc/nuwiki.txt §5) ===== -- ===== Documented command surface =====
-- Every command must be reachable in BOTH the canonical :Nuwiki* form -- Every command must be reachable in BOTH the canonical :Nuwiki* form
-- and the vimwiki-compatible :Vimwiki* form. The suffixes below are the -- and the vimwiki-compatible :Vimwiki* form. The suffixes below are the
-- §5 list minus the prefix. :NuwikiInstall is the one exception — a -- command list minus the prefix. :NuwikiInstall is the one exception — a
-- nuwiki-only maintenance command with no :Vimwiki* alias. -- nuwiki-only maintenance command with no :Vimwiki* alias.
local both_forms = { local both_forms = {
'Index', 'TabIndex', 'UISelect', 'Goto', 'FollowLink', 'Backlinks', 'Index', 'TabIndex', 'UISelect', 'Goto', 'FollowLink', 'Backlinks',
@@ -169,7 +169,7 @@ vim.defer_fn(function()
record(ok, 'invoke.next_prev_link_cursor_movement', ok and '' or tostring(err)) record(ok, 'invoke.next_prev_link_cursor_movement', ok and '' or tostring(err))
end end
-- ===== Documented mapping surface (doc §6/§7/§8) ===== -- ===== Documented mapping surface =====
-- Every documented mapping must resolve to a buffer-local mapping in -- Every documented mapping must resolve to a buffer-local mapping in
-- the mode(s) the doc lists it under. `modes` is a string of mode -- the mode(s) the doc lists it under. `modes` is a string of mode
-- letters: n/x/o/i. The mouse group is opt-in — the harness enables it -- letters: n/x/o/i. The mouse group is opt-in — the harness enables it
@@ -179,36 +179,36 @@ vim.defer_fn(function()
return next(d) ~= nil and d.buffer == 1 return next(d) ~= nil and d.buffer == 1
end end
local mapping_surface = { local mapping_surface = {
-- §6 Links -- Links
{ '<CR>', 'n' }, { '<S-CR>', 'n' }, { '<C-CR>', 'n' }, { '<C-S-CR>', 'n' }, { '<CR>', 'n' }, { '<S-CR>', 'n' }, { '<C-CR>', 'n' }, { '<C-S-CR>', 'n' },
{ '<BS>', 'n' }, { '<Tab>', 'n' }, { '<S-Tab>', 'n' }, { '+', 'nx' }, { '<BS>', 'n' }, { '<Tab>', 'n' }, { '<S-Tab>', 'n' }, { '+', 'nx' },
-- §6 Lists -- Lists
{ '<C-Space>', 'nx' }, { '<C-@>', 'nx' }, { '<Nul>', 'nx' }, { '<C-Space>', 'nx' }, { '<C-@>', 'nx' }, { '<Nul>', 'nx' },
{ 'gnt', 'n' }, { 'gln', 'nx' }, { 'glp', 'nx' }, { 'glx', 'nx' }, { 'gnt', 'n' }, { 'gln', 'nx' }, { 'glp', 'nx' }, { 'glx', 'nx' },
{ 'glh', 'n' }, { 'gll', 'n' }, { 'gLh', 'n' }, { 'gLl', 'n' }, { 'glh', 'n' }, { 'gll', 'n' }, { 'gLh', 'n' }, { 'gLl', 'n' },
{ 'glr', 'n' }, { 'gLr', 'n' }, { 'gl<Space>', 'n' }, { 'gL<Space>', 'n' }, { 'glr', 'n' }, { 'gLr', 'n' }, { 'gl<Space>', 'n' }, { 'gL<Space>', 'n' },
{ 'o', 'n' }, { 'O', 'n' }, { 'o', 'n' }, { 'O', 'n' },
-- §6 Headers -- Headers
{ '=', 'n' }, { '-', 'n' }, { ']]', 'n' }, { '[[', 'n' }, { '=', 'n' }, { '-', 'n' }, { ']]', 'n' }, { '[[', 'n' },
{ ']=', 'n' }, { '[=', 'n' }, { ']u', 'n' }, { '[u', 'n' }, { ']=', 'n' }, { '[=', 'n' }, { ']u', 'n' }, { '[u', 'n' },
-- §6 Tables -- Tables
{ 'gqq', 'n' }, { 'gq1', 'n' }, { 'gww', 'n' }, { 'gw1', 'n' }, { 'gqq', 'n' }, { 'gq1', 'n' }, { 'gww', 'n' }, { 'gw1', 'n' },
{ '<A-Left>', 'n' }, { '<A-Right>', 'n' }, { '<A-Left>', 'n' }, { '<A-Right>', 'n' },
-- §6 Wiki / diary / export -- Wiki / diary / export
{ '<Leader>ww', 'n' }, { '<Leader>ws', 'n' }, { '<Leader>wi', 'n' }, { '<Leader>ww', 'n' }, { '<Leader>ws', 'n' }, { '<Leader>wi', 'n' },
{ '<Leader>w<Leader>w', 'n' }, { '<Leader>w<Leader>y', 'n' }, { '<Leader>w<Leader>w', 'n' }, { '<Leader>w<Leader>y', 'n' },
{ '<Leader>w<Leader>t', 'n' }, { '<Leader>w<Leader>i', 'n' }, { '<Leader>w<Leader>t', 'n' }, { '<Leader>w<Leader>i', 'n' },
{ '<Leader>wn', 'n' }, { '<Leader>wd', 'n' }, { '<Leader>wr', 'n' }, { '<Leader>wn', 'n' }, { '<Leader>wd', 'n' }, { '<Leader>wr', 'n' },
{ '<Leader>wh', 'n' }, { '<Leader>whh', 'n' }, { '<Leader>wha', 'n' }, { '<Leader>wh', 'n' }, { '<Leader>whh', 'n' }, { '<Leader>wha', 'n' },
{ '<Leader>wc', 'nx' }, { '<C-Down>', 'n' }, { '<C-Up>', 'n' }, { '<Leader>wc', 'nx' }, { '<C-Down>', 'n' }, { '<C-Up>', 'n' },
-- §6 Mouse (opt-in) -- Mouse (opt-in)
{ '<2-LeftMouse>', 'n' }, { '<S-2-LeftMouse>', 'n' }, { '<C-2-LeftMouse>', 'n' }, { '<2-LeftMouse>', 'n' }, { '<S-2-LeftMouse>', 'n' }, { '<C-2-LeftMouse>', 'n' },
{ '<MiddleMouse>', 'n' }, { '<RightMouse>', 'n' }, { '<MiddleMouse>', 'n' }, { '<RightMouse>', 'n' },
-- §7 Text objects (operator-pending + visual) -- Text objects (operator-pending + visual)
{ 'ah', 'ox' }, { 'ih', 'ox' }, { 'aH', 'ox' }, { 'iH', 'ox' }, { 'ah', 'ox' }, { 'ih', 'ox' }, { 'aH', 'ox' }, { 'iH', 'ox' },
{ 'al', 'ox' }, { 'il', 'ox' }, { 'a\\', 'ox' }, { 'i\\', 'ox' }, { 'al', 'ox' }, { 'il', 'ox' }, { 'a\\', 'ox' }, { 'i\\', 'ox' },
{ 'ac', 'ox' }, { 'ic', 'ox' }, { 'ac', 'ox' }, { 'ic', 'ox' },
-- §8 Insert-mode -- Insert-mode
{ '<CR>', 'i' }, { '<Tab>', 'i' }, { '<S-Tab>', 'i' }, { '<CR>', 'i' }, { '<Tab>', 'i' }, { '<S-Tab>', 'i' },
{ '<C-D>', 'i' }, { '<C-T>', 'i' }, { '<C-D>', 'i' }, { '<C-T>', 'i' },
{ '<C-L><C-J>', 'i' }, { '<C-L><C-K>', 'i' }, { '<C-L><C-M>', 'i' }, { '<C-L><C-J>', 'i' }, { '<C-L><C-K>', 'i' }, { '<C-L><C-M>', 'i' },
@@ -424,7 +424,7 @@ vim.defer_fn(function()
wait_ms = 200, wait_ms = 200,
}) })
-- ===== Link helpers (§13.1 Cluster C) ===== -- ===== Link helpers (Cluster C) =====
run('links.normalize_via_+', { run('links.normalize_via_+', {
lines = { 'MyPage rest' }, lines = { 'MyPage rest' },
+8 -8
View File
@@ -1,9 +1,9 @@
" ftplugin/nuwiki.vim — per-buffer settings + Phase 19 command/keymap glue. " ftplugin/nuwiki.vim — per-buffer settings + command/keymap glue.
" "
" Wires up: " Wires up:
" - basic edit-time options (commentstring, suffixesadd, …) " - basic edit-time options (commentstring, suffixesadd, …)
" - every `:Vimwiki*` / `:Nuwiki*` command from SPEC §12.10 that has a " - every `:Vimwiki*` / `:Nuwiki*` command that has a
" server-side counterpart (deferred §13.1 commands stub out with a " server-side counterpart (deferred commands stub out with a
" "not yet implemented" notification) " "not yet implemented" notification)
" - on Neovim: keymaps + text objects + folding via " - on Neovim: keymaps + text objects + folding via
" `nuwiki.ftplugin.attach()` " `nuwiki.ftplugin.attach()`
@@ -78,7 +78,7 @@ if !has('nvim')
command! -buffer -nargs=? VimwikiSearchTags call nuwiki#commands#tags_search(<q-args>) command! -buffer -nargs=? VimwikiSearchTags call nuwiki#commands#tags_search(<q-args>)
command! -buffer -nargs=? VimwikiGenerateTagLinks call nuwiki#commands#tags_generate_links(<q-args>) command! -buffer -nargs=? VimwikiGenerateTagLinks call nuwiki#commands#tags_generate_links(<q-args>)
" §13.1 deferred stubs. " Deferred stubs.
command! -buffer -nargs=1 VimwikiTable call nuwiki#commands#table_insert() command! -buffer -nargs=1 VimwikiTable call nuwiki#commands#table_insert()
command! -buffer VimwikiTableMoveColumnLeft call nuwiki#commands#table_move_left() command! -buffer VimwikiTableMoveColumnLeft call nuwiki#commands#table_move_left()
command! -buffer VimwikiTableMoveColumnRight call nuwiki#commands#table_move_right() command! -buffer VimwikiTableMoveColumnRight call nuwiki#commands#table_move_right()
@@ -115,7 +115,7 @@ if !has('nvim')
command! -buffer -nargs=? NuwikiGenerateTagLinks call nuwiki#commands#tags_generate_links(<q-args>) command! -buffer -nargs=? NuwikiGenerateTagLinks call nuwiki#commands#tags_generate_links(<q-args>)
command! -buffer -nargs=1 NuwikiGoto call nuwiki#commands#wiki_goto_page(<q-args>) command! -buffer -nargs=1 NuwikiGoto call nuwiki#commands#wiki_goto_page(<q-args>)
" Canonical :Nuwiki* names that mirror the documented surface (doc §5). " Canonical :Nuwiki* names that mirror the documented surface.
" The older :Nuwiki* spellings above stay defined for back-compat. " The older :Nuwiki* spellings above stay defined for back-compat.
command! -buffer NuwikiNextLink call nuwiki#commands#link_next() command! -buffer NuwikiNextLink call nuwiki#commands#link_next()
command! -buffer NuwikiPrevLink call nuwiki#commands#link_prev() command! -buffer NuwikiPrevLink call nuwiki#commands#link_prev()
@@ -331,7 +331,7 @@ command! -buffer VimwikiRebuildTags lua require('nuwiki.commands').
command! -buffer -nargs=? VimwikiSearchTags lua require('nuwiki.commands').tags_search(<q-args>) command! -buffer -nargs=? VimwikiSearchTags lua require('nuwiki.commands').tags_search(<q-args>)
command! -buffer -nargs=? VimwikiGenerateTagLinks lua require('nuwiki.commands').tags_generate_links(<q-args>) command! -buffer -nargs=? VimwikiGenerateTagLinks lua require('nuwiki.commands').tags_generate_links(<q-args>)
" §13.1 deferred — stubs notify the user instead of erroring. " Deferred — stubs notify the user instead of erroring.
command! -buffer -nargs=1 VimwikiTable lua require('nuwiki.commands').table_insert() command! -buffer -nargs=1 VimwikiTable lua require('nuwiki.commands').table_insert()
command! -buffer VimwikiTableMoveColumnLeft lua require('nuwiki.commands').table_move_column_left() command! -buffer VimwikiTableMoveColumnLeft lua require('nuwiki.commands').table_move_column_left()
command! -buffer VimwikiTableMoveColumnRight lua require('nuwiki.commands').table_move_column_right() command! -buffer VimwikiTableMoveColumnRight lua require('nuwiki.commands').table_move_column_right()
@@ -370,7 +370,7 @@ command! -buffer -nargs=? NuwikiSearchTags lua require('nuwiki.commands'
command! -buffer -nargs=? NuwikiGenerateTagLinks lua require('nuwiki.commands').tags_generate_links(<q-args>) command! -buffer -nargs=? NuwikiGenerateTagLinks lua require('nuwiki.commands').tags_generate_links(<q-args>)
command! -buffer -nargs=1 NuwikiGoto lua require('nuwiki.commands').wiki_goto_page(<q-args>) command! -buffer -nargs=1 NuwikiGoto lua require('nuwiki.commands').wiki_goto_page(<q-args>)
" Canonical :Nuwiki* names that mirror the documented surface (doc §5). " Canonical :Nuwiki* names that mirror the documented surface.
" The older :Nuwiki* spellings above stay defined for back-compat. " The older :Nuwiki* spellings above stay defined for back-compat.
command! -buffer NuwikiNextLink lua require('nuwiki.commands').link_next() command! -buffer NuwikiNextLink lua require('nuwiki.commands').link_next()
command! -buffer NuwikiPrevLink lua require('nuwiki.commands').link_prev() command! -buffer NuwikiPrevLink lua require('nuwiki.commands').link_prev()
@@ -384,5 +384,5 @@ command! -buffer Nuwiki2HTML lua require('nuwiki.command
command! -buffer Nuwiki2HTMLBrowse lua require('nuwiki.commands').export_browse() command! -buffer Nuwiki2HTMLBrowse lua require('nuwiki.commands').export_browse()
command! -buffer -bang NuwikiAll2HTML execute (<bang>0 ? "lua require('nuwiki.commands').export_all_force()" : "lua require('nuwiki.commands').export_all()") command! -buffer -bang NuwikiAll2HTML execute (<bang>0 ? "lua require('nuwiki.commands').export_all_force()" : "lua require('nuwiki.commands').export_all()")
" Phase 19 buffer attach: keymaps + text objects + folding. " Buffer attach: keymaps + text objects + folding.
lua require('nuwiki.ftplugin').attach(0) lua require('nuwiki.ftplugin').attach(0)
+5 -5
View File
@@ -1,5 +1,5 @@
-- lua/nuwiki/commands.lua — Lua wrappers around every `nuwiki.*` -- lua/nuwiki/commands.lua — Lua wrappers around every `nuwiki.*`
-- `workspace/executeCommand` the server advertises. Phase 19 plumbing. -- `workspace/executeCommand` the server advertises.
-- --
-- Each public function is named after its `:Vimwiki*` / `:Nuwiki*` -- Each public function is named after its `:Vimwiki*` / `:Nuwiki*`
-- counterpart so `ftplugin/nuwiki.vim` can wire them up declaratively. -- counterpart so `ftplugin/nuwiki.vim` can wire them up declaratively.
@@ -468,7 +468,7 @@ function M.rename_file()
vim.lsp.buf.rename() vim.lsp.buf.rename()
end end
-- ===== Deferred placeholders (§13.1) ===== -- ===== Deferred placeholders =====
-- --
-- These commands aren't implemented on the server yet. Stub them so the -- These commands aren't implemented on the server yet. Stub them so the
-- `:Vimwiki*` compat surface exists and users get a clear message -- `:Vimwiki*` compat surface exists and users get a clear message
@@ -483,7 +483,7 @@ local function _not_yet(name)
end end
end end
-- §13.1 Cluster A — list rewriters. -- Cluster A — list rewriters.
-- `gl<Space>` — remove done items from the current list only. Passing the -- `gl<Space>` — remove done items from the current list only. Passing the
-- cursor position scopes the server to the contiguous list block under it. -- cursor position scopes the server to the contiguous list block under it.
@@ -891,7 +891,7 @@ function M.smart_shift_tab()
) )
end end
-- §13.1 Cluster B — table rewriters. -- Cluster B — table rewriters.
function M.table_insert(cols, rows) function M.table_insert(cols, rows)
cols = tonumber(cols) or 3 cols = tonumber(cols) or 3
@@ -974,7 +974,7 @@ function M.colorize(color)
vim.api.nvim_buf_set_lines(0, row - 1, row, false, { new_line }) vim.api.nvim_buf_set_lines(0, row - 1, row, false, { new_line })
end end
-- §13.1 Cluster C — link helpers. -- Cluster C — link helpers.
function M.paste_link() function M.paste_link()
exec('nuwiki.link.pasteWikilink', pos_args()) exec('nuwiki.link.pasteWikilink', pos_args())
+7 -8
View File
@@ -1,12 +1,11 @@
-- lua/nuwiki/config.lua — defaults and user-config merge. -- lua/nuwiki/config.lua — defaults and user-config merge.
-- --
-- Schema mirrors SPEC §7.5 + §12.11. Keep this aligned with -- Keep this aligned with `doc/nuwiki.txt`.
-- `doc/nuwiki.txt`.
local M = {} local M = {}
M.defaults = { M.defaults = {
-- v1.0 single-wiki shorthand. The server desugars these into a -- Single-wiki shorthand. The server desugars these into a
-- one-entry `wikis = {…}` list. Use either this or the multi-wiki -- one-entry `wikis = {…}` list. Use either this or the multi-wiki
-- form below; if both are set, `wikis` wins. -- form below; if both are set, `wikis` wins.
wiki_root = '~/vimwiki', wiki_root = '~/vimwiki',
@@ -14,12 +13,12 @@ M.defaults = {
syntax = 'vimwiki', syntax = 'vimwiki',
log_level = 'warn', log_level = 'warn',
-- v1.1 multi-wiki shape. Each entry honours every per-wiki key the -- Multi-wiki shape. Each entry honours every per-wiki key the
-- server understands. Leave nil to fall through to the shorthand -- server understands. Leave nil to fall through to the shorthand
-- above. -- above.
-- --
-- Per-wiki keys (all optional, server defaults documented in SPEC -- Per-wiki keys (all optional, server defaults documented in
-- §12.11): -- `doc/nuwiki.txt`):
-- name, root, file_extension, syntax, index -- name, root, file_extension, syntax, index
-- diary_rel_path, diary_index, diary_frequency, -- diary_rel_path, diary_index, diary_frequency,
-- diary_start_week_day, diary_caption_level, diary_sort, -- diary_start_week_day, diary_caption_level, diary_sort,
@@ -31,7 +30,7 @@ M.defaults = {
-- links_space_char, nested_syntaxes -- links_space_char, nested_syntaxes
wikis = nil, wikis = nil,
-- Phase 19: per-buffer glue. Each subgroup mirrors vimwiki's -- Per-buffer glue. Each subgroup mirrors vimwiki's
-- `g:vimwiki_key_mappings` shape and can be flipped off -- `g:vimwiki_key_mappings` shape and can be flipped off
-- independently to suppress that group of keymaps. -- independently to suppress that group of keymaps.
mappings = { mappings = {
@@ -47,7 +46,7 @@ M.defaults = {
mouse = false, -- <2-LeftMouse>, <RightMouse>, … (opt-in) mouse = false, -- <2-LeftMouse>, <RightMouse>, … (opt-in)
}, },
-- Phase 19: folding. `'lsp'` uses the server's `foldingRange` -- Folding. `'lsp'` uses the server's `foldingRange`
-- provider (Neovim 0.11+ wires it automatically). `'expr'` falls -- provider (Neovim 0.11+ wires it automatically). `'expr'` falls
-- back to a regex-based `foldexpr`. `'off'` skips folding setup. -- back to a regex-based `foldexpr`. `'off'` skips folding setup.
folding = 'lsp', folding = 'lsp',
+2 -2
View File
@@ -1,6 +1,6 @@
-- lua/nuwiki/ftplugin.lua — per-buffer hookup invoked by -- lua/nuwiki/ftplugin.lua — per-buffer hookup invoked by
-- `ftplugin/nuwiki.vim`. Centralises the Lua side of Phase 19 so the -- `ftplugin/nuwiki.vim`. Centralises the Lua side of the per-buffer
-- VimL ftplugin stays a one-liner regardless of which subgroups are -- glue so the VimL ftplugin stays a one-liner regardless of which subgroups are
-- enabled. -- enabled.
local M = {} local M = {}
+2 -2
View File
@@ -1,7 +1,7 @@
-- lua/nuwiki/health.lua — `:checkhealth nuwiki` target. -- lua/nuwiki/health.lua — `:checkhealth nuwiki` target.
-- --
-- v1.0 checks per SPEC §7.6 (binary, version, filetype, LSP attach). -- Core checks: binary, version, filetype, LSP attach.
-- v1.1 §12.10 additions: registered LSP commands, indexed wikis, HTML -- Additional checks: registered LSP commands, indexed wikis, HTML
-- output path writability, default keymaps installed. -- output path writability, default keymaps installed.
local M = {} local M = {}
+1 -1
View File
@@ -5,7 +5,7 @@
-- require('nuwiki').install() — install / build the language server -- require('nuwiki').install() — install / build the language server
-- require('nuwiki').health() — :checkhealth nuwiki target -- require('nuwiki').health() — :checkhealth nuwiki target
-- --
-- Per SPEC §6.2/§6.3 this layer is wiring only. Everything substantive -- This layer is wiring only. Everything substantive
-- happens in the Rust language server. -- happens in the Rust language server.
local M = {} local M = {}
+1 -1
View File
@@ -1,6 +1,6 @@
-- lua/nuwiki/install.lua — install / build the `nuwiki-ls` binary. -- lua/nuwiki/install.lua — install / build the `nuwiki-ls` binary.
-- --
-- Strategy (matches SPEC §7.2): -- Strategy:
-- 1. Look for `bin/nuwiki-ls[.exe]` next to the plugin. -- 1. Look for `bin/nuwiki-ls[.exe]` next to the plugin.
-- 2. If missing or `g:nuwiki_build_from_source` is set, build with cargo. -- 2. If missing or `g:nuwiki_build_from_source` is set, build with cargo.
-- 3. Otherwise try to download a release asset for the current target. -- 3. Otherwise try to download a release asset for the current target.
+2 -2
View File
@@ -16,7 +16,7 @@
-- wiki_prefix = true, -- <Leader>ww, <Leader>ws, <Leader>wi, … -- wiki_prefix = true, -- <Leader>ww, <Leader>ws, <Leader>wi, …
-- } -- }
-- --
-- Mappings that point at §13.1-deferred commands stub out to a -- Mappings that point at deferred commands stub out to a
-- "not yet implemented" notification — better than a silent no-op -- "not yet implemented" notification — better than a silent no-op
-- so users get parity *signalling*, not just parity in lhs. -- so users get parity *signalling*, not just parity in lhs.
@@ -133,7 +133,7 @@ local function open_above_with_bullet()
vim.api.nvim_feedkeys('O' .. prefix, 'n', false) vim.api.nvim_feedkeys('O' .. prefix, 'n', false)
end end
-- ===== Stub for deferred §13.1 commands ===== -- ===== Stub for deferred commands =====
local function deferred(name) local function deferred(name)
return function() return function()
+1 -1
View File
@@ -4,7 +4,7 @@
" their config (lazy.nvim does this automatically through `opts`). On Vim " their config (lazy.nvim does this automatically through `opts`). On Vim
" we start the LSP client when a vimwiki buffer is loaded. " we start the LSP client when a vimwiki buffer is loaded.
" "
" Per SPEC §6.2 / §6.3 this file contains wiring only — all logic lives in " This file contains wiring only — all logic lives in
" the Rust language server. " the Rust language server.
if exists('g:loaded_nuwiki') if exists('g:loaded_nuwiki')
+2 -2
View File
@@ -1,8 +1,8 @@
" syntax/vimwiki.vim — default highlight groups for nuwiki. " syntax/vimwiki.vim — default highlight groups for nuwiki.
" "
" Two responsibilities: " Two responsibilities:
" 1. Default `@vimwiki*` highlight groups for the LSP semantic tokens " 1. Default `@vimwiki*` highlight groups for the LSP semantic tokens.
" (SPEC §11 P7). Loaded eagerly so the very first hover/highlight " Loaded eagerly so the very first hover/highlight
" cycle after server startup already has colours. " cycle after server startup already has colours.
" 2. A small regex-based fallback so static highlighting works on " 2. A small regex-based fallback so static highlighting works on
" buffers where the LSP isn't running yet. " buffers where the LSP isn't running yet.