ricecoder_ide/
editor_config.rs

1/// Editor-specific configuration module
2/// Handles loading and managing configuration for vim, neovim, and emacs
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::path::Path;
6use thiserror::Error;
7
8/// Editor configuration error
9#[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/// Vim/Neovim configuration
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct VimConfig {
27    /// Enable vim integration
28    pub enabled: bool,
29    
30    /// RiceCoder host
31    pub host: String,
32    
33    /// RiceCoder port
34    pub port: u16,
35    
36    /// Request timeout in milliseconds
37    pub timeout_ms: u64,
38    
39    /// Completion settings
40    pub completion: CompletionSettings,
41    
42    /// Diagnostics settings
43    pub diagnostics: DiagnosticsSettings,
44    
45    /// Hover settings
46    pub hover: HoverSettings,
47    
48    /// Custom keybindings
49    pub keybindings: HashMap<String, String>,
50}
51
52/// Emacs configuration
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct EmacsConfig {
55    /// Enable emacs integration
56    pub enabled: bool,
57    
58    /// RiceCoder host
59    pub host: String,
60    
61    /// RiceCoder port
62    pub port: u16,
63    
64    /// Request timeout in milliseconds
65    pub timeout_ms: u64,
66    
67    /// Completion settings
68    pub completion: CompletionSettings,
69    
70    /// Diagnostics settings
71    pub diagnostics: DiagnosticsSettings,
72    
73    /// Hover settings
74    pub hover: HoverSettings,
75    
76    /// Custom keybindings
77    pub keybindings: HashMap<String, String>,
78}
79
80/// Completion settings
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct CompletionSettings {
83    /// Enable completion
84    pub enabled: bool,
85    
86    /// Maximum number of completion items
87    pub max_items: usize,
88    
89    /// Trigger completion on these characters
90    pub trigger_characters: Vec<String>,
91}
92
93/// Diagnostics settings
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct DiagnosticsSettings {
96    /// Enable diagnostics
97    pub enabled: bool,
98    
99    /// Show diagnostics on file change
100    pub show_on_change: bool,
101    
102    /// Minimum severity level to display (1=error, 2=warning, 3=info)
103    pub min_severity: u8,
104}
105
106/// Hover settings
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct HoverSettings {
109    /// Enable hover
110    pub enabled: bool,
111    
112    /// Show hover on cursor move
113    pub show_on_move: bool,
114    
115    /// Hover delay in milliseconds
116    pub delay_ms: u64,
117}
118
119/// Terminal editor configuration
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct TerminalEditorConfig {
122    /// Vim/Neovim configuration
123    pub vim: Option<VimConfig>,
124    
125    /// Emacs configuration
126    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    /// Load configuration from YAML file
200    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    /// Load configuration from JSON file
212    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    /// Validate configuration
224    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    /// Get vim configuration
237    pub fn vim(&self) -> Option<&VimConfig> {
238        self.vim.as_ref()
239    }
240    
241    /// Get emacs configuration
242    pub fn emacs(&self) -> Option<&EmacsConfig> {
243        self.emacs.as_ref()
244    }
245}
246
247impl VimConfig {
248    /// Validate vim configuration
249    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    /// Validate emacs configuration
274    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}