2026-05-11 14:32:22 +00:00
|
|
|
//! `Wiki` aggregate — per-wiki state, even when there's only one.
|
|
|
|
|
//!
|
2026-05-30 18:35:40 +00:00
|
|
|
//! The single `Arc<RwLock<WorkspaceIndex>>` 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.
|
2026-05-11 14:32:22 +00:00
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
pub config: WikiConfig,
|
|
|
|
|
pub index: Arc<RwLock<WorkspaceIndex>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Wiki {
|
|
|
|
|
pub fn new(id: WikiId, config: WikiConfig) -> Self {
|
2026-05-11 21:04:55 +00:00
|
|
|
let index = WorkspaceIndex::new(Some(config.root.clone()))
|
2026-05-26 22:00:03 -03:00
|
|
|
.with_diary_rel_path(Some(config.diary_rel_path.clone()))
|
|
|
|
|
.with_file_extension(Some(config.file_extension.clone()));
|
2026-05-11 21:04:55 +00:00
|
|
|
let index = Arc::new(RwLock::new(index));
|
2026-05-11 14:32:22 +00:00
|
|
|
Self { id, 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<WikiId> {
|
|
|
|
|
wikis
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|w| w.contains(uri))
|
|
|
|
|
.max_by_key(|w| w.root_depth())
|
|
|
|
|
.map(|w| w.id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Build the initial `Vec<Wiki>` 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<Wiki> {
|
|
|
|
|
configs
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.map(|(i, cfg)| Wiki::new(WikiId(i as u32), cfg.clone()))
|
|
|
|
|
.collect()
|
|
|
|
|
}
|