Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

feat(preview): add configurable scroll hotkeys and bulk scroll size#1506

Open
KristianHolme wants to merge 6 commits into
yorukot:mainyorukot/superfile:mainfrom
KristianHolme:feat/file-preview-scrollKristianHolme/superfile:feat/file-preview-scrollCopy head branch name to clipboard
Open

feat(preview): add configurable scroll hotkeys and bulk scroll size#1506
KristianHolme wants to merge 6 commits into
yorukot:mainyorukot/superfile:mainfrom
KristianHolme:feat/file-preview-scrollKristianHolme/superfile:feat/file-preview-scrollCopy head branch name to clipboard

Conversation

@KristianHolme

@KristianHolme KristianHolme commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🤖 Written with Cursor, and manually tested. Hopefully this can be useful :)

Note that this does not yet enable scrolling preview with mouse wheel.

Description

Adds file preview scrolling in browser mode when the preview panel is open. Long files, directories, and syntax-highlighted content can be scrolled without moving the file-panel cursor.

Scrolling is active only when:

  • the preview panel is open (f by default)
  • the focused file panel is in browser mode
  • no modal, prompt, rename, or search overlay is capturing input

Scroll position resets when the previewed file or directory changes.

Preview scroll hotkeys use the same matching as the rest of superfile (msg.String() + slices.Contains). Shifted letters are configured as uppercase (J/K), not shift+j/shift+k, because Bubbletea reports them as "J"/"K".

Config (config.toml)

Key Default Description
preview_scroll_bulk 2 Bulk scroll step as a fraction of preview height. 1 = whole page, 2 = half page, 3 = one third. Must be 1, 2, or 3.

Example:

preview_scroll_bulk = 2

Hotkeys (hotkeys.toml)

Six new global hotkeys:

Action Default hotkeys Config key
Scroll preview up one line shift+up preview_scroll_line_up
Scroll preview down one line shift+down preview_scroll_line_down
Scroll preview up by bulk step ctrl+k, ctrl+up preview_scroll_bulk_up
Scroll preview down by bulk step ctrl+j, ctrl+down preview_scroll_bulk_down
Scroll preview to top alt+k, alt+up preview_scroll_top
Scroll preview to bottom alt+j, alt+down preview_scroll_bottom

Regular defaults use arrow keys for line scroll (no J/K), so j/k continue to move the file list.

Vim hotkeys (vimHotkeys.toml)

Line scroll uses both letter and arrow bindings:

preview_scroll_line_up = ['K', 'shift+up']
preview_scroll_line_down = ['J', 'shift+down']

Bulk and jump bindings stay vim-style: ctrl+k/ctrl+j, alt+k/alt+j.

Upgrading

Existing installs need the new fields. Run:

spf --fix-hotkeys
spf --fix-config-file

Or merge the new keys from the embedded defaults into ~/.config/superfile/hotkeys.toml and add preview_scroll_bulk to config.toml.

Related Issues

closes #806

Screenshots (Optional)

asciicast

Pre-Submission Checklist

  • I have run go fmt ./... to format the code
  • I have run golangci-lint run and fixed any reported issues in this PR (remaining lint findings are pre-existing on main)
  • I have tested my changes and verified they work as expected
  • I have reviewed the diff to make sure I'm not committing any debug logs or TODOs
  • I have filled out the PR template with description, context, and screenshots if needed
  • I have checked that the PR title follows the Conventional Commits format

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added file preview scrolling hotkeys for line-by-line, bulk moves, and jump to top/bottom (browser mode only).
    • Added preview_scroll_bulk setting to control bulk step as a fraction of preview height (validated to values 1–3).
    • Updated help menu with the new File preview scrolling shortcuts.
  • Bug Fixes

    • Improved preview scroll handling and interception so shortcuts only work when appropriate, and scrolling stays consistent when preview content changes (including empty/unsupported states).
  • Tests

    • Added/expanded coverage for bottom scrolling and preview line-counting/timeouts.

KristianHolme and others added 4 commits July 1, 2026 13:06
File preview can scroll by line, bulk steps, and jump to top/bottom via
explicit hotkeys while the panel is open in browser mode. Bulk scroll
distance is controlled by preview_scroll_bulk in config.toml.

Co-authored-by: Cursor <cursoragent@cursor.com>
Bubbletea reports shifted printable keys as their text form (J, K) while
hotkey config uses shift+j and shift+k. Preview scroll matching now
checks both String() and Keystroke() so configured shift combos work.

Co-authored-by: Kristian Holme <KristianHolme@users.noreply.github.com>
Refactor renderTextPreview to satisfy gocognit, wrap long validation
message for golines, and apply gofmt alignment in config_type.

Co-authored-by: Kristian Holme <KristianHolme@users.noreply.github.com>
Remove HotkeyMatches helper and match preview scroll keys via the standard
msg.String() + slices.Contains path. Regular defaults use shift+up/down
only; vim defaults add K/J alongside arrow alternates.

Co-authored-by: Kristian Holme <KristianHolme@users.noreply.github.com>
@github-actions github-actions Bot added test related PR / Issue related to testcases or testing in general. new hotkeys new config labels Jul 1, 2026
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🎉 Thank you for your first contribution to superfile!

We’re really excited to have you here 🙌

A maintainer might ask you to make a few changes before we can merge this PR.
That’s totally normal and part of the process. Don’t worry, we’ll help guide you through it.

👉 Please also take a moment to review our Contribution Guide

If you have any questions, feel free to open a Discussion or just ask in the comments!

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds configurable file-preview scrolling, including line, bulk, top, and bottom navigation. It updates preview rendering, file slicing, key handling, help text, and config validation to support the new behavior.

Changes

Preview scrolling feature

Layer / File(s) Summary
Config and hotkey schema
src/internal/common/config_type.go, src/internal/common/load_config.go, src/superfile_config/config.toml, src/superfile_config/hotkeys.toml, src/superfile_config/vimHotkeys.toml
Adds preview scroll config and hotkey fields, validates the bulk setting, and updates TOML defaults and bindings.
File slicing support
src/pkg/utils/file_utils.go, src/pkg/utils/file_utils_test.go
Extends file reading to start at a requested line and report whether more content remains, with matching tests.
Preview scroll state and navigation
src/internal/ui/preview/model.go, src/internal/ui/preview/navigation.go, src/internal/ui/preview/model_utils.go, src/internal/ui/preview/navigation_test.go
Adds preview scroll state, scroll methods, location reset behavior, and tests covering text, directory, and bottom-scroll behavior.
Preview rendering and refresh
src/internal/ui/filemodel/preview_navigation.go, src/internal/ui/preview/render.go, src/internal/ui/preview/render_utils.go
Reworks preview rendering to respect scroll state, adds line-based rendering helpers, and updates the synchronous refresh path.
Key routing and help text
src/internal/key_function.go, src/internal/model.go, src/internal/ui/helpmenu/data.go
Adds preview-scroll key gating and handlers, intercepts those keys in model input processing, and updates help text for the new shortcuts.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • yorukot/superfile#1013: Extends the same preview panel model and rendering path that this PR adds scroll behavior to.
  • yorukot/superfile#1021: Introduced the preview model separation that this PR builds on for scrolling.
  • yorukot/superfile#1245: Also changes src/internal/ui/preview/render.go, overlapping with the rendering path updated here.

Suggested labels: new feature

Suggested reviewers: yorukot

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The select-mode navigation help text edits are unrelated to preview scrolling and look like extra changes. Move the select-mode wording changes into a separate PR unless they are required for the preview-scroll feature.
Docstring Coverage ⚠️ Warning Docstring coverage is 15.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main preview-scrolling feature.
Linked Issues check ✅ Passed The changes add preview-panel scrolling and stop file-panel cursor movement, matching the issue's expected behavior.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/internal/ui/preview/render_utils.go (1)

16-58: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat EOF as no more content

lineCount >= previewLine is also true when bat returns a full page that ends exactly at EOF, so the last page of files whose length is a multiple of the preview height still leaves scrolling enabled. Peek one line past the window, like ReadFileContent, to distinguish “full page” from “full page + more”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/internal/ui/preview/render_utils.go` around lines 16 - 58, The paging
logic in getBatSyntaxHighlightedContent currently treats any full page as having
more content, even when bat has reached EOF. Update the bat invocation to peek
one line past the requested window, mirroring ReadFileContent, so you can
distinguish a full page from “full page plus more.” Then compute hasMore from
that extra line instead of comparing lineCount to previewLine.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/internal/key_function.go`:
- Around line 151-159: `shouldHandlePreviewScrollKeys` is missing the search
overlay focus check, so preview scroll hotkeys can still intercept input while
the search bar is active. Update `model.shouldHandlePreviewScrollKeys` to also
require that `focusPanel.SearchBar.Focused()` is false, so `tryPreviewScrollKey`
won’t consume search keystrokes. This will correctly gate the existing
`updateComponentState` search-bar path and `handleKeyInput` interception without
further changes in `model.go`.
- Around line 228-241: The scroll-to-bottom handler in handlePreviewScrollBottom
is doing a full RefreshPreviewScroll on every ScrollBulkDown iteration, which
causes repeated expensive renders inside a single Update path. Change the loop
so it only advances the preview state through bulk scrolling, then trigger
RefreshPreviewScroll once after the loop finishes, using the final position. If
CanScrollDown must be re-evaluated from a full render, add a cheaper peek/check
in model or fileModel so handlePreviewScrollBottom can preserve correctness
without calling RefreshPreviewScroll repeatedly.

In `@src/internal/ui/preview/model_utils.go`:
- Around line 25-30: Update the scroll-reset behavior in Model so the invariant
enforced by SetLocation is also applied by UpdateRenderedContent/setContent.
Right now SetLocation resets scroll only on location changes, but setContent
assigns m.location directly and can bypass that check, leaving a stale
scrollOffset for future callers. Refactor setContent to reuse SetLocation or
otherwise compare the incoming location before assigning, so Model.resetScroll
is triggered consistently whenever the location changes.

---

Outside diff comments:
In `@src/internal/ui/preview/render_utils.go`:
- Around line 16-58: The paging logic in getBatSyntaxHighlightedContent
currently treats any full page as having more content, even when bat has reached
EOF. Update the bat invocation to peek one line past the requested window,
mirroring ReadFileContent, so you can distinguish a full page from “full page
plus more.” Then compute hasMore from that extra line instead of comparing
lineCount to previewLine.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 48fb7eb5-3c96-40dd-a083-674e984c0359

📥 Commits

Reviewing files that changed from the base of the PR and between 0a4336a and 77eb00a.

📒 Files selected for processing (17)
  • src/internal/common/config_type.go
  • src/internal/common/load_config.go
  • src/internal/key_function.go
  • src/internal/model.go
  • src/internal/ui/filemodel/preview_navigation.go
  • src/internal/ui/helpmenu/data.go
  • src/internal/ui/preview/model.go
  • src/internal/ui/preview/model_utils.go
  • src/internal/ui/preview/navigation.go
  • src/internal/ui/preview/navigation_test.go
  • src/internal/ui/preview/render.go
  • src/internal/ui/preview/render_utils.go
  • src/pkg/utils/file_utils.go
  • src/pkg/utils/file_utils_test.go
  • src/superfile_config/config.toml
  • src/superfile_config/hotkeys.toml
  • src/superfile_config/vimHotkeys.toml

Comment thread src/internal/key_function.go
Comment thread src/internal/key_function.go
Comment on lines 25 to 30
func (m *Model) SetLocation(location string) {
if m.location != location {
m.resetScroll()
}
m.location = location
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

UpdateRenderedContent bypasses the new scroll-reset invariant.

SetLocation resets scroll only when the location changes, but UpdateRenderedContentsetContent sets m.location unconditionally without that check. Currently safe because RefreshPreviewScroll always passes the already-focused item's location, but any future caller passing a different location would silently retain a stale scrollOffset, undoing the fix TestPreviewScrollResetsOnNewLocation validates.

♻️ Suggested consolidation
 func (m *Model) UpdateRenderedContent(content string, width int, height int, location string) {
-	m.setContent(content, width, height, location)
+	if m.location != location {
+		m.resetScroll()
+	}
+	m.setContent(content, width, height, location)
 }

Also applies to: 46-49

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/internal/ui/preview/model_utils.go` around lines 25 - 30, Update the
scroll-reset behavior in Model so the invariant enforced by SetLocation is also
applied by UpdateRenderedContent/setContent. Right now SetLocation resets scroll
only on location changes, but setContent assigns m.location directly and can
bypass that check, leaving a stale scrollOffset for future callers. Refactor
setContent to reuse SetLocation or otherwise compare the incoming location
before assigning, so Model.resetScroll is triggered consistently whenever the
location changes.

- Gate preview scroll hotkeys when the search bar is focused
- Scroll to bottom with a single render via ScrollBottom sentinel
- Reset scroll offset when setContent receives a new location
- Peek one extra bat line to detect EOF correctly

Co-authored-by: Kristian Holme <KristianHolme@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/internal/ui/preview/render_utils.go (1)

67-84: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Unbounded full-file scan with no timeout for line counting.

countFileLines scans the entire file line-by-line with no size cap or context timeout, unlike the sibling bat invocation which is bounded by common.DefaultPreviewTimeout. For very large files this can block the render call (triggered by scroll-to-bottom) for a noticeable amount of time with no way to bail out.

Consider bounding this (e.g. a max-lines cap, or reading file size and estimating/streaming with a timeout) to keep scroll-to-bottom responsive on huge files.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/internal/ui/preview/render_utils.go` around lines 67 - 84, countFileLines
currently does an unbounded full-file scan, so update this helper in
render_utils.go to avoid blocking preview rendering on huge files. Add a bound
comparable to the existing preview timeout behavior used elsewhere (for example
by threading through a context, enforcing a max-line cap, or short-circuiting
based on file size/estimated work) and ensure callers can bail out when the
limit is hit. Keep the fix localized around countFileLines and its preview
scroll-to-bottom usage so line counting remains responsive.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/internal/ui/preview/render_utils.go`:
- Around line 67-84: countFileLines currently does an unbounded full-file scan,
so update this helper in render_utils.go to avoid blocking preview rendering on
huge files. Add a bound comparable to the existing preview timeout behavior used
elsewhere (for example by threading through a context, enforcing a max-line cap,
or short-circuiting based on file size/estimated work) and ensure callers can
bail out when the limit is hit. Keep the fix localized around countFileLines and
its preview scroll-to-bottom usage so line counting remains responsive.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 14a179fd-d2f9-49be-9e56-e8772ad19423

📥 Commits

Reviewing files that changed from the base of the PR and between 77eb00a and 604b9ae.

📒 Files selected for processing (6)
  • src/internal/key_function.go
  • src/internal/ui/preview/model_utils.go
  • src/internal/ui/preview/navigation.go
  • src/internal/ui/preview/navigation_test.go
  • src/internal/ui/preview/render.go
  • src/internal/ui/preview/render_utils.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/internal/ui/preview/model_utils.go
  • src/internal/ui/preview/navigation.go
  • src/internal/ui/preview/render.go

Scroll-to-bottom line counting no longer scans huge files without limit.
Incomplete counts clamp to the best-known offset within the same 500ms
budget used by bat preview rendering.

Co-authored-by: Kristian Holme <KristianHolme@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/internal/ui/preview/render_utils.go (1)

75-95: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve the partial line count when bufio.Scanner hits ErrTooLong
countFileLinesBefore returns 0 on scanner.Err(), so a single oversized line makes normalizeScrollOffsetForText reset scroll instead of falling back to the lines already counted. Returning the partial count (and/or raising the scanner buffer) keeps scroll-to-bottom working for minified or single-line files.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/internal/ui/preview/render_utils.go` around lines 75 - 95, The issue is
in countFileLinesBefore: when bufio.Scanner returns an error such as ErrTooLong,
it currently drops the accumulated lineCount and returns 0, which breaks the
fallback behavior in normalizeScrollOffsetForText. Update countFileLinesBefore
to preserve and return the partial lineCount on scanner errors (especially
ErrTooLong), and consider increasing the scanner buffer if needed so oversized
or single-line files still allow scroll-to-bottom handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/internal/ui/preview/render_utils.go`:
- Around line 75-95: The issue is in countFileLinesBefore: when bufio.Scanner
returns an error such as ErrTooLong, it currently drops the accumulated
lineCount and returns 0, which breaks the fallback behavior in
normalizeScrollOffsetForText. Update countFileLinesBefore to preserve and return
the partial lineCount on scanner errors (especially ErrTooLong), and consider
increasing the scanner buffer if needed so oversized or single-line files still
allow scroll-to-bottom handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7b37e575-8210-47be-af3e-16b61d109e01

📥 Commits

Reviewing files that changed from the base of the PR and between 604b9ae and 0b3f757.

📒 Files selected for processing (3)
  • src/internal/ui/preview/navigation.go
  • src/internal/ui/preview/render_utils.go
  • src/internal/ui/preview/render_utils_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/internal/ui/preview/navigation.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting pr review new config new hotkeys test related PR / Issue related to testcases or testing in general.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow scrolling in File Preview Panel

2 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.