fix(review): close all 2026-06-03 codebase-review findings (R1-R18)
Resolves the 18 findings from the parallel codebase review, tracked in development/vimwiki-gap.md. Correctness / perf: - Wiki.config -> Arc<WikiConfig> so cloning a Wiki is a refcount bump (R5) - WorkspaceIndex::remove is no longer O(n^2): a per-source contributions map limits the scan to buckets the source actually wrote into (R6) - render_color now expands color_tag_template (__STYLE__/__CONTENT__), consuming the previously-dead field; ColorNode documented as an extension point; 3 renderer tests added (R3/R4) - wiki_root_for returns empty/nil on no-match instead of falling back to the first wiki (R2); auto_header honours links_space_char (R7) Cleanup / dedup: - Remove dead #[allow(dead_code)] stubs + uncalled pub helpers, narrow imports (R10/R11) - Dedup span_of_inline x3 -> InlineNode::span() (R12) - diary_step single read lock; page_captions single pass (R13) - Lua auto_header loop -> wiki_list(); detect_current_symbol cleanup (R14/R18) Client / docs: - :VimwikiNormalizeLink Vim cmds -> <q-args> (R17); ftplugin header fix (R16); vars.vim multi-wiki limitation documented (R15) - Document 19 config options in README.md + doc/nuwiki.txt; fix list_margin/shiftwidth doc and stale comments (R1/R9) - R8 investigated, confirmed not a real bug (documented) Verified: Neovim harness 307, Vim harness 301/18/21, Rust suite 568, all 0 failed; clippy clean; fresh parallel-agent audit found no regressions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -123,6 +123,18 @@ pub struct WorkspaceIndex {
|
||||
/// the nav handlers (tag-as-anchor `definition`, `workspace/symbol`
|
||||
/// inclusion) and the `nuwiki.tags.search` command.
|
||||
pub tags_by_name: HashMap<String, Vec<TagOccurrence>>,
|
||||
/// For each source URI, the backlink-bucket keys and tag-bucket names it
|
||||
/// contributed. Lets `remove` touch only the affected buckets instead of
|
||||
/// scanning every bucket in the workspace (O(touched), not O(total)).
|
||||
contributions: HashMap<Url, SourceContributions>,
|
||||
}
|
||||
|
||||
/// The bucket keys a single source page wrote into, so they can be undone
|
||||
/// in `remove` without a full scan. See [`WorkspaceIndex::contributions`].
|
||||
#[derive(Debug, Default)]
|
||||
struct SourceContributions {
|
||||
backlink_keys: Vec<String>,
|
||||
tag_names: Vec<String>,
|
||||
}
|
||||
|
||||
impl WorkspaceIndex {
|
||||
@@ -175,15 +187,20 @@ impl WorkspaceIndex {
|
||||
};
|
||||
index_blocks(&ast.children, &mut page);
|
||||
|
||||
let mut contrib = SourceContributions::default();
|
||||
let ext = self.file_extension.as_deref();
|
||||
for link in &page.outgoing {
|
||||
if let Some(tgt) = &link.target_page {
|
||||
let key = canonical_link_name(tgt, &name, link.is_absolute, ext);
|
||||
self.backlinks.entry(key).or_default().push(Backlink {
|
||||
source_uri: uri.clone(),
|
||||
source_span: link.span,
|
||||
target_anchor: link.anchor.clone(),
|
||||
});
|
||||
self.backlinks
|
||||
.entry(key.clone())
|
||||
.or_default()
|
||||
.push(Backlink {
|
||||
source_uri: uri.clone(),
|
||||
source_span: link.span,
|
||||
target_anchor: link.anchor.clone(),
|
||||
});
|
||||
contrib.backlink_keys.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +214,9 @@ impl WorkspaceIndex {
|
||||
span: tag.span,
|
||||
scope: tag.scope,
|
||||
});
|
||||
contrib.tag_names.push(tag.name.clone());
|
||||
}
|
||||
self.contributions.insert(uri.clone(), contrib);
|
||||
|
||||
self.pages_by_name.insert(name, uri.clone());
|
||||
self.pages_by_uri.insert(uri, page);
|
||||
@@ -207,14 +226,26 @@ impl WorkspaceIndex {
|
||||
if let Some(page) = self.pages_by_uri.remove(uri) {
|
||||
self.pages_by_name.remove(&page.name);
|
||||
}
|
||||
for entries in self.backlinks.values_mut() {
|
||||
entries.retain(|b| &b.source_uri != uri);
|
||||
// Only revisit the buckets this source actually wrote into, instead
|
||||
// of scanning every backlink/tag bucket in the workspace.
|
||||
if let Some(contrib) = self.contributions.remove(uri) {
|
||||
for key in contrib.backlink_keys {
|
||||
if let Some(entries) = self.backlinks.get_mut(&key) {
|
||||
entries.retain(|b| &b.source_uri != uri);
|
||||
if entries.is_empty() {
|
||||
self.backlinks.remove(&key);
|
||||
}
|
||||
}
|
||||
}
|
||||
for name in contrib.tag_names {
|
||||
if let Some(entries) = self.tags_by_name.get_mut(&name) {
|
||||
entries.retain(|t| &t.uri != uri);
|
||||
if entries.is_empty() {
|
||||
self.tags_by_name.remove(&name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.backlinks.retain(|_, v| !v.is_empty());
|
||||
for entries in self.tags_by_name.values_mut() {
|
||||
entries.retain(|t| &t.uri != uri);
|
||||
}
|
||||
self.tags_by_name.retain(|_, v| !v.is_empty());
|
||||
}
|
||||
|
||||
/// All occurrences of a tag across the workspace. Empty slice when the
|
||||
@@ -306,21 +337,6 @@ impl WorkspaceIndex {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a parsed [`DiaryDate`] when `uri` is a daily diary entry —
|
||||
/// the path lives under `<root>/<diary_rel_path>/` and its stem parses
|
||||
/// as `YYYY-MM-DD`. For weekly/monthly/yearly entries see
|
||||
/// [`diary_period_for_uri`].
|
||||
pub fn diary_date_for_uri(
|
||||
uri: &Url,
|
||||
root: Option<&Path>,
|
||||
diary_rel_path: Option<&str>,
|
||||
) -> Option<DiaryDate> {
|
||||
match diary_period_for_uri(uri, root, diary_rel_path)? {
|
||||
nuwiki_core::date::DiaryPeriod::Day(d) => Some(d),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a parsed [`DiaryPeriod`] when `uri` is a diary entry at any of
|
||||
/// the four supported frequencies. Recognises:
|
||||
///
|
||||
@@ -329,9 +345,8 @@ pub fn diary_date_for_uri(
|
||||
/// `YYYY-MM` → Month
|
||||
/// `YYYY` → Year
|
||||
///
|
||||
/// As with `diary_date_for_uri`, the path must sit under
|
||||
/// `<root>/<diary_rel_path>/` to be accepted; without a `root` we accept
|
||||
/// any stem (so ad-hoc test setups still work).
|
||||
/// The path must sit under `<root>/<diary_rel_path>/` to be accepted;
|
||||
/// without a `root` we accept any stem (so ad-hoc test setups still work).
|
||||
pub fn diary_period_for_uri(
|
||||
uri: &Url,
|
||||
root: Option<&Path>,
|
||||
|
||||
Reference in New Issue
Block a user