diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-06 17:14:40 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-06 17:14:40 -0300 |
| commit | fa82cac885cb4738fe36d1e49b4749b5a3e31a4a (patch) | |
| tree | 75e6c02e75cfde2282c7b4f9d5a467afa71e0a6b /src/file_pane.zig | |
| parent | 1852dd3c1247ee66b4265b0a7ee8b3afd08a8fa5 (diff) | |
| download | pardes-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.zig | 19 |
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; |
