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;