Skip to main content

lattice_config/
domain.rs

1//! [`OptionType`] impls for domain types defined in other crates.
2//!
3//! These impls live in `lattice-config` (rather than alongside the
4//! types) because the alternative — having `lattice-core` depend
5//! on `lattice-config` — would invert the dependency direction.
6//! `lattice-config` already depends on `lattice-core` (transitively
7//! through `lattice-completion`'s `Buffer`-aware traits), so adding
8//! domain impls here is cycle-free.
9//!
10//! The orphan rule allows this: `OptionType` is local to
11//! `lattice-config`; the types implemented here (`FoldMethod`, ...)
12//! are foreign — local-trait-on-foreign-type is permitted.
13
14use lattice_core::FoldMethod;
15use lattice_core::IndentMethod;
16use lattice_core::ui::display::BufferDisplayPreference;
17use lattice_core::{AutoWrap, ProviderChain};
18
19use crate::option_type::{EnumeratedValue, OptionType};
20
21impl OptionType for BufferDisplayPreference {
22    fn parse(s: &str) -> Result<Self, String> {
23        BufferDisplayPreference::parse_label(s)
24    }
25
26    fn format(&self) -> String {
27        self.label().to_string()
28    }
29
30    fn type_label() -> &'static str {
31        "display-preference"
32    }
33
34    fn enumerate() -> Option<Vec<&'static str>> {
35        Some(vec![
36            "default",
37            "popup-centered",
38            "popup-cursor",
39            "floating-cursor",
40            "active-pane",
41            "split-h",
42            "split-v",
43        ])
44    }
45
46    /// TC.1: closed — `parse` accepts these forms and nothing else, so
47    /// the schema is an `enum` and `:customize` can offer a picker.
48    fn enumerate_is_exhaustive() -> bool {
49        true
50    }
51
52    fn enumerate_with_docs() -> Option<Vec<EnumeratedValue>> {
53        // Slice `3c.unify.option-docs-builtin`: per-value
54        // marginalia pulled from the variant's own `doc()`
55        // accessor in `lattice-core`. Adding a new variant
56        // requires extending `label` / `doc` / `all` together;
57        // this method picks the docs up automatically.
58        Some(
59            BufferDisplayPreference::all()
60                .iter()
61                .map(|v| EnumeratedValue {
62                    form: v.label(),
63                    doc: v.doc(),
64                })
65                .collect(),
66        )
67    }
68}
69
70impl OptionType for FoldMethod {
71    fn parse(s: &str) -> Result<Self, String> {
72        FoldMethod::parse_label(s)
73    }
74
75    fn format(&self) -> String {
76        self.label().to_string()
77    }
78
79    fn type_label() -> &'static str {
80        "foldmethod"
81    }
82
83    fn enumerate() -> Option<Vec<&'static str>> {
84        // Order matches the legacy `gen:options` value list so
85        // `:set foldmethod=<Tab>` shows the same candidates;
86        // `lsp` (4.4.f) appended at the end.
87        Some(vec!["manual", "indent", "markdown", "syntax", "lsp"])
88    }
89
90    /// TC.1: closed — `parse` accepts these forms and nothing else, so
91    /// the schema is an `enum` and `:customize` can offer a picker.
92    fn enumerate_is_exhaustive() -> bool {
93        true
94    }
95
96    fn enumerate_with_docs() -> Option<Vec<EnumeratedValue>> {
97        // Slice `3c.unify.option-docs-builtin`: per-value
98        // marginalia pulled from the variant's own `doc()`
99        // accessor in `lattice-core`. Adding a new fold method
100        // requires extending `label` / `doc` / `all` together;
101        // this method picks the docs up automatically.
102        Some(
103            FoldMethod::all()
104                .iter()
105                .map(|v| EnumeratedValue {
106                    form: v.label(),
107                    doc: v.doc(),
108                })
109                .collect(),
110        )
111    }
112}
113
114// IN.0: `:set indentmethod=none|keep|syntax`.
115impl OptionType for IndentMethod {
116    fn parse(s: &str) -> Result<Self, String> {
117        IndentMethod::parse_label(s)
118    }
119
120    fn format(&self) -> String {
121        self.label().to_string()
122    }
123
124    fn type_label() -> &'static str {
125        "indentmethod"
126    }
127
128    fn enumerate() -> Option<Vec<&'static str>> {
129        // Derived from `all()` rather than hand-listed. `FoldMethod`
130        // above hardcodes its list to preserve a legacy completion
131        // order; a new option has no such constraint, so deriving is
132        // strictly better -- adding a variant cannot leave the
133        // completion list behind.
134        Some(IndentMethod::all().iter().map(|v| v.label()).collect())
135    }
136
137    /// TC.1: closed — `parse` accepts these forms and nothing else, so
138    /// the schema is an `enum` and `:customize` can offer a picker.
139    fn enumerate_is_exhaustive() -> bool {
140        true
141    }
142
143    fn enumerate_with_docs() -> Option<Vec<EnumeratedValue>> {
144        Some(
145            IndentMethod::all()
146                .iter()
147                .map(|v| EnumeratedValue {
148                    form: v.label(),
149                    doc: v.doc(),
150                })
151                .collect(),
152        )
153    }
154}
155
156// Issue #29 (2026-05-22): tabline.show enum option.
157impl OptionType for lattice_core::ui::tab::TablineShow {
158    fn parse(s: &str) -> Result<Self, String> {
159        lattice_core::ui::tab::TablineShow::parse_label(s)
160    }
161
162    fn format(&self) -> String {
163        self.label().to_string()
164    }
165
166    fn type_label() -> &'static str {
167        "tabline-show"
168    }
169
170    fn enumerate() -> Option<Vec<&'static str>> {
171        Some(
172            lattice_core::ui::tab::TablineShow::all()
173                .iter()
174                .map(|v| v.label())
175                .collect(),
176        )
177    }
178
179    /// TC.1: closed — `parse` accepts these forms and nothing else, so
180    /// the schema is an `enum` and `:customize` can offer a picker.
181    fn enumerate_is_exhaustive() -> bool {
182        true
183    }
184
185    fn enumerate_with_docs() -> Option<Vec<EnumeratedValue>> {
186        Some(
187            lattice_core::ui::tab::TablineShow::all()
188                .iter()
189                .map(|v| EnumeratedValue {
190                    form: v.label(),
191                    doc: v.doc(),
192                })
193                .collect(),
194        )
195    }
196}
197
198// ZP.4: `:set pane.zoom-indicator=both|modeline|tabline|none`.
199impl OptionType for lattice_core::ui::pane::ZoomIndicator {
200    fn parse(s: &str) -> Result<Self, String> {
201        lattice_core::ui::pane::ZoomIndicator::parse_label(s)
202    }
203
204    fn format(&self) -> String {
205        self.label().to_string()
206    }
207
208    fn type_label() -> &'static str {
209        "zoom-indicator"
210    }
211
212    fn enumerate() -> Option<Vec<&'static str>> {
213        Some(
214            lattice_core::ui::pane::ZoomIndicator::all()
215                .iter()
216                .map(|v| v.label())
217                .collect(),
218        )
219    }
220
221    /// Closed — `parse` accepts these four forms and nothing else, so
222    /// `:customize` can offer a picker rather than a free-text field.
223    fn enumerate_is_exhaustive() -> bool {
224        true
225    }
226
227    fn enumerate_with_docs() -> Option<Vec<EnumeratedValue>> {
228        Some(
229            lattice_core::ui::pane::ZoomIndicator::all()
230                .iter()
231                .map(|v| EnumeratedValue {
232                    form: v.label(),
233                    doc: v.doc(),
234                })
235                .collect(),
236        )
237    }
238}
239
240// RF.0: `:set autowrap=off|comments|all`.
241impl OptionType for AutoWrap {
242    fn parse(s: &str) -> Result<Self, String> {
243        AutoWrap::parse_label(s)
244    }
245
246    fn format(&self) -> String {
247        self.label().to_string()
248    }
249
250    fn type_label() -> &'static str {
251        "autowrap"
252    }
253
254    fn enumerate() -> Option<Vec<&'static str>> {
255        Some(AutoWrap::all().iter().map(|v| v.label()).collect())
256    }
257
258    /// Closed: `parse` accepts the three canonical labels plus the
259    /// boolean-ish aliases, and nothing else — so `:customize` offers a
260    /// picker rather than a text field.
261    fn enumerate_is_exhaustive() -> bool {
262        true
263    }
264
265    fn enumerate_with_docs() -> Option<Vec<EnumeratedValue>> {
266        Some(
267            AutoWrap::all()
268                .iter()
269                .map(|v| EnumeratedValue {
270                    form: v.label(),
271                    doc: v.doc(),
272                })
273                .collect(),
274        )
275    }
276}
277
278// RF.0: `:set format.reformat=lsp,lang-default` and peers.
279//
280// The first LIST-valued option in the editor. Comma-separated rather
281// than a TOML array because the value has to survive `:set name=value`,
282// where vim's own convention for a list is a comma-separated string —
283// so this needs no new config machinery beyond the impl.
284impl OptionType for ProviderChain {
285    fn parse(s: &str) -> Result<Self, String> {
286        ProviderChain::parse(s)
287    }
288
289    fn format(&self) -> String {
290        self.label()
291    }
292
293    fn type_label() -> &'static str {
294        "formatter-chain"
295    }
296
297    /// The bare rungs only. `external:` and `plugin:` carry a payload,
298    /// so they cannot be completion candidates — offering `external:`
299    /// alone would complete to a value that fails to parse.
300    fn enumerate() -> Option<Vec<&'static str>> {
301        Some(vec!["native", "lsp", "lang-default"])
302    }
303
304    /// **Open**, unlike every other enumerated option here: the value is
305    /// a list, and two of its rungs take arbitrary payloads. A
306    /// `:customize` picker over these three forms would be actively
307    /// wrong — it would hide `external:` and offer no way to write a
308    /// chain of more than one.
309    fn enumerate_is_exhaustive() -> bool {
310        false
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    #![allow(clippy::unwrap_used, clippy::panic)]
317    use super::*;
318
319    #[test]
320    fn foldmethod_parse_round_trip() {
321        for fm in [
322            FoldMethod::Manual,
323            FoldMethod::Indent,
324            FoldMethod::Markdown,
325            FoldMethod::Syntax,
326            FoldMethod::Lsp,
327        ] {
328            assert_eq!(FoldMethod::parse(&fm.format()), Ok(fm));
329        }
330    }
331
332    #[test]
333    fn foldmethod_enumerate_lists_every_variant() {
334        let values = FoldMethod::enumerate().expect("enumeration available");
335        assert_eq!(
336            values,
337            vec!["manual", "indent", "markdown", "syntax", "lsp"]
338        );
339    }
340
341    #[test]
342    fn foldmethod_parse_error_message_matches_legacy_wording() {
343        // Error wording grew the `lsp` option in 4.4.f; check
344        // for the new shape (the legacy-string requirement is
345        // dropped because the option set itself grew).
346        let err = FoldMethod::parse("xyz").unwrap_err();
347        assert!(
348            err.contains("expected `manual`, `indent`, `markdown`, `syntax`, or `lsp`")
349                && err.contains("xyz"),
350            "got `{err}`"
351        );
352    }
353}