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>
This commit is contained in:
2026-06-02 10:50:38 +00:00
parent 079e7246ac
commit 9441fe918c
19 changed files with 603 additions and 202 deletions
+70 -20
View File
@@ -290,7 +290,7 @@ fn build_toc_text_nests_by_level() {
(2u8, "Sub".into(), "sub".into()),
(1u8, "Other".into(), "other".into()),
];
let out = ops::build_toc_text(&items, "Contents", 1);
let out = ops::build_toc_text(&items, "Contents", 1, 0);
assert!(out.starts_with("= Contents =\n"));
let lines: Vec<&str> = out.lines().collect();
assert_eq!(lines[0], "= Contents =");
@@ -301,7 +301,7 @@ fn build_toc_text_nests_by_level() {
#[test]
fn build_toc_text_with_no_headings_is_just_the_heading() {
let out = ops::build_toc_text(&[], "Contents", 1);
let out = ops::build_toc_text(&[], "Contents", 1, 0);
assert_eq!(out, "= Contents =\n");
}
@@ -319,7 +319,8 @@ fn links_rebuild_edit_only_acts_when_section_present() {
&pages,
true,
"Generated Links",
1
1,
0
)
.is_none(),
"must not insert a links section into a page that lacks one"
@@ -333,7 +334,8 @@ fn links_rebuild_edit_only_acts_when_section_present() {
&pages,
true,
"Generated Links",
1
1,
0
)
.is_some());
}
@@ -358,15 +360,42 @@ fn find_section_range_stops_at_same_level_sibling() {
#[test]
fn captions_honour_custom_header_and_level() {
// toc_header="Table of Contents", level 2 → `== Table of Contents ==`.
let out = ops::build_toc_text(&[], "Table of Contents", 2);
let out = ops::build_toc_text(&[], "Table of Contents", 2, 0);
assert_eq!(out, "== Table of Contents ==\n");
// links_header at level 3.
let pages = vec!["A".to_string(), "B".to_string()];
let links = ops::build_links_text(&pages, "All Pages", 3, None);
let links = ops::build_links_text(&pages, "All Pages", 3, None, 0);
assert!(links.starts_with("=== All Pages ===\n"));
// level clamps to 1..=6.
assert!(ops::build_toc_text(&[], "X", 9).starts_with("====== X ======"));
assert!(ops::build_toc_text(&[], "X", 0).starts_with("= X ="));
assert!(ops::build_toc_text(&[], "X", 9, 0).starts_with("====== X ======"));
assert!(ops::build_toc_text(&[], "X", 0, 0).starts_with("= X ="));
}
#[test]
fn list_margin_indents_generated_bullets_not_headings() {
// vimwiki `list_margin` = leading spaces before each generated bullet.
// The heading stays at column 0; bullets get `margin` spaces (TOC keeps
// its per-depth nesting *after* the base margin).
let pages = vec!["A".to_string(), "B".to_string()];
let links = ops::build_links_text(&pages, "Generated Links", 1, None, 2);
assert!(links.contains("\n - [[A]]\n"), "2-space margin: {links:?}");
assert!(
links.starts_with("= Generated Links =\n"),
"heading unindented: {links:?}"
);
let toc = ops::build_toc_text(
&[
(1, "Top".into(), "top".into()),
(2, "Sub".into(), "sub".into()),
],
"Contents",
1,
2,
);
// Top bullet: 2-space margin; Sub bullet: margin + one nesting level.
assert!(toc.contains("\n - [[#top|Top]]\n"), "top margin: {toc:?}");
assert!(toc.contains("\n - [[#sub|Sub]]\n"), "nested: {toc:?}");
}
// ===== Links text generation =====
@@ -374,7 +403,7 @@ fn captions_honour_custom_header_and_level() {
#[test]
fn build_links_text_excludes_current_page() {
let pages = vec!["A".to_string(), "Home".to_string(), "B".to_string()];
let out = ops::build_links_text(&pages, "Generated Links", 1, Some("Home"));
let out = ops::build_links_text(&pages, "Generated Links", 1, Some("Home"), 0);
let lines: Vec<&str> = out.lines().collect();
assert_eq!(lines[0], "= Generated Links =");
assert!(lines.contains(&"- [[A]]"));
@@ -385,7 +414,7 @@ fn build_links_text_excludes_current_page() {
#[test]
fn build_links_text_no_excludes_includes_all() {
let pages = vec!["A".to_string(), "B".to_string()];
let out = ops::build_links_text(&pages, "Generated Links", 1, None);
let out = ops::build_links_text(&pages, "Generated Links", 1, None, 0);
assert!(out.contains("[[A]]"));
assert!(out.contains("[[B]]"));
}
@@ -423,7 +452,7 @@ fn toc_edit_inserts_at_top_when_no_existing_toc() {
let src = "= One =\n== Two ==\n";
let ast = parse(src);
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
let edit = ops::toc_edit(src, &ast, &uri, true, "Contents", 1).expect("got an edit");
let edit = ops::toc_edit(src, &ast, &uri, true, "Contents", 1, 0).expect("got an edit");
let changes = edit.changes.expect("changes map");
let edits = &changes[&uri];
assert_eq!(edits.len(), 1);
@@ -439,7 +468,7 @@ fn toc_edit_replaces_existing_toc() {
let src = "= Contents =\n- [[#stale|Stale]]\n\n= Real =\n";
let ast = parse(src);
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
let edit = ops::toc_edit(src, &ast, &uri, true, "Contents", 1).expect("got an edit");
let edit = ops::toc_edit(src, &ast, &uri, true, "Contents", 1, 0).expect("got an edit");
let changes = edit.changes.expect("changes map");
let edits = &changes[&uri];
let te = &edits[0];
@@ -454,7 +483,7 @@ fn toc_edit_returns_none_for_empty_doc() {
let src = "";
let ast = parse(src);
let uri = Url::parse("file:///tmp/empty.wiki").unwrap();
assert!(ops::toc_edit(src, &ast, &uri, true, "Contents", 1).is_none());
assert!(ops::toc_edit(src, &ast, &uri, true, "Contents", 1, 0).is_none());
}
#[test]
@@ -465,14 +494,15 @@ fn toc_rebuild_edit_only_acts_when_toc_already_present() {
let without = "= One =\n== Two ==\n";
let ast = parse(without);
assert!(
ops::toc_rebuild_edit(without, &ast, &uri, true, "Contents", 1).is_none(),
ops::toc_rebuild_edit(without, &ast, &uri, true, "Contents", 1, 0).is_none(),
"auto_toc should not insert a TOC where none existed"
);
// Existing TOC → rebuild refreshes it.
let with = "= Contents =\n- [[#stale|Stale]]\n\n= Real =\n";
let ast = parse(with);
let edit = ops::toc_rebuild_edit(with, &ast, &uri, true, "Contents", 1).expect("rebuild edit");
let edit =
ops::toc_rebuild_edit(with, &ast, &uri, true, "Contents", 1, 0).expect("rebuild edit");
let te = &edit.changes.unwrap()[&uri][0];
assert!(te.new_text.contains("[[#real|Real]]"));
assert!(!te.new_text.contains("Stale"));
@@ -486,8 +516,18 @@ fn links_edit_inserts_when_section_absent() {
let ast = parse(src);
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
let pages = vec!["A".into(), "B".into(), "Home".into()];
let edit =
ops::links_edit(src, &ast, &uri, "Home", &pages, true, "Generated Links", 1).expect("edit");
let edit = ops::links_edit(
src,
&ast,
&uri,
"Home",
&pages,
true,
"Generated Links",
1,
0,
)
.expect("edit");
let te = &edit.changes.unwrap()[&uri][0];
assert!(te.new_text.contains("[[A]]"));
assert!(te.new_text.contains("[[B]]"));
@@ -500,8 +540,18 @@ fn links_edit_replaces_when_section_present() {
let ast = parse(src);
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
let pages = vec!["Fresh".into()];
let edit =
ops::links_edit(src, &ast, &uri, "Home", &pages, true, "Generated Links", 1).expect("edit");
let edit = ops::links_edit(
src,
&ast,
&uri,
"Home",
&pages,
true,
"Generated Links",
1,
0,
)
.expect("edit");
let te = &edit.changes.unwrap()[&uri][0];
assert!(te.new_text.contains("[[Fresh]]"));
assert!(!te.new_text.contains("Stale"));
@@ -513,7 +563,7 @@ fn links_edit_returns_none_for_empty_page_list() {
let src = "Hi\n";
let ast = parse(src);
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
assert!(ops::links_edit(src, &ast, &uri, "Home", &[], true, "Generated Links", 1).is_none());
assert!(ops::links_edit(src, &ast, &uri, "Home", &[], true, "Generated Links", 1, 0).is_none());
}
// ===== find_orphans =====