Skip to content

shell: nushell integration scripts#4630

Open
sim590 wants to merge 102 commits intojunegunn:masterfrom
sim590:nushell-integration
Open

shell: nushell integration scripts#4630
sim590 wants to merge 102 commits intojunegunn:masterfrom
sim590:nushell-integration

Conversation

@sim590
Copy link
Copy Markdown

@sim590 sim590 commented Dec 9, 2025

  • Keybindings alt_c, ctrl_r and ctrl_t
  • Completion functions:
    • base implementation for ** completion
    • extra completion functionalities for programs such as:
      • pass
      • ssh, scp, sftp, telnet
      • kill
      • cd, pushd, rmdir

Closes: #4122

imsys and others added 30 commits May 4, 2025 04:58
Fixes some path issues. There are still some other things to fix
Pass~[1] is a command line password storage utility. Possible queries
are deduced from the file GPG hierarchy under ~/.password-store.

[1]: https://www.passwordstore.org/
Not sure why it doesn't work in this context, but if those are not
removed, the completion process doesn't substitute the selected value
to '**'.
fix typo  shell/key-bindinds.nu → shell/key-bindings.nu
Comment thread README.md Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class Nushell integration to fzf, including keybindings, **<TAB> fuzzy completion via Nushell’s external completer mechanism, installation/uninstallation support, and CI/test coverage to prevent regressions.

Changes:

  • Add fzf --nushell flag that prints Nushell integration scripts (keybindings + completion)
  • Add Nushell install/uninstall handling (autoload fzf.nu) and update docs
  • Add Nushell integration tests + Linux CI dependency install for nushell

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
main.go Embeds and prints Nushell integration scripts when --nushell is used
src/options.go Adds --nushell option parsing and Options.Nushell flag
shell/key-bindings.nu Implements CTRL-T / CTRL-R / ALT-C bindings for Nushell
shell/completion.nu Implements ** completion via Nushell external completer + command-specific completers
install Adds Nushell to supported shells and generates autoload fzf.nu
uninstall Removes Nushell autoload file during uninstall
README.md Documents Nushell integration setup and limitations
test/lib/common.rb Adds Nushell shell harness + tmux prep behavior
test/test_shell_integration.rb Adds Nushell-specific integration tests (bindings + completion smoke test)
.github/workflows/linux.yml Installs nushell in CI to run Nushell tests
typos.toml Adds Slq to allowed words (for pacman -Slq)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread shell/completion.nu
Comment thread README.md Outdated
Comment thread README.md Outdated
sim590 added 2 commits April 22, 2026 10:49
Replace the confusing 'run this in your regular shell' comment
with a pure Nushell command using 'save -f'.
The note references fzf 0.48.0 which only introduced --bash, --zsh,
and --fish. The --nushell flag will be available in a later release.
Comment thread README.md
Use 'recent versions of fzf' instead of a specific version number
as suggested by junegunn. Also add mkdir before save to handle the
case where the autoload directory does not exist yet.
@sim590 sim590 force-pushed the nushell-integration branch 2 times, most recently from f154fa3 to adbd8e7 Compare April 23, 2026 07:04
Comment thread test/test_shell_integration.rb Outdated
Comment thread test/test_shell_integration.rb Outdated
@junegunn
Copy link
Copy Markdown
Owner

junegunn commented Apr 23, 2026

Let me just randomly post issues I noticed in the comments.

~/github/fzf> pass **Error executing data_gen closure. Closure code: closure_635. Actual error: {msg: No matches found for Expand("~/.password-store/**/*.gpg"), debug: Generic(GenericError { code: "nu::shell::error", error: "No matches found for Expand(\"~/.password-store/**/*.gpg\")", msg: "Pattern, file or folder not found", site: Span(Span[165878..165904]), help: Some("no matches found"), inner: [], source: None }), raw: Generic(GenericError { code: "nu::shell::error", error: "No matches found for Expand(\"~/.password-store/**/*.gpg\")", msg: "Pattern, file or folder not found", site: Span(Span[165878..165904]), help: Some("no matches found"), inner: [], source: None }), rendered: Error: nu::shell::error

                                              × No matches found for Expand("~/.password-store/**/*.gpg")
                                                                                                              ╭─[/Users/***/Library/Application Support/nushell/autoload/fzf.nu:528:8]
                                                                              527 │   let passwordstore_files_gen_closure = {||
                528 │     ls ~/.password-store/**/*.gpg | get name | each {$in | str replace -r '^.*?\.password-store/(.*).gpg' '${1}' }
                             ·        ─────────────┬────────────
                                                                     ·                     ╰── Pattern, file or folder not found
                 529 │   }
                               ╰────
                                      help: no matches found
                                                            , json: {"msg":"No matches found for Expand(\"~/.password-store/**/*.gpg\")","labels":[{"text":"Pattern, file or folder not found","span":{"start":165878,"end":165904}}],"code":"nu::shell::error","url":null,"help":"no matches found","inner":[]}}

@junegunn
Copy link
Copy Markdown
Owner

Fuzzy completion does not respect --tmux option in my $FZF_DEFAULT_OPTS. CTRL-T does.

export FZF_DEFAULT_OPTS='--tmux 90%,70%'

Comment thread README.md Outdated
@junegunn
Copy link
Copy Markdown
Owner

Multi-selection (tab) in fuzzy completion doesn't work properly.

~/github/fzf> vim .editorconfig
::: .gitignore
::: .goreleaser.yml

It works for ^kill.

sim590 added 6 commits April 23, 2026 16:43
Wrap the ls glob in a try/catch so that pass completion
returns an empty list instead of a verbose error when
~/.password-store does not exist.
When multiple files are selected with Tab in fzf, join them
with spaces so the completer returns a single result instead
of separate candidates displayed with ::: separators.
Options from FZF_DEFAULT_OPTS are already parsed and passed as
CLI arguments. Without clearing the env var, fzf reads it again,
causing --height (CLI) to override --tmux (env) since CLI args
take precedence over FZF_DEFAULT_OPTS.
Rewrite __fzf_defaults_nu to return a string (like bash's
__fzf_defaults) instead of a list, and pass it via FZF_DEFAULT_OPTS
with-env. This ensures --height has a lower index than --tmux from
the user's FZF_DEFAULT_OPTS, so --tmux wins when both are present.

Also fix a critical bug where the non-existent is-string command
silently swallowed all options from FZF_DEFAULT_OPTS.
These tests were identical to the ones inherited from TestShell
via include, so the redefinitions were unnecessary.
@sim590
Copy link
Copy Markdown
Author

sim590 commented Apr 23, 2026

Thanks for testing! Here's a summary of the fixes:

  • pass crash: Fixed in 8057be90 — added try/catch so pass **<TAB> no longer errors when ~/.password-store doesn't exist.
  • Multi-selection: Fixed in e127c4ef — selected paths are now joined with lines | str join ' ' instead of being returned as separate entries.
  • --tmux in FZF_DEFAULT_OPTS: Fixed in 0025d228 — __fzf_defaults_nu now builds FZF_DEFAULT_OPTS as a string (like bash's __fzf_defaults), so --tmux takes precedence over --height. The root cause was twofold: (1) is-string doesn't exist in Nushell, so a filter was silently dropping all FZF_DEFAULT_OPTS entries, and (2) --height was passed as a CLI argument (higher index) instead of through FZF_DEFAULT_OPTS (where --tmux could override it).

Remove test_ctrl_r which was identical to the inherited TestShell
version. Add comments explaining why the remaining overrides are
necessary:
- test_ctrl_t_unicode: uses ^echo (external) instead of echo
- test_file_completion: single-selection only (completer replaces token)
- test_ctrl_r_abort: skips quote characters that break Nushell
Comment thread test/test_shell_integration.rb Outdated
@junegunn
Copy link
Copy Markdown
Owner

Coding agents can miss subtle issues that only surface in hands-on use, which matters a lot for an interactive tool like fzf. Could you share a list of what you manually tested, ideally with screenshots?

Nushell passes ~/path to the external completer with the tilde
unexpanded. Expand it with 'path expand' before passing it to fzf
as --walker-root, and restore the tilde prefix in the result.
@sim590 sim590 force-pushed the nushell-integration branch from 54c2615 to b4e1992 Compare April 24, 2026 02:31
sim590 added 2 commits April 23, 2026 23:01
…letion

Cover multi-selection (Tab+Tab), single selection, and hidden
files. Previously only single selection was tested, which let
the multi-selection bug slip through unnoticed.
@sim590
Copy link
Copy Markdown
Author

sim590 commented Apr 24, 2026

Coding agents can miss subtle issues that only surface in hands-on use, which matters a lot for an interactive tool like fzf.

Yes. I agree.

Could you share a list of what you manually tested, ideally with screenshots?

I tested:

  • all keybindings
  • vim **<TAB>
    • with no prefix.
    • with absolute path prefix
    • with relative path prefix
    • with ~
    • with $env.HOME/**, but that doesn't work. I think that this is a limitation of Nushell's completion dipatcher
  • ^cd **<TAB>
  • ^kill **<TAB>
  • pass **<TAB>
  • pacman -S **<TAB>
  • pacman -Q **<TAB>

All of these work as expected. Here are some screenshots...

image image image image image image

vim and ctrl+t:

image image

Comment thread README.md
Comment on lines +846 to +848
- Custom completion extensibility (e.g. `_fzf_complete_COMMAND` in bash/zsh)
is not available. Custom completions are defined via a `match` statement
in `completion.nu`.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to implement this scheme? A hard-coded match statement feels quite limiting. It could easily turn into a "kitchen sink", with users continually requesting more commands to be added.

For what it's worth, I'm on macOS and don't use tools like pacman or pass, so having them in the default list feels a bit off. I imagine this concern will only grow as more items get added over time.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, the hard-coded match is not ideal. I investigated a dynamic dispatch approach that works within Nushell's constraints.

Users could define an $env.FZF_COMPLETERS record in their config.nu with closures keyed by command name. Each closure receives the prefix and returns a list of candidates, which are then fed to fzf:

# In config.nu
$env.FZF_COMPLETERS = {
    git: {|prefix| ["branch-main", "branch-dev", "branch-feature"]}
    pacman: {|prefix| ^pacman -Slq | lines}
}

In completion.nu, we'd check $env.FZF_COMPLETERS before falling through to the built-in defaults:

let user_completers = ($env.FZF_COMPLETERS? | default {})
if ($cmd_word in $user_completers) {
    # call _fzf_complete_nu with the user's closure
} else {
    match $cmd_word {
        "ssh" | ... => { ... }
        "kill"      => { ... }
        ...
    }
}

I tested this end-to-end: defining a completer in config.nu, triggering git **<TAB>, selecting from fzf, and confirming the result is inserted on the command line. It works.

With this in place, we could remove pacman, paru, and pass from the default match and keep only the universal ones (ssh, kill). The question is where to provide the removed implementations as examples for users -- perhaps in the README or in a separate example file. What do you think?

Limitation: The closures must be defined in config.nu, not in a separate autoload file, because Nushell doesn't propagate $env mutations from autoload files to later-loaded files. This is consistent with how users configure other Nushell settings.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this in place, we could remove pacman, paru, and pass from the default match and keep only the universal ones (ssh, kill). The question is where to provide the removed implementations as examples for users -- perhaps in the README or in a separate example file. What do you think?

Sounds good to me. How about putting them under shell/completion-examples.nu (any better name suggestion?) and add a link to the file in the README file?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci CI/GitHub Actions docs Documentation go Go code install Install/uninstall scripts shell Shell scripts test Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] nushell integration

6 participants