Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-config/src/lib.rs')
| -rw-r--r-- | helix-config/src/lib.rs | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/helix-config/src/lib.rs b/helix-config/src/lib.rs new file mode 100644 index 00000000..9fe65b7e --- /dev/null +++ b/helix-config/src/lib.rs @@ -0,0 +1,246 @@ +use std::any::Any; +use std::fmt::Debug; +use std::marker::PhantomData; +use std::ops::Deref; +use std::sync::Arc; + +use anyhow::bail; +use hashbrown::hash_map::Entry; +use hashbrown::HashMap; +use indexmap::IndexMap; +use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; + +use any::ConfigData; +use convert::ty_into_value; +pub use convert::IntoTy; +pub use definition::{init_config, init_language_server_config}; +pub use toml::read_toml_config; +use validator::StaticValidator; +pub use validator::{regex_str_validator, ty_validator, IntegerRangeValidator, Ty, Validator}; +pub use value::{from_value, to_value, Value}; + +mod any; +mod convert; +mod definition; +pub mod env; +mod macros; +mod toml; +mod validator; +mod value; + +pub type Guard<'a, T> = MappedRwLockReadGuard<'a, T>; +pub type Map<T> = IndexMap<Box<str>, T, ahash::RandomState>; +pub type String = Box<str>; +pub type List<T> = Box<[T]>; + +#[cfg(test)] +mod tests; + +#[derive(Debug)] +pub struct OptionInfo { + pub name: Arc<str>, + pub description: Box<str>, + pub validator: Box<dyn Validator>, + pub into_value: fn(&ConfigData) -> Value, +} + +#[derive(Debug)] +pub struct OptionManager { + vals: RwLock<HashMap<Arc<str>, ConfigData>>, + parent: Option<Arc<OptionManager>>, +} + +impl OptionManager { + pub fn get<T: Any>(&self, option: &str) -> Guard<'_, T> { + Guard::map(self.get_data(option), ConfigData::get) + } + + pub fn get_data(&self, option: &str) -> Guard<'_, ConfigData> { + let mut current_scope = self; + loop { + let lock = current_scope.vals.read(); + if let Ok(res) = RwLockReadGuard::try_map(lock, |options| options.get(option)) { + return res; + } + let Some(new_scope) = current_scope.parent.as_deref() else{ + unreachable!("option must be atleast defined in the global scope") + }; + current_scope = new_scope; + } + } + + pub fn get_deref<T: Deref + Any>(&self, option: &str) -> Guard<'_, T::Target> { + Guard::map(self.get::<T>(option), T::deref) + } + + pub fn get_folded<T: Any, R>( + &self, + option: &str, + init: R, + mut fold: impl FnMut(&T, R) -> R, + ) -> R { + let mut res = init; + let mut current_scope = self; + loop { + let options = current_scope.vals.read(); + if let Some(option) = options.get(option).map(|val| val.get()) { + res = fold(option, res); + } + let Some(new_scope) = current_scope.parent.as_deref() else{ + break + }; + current_scope = new_scope; + } + res + } + + pub fn get_value( + &self, + option: impl Into<Arc<str>>, + registry: &OptionRegistry, + ) -> anyhow::Result<Value> { + let option: Arc<str> = option.into(); + let Some(opt) = registry.get(&option) else { bail!("unknown option {option:?}") }; + let data = self.get_data(&option); + let val = (opt.into_value)(&data); + Ok(val) + } + + pub fn create_scope(self: &Arc<OptionManager>) -> OptionManager { + OptionManager { + vals: RwLock::default(), + parent: Some(self.clone()), + } + } + + pub fn set_parent_scope(&mut self, parent: Arc<OptionManager>) { + self.parent = Some(parent) + } + + pub fn set_unchecked(&self, option: Arc<str>, val: ConfigData) { + self.vals.write().insert(option, val); + } + + pub fn append( + &self, + option: impl Into<Arc<str>>, + val: impl Into<Value>, + registry: &OptionRegistry, + max_depth: usize, + ) -> anyhow::Result<()> { + let val = val.into(); + let option: Arc<str> = option.into(); + let Some(opt) = registry.get(&option) else { bail!("unknown option {option:?}") }; + let old_data = self.get_data(&option); + let mut old = (opt.into_value)(&old_data); + old.append(val, max_depth); + let val = opt.validator.validate(old)?; + self.set_unchecked(option, val); + Ok(()) + } + + /// Sets the value of a config option. Returns an error if this config + /// option doesn't exist or the provided value is not valid. + pub fn set( + &self, + option: impl Into<Arc<str>>, + val: impl Into<Value>, + registry: &OptionRegistry, + ) -> anyhow::Result<()> { + let option: Arc<str> = option.into(); + let val = val.into(); + let Some(opt) = registry.get(&option) else { bail!("unknown option {option:?}") }; + let val = opt.validator.validate(val)?; + self.set_unchecked(option, val); + Ok(()) + } + + /// unsets an options so that its value will be read from + /// the parent scope instead + pub fn unset(&self, option: &str) { + self.vals.write().remove(option); + } +} + +#[derive(Debug)] +pub struct OptionRegistry { + options: HashMap<Arc<str>, OptionInfo>, + defaults: Arc<OptionManager>, +} + +impl OptionRegistry { + pub fn new() -> Self { + Self { + options: HashMap::with_capacity(1024), + defaults: Arc::new(OptionManager { + vals: RwLock::new(HashMap::with_capacity(1024)), + parent: None, + }), + } + } + + pub fn register<T: IntoTy>(&mut self, name: &str, description: &str, default: T) { + self.register_with_validator( + name, + description, + default, + StaticValidator::<T::Ty> { ty: PhantomData }, + ); + } + + pub fn register_with_validator<T: IntoTy>( + &mut self, + name: &str, + description: &str, + default: T, + validator: impl Validator, + ) { + let mut name: Arc<str> = name.into(); + // convert from snake case to kebab case in place without an additional + // allocation this is save since we only replace ascii with ascii in + // place std really ougth to have a function for this :/ + // TODO: move to stdx as extension trait + for byte in unsafe { Arc::get_mut(&mut name).unwrap().as_bytes_mut() } { + if *byte == b'-' { + *byte = b'_'; + } + } + let default = default.into_ty(); + match self.options.entry(name.clone()) { + Entry::Vacant(e) => { + // make sure the validator is correct + if cfg!(debug_assertions) { + validator.validate(T::Ty::to_value(&default)).unwrap(); + } + let opt = OptionInfo { + name: name.clone(), + description: description.into(), + validator: Box::new(validator), + into_value: ty_into_value::<T::Ty>, + }; + e.insert(opt); + } + Entry::Occupied(ent) => { + ent.get() + .validator + .validate(T::Ty::to_value(&default)) + .unwrap(); + } + } + self.defaults.set_unchecked(name, ConfigData::new(default)); + } + + pub fn global_scope(&self) -> Arc<OptionManager> { + self.defaults.clone() + } + + pub fn get(&self, name: &str) -> Option<&OptionInfo> { + self.options.get(name) + } +} + +impl Default for OptionRegistry { + fn default() -> Self { + Self::new() + } +} |