lattice_config/lib.rs
1//! The typed options system: every editor, mode, renderer and plugin
2//! setting is a registered, typed value in one [`ConfigRegistry`], read
3//! on the hot path by type and written at the boundaries (`:set`,
4//! `lattice.toml`, `:setlocal`, plugins) by name (DESIGN.md §5.12).
5//!
6//! Renderer-agnostic option machinery: the [`OptionType`] trait,
7//! the typed [`option::Option<T>`] spec, the type-erased [`ErasedOption`]
8//! trait the registry stores, and [`ConfigRegistry`] itself.
9//!
10//! ## What it owns
11//!
12//! - **Declaration.** The [`options!`] / [`groups!`] macros (from
13//! `lattice-config-macros`) turn a declaration into an [`OptionDecl`]
14//! marker type plus an [`OptionDeclMetadata`] entry in the
15//! [`OPTION_DECLS`] link-time slice; [`ConfigRegistry::init_from_linkme`]
16//! registers every one linked into the binary. The built-in set lives
17//! in [`core_options`] and is re-exported here (`Tabstop`, `Wrap`, ...);
18//! groups ([`OptionGroup`]) organise them for `:customize`.
19//! - **Values.** [`OptionType`] (parse / format / enumerate / schema) with
20//! impls for `bool`, `i64`, `String`, the lattice-core domain enums, and
21//! the display-policy value types defined here ([`SignColumn`],
22//! [`ModelineZone`], [`DiagnosticsInline`], [`Decorations`], ...).
23//! [`ConfigSchema`] / [`ConfigValue`] describe composite (list / record)
24//! values as data — see [`schema`].
25//! - **Access.** Type-keyed reads/writes ([`ConfigRegistry::get_typed`] /
26//! [`ConfigRegistry::set_typed`]), handle reads for runtime-registered
27//! options ([`option::OptionHandle`]), and the by-name `:set` path
28//! ([`parse_set`] → [`ConfigRegistry::parse_and_set_command`]).
29//! - **Layering.** Per-buffer resolution: modes and `:setlocal` contribute
30//! [`OptionOverrideSet`]s, the [`Resolver`] merges them by layer and
31//! [`OverridePriority`] into a [`ResolvedOptions`] cache, each winner
32//! tagged with its [`OptionOrigin`].
33//! - **Files.** The TOML [`loader`] (`lattice.toml`, `.lattice/config.toml`)
34//! and the config-home paths ([`config_home`], [`cache_home`]).
35//! - **Completion.** [`OptionsGenerator`], the `:set <Tab>` candidate source.
36//!
37//! ## What it must not depend on
38//!
39//! Its dependencies are the foundation only (`lattice-protocol`,
40//! `lattice-core`, `lattice-completion`). Almost every crate reads options,
41//! so anything this crate imported would sit beneath the whole editor:
42//! no host / `App`, no renderer, no mode registry, no event bus
43//! (`lattice-runtime` — events leave through an injected
44//! [`EventPublisher`] closure instead), no plugin host (so
45//! [`PluginTraceLevel`] duplicates the host's trace-level labels rather
46//! than importing the type). The layer-input override types moved *into*
47//! this crate for the same reason (see [`mod@overrides`]).
48//!
49//! ## Example
50//!
51//! ```
52//! use lattice_config::{ConfigRegistry, Tabstop};
53//!
54//! let config = ConfigRegistry::new();
55//! config.init_from_linkme(); // register every `options!` declaration
56//!
57//! // Hot path: read by type.
58//! assert_eq!(*config.get_typed::<Tabstop>().unwrap(), 4);
59//!
60//! // Boundary: write by name, exactly as `:set ts=2` does.
61//! assert_eq!(config.parse_and_set_command("ts=2").unwrap(), "tabstop=2");
62//! assert_eq!(*config.get_typed::<Tabstop>().unwrap(), 2);
63//!
64//! // Validation runs on every write; a rejected write changes nothing.
65//! assert!(config.parse_and_set_command("tabstop=99").is_err());
66//! assert_eq!(*config.get_typed::<Tabstop>().unwrap(), 2);
67//! ```
68//!
69//! Design: `docs/dev/architecture/typed-configuration.md` (schemas, composite
70//! values), `docs/dev/architecture/config-and-init.md` (when configuration
71//! arrives), `docs/dev/architecture/buffer-local-options.md` (`:setlocal`,
72//! origins), `docs/dev/architecture/mode-architecture.md` §6 (declarations,
73//! layering, groups).
74//!
75//! ## Design (γ — value-on-spec storage)
76//!
77//! Each [`option::Option<T>`] owns its current value behind an
78//! [`arc_swap::ArcSwap<T>`]. Reads through a typed
79//! [`crate::option::OptionHandle<T>`] are wait-free pointer loads. Writes go
80//! through the registry (typed via `set` / by-name via
81//! `parse_and_set_command`), which validates and stores.
82//!
83//! Renderer-specific options live in the renderer's crate but
84//! register against the same [`ConfigRegistry`] at App startup.
85//! The crate has no knowledge of the App or any concrete renderer.
86//!
87//! ## Crate boundary
88//!
89//! The trait + the three primitive impls (`bool`, `i64`, `String`)
90//! live here. So do the impls for the `lattice-core` domain enums
91//! (`FoldMethod`, `IndentMethod`, ...): the orphan rule allows a local
92//! trait on a foreign type, and `lattice-core` cannot depend on this
93//! crate. A crate *above* this one that defines its own value type
94//! implements [`OptionType`] itself, importing the trait from
95//! `lattice-config`.
96//!
97//! ## What's NOT here
98//!
99//! - **`App` reference.** Setters don't take an `&mut App`. The
100//! value lives in the spec; consumers read it through their
101//! typed handle. Renderers run side-effect cascades
102//! (`relativenumber` ⇒ `number`, `foldmethod` ⇒ recompute folds,
103//! `ui.*` ⇒ refresh derived theme styles) in their own
104//! post-set hook, polling the parsed `:set` form.
105//! - **Other crates' options.** Options owned elsewhere are declared
106//! with the same [`options!`] macro in the owning crate and join the
107//! registry through [`OPTION_DECLS`] at boot — e.g. the `ui.separator`
108//! / `ui.statusline_*_fg` family in `lattice_host::ui::theme_options`.
109//! Runtime-only options (plugins) use [`ConfigRegistry::register`].
110//!
111//! ## Event-bus integration (DESIGN.md §5.10 + §5.12)
112//!
113//! The registry optionally publishes [`lattice_protocol::Event::OptionChanged`]
114//! on every successful set so consumers can react to typed-option
115//! changes without polling. Wire it via
116//! [`ConfigRegistry::set_event_publisher`] -- the closure receives
117//! the `Event` and delegates to the consumer's bus. The crate is
118//! agnostic to *which* bus (avoids a dep on `lattice-runtime`); the
119//! App today calls `event_bus.publish(event)` from inside the
120//! closure.
121//!
122//! Events fire on:
123//! - typed `set::<T>(handle, value)` writes
124//! - cmdline `parse_and_set_command(":set foo=bar")` (Assign)
125//! - cmdline `:set nofoo` (Negate)
126//! - cmdline `:set foo` boolean toggle (NameOnly on bool option)
127//!
128//! Events do NOT fire on `:set foo?` (Query) or on validation /
129//! parse failures.
130
131#![warn(missing_docs)]
132
133// Allow this crate to refer to itself by name. The
134// `lattice-config-macros` proc macros emit code that
135// references `::lattice_config::*` -- the absolute path lets
136// expansions work uniformly in consumer crates AND inside
137// `lattice-config` itself. Without this `extern crate`,
138// `::lattice_config` doesn't resolve when the macro is
139// invoked inside this crate.
140extern crate self as lattice_config;
141
142pub mod completion;
143pub mod core_options;
144mod decorations;
145mod diagnostics_options;
146mod domain;
147mod erased;
148mod expand_height;
149pub mod group;
150pub mod loader;
151mod modeline_zone;
152mod pane_options;
153mod plugin_options;
154// PR.2: the `project.root-markers` list option.
155mod root_markers;
156mod signcolumn;
157mod window_options;
158// `option` is `pub` so the proc macros' generated code can name
159// `::lattice_config::option::Option<T>` for runtime spec
160// construction. Direct construction of `Option<T>` is the
161// macro-internal path (the macros' `build_spec()` calls
162// `Option::<T>::builder(...)`); consumer-level code uses the
163// macro and `config.get_typed::<X>()` instead.
164pub mod option;
165mod option_decl;
166mod option_type;
167mod origin;
168/// TC.1: `ConfigSchema` / `ConfigValue` — what an option's value is shaped
169/// like, as data, plus schema-checked validation that reports a path.
170pub mod schema;
171// M.4 dep-inversion: layer-input types (`OptionOverride`,
172// `OptionOverrideSet`, `OverridePriority`) live here now.
173// Previously hosted in `lattice-mode` to break a cycle through
174// `lattice-core -> lattice-mode -> lattice-config`; the cycle
175// was retired by removing `Document::modes` from lattice-core.
176// With the cycle gone, the override types belong in lattice-
177// config alongside the resolver and the typed-options layer they
178// override against.
179pub mod overrides;
180mod parse;
181#[cfg(test)]
182mod proc_macro_tests;
183mod registry;
184mod resolved;
185mod resolver;
186
187// Re-export `linkme` so the proc macros can reference
188// `::lattice_config::linkme::distributed_slice` reliably when
189// expanded outside this crate.
190#[doc(hidden)]
191pub use linkme;
192
193// Re-export the proc macros from `lattice-config-macros`.
194// Users write `lattice_config::options! { ... }` /
195// `groups! { ... }` / `overrides! { ... }`; the proc-macro
196// crate is a private implementation detail.
197pub use lattice_config_macros::{groups, options, overrides};
198
199pub use completion::OptionsGenerator;
200// M.2.0c: re-export the macro-generated option types at the
201// crate root for ergonomic type-keyed access.
202// Callers write `config.get_typed::<lattice_config::Tabstop>()`
203// instead of the longer `lattice_config::core_options::Tabstop`.
204pub use core_options::COMPLETION_SOURCE_SNIPPET_DEFAULT_PRIORITY;
205pub use core_options::{
206 AutoWrapOption, ClipboardEnabled, CommandLineExpandHeight, CompletionAutoInsertSingle,
207 CompletionExtraCommitChars, CompletionGhostText, CompletionSourceBufferWordsPriority,
208 CompletionSourceLspPriority, CompletionSourcePathPriority, CompletionSourceSnippetPriority,
209 CompletionSourceTreeSitterPriority, CursorLine, DiagnosticsInlineOption,
210 DiagnosticsMinSeverityOption, ElectricIndent, ExpandTab, FoldEnable, FoldMethodOption,
211 FormatIndentChain, FormatOnSave, FormatPrg, FormatReflowChain, FormatReformatChain,
212 HelpAproposDisplay, HelpDescribeDisplay, HelpListDisplay, HelpTopicDisplay, HoverDisplay,
213 IgnoreCase, IndentMethodOption, LspLogDisplay, LspStatusDisplay, MessagesDisplay,
214 MessagesFilter, ModelineCenter, ModelineLeft, ModelinePadding, ModelineRight,
215 ModelineSeparator, MouseEnabled, NoFile, Number, PickerResultDisplay, ProjectRootMarkers,
216 ReadOnly, RelativeNumber, Scroll, Scrollbind, Scrolloff, Shiftwidth, Sidescroll, Sidescrolloff,
217 SignColumnOption, SignatureDisplay, StartOfLine, TablineShowOption, Tabstop, TerminalEscExits,
218 TerminalScrollbackLines, TextWidth, TransientMaxRows, Whitespace, WhitespaceEol,
219 WhitespaceLeading, WhitespaceSpace, WhitespaceTab, WhitespaceTrailing, Wrap,
220};
221pub use erased::ErasedOption;
222pub use group::{
223 Ai, Appearance, Completion, Diagnostics, Display, Editing, Editor, Filetree, GROUP_DECLS, Help,
224 Lsp, Magit, Messages, Modeline, Mouse, Notifications, Oil, OptionGroup, OptionGroupMetadata,
225 Pane, Picker, Plugin, Project, Search, Snippet, Tabline, Terminal, Window,
226 ends_with_mode_suffix,
227};
228pub use loader::{
229 LoadMessage, LoadMessageLevel, LoadOutcome, cache_home, cache_home_from, config_home,
230 default_user_config_path, load_default_paths, load_file, lookup_dotted_path, migrate_path,
231 project_config_path,
232};
233// ML.5: the modeline zone-layout value type (`ui.modeline.{left,center,
234// right}`). The first list-valued option; see `modeline_zone`.
235pub use modeline_zone::ModelineZone;
236// PR.2: the `project.root-markers` value type. Note `Project` above is
237// the option GROUP marker (`:customize project`), not `lattice_core::Project`
238// — the resolved root. Different crates, different jobs.
239pub use root_markers::RootMarkers;
240// L4a: inline-diagnostics option value types (`ui.diagnostics.*`).
241pub use diagnostics_options::{DiagnosticsInline, DiagnosticsSeverity};
242// PO.4.3: the `plugin.trace-level` option value + decl type — the global default
243// plugin boundary-trace verbosity (the loader observes changes → the tracer gate).
244pub use plugin_options::{PluginTraceLevel, PluginTraceLevelOption};
245// PU.1b: the `signcolumn` option value type — gates the gutter sign
246// columns (diagnostics severity + diff sign) so help / synthetic
247// buffers render gutterless without the renderer knowing it's help.
248pub use signcolumn::SignColumn;
249// MB.2e: the `command-line.expand-height` option value type — how tall
250// the expanded `:` mini-buffer band grows (`half` / `full` / fixed rows).
251pub use expand_height::ExpandHeight;
252// W.1: the `decorations` option value type — controls OS window chrome
253// (titlebar + controls) on the GPUI peer (full vs. borderless).
254pub use decorations::Decorations;
255// W.2: the `ui.window.*` decl types — GPUI peer window chrome +
256// maximize-on-launch. Value type (`Decorations`) re-exported above.
257pub use pane_options::{PaneBufferHistorySize, PaneZoomIndicator};
258pub use window_options::{StartMaximized, WindowDecorationsOption};
259// M.2.0c: `Option<T>`, `OptionBuilder<T>`, `OptionHandle<T>`
260// remain `pub` from the `option` module so the macros' generated
261// `build_spec()` methods can name them, but they are no longer
262// re-exported at the crate root. The intended public surface is
263// the macro path -- callers declare options via `options! { ... }`
264// and read via `config.get_typed::<X>()`. Direct construction of
265// `Option<T>` survives for the future plugin-adapter path.
266pub use option_decl::{HasGroup, OPTION_DECLS, OptionDecl, OptionDeclMetadata};
267pub use option_type::OptionType;
268// TC.1: an option's shape as data, and values in that shape
269// (`typed-configuration.md`).
270pub use schema::{ConfigSchema, ConfigValue, ScalarKind, SchemaError, SchemaField};
271// Layer-input types live in this crate now (post M.4 dep
272// inversion). Modes pull them in via lattice-mode's re-export.
273pub use origin::OptionOrigin;
274pub use overrides::{OptionOverride, OptionOverrideSet, OverridePriority};
275pub use parse::{ParsedSet, parse_set};
276pub use registry::{ConfigError, ConfigRegistry, EventPublisher, plugin_option_groups};
277pub use resolved::ResolvedOptions;
278pub use resolver::Resolver;