feat: implement path normalization across commands
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use crate::{DesktopRuntime, SettingsStore};
|
||||
use crate::path_utils::expand_tilde_path;
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
collections::{HashSet, VecDeque},
|
||||
@@ -346,7 +347,7 @@ async fn resolve_sandboxed_path(
|
||||
.filter(|value| !value.is_empty());
|
||||
|
||||
let candidate_path = match (candidate_input, workspace_root) {
|
||||
(Some(value), _) => PathBuf::from(value),
|
||||
(Some(value), _) => expand_tilde_path(value),
|
||||
(None, Some(root)) => root.clone(),
|
||||
(None, None) => default_home_directory(),
|
||||
};
|
||||
@@ -376,7 +377,7 @@ async fn resolve_creatable_path(
|
||||
path: &str,
|
||||
workspace_root: Option<&PathBuf>,
|
||||
) -> Result<PathBuf, FsCommandError> {
|
||||
let candidate = PathBuf::from(path);
|
||||
let candidate = expand_tilde_path(path);
|
||||
if candidate.as_os_str().is_empty() {
|
||||
return Err(FsCommandError::Other("Path is required".to_string()));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{DesktopRuntime, SettingsStore};
|
||||
use crate::path_utils::expand_tilde_path;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use log::{error, info, warn};
|
||||
use regex::Regex;
|
||||
@@ -357,7 +358,7 @@ fn append_git_option_map(args: &mut Vec<String>, map: &serde_json::Map<String, V
|
||||
// Removed unused resolve_workspace_root function
|
||||
|
||||
async fn validate_git_path(path: &str, _settings: &SettingsStore) -> Result<PathBuf> {
|
||||
let path_buf = PathBuf::from(path);
|
||||
let path_buf = expand_tilde_path(path);
|
||||
if !path_buf.exists() {
|
||||
return Err(anyhow!("Directory does not exist: {}", path));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use tauri::AppHandle;
|
||||
use tauri::State;
|
||||
|
||||
use crate::DesktopRuntime;
|
||||
use crate::path_utils::expand_tilde_path;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -34,10 +35,12 @@ pub async fn process_directory_selection(
|
||||
path: String,
|
||||
state: State<'_, DesktopRuntime>,
|
||||
) -> Result<DirectoryPermissionResult, String> {
|
||||
use std::path::PathBuf;
|
||||
|
||||
// Validate directory exists
|
||||
let path_buf = PathBuf::from(&path);
|
||||
let mut path_buf = expand_tilde_path(&path);
|
||||
if let Ok(canonicalized) = std::fs::canonicalize(&path_buf) {
|
||||
path_buf = canonicalized;
|
||||
}
|
||||
let normalized_path = path_buf.to_string_lossy().to_string();
|
||||
if !path_buf.exists() {
|
||||
return Ok(DirectoryPermissionResult {
|
||||
success: false,
|
||||
@@ -64,7 +67,7 @@ pub async fn process_directory_selection(
|
||||
if let Some(obj) = settings.as_object_mut() {
|
||||
obj.insert(
|
||||
"lastDirectory".to_string(),
|
||||
serde_json::Value::String(path.clone()),
|
||||
serde_json::Value::String(normalized_path.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,12 +79,12 @@ pub async fn process_directory_selection(
|
||||
|
||||
info!(
|
||||
"[permissions] Updated settings with lastDirectory: {}",
|
||||
path
|
||||
normalized_path
|
||||
);
|
||||
|
||||
Ok(DirectoryPermissionResult {
|
||||
success: true,
|
||||
path: Some(path),
|
||||
path: Some(normalized_path),
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
@@ -110,7 +113,11 @@ pub async fn request_directory_access(
|
||||
) -> Result<DirectoryPermissionResult, String> {
|
||||
let path = request.path;
|
||||
|
||||
let path_buf = std::path::PathBuf::from(&path);
|
||||
let mut path_buf = expand_tilde_path(&path);
|
||||
if let Ok(canonicalized) = std::fs::canonicalize(&path_buf) {
|
||||
path_buf = canonicalized;
|
||||
}
|
||||
let normalized_path = path_buf.to_string_lossy().to_string();
|
||||
if !path_buf.exists() {
|
||||
return Ok(DirectoryPermissionResult {
|
||||
success: false,
|
||||
@@ -131,7 +138,7 @@ pub async fn request_directory_access(
|
||||
match std::fs::read_dir(&path_buf) {
|
||||
Ok(_) => Ok(DirectoryPermissionResult {
|
||||
success: true,
|
||||
path: Some(path),
|
||||
path: Some(normalized_path),
|
||||
error: None,
|
||||
}),
|
||||
Err(e) => Ok(DirectoryPermissionResult {
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::collections::HashSet;
|
||||
use tauri::State;
|
||||
|
||||
use crate::DesktopRuntime;
|
||||
use crate::path_utils::expand_tilde_path;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -105,12 +106,14 @@ fn sanitize_settings_update(payload: &Value) -> Value {
|
||||
}
|
||||
if let Some(Value::String(s)) = obj.get("lastDirectory") {
|
||||
if !s.is_empty() {
|
||||
result_obj.insert("lastDirectory".to_string(), json!(s));
|
||||
let expanded = expand_tilde_path(s).to_string_lossy().to_string();
|
||||
result_obj.insert("lastDirectory".to_string(), json!(expanded));
|
||||
}
|
||||
}
|
||||
if let Some(Value::String(s)) = obj.get("homeDirectory") {
|
||||
if !s.is_empty() {
|
||||
result_obj.insert("homeDirectory".to_string(), json!(s));
|
||||
let expanded = expand_tilde_path(s).to_string_lossy().to_string();
|
||||
result_obj.insert("homeDirectory".to_string(), json!(expanded));
|
||||
}
|
||||
}
|
||||
if let Some(Value::String(s)) = obj.get("uiFont") {
|
||||
|
||||
@@ -7,6 +7,7 @@ mod session_activity;
|
||||
mod opencode_config;
|
||||
mod opencode_manager;
|
||||
mod window_state;
|
||||
mod path_utils;
|
||||
|
||||
use std::{collections::HashMap, path::PathBuf, sync::Arc, time::{Duration, Instant}};
|
||||
|
||||
@@ -63,6 +64,7 @@ use tokio::{
|
||||
};
|
||||
use tower_http::cors::CorsLayer;
|
||||
use window_state::{load_window_state, persist_window_state, WindowStateManager};
|
||||
use path_utils::expand_tilde_path;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
@@ -1485,7 +1487,10 @@ async fn change_directory_handler(
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
let resolved_path = PathBuf::from(requested_path);
|
||||
let mut resolved_path = expand_tilde_path(requested_path);
|
||||
if !resolved_path.is_absolute() {
|
||||
resolved_path = state.opencode.get_working_directory().join(resolved_path);
|
||||
}
|
||||
|
||||
// Validate directory exists and is accessible
|
||||
match fs::metadata(&resolved_path).await {
|
||||
@@ -1507,6 +1512,10 @@ async fn change_directory_handler(
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(canonicalized) = fs::canonicalize(&resolved_path).await {
|
||||
resolved_path = canonicalized;
|
||||
}
|
||||
|
||||
let current_dir = state.opencode.get_working_directory();
|
||||
let is_running = state.opencode.current_port().is_some();
|
||||
|
||||
@@ -1682,7 +1691,7 @@ impl SettingsStore {
|
||||
.and_then(|value| value.as_str())
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(PathBuf::from);
|
||||
.map(expand_tilde_path);
|
||||
Ok(candidate)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn expand_tilde_path(value: &str) -> PathBuf {
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
return PathBuf::from(trimmed);
|
||||
}
|
||||
|
||||
let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/"));
|
||||
|
||||
if trimmed == "~" {
|
||||
return home;
|
||||
}
|
||||
|
||||
if trimmed.starts_with("~/") || trimmed.starts_with("~\\") {
|
||||
return home.join(&trimmed[2..]);
|
||||
}
|
||||
|
||||
PathBuf::from(trimmed)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user