pub struct ConfigRegistry { /* private fields */ }Expand description
Process-shared registry.
Implementations§
Source§impl ConfigRegistry
impl ConfigRegistry
Sourcepub fn new() -> Self
pub fn new() -> Self
An empty registry with no event publisher. Options arrive via
Self::init_from_linkme (every options! declaration linked
into the binary) and Self::register (runtime / plugin
options).
§Examples
Declared options, read and written by type:
use lattice_config::{ConfigRegistry, Tabstop};
let reg = ConfigRegistry::new();
assert!(reg.get_typed::<Tabstop>().is_none()); // nothing registered yet
reg.init_from_linkme();
assert_eq!(*reg.get_typed::<Tabstop>().unwrap(), 4); // the declared default
reg.set_typed::<Tabstop>(2).unwrap();
assert_eq!(*reg.get_typed::<Tabstop>().unwrap(), 2);
// The option's validator (1..=32) rejects the write; the old value stays.
assert!(reg.set_typed::<Tabstop>(0).is_err());
assert_eq!(*reg.get_typed::<Tabstop>().unwrap(), 2);A runtime-registered option, read through its handle:
use lattice_config::ConfigRegistry;
use lattice_config::option::Option;
let reg = ConfigRegistry::new();
let depth = reg.register(
Option::<i64>::builder("myplugin.depth", 3, "Search depth.")
.aliases(&["mpd"])
.validate(|d| if *d > 0 { Ok(()) } else { Err(format!("depth must be > 0, got {d}")) })
.build(),
);
assert_eq!(*reg.get(depth), 3);
assert_eq!(reg.parse_and_set_command("mpd=5").unwrap(), "myplugin.depth=5");
assert_eq!(reg.with(depth, |d| *d), 5);
assert!(reg.set(depth, -1).is_err());Sourcepub fn set_event_publisher(&self, publisher: EventPublisher)
pub fn set_event_publisher(&self, publisher: EventPublisher)
Install the [Event::OptionChanged] sink. Idempotent
replacement – calling twice swaps the closure. Designed to
be called once at boot from the consumer that owns the
EventBus (the App today; future plugin-host paths route
through the same closure).
Sourcepub fn register<T: OptionType>(&self, option: Option<T>) -> OptionHandle<T>
pub fn register<T: OptionType>(&self, option: Option<T>) -> OptionHandle<T>
Register a typed option. Returns an OptionHandle<T> for
hot-path access. Returns Err if option.name or any alias
is already registered (duplicate-name detection is a
programming-error guard; recoverable via the Result so the
caller can decide whether a duplicate is fatal).
Sourcepub fn try_register<T: OptionType>(
&self,
option: Option<T>,
) -> Result<OptionHandle<T>, ConfigError>
pub fn try_register<T: OptionType>( &self, option: Option<T>, ) -> Result<OptionHandle<T>, ConfigError>
Fallible registration. Most callers want Self::register;
this exists for plugins / dynamic code that can fail
gracefully on a name collision.
Sourcepub fn register_with_typeid<T: OptionType>(
&self,
option: Option<T>,
type_id: TypeId,
) -> OptionHandle<T>
pub fn register_with_typeid<T: OptionType>( &self, option: Option<T>, type_id: TypeId, ) -> OptionHandle<T>
Variant of Self::register that additionally records a
TypeId ↔ option-index mapping for type-keyed reads via
Self::get_typed. Called from the
crate::options! macro’s register_fn thunk during
Self::init_from_linkme; not generally called by hand.
type_id is TypeId::of::<D>() where D is the
crate::OptionDecl type. Two declarations with the same
TypeId cannot exist (Rust’s type system enforces it
cross-crate), so duplicate-typeid is a programming error
and panics; the macro never produces it.
Sourcepub fn get_typed<D: OptionDecl>(&self) -> Option<Arc<D::Value>>
pub fn get_typed<D: OptionDecl>(&self) -> Option<Arc<D::Value>>
Type-keyed read: returns the resolved value for the
OptionDecl type D. D::Value is the value type.
Returns None if the option has not been registered (no
register_with_typeid call seen for this TypeId); this
is legitimate transient state during boot before
Self::init_from_linkme runs.
Sourcepub fn handle_for_decl<D: OptionDecl>(&self) -> Option<OptionHandle<D::Value>>
pub fn handle_for_decl<D: OptionDecl>(&self) -> Option<OptionHandle<D::Value>>
Type-keyed handle lookup: recover the legacy
OptionHandle<D::Value> from an OptionDecl type.
Used by the M.2.0b backwards-compatibility shim in
core_options::register_core_options to populate the
CoreOptions struct with handles after
Self::init_from_linkme has run.
Returns None if D was not registered. M.2.0c retires
the OptionHandle<T> API and this method along with it.
Sourcepub fn set_typed<D: OptionDecl>(&self, value: D::Value) -> Result<(), String>
pub fn set_typed<D: OptionDecl>(&self, value: D::Value) -> Result<(), String>
Type-keyed write: set the option declared by D to value.
Runs the option’s validator before committing, publishes
[Event::OptionChanged] on success, returns the validator
error (or a missing-registration error) on failure.
Hot-path-equivalent to the legacy set(handle, value)
shape; one TypeId lookup per write. Writes are rare (user
:set foo=bar cmdline, programmatic toggles), so the
extra hash bears no measurable cost.
Sourcepub fn bootstrap_resolved_with_current_values(&self, out: &mut ResolvedOptions)
pub fn bootstrap_resolved_with_current_values(&self, out: &mut ResolvedOptions)
Bootstrap a crate::ResolvedOptions cache with every
registered option’s current value (layer 5 + 6 of the
resolution stack – mode-architecture.md §6.1). Walks
the typeid map and writes each option’s
crate::ErasedOption::current_value_erased into the
cache, keyed by TypeId.
Used by crate::Resolver::resolve_into callers that
want a fully-populated resolved cache without manually
chaining a “default” layer. After this returns, the
caller layers any mode / buffer-local / modal overrides
on top via the resolver.
Sourcepub fn type_id_for_name(&self, name: &str) -> Option<TypeId>
pub fn type_id_for_name(&self, name: &str) -> Option<TypeId>
Look up the TypeId registered for an option’s canonical name.
Returns None if the option was not registered via
register_with_typeid (e.g. a bare try_register call, or the
option simply doesn’t exist).
Sourcepub fn init_from_linkme(&self)
pub fn init_from_linkme(&self)
Boot loop: walk the OPTION_DECLS linkme slice and
register every option declared anywhere in the workspace.
Idempotent: calling more than once is a no-op (the second
call observes that every option is already registered and
returns without doing anything). Boot panics on a true
programming error (e.g. two distinct OptionDecl types
happen to declare the same display name across crates).
After this returns, every options! { ... } declaration
is reachable via Self::get_typed / Self::lookup /
the cmdline :set / TOML loader. Boot is the moment when
“the option exists” goes from “compile-time fact in the
declaring crate” to “runtime fact in the registry.”
Sourcepub fn get<T: OptionType>(&self, handle: OptionHandle<T>) -> Arc<T> ⓘ
pub fn get<T: OptionType>(&self, handle: OptionHandle<T>) -> Arc<T> ⓘ
Wait-free typed read.
Returns Arc<T>; Arc::clone is one atomic increment.
Returns the option’s default-via-clone fallback if the
handle’s type or index doesn’t match – safer than
panicking, callers can detect via Self::try_get if they
care to. Handles obtained from Self::register are
always type-correct, so this only matters for forged ones.
Sourcepub fn try_get<T: OptionType>(&self, handle: OptionHandle<T>) -> Option<Arc<T>>
pub fn try_get<T: OptionType>(&self, handle: OptionHandle<T>) -> Option<Arc<T>>
Fallible typed read. Returns None if the handle’s index
is out of range or refers to an option of a different T.
Sourcepub fn with<T: OptionType, R>(
&self,
handle: OptionHandle<T>,
f: impl FnOnce(&T) -> R,
) -> R
pub fn with<T: OptionType, R>( &self, handle: OptionHandle<T>, f: impl FnOnce(&T) -> R, ) -> R
Closure-style read. Avoids the Arc::clone cost of
Self::get for one-shot reads.
Sourcepub fn set<T: OptionType>(
&self,
handle: OptionHandle<T>,
value: T,
) -> Result<(), String>
pub fn set<T: OptionType>( &self, handle: OptionHandle<T>, value: T, ) -> Result<(), String>
Typed write through a handle. Runs the option’s validator
before committing. Publishes [Event::OptionChanged] on
success.
Sourcepub fn lookup(&self, name: &str) -> Option<Arc<dyn ErasedOption>>
pub fn lookup(&self, name: &str) -> Option<Arc<dyn ErasedOption>>
Look up an option by name (or alias). Returns the erased view – the by-name path is for cmdline / customize / plugin introspection where the type isn’t known at compile time.
Sourcepub fn get_bool_by_name(&self, name: &str) -> Option<bool>
pub fn get_bool_by_name(&self, name: &str) -> Option<bool>
Read a bool-typed option’s current value by name.
Returns None if the option doesn’t exist, isn’t boolean,
or its erased current value fails to downcast to bool
(last case is defense-in-depth – shouldn’t happen given
is_bool() agreement). Used by the host’s mode-mirror
cascade (see Mode::mirrors_option) so display modes can
stay in sync with their typed-option counterparts without
hardcoded per-mode special cases in the cascade handler.
Sourcepub fn get_int_by_name(&self, name: &str) -> Option<i64>
pub fn get_int_by_name(&self, name: &str) -> Option<i64>
Read an i64-typed option’s current value by name. Returns
None if the option doesn’t exist or its erased current value
isn’t an i64 (the downcast IS the type check — there’s no
is_int predicate, and a wrong-type option simply fails the
downcast). The i64 shape covers every integer option (the
options! macro stores counts / sizes as i64). Used by
subsystems that read a numeric option by name without importing
its decl type — e.g. lattice-diff’s UnchangedFoldSource
reading ui.diff.context.
Sourcepub fn get_string_by_name(&self, name: &str) -> Option<String>
pub fn get_string_by_name(&self, name: &str) -> Option<String>
Read a String-typed option’s current value by name. The downcast IS
the type check, as with Self::get_int_by_name. Used by subsystems
that read an enum-ish option by name without importing its decl type —
e.g. the host reading a plugin’s trim-scope, which is registered
dynamically and therefore has no decl type to import.
Sourcepub fn iter(&self) -> Vec<Arc<dyn ErasedOption>>
pub fn iter(&self) -> Vec<Arc<dyn ErasedOption>>
Iterate every registered option in registration order.
Used by completion (gen:options) and the customize buffer
view to enumerate.
Sourcepub fn len(&self) -> usize
pub fn len(&self) -> usize
Number of registered options. Counts live options only — a tombstoned (unregistered) slot does not count (PH7.12b).
Sourcepub fn is_empty(&self) -> bool
pub fn is_empty(&self) -> bool
true when no live option is registered (tombstoned slots do
not count).
Sourcepub fn unregister(&self, name: &str) -> bool
pub fn unregister(&self, name: &str) -> bool
Remove an option by name (or any alias), the teardown seam for a plugin
reload / unload (PH7.12b). Drops every name+alias mapping pointing at the
option’s slot plus any TypeId mapping, tombstones the by_id slot
(freeing the Arc), and recycles the index through the free-list so a
re-register reuses it. Idempotent: false if nothing is registered under
name. Driven by the plugin host’s teardown bundle with the option names
a plugin registered (PluginState::config_contributions); built-in
options have no unload path in practice. Any live OptionHandle for
the removed option is left dangling by contract — its slot is None, so
a by-handle read returns nothing rather than another option’s value; the
index is only ever reused by a new registration, never silently
re-pointed under an existing handle.
Sourcepub fn record_failed_assignment(
&self,
name: &str,
message: String,
source: Option<PathBuf>,
)
pub fn record_failed_assignment( &self, name: &str, message: String, source: Option<PathBuf>, )
OC.11c: record that an assignment to name failed.
name is canonicalised here so an alias (:set ts=999) records under
the name the option was declared with — a plugin asking about its own
config would never think to ask about an alias, and an alias-keyed
record is invisible to every reader.
Sourcepub fn clear_failed_assignment(&self, name: &str)
pub fn clear_failed_assignment(&self, name: &str)
OC.11c: forget any failure recorded for name — an assignment to it
has since succeeded, so the record describes something untrue.
Sourcepub fn clear_all_failed_assignments(&self)
pub fn clear_all_failed_assignments(&self)
OC.11c: drop every recorded failure.
Called at the start of a config LOAD, which is a fresh reading of the whole file: an option whose failing line the user deleted produces no message at all, so a per-message update would leave its diagnostic behind forever.
Sourcepub fn failed_assignment(&self, name: &str) -> Option<ConfigDiagnostic>
pub fn failed_assignment(&self, name: &str) -> Option<ConfigDiagnostic>
OC.11c: the failure recorded against name, if the last assignment to
it failed.
None means the last assignment succeeded or there never was one —
not distinguished, deliberately: the caller’s question is “can I trust
this value”, and both answers are yes.
Sourcepub fn parse_and_set_command(&self, input: &str) -> Result<String, ConfigError>
pub fn parse_and_set_command(&self, input: &str) -> Result<String, ConfigError>
Drive the cmdline :set syntax against the registry. Parses
the input through parse_set, then dispatches to the
matching option’s parse / set / negate / format path.
Returns the echo line on success (canonical-name=value) — the
caller surfaces it via the cmdline echo. Forms:
:set foo– echoesfoo=currentfor non-bool, sets true for bool (vim convention).:set nofoo– sets bool to false;ConfigError::NotBooleanfor any other type.:set foo=value– parses + validates + sets.:set foo?– always echoes the current value.:set foo&– resets to the registered default.
Every successful write publishes [Event::OptionChanged]; a
query does not. The outcome also maintains the failed-assignment
record (Self::failed_assignment): a failure on a known option
is recorded (with no source file), a success clears it, and an
unknown name records nothing.
§Examples
use lattice_config::{ConfigError, ConfigRegistry};
let reg = ConfigRegistry::new();
reg.init_from_linkme();
assert_eq!(reg.parse_and_set_command("ts=8").unwrap(), "tabstop=8");
assert_eq!(reg.parse_and_set_command("wrap").unwrap(), "wrap=true");
assert_eq!(reg.parse_and_set_command("nowrap").unwrap(), "wrap=false");
assert_eq!(reg.parse_and_set_command("tabstop&").unwrap(), "tabstop=4");
assert!(matches!(
reg.parse_and_set_command("notabstop"),
Err(ConfigError::NotBoolean(_))
));
assert!(matches!(
reg.parse_and_set_command("nosuch=1"),
Err(ConfigError::UnknownOption(_))
));
// A rejected write is remembered against the canonical name...
assert!(reg.parse_and_set_command("ts=0").is_err());
assert!(reg.failed_assignment("tabstop").is_some());
// ...until a later write succeeds.
reg.parse_and_set_command("tabstop=2").unwrap();
assert!(reg.failed_assignment("tabstop").is_none());Sourcepub fn parse_for_buffer_local(
&self,
input: &str,
) -> Result<(TypeId, Arc<dyn Any + Send + Sync>, String), ConfigError>
pub fn parse_for_buffer_local( &self, input: &str, ) -> Result<(TypeId, Arc<dyn Any + Send + Sync>, String), ConfigError>
Parse an option spec string for use as a buffer-local override,
without writing to the global registry. Returns the triple
(TypeId, erased_value, canonical_name) needed to construct an
crate::OptionOverride for the buffer-local layer.
NameOnly(name)— for bool options, returns erasedtrue. For non-bool, returnsErr(callers echo the value instead).Negate(name)— returns erasedfalse(rejects non-bool via the option’s own error message).Assign { name, value }— parsesvalueagainst the option’scrate::OptionTypeand validates without writing.Query(name)— always returnsConfigError::QueryNotAllowed; callers use:setlocal name?echo path instead.Reset(name)— returnsConfigError::QueryNotAllowed; the caller’s:setlocal name&clear-override path handles it before reaching here.
§Examples
use std::any::TypeId;
use lattice_config::{ConfigRegistry, OptionOverride, Tabstop};
let reg = ConfigRegistry::new();
reg.init_from_linkme();
let (type_id, value, name) = reg.parse_for_buffer_local("ts=2").unwrap();
assert_eq!(type_id, TypeId::of::<Tabstop>());
assert_eq!(name, "tabstop");
assert_eq!(value.downcast_ref::<i64>(), Some(&2));
// The global value is untouched.
assert_eq!(*reg.get_typed::<Tabstop>().unwrap(), 4);
let ov = OptionOverride { option_type_id: type_id, value, priority: Default::default() };
assert_eq!(ov.downcast_value::<i64>(), Some(&2));