Commit Graph

15 Commits

Author SHA1 Message Date
gffranco 8fdfa9e256 fix(html): match vimwiki export output for headings, code fences, tags, anchors (#6)
CI / cargo fmt --check (push) Failing after 42s
CI / cargo clippy (push) Successful in 1m6s
CI / cargo test (push) Successful in 1m14s
CI / editor keymaps (push) Successful in 1m31s
Exported HTML diverged from upstream vimwiki's :VimwikiAll2HTML in ways
that break custom templates' CSS/JS and deep-links. Bring the renderer to
parity on the structural differences reported in #6:

1. Headings — restore vimwiki's structure: a wrapping
   `<div id="{hierarchical}">` (parent anchors joined by `-`),
   `class="header"`, and an in-heading `<a href="#{hierarchical}">`
   self-link. The flat `id` stays on the heading element so intra-page
   TOC / `#anchor` links keep resolving. Ancestor path is tracked while
   walking top-level headings.

2. Code fences — emit vimwiki's `<pre {raw-attrs}>` (the verbatim text
   after `{{{`, e.g. `<pre python>`) instead of
   `<pre><code class="language-X">`, so highlighters wired for vimwiki
   output apply. The fence text is now preserved verbatim through the
   lexer/AST (PreformattedNode.attrs).

3. Inline tag ids — drop the `tag-` prefix (`id="wiki"`, not
   `id="tag-wiki"`). Also fixes a real inconsistency: the LSP validated
   `[[Page#wiki]]` as resolvable while the HTML emitted `id="tag-wiki"`,
   so the exported link was broken.

4. Same-page anchors — `[[#Section]]` resolves to `index.html#Section`
   (current page filename + fragment), matching vimwiki, rather than a
   bare `#Section`.

Also ship vimwiki's stock style.css verbatim as the default stylesheet
(was a ~7-line minimal one) so exports look identical out of the box and
the `.header`/`.tag`/`.toc`/`done*` classes are styled.

Centered headings and the `<div class="toc">` Contents wrapper keep their
existing form. Tests updated across core + lsp to the new output; added
coverage for hierarchical ids, raw fence attrs, bare tag ids, and the
same-page anchor filename.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:04:14 +00:00
gffranco daee0f1902 feat(links): wire up nuwiki.links.generateForPath (scoped GenerateLinks)
CI / cargo fmt --check (push) Successful in 24s
CI / cargo clippy (push) Successful in 30s
CI / cargo test (push) Successful in 40s
CI / editor keymaps (push) Successful in 1m22s
`:VimwikiGenerateLinks <path>` sent nuwiki.links.generateForPath, but the
server had no handler and didn't advertise it — so the scoped form was
silently dropped (only the no-arg form worked). Register and implement it.

The page list is scoped via page_in_scope: a plain `path` is a directory
subtree (the page itself or anything under `<path>/`); a `path` with `*`/`?`
is a glob pattern (upstream's behaviour), matched with export::glob_match
(now pub(crate)). links_generate and the scoped form share
links_generate_scoped, so cursor-line insertion + captions + config all
apply uniformly.

Tests: page_in_scope unit tests (subtree, glob, prefix-sibling, trailing
slash) + COMMANDS advertises both generate forms. 580 passed, clippy
clean, harnesses green. Doc updated with the [path] argument.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 01:23:42 +00:00
gffranco f3d8af5f23 fix: TOC header class, numbering config, and Lua defaults
CI / cargo fmt --check (push) Successful in 31s
CI / cargo clippy (push) Successful in 31s
CI / cargo test (push) Successful in 36s
Release / build x86_64-unknown-linux-gnu (push) Successful in 53s
Release / build aarch64-unknown-linux-musl (push) Successful in 1m6s
Release / build x86_64-unknown-linux-musl (push) Successful in 1m8s
Release / build aarch64-unknown-linux-gnu (push) Successful in 1m47s
Release / gitea release (push) Successful in 28s
CI / editor keymaps (push) Successful in 1m39s
- Add class='toc' to TOC heading in HTML export by detecting the
  configured toc_header value in render_heading()
- Add toc_header field to HtmlRenderer and wire it through
  render_page_html()
- Add toc_header, toc_header_level, html_header_numbering, and
  html_header_numbering_sym to Lua config defaults so users can
  discover and set them
2026-06-04 00:39:20 -03:00
gffranco 68f10971f7 fix(html): emit heading id anchors so exported #links resolve
CI / cargo fmt --check (push) Successful in 26s
CI / cargo clippy (push) Successful in 27s
CI / cargo test (push) Successful in 31s
CI / editor keymaps (push) Successful in 1m22s
Exported HTML anchor links (TOC entries, [[Page#Heading]], [[#Heading]])
went nowhere because heading elements carried no `id`. Worse, the two TOC
builders slugified anchors (`#my-heading`) while the link resolver emits
the raw heading text (`#My Heading`, matching upstream vimwiki), so even
once ids existed the two halves wouldn't have agreed.

Fix — adopt vimwiki's scheme (raw heading text as the anchor) everywhere:
- render_heading emits `<hN id="<plain heading text>">`.
- build_toc_html (HTML export TOC) uses the raw, HTML-escaped title for
  both the href and the link text (was slugify).
- collect_toc_items (:VimwikiTOC buffer output) uses the raw title for the
  generated `[[#anchor]]` (was slugify). In-editor navigation slugifies
  both sides, so it still resolves.

Consistency: add canonical `nuwiki_core::ast::inline_text()` and route the
heading id, the HTML-TOC title, the buffer-TOC title, and
`diagnostics::heading_text` through it — three duplicated extractors
collapsed into one, so the anchor and its validation can't drift.

Regression test: heading_id_matches_anchor_link_href (core) asserts the
heading id and a `#anchor` link's href are identical. Updated the heading
assertions in the renderer/export tests for the new `id=` attribute.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:09:33 +00:00
gffranco 3b92b11948 fix(review): close all 2026-06-03 codebase-review findings (R1-R18)
CI / cargo fmt --check (push) Successful in 20s
CI / cargo clippy (push) Successful in 32s
CI / cargo test (push) Successful in 38s
CI / editor keymaps (push) Successful in 1m33s
Resolves the 18 findings from the parallel codebase review, tracked in
development/vimwiki-gap.md.

Correctness / perf:
- Wiki.config -> Arc<WikiConfig> so cloning a Wiki is a refcount bump (R5)
- WorkspaceIndex::remove is no longer O(n^2): a per-source contributions
  map limits the scan to buckets the source actually wrote into (R6)
- render_color now expands color_tag_template (__STYLE__/__CONTENT__),
  consuming the previously-dead field; ColorNode documented as an
  extension point; 3 renderer tests added (R3/R4)
- wiki_root_for returns empty/nil on no-match instead of falling back to
  the first wiki (R2); auto_header honours links_space_char (R7)

Cleanup / dedup:
- Remove dead #[allow(dead_code)] stubs + uncalled pub helpers, narrow
  imports (R10/R11)
- Dedup span_of_inline x3 -> InlineNode::span() (R12)
- diary_step single read lock; page_captions single pass (R13)
- Lua auto_header loop -> wiki_list(); detect_current_symbol cleanup
  (R14/R18)

Client / docs:
- :VimwikiNormalizeLink Vim cmds -> <q-args> (R17); ftplugin header fix
  (R16); vars.vim multi-wiki limitation documented (R15)
- Document 19 config options in README.md + doc/nuwiki.txt; fix
  list_margin/shiftwidth doc and stale comments (R1/R9)
- R8 investigated, confirmed not a real bug (documented)

Verified: Neovim harness 307, Vim harness 301/18/21, Rust suite 568, all
0 failed; clippy clean; fresh parallel-agent audit found no regressions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 14:13:55 +00:00
gffranco e6e3f70dd2 feat(render): list_ignore_newline / text_ignore_newline = false → <br>
CI / cargo fmt --check (push) Failing after 22s
CI / cargo clippy (push) Successful in 22s
CI / cargo test (push) Successful in 36s
CI / editor keymaps (push) Successful in 1m26s
Implements the deferred half of these options: a soft line break inside a
paragraph / list item can render as <br /> instead of a space.

- Parser: soft breaks now emit a dedicated InlineNode::SoftBreak node
  (parse_inline_seq K::Newline + the list-continuation synthetic join) instead
  of collapsing to Text(" "). Added SoftBreakNode + the variant.
- All inline consumers treat SoftBreak as whitespace: AST visitor (no-op),
  text extraction (index.rs + lib.rs → space), nav/semantic_tokens span_of_inline,
  semantic emit (skip like Text), parser span_of_inline.
- Renderer: render_inlines_break renders top-level SoftBreaks as the
  context-specific string; render_paragraph uses text_ignore_newline,
  render_list_item uses list_ignore_newline; default render_inline → space.
  with_newline_handling builder; export.rs wires both from HtmlConfig.

Default (true/true = upstream default) behaviour is unchanged (space). Updated
3 core tests + 1 parser test that asserted the old Text(" ") shape to treat
SoftBreak as whitespace. New test render_page_html_ignore_newline_controls_soft_breaks.

Gap doc: P3 Config item closed — only the deferred markdown cluster remains.
Full rust suite + clippy clean; Neovim 307, Vim 301/18/21.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 13:06:38 +00:00
gffranco 5d1ac09245 feat(config): P3 config batch — group 4 (valid_html_tags, emoji, color_tag_template)
- valid_html_tags: HTML export now passes the allowlisted inline tags through
  verbatim (b/i/sub/sup/kbd/div/center/strong/em/...) and escapes the rest,
  via a render-time tag-aware escaper (write_escaped_allowing/match_allowed_tag)
  — mirroring upstream's s:safe_html_line regex rather than a parser change.
  `span` is always allowed so colour spans render.
- emoji_enable: render-time `:alias:` -> glyph substitution (curated common
  set), default on; with_emoji builder + substitute_emoji/emoji_for.
- color_tag_template: :VimwikiColorize now wraps via a configurable template
  (g:nuwiki_color_tag_template / setup color_tag_template), splitting on
  __CONTENT__ with __COLORFG__ -> colour; both clients. Colour spans render in
  export via the valid_html_tags span passthrough. Default template reproduces
  the prior `<span style="color:NAME">` output (harness colorize tests green).

Renderer gets with_valid_html_tags + with_emoji; export.rs wires both from
HtmlConfig. Tests: valid_html_tags passthrough + escape, emoji on/off
(html_export.rs). Full rust suite + both harnesses green (Neovim 307,
Vim 301/18/21); clippy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 12:30:38 +00:00
gffranco 9441fe918c fix(parity): close the 2026-06-02 re-audit config/command findings
CI / cargo fmt --check (push) Successful in 19s
CI / cargo clippy (push) Successful in 37s
CI / cargo test (push) Successful in 29s
CI / editor keymaps (push) Successful in 1m36s
Implements the remaining fourth-pass findings (gap doc updated):

- list_margin: rework to upstream's buffer-side meaning. Drop the
  nuwiki-only HTML em-margin (renderer field/method + render_page_html
  param removed) and prepend max(0, list_margin) leading spaces to every
  generated bullet in build_toc_text / build_links_text /
  build_tag_links_text / diary::build_index_body; headings stay at col 0.
  From<RawWiki> derives 0 for markdown wikis when unset. Negatives can't
  resolve 'shiftwidth' server-side, so they collapse to zero indent
  (documented divergence).

- diary_months: per-wiki Vec<String> (default 12 English names), threaded
  into the diary-index month labels; missing/empty slots fall back to the
  English name.

- diary_caption_level: widen u8 -> i8 so vimwiki's -1 (min: -1) parses;
  build_index_body clamps < 0 to base tree level 0.

- VimwikiRemoveDone: regain upstream's -range. All four defs are now
  -bang -range, dispatched via remove_done(bang, range, l1, l2) in both
  clients: ! -> whole buffer, explicit range -> new list_remove_done_range
  ({range:[l1-1,l2-1]}), else current list. Server remove_done_edit gained
  an Option<(u32,u32)> range that filters whole-doc victims by start line.

markdown_header_style is deferred: the generators emit vimwiki syntax only
(caption_line never writes markdown headers), so there's no markdown header
to attach the style to. Logged as the "generated-content is vimwiki-only"
intentional divergence pending a later markdown generated-content effort.

Tests: list_margin indent, markdown list_margin-0 default, diary_months
custom + fallback, negative caption_level clamp/parse, ranged remove-done
(server + both keymap harnesses). 553 lsp/core tests pass; clippy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 10:50:38 +00:00
gffranco 79b502fa5c feat(html): html_header_numbering section numbering for HTML export
CI / cargo fmt --check (push) Successful in 51s
CI / cargo clippy (push) Successful in 27s
CI / cargo test (push) Successful in 49s
CI / editor keymaps (push) Successful in 2m52s
Mirror vimwiki's html_header_numbering / html_header_numbering_sym:
top-level headings at or below the start level get a dotted section
number (1, 1.1, 1.2, ...) with a configurable trailing symbol. Off by
default (level 0). Nested headings in lists/quotes stay unnumbered,
matching upstream's document-level-only scan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 00:33:01 +00:00
gffranco a53ba167d6 fix(lsp): silence clippy too_many_arguments on render_page_html
CI / cargo fmt --check (push) Successful in 17s
CI / cargo clippy (push) Successful in 33s
CI / cargo test (push) Successful in 33s
CI / editor keymaps (push) Successful in 1m34s
Adding list_margin pushed render_page_html to 8 params, tripping
clippy::too_many_arguments under -D warnings in CI. Every parameter is a
distinct render input and the function has a single production caller, so
scope an #[allow] rather than introduce an options-struct abstraction.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 08:13:30 -03:00
gffranco c63ec679ae feat(lsp): wire diary/auto_toc/links_space_char/list_margin; drop client-side keys
Consume several per-wiki config keys that the server deserialized but
ignored, and remove keys whose effect is purely client-side:

- diagnostics: re-publish open-doc diagnostics on
  didChangeConfiguration so a link_severity change takes effect
  immediately; fix the single-key unwrap in apply_change so a minimal
  `{ diagnostic: {...} }` payload isn't mistaken for a namespace wrapper.
- auto_toc: rebuild an existing TOC section on save (new
  ops::toc_rebuild_edit, no-op when the page has no TOC).
- diary index: honour diary_header, diary_sort and diary_caption_level
  when rendering the diary index body.
- links_space_char: apply on rename so spaces in the link target and
  the on-disk path become the configured glyph (default " " = verbatim).
- list_margin: thread the per-wiki value into render_page_html.
- remove nested_syntaxes, maxhi and diary_start_week_day from the server
  config: nested-syntax and heading highlighting are client-side, and
  the weekly diary is ISO-week based so a custom week start has no clean
  server-side meaning.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 23:12:08 -03:00
gffranco 8ab6015405 Major clean up and improvements to vimwiki compatibility and documentation.
CI / cargo fmt --check (push) Successful in 33s
CI / cargo clippy (push) Successful in 23s
CI / cargo test (push) Successful in 33s
CI / editor keymaps (push) Successful in 1m25s
Reviewed-on: #1
2026-05-30 18:35:40 +00:00
gffranco e90bbab39e fix(export): render vimwiki-compatible HTML for templates, links, and tasks
Stock vimwiki templates and stylesheets rendered broken pages because the
HTML output diverged from vimwiki conventions on three fronts:

- Templates: the renderer only substituted nuwiki's `{{key}}` placeholders,
  so vimwiki's `%title%`/`%content%`/`%root_path%`/`%date%`/`%wiki_css%`
  passed through literally — dropping the body and breaking asset links.
  Both delimiter styles are now recognised; `%wiki_css%` aliases the css var.
- Links: `[[todo.wiki]]` exported to `todo.wiki.html`. render_page_html now
  takes the wiki file extension and strips it from wiki/interwiki targets
  (via index::strip_wiki_extension) before the `.html` URL is built, matching
  in-editor navigation. file:/local: links keep their literal extension.
- Tasks: checkbox items used bespoke `task-*` classes plus an `<input>`,
  which double-rendered against vimwiki stylesheets. Emit `done0..done4`
  (`[ ] [.] [o] [O] [X]`) and `rejected` (`[-]`) classes with no input — the
  stylesheet draws the box.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 21:19:55 -03:00
gffranco de8b8fa30d feat(13.1): colorize + color_dic → ColorNode renderer
CI / cargo fmt --check (push) Successful in 26s
CI / cargo clippy (push) Successful in 1m24s
CI / cargo test (push) Successful in 1m24s
CI / editor keymaps (push) Successful in 1m45s
Closes the last pending §13.1 entries:

- **`nuwiki.colorize`** (client-side, Lua + VimL) — wraps the word
  at cursor (or the visual selection on Neovim) in an inline
  `<span style="color:%c">…</span>` template. Matches vimwiki's
  default `color_tag_template`. Bound to `<Leader>wc` in both
  normal and visual mode on both editor paths; the previous
  "deferred" stub is gone.

- **`color_dic` → renderer** — `HtmlConfig` gains a
  `color_dic: HashMap<String, String>` field reading the same key
  out of `initializationOptions` / `didChangeConfiguration`. The
  HTML export path threads it into `HtmlRenderer::with_colors`;
  `ColorNode` now picks `style="color:<value>"` when the colour
  name is in the dict, falling back to the existing
  `class="color-<name>"` when it isn't.

SPEC §13.1 is fully checked off — the table moved from "deferred
sketch" to a per-command status table marking all 11 commands done,
plus a note on the `color_dic` follow-up landing alongside. README's
"Phase 14 list & table edit commands" row now reads  without the
deferred-subset caveat, and the §13.1-blocked text-objects note in
the keymaps section is rephrased as "planned follow-ups".

Tests: 4 new in `phase17_colorize.rs` covering the renderer
fallback when the dict is empty, the inline-style emission for a
listed name, the fallback path for an unlisted name in a populated
dict, and the `initializationOptions` JSON shape. Total 414 Rust
tests pass; clippy clean; keymap harness still 20/20.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:45:39 +00:00
gffranco d1b807b7d1 phase 17: html export commands + extended template vars
CI / cargo fmt --check (push) Successful in 23s
CI / cargo clippy (push) Successful in 1m26s
CI / cargo test (push) Successful in 1m16s
HtmlRenderer (nuwiki-core):
- `with_var(k, v)` / `with_vars(map)` add arbitrary `{{key}}`
  substitution alongside the existing `{{title}}` / `{{content}}`.
  Substitution order: content → title → user vars, so a rendered body
  that happens to contain a literal `{{title}}` isn't double-processed.

WikiConfig (nuwiki-lsp/config):
- New `html: HtmlConfig` field with per-wiki export options matching
  vimwiki's `g:vimwiki_*` keys: `html_path`, `template_path`,
  `template_default`, `template_ext`, `template_date_format`,
  `css_name`, `auto_export`, `html_filename_parameterization`,
  `exclude_files`. Defaults: `<root>/_html` and `<root>/_templates`.
- `RawWiki` accepts all of the above through `initializationOptions`
  (and `workspace/didChangeConfiguration` reload).

New `export` module (pure helpers):
- `output_path_for` — page name → on-disk HTML path. Slugifies only
  the final segment when `html_filename_parameterization = true` so
  the `diary/` subdir survives.
- `template_for` — primary/fallback path lookup tuple.
- `fallback_template` — minimal in-memory page used when neither
  template file exists.
- `format_date` — strftime subset (`%Y`/`%m`/`%d`/`%%`) so we don't
  depend on `chrono`/`time`.
- `build_toc_html` — nested `<ul class="toc">` from the doc's
  headings, with HTML escaping.
- `compute_root_path` — `../` prefix per directory depth for
  stylesheet hrefs in nested pages.
- `is_excluded` — glob matcher supporting `*`, `**`, `?`. Used by
  the `allToHtml*` walker against `exclude_files`.
- `render_page_html` — wires the above into HtmlRenderer with
  `{{date}}`, `{{root_path}}`, `{{toc}}`, `{{css}}` populated.
- `DEFAULT_CSS` — minimal stylesheet bundled for first-time exports.

New commands:
- `nuwiki.export.currentToHtml` — render the current buffer; honours
  `%nohtml` and `%template`; ensures CSS exists.
- `nuwiki.export.allToHtml` — walk the wiki root, skip pages matched
  by `exclude_files` and `%nohtml`, and only re-export pages whose
  source is newer than the existing HTML (incremental behaviour
  matching `:VimwikiAll2HTML`).
- `nuwiki.export.allToHtmlForce` — same but unconditional re-export.
- `nuwiki.export.browse` — alias of currentToHtml that returns the
  `file://` URL of the rendered page in the response so the client
  can open it.
- `nuwiki.export.rss` — write `<html_path>/rss.xml` listing the
  wiki's diary entries newest-first.
- `did_save` handler — runs the auto-export equivalent when the
  resolved wiki has `auto_export = true`.

LSP capability:
- `text_document_sync` switched from `Kind(FULL)` to `Options(...)`
  so the server can register a `save` notification (otherwise
  clients never push `did_save` events).

Tests: 33 new in `phase17_html_export.rs` covering HtmlConfig
defaults + JSON-shape config, the `format_date` subset (including
the `%%` escape and pass-through for unsupported specifiers),
root-path computation, output-path slugification rules, the glob
matcher (single-star, globstar, question-mark, non-match),
template_for primary/fallback selection, render_page_html for the
title/content/date/root_path triad and the override-by-extras flow,
and side-effecting disk paths (write_page creates parents, picks up
on-disk template, RSS is newest-first, ensure_css is idempotent).
Total 357 tests pass; fmt + clippy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 21:37:36 +00:00