From 94e0c704f8606dc0afe87aa0db023a3f3a17d0a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffrey=20Gu=C3=A9ret?= Date: Sun, 14 Jun 2026 04:27:45 +0200 Subject: [PATCH 1/2] feat: add a dedicated SCISSORS_EDITOR override Lets a caller point scissors at a specific editor or flags (e.g. a GUI editor with a dedicated window) without overriding $VISUAL/$EDITOR, which the rest of the shell relies on. Mirrors git's GIT_EDITOR. --- README.md | 17 +++++++++++++++++ src/lib.rs | 23 +++++++++++++++++------ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ba3fbad..319c4a8 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,23 @@ greyed-out comments while you edit and the body stays plain. The cut is located by the `>8` marker, so the footer is detected and stripped even if its `#` prefix gets altered. +## Choosing the editor + +`scissors` resolves the editor in order: `$SCISSORS_EDITOR`, then `$VISUAL`, +then `$EDITOR`, then `vi`. `SCISSORS_EDITOR` is a dedicated override, like git's +`GIT_EDITOR`: point `scissors` at a specific editor or flags without touching +`$VISUAL`/`$EDITOR`, which the rest of your shell relies on. + +A GUI editor needs a blocking flag; a dedicated window also makes the review +easy to find when many windows are open: + +```bash +export SCISSORS_EDITOR="code --wait --new-window" # VS Code, dedicated window +``` + +Without a wait flag the editor returns immediately and `scissors` reports a +silent failure (exit 2). + ## File mode Pass a file to edit it in place, like `$EDITOR `: diff --git a/src/lib.rs b/src/lib.rs index f4e1917..28fe616 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,7 +80,7 @@ pub enum Outcome { /// and report its path so the user can recover their work. #[derive(Debug, Error)] pub enum ScissorsError { - #[error("no editor available ($VISUAL, $EDITOR unset and editor not found)")] + #[error("no editor available ($SCISSORS_EDITOR, $VISUAL, $EDITOR unset and editor not found)")] NoEditor, #[error("editor exited with code {code}; draft at {draft_path}")] @@ -114,7 +114,7 @@ pub enum FileOutcome { /// failure. #[derive(Debug, Error)] pub enum FileError { - #[error("no editor available ($VISUAL, $EDITOR unset and editor not found)")] + #[error("no editor available ($SCISSORS_EDITOR, $VISUAL, $EDITOR unset and editor not found)")] NoEditor, #[error("cannot read {}: {source}", path.display())] Read { path: PathBuf, source: io::Error }, @@ -197,14 +197,14 @@ fn build_draft(content: &str, context: Option<&str>) -> String { out } -/// Resolve the editor command, honouring $VISUAL > $EDITOR > `vi`. +/// Resolve the editor command, honouring $SCISSORS_EDITOR > $VISUAL > $EDITOR > `vi`. /// Returns the command split into program + args (e.g. `["code", "--wait"]`). /// /// # Errors -/// [`ScissorsError::Io`] if `$VISUAL`/`$EDITOR` contains unbalanced quotes that +/// [`ScissorsError::Io`] if `$SCISSORS_EDITOR`/`$VISUAL`/`$EDITOR` contains unbalanced quotes that /// `shell-words` cannot split. pub fn resolve_editor() -> Result, ScissorsError> { - for var in ["VISUAL", "EDITOR"] { + for var in ["SCISSORS_EDITOR", "VISUAL", "EDITOR"] { if let Ok(val) = std::env::var(var) { let trimmed = val.trim(); if !trimmed.is_empty() { @@ -285,7 +285,7 @@ fn keep_draft(dir: TempDir, path: PathBuf) -> PathBuf { /// /// # Errors /// All error cases preserve the draft file and report its path. -/// - [`ScissorsError::NoEditor`] -- `$VISUAL`/`$EDITOR` unset/empty and `vi` not found. +/// - [`ScissorsError::NoEditor`] -- `$SCISSORS_EDITOR`/`$VISUAL`/`$EDITOR` unset/empty and `vi` not found. /// - [`ScissorsError::EditorFailed`] -- the editor exited non-zero. /// - [`ScissorsError::SilentFailure`] -- the editor returned in under /// `500 ms` with no change (likely never opened, e.g. a GUI editor without a @@ -496,9 +496,20 @@ mod editor_tests { use super::*; use serial_test::serial; + #[test] + #[serial] + fn scissors_editor_takes_priority() { + std::env::set_var("SCISSORS_EDITOR", "dedicated --new-window"); + std::env::set_var("VISUAL", "myvisual --wait"); + std::env::set_var("EDITOR", "myeditor"); + assert_eq!(resolve_editor().unwrap(), vec!["dedicated", "--new-window"]); + std::env::remove_var("SCISSORS_EDITOR"); + } + #[test] #[serial] fn visual_takes_priority() { + std::env::remove_var("SCISSORS_EDITOR"); std::env::set_var("VISUAL", "myvisual --wait"); std::env::set_var("EDITOR", "myeditor"); assert_eq!(resolve_editor().unwrap(), vec!["myvisual", "--wait"]); From c1b0528f302e3dbfd2cb47c95d7d90907aa18051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffrey=20Gu=C3=A9ret?= Date: Mon, 15 Jun 2026 12:22:53 +0200 Subject: [PATCH 2/2] docs: drop the duplicate editor-resolution section --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index 319c4a8..ef5b3f9 100644 --- a/README.md +++ b/README.md @@ -128,16 +128,6 @@ omitting the argument does the same. In stdin mode, on abort or error the draft tempfile is preserved and its path is printed to stderr. In file mode, on abort or error the file is left unchanged (the edit happens in a sidecar that is discarded). -## Editor resolution - -`scissors` uses `$VISUAL`, then `$EDITOR`, then falls back to `vi`. For GUI -editors, set a blocking flag so the process waits for you to close the file: - -```bash -export VISUAL="code --wait" -export VISUAL="subl -w" -``` - ## Non-interactive / headless use `scissors` is **fail-closed**: without an editor it errors (exit 2) rather than