Skip to content

perf: highlight files off the main thread (no freeze on file click) - #23

Merged
SergKam merged 1 commit into
mainfrom
perf/async-syntax-highlight
Jun 12, 2026
Merged

perf: highlight files off the main thread (no freeze on file click)#23
SergKam merged 1 commit into
mainfrom
perf/async-syntax-highlight

Conversation

@SergKam

@SergKam SergKam commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Problem

Clicking any highlightable file froze the UI. `highlight_buffer_with_theme` ran syntect line-by-line and applied tags per region synchronously on the GTK main thread, scaling with file size — and going pathological on minified single-line files (one giant `highlight_line` call).

Fix

`load_file` now shows the content as plain text immediately (`set_text`), then:

  1. A worker thread computes the highlight spans (`compute_spans`, pure + thread-safe).
  2. The main thread applies the tags in idle-chunked batches (1500 spans/tick), so GTK paints between batches.

A per-view epoch (stored on the widget via `set_data`, the same pattern as `tree.rs`) cancels a stale highlight when a newer file is opened mid-run. Lines over 10k bytes (minified blobs) are skipped by the highlighter; the rest of the file still highlights.

The click never blocks the UI regardless of file size or content.

Notes

  • Syntax/theme sets moved from per-thread `thread_local!` to a shared `OnceLock` (Send+Sync, loaded once, off the main thread on first use) — so even the first file open doesn't pay the syntax-set load on the UI thread.
  • Spans use character offsets, which also fixes a latent byte-vs-char offset bug the old code had on non-ASCII files.
  • Files over the 1 MB highlight ceiling / 10 MB view cap behave as before (plain text), now shown instantly.

Scope

`src/highlight.rs` (shared sets, `compute_spans`/`highlight_async`/`apply_spans_chunked`/epoch; diff fns reuse the shared sets), `src/textview.rs` (call site).

Checks

fmt ✅ · clippy `-D warnings` ✅ · tests ✅ (108, incl. 3 new `compute_spans` tests: offset bounds, unhighlightable, long-line guard)

Opening any highlightable file froze the UI: syntect ran line-by-line and tags
were applied per region synchronously on the GTK main thread, scaling with file
size (pathological on minified single-line files).

Now load_file shows the content as plain text immediately (set_text), then a
worker thread computes the highlight spans and the main thread applies the tags
in idle-chunked batches (1500/tick) — so the click never blocks. A per-view
epoch (stored on the widget, like tree.rs) cancels a stale highlight when a new
file is opened mid-run. Lines over 10k bytes (minified blobs) are left
unhighlighted; the rest of the file still highlights.

- highlight: shared syntax/theme sets via OnceLock (Send+Sync, loaded once off
  the main thread) replace per-thread thread_locals; new compute_spans (pure,
  thread-safe), highlight_async, apply_spans_chunked. Char-offset spans also fix
  the latent byte-vs-char offset bug on non-ASCII files.
- textview: load_file calls highlight_async instead of the synchronous path.
- tests: compute_spans offset bounds, unhighlightable, long-line guard.
@SergKam
SergKam merged commit 8aa366b into main Jun 12, 2026
2 checks passed
@SergKam
SergKam deleted the perf/async-syntax-highlight branch June 12, 2026 12:47
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