ricecoder_ide/
ide_config_applicator.rs

1//! IDE-specific configuration applicator
2//! Applies IDE-specific settings and behavior based on IDE type
3
4use crate::error::{IdeError, IdeResult};
5use crate::types::*;
6use std::collections::HashMap;
7use tracing::{debug, info};
8
9/// IDE type identifier
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum IdeType {
12    /// VS Code
13    VsCode,
14    /// Vim/Neovim
15    Vim,
16    /// Emacs
17    Emacs,
18    /// Unknown IDE
19    Unknown,
20}
21
22impl IdeType {
23    /// Parse IDE type from string
24    pub fn parse(s: &str) -> Self {
25        match s.to_lowercase().as_str() {
26            "vscode" | "vs-code" | "vs_code" => IdeType::VsCode,
27            "vim" | "neovim" | "nvim" => IdeType::Vim,
28            "emacs" => IdeType::Emacs,
29            _ => IdeType::Unknown,
30        }
31    }
32
33    /// Get string representation
34    pub fn as_str(&self) -> &'static str {
35        match self {
36            IdeType::VsCode => "vscode",
37            IdeType::Vim => "vim",
38            IdeType::Emacs => "emacs",
39            IdeType::Unknown => "unknown",
40        }
41    }
42}
43
44/// IDE-specific settings
45#[derive(Debug, Clone)]
46pub struct IdeSpecificSettings {
47    /// IDE type
48    pub ide_type: IdeType,
49    /// Enabled features
50    pub enabled_features: Vec<String>,
51    /// Custom settings
52    pub custom_settings: HashMap<String, serde_json::Value>,
53    /// Timeout in milliseconds
54    pub timeout_ms: u64,
55    /// Port for communication
56    pub port: u16,
57}
58
59impl IdeSpecificSettings {
60    /// Create new IDE-specific settings
61    pub fn new(ide_type: IdeType) -> Self {
62        IdeSpecificSettings {
63            ide_type,
64            enabled_features: Vec::new(),
65            custom_settings: HashMap::new(),
66            timeout_ms: 5000,
67            port: 0,
68        }
69    }
70
71    /// Add enabled feature
72    pub fn with_feature(mut self, feature: String) -> Self {
73        self.enabled_features.push(feature);
74        self
75    }
76
77    /// Set timeout
78    pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
79        self.timeout_ms = timeout_ms;
80        self
81    }
82
83    /// Set port
84    pub fn with_port(mut self, port: u16) -> Self {
85        self.port = port;
86        self
87    }
88
89    /// Add custom setting
90    pub fn with_setting(mut self, key: String, value: serde_json::Value) -> Self {
91        self.custom_settings.insert(key, value);
92        self
93    }
94
95    /// Check if feature is enabled
96    pub fn is_feature_enabled(&self, feature: &str) -> bool {
97        self.enabled_features.iter().any(|f| f == feature)
98    }
99
100    /// Get custom setting
101    pub fn get_setting(&self, key: &str) -> Option<&serde_json::Value> {
102        self.custom_settings.get(key)
103    }
104}
105
106/// IDE configuration applicator
107pub struct IdeConfigApplicator;
108
109impl IdeConfigApplicator {
110    /// Extract IDE-specific settings from configuration
111    pub fn extract_settings(
112        config: &IdeIntegrationConfig,
113        ide_type: IdeType,
114    ) -> IdeResult<IdeSpecificSettings> {
115        debug!("Extracting IDE-specific settings for: {}", ide_type.as_str());
116
117        match ide_type {
118            IdeType::VsCode => Self::extract_vscode_settings(config),
119            IdeType::Vim => Self::extract_vim_settings(config),
120            IdeType::Emacs => Self::extract_emacs_settings(config),
121            IdeType::Unknown => Err(IdeError::config_error(
122                "Unknown IDE type. Supported IDEs: vscode, vim, emacs",
123            )),
124        }
125    }
126
127    /// Extract VS Code specific settings
128    fn extract_vscode_settings(config: &IdeIntegrationConfig) -> IdeResult<IdeSpecificSettings> {
129        debug!("Extracting VS Code specific settings");
130
131        let vscode_config = config
132            .vscode
133            .as_ref()
134            .ok_or_else(|| IdeError::config_error("VS Code configuration not found"))?;
135
136        if !vscode_config.enabled {
137            return Err(IdeError::config_error("VS Code integration is disabled"));
138        }
139
140        let mut settings = IdeSpecificSettings::new(IdeType::VsCode)
141            .with_port(vscode_config.port)
142            .with_timeout(5000); // Default timeout for VS Code
143
144        // Add enabled features
145        for feature in &vscode_config.features {
146            settings = settings.with_feature(feature.clone());
147        }
148
149        // Add custom settings from VS Code settings
150        if let Some(settings_obj) = vscode_config.settings.as_object() {
151            for (key, value) in settings_obj {
152                settings = settings.with_setting(key.clone(), value.clone());
153            }
154        }
155
156        info!("Extracted VS Code settings: {} features enabled", settings.enabled_features.len());
157        Ok(settings)
158    }
159
160    /// Extract Vim specific settings
161    fn extract_vim_settings(config: &IdeIntegrationConfig) -> IdeResult<IdeSpecificSettings> {
162        debug!("Extracting Vim specific settings");
163
164        let terminal_config = config
165            .terminal
166            .as_ref()
167            .ok_or_else(|| IdeError::config_error("Terminal configuration not found"))?;
168
169        let vim_config = terminal_config
170            .vim
171            .as_ref()
172            .ok_or_else(|| IdeError::config_error("Vim configuration not found"))?;
173
174        if !vim_config.enabled {
175            return Err(IdeError::config_error("Vim integration is disabled"));
176        }
177
178        let mut settings = IdeSpecificSettings::new(IdeType::Vim)
179            .with_port(9000) // Default port for Vim
180            .with_timeout(5000); // Default timeout
181
182        // Add default features for Vim
183        settings = settings
184            .with_feature("completion".to_string())
185            .with_feature("diagnostics".to_string())
186            .with_feature("hover".to_string());
187
188        // Add plugin manager setting
189        settings = settings.with_setting(
190            "plugin_manager".to_string(),
191            serde_json::json!(vim_config.plugin_manager),
192        );
193
194        info!("Extracted Vim settings: {} features enabled", settings.enabled_features.len());
195        Ok(settings)
196    }
197
198    /// Extract Emacs specific settings
199    fn extract_emacs_settings(config: &IdeIntegrationConfig) -> IdeResult<IdeSpecificSettings> {
200        debug!("Extracting Emacs specific settings");
201
202        let terminal_config = config
203            .terminal
204            .as_ref()
205            .ok_or_else(|| IdeError::config_error("Terminal configuration not found"))?;
206
207        let emacs_config = terminal_config
208            .emacs
209            .as_ref()
210            .ok_or_else(|| IdeError::config_error("Emacs configuration not found"))?;
211
212        if !emacs_config.enabled {
213            return Err(IdeError::config_error("Emacs integration is disabled"));
214        }
215
216        let mut settings = IdeSpecificSettings::new(IdeType::Emacs)
217            .with_port(9000) // Default port for Emacs
218            .with_timeout(5000); // Default timeout
219
220        // Add default features for Emacs
221        settings = settings
222            .with_feature("completion".to_string())
223            .with_feature("diagnostics".to_string())
224            .with_feature("hover".to_string());
225
226        // Add package manager setting
227        settings = settings.with_setting(
228            "package_manager".to_string(),
229            serde_json::json!(emacs_config.package_manager),
230        );
231
232        info!("Extracted Emacs settings: {} features enabled", settings.enabled_features.len());
233        Ok(settings)
234    }
235
236    /// Apply IDE-specific behavior to completion items
237    pub fn apply_completion_behavior(
238        items: &mut Vec<CompletionItem>,
239        settings: &IdeSpecificSettings,
240    ) {
241        debug!("Applying IDE-specific completion behavior for: {}", settings.ide_type.as_str());
242
243        // Limit completion items based on IDE settings
244        if let Some(max_items_value) = settings.get_setting("max_completion_items") {
245            if let Some(max_items) = max_items_value.as_u64() {
246                let max_items = max_items as usize;
247                if items.len() > max_items {
248                    items.truncate(max_items);
249                    debug!("Truncated completion items to {} for IDE", max_items);
250                }
251            }
252        }
253
254        // Apply IDE-specific formatting
255        match settings.ide_type {
256            IdeType::VsCode => {
257                // VS Code prefers detailed documentation
258                for item in items.iter_mut() {
259                    if item.documentation.is_none() {
260                        if let Some(detail) = &item.detail {
261                            if !detail.is_empty() {
262                                item.documentation = Some(detail.clone());
263                            }
264                        }
265                    }
266                }
267            }
268            IdeType::Vim => {
269                // Vim prefers concise labels
270                for item in items.iter_mut() {
271                    if item.label.len() > 50 {
272                        item.label.truncate(50);
273                        item.label.push_str("...");
274                    }
275                }
276            }
277            IdeType::Emacs => {
278                // Emacs prefers detailed information
279                for item in items.iter_mut() {
280                    if item.documentation.is_none() {
281                        if let Some(detail) = &item.detail {
282                            if !detail.is_empty() {
283                                item.documentation = Some(detail.clone());
284                            }
285                        }
286                    }
287                }
288            }
289            IdeType::Unknown => {}
290        }
291    }
292
293    /// Apply IDE-specific behavior to diagnostics
294    pub fn apply_diagnostics_behavior(
295        diagnostics: &mut Vec<Diagnostic>,
296        settings: &IdeSpecificSettings,
297    ) {
298        debug!("Applying IDE-specific diagnostics behavior for: {}", settings.ide_type.as_str());
299
300        // Filter diagnostics based on minimum severity
301        if let Some(min_severity_value) = settings.get_setting("min_severity") {
302            if let Some(min_severity) = min_severity_value.as_u64() {
303                diagnostics.retain(|d| {
304                    let severity = match d.severity {
305                        DiagnosticSeverity::Error => 1,
306                        DiagnosticSeverity::Warning => 2,
307                        DiagnosticSeverity::Information => 3,
308                        DiagnosticSeverity::Hint => 4,
309                    };
310                    severity <= min_severity as u8
311                });
312                debug!("Filtered diagnostics by severity: {}", min_severity);
313            }
314        }
315    }
316
317    /// Apply IDE-specific behavior to hover information
318    pub fn apply_hover_behavior(
319        hover: &mut Option<Hover>,
320        settings: &IdeSpecificSettings,
321    ) {
322        debug!("Applying IDE-specific hover behavior for: {}", settings.ide_type.as_str());
323
324        if let Some(hover_info) = hover {
325            match settings.ide_type {
326                IdeType::VsCode => {
327                    // VS Code supports markdown in hover
328                    // Keep content as-is
329                }
330                IdeType::Vim => {
331                    // Vim prefers plain text, strip markdown
332                    hover_info.contents = Self::strip_markdown(&hover_info.contents);
333                }
334                IdeType::Emacs => {
335                    // Emacs supports some markup
336                    // Keep content as-is
337                }
338                IdeType::Unknown => {}
339            }
340        }
341    }
342
343    /// Strip markdown formatting from text
344    fn strip_markdown(text: &str) -> String {
345        // Simple markdown stripping
346        text.lines()
347            .map(|line| {
348                // Remove markdown headers
349                let line = line.trim_start_matches('#').trim();
350                // Remove markdown bold/italic
351                let line = line.replace("**", "").replace("__", "").replace("*", "").replace("_", "");
352                // Remove markdown code blocks
353                
354                line.replace("`", "")
355            })
356            .collect::<Vec<_>>()
357            .join("\n")
358    }
359
360    /// Validate IDE-specific settings
361    pub fn validate_settings(settings: &IdeSpecificSettings) -> IdeResult<()> {
362        debug!("Validating IDE-specific settings for: {}", settings.ide_type.as_str());
363
364        if settings.port == 0 && settings.ide_type != IdeType::Unknown {
365            return Err(IdeError::config_error(
366                "IDE port must be greater than 0",
367            ));
368        }
369
370        if settings.timeout_ms == 0 {
371            return Err(IdeError::config_error(
372                "IDE timeout must be greater than 0",
373            ));
374        }
375
376        Ok(())
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383
384    #[test]
385    fn test_ide_type_from_str() {
386        assert_eq!(IdeType::parse("vscode"), IdeType::VsCode);
387        assert_eq!(IdeType::parse("vs-code"), IdeType::VsCode);
388        assert_eq!(IdeType::parse("vim"), IdeType::Vim);
389        assert_eq!(IdeType::parse("neovim"), IdeType::Vim);
390        assert_eq!(IdeType::parse("emacs"), IdeType::Emacs);
391        assert_eq!(IdeType::parse("unknown"), IdeType::Unknown);
392    }
393
394    #[test]
395    fn test_ide_type_as_str() {
396        assert_eq!(IdeType::VsCode.as_str(), "vscode");
397        assert_eq!(IdeType::Vim.as_str(), "vim");
398        assert_eq!(IdeType::Emacs.as_str(), "emacs");
399        assert_eq!(IdeType::Unknown.as_str(), "unknown");
400    }
401
402    #[test]
403    fn test_ide_specific_settings_creation() {
404        let settings = IdeSpecificSettings::new(IdeType::VsCode);
405        assert_eq!(settings.ide_type, IdeType::VsCode);
406        assert!(settings.enabled_features.is_empty());
407        assert_eq!(settings.timeout_ms, 5000);
408    }
409
410    #[test]
411    fn test_ide_specific_settings_builder() {
412        let settings = IdeSpecificSettings::new(IdeType::Vim)
413            .with_feature("completion".to_string())
414            .with_feature("diagnostics".to_string())
415            .with_timeout(10000)
416            .with_port(9000);
417
418        assert_eq!(settings.enabled_features.len(), 2);
419        assert!(settings.is_feature_enabled("completion"));
420        assert!(settings.is_feature_enabled("diagnostics"));
421        assert_eq!(settings.timeout_ms, 10000);
422        assert_eq!(settings.port, 9000);
423    }
424
425    #[test]
426    fn test_ide_specific_settings_custom_settings() {
427        let settings = IdeSpecificSettings::new(IdeType::Emacs)
428            .with_setting("key1".to_string(), serde_json::json!("value1"))
429            .with_setting("key2".to_string(), serde_json::json!(42));
430
431        assert_eq!(settings.get_setting("key1").unwrap().as_str().unwrap(), "value1");
432        assert_eq!(settings.get_setting("key2").unwrap().as_u64().unwrap(), 42);
433        assert!(settings.get_setting("key3").is_none());
434    }
435
436    #[test]
437    fn test_strip_markdown() {
438        let text = "# Header\n**bold** and *italic* and `code`";
439        let stripped = IdeConfigApplicator::strip_markdown(text);
440        assert!(!stripped.contains("**"));
441        assert!(!stripped.contains("*"));
442        assert!(!stripped.contains("`"));
443    }
444
445    #[test]
446    fn test_validate_settings_valid() {
447        let settings = IdeSpecificSettings::new(IdeType::VsCode)
448            .with_port(8080)
449            .with_timeout(5000);
450
451        assert!(IdeConfigApplicator::validate_settings(&settings).is_ok());
452    }
453
454    #[test]
455    fn test_validate_settings_invalid_port() {
456        let settings = IdeSpecificSettings::new(IdeType::VsCode)
457            .with_port(0)
458            .with_timeout(5000);
459
460        assert!(IdeConfigApplicator::validate_settings(&settings).is_err());
461    }
462
463    #[test]
464    fn test_validate_settings_invalid_timeout() {
465        let settings = IdeSpecificSettings::new(IdeType::Vim)
466            .with_port(9000)
467            .with_timeout(0);
468
469        assert!(IdeConfigApplicator::validate_settings(&settings).is_err());
470    }
471}