1use crate::error::{IdeError, IdeResult};
5use crate::types::*;
6use std::collections::HashMap;
7use tracing::{debug, info};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum IdeType {
12 VsCode,
14 Vim,
16 Emacs,
18 Unknown,
20}
21
22impl IdeType {
23 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 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#[derive(Debug, Clone)]
46pub struct IdeSpecificSettings {
47 pub ide_type: IdeType,
49 pub enabled_features: Vec<String>,
51 pub custom_settings: HashMap<String, serde_json::Value>,
53 pub timeout_ms: u64,
55 pub port: u16,
57}
58
59impl IdeSpecificSettings {
60 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 pub fn with_feature(mut self, feature: String) -> Self {
73 self.enabled_features.push(feature);
74 self
75 }
76
77 pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
79 self.timeout_ms = timeout_ms;
80 self
81 }
82
83 pub fn with_port(mut self, port: u16) -> Self {
85 self.port = port;
86 self
87 }
88
89 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 pub fn is_feature_enabled(&self, feature: &str) -> bool {
97 self.enabled_features.iter().any(|f| f == feature)
98 }
99
100 pub fn get_setting(&self, key: &str) -> Option<&serde_json::Value> {
102 self.custom_settings.get(key)
103 }
104}
105
106pub struct IdeConfigApplicator;
108
109impl IdeConfigApplicator {
110 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 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); for feature in &vscode_config.features {
146 settings = settings.with_feature(feature.clone());
147 }
148
149 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 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) .with_timeout(5000); settings = settings
184 .with_feature("completion".to_string())
185 .with_feature("diagnostics".to_string())
186 .with_feature("hover".to_string());
187
188 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 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) .with_timeout(5000); settings = settings
222 .with_feature("completion".to_string())
223 .with_feature("diagnostics".to_string())
224 .with_feature("hover".to_string());
225
226 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 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 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 match settings.ide_type {
256 IdeType::VsCode => {
257 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 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 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 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 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 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 }
330 IdeType::Vim => {
331 hover_info.contents = Self::strip_markdown(&hover_info.contents);
333 }
334 IdeType::Emacs => {
335 }
338 IdeType::Unknown => {}
339 }
340 }
341 }
342
343 fn strip_markdown(text: &str) -> String {
345 text.lines()
347 .map(|line| {
348 let line = line.trim_start_matches('#').trim();
350 let line = line.replace("**", "").replace("__", "").replace("*", "").replace("_", "");
352 line.replace("`", "")
355 })
356 .collect::<Vec<_>>()
357 .join("\n")
358 }
359
360 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}