reovim_kernel/core/option/value.rs
1//! Type-safe option values.
2//!
3//! This is the core value type for editor options.
4
5use std::fmt;
6
7/// Type-safe option value.
8///
9/// This is the core value type for editor options. Each variant maps to
10/// a common configuration pattern:
11/// - `Bool`: Toggle settings (number, wrap, etc.)
12/// - `Integer`: Numeric settings (tabwidth, scrolloff)
13/// - `String`: Free-form text (theme name, paths)
14/// - `Choice`: Enum-like selection from predefined values
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum OptionValue {
17 /// Boolean on/off option (e.g., `number`, `relativenumber`)
18 Bool(bool),
19
20 /// Integer option (e.g., `tabwidth`, `scrolloff`)
21 Integer(i64),
22
23 /// String option (e.g., `theme`, `signcolumn`)
24 String(String),
25
26 /// Choice option with predefined valid values.
27 ///
28 /// The `choices` field stores all valid values for validation.
29 Choice {
30 /// Current selected value
31 value: String,
32 /// All valid choices (for validation and completion)
33 choices: Vec<String>,
34 },
35}
36
37impl OptionValue {
38 // ========================================================================
39 // Constructors
40 // ========================================================================
41
42 /// Create a boolean value.
43 #[must_use]
44 pub const fn bool(value: bool) -> Self {
45 Self::Bool(value)
46 }
47
48 /// Create an integer value.
49 #[must_use]
50 pub const fn int(value: i64) -> Self {
51 Self::Integer(value)
52 }
53
54 /// Create a string value.
55 #[must_use]
56 pub fn string(value: impl Into<String>) -> Self {
57 Self::String(value.into())
58 }
59
60 /// Create a choice value.
61 ///
62 /// # Panics
63 ///
64 /// Panics if `choices` is empty or if `value` is not in `choices`.
65 #[must_use]
66 pub fn choice(value: impl Into<String>, choices: Vec<String>) -> Self {
67 let value = value.into();
68 debug_assert!(!choices.is_empty(), "choices must not be empty");
69 debug_assert!(choices.contains(&value), "value must be in choices");
70 Self::Choice { value, choices }
71 }
72
73 // ========================================================================
74 // Accessors
75 // ========================================================================
76
77 /// Try to get as boolean.
78 #[must_use]
79 pub const fn as_bool(&self) -> Option<bool> {
80 match self {
81 Self::Bool(b) => Some(*b),
82 _ => None,
83 }
84 }
85
86 /// Try to get as integer.
87 #[must_use]
88 pub const fn as_int(&self) -> Option<i64> {
89 match self {
90 Self::Integer(i) => Some(*i),
91 _ => None,
92 }
93 }
94
95 /// Try to get as string reference.
96 #[must_use]
97 pub fn as_str(&self) -> Option<&str> {
98 match self {
99 Self::String(s) => Some(s),
100 Self::Choice { value, .. } => Some(value),
101 _ => None,
102 }
103 }
104
105 /// Get the type name for error messages.
106 #[must_use]
107 pub const fn type_name(&self) -> &'static str {
108 match self {
109 Self::Bool(_) => "bool",
110 Self::Integer(_) => "integer",
111 Self::String(_) => "string",
112 Self::Choice { .. } => "choice",
113 }
114 }
115
116 /// Check if this value has the same type as another.
117 #[must_use]
118 pub const fn same_type(&self, other: &Self) -> bool {
119 matches!(
120 (self, other),
121 (Self::Bool(_), Self::Bool(_))
122 | (Self::Integer(_), Self::Integer(_))
123 | (Self::String(_), Self::String(_))
124 | (Self::Choice { .. }, Self::Choice { .. })
125 )
126 }
127}
128
129// LLVM coverage artifact: match arm headers and closing brace in Display impl
130// are marked DA:0 despite all variants being exercised in test_option_value_display.
131#[cfg_attr(coverage_nightly, coverage(off))]
132impl fmt::Display for OptionValue {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 match self {
135 Self::Bool(b) => write!(f, "{b}"),
136 Self::Integer(i) => write!(f, "{i}"),
137 Self::String(s) => write!(f, "{s}"),
138 Self::Choice { value, .. } => write!(f, "{value}"),
139 }
140 }
141}