summaryrefslogtreecommitdiff
path: root/src/file_pane.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-06 17:14:40 -0300
committerGabriel Schneider <[email protected]>2026-09-06 17:14:40 -0300
commitfa82cac885cb4738fe36d1e49b4749b5a3e31a4a (patch)
tree75e6c02e75cfde2282c7b4f9d5a467afa71e0a6b /src/file_pane.zig
parent1852dd3c1247ee66b4265b0a7ee8b3afd08a8fa5 (diff)
downloadpardes-fa82cac885cb4738fe36d1e49b4749b5a3e31a4a.tar.gz
pardes-fa82cac885cb4738fe36d1e49b4749b5a3e31a4a.zip
syntax: a results buffer is coloured as the code it quotes
+Grep, +Search and every language answer render rows like src/look.zig:718:12-16 fn grepText(path: []const u8, text: []const u8... — a location, a space, and a piece of some file. The location names the file, the file names the grammar, and the rest of the row is a fragment of that language, so a grep over Zig reads as Zig and one Markdown row in the same buffer does not pretend otherwise. The location itself is left uncoloured: it is not code, and painting it as code is how a path starts looking like a keyword. `look.parsePathLine` decides what counts as a location — the same primitive n/N already walks these buffers with, so the two agree by construction about which rows are locations. NOT `lookableLineSpan`, which is n/N's whole heuristic: it calls `resolve`, and a `realpath` per row per scroll is not something a render path can afford. A bare filename is refused too; only `path:line` counts, or a prose line whose first word ended in `.md` would colour the rest of a sentence. THE BUFFER IS COLOURED WHOLE, ONCE, WHEN IT IS FILLED. An adversarial pass measured the obvious per-window implementation and it was untenable: the rows are independent, so a window pass buys no fidelity, only amortisation, and pays a burst on every scroll that outran the covered range. Grammars compile their highlights query on first use — zig 26.9ms, cpp 18.9ms, rust 14.3ms — so a polyglot grep showing six languages stalled a frame by 66ms, moving a cost the syntax module had deliberately put on "opening a file" onto a scroll. It also raised tree-sitter's allocation rate 3.5x (8,785 per refresh against 2,454) into a 16 MiB bump arena that only reclaims LIFO, so ~16 scroll re-highlights exhausted it — and that arena is shared with real file panes, so a results pane could evict editing. A grep is capped at 512 rows; colouring it once makes the covered-range check true forever after and scrolling free. The rest of that pass, in the same spirit: injections off for a single row (both build a SECOND parser, per fenced block and per inline node, which is absurd for one truncated row that almost never contains a fence), one query cursor for the buffer instead of one per row, a one-entry extension memo so non-matching rows stop paying a 29-spec scan, and NO highlights at all when nothing painted — an all-zero run is not the same as none, and it defeated `recolorSyntax`'s fast path, making every +Help and +Config walk its graphemes every frame to paint nothing. One correctness bug from the same pass: a failed `setLanguage` has already nulled the parser's language, so leaving `held` on the previous grammar made every later row of it skip the call and silently lose colour. Documents keep `.source`: a New scratch and a real file are output-shaped but have one language and an edit per keystroke, and `saves` is the line between the two. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_016Q4RATpafkwahrovHQLKRf
Diffstat (limited to 'src/file_pane.zig')
-rw-r--r--src/file_pane.zig19
1 files changed, 17 insertions, 2 deletions
diff --git a/src/file_pane.zig b/src/file_pane.zig
index 5c7f4b77..9b828030 100644
--- a/src/file_pane.zig
+++ b/src/file_pane.zig
@@ -722,10 +722,25 @@ pub fn refreshHighlights(p: *Pardes) void {
// a fidelity gain, and one that cannot reach a file shown from the top
// (scroll 0 clamps the window to exactly what it always was).
const slack: usize = if (f.highlights.len == 0) 0 else pane.rows;
- const start = lineStart(p.gpa, f, f.scroll -| slack);
- const end = @max(start, lineStart(p.gpa, f, f.scroll + pane.rows + SYNTAX_CONTEXT_AFTER_ROWS + slack));
+ // A RESULTS BUFFER IS COLOURED WHOLE, ONCE. Its rows are independent —
+ // each is parsed in isolation against its own grammar — so a window
+ // pass buys no fidelity, only amortisation, and pays for it with a
+ // burst on every scroll that outran the covered range: a fresh parser,
+ // a fresh query cursor and a tree per row, plus the first compile of
+ // any grammar the new rows introduce, all inside `render`. Colouring
+ // the whole buffer when it is FILLED makes the covered-range check
+ // above true forever after, so scrolling one costs nothing at all.
+ // Bounded by what fills them: `look.find_max_hits` caps a grep at 512
+ // rows, and a rendering is never typed into.
+ const whole = pane.colorAlgo() == .locations;
+ const start = if (whole) 0 else lineStart(p.gpa, f, f.scroll -| slack);
+ const end = if (whole)
+ f.content.len
+ else
+ @max(start, lineStart(p.gpa, f, f.scroll + pane.rows + SYNTAX_CONTEXT_AFTER_ROWS + slack));
const new_highlights = (switch (pane.colorAlgo()) {
.diff => syntax.highlightDiff(p.tree_sitter_gpa, f.content, start, end),
+ .locations => syntax.highlightLocations(p.tree_sitter_gpa, f.content, start, end),
else => syntax.highlightFileRange(p.tree_sitter_gpa, f.path, f.content, start, end),
}) catch {
f.syntax_dirty = false;