Skip to main content

reovim_driver_syntax/
bracket.rs

1//! Bracket configuration types and store for language-aware bracket pairing.
2//!
3//! This module provides [`BracketConfig`] and [`BracketConfigStore`], following
4//! the same self-registration pattern as [`LanguageInfoStore`](crate::LanguageInfoStore):
5//!
6//! - Language modules register per-language bracket configs during `init()`
7//! - Consumer modules (e.g., `pair`) read configs at runtime
8//!
9//! # Example
10//!
11//! ```
12//! use reovim_driver_syntax::bracket::{BracketConfig, BracketConfigStore, BracketPair};
13//!
14//! let store = BracketConfigStore::new();
15//!
16//! // Language module registers during init()
17//! store.add(
18//!     BracketConfig::new("rust")
19//!         .with_rainbow([('(', ')'), ('[', ']'), ('{', '}')])
20//!         .with_autopair([('(', ')'), ('[', ']'), ('{', '}'), ('"', '"')])
21//!         .with_highlight([('(', ')'), ('[', ']'), ('{', '}')])
22//! );
23//!
24//! // Consumer module reads at runtime
25//! let config = store.find("rust").expect("rust config registered");
26//! assert_eq!(config.rainbow_pairs().len(), 3);
27//! ```
28
29use std::sync::Arc;
30
31use {parking_lot::RwLock, reovim_kernel::api::v1::Service};
32
33/// A bracket pair: opening and closing characters.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub struct BracketPair {
36    /// The opening character (e.g., `(`).
37    pub open: char,
38    /// The closing character (e.g., `)`).
39    pub close: char,
40}
41
42impl BracketPair {
43    /// Create a new bracket pair.
44    #[must_use]
45    pub const fn new(open: char, close: char) -> Self {
46        Self { open, close }
47    }
48
49    /// Whether this is a symmetric pair (open == close, e.g., quotes).
50    #[must_use]
51    pub const fn is_symmetric(&self) -> bool {
52        self.open as u32 == self.close as u32
53    }
54}
55
56/// Per-language bracket configuration.
57///
58/// Declares which bracket pairs are used for rainbow coloring,
59/// auto-pair insertion, and matched-pair highlighting.
60///
61/// Language modules register this during `init()`. The pair module
62/// reads it at runtime for the current buffer's language.
63#[derive(Debug, Clone)]
64pub struct BracketConfig {
65    /// Language ID this config applies to (e.g., "rust", "markdown").
66    language_id: Arc<str>,
67    /// Pairs used for rainbow depth coloring.
68    rainbow_pairs: Vec<BracketPair>,
69    /// Pairs used for auto-pair insertion (includes quotes).
70    autopair_pairs: Vec<BracketPair>,
71    /// Pairs used for matched-pair highlighting.
72    highlight_pairs: Vec<BracketPair>,
73}
74
75impl BracketConfig {
76    /// Create a new bracket config for a language.
77    #[must_use]
78    pub fn new(language_id: impl Into<Arc<str>>) -> Self {
79        Self {
80            language_id: language_id.into(),
81            rainbow_pairs: Vec::new(),
82            autopair_pairs: Vec::new(),
83            highlight_pairs: Vec::new(),
84        }
85    }
86
87    /// Set pairs used for rainbow depth coloring.
88    #[must_use]
89    pub fn with_rainbow(mut self, pairs: impl IntoIterator<Item = (char, char)>) -> Self {
90        self.rainbow_pairs = pairs
91            .into_iter()
92            .map(|(o, c)| BracketPair::new(o, c))
93            .collect();
94        self
95    }
96
97    /// Set pairs used for auto-pair insertion.
98    #[must_use]
99    pub fn with_autopair(mut self, pairs: impl IntoIterator<Item = (char, char)>) -> Self {
100        self.autopair_pairs = pairs
101            .into_iter()
102            .map(|(o, c)| BracketPair::new(o, c))
103            .collect();
104        self
105    }
106
107    /// Set pairs used for matched-pair highlighting.
108    #[must_use]
109    pub fn with_highlight(mut self, pairs: impl IntoIterator<Item = (char, char)>) -> Self {
110        self.highlight_pairs = pairs
111            .into_iter()
112            .map(|(o, c)| BracketPair::new(o, c))
113            .collect();
114        self
115    }
116
117    /// Get the language ID.
118    #[must_use]
119    pub fn language_id(&self) -> &str {
120        &self.language_id
121    }
122
123    /// Get pairs for rainbow coloring.
124    #[must_use]
125    pub fn rainbow_pairs(&self) -> &[BracketPair] {
126        &self.rainbow_pairs
127    }
128
129    /// Get pairs for auto-pair insertion.
130    #[must_use]
131    pub fn autopair_pairs(&self) -> &[BracketPair] {
132        &self.autopair_pairs
133    }
134
135    /// Get pairs for matched-pair highlighting.
136    #[must_use]
137    pub fn highlight_pairs(&self) -> &[BracketPair] {
138        &self.highlight_pairs
139    }
140}
141
142/// Default bracket config for languages without explicit registration.
143///
144/// Uses the common set: `()[]{}` for rainbow/highlight, `()[]{}""''` for autopair.
145#[must_use]
146pub fn default_bracket_config() -> BracketConfig {
147    BracketConfig::new("*")
148        .with_rainbow([('(', ')'), ('[', ']'), ('{', '}')])
149        .with_autopair([('(', ')'), ('[', ']'), ('{', '}'), ('"', '"'), ('\'', '\'')])
150        .with_highlight([('(', ')'), ('[', ']'), ('{', '}')])
151}
152
153/// Store for bracket configs registered by language modules during init.
154///
155/// Follows the same pattern as [`LanguageInfoStore`](crate::LanguageInfoStore).
156#[derive(Default)]
157pub struct BracketConfigStore {
158    entries: RwLock<Vec<BracketConfig>>,
159}
160
161impl BracketConfigStore {
162    /// Create a new empty store.
163    #[must_use]
164    pub fn new() -> Self {
165        Self::default()
166    }
167
168    /// Add a bracket config to the store.
169    ///
170    /// Called by language modules during `init()`.
171    pub fn add(&self, config: BracketConfig) {
172        self.entries.write().push(config);
173    }
174
175    /// Find a bracket config by language ID.
176    #[must_use]
177    pub fn find(&self, language_id: &str) -> Option<BracketConfig> {
178        self.entries
179            .read()
180            .iter()
181            .find(|c| &*c.language_id == language_id)
182            .cloned()
183    }
184
185    /// Find a bracket config by language ID, falling back to the default.
186    #[must_use]
187    pub fn find_or_default(&self, language_id: &str) -> BracketConfig {
188        self.find(language_id)
189            .unwrap_or_else(default_bracket_config)
190    }
191
192    /// Take all registered configs.
193    ///
194    /// This drains the store; subsequent calls return an empty vec.
195    pub fn take_all(&self) -> Vec<BracketConfig> {
196        std::mem::take(&mut *self.entries.write())
197    }
198
199    /// Get the number of registered configs.
200    #[must_use]
201    pub fn len(&self) -> usize {
202        self.entries.read().len()
203    }
204
205    /// Check if no configs are registered.
206    #[must_use]
207    pub fn is_empty(&self) -> bool {
208        self.entries.read().is_empty()
209    }
210}
211
212impl Service for BracketConfigStore {}
213
214impl std::fmt::Debug for BracketConfigStore {
215    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216        f.debug_struct("BracketConfigStore")
217            .field("count", &self.len())
218            .finish()
219    }
220}
221
222#[cfg(test)]
223#[path = "bracket_tests.rs"]
224mod tests;