Skip to main content

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}