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>
This commit is contained in:
2026-05-30 23:12:08 -03:00
parent 0741c349db
commit c63ec679ae
11 changed files with 383 additions and 77 deletions
+59 -11
View File
@@ -168,37 +168,78 @@ fn period_first_day(p: &DiaryPeriod) -> DiaryDate {
p.first_day()
}
/// Generate the body of the diary index page — a flat newest-first list
/// of `[[diary/YYYY-MM-DD]]` wikilinks, grouped under year and month
/// Ordering of entries in the diary index page.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiarySort {
/// Newest first (vimwiki default).
Desc,
/// Oldest first.
Asc,
}
impl DiarySort {
/// Parse a wiki's `diary_sort` field. Anything other than `asc`
/// (case-insensitive) is treated as the default `desc`.
pub fn parse(s: &str) -> Self {
if s.trim().eq_ignore_ascii_case("asc") {
Self::Asc
} else {
Self::Desc
}
}
}
/// Render a heading line at `level` (1 = `= … =`). Clamped to vimwiki's
/// 1..=6 range so a stray config value can't emit a 200-equals heading.
fn heading(level: u8, text: &str) -> String {
let n = level.clamp(1, 6) as usize;
let eq = "=".repeat(n);
format!("{eq} {text} {eq}")
}
/// Generate the body of the diary index page — a flat list of
/// `[[diary/YYYY-MM-DD]]` wikilinks, grouped under year and month
/// 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.
pub fn build_index_body(
entries: &[DiaryEntry],
diary_rel_path: &str,
index_heading: &str,
sort: DiarySort,
caption_level: u8,
) -> String {
let mut out = String::new();
out.push_str("= ");
out.push_str(index_heading);
out.push_str(" =\n");
out.push_str(&heading(caption_level, index_heading));
out.push('\n');
if entries.is_empty() {
return out;
}
// Sort descending by date — newest first matches vimwiki.
let mut sorted: Vec<&DiaryEntry> = entries.iter().collect();
sorted.sort_by(|a, b| b.date.cmp(&a.date));
sorted.sort_by(|a, b| match sort {
DiarySort::Desc => b.date.cmp(&a.date),
DiarySort::Asc => a.date.cmp(&b.date),
});
let year_level = caption_level.saturating_add(1);
let month_level = caption_level.saturating_add(2);
let mut current_year: Option<i32> = None;
let mut current_month: Option<u8> = None;
for e in sorted {
if current_year != Some(e.date.year) {
out.push_str(&format!("\n== {} ==\n", e.date.year));
out.push('\n');
out.push_str(&heading(year_level, &e.date.year.to_string()));
out.push('\n');
current_year = Some(e.date.year);
current_month = None;
}
if current_month != Some(e.date.month) {
out.push_str(&format!("=== {} ===\n", month_name(e.date.month)));
out.push_str(&heading(month_level, month_name(e.date.month)));
out.push('\n');
current_month = Some(e.date.month);
}
out.push_str(&format!(
@@ -211,9 +252,16 @@ pub fn build_index_body(
}
/// Render the diary-index body for the given wiki's currently-indexed
/// entries. Convenience wrapper around [`build_index_body`].
/// entries. Convenience wrapper around [`build_index_body`] that honours
/// the wiki's `diary_header`, `diary_sort`, and `diary_caption_level`.
pub fn render_index_body(cfg: &WikiConfig, index: &WorkspaceIndex) -> String {
build_index_body(&list_entries(index), &cfg.diary_rel_path, "Diary")
build_index_body(
&list_entries(index),
&cfg.diary_rel_path,
&cfg.diary_header,
DiarySort::parse(&cfg.diary_sort),
cfg.diary_caption_level,
)
}
/// Cheap "is this path under the diary subdir of this wiki" check —