Public/Tools/Enable-Fzf.ps1

function Enable-Fzf {
    <#
    .SYNOPSIS
        Installs (if necessary) fzf, themes it, and wires up its PowerShell key bindings (via PSFzf)
        for the session.
 
    .DESCRIPTION
        Runs two nested Invoke-Step substeps:
          - Install: if fzf.exe isn't on PATH, installs it with winget (junegunn.fzf, a
            portable package) and patches the current session's PATH so the exe is usable
            immediately.
          - Initialize (guarded by Get-Command fzf.exe):
              * Composes $env:FZF_DEFAULT_OPTS — the baseline for every fzf invocation (plain fzf,
                zoxide's `cdi`/`zi`, and PSFzf's widgets) — from "--ansi" plus "--style=<preset>"
                (when -Style is given) and "--color=<spec>" (when -Colors is given). It deliberately
                carries NO --preview, so directory pickers like zoxide's `cdi` stay clean. "--ansi"
                is always set as the baseline (it renders colored source output, e.g. Enable-Fd's
                `fd --color=always`, and is a no-op otherwise).
              * When -PreviewCommand is given, sets $env:FZF_CTRL_T_OPTS to "--preview '<command>'".
                PSFzf layers this on top of FZF_DEFAULT_OPTS for the Ctrl+T file picker only — so the
                bat preview shows for file searches but never for directory pickers. (Initialize-
                PwshProfile passes a `bat` command when bat is in play; bat inherits $env:BAT_THEME.)
              * When -Height is given, sets $env:_PSFZF_FZF_DEFAULT_OPTS (the base opts plus
                "--height=<value>"). PSFzf's PSReadLine widgets read that var in preference to
                FZF_DEFAULT_OPTS and otherwise force --height=40% (opening inline below the prompt);
                supplying our own --height both suppresses that default and sizes the pickers (100%
                fills the shell). FZF_DEFAULT_OPTS itself stays height-free, so a bare fzf and
                zoxide's `cdi` keep their native alternate-screen fullscreen.
              * fzf ships NO PowerShell key bindings, so when -ProviderChord / -HistoryChord / -UseFd
                / -GitKeyBindings is requested, it imports the PSFzf module (via Import-ModuleSafe)
                and calls Set-PsFzfOption to bind Ctrl+T (file picker) / Ctrl+R (fuzzy history), make
                PSFzf use fd for traversal (-EnableFd), and register the Ctrl+G fuzzy-git chords.
                PSReadLine must load before PSFzf — Initialize-PwshProfile's Shell step ensures that.
              * When -TabExpansionChord is given, binds that PSReadLine chord to PSFzf's
                Invoke-FzfTabCompletion (via Set-PSReadLineKeyHandler) so the chord opens a fuzzy fzf
                picker over PowerShell's native completion candidates. Tab is intentionally left as
                MenuComplete — Set-PsFzfOption -TabExpansion only ever targets Tab, so a non-Tab chord
                must be bound directly.
 
        If the install doesn't produce fzf.exe on PATH, a warning is emitted (with winget's
        captured output) and Initialize is skipped (guarded by Get-Command) so profile startup
        continues either way.
 
        fzf and zoxide are independent, standalone tools, but zoxide is built to integrate with
        fzf: when fzf.exe is on PATH, zoxide's interactive directory picker (`cdi` / `zi`)
        automatically uses fzf for fuzzy selection (and inherits the --color/--style set here).
 
    .PARAMETER Colors
        An fzf color spec (the value passed to fzf's `--color`, e.g.
        'hl:#5fd7ff,pointer:#c9aaff,prompt:#c9aaff'). When non-empty it is folded into
        $env:FZF_DEFAULT_OPTS as "--color=<spec>", so fzf's picker matches the prompt theme.
        Initialize-PwshProfile resolves this from the active theme's branding.
 
    .PARAMETER Style
        An fzf `--style` UI preset ('default', 'minimal', or 'full'). When non-empty it is folded
        into $env:FZF_DEFAULT_OPTS as "--style=<preset>". `--style` is an fzf 0.54+ feature, so it is
        applied only when the installed fzf is new enough (checked once via Get-FzfVersion) — a
        pre-existing older fzf that the install short-circuit didn't upgrade would otherwise fail on
        the unknown option. Initialize-PwshProfile passes 'full'.
 
    .PARAMETER Height
        An fzf `--height` value for the PSFzf PSReadLine widgets (Ctrl+T/Ctrl+R/git), e.g. '100%'
        (fills the entire shell), '~100%' (adaptive — shrinks to fit small result sets), or '40%'.
        When non-empty it is written, alongside the base opts, to $env:_PSFZF_FZF_DEFAULT_OPTS, which
        PSFzf reads in preference to $env:FZF_DEFAULT_OPTS. This overrides PSFzf's built-in
        --height=40% default (which opens the widgets inline below the prompt). Empty leaves that 40%
        default in place. Note: --height renders inline, not on the alternate screen, so it never
        perfectly matches a bare fzf's fullscreen — 100% is the closest. Initialize-PwshProfile passes
        '100%'.
 
    .PARAMETER PreviewCommand
        A command for the Ctrl+T file picker's `--preview` window, with `{}` standing in for the
        current line. When non-empty it is written to $env:FZF_CTRL_T_OPTS as "--preview '<command>'"
        — scoped to the Ctrl+T widget (PSFzf), NOT the global $env:FZF_DEFAULT_OPTS, so it never
        leaks into directory pickers like zoxide's `cdi`. Initialize-PwshProfile passes a `bat`
        command (when bat is in play) so files preview with syntax highlighting; bat inherits
        $env:BAT_THEME so the colors match the prompt.
 
    .PARAMETER ProviderChord
        The PSReadLine chord to bind to PSFzf's file/path picker (e.g. 'Ctrl+t'). When non-empty,
        PSFzf is installed/imported and the binding is registered. Empty leaves the chord unbound.
 
    .PARAMETER HistoryChord
        The PSReadLine chord to bind to PSFzf's fuzzy command-history search (e.g. 'Ctrl+r'). When
        non-empty, PSFzf is installed/imported and the binding is registered, overriding PSReadLine's
        native reverse-search on that chord. Empty leaves the chord unbound.
 
    .PARAMETER TabExpansionChord
        A PSReadLine chord to bind to PSFzf's Invoke-FzfTabCompletion (e.g. 'Ctrl+Spacebar'). When
        non-empty, PSFzf is installed/imported and the chord opens a fuzzy fzf picker over PowerShell's
        native completion candidates — paths, cmdlet/parameter names, and every registered argument
        completer (winget/az/gh/docker/tailscale/op, posh-git, etc.) — inheriting the theme/height from
        $env:_PSFZF_FZF_DEFAULT_OPTS. A single candidate inserts directly (no picker). Tab is left
        untouched (it stays PSReadLine's MenuComplete); Set-PsFzfOption -TabExpansion only ever targets
        Tab, which is why this binds Invoke-FzfTabCompletion directly. Empty leaves the chord unbound.
        Initialize-PwshProfile passes 'Ctrl+Spacebar' (a chord that otherwise duplicates Tab's
        MenuComplete, so repurposing it loses nothing).
 
    .PARAMETER UseFd
        When set, calls Set-PsFzfOption -EnableFd so PSFzf uses fd for its file/directory traversal
        (Initialize-PwshProfile passes this when fd is in play). Set-PsFzfOption only records the
        option; fd is invoked later at Ctrl+T-time, by which point the fd step has installed it.
 
    .PARAMETER GitKeyBindings
        When set (and git is on PATH), calls Set-PsFzfOption -GitKeyBindings to register PSFzf's
        Ctrl+G,Ctrl+<key> fuzzy-git chord family (files, branches, hashes, tags, stashes). Guarded by
        Get-Command git so a git-less machine isn't left with dead Ctrl+G chords.
 
    .EXAMPLE
        Enable-Fzf
 
        Installs fzf if needed and sets $env:FZF_DEFAULT_OPTS to the baseline '--ansi' (so colored
        source output renders), leaving $env:FZF_CTRL_T_OPTS untouched and PSFzf uninstalled.
 
    .EXAMPLE
        Enable-Fzf -Colors 'hl:#5fd7ff,pointer:#c9aaff' -Style full -Height '100%' `
            -PreviewCommand 'bat --color=always --style=numbers {}' `
            -ProviderChord 'Ctrl+t' -HistoryChord 'Ctrl+r' -TabExpansionChord 'Ctrl+Spacebar' `
            -UseFd -GitKeyBindings
 
        Themes fzf (Screw City palette, full UI style), gives the Ctrl+T file picker a bat preview,
        and (via PSFzf) binds Ctrl+T / Ctrl+R fullscreen, puts a fuzzy completion picker on
        Ctrl+Spacebar (Tab stays MenuComplete), uses fd for traversal, and adds the Ctrl+G git chords.
 
    .NOTES
        Standalone fuzzy finder (https://github.com/junegunn/fzf). fzf ships no PowerShell key
        bindings; the community PSFzf module (https://github.com/kelleyma49/PSFzf) supplies them,
        which is why the key-binding parameters install/import it. fzf owns its own options
        ($env:FZF_DEFAULT_OPTS / $env:FZF_CTRL_T_OPTS, plus $env:_PSFZF_FZF_DEFAULT_OPTS — PSFzf's
        widget-only override, used here to size the pickers via -Height); the "use fd as fzf's source"
        wiring ($env:FZF_DEFAULT_COMMAND) lives in Enable-Fd. The preview is Ctrl+T-scoped on purpose:
        zoxide's `cdi`/`zi` reads only FZF_DEFAULT_OPTS, so keeping the preview out of it leaves the
        directory picker clean.
 
        PSFzf double-quotes any completion candidate containing whitespace — including the trailing
        "this token is complete" space that several completers append (argcomplete-based `az`, Cobra
        CLIs in MenuComplete mode like `gh`/`tailscale`/`op`, winget) — so those would insert as
        `"account "` instead of `account `. After importing PSFzf this calls
        Repair-PsFzfCompletionQuoting, which trims that trailing space inside PSFzf's own quoting
        helper so fuzzy completions insert unquoted (completers that emit no trailing space, e.g.
        posh-git's git completer, were already fine and stay unchanged).
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$Colors = '',

        [Parameter()]
        [string]$Style = '',

        [Parameter()]
        [string]$Height = '',

        [Parameter()]
        [string]$PreviewCommand = '',

        [Parameter()]
        [string]$ProviderChord = '',

        [Parameter()]
        [string]$HistoryChord = '',

        [Parameter()]
        [string]$TabExpansionChord = '',

        [Parameter()]
        [switch]$UseFd,

        [Parameter()]
        [switch]$GitKeyBindings
    )

    Invoke-Step "Install" {
        # fzf is a winget portable: its exe lands in the default Links dir.
        Install-WingetPackageSafe -Id 'junegunn.fzf' -Exe 'fzf.exe' -CallerName 'Enable-Fzf'
    }

    Invoke-Step "Initialize" {
        if (Get-Command fzf.exe -ErrorAction SilentlyContinue) {
            # Global baseline opts (read by EVERY fzf invocation, incl. zoxide's cdi): theme + style
            # only, NO --preview, so directory pickers stay clean. Plain assignment — env vars are
            # process-global. --ansi renders ANSI-colored source output (e.g. fd --color=always).
            $opts = [System.Collections.Generic.List[string]]::new()
            $opts.Add('--ansi')
            # --style is an fzf 0.54+ feature. The Install substep short-circuits when fzf.exe is
            # already on PATH, so it can't assume winget just supplied a current build — a pre-existing
            # older fzf would choke on --style and fail *every* fzf invocation (and zoxide's cdi). So
            # gate on the installed version (one `fzf --version` probe per session, via Get-FzfVersion,
            # and only when a -Style was actually requested); an undeterminable version ($null) is
            # treated as too-old and skips --style.
            if (-not [string]::IsNullOrWhiteSpace($Style)) {
                $fzfVersion = Get-FzfVersion
                if ($fzfVersion -and $fzfVersion -ge [version]'0.54') { $opts.Add("--style=$Style") }
            }
            if (-not [string]::IsNullOrWhiteSpace($Colors)) { $opts.Add("--color=$Colors") }

            # Always assign OPTS so --ansi is a guaranteed baseline: Enable-Fd's `fd --color=always`
            # source command relies on it to render colored output rather than raw escape codes, and
            # --ansi is a no-op when the input carries no color.
            $env:FZF_DEFAULT_OPTS = ($opts -join ' ')

            # PSFzf's PSReadLine widgets (Ctrl+T/Ctrl+R/git) force --height=40% unless the opts it
            # reads already carry a --height. PSFzf reads _PSFZF_FZF_DEFAULT_OPTS in preference to
            # FZF_DEFAULT_OPTS, so giving it its own opts (= the base + an explicit --height) both
            # suppresses that 40% default and sizes the pickers, while FZF_DEFAULT_OPTS stays
            # height-free → a bare fzf and zoxide's cdi keep their native alternate-screen fullscreen.
            # Assign unconditionally (like FZF_DEFAULT_OPTS above) so a live-session reload that drops
            # -Height resets to the height-free baseline rather than leaving a stale --height behind.
            $env:_PSFZF_FZF_DEFAULT_OPTS = if (-not [string]::IsNullOrWhiteSpace($Height)) {
                "$env:FZF_DEFAULT_OPTS --height=$Height"
            }
            else { $env:FZF_DEFAULT_OPTS }

            # The bat preview is scoped to PSFzf's Ctrl+T file picker (FZF_CTRL_T_OPTS), never the
            # global opts — so it shows for file searches but not for directory pickers like cdi.
            # Assign unconditionally so a reload that drops -PreviewCommand clears a stale preview.
            $env:FZF_CTRL_T_OPTS = if (-not [string]::IsNullOrWhiteSpace($PreviewCommand)) {
                "--preview '$PreviewCommand'"
            }
            else { '' }

            # fzf ships no PowerShell key bindings — PSFzf provides them. Build the option set first
            # (the Ctrl+G git chords only when git is present, so a git-less machine isn't left with
            # dead bindings), then install/import PSFzf and apply it only when something will actually
            # be set — so e.g. a lone -GitKeyBindings on a git-less box doesn't pull PSFzf in for nothing.
            $psfzf = @{}
            if (-not [string]::IsNullOrWhiteSpace($ProviderChord)) { $psfzf.PSReadlineChordProvider = $ProviderChord }
            if (-not [string]::IsNullOrWhiteSpace($HistoryChord))  { $psfzf.PSReadlineChordReverseHistory = $HistoryChord }
            if ($UseFd) { $psfzf.EnableFd = $true }
            if ($GitKeyBindings -and (Get-Command git -ErrorAction SilentlyContinue)) { $psfzf.GitKeyBindings = $true }
            # -TabExpansionChord also needs PSFzf (it binds PSFzf's Invoke-FzfTabCompletion), so fold it
            # into the "do we need PSFzf?" decision even though it's not a Set-PsFzfOption option.
            $needPsfzf = $psfzf.Count -gt 0 -or -not [string]::IsNullOrWhiteSpace($TabExpansionChord)
            if ($needPsfzf) {
                Import-ModuleSafe PSFzf
                # PSFzf double-quotes any completion candidate containing whitespace — including
                # the trailing "complete" space that argcomplete (az), Cobra MenuComplete
                # (gh/tailscale/op), and winget append — so they'd insert as `"account "`. Patch
                # PSFzf's FixCompletionResult to trim that trailing space so fuzzy completions
                # insert unquoted. No-op when PSFzf didn't load. Benefits Ctrl+T too, not just the
                # Tab-expansion chord, so it runs whenever PSFzf is imported.
                Repair-PsFzfCompletionQuoting
                if ($psfzf.Count -gt 0 -and (Get-Command Set-PsFzfOption -ErrorAction SilentlyContinue)) {
                    Set-PsFzfOption @psfzf
                }
                # Fuzzy completion on its own chord, NOT Tab: Set-PsFzfOption -TabExpansion only ever
                # targets Tab, so we bind Invoke-FzfTabCompletion directly and leave Tab = MenuComplete.
                # Invoke-FzfTabCompletion feeds PowerShell's native completions (paths, cmdlet/parameter
                # names, and every registered argument completer) into fzf, and the picker inherits the
                # theme + height from $env:_PSFZF_FZF_DEFAULT_OPTS. The scriptblock resolves the global
                # PSFzf Invoke-FzfTabCompletion at key-press time.
                if (-not [string]::IsNullOrWhiteSpace($TabExpansionChord) -and
                    (Get-Command Invoke-FzfTabCompletion -ErrorAction SilentlyContinue)) {
                    Set-PSReadLineKeyHandler -Key $TabExpansionChord -ScriptBlock { Invoke-FzfTabCompletion } `
                        -BriefDescription 'FzfTabCompletion' `
                        -Description 'Fuzzy completion picker via fzf (PSFzf)'
                }
            }
        }
    }
}