dx_forge/core/
editor_integration.rs

1//! Editor integration for detecting active editor and output directory
2//!
3//! Provides functionality to detect VSCode extension presence and determine
4//! where to output generated files.
5
6use std::path::PathBuf;
7
8/// Type of code editor
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum EditorType {
11    /// Visual Studio Code
12    VSCode,
13    
14    /// Other or unknown editor
15    Other(String),
16}
17
18/// Information about the active editor
19#[derive(Debug, Clone)]
20pub struct EditorInfo {
21    /// Type of editor
22    pub editor_type: EditorType,
23    
24    /// Workspace/project path
25    pub workspace_path: PathBuf,
26    
27    /// Installed extensions
28    pub extensions: Vec<String>,
29}
30
31/// Strategy for where to output generated files
32#[derive(Debug, Clone)]
33pub enum OutputStrategy {
34    /// Output to current editor's active directory
35    CurrentEditorDir {
36        path: PathBuf,
37    },
38    
39    /// Output to project root
40    ProjectRoot {
41        path: PathBuf,
42    },
43    
44    /// File watching only, no specific output directory
45    FileWatchOnly,
46}
47
48/// Manages editor integration
49pub struct EditorIntegration {
50    active_editor: Option<EditorInfo>,
51    output_strategy: OutputStrategy,
52    vscode_extension_present: bool,
53}
54
55impl EditorIntegration {
56    /// Create a new editor integration manager
57    pub fn new() -> Self {
58        Self {
59            active_editor: None,
60            output_strategy: OutputStrategy::FileWatchOnly,
61            vscode_extension_present: false,
62        }
63    }
64    
65    /// Detect the active editor
66    pub fn detect_editor(&mut self) -> Option<EditorType> {
67        // Check for VSCode via environment variables
68        if std::env::var("VSCODE_PID").is_ok() || std::env::var("TERM_PROGRAM").map_or(false, |t| t == "vscode") {
69            self.vscode_extension_present = self.check_vscode_extension();
70            return Some(EditorType::VSCode);
71        }
72        
73        None
74    }
75    
76    /// Check if VSCode Forge extension is installed
77    fn check_vscode_extension(&self) -> bool {
78        // Check common VSCode extension directories
79        if let Some(home) = dirs::home_dir() {
80            let extension_dirs = vec![
81                home.join(".vscode").join("extensions"),
82                home.join(".vscode-server").join("extensions"),
83            ];
84            
85            for ext_dir in extension_dirs {
86                if ext_dir.exists() {
87                    if let Ok(entries) = std::fs::read_dir(ext_dir) {
88                        for entry in entries.flatten() {
89                            let name = entry.file_name();
90                            if name.to_string_lossy().contains("forge-lsp") {
91                                return true;
92                            }
93                        }
94                    }
95                }
96            }
97        }
98        
99        false
100    }
101    
102    /// Get current editor directory (if available from extension)
103    pub fn get_current_editor_dir(&self) -> Option<PathBuf> {
104        // This would be populated by WebSocket communication from VSCode extension
105        // For now, return None - will be implemented when WebSocket integration is added
106        None
107    }
108    
109    /// Set the active editor
110    pub fn set_active_editor(&mut self, editor_info: EditorInfo) {
111        self.active_editor = Some(editor_info);
112    }
113    
114    /// Check if VSCode extension is present
115    pub fn has_vscode_extension(&self) -> bool {
116        self.vscode_extension_present
117    }
118    
119    /// Set output strategy
120    pub fn set_output_strategy(&mut self, strategy: OutputStrategy) {
121        tracing::info!("Output strategy changed to: {:?}", strategy);
122        self.output_strategy = strategy;
123    }
124    
125    /// Get current output strategy
126    pub fn output_strategy(&self) -> &OutputStrategy {
127        &self.output_strategy
128    }
129    
130    /// Get output directory based on current strategy
131    pub fn get_output_directory(&self) -> Option<PathBuf> {
132        match &self.output_strategy {
133            OutputStrategy::CurrentEditorDir { path } => Some(path.clone()),
134            OutputStrategy::ProjectRoot { path } => Some(path.clone()),
135            OutputStrategy::FileWatchOnly => None,
136        }
137    }
138    
139    /// Update current editor directory (called by extension via WebSocket)
140    pub fn update_editor_directory(&mut self, path: PathBuf) {
141        self.output_strategy = OutputStrategy::CurrentEditorDir { path: path.clone() };
142        tracing::debug!("Updated editor directory to: {:?}", path);
143    }
144}
145
146impl Default for EditorIntegration {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151
152// Helper function to get home directory (cross-platform)
153mod dirs {
154    use std::path::PathBuf;
155    
156    pub fn home_dir() -> Option<PathBuf> {
157        #[cfg(windows)]
158        {
159            std::env::var_os("USERPROFILE").map(PathBuf::from)
160        }
161        
162        #[cfg(not(windows))]
163        {
164            std::env::var_os("HOME").map(PathBuf::from)
165        }
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172    
173    #[test]
174    fn test_editor_integration_creation() {
175        let integration = EditorIntegration::new();
176        assert!(matches!(integration.output_strategy, OutputStrategy::FileWatchOnly));
177    }
178    
179    #[test]
180    fn test_set_output_strategy() {
181        let mut integration = EditorIntegration::new();
182        
183        let path = PathBuf::from("/test/path");
184        integration.set_output_strategy(OutputStrategy::CurrentEditorDir {
185            path: path.clone(),
186        });
187        
188        assert_eq!(integration.get_output_directory(), Some(path));
189    }
190    
191    #[test]
192    fn test_update_editor_directory() {
193        let mut integration = EditorIntegration::new();
194        
195        let path = PathBuf::from("/new/dir");
196        integration.update_editor_directory(path.clone());
197        
198        match integration.output_strategy() {
199            OutputStrategy::CurrentEditorDir { path: p } => {
200                assert_eq!(p, &path);
201            }
202            _ => panic!("Expected CurrentEditorDir strategy"),
203        }
204    }
205}