Files
nuwiki-rs/crates/nuwiki-lsp/src/wiki.rs
T

81 lines
2.5 KiB
Rust
Raw Normal View History

//! `Wiki` aggregate — per-wiki state, even when there's only one.
//!
//! 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.
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<WikiConfig>,
pub index: Arc<RwLock<WorkspaceIndex>>,
}
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<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()
}