use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EditorType {
VSCode,
Other(String),
}
#[derive(Debug, Clone)]
pub struct EditorInfo {
pub editor_type: EditorType,
pub workspace_path: PathBuf,
pub extensions: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum OutputStrategy {
CurrentEditorDir {
path: PathBuf,
},
ProjectRoot {
path: PathBuf,
},
FileWatchOnly,
}
pub struct EditorIntegration {
active_editor: Option<EditorInfo>,
output_strategy: OutputStrategy,
vscode_extension_present: bool,
}
impl EditorIntegration {
pub fn new() -> Self {
Self {
active_editor: None,
output_strategy: OutputStrategy::FileWatchOnly,
vscode_extension_present: false,
}
}
pub fn detect_editor(&mut self) -> Option<EditorType> {
if std::env::var("VSCODE_PID").is_ok() || std::env::var("TERM_PROGRAM").map_or(false, |t| t == "vscode") {
self.vscode_extension_present = self.check_vscode_extension();
return Some(EditorType::VSCode);
}
None
}
fn check_vscode_extension(&self) -> bool {
if let Some(home) = dirs::home_dir() {
let extension_dirs = vec![
home.join(".vscode").join("extensions"),
home.join(".vscode-server").join("extensions"),
];
for ext_dir in extension_dirs {
if ext_dir.exists() {
if let Ok(entries) = std::fs::read_dir(ext_dir) {
for entry in entries.flatten() {
let name = entry.file_name();
if name.to_string_lossy().contains("forge-lsp") {
return true;
}
}
}
}
}
}
false
}
pub fn get_current_editor_dir(&self) -> Option<PathBuf> {
None
}
pub fn set_active_editor(&mut self, editor_info: EditorInfo) {
self.active_editor = Some(editor_info);
}
pub fn has_vscode_extension(&self) -> bool {
self.vscode_extension_present
}
pub fn set_output_strategy(&mut self, strategy: OutputStrategy) {
tracing::info!("Output strategy changed to: {:?}", strategy);
self.output_strategy = strategy;
}
pub fn output_strategy(&self) -> &OutputStrategy {
&self.output_strategy
}
pub fn get_output_directory(&self) -> Option<PathBuf> {
match &self.output_strategy {
OutputStrategy::CurrentEditorDir { path } => Some(path.clone()),
OutputStrategy::ProjectRoot { path } => Some(path.clone()),
OutputStrategy::FileWatchOnly => None,
}
}
pub fn update_editor_directory(&mut self, path: PathBuf) {
self.output_strategy = OutputStrategy::CurrentEditorDir { path: path.clone() };
tracing::debug!("Updated editor directory to: {:?}", path);
}
}
impl Default for EditorIntegration {
fn default() -> Self {
Self::new()
}
}
mod dirs {
use std::path::PathBuf;
pub fn home_dir() -> Option<PathBuf> {
#[cfg(windows)]
{
std::env::var_os("USERPROFILE").map(PathBuf::from)
}
#[cfg(not(windows))]
{
std::env::var_os("HOME").map(PathBuf::from)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_editor_integration_creation() {
let integration = EditorIntegration::new();
assert!(matches!(integration.output_strategy, OutputStrategy::FileWatchOnly));
}
#[test]
fn test_set_output_strategy() {
let mut integration = EditorIntegration::new();
let path = PathBuf::from("/test/path");
integration.set_output_strategy(OutputStrategy::CurrentEditorDir {
path: path.clone(),
});
assert_eq!(integration.get_output_directory(), Some(path));
}
#[test]
fn test_update_editor_directory() {
let mut integration = EditorIntegration::new();
let path = PathBuf::from("/new/dir");
integration.update_editor_directory(path.clone());
match integration.output_strategy() {
OutputStrategy::CurrentEditorDir { path: p } => {
assert_eq!(p, &path);
}
_ => panic!("Expected CurrentEditorDir strategy"),
}
}
}