Public/Tools/Enable-Bat.ps1

function Enable-Bat {
    <#
    .SYNOPSIS
        Installs (if necessary) and activates bat, a cat clone with syntax highlighting, for the
        session.
 
    .DESCRIPTION
        Runs two nested Invoke-Step substeps:
          - Install: if bat.exe isn't on PATH, installs it with winget (sharkdp.bat, a portable
            package) and patches the current session's PATH so the Initialize substep can see it
            immediately.
          - Initialize (guarded by Get-Command bat.exe): configures bat's appearance and tab
            completion for the session:
              * Sets $env:BAT_THEME to the chosen syntax theme (when -Theme is non-empty) so bat's
                colors blend with the active oh-my-posh theme, and $env:BAT_STYLE to the chosen
                layout components.
              * When -ReplaceCat is set, aliases cat -> bat.exe globally (with -Force, since the
                built-in cat alias for Get-Content is ReadOnly), so `cat file` renders through bat.
              * Registers bat's PowerShell tab completion. bat emits a Register-ArgumentCompleter
                script via `bat --completion ps1`; it is run through Invoke-InGlobalScope (not a bare
                Invoke-Expression) so the registered completer lands in the true global scope and
                isn't tagged to this module — see Private/Core/Invoke-InGlobalScope.ps1. When
                -ReplaceCat is set, the completer's -CommandName is extended to also cover the `cat`
                alias (PowerShell completers don't follow aliases), so `cat <Tab>` completes bat's
                flags too — the same trick Enable-Xh uses for http/https.
 
        If the install doesn't produce bat.exe on PATH, a warning is emitted (with winget's captured
        output) and Initialize is skipped (guarded by Get-Command) so profile startup continues.
 
    .PARAMETER Theme
        The bat syntax-highlighting theme, assigned to $env:BAT_THEME for the session (a value from
        `bat --list-themes`, e.g. 'Dracula' or 'gruvbox-dark'). Initialize-PwshProfile resolves this
        from the active theme's branding so bat's colors match the prompt. An empty value leaves
        $env:BAT_THEME untouched (bat keeps its own default).
 
    .PARAMETER Style
        The bat layout, assigned to $env:BAT_STYLE — a comma-separated list of components (e.g.
        'numbers,changes,header', 'full', 'plain'). Defaults to 'numbers,changes,header': line
        numbers, git change marks, and a file header, without the heavier grid.
 
    .PARAMETER ReplaceCat
        When set, aliases cat -> bat.exe in the global scope, so the built-in cat (an alias for
        Get-Content) is replaced by bat for the session, and extends bat's registered completer to the
        `cat` alias so it tab-completes like `bat`. Off by default, leaving cat (and its completion)
        untouched.
 
    .EXAMPLE
        Enable-Bat -Theme Dracula
 
        Installs bat if needed and sets its theme to Dracula with the default style, leaving cat alone.
 
    .EXAMPLE
        Enable-Bat -Theme gruvbox-dark -ReplaceCat
 
        Sets the gruvbox-dark theme and replaces the cat alias with bat for the session.
 
    .NOTES
        Unlike jq, bat ships a PowerShell completer: `bat --completion ps1` emits a
        Register-ArgumentCompleter script, registered here in the Initialize substep (run in the
        global scope so it isn't attributed to the module). Its -CommandName is `'bat'` (bat uses
        `-Native` and single quotes, unlike xh's `'xh'`); under -ReplaceCat a string -replace extends
        it to `'bat', 'cat'` so the cat alias completes — coupled to that exact literal, so a future
        bat build that re-quotes it would silently drop only the alias's completion (bat still
        completes). bat's themes blend with the bundled oh-my-posh themes when driven through
        Initialize-PwshProfile (screwcity -> Dracula, forestcity -> gruvbox-dark).
    #>

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

        [Parameter()]
        [string]$Style = 'numbers,changes,header',

        [Parameter()]
        [switch]$ReplaceCat
    )

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

    Invoke-Step "Initialize" {
        if (Get-Command bat.exe -ErrorAction SilentlyContinue) {
            # Drive bat's appearance through environment variables (process-global already, so these
            # are plain assignments — no Invoke-InGlobalScope needed for env vars).
            if (-not [string]::IsNullOrWhiteSpace($Theme)) { $env:BAT_THEME = $Theme }
            if (-not [string]::IsNullOrWhiteSpace($Style)) { $env:BAT_STYLE = $Style }

            if ($ReplaceCat) {
                # The built-in cat alias (Get-Content) is ReadOnly, so -Force is required to retarget it.
                Set-Alias -Name cat -Value bat.exe -Scope Global -Force
            }

            # Register bat's completer in the global scope (not this module's) so it isn't tagged to
            # the module — see Private/Core/Invoke-InGlobalScope.ps1.
            $batCompletion = bat --completion ps1
            if ($ReplaceCat) {
                # Extend bat's own completer registration to also cover the `cat` alias, so `cat <Tab>`
                # completes bat's flags. PowerShell completers don't follow aliases, so the alias must be
                # named explicitly. Coupled to bat's exact output (the literal `-CommandName 'bat'`; note
                # bat uses `-Native` and single quotes — unlike xh's `-CommandName 'xh'`). If a future bat
                # build changes that quoting/spacing the replace silently no-ops and `cat` loses completion
                # (bat itself still completes). Gated on -ReplaceCat: without the alias, `cat` is still
                # Get-Content, and registering bat's flag completer onto it would be wrong.
                $batCompletion = $batCompletion -replace "-CommandName 'bat'", "-CommandName 'bat', 'cat'"
            }
            Invoke-InGlobalScope ($batCompletion | Out-String)
        }
    }
}