generated from nhcarrigan/template
fix: resolve the weird path issues from windows <-> WSL (#106)
### Explanation _No response_ ### Issue _No response_ ### Attestations - [ ] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/) - [ ] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/). - [ ] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/). ### Dependencies - [ ] I have pinned the dependencies to a specific patch version. ### Style - [ ] I have run the linter and resolved any errors. - [ ] My pull request uses an appropriate title, matching the conventional commit standards. - [ ] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request. ### Tests - [ ] My contribution adds new code, and I have added tests to cover it. - [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes. - [ ] All new and existing tests pass locally with my changes. - [ ] Code coverage remains at or above the configured threshold. ### Documentation _No response_ ### Versioning _No response_ Reviewed-on: #106 Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com> Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit was merged in pull request #106.
This commit is contained in:
Generated
+105
-6
@@ -767,13 +767,34 @@ dependencies = [
|
|||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs"
|
||||||
|
version = "5.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys 0.4.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs-sys",
|
"dirs-sys 0.5.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"option-ext",
|
||||||
|
"redox_users 0.4.6",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -784,7 +805,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users",
|
"redox_users 0.5.2",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1618,6 +1639,7 @@ name = "hikari-desktop"
|
|||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"dirs 5.0.1",
|
||||||
"discord-rich-presence",
|
"discord-rich-presence",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"semver",
|
"semver",
|
||||||
@@ -3352,6 +3374,17 @@ dependencies = [
|
|||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"libredox",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_users"
|
name = "redox_users"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -4187,7 +4220,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
"cookie",
|
"cookie",
|
||||||
"dirs",
|
"dirs 6.0.0",
|
||||||
"dunce",
|
"dunce",
|
||||||
"embed_plist",
|
"embed_plist",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
@@ -4238,7 +4271,7 @@ checksum = "17fcb8819fd16463512a12f531d44826ce566f486d7ccd211c9c8cebdaec4e08"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
"dirs",
|
"dirs 6.0.0",
|
||||||
"glob",
|
"glob",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"json-patch",
|
"json-patch",
|
||||||
@@ -4962,7 +4995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c"
|
checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dirs",
|
"dirs 6.0.0",
|
||||||
"libappindicator",
|
"libappindicator",
|
||||||
"muda",
|
"muda",
|
||||||
"objc2",
|
"objc2",
|
||||||
@@ -5710,6 +5743,15 @@ dependencies = [
|
|||||||
"windows-targets 0.42.2",
|
"windows-targets 0.42.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.48.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
@@ -5761,6 +5803,21 @@ dependencies = [
|
|||||||
"windows_x86_64_msvc 0.42.2",
|
"windows_x86_64_msvc 0.42.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm 0.48.5",
|
||||||
|
"windows_aarch64_msvc 0.48.5",
|
||||||
|
"windows_i686_gnu 0.48.5",
|
||||||
|
"windows_i686_msvc 0.48.5",
|
||||||
|
"windows_x86_64_gnu 0.48.5",
|
||||||
|
"windows_x86_64_gnullvm 0.48.5",
|
||||||
|
"windows_x86_64_msvc 0.48.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5827,6 +5884,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5845,6 +5908,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5863,6 +5932,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5893,6 +5968,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5911,6 +5992,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5929,6 +6016,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5947,6 +6040,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -6027,7 +6126,7 @@ dependencies = [
|
|||||||
"block2",
|
"block2",
|
||||||
"cookie",
|
"cookie",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dirs",
|
"dirs 6.0.0",
|
||||||
"dpi",
|
"dpi",
|
||||||
"dunce",
|
"dunce",
|
||||||
"gdkx11",
|
"gdkx11",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ tempfile = "3"
|
|||||||
semver = "1"
|
semver = "1"
|
||||||
chrono = { version = "0.4.43", features = ["serde"] }
|
chrono = { version = "0.4.43", features = ["serde"] }
|
||||||
discord-rich-presence = "0.2"
|
discord-rich-presence = "0.2"
|
||||||
|
dirs = "5"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows = { version = "0.62", features = [
|
windows = { version = "0.62", features = [
|
||||||
|
|||||||
+536
-62
@@ -1,5 +1,5 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tauri::{AppHandle, State};
|
use tauri::{AppHandle, Manager, State};
|
||||||
use tauri_plugin_http::reqwest;
|
use tauri_plugin_http::reqwest;
|
||||||
use tauri_plugin_store::StoreExt;
|
use tauri_plugin_store::StoreExt;
|
||||||
|
|
||||||
@@ -11,6 +11,43 @@ use crate::temp_manager::SharedTempFileManager;
|
|||||||
|
|
||||||
const CONFIG_STORE_KEY: &str = "config";
|
const CONFIG_STORE_KEY: &str = "config";
|
||||||
|
|
||||||
|
/// Convert a Windows path to a WSL path
|
||||||
|
/// Example: C:\Users\accou\Documents\item.txt -> /mnt/c/Users/accou/Documents/item.txt
|
||||||
|
fn windows_path_to_wsl(windows_path: &str) -> Option<String> {
|
||||||
|
// Check if it's a Windows path (has drive letter like C:\)
|
||||||
|
if windows_path.len() >= 3 && windows_path.chars().nth(1) == Some(':') {
|
||||||
|
let drive_letter = windows_path.chars().next()?.to_lowercase().to_string();
|
||||||
|
let path_without_drive = &windows_path[2..]; // Remove "C:"
|
||||||
|
|
||||||
|
// Replace backslashes with forward slashes and convert to WSL mount point
|
||||||
|
let wsl_path = path_without_drive.replace('\\', "/");
|
||||||
|
Some(format!("/mnt/{}{}", drive_letter, wsl_path))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a WSL path to a Windows path
|
||||||
|
/// Example: /mnt/c/Users/accou/Documents/item.txt -> C:\Users\accou\Documents\item.txt
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn wsl_path_to_windows(wsl_path: &str) -> Option<String> {
|
||||||
|
// Check if it's a WSL mount point path
|
||||||
|
if wsl_path.starts_with("/mnt/") && wsl_path.len() > 6 {
|
||||||
|
let rest = &wsl_path[5..]; // Remove "/mnt/"
|
||||||
|
if let Some(drive_letter) = rest.chars().next() {
|
||||||
|
let path_after_drive = &rest[1..]; // Remove drive letter
|
||||||
|
|
||||||
|
// Convert to Windows path with backslashes
|
||||||
|
let windows_path = path_after_drive.replace('/', "\\");
|
||||||
|
Some(format!("{}:{}", drive_letter.to_uppercase(), windows_path))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn start_claude(
|
pub async fn start_claude(
|
||||||
bridge_manager: State<'_, SharedBridgeManager>,
|
bridge_manager: State<'_, SharedBridgeManager>,
|
||||||
@@ -69,7 +106,10 @@ pub async fn get_working_directory(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn select_wsl_directory() -> Result<String, String> {
|
pub async fn select_wsl_directory() -> Result<String, String> {
|
||||||
Ok("/home".to_string())
|
// Return the user's home directory cross-platform
|
||||||
|
dirs::home_dir()
|
||||||
|
.ok_or_else(|| "Could not determine home directory".to_string())
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -120,53 +160,93 @@ pub async fn validate_directory(
|
|||||||
path: String,
|
path: String,
|
||||||
current_dir: Option<String>,
|
current_dir: Option<String>,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
let path = Path::new(&path);
|
// Detect if we're dealing with a WSL path (starts with / on Windows, or current_dir is a WSL path)
|
||||||
|
let is_wsl_path = cfg!(windows) && (path.starts_with('/') || current_dir.as_ref().is_some_and(|p| p.starts_with('/')));
|
||||||
|
|
||||||
// Expand ~ to home directory
|
if is_wsl_path {
|
||||||
let expanded_path = if path.starts_with("~") {
|
// WSL path - handle as Unix-style path without filesystem validation
|
||||||
if let Some(home) = std::env::var_os("HOME") {
|
// since the Windows binary can't validate WSL filesystem paths
|
||||||
let home_path = Path::new(&home);
|
let resolved = if path.starts_with('/') {
|
||||||
if path == Path::new("~") {
|
// Absolute WSL path - use as-is
|
||||||
home_path.to_path_buf()
|
path
|
||||||
|
} else if let Some(ref cwd) = current_dir {
|
||||||
|
// Relative path - resolve manually using Unix path logic
|
||||||
|
if path == "." {
|
||||||
|
cwd.clone()
|
||||||
|
} else if path == ".." {
|
||||||
|
// Go up one directory
|
||||||
|
cwd.rsplit_once('/').map(|x| x.0).unwrap_or("/").to_string()
|
||||||
|
} else if path.starts_with("../") {
|
||||||
|
// Handle ../ prefix
|
||||||
|
let parent = cwd.rsplit_once('/').map(|x| x.0).unwrap_or("/");
|
||||||
|
let remainder = path.strip_prefix("../").unwrap();
|
||||||
|
if remainder.is_empty() {
|
||||||
|
parent.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{}/{}", parent, remainder)
|
||||||
|
}
|
||||||
|
} else if path.starts_with("./") {
|
||||||
|
// Handle ./ prefix
|
||||||
|
format!("{}/{}", cwd, path.strip_prefix("./").unwrap())
|
||||||
} else {
|
} else {
|
||||||
home_path.join(path.strip_prefix("~").unwrap())
|
// Regular relative path
|
||||||
|
format!("{}/{}", cwd, path)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err("Could not determine home directory".to_string());
|
return Err("Cannot resolve relative WSL path without current directory".to_string());
|
||||||
}
|
};
|
||||||
} else if path.is_relative() {
|
|
||||||
// Handle relative paths (., .., or any relative path) by resolving against current_dir
|
// Normalize the path (remove duplicate slashes, etc.)
|
||||||
if let Some(ref cwd) = current_dir {
|
let normalized = resolved.split('/').filter(|s| !s.is_empty()).collect::<Vec<_>>().join("/");
|
||||||
Path::new(cwd).join(path)
|
Ok(if normalized.is_empty() { "/".to_string() } else { format!("/{}", normalized) })
|
||||||
|
} else {
|
||||||
|
// Native path (Windows on Windows, Unix on Unix) - validate normally
|
||||||
|
let path = Path::new(&path);
|
||||||
|
|
||||||
|
let expanded_path = if path.starts_with("~") {
|
||||||
|
if let Some(home) = dirs::home_dir() {
|
||||||
|
if path == Path::new("~") {
|
||||||
|
home
|
||||||
|
} else {
|
||||||
|
home.join(path.strip_prefix("~").unwrap())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err("Could not determine home directory".to_string());
|
||||||
|
}
|
||||||
|
} else if path.is_relative() {
|
||||||
|
if let Some(ref cwd) = current_dir {
|
||||||
|
let cwd_path = PathBuf::from(cwd);
|
||||||
|
cwd_path.join(path)
|
||||||
|
} else {
|
||||||
|
path.to_path_buf()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
path.to_path_buf()
|
path.to_path_buf()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the path exists and is a directory
|
||||||
|
if !expanded_path.exists() {
|
||||||
|
return Err(format!(
|
||||||
|
"Directory does not exist: {}",
|
||||||
|
expanded_path.display()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
path.to_path_buf()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if the path exists and is a directory
|
if !expanded_path.is_dir() {
|
||||||
if !expanded_path.exists() {
|
return Err(format!(
|
||||||
return Err(format!(
|
"Path is not a directory: {}",
|
||||||
"Directory does not exist: {}",
|
expanded_path.display()
|
||||||
expanded_path.display()
|
));
|
||||||
));
|
}
|
||||||
|
|
||||||
|
// Return the canonicalized (absolute) path
|
||||||
|
expanded_path
|
||||||
|
.canonicalize()
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
.map_err(|e| format!("Failed to resolve path: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !expanded_path.is_dir() {
|
|
||||||
return Err(format!(
|
|
||||||
"Path is not a directory: {}",
|
|
||||||
expanded_path.display()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the canonicalized (absolute) path
|
|
||||||
expanded_path
|
|
||||||
.canonicalize()
|
|
||||||
.map(|p| p.to_string_lossy().to_string())
|
|
||||||
.map_err(|e| format!("Failed to resolve path: {}", e))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -202,21 +282,22 @@ pub async fn answer_question(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn list_skills() -> Result<Vec<String>, String> {
|
pub async fn list_skills() -> Result<Vec<String>, String> {
|
||||||
|
// On Windows, we need to use WSL to access the skills directory
|
||||||
|
// since skills are stored in the WSL home directory
|
||||||
|
if cfg!(windows) {
|
||||||
|
return list_skills_via_wsl().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// On Unix systems, use the native filesystem
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
// Get the home directory
|
let home = dirs::home_dir().ok_or_else(|| "Could not determine home directory".to_string())?;
|
||||||
let home =
|
let skills_dir = home.join(".claude").join("skills");
|
||||||
std::env::var_os("HOME").ok_or_else(|| "Could not determine home directory".to_string())?;
|
|
||||||
|
|
||||||
let skills_dir = Path::new(&home).join(".claude").join("skills");
|
|
||||||
|
|
||||||
// If the skills directory doesn't exist, return empty list
|
|
||||||
if !skills_dir.exists() {
|
if !skills_dir.exists() {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the directory and collect skill names
|
|
||||||
let mut skills = Vec::new();
|
let mut skills = Vec::new();
|
||||||
let entries =
|
let entries =
|
||||||
fs::read_dir(&skills_dir).map_err(|e| format!("Failed to read skills directory: {}", e))?;
|
fs::read_dir(&skills_dir).map_err(|e| format!("Failed to read skills directory: {}", e))?;
|
||||||
@@ -225,7 +306,6 @@ pub async fn list_skills() -> Result<Vec<String>, String> {
|
|||||||
let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
|
let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
|
||||||
// Only include directories that contain a SKILL.md file
|
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
let skill_file = path.join("SKILL.md");
|
let skill_file = path.join("SKILL.md");
|
||||||
if skill_file.exists() {
|
if skill_file.exists() {
|
||||||
@@ -236,9 +316,42 @@ pub async fn list_skills() -> Result<Vec<String>, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort alphabetically
|
|
||||||
skills.sort();
|
skills.sort();
|
||||||
|
Ok(skills)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List skills by executing commands through WSL (for Windows)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn list_skills_via_wsl() -> Result<Vec<String>, String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// Use WSL to list directories in ~/.claude/skills that contain SKILL.md
|
||||||
|
let output = Command::new("wsl")
|
||||||
|
.args([
|
||||||
|
"-e",
|
||||||
|
"sh",
|
||||||
|
"-c",
|
||||||
|
"if [ -d ~/.claude/skills ]; then for d in ~/.claude/skills/*/; do [ -f \"${d}SKILL.md\" ] && basename \"$d\"; done; fi",
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
if stderr.contains("not found") || stderr.contains("No such file") {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
return Err(format!("WSL command failed: {}", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let mut skills: Vec<String> = stdout
|
||||||
|
.lines()
|
||||||
|
.filter(|line| !line.is_empty())
|
||||||
|
.map(|line| line.to_string())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
skills.sort();
|
||||||
Ok(skills)
|
Ok(skills)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,8 +448,18 @@ pub async fn save_temp_file(
|
|||||||
.map(|n| n.to_string_lossy().to_string())
|
.map(|n| n.to_string_lossy().to_string())
|
||||||
.unwrap_or_else(|| "unknown".to_string());
|
.unwrap_or_else(|| "unknown".to_string());
|
||||||
|
|
||||||
|
let path_string = path.to_string_lossy().to_string();
|
||||||
|
|
||||||
|
// On Windows, convert the path to WSL format if needed
|
||||||
|
// so Claude Code (running in WSL) can access it via /mnt/c/...
|
||||||
|
let final_path = if cfg!(windows) {
|
||||||
|
windows_path_to_wsl(&path_string).unwrap_or(path_string)
|
||||||
|
} else {
|
||||||
|
path_string
|
||||||
|
};
|
||||||
|
|
||||||
Ok(SavedFileInfo {
|
Ok(SavedFileInfo {
|
||||||
path: path.to_string_lossy().to_string(),
|
path: final_path,
|
||||||
filename,
|
filename,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -405,42 +528,142 @@ pub struct FileEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn list_directory(path: String) -> Result<Vec<FileEntry>, String> {
|
pub async fn list_directory(app: AppHandle, path: String) -> Result<Vec<FileEntry>, String> {
|
||||||
|
// Set up logging
|
||||||
|
let log_path = if let Ok(app_data_dir) = app.path().app_data_dir() {
|
||||||
|
let _ = std::fs::create_dir_all(&app_data_dir);
|
||||||
|
app_data_dir.join("hikari_editor_debug.log")
|
||||||
|
} else {
|
||||||
|
PathBuf::from("hikari_editor_debug.log")
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut log_file = std::fs::OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(&log_path)
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
let mut log = |msg: String| {
|
||||||
|
if let Some(ref mut file) = log_file {
|
||||||
|
use std::io::Write;
|
||||||
|
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
|
||||||
|
let _ = writeln!(file, "[{}] {}", timestamp, msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
log(format!("list_directory called with path: {}", path));
|
||||||
|
log(format!("cfg!(windows) = {}", cfg!(windows)));
|
||||||
|
log(format!("path.starts_with('/') = {}", path.starts_with('/')));
|
||||||
|
|
||||||
|
// On Windows with a WSL path (starts with /), use WSL to list the directory
|
||||||
|
if cfg!(windows) && path.starts_with('/') {
|
||||||
|
log("Using WSL path".to_string());
|
||||||
|
return list_directory_via_wsl(&path).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
log("Using native filesystem access".to_string());
|
||||||
|
|
||||||
|
// Native filesystem access
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
let dir_path = Path::new(&path);
|
let dir_path = Path::new(&path);
|
||||||
|
|
||||||
if !dir_path.exists() {
|
if !dir_path.exists() {
|
||||||
return Err(format!("Directory does not exist: {}", path));
|
let err = format!("Directory does not exist: {}", path);
|
||||||
|
log(format!("ERROR: {}", err));
|
||||||
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !dir_path.is_dir() {
|
if !dir_path.is_dir() {
|
||||||
return Err(format!("Path is not a directory: {}", path));
|
let err = format!("Path is not a directory: {}", path);
|
||||||
|
log(format!("ERROR: {}", err));
|
||||||
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let entries = fs::read_dir(dir_path)
|
let entries = fs::read_dir(dir_path)
|
||||||
.map_err(|e| format!("Failed to read directory: {}", e))?;
|
.map_err(|e| {
|
||||||
|
let err = format!("Failed to read directory: {}", e);
|
||||||
|
log(format!("ERROR: {}", err));
|
||||||
|
err
|
||||||
|
})?;
|
||||||
|
|
||||||
let mut file_entries = Vec::new();
|
let mut file_entries = Vec::new();
|
||||||
|
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
let entry = entry.map_err(|e| format!("Failed to read entry: {}", e))?;
|
let entry = entry.map_err(|e| {
|
||||||
|
let err = format!("Failed to read entry: {}", e);
|
||||||
|
log(format!("ERROR: {}", err));
|
||||||
|
err
|
||||||
|
})?;
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
let name = entry
|
let name = entry
|
||||||
.file_name()
|
.file_name()
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
// Skip hidden files by default (can be made configurable later)
|
file_entries.push(FileEntry {
|
||||||
if name.starts_with('.') {
|
name: name.clone(),
|
||||||
|
path: path.to_string_lossy().to_string(),
|
||||||
|
is_directory: path.is_dir(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log(format!("Successfully listed {} entries", file_entries.len()));
|
||||||
|
Ok(file_entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List directory contents via WSL (for Windows with WSL paths)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn list_directory_via_wsl(path: &str) -> Result<Vec<FileEntry>, String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// Use WSL to list directory contents
|
||||||
|
// Output format: type<tab>name (d for directory, f for file)
|
||||||
|
let script = format!(
|
||||||
|
r#"if [ -d '{}' ]; then for f in '{}'/* '{}'/.* ; do [ -e "$f" ] || continue; name=$(basename "$f"); if [ "$name" = "." ] || [ "$name" = ".." ]; then continue; fi; if [ -d "$f" ]; then echo "d $name"; else echo "f $name"; fi; done; else echo "ERROR: Directory does not exist"; exit 1; fi"#,
|
||||||
|
path, path, path
|
||||||
|
);
|
||||||
|
|
||||||
|
let output = Command::new("wsl")
|
||||||
|
.args(["-e", "sh", "-c", &script])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
||||||
|
if !output.status.success() || stdout.starts_with("ERROR:") {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
if stdout.starts_with("ERROR:") {
|
||||||
|
return Err(stdout.trim().to_string());
|
||||||
|
}
|
||||||
|
return Err(format!("WSL command failed: {}", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file_entries = Vec::new();
|
||||||
|
|
||||||
|
for line in stdout.lines() {
|
||||||
|
if line.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let parts: Vec<&str> = line.splitn(2, '\t').collect();
|
||||||
|
if parts.len() != 2 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_directory = parts[0] == "d";
|
||||||
|
let name = parts[1].to_string();
|
||||||
|
let entry_path = if path == "/" {
|
||||||
|
format!("/{}", name)
|
||||||
|
} else {
|
||||||
|
format!("{}/{}", path, name)
|
||||||
|
};
|
||||||
|
|
||||||
file_entries.push(FileEntry {
|
file_entries.push(FileEntry {
|
||||||
name,
|
name,
|
||||||
path: path.to_string_lossy().to_string(),
|
path: entry_path,
|
||||||
is_directory: path.is_dir(),
|
is_directory,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,22 +672,80 @@ pub async fn list_directory(path: String) -> Result<Vec<FileEntry>, String> {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn read_file_content(path: String) -> Result<String, String> {
|
pub async fn read_file_content(path: String) -> Result<String, String> {
|
||||||
use std::fs;
|
// On Windows with a WSL path, use WSL to read the file
|
||||||
|
if cfg!(windows) && path.starts_with('/') {
|
||||||
|
return read_file_via_wsl(&path).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
fs::read_to_string(&path)
|
fs::read_to_string(&path)
|
||||||
.map_err(|e| format!("Failed to read file: {}", e))
|
.map_err(|e| format!("Failed to read file: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read file content via WSL (for Windows with WSL paths)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn read_file_via_wsl(path: &str) -> Result<String, String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let output = Command::new("wsl")
|
||||||
|
.args(["-e", "cat", path])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(format!("Failed to read file: {}", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn write_file_content(path: String, content: String) -> Result<(), String> {
|
pub async fn write_file_content(path: String, content: String) -> Result<(), String> {
|
||||||
use std::fs;
|
// On Windows with a WSL path, use WSL to write the file
|
||||||
|
if cfg!(windows) && path.starts_with('/') {
|
||||||
|
return write_file_via_wsl(&path, &content).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
fs::write(&path, content)
|
fs::write(&path, content)
|
||||||
.map_err(|e| format!("Failed to write file: {}", e))
|
.map_err(|e| format!("Failed to write file: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write file content via WSL (for Windows with WSL paths)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn write_file_via_wsl(path: &str, content: &str) -> Result<(), String> {
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
let mut child = Command::new("wsl")
|
||||||
|
.args(["-e", "sh", "-c", &format!("cat > '{}'", path)])
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if let Some(mut stdin) = child.stdin.take() {
|
||||||
|
stdin.write_all(content.as_bytes())
|
||||||
|
.map_err(|e| format!("Failed to write to stdin: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = child.wait()
|
||||||
|
.map_err(|e| format!("Failed to wait for WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
return Err("Failed to write file via WSL".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn create_file(path: String) -> Result<(), String> {
|
pub async fn create_file(path: String) -> Result<(), String> {
|
||||||
|
// On Windows with a WSL path, use WSL to create the file
|
||||||
|
if cfg!(windows) && path.starts_with('/') {
|
||||||
|
return create_file_via_wsl(&path).await;
|
||||||
|
}
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -479,8 +760,41 @@ pub async fn create_file(path: String) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create file via WSL (for Windows with WSL paths)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn create_file_via_wsl(path: &str) -> Result<(), String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// Check if file exists first
|
||||||
|
let check = Command::new("wsl")
|
||||||
|
.args(["-e", "test", "-e", path])
|
||||||
|
.status()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if check.success() {
|
||||||
|
return Err("File already exists".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Command::new("wsl")
|
||||||
|
.args(["-e", "touch", path])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(format!("Failed to create file: {}", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn create_directory(path: String) -> Result<(), String> {
|
pub async fn create_directory(path: String) -> Result<(), String> {
|
||||||
|
// On Windows with a WSL path, use WSL to create the directory
|
||||||
|
if cfg!(windows) && path.starts_with('/') {
|
||||||
|
return create_directory_via_wsl(&path).await;
|
||||||
|
}
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -495,8 +809,41 @@ pub async fn create_directory(path: String) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create directory via WSL (for Windows with WSL paths)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn create_directory_via_wsl(path: &str) -> Result<(), String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// Check if directory exists first
|
||||||
|
let check = Command::new("wsl")
|
||||||
|
.args(["-e", "test", "-e", path])
|
||||||
|
.status()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if check.success() {
|
||||||
|
return Err("Directory already exists".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Command::new("wsl")
|
||||||
|
.args(["-e", "mkdir", "-p", path])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(format!("Failed to create directory: {}", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn delete_file(path: String) -> Result<(), String> {
|
pub async fn delete_file(path: String) -> Result<(), String> {
|
||||||
|
// On Windows with a WSL path, use WSL to delete the file
|
||||||
|
if cfg!(windows) && path.starts_with('/') {
|
||||||
|
return delete_file_via_wsl(&path).await;
|
||||||
|
}
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -515,8 +862,51 @@ pub async fn delete_file(path: String) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delete file via WSL (for Windows with WSL paths)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn delete_file_via_wsl(path: &str) -> Result<(), String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// Check if path exists
|
||||||
|
let check_exists = Command::new("wsl")
|
||||||
|
.args(["-e", "test", "-e", path])
|
||||||
|
.status()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if !check_exists.success() {
|
||||||
|
return Err("File does not exist".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if path is a directory
|
||||||
|
let check_dir = Command::new("wsl")
|
||||||
|
.args(["-e", "test", "-d", path])
|
||||||
|
.status()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if check_dir.success() {
|
||||||
|
return Err("Path is a directory, use delete_directory instead".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Command::new("wsl")
|
||||||
|
.args(["-e", "rm", path])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(format!("Failed to delete file: {}", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn delete_directory(path: String) -> Result<(), String> {
|
pub async fn delete_directory(path: String) -> Result<(), String> {
|
||||||
|
// On Windows with a WSL path, use WSL to delete the directory
|
||||||
|
if cfg!(windows) && path.starts_with('/') {
|
||||||
|
return delete_directory_via_wsl(&path).await;
|
||||||
|
}
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -535,8 +925,51 @@ pub async fn delete_directory(path: String) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delete directory via WSL (for Windows with WSL paths)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn delete_directory_via_wsl(path: &str) -> Result<(), String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// Check if path exists
|
||||||
|
let check_exists = Command::new("wsl")
|
||||||
|
.args(["-e", "test", "-e", path])
|
||||||
|
.status()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if !check_exists.success() {
|
||||||
|
return Err("Directory does not exist".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if path is a directory
|
||||||
|
let check_dir = Command::new("wsl")
|
||||||
|
.args(["-e", "test", "-d", path])
|
||||||
|
.status()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if !check_dir.success() {
|
||||||
|
return Err("Path is not a directory".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Command::new("wsl")
|
||||||
|
.args(["-e", "rm", "-rf", path])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(format!("Failed to delete directory: {}", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn rename_path(old_path: String, new_path: String) -> Result<(), String> {
|
pub async fn rename_path(old_path: String, new_path: String) -> Result<(), String> {
|
||||||
|
// On Windows with WSL paths, use WSL to rename
|
||||||
|
if cfg!(windows) && old_path.starts_with('/') {
|
||||||
|
return rename_path_via_wsl(&old_path, &new_path).await;
|
||||||
|
}
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -556,6 +989,44 @@ pub async fn rename_path(old_path: String, new_path: String) -> Result<(), Strin
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rename path via WSL (for Windows with WSL paths)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn rename_path_via_wsl(old_path: &str, new_path: &str) -> Result<(), String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// Check if old path exists
|
||||||
|
let check_old = Command::new("wsl")
|
||||||
|
.args(["-e", "test", "-e", old_path])
|
||||||
|
.status()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if !check_old.success() {
|
||||||
|
return Err("Path does not exist".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if new path already exists
|
||||||
|
let check_new = Command::new("wsl")
|
||||||
|
.args(["-e", "test", "-e", new_path])
|
||||||
|
.status()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if check_new.success() {
|
||||||
|
return Err("Destination already exists".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Command::new("wsl")
|
||||||
|
.args(["-e", "mv", old_path, new_path])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(format!("Failed to rename: {}", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== Cost Tracking Commands ====================
|
// ==================== Cost Tracking Commands ====================
|
||||||
|
|
||||||
const COST_HISTORY_STORE_KEY: &str = "cost_history";
|
const COST_HISTORY_STORE_KEY: &str = "cost_history";
|
||||||
@@ -892,7 +1363,10 @@ mod tests {
|
|||||||
fn test_select_wsl_directory_returns_home() {
|
fn test_select_wsl_directory_returns_home() {
|
||||||
let result = run_async(select_wsl_directory());
|
let result = run_async(select_wsl_directory());
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(result.unwrap(), "/home");
|
|
||||||
|
// Should return the user's home directory
|
||||||
|
let home_dir = result.unwrap();
|
||||||
|
assert!(home_dir.starts_with("/home/") || home_dir == "/root");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== UpdateInfo struct tests ====================
|
// ==================== UpdateInfo struct tests ====================
|
||||||
|
|||||||
@@ -447,11 +447,12 @@ User: ${formattedMessage}`;
|
|||||||
try {
|
try {
|
||||||
const arrayBuffer = await file.arrayBuffer();
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
const bytes = Array.from(new Uint8Array(arrayBuffer));
|
const bytes = Array.from(new Uint8Array(arrayBuffer));
|
||||||
savedPath = await invoke<string>("save_temp_file", {
|
const result = await invoke<{ path: string; filename: string }>("save_temp_file", {
|
||||||
conversationId,
|
conversationId,
|
||||||
filename,
|
filename,
|
||||||
data: bytes,
|
data: bytes,
|
||||||
});
|
});
|
||||||
|
savedPath = result.path;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to save dropped file to temp:", error);
|
console.error("Failed to save dropped file to temp:", error);
|
||||||
savedPath = file.name;
|
savedPath = file.name;
|
||||||
@@ -585,11 +586,12 @@ User: ${formattedMessage}`;
|
|||||||
try {
|
try {
|
||||||
const arrayBuffer = await file.arrayBuffer();
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
const bytes = Array.from(new Uint8Array(arrayBuffer));
|
const bytes = Array.from(new Uint8Array(arrayBuffer));
|
||||||
savedPath = await invoke<string>("save_temp_file", {
|
const result = await invoke<{ path: string; filename: string }>("save_temp_file", {
|
||||||
conversationId,
|
conversationId,
|
||||||
filename,
|
filename,
|
||||||
data: bytes,
|
data: bytes,
|
||||||
});
|
});
|
||||||
|
savedPath = result.path;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to save pasted file to temp:", error);
|
console.error("Failed to save pasted file to temp:", error);
|
||||||
}
|
}
|
||||||
@@ -647,11 +649,12 @@ User: ${formattedMessage}`;
|
|||||||
try {
|
try {
|
||||||
const arrayBuffer = await blob.arrayBuffer();
|
const arrayBuffer = await blob.arrayBuffer();
|
||||||
const bytes = Array.from(new Uint8Array(arrayBuffer));
|
const bytes = Array.from(new Uint8Array(arrayBuffer));
|
||||||
savedPath = await invoke<string>("save_temp_file", {
|
const result = await invoke<{ path: string; filename: string }>("save_temp_file", {
|
||||||
conversationId,
|
conversationId,
|
||||||
filename,
|
filename,
|
||||||
data: bytes,
|
data: bytes,
|
||||||
});
|
});
|
||||||
|
savedPath = result.path;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to save clipboard image to temp:", error);
|
console.error("Failed to save clipboard image to temp:", error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user