Public/Shell.ps1

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

# The shell layer: what `Invoke-Expression (& { (zoxide init powershell | Out-String) })` sets up,
# done by Import-Module. Install-ZoxideHook renders the same init script (Get-ZoInitScript, from
# zoxide 0.10.0's templates/powershell.txt) and applies it the way evaluating it would:
#
# - __zoxide_bin, __zoxide_pwd, __zoxide_cd, __zoxide_hook, __zoxide_z and __zoxide_zi become
# global functions with the template's own text. They are created from unbound script blocks,
# not as module functions, because PowerShell keeps the `cd -` / `cd +` location history per
# session state: code that runs inside a module cannot see or extend the history of the
# session that typed `cd`, so a module-bound `z -` would fail. Unbound, they run in the
# caller's session state, exactly like the template's functions. Their `zoxide` calls reach
# this module through the exported `zoxide` alias.
# - The prompt is wrapped once, guarded by $global:__zoxide_hooked, so importing the module
# again, or also evaluating the binary's init output before or after, leaves one wrapper.
# - The z and zi aliases are set globally, as the template sets them.
#
# _ZO_HOOK, _ZO_CMD and _ZO_NO_CMD stand in for --hook, --cmd and --no-cmd (Get-ZoShellOption), and
# _ZO_ECHO is read when the script is rendered, as `zoxide init` reads it.

function Install-ZoxideHook {
    <#
    .SYNOPSIS
        Installs z, zi, the __zoxide_* functions and the prompt hook, as `zoxide init powershell` would.
    .DESCRIPTION
        Mirrors evaluating the output of Init::run (cmd/init.rs) over templates/powershell.txt,
        with _ZO_HOOK, _ZO_CMD and _ZO_NO_CMD in place of --hook, --cmd and --no-cmd. Every call
        (re)defines the template's global __zoxide_* functions from the rendered text. Unless the
        hook is none it also sets $global:__zoxide_oldpwd (pwd hook) and, only while
        $global:__zoxide_hooked is not 1, sets it to 1, keeps the current prompt in
        $global:__zoxide_prompt_old and replaces the prompt with the template's wrapper. Unless
        _ZO_NO_CMD is 1 it sets the global aliases <cmd> and <cmd>i (default z and zi).
        PSZoxide.psm1 calls this on import.
    .EXAMPLE
        $env:_ZO_CMD = 'j'; Install-ZoxideHook
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Justification = 'The template keeps its hook state in $global:__zoxide_*; both must share it to hook once.')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalAliases', '', Justification = 'The template defines z and zi with -Scope Global; the binary init output must be able to replace them.')]
    [CmdletBinding()]
    param()
    $options = Get-ZoShellOption
    $cmd = if ($options.NoCmd) { $null } else { $options.Cmd }
    # The function bodies never depend on cmd, so they come from a render without it: no user
    # text reaches the parser that turns the template into global functions.
    $functions = Get-ZoInitFunction -Script (Get-ZoInitScript -Cmd $null -Hook $options.Hook -Echo (Test-ZoEcho))

    foreach ($name in $functions.Keys) {
        if ($name -ne 'prompt') { Microsoft.PowerShell.Management\Set-Item -Path "function:global:$name" -Value $functions[$name] }
    }
    if ($options.Hook -ne 'none') {
        if ($options.Hook -eq 'pwd') { $global:__zoxide_oldpwd = __zoxide_pwd }
        $global:__zoxide_hooked = Microsoft.PowerShell.Utility\Get-Variable -Name __zoxide_hooked -Scope Global -ValueOnly -ErrorAction Ignore
        if ($global:__zoxide_hooked -ne 1) {
            $global:__zoxide_hooked = 1
            $global:__zoxide_prompt_old = $function:prompt
            Microsoft.PowerShell.Management\Set-Item -Path 'function:global:prompt' -Value $functions['prompt']
        }
    }
    if ($cmd) {
        Microsoft.PowerShell.Utility\Set-Alias -Name $cmd -Value __zoxide_z -Option AllScope -Scope Global -Force
        Microsoft.PowerShell.Utility\Set-Alias -Name "${cmd}i" -Value __zoxide_zi -Option AllScope -Scope Global -Force
    }
}