From b9fa29f6569d4416a0f9f8499298eec8cb1bfbe1 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Thu, 23 Jul 2026 07:25:14 +0200 Subject: [PATCH] fix: editor commands configured with flags never launched MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Command::new(&cfg.editor) treated the whole configured string as one binary name, so an editor set to e.g. 'code --wait' or 'vim -R' — a common config style — failed to spawn at all every single time. Added a shared launch_editor() that splits on whitespace (program + args) first, used by every one of the 6 call sites across detail/ and hub/. --- src/detail/actions.rs | 11 ++++++----- src/hub/mod.rs | 2 +- src/util/mod.rs | 13 +++++++++++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/detail/actions.rs b/src/detail/actions.rs index 8656cdc..aace7b1 100644 --- a/src/detail/actions.rs +++ b/src/detail/actions.rs @@ -7,6 +7,7 @@ use super::git::{build_history, checkout_branch, git_run, list_branches, push_branch}; use super::settings_io::{try_load_settings, try_make_provider}; use super::types::{RepoSettings, St}; +use crate::util::launch_editor; use anyhow::Result; /// Runs the action identified by `idx` (one of the numeric codes set on @@ -78,19 +79,19 @@ pub async fn exec_action(st: &mut St, idx: usize) -> Result<()> { // ── 106-109: open in editor (leave_tui is handled by run_detail_loop) ─ 106 => { let editor = st.cfg_editor.clone(); - if let Err(e) = std::process::Command::new(&editor).arg(&path).status() { + if let Err(e) = launch_editor(&editor, &path) { st.status_msg = Some(format!("Couldn't launch editor '{}': {}", editor, e)); } } 107 => { let editor = st.cfg_editor.clone(); - if let Err(e) = std::process::Command::new(&editor).arg(path.join(".gitignore")).status() { + if let Err(e) = launch_editor(&editor, &path.join(".gitignore")) { st.status_msg = Some(format!("Couldn't launch editor '{}': {}", editor, e)); } } 108 => { let editor = st.cfg_editor.clone(); - if let Err(e) = std::process::Command::new(&editor).arg(path.join("LICENSE")).status() { + if let Err(e) = launch_editor(&editor, &path.join("LICENSE")) { st.status_msg = Some(format!("Couldn't launch editor '{}': {}", editor, e)); } } @@ -99,7 +100,7 @@ pub async fn exec_action(st: &mut St, idx: usize) -> Result<()> { let readme = ["README.md", "README.rst", "README"].iter() .map(|f| path.join(f)).find(|p| p.exists()) .unwrap_or_else(|| path.join("README.md")); - if let Err(e) = std::process::Command::new(&editor).arg(&readme).status() { + if let Err(e) = launch_editor(&editor, &readme) { st.status_msg = Some(format!("Couldn't launch editor '{}': {}", editor, e)); } } @@ -108,7 +109,7 @@ pub async fn exec_action(st: &mut St, idx: usize) -> Result<()> { 110 => { if let Some(file) = st.pending_file.take() { let editor = st.cfg_editor.clone(); - if let Err(e) = std::process::Command::new(&editor).arg(&file).status() { + if let Err(e) = launch_editor(&editor, &file) { st.status_msg = Some(format!("Couldn't launch editor '{}': {}", editor, e)); } } diff --git a/src/hub/mod.rs b/src/hub/mod.rs index 778ad46..d42c863 100644 --- a/src/hub/mod.rs +++ b/src/hub/mod.rs @@ -246,7 +246,7 @@ pub async fn run_overview_tui( } PhaseResult::ExecOpenEditor(path) => { let editor = state.editor.clone(); - let _ = std::process::Command::new(&editor).arg(&path).status(); + let _ = crate::util::launch_editor(&editor, &path); } PhaseResult::None => {} } diff --git a/src/util/mod.rs b/src/util/mod.rs index b7d69fa..2fed29a 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -34,6 +34,19 @@ pub fn walk_files(root: &std::path::Path, cb: &mut F } } +/// Launches the configured editor against `target`, treating `editor` as a +/// whitespace-separated command line (program + flags) rather than a single +/// binary name — common editor configs like `"code --wait"` or `"vim -R"` +/// would otherwise fail to spawn entirely, since `Command::new` looks for a +/// literal executable named `"code --wait"`. +pub fn launch_editor(editor: &str, target: &std::path::Path) -> std::io::Result { + let mut parts = editor.split_whitespace(); + let Some(prog) = parts.next() else { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "empty editor command")); + }; + std::process::Command::new(prog).args(parts).arg(target).status() +} + /// Open a file, directory, or URL with the system default application. pub fn open_path(target: &str) { #[cfg(target_os = "macos")]