Skip to content

perf(pwsh): lazy-load PSFzf off the shell-render path#77

Merged
Gerrrt merged 2 commits into
mainfrom
perf/lazy-psfzf-init
Jul 1, 2026
Merged

perf(pwsh): lazy-load PSFzf off the shell-render path#77
Gerrrt merged 2 commits into
mainfrom
perf/lazy-psfzf-init

Conversation

@Gerrrt

@Gerrrt Gerrrt commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

What

Defers the Import-Module PSFzf cost (~260ms, measured — the single biggest slice of core/10-tools.ps1) out of the synchronous startup path. It only matters when you press Ctrl+T / Ctrl+R, so it's now paid once, on first use.

How

  • Env stays eager (FZF_DEFAULT_OPTS / FZF_DEFAULT_COMMAND) — cheap and inherited by child panes.
  • Stub key handlers bind Ctrl+T/Ctrl+R at load. On first press, Invoke-DotLoadPSFzf imports PSFzf, calls Set-PsFzfOption (which overwrites the stubs with PSFzf's real handlers), then fires the requested action so the first keystroke isn't swallowed. Later presses skip the loader entirely.
  • Session-runspace trigger, not OnIdle — an OnIdle event action would import into the eventing runspace where the interactive shell can't see it.
  • Atuin Ctrl+R handoff threaded through the deferral: after atuin's init seizes Ctrl+R, re-assert the lazy stub (normal path) / hand Ctrl+R to PSFzf if already loaded / fall back to ReverseSearchHistory when PSFzf isn't installed.

Cache audit (no change needed)

The original ask assumed the Get-InitCache cache was invalidating every launch. Audited it: the starship/zoxide/mise/atuin caches all resolve stale=False (mtime + generator-hash both pass), so the cache is healthy and the residual cold-start cost is genuine per-process init, not regeneration. No cache change made.

Verification

  • Clean-shell functional test: stub → first-use import → both chords flip to PSFzf's real handlers (Fzf Provider Select / Fzf Reverse History Select).
  • Full Pester 5.6.1 suite: 556 passed, 0 failed.
  • Pre-commit validation gate passed (parse, manifests, PSScriptAnalyzer — no new warnings).

🤖 Generated with Claude Code

`Import-Module PSFzf` costs ~260ms (measured) — the single biggest slice
of 10-tools.ps1 — but it's dead weight until Ctrl+T/Ctrl+R is pressed.
Defer it: keep FZF_DEFAULT_OPTS/COMMAND eager (cheap, child-inherited),
bind lightweight stub key handlers, and pay the import + install PSFzf's
real handlers on the first press of either chord via Invoke-DotLoadPSFzf.
Set-PsFzfOption overwrites the stubs, so later presses skip the loader.

The stub scriptblock runs in the session runspace on keypress, so the
deferred Import-Module lands where the interactive shell sees it (an
OnIdle event action would import into the eventing runspace instead).

Thread the deferral through the atuin Ctrl+R handoff: after atuin's init
seizes Ctrl+R, re-assert the lazy stub (normal path), or hand Ctrl+R to
PSFzf if already loaded, or fall back to ReverseSearchHistory when PSFzf
isn't installed.

Audited Get-InitCache while here: the starship/zoxide/mise/atuin caches
resolve stale=False (mtime + generator-hash both pass), so the cache is
healthy and the residual cold-start cost is genuine per-process init, not
regeneration. No cache change needed.

Full Pester suite: 556 passed, 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 1, 2026 23:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes PowerShell profile startup by deferring the expensive Import-Module PSFzf work (~260ms) out of the synchronous shell-render path, loading PSFzf only on first use of the relevant key chords.

Changes:

  • Adds lazy-loading stubs for PSFzf keybindings (Ctrl+T / Ctrl+R) and a one-shot loader function that imports PSFzf on first use.
  • Keeps FZF_DEFAULT_OPTS / FZF_DEFAULT_COMMAND eager to preserve cheap, child-pane-inherited environment configuration.
  • Adjusts the atuin Ctrl+R handoff logic to re-assert the lazy stub (or fall back to ReverseSearchHistory) after atuin initialization.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread powershell/core/10-tools.ps1 Outdated
Comment on lines +318 to +330
# swallowed. Handler names verified against PSFzf 2.7.10 (Invoke-FzfPsReadlineHandler*).
function global:Invoke-DotLoadPSFzf {
param([ValidateSet('Provider','History')][string]$Action)
Import-Module PSFzf
Set-PsFzfOption -PSReadlineChordProvider 'Ctrl+t' -PSReadlineChordReverseHistory 'Ctrl+r'
switch ($Action) {
'Provider' { Invoke-FzfPsReadlineHandlerProvider }
'History' { Invoke-FzfPsReadlineHandlerHistory }
}
}
# Cheap stubs bound now; the ~260ms import is deferred to first use, off the render path.
Set-PSReadLineKeyHandler -Chord 'Ctrl+t' -BriefDescription 'PSFzf file picker (lazy load)' -ScriptBlock { Invoke-DotLoadPSFzf -Action Provider }
Set-PSReadLineKeyHandler -Chord 'Ctrl+r' -BriefDescription 'PSFzf history (lazy load)' -ScriptBlock { Invoke-DotLoadPSFzf -Action History }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — good catch on the hard-coding risk. I verified it empirically: downloaded PSFzf 2.4.0 (the exact baseline pin in packages/modules.ps1:27) and both Invoke-FzfPsReadlineHandlerProvider and Invoke-FzfPsReadlineHandlerHistory are present there, so the names are stable 2.4.0 → 2.7.10 and the first Ctrl+T/Ctrl+R press is safe on a fresh box before the maintenance runner upgrades anything.

I did take the robustness half of your suggestion (788538f): the loader now wraps Import-Module PSFzf -ErrorAction Stop + Set-PsFzfOption in try/catch and warns via Write-DotWarn on failure (matching the starship/atuin/CompletionPredictor init guards), returning with the stub still bound so a later press retries. The handler invocation stays outside the try so an Esc-cancelled fzf isn't misreported as an init failure.

Address PR review: wrap the deferred Import-Module PSFzf + Set-PsFzfOption
in try/catch so a broken/removed PSFzf surfaces as a friendly Write-DotWarn
instead of a raw error at the prompt (mirrors the starship/atuin/
CompletionPredictor init guards). On failure the stub stays bound, so a
later keypress retries. The real handler is invoked outside the try, so an
Esc-cancelled fzf isn't misreported as an init failure.

The reviewer also worried the Invoke-FzfPsReadlineHandler* names might be
absent on the pinned baseline PSFzf 2.4.0 — verified by downloading 2.4.0:
both Invoke-FzfPsReadlineHandlerProvider and ...History are present, so the
first press is safe on a fresh box. Comment updated to cite the baseline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Gerrrt Gerrrt merged commit aedcb27 into main Jul 1, 2026
6 checks passed
@Gerrrt Gerrrt deleted the perf/lazy-psfzf-init branch July 1, 2026 23:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants