shell: nushell integration scripts (#4630)

Co-authored-by: imsys <911254+imsys@users.noreply.github.com>
Co-authored-by: Grzegorz Zalewski (Greg) <12560152+zalewskigrzegorz@users.noreply.github.com>
Co-authored-by: René Jochum <rene@jochum.dev>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Simon Désaulniers
2026-05-23 10:13:46 -04:00
committed by GitHub
parent ccedd064ca
commit 290b18d9fe
13 changed files with 1040 additions and 16 deletions
+66 -8
View File
@@ -78,6 +78,38 @@ class Shell
"rm -f ~/.local/share/fish/fzf_test_history; XDG_CONFIG_HOME=#{confdir} fish"
end
end
def nushell
@nushell ||=
begin
xdg_home = '/tmp/fzf-nushell-xdg'
config_dir = "#{xdg_home}/nushell"
FileUtils.rm_rf(xdg_home)
FileUtils.mkdir_p(config_dir)
# Write env.nu to set up PATH and unset FZF variables
File.open("#{config_dir}/env.nu", 'w') do |f|
f.puts "$env.PATH = ($env.PATH | split row (char esep) | prepend '#{BASE}/bin')"
UNSETS.each do |var|
f.puts "hide-env -i #{var}"
end
f.puts "$env.FZF_DEFAULT_OPTS = \"--no-scrollbar --pointer '>' --marker '>'\""
f.puts '$env.config = ($env.config | upsert history { file_format: "plaintext", max_size: 100 })'
end
# Write config.nu with minimal prompt
File.open("#{config_dir}/config.nu", 'w') do |f|
f.puts '$env.PROMPT_COMMAND = {|| "" }'
f.puts '$env.PROMPT_INDICATOR = ""'
f.puts '$env.PROMPT_COMMAND_RIGHT = {|| "" }'
f.puts '$env.config = ($env.config | upsert show_banner false)'
f.puts "source #{BASE}/shell/key-bindings.nu"
f.puts "source #{BASE}/shell/completion.nu"
end
"unset #{UNSETS.join(' ')}; env XDG_CONFIG_HOME=#{xdg_home} XDG_DATA_HOME=#{xdg_home}/../fzf-nushell-data nu --config #{config_dir}/config.nu --env-config #{config_dir}/env.nu"
end
end
end
end
@@ -85,12 +117,30 @@ class Tmux
attr_reader :win
def initialize(shell = :bash)
@shell = shell
@win = go(%W[new-window -d -P -F #I #{Shell.send(shell)}]).first
go(%W[set-window-option -t #{@win} pane-base-index 0])
return unless shell == :fish
send_keys 'function fish_prompt; end; clear', :Enter
self.until(&:empty?)
if shell == :fish
send_keys 'function fish_prompt; end; clear', :Enter
self.until(&:empty?)
elsif shell == :nushell
# Clear history from previous tests to avoid contamination
FileUtils.rm_f('/tmp/fzf-nushell-xdg/nushell/history.txt')
# Wait for nushell to be ready by polling with a marker command.
# We use 'print "fzf-ready"' and check for a line that is exactly
# 'fzf-ready' (not the command echo which includes 'print').
retries = 0
begin
send_keys 'print "fzf-ready"', :Enter
self.until { |lines| lines.any? { |l| l.strip == 'fzf-ready' } }
rescue Minitest::Assertion
retries += 1
raise if retries > 5
retry
end
send_keys 'clear', :Enter
self.until(&:empty?)
end
end
def kill
@@ -242,11 +292,19 @@ class Tmux
def prepare
tries = 0
begin
self.until(true) do |lines|
if @shell == :nushell
message = "Prepare[#{tries}]"
send_keys ' ', 'C-u', :Enter, message, :Left, :Right
sleep(0.15)
lines[-1] == message
send_keys 'C-u', 'C-l'
sleep 0.2
send_keys ' ', 'C-u', :Enter, message
self.until { |lines| lines[-1] == message }
else
self.until(true) do |lines|
message = "Prepare[#{tries}]"
send_keys ' ', 'C-u', :Enter, message, :Left, :Right
sleep(0.15)
lines[-1] == message
end
end
rescue Minitest::Assertion
(tries += 1) < 5 ? retry : raise
+120
View File
@@ -1103,3 +1103,123 @@ class TestFish < TestBase
end
end
end
class TestNushell < TestBase
include TestShell
def teardown
@tmux&.kill
end
def shell
:nushell
end
def set_var(name, val)
tmux.prepare
tmux.send_keys "$env.#{name} = '#{val}'", :Enter
tmux.prepare
end
def unset_var(name)
tmux.prepare
tmux.send_keys "hide-env -i #{name}", :Enter
tmux.prepare
end
def new_shell
tmux.send_keys 'FZF_TMUX=1 nu', :Enter
tmux.prepare
end
# Override: Nushell's builtin `echo` outputs structured data, so we need
# `^echo` (external echo) for plain text output on the command line.
def test_ctrl_t_unicode
writelines(['fzf-unicode 테스트1', 'fzf-unicode 테스트2'])
set_var('FZF_CTRL_T_COMMAND', "cat #{tempname}")
tmux.prepare
tmux.send_keys '^echo ', 'C-t'
tmux.until { |lines| assert_equal 2, lines.match_count }
tmux.send_keys 'fzf-unicode'
tmux.until { |lines| assert_equal 2, lines.match_count }
tmux.send_keys '1'
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :Tab
tmux.until { |lines| assert_equal 1, lines.select_count }
tmux.send_keys :BSpace
tmux.until { |lines| assert_equal 2, lines.match_count }
tmux.send_keys '2'
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :Tab
tmux.until { |lines| assert_equal 2, lines.select_count }
tmux.send_keys :Enter
tmux.until { |lines| assert_match(/\^echo .*fzf-unicode.*1.* .*fzf-unicode.*2/, lines.join) }
tmux.send_keys :Enter
tmux.until { |lines| assert_equal 'fzf-unicode 테스트1 fzf-unicode 테스트2', lines[-1] }
end
# Override: Nushell's external completer replaces the entire token,
# so we use assert_includes instead of assert_equal for the result.
# ~USERNAME expansion and backslash-escaped spaces are not applicable.
def test_file_completion
FileUtils.mkdir_p('/tmp/fzf-test')
(1..100).each { |i| FileUtils.touch("/tmp/fzf-test/#{i}") }
tmux.prepare
# Multi-selection
tmux.send_keys "cat /tmp/fzf-test/10#{trigger}", :Tab
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(true) do |lines|
assert_includes lines[-1].to_s, '/tmp/fzf-test/10'
assert_includes lines[-1].to_s, '/tmp/fzf-test/100'
end
# Single selection
tmux.prepare
tmux.send_keys "cat /tmp/fzf-test/10#{trigger}", :Tab
tmux.until { |lines| assert_equal 2, lines.match_count }
tmux.send_keys '0'
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :Enter
tmux.until(true) do |lines|
assert_includes lines[-1].to_s, '/tmp/fzf-test/100'
end
# Should include hidden files
(1..100).each { |i| FileUtils.touch("/tmp/fzf-test/.hidden-#{i}") }
tmux.prepare
tmux.send_keys "cat /tmp/fzf-test/hidden#{trigger}", :Tab
tmux.until(true) do |lines|
assert_equal 100, lines.match_count
assert lines.any_include?('/tmp/fzf-test/.hidden-')
end
tmux.send_keys :Enter
ensure
FileUtils.rm_rf('/tmp/fzf-test')
end
# Nushell does not support multiline command recall the same way
# as bash/zsh/fish, so test_ctrl_r_multiline is omitted.
# Override: only test with 'foo' -- single and double quotes cause
# issues in Nushell's line editor.
def test_ctrl_r_abort
%w[foo].each do |query|
tmux.prepare
tmux.send_keys :Enter, query
tmux.until { |lines| assert lines[-1]&.start_with?(query) }
tmux.send_keys 'C-r'
tmux.until { |lines| assert_equal "> #{query}", lines[-1] }
tmux.send_keys 'C-g'
tmux.until { |lines| assert lines[-1]&.start_with?(query) }
end
end
end