parity(1): vimwiki per-wiki config keys, table colspan/rowspan,
named link commands, mouse maps
Parity audit Cluster 1 — small wins.
WikiConfig (server) extended with 14 vimwiki globals:
- `index` (`g:vimwiki_index`), wired into `:VimwikiIndex` so users
with a custom index page stem (e.g. `home`) get the right file.
- `diary_frequency`, `diary_start_week_day`, `diary_caption_level`,
`diary_sort`, `diary_header` — diary keys (weekly/monthly/yearly
semantics land in Cluster 4; the keys parse now so config
migration doesn't need to wait).
- `maxhi`, `listsyms`, `listsyms_propagate`, `list_margin`,
`links_space_char`, `nested_syntaxes`, `auto_toc` — list +
highlight knobs the renderer/handlers can consult.
All keys threaded through `RawWiki` → `WikiConfig::from(RawWiki)`,
defaults centralised in `wiki_defaults()` so `empty()`, `from_root()`,
the legacy single-wiki path, and the `RawWiki` impl stay in sync.
Lua front-door (`lua/nuwiki/config.lua`) now documents the multi-wiki
`wikis = {…}` shape with every accepted per-wiki key — users who
prefer Lua tables no longer have to round-trip through
`init_options` raw JSON.
HTML renderer (`crates/nuwiki-core/src/render/html.rs`):
- New `table_spans()` pre-pass resolves vimwiki's `>` (col_span) and
`\\/` (row_span) continuation markers into proper HTML
`colspan="N"` / `rowspan="N"` attributes on the lead cell. Continuation
cells emit nothing, matching what browsers expect.
- The old behaviour (emitting `class="col-span"` / `class="row-span"`
as visual markers) is gone — replaced with real merging so the
rendered HTML matches the source semantics.
Named ex-commands:
- `:VimwikiNextLink` / `:VimwikiPrevLink` — were keymapped only
(`<Tab>`/`<S-Tab>`); now have explicit `:` commands in both editor
paths, dispatching to new `nuwiki.commands.link_next` / `link_prev`
pure-Vim/Lua helpers.
- `:VimwikiBaddLink` — adds the link target's file to the buffer
list without switching focus. Goes through
`textDocument/definition` and runs `:badd <fname>` on the resolved
URI.
Mouse maps (opt-in via `mappings.mouse = true` in Lua or
`g:nuwiki_mouse_mappings = 1` in Vim):
- `<2-LeftMouse>` follows, `<S-2-LeftMouse>` / `<C-2-LeftMouse>`
follow in split/vsplit, `<MiddleMouse>` adds to buflist,
`<RightMouse>` goes back. Mirrors upstream vimwiki's defaults.
Tests: 7 new in `parity_cluster_1.rs` covering the per-wiki config
defaults + raw JSON parse, the legacy-single-wiki path, colspan
folding into a lead cell with no leftover class markers, rowspan
across rows, no-attrs when the table has no spans, and a sanity
paragraph render. Total 421 Rust tests pass; clippy clean; both
keymap harnesses still green (20 nvim + 12 vim).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -285,10 +285,16 @@ impl HtmlRenderer {
|
||||
}
|
||||
|
||||
fn render_table(&self, n: &TableNode, w: &mut dyn Write) -> io::Result<()> {
|
||||
// Pre-compute per-cell rowspan/colspan + suppression so we can
|
||||
// emit valid HTML colspan/rowspan attributes instead of CSS
|
||||
// class markers. `col_span` cells get folded into the colspan
|
||||
// of the lead cell to their left; `row_span` cells get folded
|
||||
// into the rowspan of the lead cell directly above.
|
||||
let layout = table_spans(n);
|
||||
w.write_all(b"<table>\n")?;
|
||||
let mut in_thead = false;
|
||||
let mut in_tbody = false;
|
||||
for row in &n.rows {
|
||||
for (row_idx, row) in n.rows.iter().enumerate() {
|
||||
if row.is_header {
|
||||
if !in_thead {
|
||||
w.write_all(b"<thead>\n")?;
|
||||
@@ -304,7 +310,7 @@ impl HtmlRenderer {
|
||||
in_tbody = true;
|
||||
}
|
||||
}
|
||||
self.render_table_row(row, w)?;
|
||||
self.render_table_row(row, row_idx, &layout, w)?;
|
||||
}
|
||||
if in_thead {
|
||||
w.write_all(b"</thead>\n")?;
|
||||
@@ -315,15 +321,29 @@ impl HtmlRenderer {
|
||||
w.write_all(b"</table>\n")
|
||||
}
|
||||
|
||||
fn render_table_row(&self, n: &TableRowNode, w: &mut dyn Write) -> io::Result<()> {
|
||||
fn render_table_row(
|
||||
&self,
|
||||
n: &TableRowNode,
|
||||
row_idx: usize,
|
||||
layout: &[Vec<CellLayout>],
|
||||
w: &mut dyn Write,
|
||||
) -> io::Result<()> {
|
||||
w.write_all(b"<tr>")?;
|
||||
for cell in &n.cells {
|
||||
// col_span / row_span here are *spec flags* meaning "this cell is
|
||||
// a continuation marker", which HTML expresses via the *previous*
|
||||
// cell's colspan/rowspan attrs. Computing those attrs requires
|
||||
// forward state on the renderer side; for now we emit class
|
||||
// markers so styling can show the span visually.
|
||||
self.render_table_cell(cell, n.is_header, w)?;
|
||||
for (col_idx, cell) in n.cells.iter().enumerate() {
|
||||
let info = layout
|
||||
.get(row_idx)
|
||||
.and_then(|r| r.get(col_idx))
|
||||
.copied()
|
||||
.unwrap_or(CellLayout::Lead {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
});
|
||||
match info {
|
||||
CellLayout::Skip => continue,
|
||||
CellLayout::Lead { colspan, rowspan } => {
|
||||
self.render_table_cell(cell, n.is_header, colspan, rowspan, w)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
w.write_all(b"</tr>\n")
|
||||
}
|
||||
@@ -332,15 +352,19 @@ impl HtmlRenderer {
|
||||
&self,
|
||||
n: &TableCellNode,
|
||||
is_header: bool,
|
||||
colspan: u32,
|
||||
rowspan: u32,
|
||||
w: &mut dyn Write,
|
||||
) -> io::Result<()> {
|
||||
let tag = if is_header { "th" } else { "td" };
|
||||
let class = match (n.col_span, n.row_span) {
|
||||
(true, _) => " class=\"col-span\"",
|
||||
(_, true) => " class=\"row-span\"",
|
||||
_ => "",
|
||||
};
|
||||
write!(w, "<{tag}{class}>")?;
|
||||
write!(w, "<{tag}")?;
|
||||
if colspan > 1 {
|
||||
write!(w, " colspan=\"{colspan}\"")?;
|
||||
}
|
||||
if rowspan > 1 {
|
||||
write!(w, " rowspan=\"{rowspan}\"")?;
|
||||
}
|
||||
write!(w, ">")?;
|
||||
self.render_inlines(&n.children, w)?;
|
||||
write!(w, "</{tag}>")
|
||||
}
|
||||
@@ -528,6 +552,85 @@ impl HtmlRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Table colspan/rowspan layout =====
|
||||
|
||||
/// Per-cell render decision: emit a lead cell with the computed spans,
|
||||
/// or skip (this cell is a continuation marker absorbed by its lead).
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum CellLayout {
|
||||
Lead { colspan: u32, rowspan: u32 },
|
||||
Skip,
|
||||
}
|
||||
|
||||
/// Walk every cell in the table and resolve `col_span` / `row_span`
|
||||
/// markers into HTML `colspan="N"` / `rowspan="N"` attributes on the
|
||||
/// preceding lead cell.
|
||||
///
|
||||
/// Rules:
|
||||
/// - `col_span: true` (`>` in vimwiki source) bumps the colspan of
|
||||
/// the lead cell to its *left* in the same row.
|
||||
/// - `row_span: true` (`\\/` in vimwiki source) bumps the rowspan
|
||||
/// of the lead cell *above* in the same column.
|
||||
/// - Markers that don't have a valid lead cell (first column /
|
||||
/// first row) render as a plain empty cell so the document still
|
||||
/// parses cleanly.
|
||||
fn table_spans(table: &TableNode) -> Vec<Vec<CellLayout>> {
|
||||
let rows = table.rows.len();
|
||||
if rows == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
let cols = table.rows.iter().map(|r| r.cells.len()).max().unwrap_or(0);
|
||||
let mut layout: Vec<Vec<CellLayout>> = (0..rows)
|
||||
.map(|_| {
|
||||
(0..cols)
|
||||
.map(|_| CellLayout::Lead {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
for (ri, row) in table.rows.iter().enumerate() {
|
||||
for (ci, cell) in row.cells.iter().enumerate() {
|
||||
if cell.col_span {
|
||||
// Find the lead cell to the left in this row (skipping
|
||||
// already-skipped continuation slots) and bump it.
|
||||
let mut k = ci;
|
||||
while k > 0 {
|
||||
k -= 1;
|
||||
match layout[ri][k] {
|
||||
CellLayout::Lead {
|
||||
ref mut colspan, ..
|
||||
} => {
|
||||
*colspan += 1;
|
||||
layout[ri][ci] = CellLayout::Skip;
|
||||
break;
|
||||
}
|
||||
CellLayout::Skip => continue,
|
||||
}
|
||||
}
|
||||
} else if cell.row_span {
|
||||
// Walk up in the same column for the lead cell.
|
||||
let mut k = ri;
|
||||
while k > 0 {
|
||||
k -= 1;
|
||||
match layout[k][ci] {
|
||||
CellLayout::Lead {
|
||||
ref mut rowspan, ..
|
||||
} => {
|
||||
*rowspan += 1;
|
||||
layout[ri][ci] = CellLayout::Skip;
|
||||
break;
|
||||
}
|
||||
CellLayout::Skip => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
layout
|
||||
}
|
||||
|
||||
// ===== Default link resolver =====
|
||||
|
||||
/// Default `LinkResolver`. Mirrors the conventions in SPEC.md §9:
|
||||
|
||||
Reference in New Issue
Block a user