mirror of
https://github.com/junegunn/fzf.git
synced 2026-05-25 01:38:48 +08:00
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:
committed by
GitHub
parent
ccedd064ca
commit
290b18d9fe
@@ -33,7 +33,12 @@ jobs:
|
|||||||
ruby-version: 3.4.6
|
ruby-version: 3.4.6
|
||||||
|
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
run: sudo apt-get install --yes zsh fish tmux shfmt
|
run: |
|
||||||
|
sudo install -d -m 0755 /etc/apt/keyrings
|
||||||
|
wget -qO- https://apt.fury.io/nushell/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/fury-nushell.gpg
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/fury-nushell.gpg] https://apt.fury.io/nushell/ /" | sudo tee /etc/apt/sources.list.d/fury-nushell.list
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install --yes zsh fish tmux shfmt nushell
|
||||||
|
|
||||||
- name: Install Ruby gems
|
- name: Install Ruby gems
|
||||||
run: bundle install
|
run: bundle install
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ Highlights
|
|||||||
- **Portable** -- Distributed as a single binary for easy installation
|
- **Portable** -- Distributed as a single binary for easy installation
|
||||||
- **Fast** -- Optimized to process millions of items instantly
|
- **Fast** -- Optimized to process millions of items instantly
|
||||||
- **Versatile** -- Fully customizable through an event-action binding mechanism
|
- **Versatile** -- Fully customizable through an event-action binding mechanism
|
||||||
- **All-inclusive** -- Comes with integrations for Bash, Zsh, Fish, Vim, and Neovim
|
- **All-inclusive** -- Comes with integrations for Bash, Zsh, Fish, Nushell, Vim, and Neovim
|
||||||
|
|
||||||
Table of Contents
|
Table of Contents
|
||||||
-----------------
|
-----------------
|
||||||
@@ -81,6 +81,7 @@ Table of Contents
|
|||||||
* [Supported commands (bash)](#supported-commands-bash)
|
* [Supported commands (bash)](#supported-commands-bash)
|
||||||
* [Custom fuzzy completion](#custom-fuzzy-completion)
|
* [Custom fuzzy completion](#custom-fuzzy-completion)
|
||||||
* [Fuzzy completion for fish](#fuzzy-completion-for-fish)
|
* [Fuzzy completion for fish](#fuzzy-completion-for-fish)
|
||||||
|
* [Fuzzy completion for Nushell](#fuzzy-completion-for-nushell)
|
||||||
* [Vim plugin](#vim-plugin)
|
* [Vim plugin](#vim-plugin)
|
||||||
* [Advanced topics](#advanced-topics)
|
* [Advanced topics](#advanced-topics)
|
||||||
* [Customizing for different types of input](#customizing-for-different-types-of-input)
|
* [Customizing for different types of input](#customizing-for-different-types-of-input)
|
||||||
@@ -210,10 +211,18 @@ Add the following line to your shell configuration file.
|
|||||||
# Set up fzf key bindings
|
# Set up fzf key bindings
|
||||||
fzf --fish | source
|
fzf --fish | source
|
||||||
```
|
```
|
||||||
|
* Nushell -- Nushell does not support piping into `source`, so the install
|
||||||
|
script generates a file in the autoload directory. If you didn't use the
|
||||||
|
install script, you can manually set it up:
|
||||||
|
```nu
|
||||||
|
# Generate the integration script
|
||||||
|
mkdir ($nu.default-config-dir | path join "autoload")
|
||||||
|
fzf --nushell | save -f ($nu.default-config-dir | path join "autoload" "_fzf_integration.nu")
|
||||||
|
```
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> `--bash`, `--zsh`, and `--fish` options are only available in fzf 0.48.0 or
|
> `--bash`, `--zsh`, `--fish`, and `--nushell` options are only available in
|
||||||
> later. If you have an older version of fzf, or want finer control, you can
|
> recent versions of fzf. If you have an older version of fzf, or want finer control, you can
|
||||||
> source individual script files in the [/shell](/shell) directory. The
|
> source individual script files in the [/shell](/shell) directory. The
|
||||||
> location of the files may vary depending on the package manager you use.
|
> location of the files may vary depending on the package manager you use.
|
||||||
> Please refer to the package documentation for more information.
|
> Please refer to the package documentation for more information.
|
||||||
@@ -227,6 +236,8 @@ Add the following line to your shell configuration file.
|
|||||||
> * bash: `FZF_CTRL_R_COMMAND= FZF_ALT_C_COMMAND= eval "$(fzf --bash)"`
|
> * bash: `FZF_CTRL_R_COMMAND= FZF_ALT_C_COMMAND= eval "$(fzf --bash)"`
|
||||||
> * zsh: `FZF_CTRL_R_COMMAND= FZF_ALT_C_COMMAND= source <(fzf --zsh)`
|
> * zsh: `FZF_CTRL_R_COMMAND= FZF_ALT_C_COMMAND= source <(fzf --zsh)`
|
||||||
> * fish: `fzf --fish | FZF_CTRL_R_COMMAND= FZF_ALT_C_COMMAND= source`
|
> * fish: `fzf --fish | FZF_CTRL_R_COMMAND= FZF_ALT_C_COMMAND= source`
|
||||||
|
> * nushell: add to your `env.nu`:
|
||||||
|
> `$env.FZF_CTRL_R_COMMAND = ""; $env.FZF_ALT_C_COMMAND = ""`
|
||||||
>
|
>
|
||||||
> Setting the variables after sourcing the script will have no effect.
|
> Setting the variables after sourcing the script will have no effect.
|
||||||
|
|
||||||
@@ -506,7 +517,7 @@ Key bindings for command-line
|
|||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
By [setting up shell integration](#setting-up-shell-integration), you can use
|
By [setting up shell integration](#setting-up-shell-integration), you can use
|
||||||
the following key bindings in bash, zsh, and fish.
|
the following key bindings in bash, zsh, fish, and Nushell.
|
||||||
|
|
||||||
- `CTRL-T` - Paste the selected files and directories onto the command-line
|
- `CTRL-T` - Paste the selected files and directories onto the command-line
|
||||||
- The list is generated using `--walker file,dir,follow,hidden` option
|
- The list is generated using `--walker file,dir,follow,hidden` option
|
||||||
@@ -574,7 +585,7 @@ More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/C
|
|||||||
Fuzzy completion
|
Fuzzy completion
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
Shell integration also provides fuzzy completion for bash, zsh, and fish.
|
Shell integration also provides fuzzy completion for bash, zsh, fish, and Nushell.
|
||||||
|
|
||||||
### Files and directories
|
### Files and directories
|
||||||
|
|
||||||
@@ -823,6 +834,37 @@ function _fzf_post_complete_foo
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Fuzzy completion for Nushell
|
||||||
|
|
||||||
|
Fuzzy completion in Nushell works via the
|
||||||
|
[external completer](https://www.nushell.sh/cookbook/external_completers.html)
|
||||||
|
mechanism. There are some differences compared to bash and zsh:
|
||||||
|
|
||||||
|
- On Nushell >= 0.103.0, the external completer is no longer called for
|
||||||
|
built-in commands (e.g. `cd`, `ls`). Fuzzy completion with `**<TAB>` only
|
||||||
|
works for external commands.
|
||||||
|
- Custom completers can be defined via the `$env.FZF_COMPLETERS` record in
|
||||||
|
your `config.nu`. Each entry is a closure that receives the prefix and the
|
||||||
|
command spans, and returns either a list of candidate strings or a record
|
||||||
|
`{ candidates: [...], opts: [...] }` for custom fzf options:
|
||||||
|
```nu
|
||||||
|
$env.FZF_COMPLETERS = {
|
||||||
|
pacman: {|prefix, spans|
|
||||||
|
let sub = $spans | skip 1 | first
|
||||||
|
let candidates = (if ($sub =~ "-[SF]") { ^pacman -Slq | lines
|
||||||
|
} else if ($sub =~ "-[QR]") { ^pacman -Qq | lines
|
||||||
|
} else { [] })
|
||||||
|
{ candidates: $candidates, opts: ["--preview", "pacman -Si {}"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
See [shell/completion-examples.nu](shell/completion-examples.nu) for more
|
||||||
|
examples.
|
||||||
|
- The following environment variables are supported:
|
||||||
|
`FZF_COMPLETION_TRIGGER`, `FZF_COMPLETION_OPTS`,
|
||||||
|
`FZF_COMPLETION_PATH_OPTS`, `FZF_COMPLETION_DIR_OPTS`,
|
||||||
|
`FZF_COMPLETION_DIR_COMMANDS`.
|
||||||
|
|
||||||
Vim plugin
|
Vim plugin
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ version=0.72.0
|
|||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
shells="bash zsh fish"
|
shells="bash zsh fish nushell"
|
||||||
prefix='~/.fzf'
|
prefix='~/.fzf'
|
||||||
prefix_expand=~/.fzf
|
prefix_expand=~/.fzf
|
||||||
fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish
|
fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish
|
||||||
|
nushell_autoload_dir=${XDG_CONFIG_HOME:-$HOME/.config}/nushell/autoload
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
cat << EOF
|
cat << EOF
|
||||||
@@ -27,6 +28,7 @@ usage: $0 [OPTIONS]
|
|||||||
--no-bash Do not set up bash configuration
|
--no-bash Do not set up bash configuration
|
||||||
--no-zsh Do not set up zsh configuration
|
--no-zsh Do not set up zsh configuration
|
||||||
--no-fish Do not set up fish configuration
|
--no-fish Do not set up fish configuration
|
||||||
|
--no-nushell Do not set up nushell configuration
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +58,7 @@ for opt in "$@"; do
|
|||||||
--no-bash) shells=${shells/bash/} ;;
|
--no-bash) shells=${shells/bash/} ;;
|
||||||
--no-zsh) shells=${shells/zsh/} ;;
|
--no-zsh) shells=${shells/zsh/} ;;
|
||||||
--no-fish) shells=${shells/fish/} ;;
|
--no-fish) shells=${shells/fish/} ;;
|
||||||
|
--no-nushell) shells=${shells/nushell/} ;;
|
||||||
*)
|
*)
|
||||||
echo "unknown option: $opt"
|
echo "unknown option: $opt"
|
||||||
help
|
help
|
||||||
@@ -224,7 +227,9 @@ fi
|
|||||||
[[ $* =~ "--bin" ]] && exit 0
|
[[ $* =~ "--bin" ]] && exit 0
|
||||||
|
|
||||||
for s in $shells; do
|
for s in $shells; do
|
||||||
if ! command -v "$s" > /dev/null; then
|
bin=$s
|
||||||
|
[[ "$s" = nushell ]] && bin=nu
|
||||||
|
if ! command -v "$bin" > /dev/null; then
|
||||||
shells=${shells/$s/}
|
shells=${shells/$s/}
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -251,6 +256,7 @@ for shell in $shells; do
|
|||||||
fzf_completion="source \"$fzf_base/shell/completion.${shell}\""
|
fzf_completion="source \"$fzf_base/shell/completion.${shell}\""
|
||||||
fzf_key_bindings="source \"$fzf_base/shell/key-bindings.${shell}\""
|
fzf_key_bindings="source \"$fzf_base/shell/key-bindings.${shell}\""
|
||||||
[[ $shell == fish ]] && continue
|
[[ $shell == fish ]] && continue
|
||||||
|
[[ $shell == nushell ]] && continue
|
||||||
src=${prefix_expand}.${shell}
|
src=${prefix_expand}.${shell}
|
||||||
echo -n "Generate $src ... "
|
echo -n "Generate $src ... "
|
||||||
|
|
||||||
@@ -368,6 +374,7 @@ fi
|
|||||||
echo
|
echo
|
||||||
for shell in $shells; do
|
for shell in $shells; do
|
||||||
[[ $shell == fish ]] && continue
|
[[ $shell == fish ]] && continue
|
||||||
|
[[ $shell == nushell ]] && continue
|
||||||
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
|
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
|
||||||
append_line $update_config "[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" "$dest" "${prefix}.${shell}"
|
append_line $update_config "[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" "$dest" "${prefix}.${shell}"
|
||||||
done
|
done
|
||||||
@@ -436,6 +443,23 @@ if [[ $shells =~ fish ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "$shells" =~ nushell ]]; then
|
||||||
|
if [[ $key_bindings -eq 1 || $auto_completion -eq 1 ]]; then
|
||||||
|
echo "Setting up Nushell integration ..."
|
||||||
|
mkdir -p "$nushell_autoload_dir"
|
||||||
|
echo -n " Generate _fzf_integration.nu ... "
|
||||||
|
if [[ $key_bindings -eq 1 && $auto_completion -eq 1 ]]; then
|
||||||
|
"$fzf_base"/bin/fzf --nushell > "$nushell_autoload_dir/_fzf_integration.nu"
|
||||||
|
elif [[ $key_bindings -eq 1 ]]; then
|
||||||
|
cp "$fzf_base/shell/key-bindings.nu" "$nushell_autoload_dir/_fzf_integration.nu"
|
||||||
|
else
|
||||||
|
cp "$fzf_base/shell/completion.nu" "$nushell_autoload_dir/_fzf_integration.nu"
|
||||||
|
fi
|
||||||
|
echo "OK"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ $update_config -eq 1 ]; then
|
if [ $update_config -eq 1 ]; then
|
||||||
echo 'Finished. Restart your shell or reload config file.'
|
echo 'Finished. Restart your shell or reload config file.'
|
||||||
if [[ $shells =~ bash ]]; then
|
if [[ $shells =~ bash ]]; then
|
||||||
@@ -445,6 +469,7 @@ if [ $update_config -eq 1 ]; then
|
|||||||
fi
|
fi
|
||||||
[[ $shells =~ zsh ]] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
|
[[ $shells =~ zsh ]] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
|
||||||
[[ $shells =~ fish && $lno_func -ne 0 ]] && echo ' fish_user_key_bindings # fish'
|
[[ $shells =~ fish && $lno_func -ne 0 ]] && echo ' fish_user_key_bindings # fish'
|
||||||
|
[[ $shells =~ nushell ]] && echo ' # nushell: files are loaded automatically from autoload directory'
|
||||||
echo
|
echo
|
||||||
echo 'Use uninstall script to remove fzf.'
|
echo 'Use uninstall script to remove fzf.'
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ var zshCompletion []byte
|
|||||||
//go:embed shell/key-bindings.fish
|
//go:embed shell/key-bindings.fish
|
||||||
var fishKeyBindings []byte
|
var fishKeyBindings []byte
|
||||||
|
|
||||||
|
//go:embed shell/key-bindings.nu
|
||||||
|
var nushellKeyBindings []byte
|
||||||
|
|
||||||
|
//go:embed shell/completion.nu
|
||||||
|
var nushellCompletion []byte
|
||||||
|
|
||||||
//go:embed shell/completion.fish
|
//go:embed shell/completion.fish
|
||||||
var fishCompletion []byte
|
var fishCompletion []byte
|
||||||
|
|
||||||
@@ -71,6 +77,11 @@ func main() {
|
|||||||
printScript("completion.fish", fishCompletion)
|
printScript("completion.fish", fishCompletion)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if options.Nushell {
|
||||||
|
printScript("key-bindings.nu", nushellKeyBindings)
|
||||||
|
printScript("completion.nu", nushellCompletion)
|
||||||
|
return
|
||||||
|
}
|
||||||
if options.Help {
|
if options.Help {
|
||||||
fmt.Print(fzf.Usage)
|
fmt.Print(fzf.Usage)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1373,6 +1373,12 @@ Print script to set up Fish shell integration
|
|||||||
|
|
||||||
e.g. \fBfzf \-\-fish | source\fR
|
e.g. \fBfzf \-\-fish | source\fR
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B "\-\-nushell"
|
||||||
|
Print script to set up Nushell shell integration
|
||||||
|
|
||||||
|
e.g. \fBfzf \-\-nushell | save \-f ~/.config/nushell/autoload/_fzf_integration.nu\fR
|
||||||
|
|
||||||
.SS OTHERS
|
.SS OTHERS
|
||||||
.TP
|
.TP
|
||||||
.B "\-\-no\-mouse"
|
.B "\-\-no\-mouse"
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# ____ ____
|
||||||
|
# / __/___ / __/
|
||||||
|
# / /_/_ / / /_
|
||||||
|
# / __/ / /_/ __/
|
||||||
|
# /_/ /___/_/ completion-examples.nu
|
||||||
|
#
|
||||||
|
# Example custom completers for fzf's Nushell integration.
|
||||||
|
#
|
||||||
|
# To use these, add the desired entries to $env.FZF_COMPLETERS in your
|
||||||
|
# config.nu. Each closure receives two arguments:
|
||||||
|
# - prefix: the text before the trigger (e.g. "vim" in "vim **<TAB>")
|
||||||
|
# - spans: the full command as a list of words (e.g. ["pacman", "-S", "vim**"])
|
||||||
|
#
|
||||||
|
# A closure can return either:
|
||||||
|
# - a list of candidate strings (fzf will use default options), or
|
||||||
|
# - a record with the following optional fields:
|
||||||
|
# candidates: list<string> # candidates to feed to fzf
|
||||||
|
# opts: list<string> # custom fzf options (default: ["-m"])
|
||||||
|
# post: closure (|sel| ...) # post-processing of the selected item
|
||||||
|
#
|
||||||
|
# Simple example:
|
||||||
|
# $env.FZF_COMPLETERS = {
|
||||||
|
# git: {|prefix, spans| ["branch-main", "branch-dev", "branch-feature"]}
|
||||||
|
# }
|
||||||
|
|
||||||
|
# --- pacman / paru ---
|
||||||
|
# Completes package names for pacman and paru.
|
||||||
|
# Uses the spans to distinguish between subcommands:
|
||||||
|
# -S (sync), -F (files): list available packages from repos
|
||||||
|
# -Q (query), -R (remove): list installed packages
|
||||||
|
# Returns a record with custom fzf options for package preview.
|
||||||
|
|
||||||
|
$env.FZF_COMPLETERS = {}
|
||||||
|
|
||||||
|
$env.FZF_COMPLETERS.pacman = {|prefix, spans|
|
||||||
|
let sub = $spans | skip 1 | first
|
||||||
|
let candidates = (if ($sub =~ "-[SF]") {
|
||||||
|
^pacman -Slq | lines
|
||||||
|
} else if ($sub =~ "-[QR]") {
|
||||||
|
^pacman -Qq | lines
|
||||||
|
} else {
|
||||||
|
[]
|
||||||
|
})
|
||||||
|
{
|
||||||
|
candidates: $candidates
|
||||||
|
opts: ["-m", "--preview", "pacman -Si {}", "--prompt", "Package > "]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$env.FZF_COMPLETERS.paru = {|prefix, spans|
|
||||||
|
let sub = $spans | skip 1 | first
|
||||||
|
let candidates = (if ($sub =~ "-[SF]") {
|
||||||
|
^pacman -Slq | lines
|
||||||
|
} else if ($sub =~ "-[QR]") {
|
||||||
|
^pacman -Qq | lines
|
||||||
|
} else {
|
||||||
|
[]
|
||||||
|
})
|
||||||
|
{
|
||||||
|
candidates: $candidates
|
||||||
|
opts: ["-m", "--preview", "pacman -Si {}", "--prompt", "Package > "]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- pass (password-store) ---
|
||||||
|
# Completes entry names from ~/.password-store.
|
||||||
|
# Returns a simple list (no custom fzf options needed).
|
||||||
|
|
||||||
|
$env.FZF_COMPLETERS.pass = {|prefix, spans|
|
||||||
|
try {
|
||||||
|
ls ~/.password-store/**/*.gpg
|
||||||
|
| get name
|
||||||
|
| each {$in | str replace -r '^.*?\.password-store/(.*).gpg' '${1}'}
|
||||||
|
} catch {
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Example with post-processing hook ---
|
||||||
|
# The "post" closure transforms the selected line after fzf returns.
|
||||||
|
# This is useful when the displayed line contains more information than
|
||||||
|
# what you want inserted on the command line (e.g. extracting a PID from
|
||||||
|
# a full "ps" output line).
|
||||||
|
#
|
||||||
|
# $env.FZF_COMPLETERS.mycommand = {|prefix, spans|
|
||||||
|
# {
|
||||||
|
# candidates: (^some-command | lines)
|
||||||
|
# opts: ["+m", "--header-lines=1"]
|
||||||
|
# post: {|selection| $selection | split row ' ' | get 0}
|
||||||
|
# }
|
||||||
|
# }
|
||||||
@@ -0,0 +1,489 @@
|
|||||||
|
# ____ ____
|
||||||
|
# / __/___ / __/
|
||||||
|
# / /_/_ / / /_
|
||||||
|
# / __/ / /_/ __/
|
||||||
|
# /_/ /___/_/ completion.nu
|
||||||
|
|
||||||
|
|
||||||
|
# An implementation of completion.nu
|
||||||
|
# This loads FZF as a Nushell External Completer
|
||||||
|
# https://www.nushell.sh/cookbook/external_completers.html
|
||||||
|
|
||||||
|
|
||||||
|
# --- Default Environment Variables ---
|
||||||
|
# These can be overridden in your config.nu or environment.
|
||||||
|
# Example: $env.FZF_COMPLETION_TRIGGER = "!<TAB>"
|
||||||
|
|
||||||
|
# - $env.FZF_TMUX (default: 0)
|
||||||
|
# - $env.FZF_TMUX_OPTS (default: empty)
|
||||||
|
# - $env.FZF_TMUX_HEIGHT (default: 40%)
|
||||||
|
# - $env.FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
|
# - $env.FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
# - $env.FZF_COMPLETION_PATH_OPTS (default: empty)
|
||||||
|
# - $env.FZF_COMPLETION_DIR_OPTS (default: empty)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$env.FZF_COMPLETION_TRIGGER = $env.FZF_COMPLETION_TRIGGER? | default '**'
|
||||||
|
|
||||||
|
# Options for fzf completion in general. e.g. '--border'
|
||||||
|
$env.FZF_COMPLETION_OPTS = $env.FZF_COMPLETION_OPTS? | default ''
|
||||||
|
|
||||||
|
# Options specific to path completion. e.g. '--extended'
|
||||||
|
$env.FZF_COMPLETION_PATH_OPTS = $env.FZF_COMPLETION_PATH_OPTS? | default ''
|
||||||
|
# Options specific to directory completion. e.g. '--extended'
|
||||||
|
$env.FZF_COMPLETION_DIR_OPTS = $env.FZF_COMPLETION_DIR_OPTS? | default ''
|
||||||
|
|
||||||
|
$env.FZF_COMPLETION_DIR_COMMANDS = $env.FZF_COMPLETION_DIR_COMMANDS? | default ['cd', 'pushd', 'rmdir']
|
||||||
|
|
||||||
|
# --- Helper Functions ---
|
||||||
|
|
||||||
|
# Helper to build default fzf options list
|
||||||
|
def __fzf_defaults [prepend: string, append: string]: nothing -> string {
|
||||||
|
let base = $"--height ($env.FZF_TMUX_HEIGHT? | default '40%') --min-height 20+ --bind=ctrl-z:ignore ($prepend)"
|
||||||
|
let opts_file = if ($env.FZF_DEFAULT_OPTS_FILE? | default '' | is-not-empty) {
|
||||||
|
try { open --raw ($env.FZF_DEFAULT_OPTS_FILE) | str trim } catch { '' }
|
||||||
|
} else {
|
||||||
|
''
|
||||||
|
}
|
||||||
|
let default_opts = $env.FZF_DEFAULT_OPTS? | default ''
|
||||||
|
$"($base) ($opts_file) ($default_opts) ($append)" | str trim
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wrapper for running fzf or fzf-tmux
|
||||||
|
def __fzf_comprun [ context_name: string # e.g., "fzf-completion" , "fzf-helper" - mainly for potential debugging
|
||||||
|
, query: string # The initial query string for fzf
|
||||||
|
, fzf_opts_arg: list<string> # Remaining options for fzf/fzf-tmux
|
||||||
|
] {
|
||||||
|
let stdin_content = try {
|
||||||
|
# Collect stdin into a single string. Adjust if structured data is expected.
|
||||||
|
$in | into string
|
||||||
|
} catch {
|
||||||
|
null # Set to null if there's no stdin or an error occurs reading it
|
||||||
|
}
|
||||||
|
|
||||||
|
let fzf_default_opts = (__fzf_defaults "" ($env.FZF_COMPLETION_OPTS | default ''))
|
||||||
|
let fzf_prefinal_opt = ['--query', $query, '--reverse'] | append $fzf_opts_arg
|
||||||
|
|
||||||
|
# Get the configured height, defaulting to '40%'
|
||||||
|
let height_opt = $env.FZF_TMUX_HEIGHT? | default '40%'
|
||||||
|
|
||||||
|
# Determine if fzf should generate its own candidates via walker
|
||||||
|
let has_walker = ($fzf_prefinal_opt | find '--walker' | is-not-empty)
|
||||||
|
|
||||||
|
# Check for custom comprun function (Nu equivalent)
|
||||||
|
if (which _fzf_comprun | is-not-empty) {
|
||||||
|
# Note: Nushell doesn't have a direct equivalent to Zsh/Bash `type -t _fzf_comprun`.
|
||||||
|
# This check assumes a user might define a custom command named `_fzf_comprun`.
|
||||||
|
_fzf_comprun $context_name $query ...$fzf_prefinal_opt # Pass args correctly to custom function
|
||||||
|
} else if ($env.TMUX_PANE? | default '' | into string | is-not-empty) and (($env.FZF_TMUX? | default 0) != 0 or ($env.FZF_TMUX_OPTS? | is-not-empty)) {
|
||||||
|
# Running inside tmux, use fzf-tmux
|
||||||
|
let final_fzf_opts = if ($env.FZF_TMUX_OPTS? | is-not-empty) {
|
||||||
|
$env.FZF_TMUX_OPTS | split row ' ' | append ['--'] | append $fzf_prefinal_opt
|
||||||
|
} else {
|
||||||
|
# Use the default -d option with the configured height for fzf-tmux
|
||||||
|
['-d', $height_opt, '--'] | append $fzf_prefinal_opt
|
||||||
|
}
|
||||||
|
|
||||||
|
if $has_walker or ($stdin_content == null) {
|
||||||
|
with-env { FZF_DEFAULT_OPTS: $fzf_default_opts, FZF_DEFAULT_OPTS_FILE: '' } { fzf-tmux ...$final_fzf_opts }
|
||||||
|
} else {
|
||||||
|
$stdin_content | with-env { FZF_DEFAULT_OPTS: $fzf_default_opts, FZF_DEFAULT_OPTS_FILE: '' } { fzf-tmux ...$final_fzf_opts }
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
# Not in tmux or not configured for fzf-tmux, use fzf directly
|
||||||
|
let final_fzf_opts = $fzf_prefinal_opt
|
||||||
|
|
||||||
|
if $has_walker or ($stdin_content == null) {
|
||||||
|
with-env { FZF_DEFAULT_OPTS: $fzf_default_opts, FZF_DEFAULT_OPTS_FILE: '' } { fzf ...$final_fzf_opts }
|
||||||
|
} else {
|
||||||
|
$stdin_content | with-env { FZF_DEFAULT_OPTS: $fzf_default_opts, FZF_DEFAULT_OPTS_FILE: '' } { fzf ...$final_fzf_opts }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate host list for ssh/telnet
|
||||||
|
def __fzf_list_hosts [] {
|
||||||
|
# Translate the Zsh pipeline using Nu commands and external tools
|
||||||
|
let ssh_configs = try { open ~/.ssh/config | lines } catch { [] }
|
||||||
|
let ssh_configs_d = try { open ~/.ssh/config.d/* | lines } catch { [] }
|
||||||
|
let ssh_config_global = try { open /etc/ssh/ssh_config | lines } catch { [] }
|
||||||
|
let known_hosts = try { open ~/.ssh/known_hosts | lines } catch { [] }
|
||||||
|
let hosts_file = try { open /etc/hosts | lines } catch { [] }
|
||||||
|
|
||||||
|
[
|
||||||
|
(
|
||||||
|
# Process ssh config files
|
||||||
|
$ssh_configs | append $ssh_configs_d | append $ssh_config_global
|
||||||
|
| where {|it| ($it | str downcase | str starts-with 'host') or ($it | str downcase | str starts-with 'hostname') }
|
||||||
|
| parse --regex '^\s*host(?:name)?\s+(?<hosts>.+)' # Extract hosts after keyword
|
||||||
|
| default { hosts: null } # Handle lines that don't match regex
|
||||||
|
| get hosts
|
||||||
|
| where {|it| $it != null }
|
||||||
|
| split row ' '
|
||||||
|
| where {|it| not ($it =~ '[*?%]') } # Exclude patterns containing *, ?, or %
|
||||||
|
)
|
||||||
|
(
|
||||||
|
# Process known_hosts file
|
||||||
|
$known_hosts | parse --regex '^(?:\[)?(?<hosts>[a-z0-9.,:_-]+)' # Extract hostnames (possibly in [], possibly comma-separated) - added underscore
|
||||||
|
| default { hosts: null }
|
||||||
|
| get hosts
|
||||||
|
| where {|it| $it != null }
|
||||||
|
| each { |it| $it | split row ',' } # Split comma-separated hosts if any
|
||||||
|
| flatten
|
||||||
|
)
|
||||||
|
(
|
||||||
|
# Process /etc/hosts file
|
||||||
|
$hosts_file | where { |it| not ($it | str starts-with '#') } # Ignore comments
|
||||||
|
| where { |it| not ($it | str trim | is-empty) } # Ignore empty lines
|
||||||
|
| where { |it| not ($it | str contains '0.0.0.0') } # Ignore 0.0.0.0
|
||||||
|
| str replace --regex '#.*$' '' # Remove trailing comments
|
||||||
|
| parse --regex '^\s*\S+\s+(?<hosts>.+)' # Extract hosts part (after IP)
|
||||||
|
| default { hosts: null }
|
||||||
|
| get hosts
|
||||||
|
| where {|it| $it != null }
|
||||||
|
| split row ' ' # Split multiple hosts on the same line
|
||||||
|
)
|
||||||
|
]
|
||||||
|
| flatten # Combine all lists into a single stream
|
||||||
|
| where {|it| not ($it | is-empty) } # Remove empty entries
|
||||||
|
| sort | uniq # Sort and remove duplicates
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Base function for path/directory completion
|
||||||
|
def __fzf_generic_path_completion [ prefix: string # The text before the trigger
|
||||||
|
, fzf_opts_arg: list<string> # Extra options for fzf
|
||||||
|
, suffix: string # Suffix to add to selection (e.g. , "/")
|
||||||
|
] {
|
||||||
|
# --- Determine walker root and initial query from the prefix ---
|
||||||
|
|
||||||
|
mut walker_root = "."
|
||||||
|
mut initial_query = ""
|
||||||
|
|
||||||
|
if ($prefix | is-empty) {
|
||||||
|
# Case: "**"
|
||||||
|
$walker_root = "."
|
||||||
|
$initial_query = ""
|
||||||
|
} else if ($prefix | str contains (char separator)) {
|
||||||
|
# Case: "dir/subdir/partial**" or "dir/**"
|
||||||
|
$walker_root = $prefix | path dirname
|
||||||
|
$initial_query = $prefix | path basename
|
||||||
|
# Handle edge case where prefix ends with separator, e.g., "dir/"
|
||||||
|
if ($prefix | str ends-with (char separator)) {
|
||||||
|
# Remove trailing separator to get the intended directory
|
||||||
|
$walker_root = $prefix | str substring 0..-2
|
||||||
|
$initial_query = ""
|
||||||
|
}
|
||||||
|
# Ensure walker_root isn't empty if prefix was like "/file**"
|
||||||
|
# or if path dirname returned empty string for some reason (e.g. prefix="file/")
|
||||||
|
if ($walker_root | is-empty) {
|
||||||
|
if ($prefix | str starts-with (char separator)) {
|
||||||
|
$walker_root = (char separator)
|
||||||
|
} else if ($prefix | str ends-with (char separator)) {
|
||||||
|
$walker_root = $prefix | str substring 0..-2
|
||||||
|
} else { $walker_root = "." } # Fallback if dirname weirdly fails
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
# Case: "partial**" (no slashes)
|
||||||
|
$walker_root = "."
|
||||||
|
$initial_query = $prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Prepare FZF options ---
|
||||||
|
let completion_type_opts = if $suffix == '/' {
|
||||||
|
$env.FZF_COMPLETION_DIR_OPTS? | default '' | split row ' ' | where {not ($in | is-empty)}
|
||||||
|
} else {
|
||||||
|
$env.FZF_COMPLETION_PATH_OPTS? | default '' | split row ' ' | where {not ($in | is-empty)}
|
||||||
|
}
|
||||||
|
|
||||||
|
let walker_type = if ($suffix == '/') {
|
||||||
|
"dir,follow"
|
||||||
|
} else {
|
||||||
|
"file,dir,follow,hidden"
|
||||||
|
}
|
||||||
|
# Expand tilde so fzf receives a valid absolute path as walker-root
|
||||||
|
let needs_tilde_rewrite = ($walker_root | str starts-with '~')
|
||||||
|
let walker_root_expanded = ($walker_root | path expand)
|
||||||
|
|
||||||
|
# Use the 'walker_root' calculated at the beginning
|
||||||
|
let fzf_all_opts = ["--scheme=path", "--walker", $walker_type, "--walker-root", $walker_root_expanded] | append $fzf_opts_arg
|
||||||
|
| append $completion_type_opts
|
||||||
|
|
||||||
|
# Call FZF run
|
||||||
|
let fzf_selection = ( __fzf_comprun "fzf-path-completion-walker" $initial_query $fzf_all_opts ) | str trim
|
||||||
|
|
||||||
|
|
||||||
|
# --- Return Result ---
|
||||||
|
if ($fzf_selection | is-not-empty) {
|
||||||
|
# Restore tilde prefix if the user originally typed ~/
|
||||||
|
let home = $nu.home-dir | path expand
|
||||||
|
let result = if $needs_tilde_rewrite {
|
||||||
|
$fzf_selection | lines | each {|line| $line | str replace $home '~' } | str join ' '
|
||||||
|
} else {
|
||||||
|
$fzf_selection | lines | str join ' '
|
||||||
|
}
|
||||||
|
[$result]
|
||||||
|
} else {
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Specific path completion wrapper
|
||||||
|
def _fzf_path_completion [prefix: string] {
|
||||||
|
# Zsh args: base, lbuf, _fzf_compgen_path, "-m", "", " "
|
||||||
|
# Nu: prefix, empty command name (use find), ["-m"], "", " "
|
||||||
|
__fzf_generic_path_completion $prefix ["-m"] ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# General completion helper for commands that feed a list to fzf
|
||||||
|
# This is called by ssh, kill, and user-defined completers.
|
||||||
|
def _fzf_complete [ query: string # The initial query string for fzf
|
||||||
|
, data_gen_closure: closure # Closure that generates candidates
|
||||||
|
, fzf_opts_arg: list<string> # Extra options for fzf (like -m, +m)
|
||||||
|
, --post_process_closure: closure # Closure to process the selected item (optional)
|
||||||
|
] {
|
||||||
|
# Generate candidates using the provided command
|
||||||
|
let candidates = try {
|
||||||
|
do $data_gen_closure
|
||||||
|
} catch {
|
||||||
|
# Capture the actual error object provided by the catch block
|
||||||
|
let actual_error = $in
|
||||||
|
# Print a more informative error message including the actual error details
|
||||||
|
print -e $"Error executing data_gen closure. Closure code: ($data_gen_closure). Actual error: ($actual_error)"
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run fzf and get selection
|
||||||
|
let fzf_selection = $candidates | to text
|
||||||
|
| __fzf_comprun "fzf-helper" $query $fzf_opts_arg
|
||||||
|
| str trim # Trim potential trailing newline from fzf
|
||||||
|
|
||||||
|
# Apply post-processing if closure provided and selection is not empty
|
||||||
|
let processed_selection = if ($fzf_selection | is-not-empty) and ($post_process_closure | is-not-empty) {
|
||||||
|
# Call the post-processing closure with the selection
|
||||||
|
try {
|
||||||
|
do $post_process_closure $fzf_selection
|
||||||
|
} catch {
|
||||||
|
print -e $"Error executing post_process closure: ($post_process_closure)"
|
||||||
|
$fzf_selection # Return original selection on error
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$fzf_selection
|
||||||
|
}
|
||||||
|
|
||||||
|
if not ($processed_selection | is-empty) {
|
||||||
|
[($processed_selection | lines | str join ' ')]
|
||||||
|
} else {
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# SSH/Telnet completion
|
||||||
|
def _fzf_complete_ssh [ prefix: string
|
||||||
|
, input_line_before_trigger: string
|
||||||
|
] {
|
||||||
|
let words = ($input_line_before_trigger | split row ' ')
|
||||||
|
let word_count = $words | length
|
||||||
|
|
||||||
|
# Find the index of the word being completed (which is the prefix)
|
||||||
|
# If prefix is empty, completion happens after a space, index is word_count
|
||||||
|
# If prefix is not empty, it's the last word, index is word_count - 1
|
||||||
|
let completion_index = if ($prefix | is-empty) { $word_count } else { $word_count - 1 }
|
||||||
|
|
||||||
|
mut handled = false
|
||||||
|
mut completion_result = [] # List of completion strings to return
|
||||||
|
|
||||||
|
# Check for -i, -F, -E flags immediately preceding the cursor position
|
||||||
|
if $completion_index > 0 {
|
||||||
|
let prev_arg = ($words | get ($completion_index - 1))
|
||||||
|
if ($prev_arg in ['-i', '-F', '-E']) {
|
||||||
|
$handled = true
|
||||||
|
# Call path completion with the current prefix
|
||||||
|
$completion_result = (_fzf_path_completion $prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# If not handled by path completion, do host completion
|
||||||
|
if not $handled {
|
||||||
|
let user_part = if ($prefix | str contains "@") { ($prefix | split row "@" | first) + "@" } else { "" }
|
||||||
|
# The part after '@' (or the whole prefix if no '@') is the initial query for fzf
|
||||||
|
let query = if ($prefix | str contains "@") { $prefix | split row "@" | last } else { $prefix }
|
||||||
|
|
||||||
|
let host_candidates_gen = {||
|
||||||
|
__fzf_list_hosts
|
||||||
|
| each {|host_item| $user_part + $host_item } # Prepend user@ if present in prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
# Zsh options: +m -- ; Nu: pass ["+m"]
|
||||||
|
# Pass the host part of the prefix to _fzf_complete for the initial query
|
||||||
|
let selected_host = (_fzf_complete $query $host_candidates_gen ["+m"]) # Pass host_prefix here
|
||||||
|
if not ($selected_host | is-empty) {
|
||||||
|
$completion_result = $selected_host # _fzf_complete returns a list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$completion_result
|
||||||
|
}
|
||||||
|
|
||||||
|
# Kill completion post-processor (extracts PID)
|
||||||
|
def _fzf_complete_kill_post_get_pid [selected_line: string] {
|
||||||
|
# Assuming standard ps output where PID is the second column
|
||||||
|
$selected_line | lines | each { $in | from ssv --noheaders | get 0.column1 } | to text
|
||||||
|
}
|
||||||
|
|
||||||
|
# Kill completion to get process PID
|
||||||
|
def _fzf_complete_kill [query: string] {
|
||||||
|
let ps_gen_closure = {|| # Define ps generator as a closure
|
||||||
|
# Try standard ps, then busybox, then cygwin format approximation
|
||||||
|
# Use `^ps` to ensure external command execution
|
||||||
|
try {
|
||||||
|
^ps -eo user,pid,ppid,start,time,command | complete | if $in.exit_code == 0 { $in.stdout | lines } else { error make {msg: "ps failed"} }
|
||||||
|
} catch {
|
||||||
|
try {
|
||||||
|
^ps -eo user,pid,ppid,time,args | complete | if $in.exit_code == 0 { $in.stdout | lines } else { error make {msg: "ps failed"} }
|
||||||
|
} catch {
|
||||||
|
try {
|
||||||
|
^ps --everyone --full --windows | complete | if $in.exit_code == 0 { $in.stdout | lines } else { error make {msg: "ps failed"} }
|
||||||
|
} catch {
|
||||||
|
print -e "Error: ps command failed."
|
||||||
|
[] # Return empty list on failure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Note: Complex Zsh FZF bindings for kill (click-header transformer) are omitted for simplicity.
|
||||||
|
# Users can set custom bindings via FZF_DEFAULT_OPTS if needed.
|
||||||
|
let kill_post_closure = {|selected_line| _fzf_complete_kill_post_get_pid $selected_line }
|
||||||
|
|
||||||
|
let fzf_opts = ["-m", "--header-lines=1", "--no-preview", "--wrap", "--color", "fg:dim,nth:regular"]
|
||||||
|
|
||||||
|
_fzf_complete $query $ps_gen_closure $fzf_opts --post_process_closure $kill_post_closure
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --- Main FZF External Completer ---
|
||||||
|
|
||||||
|
# This function is registered with Nushell's external completion system.
|
||||||
|
# It gets called when Tab is pressed.
|
||||||
|
let fzf_external_completer = {|spans|
|
||||||
|
let trigger: string = $env.FZF_COMPLETION_TRIGGER? | default '**'
|
||||||
|
|
||||||
|
if ($trigger | is-empty) { return null } # Cannot work with empty trigger
|
||||||
|
if (($spans | length ) == 0) { return null } # Nothing to complete
|
||||||
|
|
||||||
|
let last_span = $spans | last
|
||||||
|
|
||||||
|
if ($last_span | str ends-with $trigger) {
|
||||||
|
# --- Trigger Found ---
|
||||||
|
|
||||||
|
# Skip sudo to determine the actual command
|
||||||
|
let cmd_spans = if ($spans | first) == "sudo" { $spans | skip 1 } else { $spans }
|
||||||
|
let cmd_word = ($cmd_spans | first | default "")
|
||||||
|
|
||||||
|
# Calculate the prefix (part before the trigger in the last span)
|
||||||
|
let prefix = $last_span | str substring 0..(-1 * ($trigger | str length) - 1)
|
||||||
|
|
||||||
|
# Reconstruct the line content *before* the trigger for context
|
||||||
|
# This is an approximation based on spans
|
||||||
|
let line_without_trigger = $cmd_spans | take (($cmd_spans | length) - 1) | append $prefix | str join ' '
|
||||||
|
|
||||||
|
# --- Dispatch to Completer ---
|
||||||
|
mut completion_results = [] # Will hold the list of strings from the completer
|
||||||
|
|
||||||
|
# Check for user-defined completer in $env.FZF_COMPLETERS first.
|
||||||
|
# Users can define custom completers in their config.nu as a record of closures:
|
||||||
|
# $env.FZF_COMPLETERS = { git: {|prefix, spans| ... }, docker: {|prefix, spans| ... } }
|
||||||
|
# Each closure receives the prefix (text before the trigger) and the full
|
||||||
|
# command spans (e.g. ["pacman", "-S", "vim**"]), and should return either:
|
||||||
|
# - a list of candidate strings, or
|
||||||
|
# - a record { candidates: [...], opts: [...], post: {|sel| ...} } to pass
|
||||||
|
# custom fzf options and/or a post-processing closure.
|
||||||
|
# See shell/completion-examples.nu for examples.
|
||||||
|
let user_completers = ($env.FZF_COMPLETERS? | default {})
|
||||||
|
if ($cmd_word in $user_completers) {
|
||||||
|
let user_gen = ($user_completers | get $cmd_word)
|
||||||
|
let user_result = (do $user_gen $prefix $cmd_spans)
|
||||||
|
if ($user_result | describe | str starts-with 'record') {
|
||||||
|
let candidates = ($user_result | get candidates)
|
||||||
|
let fzf_opts = ($user_result | get opts? | default ["-m"])
|
||||||
|
let post = ($user_result | get post? | default null)
|
||||||
|
if ($post != null) {
|
||||||
|
$completion_results = (_fzf_complete $prefix {|| $candidates} $fzf_opts --post_process_closure $post)
|
||||||
|
} else {
|
||||||
|
$completion_results = (_fzf_complete $prefix {|| $candidates} $fzf_opts)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$completion_results = (_fzf_complete $prefix {|| $user_result} ["-m"])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match $cmd_word {
|
||||||
|
"ssh" | "scp" | "sftp" | "telnet" => { $completion_results = (_fzf_complete_ssh $prefix $line_without_trigger) }
|
||||||
|
"kill" => { $completion_results = (_fzf_complete_kill $prefix) }
|
||||||
|
_ if ($cmd_word in $env.FZF_COMPLETION_DIR_COMMANDS) => {
|
||||||
|
$completion_results = (__fzf_generic_path_completion $prefix [] "/")
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
# Default to path completion if no specific command matches
|
||||||
|
$completion_results = (_fzf_path_completion $prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Return Results ---
|
||||||
|
# The _fzf_... functions return a list of completion strings.
|
||||||
|
# Nushell's completer expects the suggestions for the token being completed (prefix + trigger).
|
||||||
|
# The results from the helper functions should be the final desired strings.
|
||||||
|
# We don't need to manually add spaces; Nushell handles that.
|
||||||
|
$completion_results # Return the list directly
|
||||||
|
} else {
|
||||||
|
# --- Trigger Not Found ---
|
||||||
|
# Return null to let Nushell fall back to other completers (e.g., default file completion).
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- WRAPPER AND REGISTRATION ---
|
||||||
|
|
||||||
|
# Guard against re-sourcing: wrapping the completer multiple times would
|
||||||
|
# nest wrappers and grow the call chain on every reload.
|
||||||
|
if ($env.__fzf_completer_registered? | default false) != true {
|
||||||
|
|
||||||
|
# Get the currently configured external completer, if any exists
|
||||||
|
let previous_external_completer = $env.config? | get completions? | get external? | get completer?
|
||||||
|
|
||||||
|
# Define the new wrapper completer
|
||||||
|
let fzf_wrapper_completer = {|spans|
|
||||||
|
# 1. Try the FZF completer logic first
|
||||||
|
let fzf_result = do $fzf_external_completer $spans
|
||||||
|
|
||||||
|
# 2. If FZF returned a result (a list, even an empty one), return it.
|
||||||
|
# `null` means FZF didn't handle it because the trigger wasn't present.
|
||||||
|
if $fzf_result != null {
|
||||||
|
$fzf_result
|
||||||
|
} else {
|
||||||
|
# 3. FZF didn't handle it, so call the previous completer (if it exists).
|
||||||
|
if $previous_external_completer != null {
|
||||||
|
do $previous_external_completer $spans
|
||||||
|
} else {
|
||||||
|
# 4. No previous completer, and FZF didn't handle it. Return null.
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Register the new wrapper completer
|
||||||
|
# This ensures external completions are enabled and sets our wrapper.
|
||||||
|
$env.config = $env.config | upsert completions {
|
||||||
|
external: {
|
||||||
|
enable: true
|
||||||
|
completer: $fzf_wrapper_completer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$env.__fzf_completer_registered = true
|
||||||
|
}
|
||||||
|
|
||||||
|
# vim: set sts=2 ts=2 sw=2 tw=120 et :
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
# ____ ____
|
||||||
|
# / __/___ / __/
|
||||||
|
# / /_/_ / / /_
|
||||||
|
# / __/ / /_/ __/
|
||||||
|
# /_/ /___/_/ key-bindings.nu
|
||||||
|
#
|
||||||
|
# - $FZF_TMUX (default: 0)
|
||||||
|
# - $FZF_TMUX_OPTS
|
||||||
|
# - $FZF_TMUX_HEIGHT (default: 40%)
|
||||||
|
# - $FZF_CTRL_T_COMMAND (set to "" to disable)
|
||||||
|
# - $FZF_CTRL_T_OPTS
|
||||||
|
# - $FZF_CTRL_R_COMMAND (set to "" to disable)
|
||||||
|
# - $FZF_CTRL_R_OPTS
|
||||||
|
# - $FZF_ALT_C_COMMAND (set to "" to disable)
|
||||||
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
|
# Code provided by @igor-ramazanov
|
||||||
|
# Source: https://github.com/junegunn/fzf/issues/4122#issuecomment-2607368316
|
||||||
|
|
||||||
|
# Merge default options in the same order as bash/zsh:
|
||||||
|
# 1. --height, --min-height, --bind=ctrl-z:ignore, $prepend
|
||||||
|
# 2. $FZF_DEFAULT_OPTS_FILE contents
|
||||||
|
# 3. $FZF_DEFAULT_OPTS, $append
|
||||||
|
def __fzf_defaults [prepend: string, append: string]: nothing -> string {
|
||||||
|
let base = $"--height ($env.FZF_TMUX_HEIGHT? | default '40%') --min-height 20+ --bind=ctrl-z:ignore ($prepend)"
|
||||||
|
let opts_file = if ($env.FZF_DEFAULT_OPTS_FILE? | default '' | is-not-empty) {
|
||||||
|
try { open --raw ($env.FZF_DEFAULT_OPTS_FILE) | str trim } catch { '' }
|
||||||
|
} else {
|
||||||
|
''
|
||||||
|
}
|
||||||
|
let default_opts = $env.FZF_DEFAULT_OPTS? | default ''
|
||||||
|
$"($base) ($opts_file) ($default_opts) ($append)" | str trim
|
||||||
|
}
|
||||||
|
|
||||||
|
# Return the fzf command to use: fzf-tmux when inside tmux and
|
||||||
|
# FZF_TMUX is enabled or FZF_TMUX_OPTS is set, plain fzf otherwise.
|
||||||
|
def __fzfcmd []: nothing -> list<string> {
|
||||||
|
let in_tmux = ($env.TMUX_PANE? | default '' | into string | is-not-empty)
|
||||||
|
if $in_tmux {
|
||||||
|
let fzf_tmux = ($env.FZF_TMUX? | default 0 | into string)
|
||||||
|
let fzf_tmux_opts = ($env.FZF_TMUX_OPTS? | default '' | into string)
|
||||||
|
if ($fzf_tmux != '0') or ($fzf_tmux_opts | is-not-empty) {
|
||||||
|
let opts = if ($fzf_tmux_opts | is-not-empty) { $fzf_tmux_opts } else { $"-d($env.FZF_TMUX_HEIGHT? | default '40%')" }
|
||||||
|
return ['fzf-tmux' ...(($opts | split row ' ' | where { $in != '' })) '--']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
['fzf']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export-env {
|
||||||
|
$env.FZF_CTRL_T_OPTS = $env.FZF_CTRL_T_OPTS? | default ""
|
||||||
|
$env.FZF_CTRL_R_OPTS = $env.FZF_CTRL_R_OPTS? | default ""
|
||||||
|
$env.FZF_ALT_C_OPTS = $env.FZF_ALT_C_OPTS? | default ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Directories
|
||||||
|
const alt_c = {
|
||||||
|
name: fzf_dirs
|
||||||
|
modifier: alt
|
||||||
|
keycode: char_c
|
||||||
|
mode: [emacs, vi_normal, vi_insert]
|
||||||
|
event: [
|
||||||
|
{
|
||||||
|
send: executehostcommand
|
||||||
|
cmd: "
|
||||||
|
let fzf_opts = (__fzf_defaults '--reverse --walker=dir,follow,hidden --scheme=path' $'($env.FZF_ALT_C_OPTS) +m');
|
||||||
|
let fzfcmd = (__fzfcmd);
|
||||||
|
let fzf_args = ($fzfcmd | skip 1);
|
||||||
|
let alt_c_cmd = ($env.FZF_ALT_C_COMMAND? | default null);
|
||||||
|
let result = if ($alt_c_cmd == null) or ($alt_c_cmd | is-empty) {
|
||||||
|
with-env { FZF_DEFAULT_OPTS: $fzf_opts, FZF_DEFAULT_OPTS_FILE: '' } { ^($fzfcmd | first) ...$fzf_args }
|
||||||
|
} else {
|
||||||
|
let fzf_cmd_str = ($fzfcmd | str join ' ');
|
||||||
|
let sh_cmd = [$alt_c_cmd '|' $fzf_cmd_str] | str join ' ';
|
||||||
|
with-env { FZF_DEFAULT_OPTS: $fzf_opts, FZF_DEFAULT_OPTS_FILE: '' } { ^sh -c $sh_cmd }
|
||||||
|
};
|
||||||
|
if ($result | is-not-empty) { cd $result };
|
||||||
|
"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# History
|
||||||
|
const ctrl_r = {
|
||||||
|
name: fzf_history
|
||||||
|
modifier: control
|
||||||
|
keycode: char_r
|
||||||
|
mode: [emacs, vi_insert, vi_normal]
|
||||||
|
event: [
|
||||||
|
{
|
||||||
|
send: executehostcommand
|
||||||
|
cmd: "commandline edit --replace (
|
||||||
|
let fzf_opts = (__fzf_defaults '' $'--scheme=history --bind=ctrl-r:toggle-sort --wrap-sign \"\t↳ \" --highlight-line ($env.FZF_CTRL_R_OPTS) +m --read0');
|
||||||
|
let fzfcmd = (__fzfcmd);
|
||||||
|
let fzf_args = ($fzfcmd | skip 1);
|
||||||
|
# reverse | uniq: show most recent first, deduplicate keeping the latest.
|
||||||
|
# Nushell's `history` loads the full history as an in-memory table
|
||||||
|
# (bounded by $env.config.history.max_size, default 100,000), so
|
||||||
|
# reverse and uniq run on an already-materialized list. This is O(n)
|
||||||
|
# but acceptable for typical history sizes; unlike bash/zsh `fc -r`,
|
||||||
|
# there is no streaming primitive that would let fzf show the latest
|
||||||
|
# entries before the full list is consumed.
|
||||||
|
history
|
||||||
|
| get command
|
||||||
|
| reverse
|
||||||
|
| uniq
|
||||||
|
| str join (char -i 0)
|
||||||
|
| with-env { FZF_DEFAULT_OPTS: $fzf_opts, FZF_DEFAULT_OPTS_FILE: '' } { ^($fzfcmd | first) ...$fzf_args --query (commandline) }
|
||||||
|
| decode utf-8
|
||||||
|
| str trim
|
||||||
|
)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Files
|
||||||
|
const ctrl_t = {
|
||||||
|
name: fzf_files
|
||||||
|
modifier: control
|
||||||
|
keycode: char_t
|
||||||
|
mode: [emacs, vi_normal, vi_insert]
|
||||||
|
event: [
|
||||||
|
{
|
||||||
|
send: executehostcommand
|
||||||
|
cmd: "
|
||||||
|
let fzf_opts = (__fzf_defaults '--reverse --walker=file,dir,follow,hidden --scheme=path' $'($env.FZF_CTRL_T_OPTS) -m');
|
||||||
|
let fzfcmd = (__fzfcmd);
|
||||||
|
let fzf_args = ($fzfcmd | skip 1);
|
||||||
|
let ctrl_t_cmd = ($env.FZF_CTRL_T_COMMAND? | default null);
|
||||||
|
let result = if ($ctrl_t_cmd == null) or ($ctrl_t_cmd | is-empty) {
|
||||||
|
with-env { FZF_DEFAULT_OPTS: $fzf_opts, FZF_DEFAULT_OPTS_FILE: '' } { ^($fzfcmd | first) ...$fzf_args }
|
||||||
|
} else {
|
||||||
|
let fzf_cmd_str = ($fzfcmd | str join ' ');
|
||||||
|
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
|
||||||
|
"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper to check if a binding is enabled. A binding is disabled when
|
||||||
|
# the corresponding *_COMMAND variable is explicitly set to "".
|
||||||
|
# When not defined (null), the binding is enabled (using fzf's built-in walker).
|
||||||
|
def __fzf_binding_enabled [var_name: string]: nothing -> bool {
|
||||||
|
let val = ($env | get -o $var_name)
|
||||||
|
# null = not defined = enabled; "" = explicitly disabled
|
||||||
|
$val == null or ($val | into string | is-not-empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update the $env.config
|
||||||
|
export-env {
|
||||||
|
let fzf_names = ['fzf_files', 'fzf_dirs', 'fzf_history']
|
||||||
|
# Filter out any existing fzf bindings, then re-add the enabled ones.
|
||||||
|
# This allows re-sourcing to update bindings (e.g. after changing
|
||||||
|
# FZF_CTRL_T_COMMAND) without creating duplicates.
|
||||||
|
mut bindings = ($env.config.keybindings | where { |kb| $kb.name not-in $fzf_names })
|
||||||
|
if (__fzf_binding_enabled 'FZF_ALT_C_COMMAND') { $bindings = ($bindings | append $alt_c) }
|
||||||
|
if (__fzf_binding_enabled 'FZF_CTRL_R_COMMAND') { $bindings = ($bindings | append $ctrl_r) }
|
||||||
|
if (__fzf_binding_enabled 'FZF_CTRL_T_COMMAND') { $bindings = ($bindings | append $ctrl_t) }
|
||||||
|
$env.config.keybindings = $bindings
|
||||||
|
}
|
||||||
@@ -233,6 +233,7 @@ Usage: fzf [options]
|
|||||||
--bash Print script to set up Bash shell integration
|
--bash Print script to set up Bash shell integration
|
||||||
--zsh Print script to set up Zsh shell integration
|
--zsh Print script to set up Zsh shell integration
|
||||||
--fish Print script to set up Fish shell integration
|
--fish Print script to set up Fish shell integration
|
||||||
|
--nushell Print script to set up Nushell integration
|
||||||
|
|
||||||
HELP
|
HELP
|
||||||
--version Display version information and exit
|
--version Display version information and exit
|
||||||
@@ -586,6 +587,7 @@ type Options struct {
|
|||||||
Bash bool
|
Bash bool
|
||||||
Zsh bool
|
Zsh bool
|
||||||
Fish bool
|
Fish bool
|
||||||
|
Nushell bool
|
||||||
Man bool
|
Man bool
|
||||||
Fuzzy bool
|
Fuzzy bool
|
||||||
FuzzyAlgo algo.Algo
|
FuzzyAlgo algo.Algo
|
||||||
@@ -733,6 +735,7 @@ func defaultOptions() *Options {
|
|||||||
Bash: false,
|
Bash: false,
|
||||||
Zsh: false,
|
Zsh: false,
|
||||||
Fish: false,
|
Fish: false,
|
||||||
|
Nushell: false,
|
||||||
Man: false,
|
Man: false,
|
||||||
Fuzzy: true,
|
Fuzzy: true,
|
||||||
FuzzyAlgo: algo.FuzzyMatchV2,
|
FuzzyAlgo: algo.FuzzyMatchV2,
|
||||||
@@ -2553,6 +2556,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
opts.Bash = false
|
opts.Bash = false
|
||||||
opts.Zsh = false
|
opts.Zsh = false
|
||||||
opts.Fish = false
|
opts.Fish = false
|
||||||
|
opts.Nushell = false
|
||||||
opts.Help = false
|
opts.Help = false
|
||||||
opts.Version = false
|
opts.Version = false
|
||||||
opts.Man = false
|
opts.Man = false
|
||||||
@@ -2665,6 +2669,9 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
case "--fish":
|
case "--fish":
|
||||||
clearExitingOpts()
|
clearExitingOpts()
|
||||||
opts.Fish = true
|
opts.Fish = true
|
||||||
|
case "--nushell":
|
||||||
|
clearExitingOpts()
|
||||||
|
opts.Nushell = true
|
||||||
case "-h", "--help":
|
case "-h", "--help":
|
||||||
clearExitingOpts()
|
clearExitingOpts()
|
||||||
opts.Help = true
|
opts.Help = true
|
||||||
|
|||||||
+66
-8
@@ -78,6 +78,38 @@ class Shell
|
|||||||
"rm -f ~/.local/share/fish/fzf_test_history; XDG_CONFIG_HOME=#{confdir} fish"
|
"rm -f ~/.local/share/fish/fzf_test_history; XDG_CONFIG_HOME=#{confdir} fish"
|
||||||
end
|
end
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -85,12 +117,30 @@ class Tmux
|
|||||||
attr_reader :win
|
attr_reader :win
|
||||||
|
|
||||||
def initialize(shell = :bash)
|
def initialize(shell = :bash)
|
||||||
|
@shell = shell
|
||||||
@win = go(%W[new-window -d -P -F #I #{Shell.send(shell)}]).first
|
@win = go(%W[new-window -d -P -F #I #{Shell.send(shell)}]).first
|
||||||
go(%W[set-window-option -t #{@win} pane-base-index 0])
|
go(%W[set-window-option -t #{@win} pane-base-index 0])
|
||||||
return unless shell == :fish
|
if shell == :fish
|
||||||
|
send_keys 'function fish_prompt; end; clear', :Enter
|
||||||
send_keys 'function fish_prompt; end; clear', :Enter
|
self.until(&:empty?)
|
||||||
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
|
end
|
||||||
|
|
||||||
def kill
|
def kill
|
||||||
@@ -242,11 +292,19 @@ class Tmux
|
|||||||
def prepare
|
def prepare
|
||||||
tries = 0
|
tries = 0
|
||||||
begin
|
begin
|
||||||
self.until(true) do |lines|
|
if @shell == :nushell
|
||||||
message = "Prepare[#{tries}]"
|
message = "Prepare[#{tries}]"
|
||||||
send_keys ' ', 'C-u', :Enter, message, :Left, :Right
|
send_keys 'C-u', 'C-l'
|
||||||
sleep(0.15)
|
sleep 0.2
|
||||||
lines[-1] == message
|
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
|
end
|
||||||
rescue Minitest::Assertion
|
rescue Minitest::Assertion
|
||||||
(tries += 1) < 5 ? retry : raise
|
(tries += 1) < 5 ? retry : raise
|
||||||
|
|||||||
@@ -1103,3 +1103,123 @@ class TestFish < TestBase
|
|||||||
end
|
end
|
||||||
end
|
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
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ enew = "enew"
|
|||||||
tabe = "tabe"
|
tabe = "tabe"
|
||||||
Iterm = "Iterm"
|
Iterm = "Iterm"
|
||||||
ser = "ser"
|
ser = "ser"
|
||||||
|
Slq = "Slq"
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
extend-exclude = ["README.md", "*.s"]
|
extend-exclude = ["README.md", "*.s"]
|
||||||
|
|||||||
@@ -114,6 +114,9 @@ if [ -d "${fish_dir}/functions" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
nushell_autoload_dir=${XDG_CONFIG_HOME:-$HOME/.config}/nushell/autoload
|
||||||
|
remove "${nushell_autoload_dir}/_fzf_integration.nu"
|
||||||
|
|
||||||
config_dir=$(dirname "$prefix_expand")
|
config_dir=$(dirname "$prefix_expand")
|
||||||
if [[ $xdg == 1 ]] && [[ $config_dir == */fzf ]] && [[ -d $config_dir ]]; then
|
if [[ $xdg == 1 ]] && [[ $config_dir == */fzf ]] && [[ -d $config_dir ]]; then
|
||||||
rmdir "$config_dir"
|
rmdir "$config_dir"
|
||||||
|
|||||||
Reference in New Issue
Block a user