fix(config): address P3 config-batch audit findings
CI / cargo fmt --check (push) Failing after 19s
CI / cargo clippy (push) Successful in 21s
CI / cargo test (push) Successful in 34s
CI / editor keymaps (push) Successful in 1m41s

Parallel-agent audit of the config batch found:

- cycle_bullets used find_marker_span's third element as an indent column, but
  it's the ABSOLUTE byte offset after the marker — so depth (and the rotated
  glyph) was wrong for any item not near offset 0 (tests passed by coincidence).
  Fixed to use the marker span's column (the indentation). Regression test now
  places the list deep in the document.

- :VimwikiColorize interpolated the colour into the template via an unescaped
  replacement: Lua gsub treats `%` specially, Vim substitute() treats `&`/`\`.
  Lua now uses a function replacement (verbatim); Vim escapes `\&~`.

- render_swapped_table applied table_reduce_last_col before the column swap, so
  a swap involving the last column moved the narrow slot — now clamped after.

- Fixed a stale toc_link_format doc comment (format 1 = [[#anchor]], not a
  full parent path).

Verified-correct by the audit (no change needed): RSS rfc822/cdata/fidelity,
prune_orphan_html reverse-mapping + guards, write_escaped_allowing/match_allowed_tag
(multibyte-safe), substitute_emoji (byte-boundary-safe), resolve_target_uri
create_link/dir_link ordering, all config defaults + From/constructors.

Full rust suite + clippy clean; Neovim 307, Vim 301/18/21.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 12:48:25 +00:00
parent f5420bd1da
commit 1246c99c3c
5 changed files with 23 additions and 9 deletions
+3 -1
View File
@@ -1089,7 +1089,9 @@ function! nuwiki#commands#colorize(...) abort
" __COLORFG__ replaced by the colour. Configurable via g:nuwiki_color_tag_template. " __COLORFG__ replaced by the colour. Configurable via g:nuwiki_color_tag_template.
let l:tpl = get(g:, 'nuwiki_color_tag_template', let l:tpl = get(g:, 'nuwiki_color_tag_template',
\ '<span style="color:__COLORFG__">__CONTENT__</span>') \ '<span style="color:__COLORFG__">__CONTENT__</span>')
let l:tpl = substitute(l:tpl, '__COLORFG__', l:color, 'g') " Escape `\`, `&`, `~` so a colour containing them isn't treated as a
" substitute() replacement metacharacter.
let l:tpl = substitute(l:tpl, '__COLORFG__', escape(l:color, '\&~'), 'g')
let l:parts = split(l:tpl, '__CONTENT__', 1) let l:parts = split(l:tpl, '__CONTENT__', 1)
let l:open_tag = get(l:parts, 0, '') let l:open_tag = get(l:parts, 0, '')
let l:close_tag = get(l:parts, 1, '') let l:close_tag = get(l:parts, 1, '')
+12 -3
View File
@@ -2725,9 +2725,11 @@ pub mod ops {
// vimwiki `cycle_bullets`: as an unordered item changes indent level, // vimwiki `cycle_bullets`: as an unordered item changes indent level,
// rotate its glyph through `bullet_types` (ring buffer keyed by depth). // rotate its glyph through `bullet_types` (ring buffer keyed by depth).
if cycle_bullets && !bullet_types.is_empty() { if cycle_bullets && !bullet_types.is_empty() {
if let Some((mspan, marker, col_off)) = find_marker_span(text, item.span) { if let Some((mspan, marker, _)) = find_marker_span(text, item.span) {
if marker.chars().count() == 1 && bullet_types.iter().any(|b| b == marker) { if marker.chars().count() == 1 && bullet_types.iter().any(|b| b == marker) {
let old_depth = (col_off / 2) as i32; // The marker's column is the item's indentation width (the
// item span starts at line start). 2 cols per depth level.
let old_depth = (mspan.start.column / 2) as i32;
let new_depth = (old_depth + delta).max(0) as usize; let new_depth = (old_depth + delta).max(0) as usize;
let glyph = &bullet_types[new_depth % bullet_types.len()]; let glyph = &bullet_types[new_depth % bullet_types.len()];
if glyph.as_str() != marker { if glyph.as_str() != marker {
@@ -3160,10 +3162,17 @@ pub mod ops {
) -> Option<String> { ) -> Option<String> {
// Swap the column-width slots first so the rendered table // Swap the column-width slots first so the rendered table
// remains visually aligned after the move. // remains visually aligned after the move.
let mut widths = column_widths(table, text, reduce_last_col); let mut widths = column_widths(table, text, false);
if a < widths.len() && b < widths.len() { if a < widths.len() && b < widths.len() {
widths.swap(a, b); widths.swap(a, b);
} }
// Apply reduce_last_col AFTER the swap so the column physically last
// stays minimal (clamping before the swap would move the narrow slot).
if reduce_last_col {
if let Some(last) = widths.last_mut() {
*last = 1;
}
}
let mut out = String::new(); let mut out = String::new();
for (row_idx, row) in table.rows.iter().enumerate() { for (row_idx, row) in table.rows.iter().enumerate() {
out.push('|'); out.push('|');
+1 -2
View File
@@ -124,8 +124,7 @@ pub struct WikiConfig {
/// `[[page|Heading]]` (first heading as caption) from `:VimwikiGenerateLinks`. /// `[[page|Heading]]` (first heading as caption) from `:VimwikiGenerateLinks`.
pub generated_links_caption: bool, pub generated_links_caption: bool,
/// vimwiki `toc_link_format` (default `0`): `0` = `[[#anchor|title]]` /// vimwiki `toc_link_format` (default `0`): `0` = `[[#anchor|title]]`
/// (leaf anchor, with description); `1` = `[[#parent#child]]` (full path, /// (with description); `1` = `[[#anchor]]` (anchor only, no description).
/// no description).
pub toc_link_format: u8, pub toc_link_format: u8,
/// vimwiki `table_reduce_last_col` (default `false`): don't pad the last /// vimwiki `table_reduce_last_col` (default `false`): don't pad the last
/// table column to fill — keep it at its minimum width. /// table column to fill — keep it at its minimum width.
+4 -2
View File
@@ -275,10 +275,12 @@ fn cycle_bullets_rotates_glyph_on_indent() {
// With cycle_bullets + bullet_types [-,*,#], indenting a `-` item (depth 0 // With cycle_bullets + bullet_types [-,*,#], indenting a `-` item (depth 0
// → 1) rotates the glyph to `*` (and dedenting back to `-`). // → 1) rotates the glyph to `*` (and dedenting back to `-`).
let bullets: Vec<String> = ["-", "*", "#"].iter().map(|s| s.to_string()).collect(); let bullets: Vec<String> = ["-", "*", "#"].iter().map(|s| s.to_string()).collect();
let src = "- one\n- two\n"; // Put the list well into the document so depth must come from the item's
// indentation, not its absolute byte offset (regression guard).
let src = "some intro paragraph text here\n\n- one\n- two\n";
let ast = parse(src); let ast = parse(src);
let edit = let edit =
ops::change_level_edit(src, &ast, &uri(), 1, 1, false, true, true, &bullets).expect("edit"); ops::change_level_edit(src, &ast, &uri(), 3, 1, false, true, true, &bullets).expect("edit");
let texts: Vec<String> = edit.changes.unwrap()[&uri()] let texts: Vec<String> = edit.changes.unwrap()[&uri()]
.iter() .iter()
.map(|e| e.new_text.clone()) .map(|e| e.new_text.clone())
+3 -1
View File
@@ -1219,7 +1219,9 @@ function M.colorize(color, visual)
local tpl = (require('nuwiki.config').options or {}).color_tag_template local tpl = (require('nuwiki.config').options or {}).color_tag_template
or vim.g.nuwiki_color_tag_template or vim.g.nuwiki_color_tag_template
or '<span style="color:__COLORFG__">__CONTENT__</span>' or '<span style="color:__COLORFG__">__CONTENT__</span>'
tpl = tpl:gsub('__COLORFG__', color) -- Function replacement returns the colour verbatim (a string replacement
-- would treat `%` specially).
tpl = tpl:gsub('__COLORFG__', function() return color end)
local open_tag, close_tag = tpl:match('^(.*)__CONTENT__(.*)$') local open_tag, close_tag = tpl:match('^(.*)__CONTENT__(.*)$')
if not open_tag then if not open_tag then
open_tag, close_tag = string.format('<span style="color:%s">', color), '</span>' open_tag, close_tag = string.format('<span style="color:%s">', color), '</span>'