parity(4): diary weekly / monthly / yearly frequency support

vimwiki's `diary_frequency` config has been honoured by the existing
`nuwiki.diary.openToday` / `openYesterday` / `openTomorrow` commands
end-to-end. Setting `diary_frequency = 'weekly'` (or `monthly` /
`yearly`) now creates and addresses entries at that cadence — file
stems follow the canonical formats:

  daily    YYYY-MM-DD     2026-05-12.wiki
  weekly   YYYY-Www       2026-W19.wiki      (ISO 8601 week)
  monthly  YYYY-MM        2026-05.wiki
  yearly   YYYY           2026.wiki

Core additions (nuwiki_core::date):
  - DiaryFrequency enum + permissive `parse` (unknown → Daily)
  - DiaryPeriod enum unifying Day / Week / Month / Year
  - format / parse / next / prev / today_utc + first_day, with
    proper ISO-week math (Thursday rule, year-boundary handling)

LSP wiring:
  - WikiConfig::frequency() and diary_path_for_period()
  - crate::diary::uri_for_period
  - Index now records `diary_period` for any of the four flavours;
    `diary_date` is preserved (filtered to Day-only) for back-compat
    with existing daily-only callers
  - `nuwiki.diary.next` / `nuwiki.diary.prev` now navigate at the
    *same flavour* as the current entry (or at the wiki's configured
    frequency when off a diary page), via new
    `crate::diary::next_period` / `prev_period` helpers
  - `nuwiki.diary.openToday` returns the period stem + frequency in
    its response payload alongside the URI

Tests:
  - 12 in crates/nuwiki-core/tests/diary_period.rs covering the date
    math (ISO week boundaries, format round-trips, next/prev across
    year edges, etc.)
  - 9 in crates/nuwiki-lsp/tests/diary_frequency.rs covering
    WikiConfig path computation, index recognition for all four
    stems, period navigation, and diary-dir filtering

Gates: 450 Rust / 39 Neovim / 12 Vim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 22:09:14 +00:00
parent bcef714805
commit d8fa59a63a
7 changed files with 844 additions and 27 deletions
+18 -15
View File
@@ -379,18 +379,20 @@ fn diary_open_relative(
let Some(wiki) = resolve_diary_wiki(backend, p.uri.as_ref()) else {
return Ok(None);
};
let today = nuwiki_core::date::DiaryDate::today_utc();
let date = match rel {
RelativeDay::Today => today,
RelativeDay::Yesterday => today.prev_day(),
RelativeDay::Tomorrow => today.next_day(),
let freq = wiki.config.frequency();
let today_period = nuwiki_core::date::DiaryPeriod::today_utc(freq);
let period = match rel {
RelativeDay::Today => today_period,
RelativeDay::Yesterday => today_period.prev(),
RelativeDay::Tomorrow => today_period.next(),
};
let Some(uri) = crate::diary::uri_for_date(&wiki.config, &date) else {
let Some(uri) = crate::diary::uri_for_period(&wiki.config, &period) else {
return Ok(None);
};
Ok(Some(serde_json::json!({
"uri": uri,
"date": date.format(),
"date": period.format(),
"frequency": freq.as_str(),
})))
}
@@ -434,17 +436,18 @@ fn diary_step(backend: &Backend, args: Vec<Value>, forward: bool) -> Result<Opti
let Some(wiki) = backend.wiki_for_uri(&p.uri) else {
return Ok(None);
};
// Pivot date: the current page's `diary_date` when it's an entry,
// otherwise today — matches vimwiki's "navigate from whatever date
// I'm currently viewing".
let pivot = {
// Pivot period: prefer the current page's own period (any flavour),
// falling back to the wiki's configured frequency for "today" when
// the cursor isn't on a diary page. This makes <C-Down> on a weekly
// entry jump to the next weekly entry, not the next daily one.
let pivot: nuwiki_core::date::DiaryPeriod = {
let idx = wiki
.index
.read()
.map_err(|_| "index lock poisoned".to_string())?;
idx.page(&p.uri)
.and_then(|page| page.diary_date)
.unwrap_or_else(nuwiki_core::date::DiaryDate::today_utc)
.and_then(|page| page.diary_period)
.unwrap_or_else(|| nuwiki_core::date::DiaryPeriod::today_utc(wiki.config.frequency()))
};
let next = {
let idx = wiki
@@ -452,9 +455,9 @@ fn diary_step(backend: &Backend, args: Vec<Value>, forward: bool) -> Result<Opti
.read()
.map_err(|_| "index lock poisoned".to_string())?;
if forward {
crate::diary::next_entry(&idx, &pivot)
crate::diary::next_period(&idx, &pivot)
} else {
crate::diary::prev_entry(&idx, &pivot)
crate::diary::prev_period(&idx, &pivot)
}
};
match next {