Skip to content

fix: use non-interactive pager in fzf preview for TUI pagers#498

Merged
wfxr merged 1 commit intomainfrom
tui-pager
Mar 14, 2026
Merged

fix: use non-interactive pager in fzf preview for TUI pagers#498
wfxr merged 1 commit intomainfrom
tui-pager

Conversation

@wfxr
Copy link
Owner

@wfxr wfxr commented Mar 11, 2026

Check list

  • I have performed a self-review of my code
  • I have commented my code in hard-to-understand areas
  • I have added unit tests for my code
  • I have made corresponding changes to the documentation

Description

Fixes #491.

When users configure a TUI/interactive diff pager (e.g., diffnav, tig) via git config pager.diff, all fzf preview panes show blank output. This is because fzf preview commands do not run in a terminal, and TUI pagers require a TTY to render.

This PR adds a FORGIT_PREVIEW_PAGER env var that is used only in actual fzf preview context. Preview detection now relies on fzf's FZF_PREVIEW_COLUMNS environment variable, so the override applies only to preview commands and leaves existing Enter/fullscreen behavior unchanged.

Users can set FORGIT_PREVIEW_PAGER to a non-interactive pager like delta to get working previews while keeping the rest of the pager flow unchanged.

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Breaking change
  • Test
  • Documentation change

Test environment

  • Shell
    • bash
    • zsh
    • fish
  • OS
    • Linux
    • Mac OS X
    • Windows
    • Others:

Summary by CodeRabbit

  • New Features

    • Added FORGIT_PREVIEW_PAGER configuration option, allowing users to specify a dedicated non-interactive pager for preview panes in TUI environments. Resolves blank preview issues with fzf by supporting pagers like delta.
  • Documentation

    • Updated README with FORGIT_PREVIEW_PAGER documentation, including pager resolution behavior and configuration guidance.

@coderabbitai
Copy link

coderabbitai bot commented Mar 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a preview-aware pager flow and a preview wrapper: introduces FORGIT_PREVIEW_PAGER, a _forgit_preview wrapper and preview private command, routes existing --preview invocations through that preview path, and adds tests and README documentation. No public command signature changes.

Changes

Cohort / File(s) Summary
Documentation
README.md
Adds FORGIT_PREVIEW_PAGER to Pagers section; documents that TUI (interactive) pagers can break fzf preview panes and that FORGIT_PREVIEW_PAGER (non-interactive) fixes previews; notes it overrides other FORGIT_*_PAGER in preview context.
Core script (preview flow & pager selection)
bin/git-forgit
Introduces preview-aware pager selection (use FORGIT_PREVIEW_PAGER when preview context is active), adds _forgit_preview wrapper that sets preview flag, registers a private preview command, and rewires many --preview invocations to route through the preview wrapper. Pager behavior otherwise preserved.
Tests
tests/preview-context.test.sh
New test suite validating preview-context behavior: preview pager selection, preview flag propagation via _forgit_preview, and that diff/show preview strings are routed through the preview wrapper. Uses mocks and a temporary repo setup.

Sequence Diagram(s)

sequenceDiagram
  participant FZF as "fzf (preview)"
  participant Forgit as "git-forgit"
  participant GitConfig as "git config / env"
  participant Pager as "pager program"

  FZF->>Forgit: invoke preview command (preview subprocess)
  Forgit->>Forgit: set FORGIT_IN_PREVIEW via _forgit_preview
  Forgit->>GitConfig: check FORGIT_PREVIEW_PAGER / other pager settings
  alt FORGIT_PREVIEW_PAGER set and IN_PREVIEW
    Forgit->>Pager: launch FORGIT_PREVIEW_PAGER (non-interactive)
  else fallback
    Forgit->>Pager: resolve and launch normal pager
  end
  Pager-->>Forgit: stream output
  Forgit-->>FZF: deliver preview content
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nudged the preview, set a gentler pager,
No TUI ghosts to fog the pager wager.
Previews hum, not stalled or black,
I box the flow in a kinder track,
Hoppity — the panes return, no danger.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding support for non-interactive pagers in fzf preview contexts for TUI pagers, which directly addresses the root cause described in issue #491.
Description check ✅ Passed The PR description follows the template structure, includes a checklist with relevant items checked, clearly references issue #491, explains the problem and solution, specifies the type of change (bug fix and documentation change), and documents the test environment (bash and zsh on Linux).
Linked Issues check ✅ Passed The PR fully addresses the objectives from issue #491 by introducing FORGIT_PREVIEW_PAGER for non-TTY contexts, using FZF_PREVIEW_COLUMNS for detection, preserving fullscreen behavior, and improving discoverability over the previous workaround.
Out of Scope Changes check ✅ Passed All changes are in scope: README.md documents the new FORGIT_PREVIEW_PAGER option, bin/git-forgit implements preview-aware pager selection with an explicit preview marker and wrapper, and tests/preview-context.test.sh provides regression tests validating the preview behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tui-pager
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@wfxr wfxr force-pushed the tui-pager branch 2 times, most recently from 1265734 to 672394f Compare March 11, 2026 02:14
@sandr01d
Copy link
Collaborator

I might very well be missing something here, but when testing this I do not get the interactive pager when pressing enter in forgit diff, which from the description I should. The only way I could trigger the fallback to the interactive pager when FORGIT_PREVIEW_PAGER is set is when pressing enter in forgit blame. Is this expected or something we need to address @wfxr?

@wfxr
Copy link
Owner Author

wfxr commented Mar 12, 2026

@sandr01d You were right to call this out.

I re-checked #491 and the scope here is only fixing blank fzf previews for TUI pagers. I did try to make TUI pagers work in the Enter/fullscreen path as well, but that turned out to be a separate problem and introduced extra complexity and compatibility concerns around the existing fullscreen pager flow.

So I narrowed this PR back to the minimal fix.

What changed now is:

  • FORGIT_PREVIEW_PAGER is only used in actual fzf preview context
  • preview detection now relies on fzf's FZF_PREVIEW_COLUMNS env var instead of the previous stdout TTY check
  • Enter/fullscreen behavior stays unchanged

@sandr01d
Copy link
Collaborator

Even with the new approach, I still get the preview pager when FORGIT_PREVIEW_PAGER is set and I'm pressing enter in forgit diff. My guess would be that FZF_PREVIEW_COLUMNS is set in this case because the command is invoked through fzf's execute function.

@wfxr
Copy link
Owner Author

wfxr commented Mar 13, 2026

Even with the new approach, I still get the preview pager when FORGIT_PREVIEW_PAGER is set and I'm pressing enter in forgit diff. My guess would be that FZF_PREVIEW_COLUMNS is set in this case because the command is invoked through fzf's execute function.

Fixed.

This turned out to be trickier than I expected.

I first looked at using inferred signals to detect preview context:

  • FZF_PREVIEW_COLUMNS
  • [[ ! -t 1 ]]

But both run into the same problem here: the enter path for forgit diff / forgit show goes through fzf execute(...) and a pipe into the fullscreen pager, so those signals can also show up outside the actual --preview command. That means they can still misclassify Enter/fullscreen as preview.

So instead of trying to infer preview context, I switched to making it explicit:

  • add a small private preview wrapper
  • route all fzf --preview commands through it
  • set FORGIT_IN_PREVIEW=1 there
  • only use FORGIT_PREVIEW_PAGER when that explicit marker is present

That keeps preview panes on the non-interactive pager, while leaving Enter/fullscreen behavior unchanged.

I also added regression tests for both the pager-selection logic and the diff / show preview wiring.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
bin/git-forgit (1)

146-153: Scope FORGIT_IN_PREVIEW to the delegated call to avoid leaking state when sourced.

Line 151 exports globally in the current shell process. When bin/git-forgit is sourced (tests/helpers), this can affect subsequent helper calls unexpectedly.

♻️ Proposed refactor
 _forgit_preview() {
     local cmd=$1
     shift
-    # Funnel all fzf --preview commands through a single wrapper so preview-only
-    # pager behavior is controlled by an explicit marker instead of ambient env.
-    export FORGIT_IN_PREVIEW=1
-    _forgit_"${cmd}" "$@"
+    local fn="_forgit_${cmd}"
+    # Funnel all fzf --preview commands through a single wrapper so preview-only
+    # pager behavior is controlled by an explicit marker instead of ambient env.
+    if ! declare -F "$fn" >/dev/null; then
+        _forgit_warn "preview command not found: $cmd"
+        return 1
+    fi
+    (
+        export FORGIT_IN_PREVIEW=1
+        "$fn" "$@"
+    )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/git-forgit` around lines 146 - 153, The code exports FORGIT_IN_PREVIEW
globally in _forgit_preview which leaks state when the script is sourced; change
it to set the variable only for the delegated call instead of exporting. Replace
the export+separate call with a single scoped invocation such as invoking
_forgit_"${cmd}" with FORGIT_IN_PREVIEW=1 in the same command (or run the call
in a subshell that exports the var) so the flag is not exported to the calling
shell; update the _forgit_preview function accordingly.
tests/preview-context.test.sh (1)

50-66: Consider adding one assertion for Enter/fullscreen binding preservation.

You already assert preview wiring; adding an Enter binding check would lock in the “preview-only override” guarantee.

🧪 Suggested assertion add-on
 function test_forgit_diff_preview_command_uses_preview_wrapper() {
     bashunit::mock "fzf" 'printf "%s" "$FZF_DEFAULT_OPTS"'
 
     local output
     output=$(_forgit_diff)
 
     assert_contains "--preview=\"$FORGIT preview diff_view {}" "$output"
+    assert_contains "--bind=\"enter:execute($FORGIT diff_enter {}" "$output"
+    assert_contains "| $FORGIT pager enter)\"" "$output"
 }

As per coding guidelines: Add or update tests for behavior changes, especially parsing, selection, and cross-shell integration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/preview-context.test.sh` around lines 50 - 66, Add one assertion in
both test_forgit_diff_preview_command_uses_preview_wrapper and
test_forgit_show_preview_command_uses_preview_wrapper to verify the
Enter/fullscreen binding is preserved: check that the generated fzf command
output from _forgit_diff and _forgit_show includes a --bind entry that maps
enter to executing the FORGIT preview wrapper (i.e., a --bind mapping where
enter triggers execution of the FORGIT preview command, such as
enter:execute:...preview), so the tests assert both preview wiring and that
Enter opens the preview wrapper rather than performing a normal selection.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@bin/git-forgit`:
- Around line 146-153: The code exports FORGIT_IN_PREVIEW globally in
_forgit_preview which leaks state when the script is sourced; change it to set
the variable only for the delegated call instead of exporting. Replace the
export+separate call with a single scoped invocation such as invoking
_forgit_"${cmd}" with FORGIT_IN_PREVIEW=1 in the same command (or run the call
in a subshell that exports the var) so the flag is not exported to the calling
shell; update the _forgit_preview function accordingly.

In `@tests/preview-context.test.sh`:
- Around line 50-66: Add one assertion in both
test_forgit_diff_preview_command_uses_preview_wrapper and
test_forgit_show_preview_command_uses_preview_wrapper to verify the
Enter/fullscreen binding is preserved: check that the generated fzf command
output from _forgit_diff and _forgit_show includes a --bind entry that maps
enter to executing the FORGIT preview wrapper (i.e., a --bind mapping where
enter triggers execution of the FORGIT preview command, such as
enter:execute:...preview), so the tests assert both preview wiring and that
Enter opens the preview wrapper rather than performing a normal selection.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e5c33ed2-e6d8-4035-b26c-0b9ba94f82e6

📥 Commits

Reviewing files that changed from the base of the PR and between ce0f6f8 and f04f41b.

📒 Files selected for processing (2)
  • bin/git-forgit
  • tests/preview-context.test.sh

Copy link
Collaborator

@sandr01d sandr01d left a comment

Choose a reason for hiding this comment

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

Can confirm this is working now 🚀
Just one minor comment for the tests from my side, but approving already. Also, I think we should squash the two commits together.

When users configure a TUI/interactive diff pager (e.g., diffnav, tig)
via `git config pager.diff`, fzf preview panes show blank output because
TUI pagers require a TTY that fzf previews don't provide.

Add FORGIT_PREVIEW_PAGER env var that overrides the diff pager only in
fzf preview context. Preview detection relies on a FORGIT_IN_PREVIEW
marker set by a new `_forgit_preview` wrapper, so the override applies
only to preview commands and leaves fullscreen behavior unchanged.

Fixes #491
@wfxr wfxr merged commit f7e2d43 into main Mar 14, 2026
8 checks passed
@wfxr wfxr deleted the tui-pager branch March 14, 2026 02:38
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.

TUI pagers (diffnav, etc.) break fzf preview panes

2 participants