fix(parity): close the 2026-06-02 re-audit config/command findings
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:
@@ -334,8 +334,12 @@ fn toc_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceE
|
||||
.as_ref()
|
||||
.map(|w| (w.config.toc_header.clone(), w.config.toc_header_level))
|
||||
.unwrap_or_else(|| (ops::TOC_HEADING.to_string(), 1));
|
||||
let margin = wiki
|
||||
.as_ref()
|
||||
.map(|w| w.config.list_margin.max(0) as usize)
|
||||
.unwrap_or(0);
|
||||
Ok(ops::toc_edit(
|
||||
&doc.text, &doc.ast, &p.uri, utf8, &header, level,
|
||||
&doc.text, &doc.ast, &p.uri, utf8, &header, level, margin,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -367,6 +371,7 @@ fn links_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<Workspac
|
||||
utf8,
|
||||
&wiki.config.links_header,
|
||||
wiki.config.links_header_level,
|
||||
wiki.config.list_margin.max(0) as usize,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -639,6 +644,7 @@ fn tags_generate_links(
|
||||
utf8,
|
||||
&wiki.config.tags_header,
|
||||
wiki.config.tags_header_level,
|
||||
wiki.config.list_margin.max(0) as usize,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -888,6 +894,9 @@ fn list_remove_done(backend: &Backend, args: Vec<Value>) -> Result<Option<Worksp
|
||||
uri: Option<Url>,
|
||||
#[serde(default)]
|
||||
position: Option<LspPosition>,
|
||||
/// `[line1, line2]` (0-indexed, inclusive) for the ranged form.
|
||||
#[serde(default)]
|
||||
range: Option<[u32; 2]>,
|
||||
}
|
||||
let parsed: Args = serde_json::from_value(raw).map_err(|e| format!("invalid args: {e}"))?;
|
||||
let uri = parsed
|
||||
@@ -899,12 +908,16 @@ fn list_remove_done(backend: &Backend, args: Vec<Value>) -> Result<Option<Worksp
|
||||
None => return Ok(None),
|
||||
};
|
||||
let utf8 = backend.use_utf8.load(Ordering::Relaxed);
|
||||
// A range takes precedence over a cursor position; the client sends at
|
||||
// most one (range for `:'<,'>RemoveDone`, position for the current list).
|
||||
let range = parsed.range.map(|[a, b]| (a.min(b), a.max(b)));
|
||||
let pos = if range.is_some() {
|
||||
None
|
||||
} else {
|
||||
parsed.position
|
||||
};
|
||||
Ok(ops::remove_done_edit(
|
||||
&doc.text,
|
||||
&doc.ast,
|
||||
&uri,
|
||||
parsed.position,
|
||||
utf8,
|
||||
&doc.text, &doc.ast, &uri, pos, range, utf8,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1814,14 +1827,21 @@ pub mod ops {
|
||||
/// Build a heading + nested list TOC for the current document. The TOC
|
||||
/// itself (any existing `= Contents =` heading on the page) is skipped
|
||||
/// so re-generation is idempotent.
|
||||
pub fn build_toc_text(items: &[(u8, String, String)], heading_name: &str, level: u8) -> String {
|
||||
pub fn build_toc_text(
|
||||
items: &[(u8, String, String)],
|
||||
heading_name: &str,
|
||||
level: u8,
|
||||
margin: usize,
|
||||
) -> String {
|
||||
let mut out = caption_line(heading_name, level);
|
||||
if items.is_empty() {
|
||||
return out;
|
||||
}
|
||||
let pad = " ".repeat(margin);
|
||||
let min_level = items.iter().map(|(l, _, _)| *l).min().unwrap_or(1);
|
||||
for (level, title, anchor) in items {
|
||||
let depth = level.saturating_sub(min_level) as usize;
|
||||
out.push_str(&pad);
|
||||
for _ in 0..depth {
|
||||
out.push_str(" ");
|
||||
}
|
||||
@@ -1841,12 +1861,15 @@ pub mod ops {
|
||||
heading_name: &str,
|
||||
level: u8,
|
||||
exclude: Option<&str>,
|
||||
margin: usize,
|
||||
) -> String {
|
||||
let mut out = caption_line(heading_name, level);
|
||||
let pad = " ".repeat(margin);
|
||||
for name in pages {
|
||||
if Some(name.as_str()) == exclude {
|
||||
continue;
|
||||
}
|
||||
out.push_str(&pad);
|
||||
out.push_str("- [[");
|
||||
out.push_str(name);
|
||||
out.push_str("]]\n");
|
||||
@@ -1966,6 +1989,7 @@ pub mod ops {
|
||||
/// Produce a `WorkspaceEdit` that replaces or inserts the TOC for the
|
||||
/// given document. Returns `None` if the document has no headings at
|
||||
/// all (other than possibly a pre-existing TOC heading).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn toc_edit(
|
||||
text: &str,
|
||||
ast: &DocumentNode,
|
||||
@@ -1973,6 +1997,7 @@ pub mod ops {
|
||||
utf8: bool,
|
||||
heading: &str,
|
||||
level: u8,
|
||||
margin: usize,
|
||||
) -> Option<WorkspaceEdit> {
|
||||
let items = collect_toc_items(ast, heading);
|
||||
if items.is_empty() {
|
||||
@@ -1982,7 +2007,7 @@ pub mod ops {
|
||||
.into_iter()
|
||||
.map(|it| (it.level, it.title, it.anchor))
|
||||
.collect();
|
||||
let new_text = build_toc_text(&triples, heading, level);
|
||||
let new_text = build_toc_text(&triples, heading, level, margin);
|
||||
let mut b = WorkspaceEditBuilder::new();
|
||||
match find_section_range(ast, heading) {
|
||||
Some((start, end)) => {
|
||||
@@ -2010,6 +2035,7 @@ pub mod ops {
|
||||
/// `= Contents =` section — used by the `auto_toc`-on-save hook so we
|
||||
/// rebuild an existing TOC without inserting one into pages that never
|
||||
/// had one.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn toc_rebuild_edit(
|
||||
text: &str,
|
||||
ast: &DocumentNode,
|
||||
@@ -2017,9 +2043,10 @@ pub mod ops {
|
||||
utf8: bool,
|
||||
heading: &str,
|
||||
level: u8,
|
||||
margin: usize,
|
||||
) -> Option<WorkspaceEdit> {
|
||||
find_section_range(ast, heading)?;
|
||||
toc_edit(text, ast, uri, utf8, heading, level)
|
||||
toc_edit(text, ast, uri, utf8, heading, level, margin)
|
||||
}
|
||||
|
||||
/// Produce a `WorkspaceEdit` that replaces or inserts the
|
||||
@@ -2034,11 +2061,12 @@ pub mod ops {
|
||||
utf8: bool,
|
||||
heading: &str,
|
||||
level: u8,
|
||||
margin: usize,
|
||||
) -> Option<WorkspaceEdit> {
|
||||
if all_pages.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let new_text = build_links_text(all_pages, heading, level, Some(current_page));
|
||||
let new_text = build_links_text(all_pages, heading, level, Some(current_page), margin);
|
||||
let mut b = WorkspaceEditBuilder::new();
|
||||
match find_section_range(ast, heading) {
|
||||
Some((start, end)) => {
|
||||
@@ -2067,6 +2095,7 @@ pub mod ops {
|
||||
utf8: bool,
|
||||
heading: &str,
|
||||
level: u8,
|
||||
margin: usize,
|
||||
) -> Option<WorkspaceEdit> {
|
||||
find_section_range(ast, heading)?;
|
||||
links_edit(
|
||||
@@ -2078,6 +2107,7 @@ pub mod ops {
|
||||
utf8,
|
||||
heading,
|
||||
level,
|
||||
margin,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2291,13 +2321,17 @@ pub mod ops {
|
||||
/// Delete every checkbox item whose state is `Done` or `Rejected`,
|
||||
/// cascading into their sublists. When `pos` is `Some`, restrict to
|
||||
/// the contiguous list block containing `pos`'s line (the "current
|
||||
/// list" — including its nested sublists); otherwise sweep the whole
|
||||
/// doc.
|
||||
/// list" — including its nested sublists). When `range` is `Some`
|
||||
/// (`(line1, line2)`, 0-indexed inclusive), sweep the whole doc but keep
|
||||
/// only items whose start line falls in that range — the vimwiki
|
||||
/// `:'<,'>VimwikiRemoveDone` ranged form. With neither, sweep the whole
|
||||
/// doc. `pos` and `range` are mutually exclusive (the client sets one).
|
||||
pub fn remove_done_edit(
|
||||
text: &str,
|
||||
ast: &DocumentNode,
|
||||
uri: &Url,
|
||||
pos: Option<LspPosition>,
|
||||
range: Option<(u32, u32)>,
|
||||
utf8: bool,
|
||||
) -> Option<WorkspaceEdit> {
|
||||
let mut victims: Vec<Span> = Vec::new();
|
||||
@@ -2307,6 +2341,12 @@ pub mod ops {
|
||||
Some(nuwiki_core::ast::CheckboxState::Done)
|
||||
| Some(nuwiki_core::ast::CheckboxState::Rejected),
|
||||
) {
|
||||
if let Some((l1, l2)) = range {
|
||||
let line = item.span.start.line;
|
||||
if line < l1 || line > l2 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
victims.push(extend_to_line_end(text, item.span));
|
||||
}
|
||||
};
|
||||
@@ -3187,7 +3227,9 @@ pub mod ops {
|
||||
tag: Option<&str>,
|
||||
header: &str,
|
||||
level: u8,
|
||||
margin: usize,
|
||||
) -> Option<String> {
|
||||
let pad = " ".repeat(margin);
|
||||
match tag {
|
||||
Some(t) => {
|
||||
let pages = pages_by_tag.get(t)?;
|
||||
@@ -3196,7 +3238,7 @@ pub mod ops {
|
||||
}
|
||||
let mut out = caption_line(&format!("Tag: {t}"), level);
|
||||
for p in pages {
|
||||
out.push_str(&format!("- [[{p}]]\n"));
|
||||
out.push_str(&format!("{pad}- [[{p}]]\n"));
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
@@ -3210,7 +3252,7 @@ pub mod ops {
|
||||
out.push('\n');
|
||||
out.push_str(&caption_line(name, sub));
|
||||
for p in pages {
|
||||
out.push_str(&format!("- [[{p}]]\n"));
|
||||
out.push_str(&format!("{pad}- [[{p}]]\n"));
|
||||
}
|
||||
}
|
||||
Some(out)
|
||||
@@ -3242,8 +3284,9 @@ pub mod ops {
|
||||
utf8: bool,
|
||||
header: &str,
|
||||
level: u8,
|
||||
margin: usize,
|
||||
) -> Option<WorkspaceEdit> {
|
||||
let body = build_tag_links_text(pages_by_tag, tag, header, level)?;
|
||||
let body = build_tag_links_text(pages_by_tag, tag, header, level, margin)?;
|
||||
let heading = tag_section_heading(tag, header);
|
||||
let mut b = WorkspaceEditBuilder::new();
|
||||
match find_section_range(ast, &heading) {
|
||||
@@ -3263,6 +3306,7 @@ pub mod ops {
|
||||
/// Like [`tag_links_edit`] for the no-arg index, but only when a
|
||||
/// `Generated Tags` section already exists — the `auto_generate_tags`
|
||||
/// on-save hook. Never inserts one into a page that lacks it.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn tag_links_rebuild_edit(
|
||||
text: &str,
|
||||
ast: &DocumentNode,
|
||||
@@ -3271,9 +3315,20 @@ pub mod ops {
|
||||
utf8: bool,
|
||||
header: &str,
|
||||
level: u8,
|
||||
margin: usize,
|
||||
) -> Option<WorkspaceEdit> {
|
||||
find_section_range(ast, header)?;
|
||||
tag_links_edit(text, ast, uri, None, pages_by_tag, utf8, header, level)
|
||||
tag_links_edit(
|
||||
text,
|
||||
ast,
|
||||
uri,
|
||||
None,
|
||||
pages_by_tag,
|
||||
utf8,
|
||||
header,
|
||||
level,
|
||||
margin,
|
||||
)
|
||||
}
|
||||
|
||||
// Keep the import live for downstream use even when the local module
|
||||
@@ -3378,7 +3433,6 @@ pub mod export_ops {
|
||||
DiaryDate::today_utc(),
|
||||
&cfg.html,
|
||||
Some(cfg.file_extension.as_str()),
|
||||
cfg.list_margin,
|
||||
std::collections::HashMap::new(),
|
||||
)?;
|
||||
let out_path = output_path_for(&cfg.html, name);
|
||||
|
||||
@@ -49,13 +49,21 @@ pub struct WikiConfig {
|
||||
/// Weekday the diary week begins on (`monday`..`sunday`, vimwiki's
|
||||
/// `diary_start_week_day`). Used only when `diary_weekly_style = date`.
|
||||
pub diary_start_week_day: String,
|
||||
/// Caption level for the diary index page headings (1 = h1).
|
||||
pub diary_caption_level: u8,
|
||||
/// Caption level for the diary index page headings (1 = h1). vimwiki's
|
||||
/// `diary_caption_level` (`min: -1`). nuwiki uses it as the base level of
|
||||
/// the year/month index tree; values `< 0` clamp to `0` (upstream's `-1`
|
||||
/// "read no per-page captions" mode isn't modelled — nuwiki builds the
|
||||
/// tree from dates, not page headers).
|
||||
pub diary_caption_level: i8,
|
||||
/// Newest-first (`desc`) or oldest-first (`asc`) in the diary
|
||||
/// index.
|
||||
pub diary_sort: String,
|
||||
/// H1 text for the diary index page.
|
||||
pub diary_header: String,
|
||||
/// vimwiki `diary_months`: month-number → display name for the diary
|
||||
/// index tree. Twelve entries (January..December) by default; a custom
|
||||
/// list falls back to the English name for any missing/out-of-range slot.
|
||||
pub diary_months: Vec<String>,
|
||||
/// Vimwiki's `g:vimwiki_listsyms`. A progression of checkbox glyphs
|
||||
/// from "empty" (first) to "done" (last); the lexer recognises these
|
||||
/// glyphs and the toggle/cycle commands walk them. Any length ≥ 2 is
|
||||
@@ -66,8 +74,12 @@ pub struct WikiConfig {
|
||||
pub listsym_rejected: String,
|
||||
/// When toggling a parent list item, also cascade to descendants.
|
||||
pub listsyms_propagate: bool,
|
||||
/// `-1` keeps tight lists; positive integers indent list items by
|
||||
/// that many extra columns. Layout hint for the renderer.
|
||||
/// vimwiki `list_margin`: number of leading spaces before bullets in
|
||||
/// **buffer-side generated lists** — `:VimwikiGenerateLinks`, the TOC, the
|
||||
/// tags index, and the diary index. `>= 0` indents by that many spaces;
|
||||
/// `< 0` means "no margin" (default `-1`; `0` for markdown wikis).
|
||||
/// Divergence: upstream resolves `< 0` to the buffer's `'shiftwidth'`,
|
||||
/// which the server can't observe, so negatives collapse to zero indent.
|
||||
pub list_margin: i32,
|
||||
/// Character used in place of literal spaces inside wikilink
|
||||
/// targets when writing to disk (a single space `" "` by default,
|
||||
@@ -229,6 +241,7 @@ impl WikiConfig {
|
||||
diary_caption_level: d.diary_caption_level,
|
||||
diary_sort: d.diary_sort,
|
||||
diary_header: d.diary_header,
|
||||
diary_months: d.diary_months,
|
||||
listsyms: d.listsyms,
|
||||
listsym_rejected: d.listsym_rejected,
|
||||
listsyms_propagate: d.listsyms_propagate,
|
||||
@@ -268,6 +281,7 @@ impl WikiConfig {
|
||||
diary_caption_level: d.diary_caption_level,
|
||||
diary_sort: d.diary_sort,
|
||||
diary_header: d.diary_header,
|
||||
diary_months: d.diary_months,
|
||||
listsyms: d.listsyms,
|
||||
listsym_rejected: d.listsym_rejected,
|
||||
listsyms_propagate: d.listsyms_propagate,
|
||||
@@ -371,6 +385,27 @@ fn default_diary_header() -> String {
|
||||
"Diary".to_string()
|
||||
}
|
||||
|
||||
/// vimwiki's default `diary_months` — the English month names, January..December.
|
||||
fn default_diary_months() -> Vec<String> {
|
||||
[
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
]
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn default_listsyms() -> String {
|
||||
" .oOX".to_string()
|
||||
}
|
||||
@@ -411,6 +446,7 @@ fn wiki_defaults() -> WikiDefaults {
|
||||
diary_caption_level: 0,
|
||||
diary_sort: default_diary_sort(),
|
||||
diary_header: default_diary_header(),
|
||||
diary_months: default_diary_months(),
|
||||
listsyms: default_listsyms(),
|
||||
listsym_rejected: default_listsym_rejected(),
|
||||
listsyms_propagate: true,
|
||||
@@ -436,9 +472,10 @@ struct WikiDefaults {
|
||||
diary_frequency: String,
|
||||
diary_weekly_style: String,
|
||||
diary_start_week_day: String,
|
||||
diary_caption_level: u8,
|
||||
diary_caption_level: i8,
|
||||
diary_sort: String,
|
||||
diary_header: String,
|
||||
diary_months: Vec<String>,
|
||||
listsyms: String,
|
||||
listsym_rejected: String,
|
||||
listsyms_propagate: bool,
|
||||
@@ -673,12 +710,14 @@ struct RawWiki {
|
||||
#[serde(default)]
|
||||
diary_start_week_day: Option<String>,
|
||||
#[serde(default)]
|
||||
diary_caption_level: Option<u8>,
|
||||
diary_caption_level: Option<i8>,
|
||||
#[serde(default)]
|
||||
diary_sort: Option<String>,
|
||||
#[serde(default)]
|
||||
diary_header: Option<String>,
|
||||
#[serde(default)]
|
||||
diary_months: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
listsyms: Option<String>,
|
||||
#[serde(default)]
|
||||
listsym_rejected: Option<String>,
|
||||
@@ -764,11 +803,19 @@ impl From<RawWiki> for WikiConfig {
|
||||
html.html_header_numbering_sym = s;
|
||||
}
|
||||
let d = wiki_defaults();
|
||||
let syntax = r.syntax.unwrap_or_else(|| "vimwiki".into());
|
||||
// vimwiki derives `list_margin = 0` for markdown wikis when the key is
|
||||
// unset (vars.vim:651); other syntaxes keep the `-1` default.
|
||||
let list_margin = r.list_margin.unwrap_or(if syntax == "markdown" {
|
||||
0
|
||||
} else {
|
||||
d.list_margin
|
||||
});
|
||||
WikiConfig {
|
||||
name: r.name.unwrap_or(derived_name),
|
||||
root,
|
||||
file_extension: r.file_extension.unwrap_or_else(|| ".wiki".into()),
|
||||
syntax: r.syntax.unwrap_or_else(|| "vimwiki".into()),
|
||||
syntax,
|
||||
index: r.index.unwrap_or(d.index),
|
||||
diary_rel_path: r.diary_rel_path.unwrap_or(d.diary_rel_path),
|
||||
diary_index: r.diary_index.unwrap_or(d.diary_index),
|
||||
@@ -778,10 +825,11 @@ impl From<RawWiki> for WikiConfig {
|
||||
diary_caption_level: r.diary_caption_level.unwrap_or(d.diary_caption_level),
|
||||
diary_sort: r.diary_sort.unwrap_or(d.diary_sort),
|
||||
diary_header: r.diary_header.unwrap_or(d.diary_header),
|
||||
diary_months: r.diary_months.unwrap_or(d.diary_months),
|
||||
listsyms: r.listsyms.unwrap_or(d.listsyms),
|
||||
listsym_rejected: r.listsym_rejected.unwrap_or(d.listsym_rejected),
|
||||
listsyms_propagate: r.listsyms_propagate.unwrap_or(d.listsyms_propagate),
|
||||
list_margin: r.list_margin.unwrap_or(d.list_margin),
|
||||
list_margin,
|
||||
links_space_char: r.links_space_char.unwrap_or(d.links_space_char),
|
||||
auto_toc: r.auto_toc.unwrap_or(d.auto_toc),
|
||||
auto_generate_links: r.auto_generate_links.unwrap_or(d.auto_generate_links),
|
||||
|
||||
@@ -202,17 +202,27 @@ fn heading(level: u8, text: &str) -> String {
|
||||
/// subheadings so the rendered page mirrors `:VimwikiDiaryGenerateLinks`.
|
||||
///
|
||||
/// `caption_level` sets the level of the top caption; the year and month
|
||||
/// subheadings nest one and two levels below it. `sort` controls whether
|
||||
/// the flat list runs newest- or oldest-first.
|
||||
/// subheadings nest one and two levels below it. A `caption_level < 0`
|
||||
/// clamps to `0` (nuwiki builds the tree from dates, so upstream's `-1`
|
||||
/// "no per-page captions" mode doesn't apply). `months` supplies the
|
||||
/// month-number → display name table; an empty slice (or a short list)
|
||||
/// falls back to the English `month_name` for any missing slot. `sort`
|
||||
/// controls whether the flat list runs newest- or oldest-first. `margin`
|
||||
/// is the number of leading spaces before each entry bullet (vimwiki's
|
||||
/// `list_margin`).
|
||||
pub fn build_index_body(
|
||||
entries: &[DiaryEntry],
|
||||
diary_rel_path: &str,
|
||||
index_heading: &str,
|
||||
sort: DiarySort,
|
||||
caption_level: u8,
|
||||
caption_level: i8,
|
||||
months: &[String],
|
||||
margin: usize,
|
||||
) -> String {
|
||||
let base_level = caption_level.max(0) as u8;
|
||||
let pad = " ".repeat(margin);
|
||||
let mut out = String::new();
|
||||
out.push_str(&heading(caption_level, index_heading));
|
||||
out.push_str(&heading(base_level, index_heading));
|
||||
out.push('\n');
|
||||
|
||||
if entries.is_empty() {
|
||||
@@ -225,8 +235,8 @@ pub fn build_index_body(
|
||||
DiarySort::Asc => a.date.cmp(&b.date),
|
||||
});
|
||||
|
||||
let year_level = caption_level.saturating_add(1);
|
||||
let month_level = caption_level.saturating_add(2);
|
||||
let year_level = base_level.saturating_add(1);
|
||||
let month_level = base_level.saturating_add(2);
|
||||
let mut current_year: Option<i32> = None;
|
||||
let mut current_month: Option<u8> = None;
|
||||
for e in sorted {
|
||||
@@ -238,12 +248,17 @@ pub fn build_index_body(
|
||||
current_month = None;
|
||||
}
|
||||
if current_month != Some(e.date.month) {
|
||||
out.push_str(&heading(month_level, month_name(e.date.month)));
|
||||
let label = months
|
||||
.get((e.date.month.saturating_sub(1)) as usize)
|
||||
.map(String::as_str)
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or_else(|| month_name(e.date.month));
|
||||
out.push_str(&heading(month_level, label));
|
||||
out.push('\n');
|
||||
current_month = Some(e.date.month);
|
||||
}
|
||||
out.push_str(&format!(
|
||||
"- [[{}/{}]]\n",
|
||||
"{pad}- [[{}/{}]]\n",
|
||||
diary_rel_path.trim_end_matches('/'),
|
||||
e.date.format()
|
||||
));
|
||||
@@ -253,7 +268,8 @@ pub fn build_index_body(
|
||||
|
||||
/// Render the diary-index body for the given wiki's currently-indexed
|
||||
/// entries. Convenience wrapper around [`build_index_body`] that honours
|
||||
/// the wiki's `diary_header`, `diary_sort`, and `diary_caption_level`.
|
||||
/// the wiki's `diary_header`, `diary_sort`, `diary_caption_level`, and
|
||||
/// `diary_months`.
|
||||
pub fn render_index_body(cfg: &WikiConfig, index: &WorkspaceIndex) -> String {
|
||||
build_index_body(
|
||||
&list_entries(index),
|
||||
@@ -261,6 +277,8 @@ pub fn render_index_body(cfg: &WikiConfig, index: &WorkspaceIndex) -> String {
|
||||
&cfg.diary_header,
|
||||
DiarySort::parse(&cfg.diary_sort),
|
||||
cfg.diary_caption_level,
|
||||
&cfg.diary_months,
|
||||
cfg.list_margin.max(0) as usize,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -203,7 +203,6 @@ pub fn render_page_html(
|
||||
today: DiaryDate,
|
||||
cfg: &HtmlConfig,
|
||||
wiki_extension: Option<&str>,
|
||||
list_margin: i32,
|
||||
extra_vars: HashMap<String, String>,
|
||||
) -> std::io::Result<String> {
|
||||
let date_str = match doc.metadata.date.as_deref() {
|
||||
@@ -228,7 +227,7 @@ pub fn render_page_html(
|
||||
vars.insert(k, v);
|
||||
}
|
||||
|
||||
let mut r = HtmlRenderer::new().with_list_margin(list_margin);
|
||||
let mut r = HtmlRenderer::new();
|
||||
if let Some(ext) = wiki_extension.filter(|e| !e.trim_start_matches('.').is_empty()) {
|
||||
// Strip the wiki extension from page links before the default resolver
|
||||
// turns them into `.html` URLs, so `[[todo.wiki]]` → `todo.html` rather
|
||||
|
||||
@@ -627,6 +627,7 @@ impl LanguageServer for Backend {
|
||||
self.use_utf8.load(Ordering::Relaxed),
|
||||
&wiki.config.toc_header,
|
||||
wiki.config.toc_header_level,
|
||||
wiki.config.list_margin.max(0) as usize,
|
||||
)
|
||||
})
|
||||
};
|
||||
@@ -656,6 +657,7 @@ impl LanguageServer for Backend {
|
||||
utf8,
|
||||
&wiki.config.links_header,
|
||||
wiki.config.links_header_level,
|
||||
wiki.config.list_margin.max(0) as usize,
|
||||
)
|
||||
});
|
||||
if let Some(edit) = edit {
|
||||
@@ -680,6 +682,7 @@ impl LanguageServer for Backend {
|
||||
utf8,
|
||||
&wiki.config.tags_header,
|
||||
wiki.config.tags_header_level,
|
||||
wiki.config.list_margin.max(0) as usize,
|
||||
)
|
||||
});
|
||||
if let Some(edit) = edit {
|
||||
|
||||
Reference in New Issue
Block a user