Skip to content

perf(tui): cache file-viewer line split and header gradient - #95

Merged
deveshctl merged 4 commits into
mainfrom
refactor/tui-perf
Aug 4, 2026
Merged

perf(tui): cache file-viewer line split and header gradient#95
deveshctl merged 4 commits into
mainfrom
refactor/tui-perf

Conversation

@deveshctl

@deveshctl deveshctl commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

Three rendering-path optimisations for the interactive TUI. All remove redundant per-frame work; none change any user-visible output.

File viewer: split content into lines once per open

The file viewer previously re-split the entire file body into lines on every keystroke and every frame — copying the whole file into a string and running two normalisation passes plus a split, across six separate call sites (render, scroll clamp, cursor-column bound, and search indexing). For a large file this allocated megabytes of throwaway strings on each redraw.

The split now happens once when a file is opened; the cached lines are reused by every hot path. Scrolling and incremental search through large files (minified JSON, logs, long single-line files) no longer generate that per-redraw allocation churn.

Skip the file-tree pipeline while the viewer is open

When the file viewer is open the status bar shows viewer hints, not the tree status bar — yet the frame still computed the full flatten/filter/sort tree result and discarded it. That computation is now skipped while the viewer is open.

Header: render the gradient image reference once

The gradient-coloured image reference in the header was recomputed on every frame, doing per-character colour interpolation on a value that is fixed for the whole session. It is now computed once at startup and reused.

Behaviour

No change to rendered output, line counts, key handling, JSON, CLI flags, or error messages. These are internal performance changes only.

Testing

go build and go vet clean. The TUI test suite was updated to keep the viewer's line cache in sync with its content in tests that set viewer state directly (via a shared helper), and a regression test renders the viewer with a file open to confirm the viewer status bar is shown. Full test suite runs in CI.

The header's gradient-coloured image reference was recomputed on every
View() call — per-rune float colour interpolation plus a lipgloss.Style
allocation per character — even though both inputs (the image reference
and the theme gradient stops) are fixed for the session. bubbletea
renders after every Update, so this ran on every keypress, scroll, and
10 Hz spinner tick for a result that never changes.

Compute it once in NewModel as renderedImageRef and have renderHeader
read the cached string. Output is unchanged; only the redundant per-frame
work is removed.
The file viewer re-ran splitFileLines on every keystroke and every
frame: it copied the whole file body into a string, ran two ReplaceAll
passes, then Split. On a large file that is megabytes of throwaway
allocation per redraw across six call sites (render, scroll clamp,
cursor-column bound, search indexing).

Split once when the file opens, cache the result on the model as
viewLines, and read the cache from every hot path. A new viewLineCount
method preserves the single-trailing-newline-counts-as-one contract that
fileViewLineCount defined. Rendered output and line counts are unchanged.
viewReady always computed the flatten/filter/sort tree result to pass to
renderStatusBar, but renderStatusBar returns early via
renderViewerStatusBar when the viewer is open and never reads it. Guard
the displayTree() call on viewState so the pipeline does not run for a
value that is discarded. Added a regression test that renders View() with
the viewer open and asserts the viewer status bar (not the tree/efficiency
bar) is shown. Rendered output is unchanged.
viewLineCount reads the cached m.viewLines that scroll clamping and the
status-bar line counter rely on, but only the direct fileViewLineCount
reference had test coverage — the cached runtime path could drift from
the reference (e.g. losing the binary guard or the trailing-newline
terminator rule) without any test catching it.

Add a table test that asserts viewLineCount matches fileViewLineCount for
every input, including the binary case the reference table omitted, plus
a nil-content case. Test-only change; no production behaviour affected.
@deveshctl
deveshctl merged commit dbb551a into main Aug 4, 2026
13 checks passed
@deveshctl
deveshctl deleted the refactor/tui-perf branch August 7, 2026 14:23
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.

1 participant