From fe04475d244f6ae1029df6b70e1d878ade93a3d8 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Fri, 11 Sep 2026 21:46:03 +0900 Subject: [PATCH] [nushell] Quote paths inserted by CTRL-T (#4919) - Selections were appended to the command line as-is, so a path with spaces became several arguments and syntax in a file name was left unquoted for the parser - Read the selection with --print0 and serialize each path with `to nuon`, so each one stays a single string literal - Skip the insertion entirely when nothing was selected --- CHANGELOG.md | 2 ++ shell/key-bindings.nu | 18 +++++++++++++---- test/test_shell_integration.rb | 37 ++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b13ee9..2c44c418 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ CHANGELOG - fish: - Fixed custom CTRL-T command not using the prefixed target directory in some cases (#4498) - Optimized description alignment of completion items (#4910) +- nushell: Fixed CTRL-T inserting the selected paths unquoted + - p4p3r (@P4P3R-HAK) reported the security vulnerability and suggested the fix 0.74.3 ------ diff --git a/shell/key-bindings.nu b/shell/key-bindings.nu index 08594dae..fef571cb 100644 --- a/shell/key-bindings.nu +++ b/shell/key-bindings.nu @@ -138,7 +138,7 @@ let ctrl_t = { { send: executehostcommand cmd: " - let fzf_opts = (__fzf_defaults '--reverse --walker=file,dir,follow,hidden --scheme=path' $'($env.FZF_CTRL_T_OPTS) -m'); + let fzf_opts = (__fzf_defaults '--reverse --walker=file,dir,follow,hidden --scheme=path' $'($env.FZF_CTRL_T_OPTS) -m --print0'); let fzfcmd = (__fzfcmd); let fzf_args = ($fzfcmd | skip 1); let ctrl_t_cmd = ($env.FZF_CTRL_T_COMMAND? | default null); @@ -149,9 +149,19 @@ let ctrl_t = { let sh_cmd = [$ctrl_t_cmd '|' $fzf_cmd_str] | str join ' '; with-env { FZF_DEFAULT_OPTS: $fzf_opts, FZF_DEFAULT_OPTS_FILE: '' } { ^sh -c $sh_cmd } }; - let result = ($result | str replace --all (char newline) ' ' | str trim); - commandline edit --append $result; - commandline set-cursor --end + # Serialize each path as a Nushell string literal, so that syntax + # in a file name is not evaluated when the line is executed. + let result = ( + $result + | split row (char nul) + | where {|path| $path != ''} + | each {|path| $path | to nuon} + | str join ' ' + ); + if ($result | is-not-empty) { + commandline edit --append $'($result) '; + commandline set-cursor --end + } " } ] diff --git a/test/test_shell_integration.rb b/test/test_shell_integration.rb index 6c9cfcc2..af195925 100644 --- a/test/test_shell_integration.rb +++ b/test/test_shell_integration.rb @@ -1273,6 +1273,43 @@ class TestNushell < TestBase FileUtils.rm_rf('/tmp/fzf-test') end + # Override: paths are inserted as Nushell string literals, so the + # selections appear quoted on the command line. + def test_ctrl_t + set_var('FZF_CTRL_T_COMMAND', 'seq 100') + + tmux.prepare + tmux.send_keys 'C-t' + tmux.until { |lines| assert_equal 100, lines.match_count } + tmux.send_keys :Tab, :Tab, :Tab + tmux.until { |lines| assert lines.any_include?(' (3)') } + tmux.send_keys :Enter + tmux.until { |lines| assert lines.any_include?('"1" "2" "3"') } + tmux.send_keys 'C-c' + end + + # A path is inserted as a Nushell string literal, so that syntax in a file + # name is not evaluated and each path stays a single argument. + def test_ctrl_t_quoting + marker = "#{tempname}-marker" + FileUtils.rm_f(marker) + writelines(["fzf-inject$(touch #{marker}).txt", 'fzf-inject space.txt']) + set_var('FZF_CTRL_T_COMMAND', "cat #{tempname}") + + tmux.prepare + tmux.send_keys '^printf "%s\n" ', 'C-t' + tmux.until { |lines| assert_equal 2, lines.match_count } + tmux.send_keys :Tab, :Tab + tmux.until { |lines| assert_equal 2, lines.select_count } + tmux.send_keys :Enter + tmux.until { |lines| assert_includes lines[-1].to_s, '"fzf-inject$(touch' } + tmux.send_keys :Enter + tmux.until do |lines| + assert_equal ["fzf-inject$(touch #{marker}).txt", 'fzf-inject space.txt'], lines[-2..] + end + refute_path_exists marker + end + # Nushell does not support multiline command recall the same way # as bash/zsh/fish, so test_ctrl_r_multiline is omitted.