//! `Wiki` aggregate — per-wiki state, even when there's only one. //! //! The single `Arc>` on `Backend` is lifted into a //! `Wiki` aggregate so multi-wiki support only changes config shape, not //! data flow. Handlers always operate on a `Wiki`; in practice the `wikis` //! `Vec` has one entry until multi-wiki support lands. use std::path::Path; use std::sync::{Arc, RwLock}; use tower_lsp::lsp_types::Url; use crate::config::WikiConfig; use crate::index::WorkspaceIndex; #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)] pub struct WikiId(pub u32); #[derive(Clone)] pub struct Wiki { pub id: WikiId, // `Arc` so cloning a `Wiki` (done on every handler entry that picks a // wiki out of the list) is a refcount bump, not a deep copy of the whole // config — color_dic, templates, header strings, and so on. pub config: Arc, pub index: Arc>, } impl Wiki { pub fn new(id: WikiId, config: WikiConfig) -> Self { let index = WorkspaceIndex::new(Some(config.root.clone())) .with_diary_rel_path(Some(config.diary_rel_path.clone())) .with_file_extension(Some(config.file_extension.clone())); let index = Arc::new(RwLock::new(index)); Self { id, config: Arc::new(config), index, } } /// True when `uri` resolves to a file under this wiki's root. pub fn contains(&self, uri: &Url) -> bool { let Ok(path) = uri.to_file_path() else { return false; }; path.starts_with(&self.config.root) } /// Length of the wiki's root path (component count) — used to break /// ties in nested-root layouts. pub fn root_depth(&self) -> usize { self.config.root.components().count() } pub fn root(&self) -> &Path { &self.config.root } } /// Pick the wiki whose root is the longest prefix of `uri`. Returns the /// wiki id, not a guard, so callers can release the read lock before /// awaiting. pub fn resolve_uri_to_wiki(wikis: &[Wiki], uri: &Url) -> Option { wikis .iter() .filter(|w| w.contains(uri)) .max_by_key(|w| w.root_depth()) .map(|w| w.id) } /// Build the initial `Vec` from a list of `WikiConfig`s. Always /// assigns ids by position so `WikiId(0)` is the default wiki. pub fn build_wikis(configs: &[WikiConfig]) -> Vec { configs .iter() .enumerate() .map(|(i, cfg)| Wiki::new(WikiId(i as u32), cfg.clone())) .collect() }