Compare commits

...
Author SHA1 Message Date
Simon Désaulniers c002dbbb58 fix(ci): linux.yml needs to pin 0.116.1 2026-10-15 12:32:41 -04:00
Simon Désaulniers 227e96f4ad fix(nushell): adapt the completer to the 0.116 contract
Nushell 0.116 runs an external completer on a background worker
without the terminal unless the registered closure reaches a
command carrying the @interactive attribute. Interactivity is
seen through the closure to the command it calls, so wrap the
completion logic in an @interactive entry point and register a
closure that ends with the call to it.

Read the named `place` input instead of the deprecated
positional `spans`; $place.command carries the same token list.

An @interactive completer cannot decline with null (null reads
as a dismissed picker), so request the fallback explicitly when
the `**` trigger is absent, keeping plain file completion
working. The previous external completer is still called when
the trigger is absent.

The script now requires Nushell 0.116+, since @interactive is a
parse error on older versions.

Fixes #4932
2026-10-13 11:38:26 -04:00
2 changed files with 47 additions and 23 deletions

No files matched your search

+4 -2
View File
@@ -38,9 +38,11 @@ jobs:
wget -qO- https://apt.fury.io/nushell/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/fury-nushell.gpg 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 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 update
# Pinned: 0.115.0 runs external completers on a background thread, so # Pinned: the Nushell scripts now require 0.116+, where an external
# completer reaches the terminal through the @interactive attribute.
# Before that (e.g. 0.115.0), completers run on a background thread, so
# fzf never receives keystrokes. https://github.com/nushell/nushell/issues/18771 # fzf never receives keystrokes. https://github.com/nushell/nushell/issues/18771
sudo apt-get install --yes zsh fish tmux shfmt nushell=0.114.1-0 sudo apt-get install --yes zsh fish tmux shfmt nushell=0.116.1-0
- name: Install Ruby gems - name: Install Ruby gems
run: bundle install run: bundle install
+43 -21
View File
@@ -8,6 +8,15 @@
# An implementation of completion.nu # An implementation of completion.nu
# This loads FZF as a Nushell External Completer # This loads FZF as a Nushell External Completer
# https://www.nushell.sh/cookbook/external_completers.html # https://www.nushell.sh/cookbook/external_completers.html
#
# Requires Nushell 0.116.0 or newer.
#
# On 0.116+, the registered completer closure must reach a command carrying the
# `@interactive` attribute: interactivity is seen through the closure to that
# command (a closure cannot carry the attribute itself), and that is what makes
# the completion run on the REPL thread with the terminal, so fzf can take it.
# A closure that does not reach such a command runs on a background worker
# without the terminal, where fzf cannot run.
# --- Default Environment Variables --- # --- Default Environment Variables ---
@@ -367,9 +376,9 @@ def _fzf_complete_kill [query: string] {
# --- Main FZF External Completer --- # --- Main FZF External Completer ---
# This function is registered with Nushell's external completion system. # Main completion logic. It receives the token list of the command being
# It gets called when Tab is pressed. # completed (the legacy `spans`). Called directly, so no attribute is needed.
let fzf_external_completer = {|spans| def __fzf_completer_logic [spans] {
let trigger: string = $env.FZF_COMPLETION_TRIGGER? | default '**' let trigger: string = $env.FZF_COMPLETION_TRIGGER? | default '**'
if ($trigger | is-empty) { return null } # Cannot work with empty trigger if ($trigger | is-empty) { return null } # Cannot work with empty trigger
@@ -446,6 +455,29 @@ let fzf_external_completer = {|spans|
} }
} }
# Nushell 0.116+ entry point. The registered closure must END with a call to
# this command for the completion to run on the REPL thread with the terminal.
#
# It declares `place` (a named input) rather than the legacy `spans`, so the
# completer never goes through the deprecated positional bridge. `$place.command`
# is the token list of the command being completed, the same value the old
# `spans` held (with a trailing "" when the cursor sits on an empty slot).
@interactive
def __fzf_external_completer [place] {
let result = (__fzf_completer_logic $place.command)
if $result != null {
$result
} else {
# Fall back to the previously configured external completer, if any.
let previous = ($env.__fzf_previous_completer? | default null)
let previous_result = if $previous != null { do $previous $place } else { null }
# An @interactive completer cannot decline with null: null reads as a
# dismissed picker with an empty, final answer. Request the fallback
# explicitly so file completion still runs when the trigger is absent.
if $previous_result != null { $previous_result } else { {completions: [], fallback: true} }
}
}
# --- WRAPPER AND REGISTRATION --- # --- WRAPPER AND REGISTRATION ---
# Guard against re-sourcing: wrapping the completer multiple times would # Guard against re-sourcing: wrapping the completer multiple times would
@@ -455,25 +487,15 @@ if ($env.__fzf_completer_registered? | default false) != true {
# Get the currently configured external completer, if any exists # Get the currently configured external completer, if any exists
let previous_external_completer = $env.config? | get completions? | get external? | get completer? let previous_external_completer = $env.config? | get completions? | get external? | get completer?
# Define the new wrapper completer # Stash it for `__fzf_external_completer`: a def cannot capture a local
let fzf_wrapper_completer = {|spans| # variable, and the fallback to the previous completer happens inside it.
# 1. Try the FZF completer logic first $env.__fzf_previous_completer = $previous_external_completer
let fzf_result = do $fzf_external_completer $spans
# 2. If FZF returned a result (a list, even an empty one), return it. # The registered closure must END with a call to an `@interactive` command so
# `null` means FZF didn't handle it because the trigger wasn't present. # that Nushell 0.116+ runs the completion on the REPL thread with the terminal
if $fzf_result != null { # (it inspects the last pipeline element of the closure). Declaring `place`
$fzf_result # instead of `spans` keeps it off the deprecated positional bridge.
} else { let fzf_wrapper_completer = {|place| __fzf_external_completer $place }
# 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 # Register the new wrapper completer
# This ensures external completions are enabled and sets our wrapper. # This ensures external completions are enabled and sets our wrapper.