1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::path::Path;
6use thiserror::Error;
7
8#[derive(Debug, Error)]
10pub enum EditorConfigError {
11 #[error("Failed to load editor configuration: {0}")]
12 LoadError(String),
13
14 #[error("Invalid editor configuration: {0}")]
15 InvalidConfig(String),
16
17 #[error("Unsupported editor: {0}")]
18 UnsupportedEditor(String),
19
20 #[error("Configuration validation failed: {0}")]
21 ValidationError(String),
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct VimConfig {
27 pub enabled: bool,
29
30 pub host: String,
32
33 pub port: u16,
35
36 pub timeout_ms: u64,
38
39 pub completion: CompletionSettings,
41
42 pub diagnostics: DiagnosticsSettings,
44
45 pub hover: HoverSettings,
47
48 pub keybindings: HashMap<String, String>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct EmacsConfig {
55 pub enabled: bool,
57
58 pub host: String,
60
61 pub port: u16,
63
64 pub timeout_ms: u64,
66
67 pub completion: CompletionSettings,
69
70 pub diagnostics: DiagnosticsSettings,
72
73 pub hover: HoverSettings,
75
76 pub keybindings: HashMap<String, String>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct CompletionSettings {
83 pub enabled: bool,
85
86 pub max_items: usize,
88
89 pub trigger_characters: Vec<String>,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct DiagnosticsSettings {
96 pub enabled: bool,
98
99 pub show_on_change: bool,
101
102 pub min_severity: u8,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct HoverSettings {
109 pub enabled: bool,
111
112 pub show_on_move: bool,
114
115 pub delay_ms: u64,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct TerminalEditorConfig {
122 pub vim: Option<VimConfig>,
124
125 pub emacs: Option<EmacsConfig>,
127}
128
129impl Default for VimConfig {
130 fn default() -> Self {
131 Self {
132 enabled: true,
133 host: "localhost".to_string(),
134 port: 9000,
135 timeout_ms: 5000,
136 completion: CompletionSettings::default(),
137 diagnostics: DiagnosticsSettings::default(),
138 hover: HoverSettings::default(),
139 keybindings: HashMap::new(),
140 }
141 }
142}
143
144impl Default for EmacsConfig {
145 fn default() -> Self {
146 Self {
147 enabled: true,
148 host: "localhost".to_string(),
149 port: 9000,
150 timeout_ms: 5000,
151 completion: CompletionSettings::default(),
152 diagnostics: DiagnosticsSettings::default(),
153 hover: HoverSettings::default(),
154 keybindings: HashMap::new(),
155 }
156 }
157}
158
159impl Default for CompletionSettings {
160 fn default() -> Self {
161 Self {
162 enabled: true,
163 max_items: 20,
164 trigger_characters: vec![".".to_string(), ":".to_string()],
165 }
166 }
167}
168
169impl Default for DiagnosticsSettings {
170 fn default() -> Self {
171 Self {
172 enabled: true,
173 show_on_change: true,
174 min_severity: 1,
175 }
176 }
177}
178
179impl Default for HoverSettings {
180 fn default() -> Self {
181 Self {
182 enabled: true,
183 show_on_move: false,
184 delay_ms: 500,
185 }
186 }
187}
188
189impl Default for TerminalEditorConfig {
190 fn default() -> Self {
191 Self {
192 vim: Some(VimConfig::default()),
193 emacs: Some(EmacsConfig::default()),
194 }
195 }
196}
197
198impl TerminalEditorConfig {
199 pub fn from_yaml(path: &Path) -> Result<Self, EditorConfigError> {
201 let content = std::fs::read_to_string(path)
202 .map_err(|e| EditorConfigError::LoadError(e.to_string()))?;
203
204 let config: TerminalEditorConfig = serde_yaml::from_str(&content)
205 .map_err(|e| EditorConfigError::InvalidConfig(e.to_string()))?;
206
207 config.validate()?;
208 Ok(config)
209 }
210
211 pub fn from_json(path: &Path) -> Result<Self, EditorConfigError> {
213 let content = std::fs::read_to_string(path)
214 .map_err(|e| EditorConfigError::LoadError(e.to_string()))?;
215
216 let config: TerminalEditorConfig = serde_json::from_str(&content)
217 .map_err(|e| EditorConfigError::InvalidConfig(e.to_string()))?;
218
219 config.validate()?;
220 Ok(config)
221 }
222
223 pub fn validate(&self) -> Result<(), EditorConfigError> {
225 if let Some(vim_config) = &self.vim {
226 vim_config.validate()?;
227 }
228
229 if let Some(emacs_config) = &self.emacs {
230 emacs_config.validate()?;
231 }
232
233 Ok(())
234 }
235
236 pub fn vim(&self) -> Option<&VimConfig> {
238 self.vim.as_ref()
239 }
240
241 pub fn emacs(&self) -> Option<&EmacsConfig> {
243 self.emacs.as_ref()
244 }
245}
246
247impl VimConfig {
248 pub fn validate(&self) -> Result<(), EditorConfigError> {
250 if self.port == 0 {
251 return Err(EditorConfigError::ValidationError(
252 "Port must be greater than 0".to_string(),
253 ));
254 }
255
256 if self.timeout_ms == 0 {
257 return Err(EditorConfigError::ValidationError(
258 "Timeout must be greater than 0".to_string(),
259 ));
260 }
261
262 if self.completion.max_items == 0 {
263 return Err(EditorConfigError::ValidationError(
264 "Max completion items must be greater than 0".to_string(),
265 ));
266 }
267
268 Ok(())
269 }
270}
271
272impl EmacsConfig {
273 pub fn validate(&self) -> Result<(), EditorConfigError> {
275 if self.port == 0 {
276 return Err(EditorConfigError::ValidationError(
277 "Port must be greater than 0".to_string(),
278 ));
279 }
280
281 if self.timeout_ms == 0 {
282 return Err(EditorConfigError::ValidationError(
283 "Timeout must be greater than 0".to_string(),
284 ));
285 }
286
287 if self.completion.max_items == 0 {
288 return Err(EditorConfigError::ValidationError(
289 "Max completion items must be greater than 0".to_string(),
290 ));
291 }
292
293 Ok(())
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_vim_config_default() {
303 let config = VimConfig::default();
304 assert!(config.enabled);
305 assert_eq!(config.host, "localhost");
306 assert_eq!(config.port, 9000);
307 assert_eq!(config.timeout_ms, 5000);
308 }
309
310 #[test]
311 fn test_emacs_config_default() {
312 let config = EmacsConfig::default();
313 assert!(config.enabled);
314 assert_eq!(config.host, "localhost");
315 assert_eq!(config.port, 9000);
316 assert_eq!(config.timeout_ms, 5000);
317 }
318
319 #[test]
320 fn test_vim_config_validation() {
321 let mut config = VimConfig::default();
322 assert!(config.validate().is_ok());
323
324 config.port = 0;
325 assert!(config.validate().is_err());
326
327 config.port = 9000;
328 config.timeout_ms = 0;
329 assert!(config.validate().is_err());
330 }
331
332 #[test]
333 fn test_emacs_config_validation() {
334 let mut config = EmacsConfig::default();
335 assert!(config.validate().is_ok());
336
337 config.port = 0;
338 assert!(config.validate().is_err());
339
340 config.port = 9000;
341 config.timeout_ms = 0;
342 assert!(config.validate().is_err());
343 }
344
345 #[test]
346 fn test_terminal_editor_config_default() {
347 let config = TerminalEditorConfig::default();
348 assert!(config.vim.is_some());
349 assert!(config.emacs.is_some());
350 assert!(config.validate().is_ok());
351 }
352}