From 93129f04e22d50bd50eef3336a474e06989c0b0e Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sun, 23 Aug 2026 20:55:02 +0800 Subject: [PATCH] test(links): a path with spaces is one argument to the file command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit expand_file_command_template splits the template into tokens first and substitutes {path} inside a token, which is what keeps `/Users/me/My Notes/a.rs` together. Substituting first and splitting after reads like the same operation and is not — the editor is handed `/Users/me/My` and `Notes/a.rs` and opens neither. Nothing pinned that order, and every existing case used a path without a space, so the invariant that makes the design correct was never exercised. Paths with spaces are the common case on the platform this ships on: Application Support, My Documents. Three shapes: the plain `{path}`, a compound `{path}:{line}:{column}` where the glue would part company with it too, and a template carrying spaces of its own, which are still separators — only the substituted value is protected. Checked by writing the refactor it guards against: the path comes back as three arguments. --- src/terminal/view.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/terminal/view.rs b/src/terminal/view.rs index 676d431a..70ddb336 100644 --- a/src/terminal/view.rs +++ b/src/terminal/view.rs @@ -7890,6 +7890,44 @@ mod tests { assert_eq!(argv, vec!["herdr", "/tmp/foo.rs"]); } + /// A path with a space in it is one argument, not two. + /// + /// The template is split into tokens *first* and `{path}` substituted + /// inside a token, which is what keeps `/Users/me/My Notes/a.rs` together. + /// Substituting first and splitting after reads like the same thing and is + /// not: the editor would be handed `/Users/me/My` and `Notes/a.rs` and + /// open neither. Nothing pinned the order, and paths with spaces are the + /// common case on macOS — `Application Support`, `My Documents`. + #[test] + fn a_path_with_spaces_stays_one_argument() { + let spacey = Path::new("/Users/me/My Notes/a file.rs"); + + let argv = expand_file_command_template("herdr edit {path}", spacey, None, None); + assert_eq!( + argv, + vec!["herdr", "edit", "/Users/me/My Notes/a file.rs"], + "the path was split on its own spaces" + ); + + // And inside a compound token, where the glue would be lost too. + let argv = expand_file_command_template( + "code --goto {path}:{line}:{column}", + spacey, + Some(42), + Some(7), + ); + assert_eq!( + argv, + vec!["code", "--goto", "/Users/me/My Notes/a file.rs:42:7"], + "the line and column parted company with the path" + ); + + // A template may legitimately carry several spaces of its own; those + // are still separators. Only the substituted value is protected. + let argv = expand_file_command_template("a b {path}", spacey, None, None); + assert_eq!(argv, vec!["a", "b", "/Users/me/My Notes/a file.rs"]); + } + /// #542's contract: a failed open is an `Err` the click site can toast, /// not a line in a logfile nobody is watching. #[test]