Skip to content

Commit c459cbf

Browse files
author
ksndmplb
committed
Windows: fix open-in editor + worktree/terminal cleanup
1 parent 67e3f6a commit c459cbf

File tree

17 files changed

+1150
-138
lines changed

17 files changed

+1150
-138
lines changed

src-tauri/src/backend/app_server.rs

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::io::ErrorKind;
66
use std::path::{Path, PathBuf};
77
use std::sync::atomic::{AtomicU64, Ordering};
88
use std::sync::Arc;
9+
use std::sync::OnceLock;
910
use std::time::Duration;
1011

1112
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
@@ -357,6 +358,55 @@ fn resolve_windows_command_path(
357358
None
358359
}
359360

361+
#[cfg(windows)]
362+
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
363+
struct WindowsCommandCacheKey {
364+
command: String,
365+
path_env: Option<String>,
366+
cwd: Option<String>,
367+
}
368+
369+
#[cfg(windows)]
370+
static WINDOWS_COMMAND_PATH_CACHE: OnceLock<
371+
std::sync::Mutex<HashMap<WindowsCommandCacheKey, PathBuf>>,
372+
> = OnceLock::new();
373+
374+
#[cfg(windows)]
375+
fn resolve_windows_command_path_cached(
376+
command: &str,
377+
path_env: Option<&OsString>,
378+
cwd_for_resolution: Option<&Path>,
379+
) -> Option<PathBuf> {
380+
let trimmed = command.trim();
381+
if trimmed.is_empty() {
382+
return None;
383+
}
384+
385+
let cwd_key = if trimmed.contains('\\') || trimmed.contains('/') {
386+
cwd_for_resolution.map(|cwd| cwd.to_string_lossy().to_string())
387+
} else {
388+
None
389+
};
390+
let key = WindowsCommandCacheKey {
391+
command: trimmed.to_string(),
392+
path_env: path_env.map(|value| value.to_string_lossy().to_string()),
393+
cwd: cwd_key,
394+
};
395+
396+
let cache = WINDOWS_COMMAND_PATH_CACHE.get_or_init(|| std::sync::Mutex::new(HashMap::new()));
397+
if let Ok(guard) = cache.lock() {
398+
if let Some(hit) = guard.get(&key) {
399+
return Some(hit.clone());
400+
}
401+
}
402+
403+
let resolved = resolve_windows_command_path(trimmed, path_env, cwd_for_resolution)?;
404+
if let Ok(mut guard) = cache.lock() {
405+
guard.insert(key, resolved.clone());
406+
}
407+
Some(resolved)
408+
}
409+
360410
pub(crate) fn build_codex_command_with_bin(
361411
codex_bin: Option<String>,
362412
cwd_for_resolution: Option<&Path>,
@@ -378,7 +428,7 @@ pub(crate) fn build_codex_command_with_bin(
378428
);
379429

380430
#[cfg(windows)]
381-
let bin = resolve_windows_command_path(raw_bin, path_env.as_ref(), cwd_for_resolution)
431+
let bin = resolve_windows_command_path_cached(raw_bin, path_env.as_ref(), cwd_for_resolution)
382432
.map(|path| path.to_string_lossy().to_string())
383433
.unwrap_or_else(|| raw_bin.to_string());
384434

@@ -506,8 +556,17 @@ pub(crate) async fn spawn_workspace_session<E: EventSink>(
506556
.clone()
507557
.filter(|value| !value.trim().is_empty())
508558
.or(default_codex_bin);
559+
let codex_bin_for_check = codex_bin.clone();
509560
let workspace_dir = Path::new(&entry.path);
510-
let _ = check_codex_installation(codex_bin.clone(), Some(workspace_dir)).await?;
561+
if !workspace_dir.is_dir() {
562+
return Err(format!(
563+
"Workspace folder does not exist: `{}`",
564+
workspace_dir.display()
565+
));
566+
}
567+
if !cfg!(windows) {
568+
let _ = check_codex_installation(codex_bin_for_check.clone(), Some(workspace_dir)).await?;
569+
}
511570

512571
let mut command = build_codex_command_with_bin(codex_bin, Some(workspace_dir));
513572
apply_codex_args(&mut command, codex_args.as_deref())?;
@@ -520,7 +579,19 @@ pub(crate) async fn spawn_workspace_session<E: EventSink>(
520579
command.stdout(std::process::Stdio::piped());
521580
command.stderr(std::process::Stdio::piped());
522581

523-
let mut child = command.spawn().map_err(|e| e.to_string())?;
582+
let mut child = match command.spawn() {
583+
Ok(child) => child,
584+
Err(err) => {
585+
if err.kind() == ErrorKind::NotFound && workspace_dir.is_dir() {
586+
if let Err(error) =
587+
check_codex_installation(codex_bin_for_check.clone(), Some(workspace_dir)).await
588+
{
589+
return Err(error);
590+
}
591+
}
592+
return Err(err.to_string());
593+
}
594+
};
524595
let stdin = child.stdin.take().ok_or("missing stdin")?;
525596
let stdout = child.stdout.take().ok_or("missing stdout")?;
526597
let stderr = child.stderr.take().ok_or("missing stderr")?;

src-tauri/src/bin/codex_monitor_daemon.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ use ignore::WalkBuilder;
7070
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
7171
use tokio::net::{TcpListener, TcpStream};
7272
use tokio::sync::{broadcast, mpsc, Mutex};
73+
use tokio::sync::{broadcast, mpsc, Mutex, Notify};
7374

7475
use backend::app_server::{
7576
spawn_workspace_session, WorkspaceSession,
@@ -135,6 +136,7 @@ struct DaemonState {
135136
data_dir: PathBuf,
136137
workspaces: Mutex<HashMap<String, WorkspaceEntry>>,
137138
sessions: Mutex<HashMap<String, Arc<WorkspaceSession>>>,
139+
workspace_connect_notifiers: Mutex<HashMap<String, Arc<Notify>>>,
138140
storage_path: PathBuf,
139141
settings_path: PathBuf,
140142
app_settings: Mutex<AppSettings>,
@@ -158,6 +160,7 @@ impl DaemonState {
158160
data_dir: config.data_dir.clone(),
159161
workspaces: Mutex::new(workspaces),
160162
sessions: Mutex::new(HashMap::new()),
163+
workspace_connect_notifiers: Mutex::new(HashMap::new()),
161164
storage_path,
162165
settings_path,
163166
app_settings: Mutex::new(app_settings),
@@ -267,7 +270,7 @@ impl DaemonState {
267270
},
268271
|error| git_core::is_missing_worktree_error(error),
269272
|path| {
270-
std::fs::remove_dir_all(path)
273+
workspaces_core::remove_dir_all_best_effort(path)
271274
.map_err(|err| format!("Failed to remove worktree folder: {err}"))
272275
},
273276
true,
@@ -287,7 +290,7 @@ impl DaemonState {
287290
},
288291
|error| git_core::is_missing_worktree_error(error),
289292
|path| {
290-
std::fs::remove_dir_all(path)
293+
workspaces_core::remove_dir_all_best_effort(path)
291294
.map_err(|err| format!("Failed to remove worktree folder: {err}"))
292295
},
293296
)
@@ -427,18 +430,12 @@ impl DaemonState {
427430
}
428431

429432
async fn connect_workspace(&self, id: String, client_version: String) -> Result<(), String> {
430-
{
431-
let sessions = self.sessions.lock().await;
432-
if sessions.contains_key(&id) {
433-
return Ok(());
434-
}
435-
}
436-
437433
let client_version = client_version.clone();
438434
workspaces_core::connect_workspace_core(
439435
id,
440436
&self.workspaces,
441437
&self.sessions,
438+
&self.workspace_connect_notifiers,
442439
&self.app_settings,
443440
move |entry, default_bin, codex_args, codex_home| {
444441
spawn_with_client(

src-tauri/src/git/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,15 @@ pub(crate) async fn get_git_status(
423423
drop(workspaces);
424424

425425
let repo_root = resolve_git_root(&entry)?;
426-
let repo = Repository::open(&repo_root).map_err(|e| e.to_string())?;
426+
if !repo_root.is_dir() {
427+
return Err(format!("Git root not found: {}", repo_root.display()));
428+
}
429+
let repo = Repository::discover(&repo_root).map_err(|e| {
430+
format!(
431+
"Git repository not found at `{}`: {e}",
432+
repo_root.display()
433+
)
434+
})?;
427435

428436
let branch_name = repo
429437
.head()

0 commit comments

Comments
 (0)