mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-24 18:36:18 +04:00
config system prototype
This commit is contained in:
parent
17dd102e5c
commit
5e74d3c821
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -1047,6 +1047,19 @@ dependencies = [
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "helix-config"
|
||||
version = "23.10.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
"hashbrown 0.14.3",
|
||||
"indexmap",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "helix-core"
|
||||
version = "23.10.0"
|
||||
@ -1059,6 +1072,7 @@ dependencies = [
|
||||
"encoding_rs",
|
||||
"etcetera",
|
||||
"hashbrown 0.14.3",
|
||||
"helix-config",
|
||||
"helix-loader",
|
||||
"imara-diff",
|
||||
"indoc",
|
||||
@ -1132,6 +1146,7 @@ dependencies = [
|
||||
"futures-executor",
|
||||
"futures-util",
|
||||
"globset",
|
||||
"helix-config",
|
||||
"helix-core",
|
||||
"helix-loader",
|
||||
"helix-parsec",
|
||||
|
@ -2,6 +2,7 @@
|
||||
resolver = "2"
|
||||
members = [
|
||||
"helix-core",
|
||||
"helix-config",
|
||||
"helix-view",
|
||||
"helix-term",
|
||||
"helix-tui",
|
||||
|
24
helix-config/Cargo.toml
Normal file
24
helix-config/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "helix-config"
|
||||
description = "Helix editor core editing primitives"
|
||||
include = ["src/**/*", "README.md"]
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
categories.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
[dependencies]
|
||||
ahash = "0.8.6"
|
||||
hashbrown = { version = "0.14.3", features = ["raw"] }
|
||||
parking_lot = "0.12"
|
||||
anyhow = "1.0.79"
|
||||
indexmap = { version = "2.1.0", features = ["serde"] }
|
||||
serde = { version = "1.0" }
|
||||
serde_json = "1.0"
|
||||
|
||||
regex-syntax = "0.8.2"
|
||||
which = "5.0.0"
|
76
helix-config/src/any.rs
Normal file
76
helix-config/src/any.rs
Normal file
@ -0,0 +1,76 @@
|
||||
/// this is a reimplementation of dynamic dispatch that only stores the
|
||||
/// information we need and stores everythin inline. Values that are smaller or
|
||||
/// the same size as a slice (2 usize) are also stored inline. This avoids
|
||||
/// significant overallocation when setting lots of simple config
|
||||
/// options (integers, strings, lists, enums)
|
||||
use std::any::{Any, TypeId};
|
||||
use std::mem::{align_of, size_of, MaybeUninit};
|
||||
|
||||
pub struct ConfigData {
|
||||
data: MaybeUninit<[usize; 2]>,
|
||||
ty: TypeId,
|
||||
drop_fn: unsafe fn(MaybeUninit<[usize; 2]>),
|
||||
}
|
||||
|
||||
const fn store_inline<T>() -> bool {
|
||||
size_of::<T>() <= size_of::<[usize; 2]>() && align_of::<T>() <= align_of::<[usize; 2]>()
|
||||
}
|
||||
|
||||
impl ConfigData {
|
||||
unsafe fn drop_impl<T: Any>(mut data: MaybeUninit<[usize; 2]>) {
|
||||
if store_inline::<T>() {
|
||||
data.as_mut_ptr().cast::<T>().drop_in_place();
|
||||
} else {
|
||||
let ptr = data.as_mut_ptr().cast::<*mut T>().read();
|
||||
drop(Box::from_raw(ptr));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get<T: Any>(&self) -> &T {
|
||||
assert_eq!(TypeId::of::<T>(), self.ty);
|
||||
unsafe {
|
||||
if store_inline::<T>() {
|
||||
return &*self.data.as_ptr().cast();
|
||||
}
|
||||
let data: *const T = self.data.as_ptr().cast::<*const T>().read();
|
||||
&*data
|
||||
}
|
||||
}
|
||||
pub fn new<T: Any>(val: T) -> Self {
|
||||
let mut data = MaybeUninit::uninit();
|
||||
if store_inline::<T>() {
|
||||
let data: *mut T = data.as_mut_ptr() as _;
|
||||
unsafe {
|
||||
data.write(val);
|
||||
}
|
||||
} else {
|
||||
assert!(store_inline::<*const T>());
|
||||
let data: *mut *const T = data.as_mut_ptr() as _;
|
||||
unsafe {
|
||||
data.write(Box::into_raw(Box::new(val)));
|
||||
}
|
||||
};
|
||||
Self {
|
||||
data,
|
||||
ty: TypeId::of::<T>(),
|
||||
drop_fn: ConfigData::drop_impl::<T>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ConfigData {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.drop_fn)(self.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ConfigData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ConfigData").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for ConfigData {}
|
||||
unsafe impl Sync for ConfigData {}
|
42
helix-config/src/convert.rs
Normal file
42
helix-config/src/convert.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use crate::any::ConfigData;
|
||||
use crate::validator::Ty;
|
||||
use crate::Value;
|
||||
|
||||
pub trait IntoTy: Clone {
|
||||
type Ty: Ty;
|
||||
fn into_ty(self) -> Self::Ty;
|
||||
}
|
||||
|
||||
impl<T: Ty> IntoTy for T {
|
||||
type Ty = Self;
|
||||
|
||||
fn into_ty(self) -> Self::Ty {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<T: IntoTy> IntoTy for &[T] {
|
||||
type Ty = Box<[T::Ty]>;
|
||||
|
||||
fn into_ty(self) -> Self::Ty {
|
||||
self.iter().cloned().map(T::into_ty).collect()
|
||||
}
|
||||
}
|
||||
impl<T: IntoTy, const N: usize> IntoTy for &[T; N] {
|
||||
type Ty = Box<[T::Ty]>;
|
||||
|
||||
fn into_ty(self) -> Self::Ty {
|
||||
self.iter().cloned().map(T::into_ty).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoTy for &str {
|
||||
type Ty = Box<str>;
|
||||
|
||||
fn into_ty(self) -> Self::Ty {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn ty_into_value<T: Ty>(val: &ConfigData) -> Value {
|
||||
T::to_value(val.get())
|
||||
}
|
242
helix-config/src/lib.rs
Normal file
242
helix-config/src/lib.rs
Normal file
@ -0,0 +1,242 @@
|
||||
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;
|
||||
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 macros;
|
||||
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()
|
||||
}
|
||||
}
|
130
helix-config/src/macros.rs
Normal file
130
helix-config/src/macros.rs
Normal file
@ -0,0 +1,130 @@
|
||||
/// This macro allows specifiying a trait of related config
|
||||
/// options with a struct like syntax. From that information
|
||||
/// two things are generated:
|
||||
///
|
||||
/// * A `init_config` function that registers the config options with the
|
||||
/// `OptionRegistry` registry.
|
||||
/// * A **trait** definition with an accessor for every config option that is
|
||||
/// implemented for `OptionManager`.
|
||||
///
|
||||
/// The accessors on the trait allow convenient statically typed access to
|
||||
/// config fields. The accessors return `Guard<T>` (which allows derferecning to
|
||||
/// &T). Any type that implements copy can be returned as a copy instead by
|
||||
/// specifying `#[read = copy]`. Collections like `List<T>` and `String` are not
|
||||
/// copy However, they usually implement deref (to &[T] and &str respectively).
|
||||
/// Working with the dereferneced &str/&[T] is more convenient then &String and &List<T>. The
|
||||
/// accessor will return these if `#[read = deref]` is specified.
|
||||
///
|
||||
/// The doc comments will be retained for the accessors and also stored in the
|
||||
/// option registrry for dispaly in the UI and documentation.
|
||||
///
|
||||
/// The name of a config option can be changed with #[name = "<name>"],
|
||||
/// otherwise the name of the field is used directly. The OptionRegistry
|
||||
/// automatically converts all names to kebab-case so a name attribute is only
|
||||
/// required if the name is supposed to be significantly altered.
|
||||
///
|
||||
/// In some cases more complex validation may be necssary. In that case the
|
||||
/// valiidtator can be provided with an exprission that implements the `Validator`
|
||||
/// trait: `#[validator = create_validator()]`.
|
||||
#[macro_export]
|
||||
macro_rules! options {
|
||||
(
|
||||
$(use $use: ident::*;)*
|
||||
$($(#[$($meta: tt)*])* struct $ident: ident {
|
||||
$(
|
||||
$(#[doc = $option_desc: literal])*
|
||||
$(#[name = $option_name: literal])?
|
||||
$(#[validator = $option_validator: expr])?
|
||||
$(#[read = $($extra: tt)*])?
|
||||
$option: ident: $ty: ty = $default: expr
|
||||
),+$(,)?
|
||||
})+
|
||||
) => {
|
||||
$(pub use $use::*;)*
|
||||
$($(#[$($meta)*])* pub trait $ident {
|
||||
$(
|
||||
$(#[doc = $option_desc])*
|
||||
fn $option(&self) -> $crate::options!(@ret_ty $($($extra)*)? $ty);
|
||||
)+
|
||||
})+
|
||||
pub fn init_config(registry: &mut $crate::OptionRegistry) {
|
||||
$($use::init_config(registry);)*
|
||||
$($(
|
||||
let name = $crate::options!(@name $option $($option_name)?);
|
||||
let docs = concat!("" $(,$option_desc,)" "*);
|
||||
$crate::options!(@register registry name docs $default, $ty $(,$option_validator)?);
|
||||
)+)+
|
||||
}
|
||||
$(impl $ident for $crate::OptionManager {
|
||||
$(
|
||||
$(#[doc = $option_desc])*
|
||||
fn $option(&self) -> $crate::options!(@ret_ty $($($extra)*)? $ty) {
|
||||
let name = $crate::options!(@name $option $($option_name)?);
|
||||
$crate::options!(@get $($($extra)*)? self, $ty, name)
|
||||
}
|
||||
)+
|
||||
})+
|
||||
};
|
||||
(@register $registry: ident $name: ident $desc: ident $default: expr, $ty:ty) => {{
|
||||
use $crate::IntoTy;
|
||||
let val: $ty = $default.into_ty();
|
||||
$registry.register($name, $desc, val);
|
||||
}};
|
||||
(@register $registry: ident $name: ident $desc: ident $default: expr, $ty:ty, $validator: expr) => {{
|
||||
use $crate::IntoTy;
|
||||
let val: $ty = $default.into_ty();
|
||||
$registry.register_with_validator($name, $desc, val, $validator);
|
||||
}};
|
||||
(@name $ident: ident) => {
|
||||
::std::stringify!($ident)
|
||||
};
|
||||
(@name $ident: ident $name: literal) => {
|
||||
$name
|
||||
};
|
||||
(@ret_ty copy $ty: ty) => {
|
||||
$ty
|
||||
};
|
||||
(@ret_ty map($fn: expr, $ret_ty: ty) $ty: ty) => {
|
||||
$ret_ty
|
||||
};
|
||||
(@ret_ty fold($init: expr, $fn: expr, $ret_ty: ty) $ty: ty) => {
|
||||
$ret_ty
|
||||
};
|
||||
(@ret_ty deref $ty: ty) => {
|
||||
$crate::Guard<'_, <$ty as ::std::ops::Deref>::Target>
|
||||
};
|
||||
(@ret_ty $ty: ty) => {
|
||||
$crate::Guard<'_, $ty>
|
||||
};
|
||||
(@get map($fn: expr, $ret_ty: ty) $config: ident, $ty: ty, $name: ident) => {
|
||||
let val = $config.get::<$ty>($name);
|
||||
$fn(val)
|
||||
};
|
||||
(@get fold($init: expr, $fn: expr, $ret_ty: ty) $config: ident, $ty: ty, $name: ident) => {
|
||||
$config.get_folded::<$ty, $ret_ty>($name, $init, $fn)
|
||||
};
|
||||
(@get copy $config: ident, $ty: ty, $name: ident) => {
|
||||
*$config.get::<$ty>($name)
|
||||
};
|
||||
(@get deref $config: ident, $ty: ty, $name: ident) => {
|
||||
$config.get_deref::<$ty>($name)
|
||||
};
|
||||
(@get $config: ident, $ty: ty, $name: ident) => {
|
||||
$config.get::<$ty>($name)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! config_serde_adapter {
|
||||
($ty: ident) => {
|
||||
impl $crate::Ty for $ty {
|
||||
fn to_value(&self) -> $crate::Value {
|
||||
$crate::to_value(self).unwrap()
|
||||
}
|
||||
fn from_value(val: $crate::Value) -> ::anyhow::Result<Self> {
|
||||
let val = $crate::from_value(val)?;
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
80
helix-config/src/tests.rs
Normal file
80
helix-config/src/tests.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config_serde_adapter;
|
||||
use crate::OptionRegistry;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum LineNumber {
|
||||
/// Show absolute line number
|
||||
#[serde(alias = "abs")]
|
||||
Absolute,
|
||||
/// If focused and in normal/select mode, show relative line number to the primary cursor.
|
||||
/// If unfocused or in insert mode, show absolute line number.
|
||||
#[serde(alias = "rel")]
|
||||
Relative,
|
||||
}
|
||||
|
||||
config_serde_adapter!(LineNumber);
|
||||
|
||||
fn setup_registry() -> OptionRegistry {
|
||||
let mut registry = OptionRegistry::new();
|
||||
registry.register(
|
||||
"scrolloff",
|
||||
"Number of lines of padding around the edge of the screen when scrolling",
|
||||
5usize,
|
||||
);
|
||||
registry.register(
|
||||
"shell",
|
||||
"Shell to use when running external commands",
|
||||
&["sh", "-c"],
|
||||
);
|
||||
registry.register("mouse", "Enable mouse mode", true);
|
||||
registry.register(
|
||||
"line-number",
|
||||
"Line number display: `absolute` simply shows each line's number, while \
|
||||
`relative` shows the distance from the current line. When unfocused or in \
|
||||
insert mode, `relative` will still show absolute line numbers",
|
||||
LineNumber::Absolute,
|
||||
);
|
||||
registry
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_values() {
|
||||
let registry = setup_registry();
|
||||
let global_scope = registry.global_scope();
|
||||
let scrolloff: usize = *global_scope.get("scrolloff");
|
||||
let shell_ = global_scope.get_deref::<Box<[_]>>("shell");
|
||||
let shell: &[Box<str>] = &shell_;
|
||||
let mouse: bool = *global_scope.get("mouse");
|
||||
let line_number: LineNumber = *global_scope.get("line-number");
|
||||
assert_eq!(scrolloff, 5);
|
||||
assert!(shell.iter().map(Box::deref).eq(["sh", "-c"]));
|
||||
assert!(mouse);
|
||||
assert_eq!(line_number, LineNumber::Absolute);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_overwrite() {
|
||||
let registry = setup_registry();
|
||||
let global_scope = registry.global_scope();
|
||||
let scope_1 = Arc::new(global_scope.create_scope());
|
||||
let scope_2 = Arc::new(global_scope.create_scope());
|
||||
let mut scope_3 = scope_1.create_scope();
|
||||
scope_1.set("line-number", "rel", ®istry).unwrap();
|
||||
let line_number: LineNumber = *scope_3.get("line-number");
|
||||
assert_eq!(line_number, LineNumber::Relative);
|
||||
scope_3.set_parent_scope(scope_2.clone());
|
||||
let line_number: LineNumber = *scope_3.get("line-number");
|
||||
assert_eq!(line_number, LineNumber::Absolute);
|
||||
scope_2.set("line-number", "rel", ®istry).unwrap();
|
||||
let line_number: LineNumber = *scope_3.get("line-number");
|
||||
assert_eq!(line_number, LineNumber::Relative);
|
||||
scope_2.set("line-number", "abs", ®istry).unwrap();
|
||||
let line_number: LineNumber = *scope_3.get("line-number");
|
||||
assert_eq!(line_number, LineNumber::Absolute);
|
||||
}
|
296
helix-config/src/validator.rs
Normal file
296
helix-config/src/validator.rs
Normal file
@ -0,0 +1,296 @@
|
||||
use std::any::{type_name, Any};
|
||||
use std::error::Error;
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
|
||||
use crate::any::ConfigData;
|
||||
use crate::Value;
|
||||
|
||||
pub trait Validator: 'static + Debug {
|
||||
fn validate(&self, val: Value) -> Result<ConfigData>;
|
||||
}
|
||||
|
||||
pub trait Ty: Sized + Clone + 'static {
|
||||
fn from_value(val: Value) -> Result<Self>;
|
||||
fn to_value(&self) -> Value;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct IntegerRangeValidator<T> {
|
||||
pub min: isize,
|
||||
pub max: isize,
|
||||
ty: PhantomData<T>,
|
||||
}
|
||||
impl<E, T> IntegerRangeValidator<T>
|
||||
where
|
||||
E: Debug,
|
||||
T: TryInto<isize, Error = E>,
|
||||
{
|
||||
pub fn new(min: T, max: T) -> Self {
|
||||
Self {
|
||||
min: min.try_into().unwrap(),
|
||||
max: max.try_into().unwrap(),
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for IntegerRangeValidator<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("IntegerRangeValidator")
|
||||
.field("min", &self.min)
|
||||
.field("max", &self.max)
|
||||
.field("ty", &type_name::<T>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, T> IntegerRangeValidator<T>
|
||||
where
|
||||
E: Error + Sync + Send + 'static,
|
||||
T: Any + TryFrom<isize, Error = E>,
|
||||
{
|
||||
pub fn validate(&self, val: Value) -> Result<T> {
|
||||
let IntegerRangeValidator { min, max, .. } = *self;
|
||||
let Value::Int(val) = val else {
|
||||
bail!("expected an integer")
|
||||
};
|
||||
ensure!(
|
||||
min <= val && val <= max,
|
||||
"expected an integer between {min} and {max} (got {val})",
|
||||
);
|
||||
Ok(T::try_from(val)?)
|
||||
}
|
||||
}
|
||||
impl<E, T> Validator for IntegerRangeValidator<T>
|
||||
where
|
||||
E: Error + Sync + Send + 'static,
|
||||
T: Any + TryFrom<isize, Error = E>,
|
||||
{
|
||||
fn validate(&self, val: Value) -> Result<ConfigData> {
|
||||
Ok(ConfigData::new(self.validate(val)))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! integer_tys {
|
||||
($($ty: ident),*) => {
|
||||
$(
|
||||
impl Ty for $ty {
|
||||
fn to_value(&self) -> Value {
|
||||
Value::Int((*self).try_into().unwrap())
|
||||
}
|
||||
|
||||
fn from_value(val: Value) -> Result<Self> {
|
||||
IntegerRangeValidator::new($ty::MIN, $ty::MAX).validate(val)
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
integer_tys! {
|
||||
i8, i16, i32, isize,
|
||||
u8, u16, u32
|
||||
}
|
||||
|
||||
impl Ty for usize {
|
||||
fn to_value(&self) -> Value {
|
||||
Value::Int((*self).try_into().unwrap())
|
||||
}
|
||||
|
||||
fn from_value(val: Value) -> Result<Self> {
|
||||
IntegerRangeValidator::new(0usize, isize::MAX as usize).validate(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ty for u64 {
|
||||
fn to_value(&self) -> Value {
|
||||
Value::Int((*self).try_into().unwrap())
|
||||
}
|
||||
|
||||
fn from_value(val: Value) -> Result<Self> {
|
||||
IntegerRangeValidator::new(0u64, isize::MAX as u64).validate(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ty for bool {
|
||||
fn to_value(&self) -> Value {
|
||||
Value::Bool(*self)
|
||||
}
|
||||
fn from_value(val: Value) -> Result<Self> {
|
||||
let Value::Bool(val) = val else {
|
||||
bail!("expected a boolean")
|
||||
};
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ty for Box<str> {
|
||||
fn to_value(&self) -> Value {
|
||||
Value::String(self.clone().into_string())
|
||||
}
|
||||
fn from_value(val: Value) -> Result<Self> {
|
||||
let Value::String(val) = val else {
|
||||
bail!("expected a string")
|
||||
};
|
||||
Ok(val.into_boxed_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Ty for char {
|
||||
fn to_value(&self) -> Value {
|
||||
Value::String(self.to_string())
|
||||
}
|
||||
|
||||
fn from_value(val: Value) -> Result<Self> {
|
||||
let Value::String(val) = val else {
|
||||
bail!("expected a string")
|
||||
};
|
||||
ensure!(
|
||||
val.chars().count() == 1,
|
||||
"expecet a single character (got {val:?})"
|
||||
);
|
||||
Ok(val.chars().next().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Ty for std::string::String {
|
||||
fn to_value(&self) -> Value {
|
||||
Value::String(self.clone())
|
||||
}
|
||||
fn from_value(val: Value) -> Result<Self> {
|
||||
let Value::String(val) = val else {
|
||||
bail!("expected a string")
|
||||
};
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ty> Ty for Option<T> {
|
||||
fn to_value(&self) -> Value {
|
||||
match self {
|
||||
Some(_) => todo!(),
|
||||
None => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_value(val: Value) -> Result<Self> {
|
||||
if val == Value::Null {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(Some(T::from_value(val)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ty> Ty for Box<T> {
|
||||
fn from_value(val: Value) -> Result<Self> {
|
||||
Ok(Box::new(T::from_value(val)?))
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
T::to_value(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ty> Ty for indexmap::IndexMap<Box<str>, T, ahash::RandomState> {
|
||||
fn from_value(val: Value) -> Result<Self> {
|
||||
let Value::Map(map) = val else {
|
||||
bail!("expected a map");
|
||||
};
|
||||
map.into_iter()
|
||||
.map(|(k, v)| Ok((k, T::from_value(v)?)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
let map = self
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.to_value()))
|
||||
.collect();
|
||||
Value::Map(Box::new(map))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ty> Ty for Box<[T]> {
|
||||
fn to_value(&self) -> Value {
|
||||
Value::List(self.iter().map(T::to_value).collect())
|
||||
}
|
||||
fn from_value(val: Value) -> Result<Self> {
|
||||
let Value::List(val) = val else {
|
||||
bail!("expected a list")
|
||||
};
|
||||
val.iter().cloned().map(T::from_value).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Ty for serde_json::Value {
|
||||
fn from_value(val: Value) -> Result<Self> {
|
||||
Ok(val.into())
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct StaticValidator<T: Ty> {
|
||||
pub(super) ty: PhantomData<fn(&T)>,
|
||||
}
|
||||
|
||||
impl<T: Ty> Debug for StaticValidator<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("StaticValidator")
|
||||
.field("ty", &type_name::<T>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ty> Validator for StaticValidator<T> {
|
||||
fn validate(&self, val: Value) -> Result<ConfigData> {
|
||||
let val = <T as Ty>::from_value(val)?;
|
||||
Ok(ConfigData::new(val))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TyValidator<F, T: Ty> {
|
||||
pub(super) ty: PhantomData<fn(&T)>,
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl<T: Ty, F> Debug for TyValidator<F, T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TyValidator")
|
||||
.field("ty", &type_name::<T>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F> Validator for TyValidator<F, T>
|
||||
where
|
||||
T: Ty,
|
||||
F: Fn(&T) -> anyhow::Result<()> + 'static,
|
||||
{
|
||||
fn validate(&self, val: Value) -> Result<ConfigData> {
|
||||
let val = <T as Ty>::from_value(val)?;
|
||||
(self.f)(&val)?;
|
||||
Ok(ConfigData::new(val))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ty_validator<T, F>(f: F) -> impl Validator
|
||||
where
|
||||
T: Ty,
|
||||
F: Fn(&T) -> anyhow::Result<()> + 'static,
|
||||
{
|
||||
TyValidator { ty: PhantomData, f }
|
||||
}
|
||||
|
||||
pub fn regex_str_validator() -> impl Validator {
|
||||
ty_validator(|val: &crate::String| {
|
||||
regex_syntax::parse(val)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
448
helix-config/src/value.rs
Normal file
448
helix-config/src/value.rs
Normal file
@ -0,0 +1,448 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::ser::{Error as _, Impossible};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Error, Result};
|
||||
|
||||
use crate::Ty;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Value {
|
||||
List(Vec<Value>),
|
||||
Map(Box<IndexMap<Box<str>, Value, ahash::RandomState>>),
|
||||
Int(isize),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
String(String),
|
||||
Null,
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn typed<T: Ty>(self) -> anyhow::Result<T> {
|
||||
T::from_value(self)
|
||||
}
|
||||
|
||||
pub fn append(&mut self, val: Value, depth: usize) {
|
||||
match (self, val) {
|
||||
(Value::List(dst), Value::List(ref mut val)) => dst.append(val),
|
||||
(Value::Map(dst), Value::Map(val)) if depth == 0 || dst.is_empty() => {
|
||||
dst.extend(val.into_iter())
|
||||
}
|
||||
(Value::Map(dst), Value::Map(val)) => {
|
||||
dst.reserve(val.len());
|
||||
for (k, v) in val.into_iter() {
|
||||
// we don't use the entry api because we want
|
||||
// to maintain thhe ordering
|
||||
let merged = match dst.shift_remove(&k) {
|
||||
Some(mut old) => {
|
||||
old.append(v, depth - 1);
|
||||
old
|
||||
}
|
||||
None => v,
|
||||
};
|
||||
dst.insert(k, merged);
|
||||
}
|
||||
}
|
||||
(dst, val) => *dst = val,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Value {
|
||||
fn from(value: &str) -> Self {
|
||||
Value::String(value.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! from_int {
|
||||
($($ty: ident),*) => {
|
||||
$(
|
||||
impl From<$ty> for Value {
|
||||
fn from(value: $ty) -> Self {
|
||||
Value::Int(value.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl From<serde_json::Value> for Value {
|
||||
fn from(value: serde_json::Value) -> Self {
|
||||
to_value(value).unwrap()
|
||||
}
|
||||
}
|
||||
impl From<&serde_json::Value> for Value {
|
||||
fn from(value: &serde_json::Value) -> Self {
|
||||
to_value(value).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Value> for serde_json::Value {
|
||||
fn from(value: Value) -> Self {
|
||||
serde_json::to_value(value).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
from_int!(isize, usize, u32, i32, i16, u16, i8, u8);
|
||||
|
||||
pub fn to_value<T>(value: T) -> Result<Value>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
value.serialize(Serializer)
|
||||
}
|
||||
|
||||
pub fn from_value<T>(value: Value) -> Result<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
// roundtripping trough json is very inefficient *and incorrect* (captures
|
||||
// json semantics that don't apply to us)
|
||||
// TODO: implement a custom deserializer just like serde_json does
|
||||
serde_json::from_value(value.into())
|
||||
}
|
||||
|
||||
// We only use our own error type; no need for From conversions provided by the
|
||||
// standard library's try! macro. This reduces lines of LLVM IR by 4%.
|
||||
macro_rules! tri {
|
||||
($e:expr $(,)?) => {
|
||||
match $e {
|
||||
core::result::Result::Ok(val) => val,
|
||||
core::result::Result::Err(err) => return core::result::Result::Err(err),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Serializer whose output is a `Value`.
|
||||
///
|
||||
/// This is the serializer that backs [`serde_json::to_value`][crate::to_value].
|
||||
/// Unlike the main serde_json serializer which goes from some serializable
|
||||
/// value of type `T` to JSON text, this one goes from `T` to
|
||||
/// `serde_json::Value`.
|
||||
///
|
||||
/// The `to_value` function is implementable as:
|
||||
///
|
||||
/// ```
|
||||
/// use serde::Serialize;
|
||||
/// use serde_json::{Error, Value};
|
||||
///
|
||||
/// pub fn to_value<T>(input: T) -> Result<Value, Error>
|
||||
/// where
|
||||
/// T: Serialize,
|
||||
/// {
|
||||
/// input.serialize(serde_json::value::Serializer)
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Serializer;
|
||||
|
||||
impl serde::Serializer for Serializer {
|
||||
type Ok = Value;
|
||||
type Error = Error;
|
||||
|
||||
type SerializeSeq = SerializeVec;
|
||||
type SerializeTuple = SerializeVec;
|
||||
type SerializeTupleStruct = SerializeVec;
|
||||
type SerializeTupleVariant = Impossible<Value, Error>;
|
||||
type SerializeMap = SerializeMap;
|
||||
type SerializeStruct = SerializeMap;
|
||||
type SerializeStructVariant = Impossible<Value, Error>;
|
||||
|
||||
#[inline]
|
||||
fn serialize_bool(self, value: bool) -> Result<Value> {
|
||||
Ok(Value::Bool(value))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_i8(self, value: i8) -> Result<Value> {
|
||||
self.serialize_i64(value as i64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_i16(self, value: i16) -> Result<Value> {
|
||||
self.serialize_i64(value as i64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_i32(self, value: i32) -> Result<Value> {
|
||||
self.serialize_i64(value as i64)
|
||||
}
|
||||
|
||||
fn serialize_i64(self, value: i64) -> Result<Value> {
|
||||
Ok(Value::Int(value.try_into().unwrap()))
|
||||
}
|
||||
|
||||
fn serialize_i128(self, _value: i128) -> Result<Value> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_u8(self, value: u8) -> Result<Value> {
|
||||
self.serialize_u64(value as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_u16(self, value: u16) -> Result<Value> {
|
||||
self.serialize_u64(value as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_u32(self, value: u32) -> Result<Value> {
|
||||
self.serialize_u64(value as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_u64(self, value: u64) -> Result<Value> {
|
||||
Ok(Value::Int(value.try_into().unwrap()))
|
||||
}
|
||||
|
||||
fn serialize_u128(self, _value: u128) -> Result<Value> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_f32(self, float: f32) -> Result<Value> {
|
||||
Ok(Value::Float(float as f64))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_f64(self, float: f64) -> Result<Value> {
|
||||
Ok(Value::Float(float))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_char(self, value: char) -> Result<Value> {
|
||||
let mut s = String::new();
|
||||
s.push(value);
|
||||
Ok(Value::String(s))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_str(self, value: &str) -> Result<Value> {
|
||||
Ok(Value::String(value.into()))
|
||||
}
|
||||
|
||||
fn serialize_bytes(self, value: &[u8]) -> Result<Value> {
|
||||
let vec = value.iter().map(|&b| Value::Int(b.into())).collect();
|
||||
Ok(Value::List(vec))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_unit(self) -> Result<Value> {
|
||||
Ok(Value::Null)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_unit_struct(self, _name: &'static str) -> Result<Value> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_unit_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
variant: &'static str,
|
||||
) -> Result<Value> {
|
||||
self.serialize_str(variant)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_newtype_struct<T>(self, _name: &'static str, _value: &T) -> Result<Value>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn serialize_newtype_variant<T>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_value: &T,
|
||||
) -> Result<Value>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_none(self) -> Result<Value> {
|
||||
self.serialize_unit()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_some<T>(self, value: &T) -> Result<Value>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
|
||||
Ok(SerializeVec {
|
||||
vec: Vec::with_capacity(len.unwrap_or(0)),
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
|
||||
self.serialize_seq(Some(len))
|
||||
}
|
||||
|
||||
fn serialize_tuple_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
len: usize,
|
||||
) -> Result<Self::SerializeTupleStruct> {
|
||||
self.serialize_seq(Some(len))
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeTupleVariant> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
|
||||
Ok(SerializeMap {
|
||||
map: IndexMap::default(),
|
||||
next_key: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn serialize_struct_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStructVariant> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn collect_str<T>(self, value: &T) -> Result<Value>
|
||||
where
|
||||
T: ?Sized + Display,
|
||||
{
|
||||
Ok(Value::String(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SerializeVec {
|
||||
vec: Vec<Value>,
|
||||
}
|
||||
|
||||
impl serde::ser::SerializeSeq for SerializeVec {
|
||||
type Ok = Value;
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
self.vec.push(tri!(to_value(value)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Value> {
|
||||
Ok(Value::List(self.vec))
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::ser::SerializeTuple for SerializeVec {
|
||||
type Ok = Value;
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
serde::ser::SerializeSeq::serialize_element(self, value)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Value> {
|
||||
serde::ser::SerializeSeq::end(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::ser::SerializeTupleStruct for SerializeVec {
|
||||
type Ok = Value;
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
serde::ser::SerializeSeq::serialize_element(self, value)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Value> {
|
||||
serde::ser::SerializeSeq::end(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SerializeMap {
|
||||
map: IndexMap<Box<str>, Value, ahash::RandomState>,
|
||||
next_key: Option<Box<str>>,
|
||||
}
|
||||
|
||||
impl serde::ser::SerializeMap for SerializeMap {
|
||||
type Ok = Value;
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_key<T>(&mut self, key: &T) -> Result<()>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
let key = to_value(key)?;
|
||||
let Value::String(val) = key else {
|
||||
return Err(Error::custom("only string keys are supported"));
|
||||
};
|
||||
self.next_key = Some(val.into_boxed_str());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_value<T>(&mut self, value: &T) -> Result<()>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
let key = self.next_key.take();
|
||||
// Panic because this indicates a bug in the program rather than an
|
||||
// expected failure.
|
||||
let key = key.expect("serialize_value called before serialize_key");
|
||||
self.map.insert(key, tri!(to_value(value)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Value> {
|
||||
Ok(Value::Map(Box::new(self.map)))
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::ser::SerializeStruct for SerializeMap {
|
||||
type Ok = Value;
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
serde::ser::SerializeMap::serialize_entry(self, key, value)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Value> {
|
||||
serde::ser::SerializeMap::end(self)
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ integration = []
|
||||
|
||||
[dependencies]
|
||||
helix-loader = { path = "../helix-loader" }
|
||||
helix-config = { path = "../helix-config" }
|
||||
|
||||
ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
|
||||
smallvec = "1.11"
|
||||
@ -51,6 +52,8 @@ textwrap = "0.16.0"
|
||||
|
||||
nucleo.workspace = true
|
||||
parking_lot = "0.12"
|
||||
anyhow = "1.0.79"
|
||||
indexmap = { version = "2.1.0", features = ["serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = { version = "1", default-features = false }
|
||||
|
@ -1,10 +0,0 @@
|
||||
/// Syntax configuration loader based on built-in languages.toml.
|
||||
pub fn default_syntax_loader() -> crate::syntax::Configuration {
|
||||
helix_loader::config::default_lang_config()
|
||||
.try_into()
|
||||
.expect("Could not serialize built-in languages.toml")
|
||||
}
|
||||
/// Syntax configuration loader based on user configured languages.toml.
|
||||
pub fn user_syntax_loader() -> Result<crate::syntax::Configuration, toml::de::Error> {
|
||||
helix_loader::config::user_lang_config()?.try_into()
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
pub mod auto_pairs;
|
||||
pub mod chars;
|
||||
pub mod comment;
|
||||
pub mod config;
|
||||
pub mod diagnostic;
|
||||
pub mod diff;
|
||||
pub mod doc_formatter;
|
||||
|
Loading…
Reference in New Issue
Block a user