Skip to main content

ai_agent/utils/
ide_path_conversion.rs

1//! IDE path conversion utilities
2//!
3//! Handles conversions between Claude's environment and the IDE's environment.
4
5use std::process::Command;
6
7/// Path converter trait for IDE communication
8pub trait IdePathConverter {
9    /// Convert path from IDE format to local format
10    fn to_local_path(&self, ide_path: &str) -> String;
11    /// Convert path from local format to IDE format
12    fn to_ide_path(&self, local_path: &str) -> String;
13}
14
15/// Converter for Windows IDE + WSL Claude scenario
16pub struct WindowsToWSLConverter {
17    wsl_distro_name: Option<String>,
18}
19
20impl WindowsToWSLConverter {
21    /// Create a new converter
22    pub fn new(wsl_distro_name: Option<String>) -> Self {
23        Self { wsl_distro_name }
24    }
25}
26
27impl IdePathConverter for WindowsToWSLConverter {
28    fn to_local_path(&self, windows_path: &str) -> String {
29        if windows_path.is_empty() {
30            return windows_path.to_string();
31        }
32
33        // Check if this is a path from a different WSL distro
34        if let Some(ref distro) = self.wsl_distro_name {
35            if let Some(caps) = regex::Regex::new(r"^\\\\wsl(?:\.localhost|\$)\\([^\\]+)(.*)$")
36                .ok()
37                .and_then(|r| r.captures(windows_path))
38            {
39                if caps.get(1).map(|m| m.as_str()) != Some(distro.as_str()) {
40                    // Different distro - wslpath will fail, so return original path
41                    return windows_path.to_string();
42                }
43            }
44        }
45
46        // Try wslpath first
47        if let Ok(result) = Command::new("wslpath").args(["-u", windows_path]).output() {
48            if result.status.success() {
49                return String::from_utf8_lossy(&result.stdout).trim().to_string();
50            }
51        }
52
53        // Fall back to manual conversion
54        // Convert backslashes to forward slashes
55        let result = windows_path.replace('\\', "/");
56
57        // Convert drive letter (e.g., C: -> /mnt/c)
58        if result.len() >= 2 && result.chars().nth(1) == Some(':') {
59            let letter = result.chars().next().unwrap().to_ascii_lowercase();
60            return format!("/mnt/{}{}", letter, &result[2..]);
61        }
62
63        result
64    }
65
66    fn to_ide_path(&self, wsl_path: &str) -> String {
67        if wsl_path.is_empty() {
68            return wsl_path.to_string();
69        }
70
71        // Try wslpath first
72        if let Ok(result) = Command::new("wslpath").args(["-w", wsl_path]).output() {
73            if result.status.success() {
74                return String::from_utf8_lossy(&result.stdout).trim().to_string();
75            }
76        }
77
78        // If wslpath fails, return the original path
79        wsl_path.to_string()
80    }
81}
82
83/// Check if distro names match for WSL UNC paths
84///
85/// # Arguments
86/// * `windows_path` - The Windows path to check
87/// * `wsl_distro_name` - The WSL distro name to match against
88///
89/// # Returns
90/// True if the path matches the distro or is not a WSL UNC path
91pub fn check_wsl_distro_match(windows_path: &str, wsl_distro_name: &str) -> bool {
92    if let Some(caps) = regex::Regex::new(r"^\\\\wsl(?:\.localhost|\$)\\([^\\]+)(.*)$")
93        .ok()
94        .and_then(|r| r.captures(windows_path))
95    {
96        caps.get(1).map(|m| m.as_str()) == Some(wsl_distro_name)
97    } else {
98        true // Not a WSL UNC path, so no distro mismatch
99    }
100}