You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
helix/helix-config/src/lib.rs

247 lines
7.2 KiB
Rust

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()
}
}