OXYGENHUB

A minified file is shown plain instead of highlighted for seconds f157bcea0e32

syntect's time grows much faster than a line's length: the file page of
assets/vendor/editor.js (860 KB in 31 lines) took 6.2 s to highlight,
and 41 such requests were slow tonight, since every restart empties the
highlight cache. A text with a line over 4 000 bytes is now shown plain
(escaped), as other forges do. Test: a long-line file renders plain at
once, a normal one is still highlighted.

Refs #228
Author
Andy McCall @andymccall <[email protected]>
via
agent Claude Code <[email protected]> · delegate for changes, until 2026-11-13 19:00Z · key ea5f78160088
When
· 2026-10-17 04:40 UTC
Signature
✔ signed by Claude Code
Hash
f157bcea0e329a44ac78d59b88fb11028b05d17aa820106d94ba128d777df124
Depends on
93be9ce4653e d23464c0ac60 f5c443378333
Depended on by
8eae2f0e255c
In bookmarks
api-unknown-fields cancel-landed-runs diffusion-pin-bookmark draft-ready main oh-comment-body-file proposal-list-fast
In tags
v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
Edits
5 atoms, 0 tree operations, 2 files

Browse the tree at this change

modified crates/hub/src/render/highlight.rs +21 −0
…
45 unchanged lines
46 46
    cache().get_or(key, || lines_uncached(path, text)).as_ref().clone()
47 47
}
48 48
49 +
/// The longest line highlighted. syntect's time grows much faster than a
50 +
/// line's length: a minified 860 KB script of 31 lines took 6.2 s to
51 +
/// highlight, every time the hub had restarted, for a page nobody reads as
52 +
/// code. A text with a longer line is shown plain, as other forges do.
53 +
pub const MAX_HIGHLIGHTED_LINE: usize = 4_000;
54 +
49 55
fn lines_uncached(path: &str, text: &str) -> Vec<String> {
56 +
    if text.lines().any(|l| l.len() > MAX_HIGHLIGHTED_LINE) {
57 +
        return text.lines().map(super::escape).collect();
58 +
    }
50 59
    let set = syntaxes();
51 60
    let ext = path.rsplit('.').next().unwrap_or("");
52 61
    let name = path.rsplit('/').next().unwrap_or(path);
…
66 unchanged lines
119 128
        assert_eq!(language("a/b/lib.rs"), Some("Rust"));
120 129
        assert_eq!(language("Makefile"), Some("Makefile"));
121 130
        assert_eq!(language("blob.bin"), None);
131 +
    }
132 +
133 +
    /// A minified file is shown plain, at once, its lines still escaped.
134 +
    #[test]
135 +
    fn a_text_with_a_very_long_line_is_shown_plain() {
136 +
        let long = format!("var a=\"<\";{}\nb();\n", "x+".repeat(MAX_HIGHLIGHTED_LINE));
137 +
        let started = std::time::Instant::now();
138 +
        let l = lines("app.min.js", &long);
139 +
        assert!(started.elapsed() < std::time::Duration::from_millis(500), "{:?}", started.elapsed());
140 +
        assert_eq!(l.len(), 2);
141 +
        assert!(l[0].starts_with("var a=&quot;&lt;&quot;;") && !l[0].contains("<span"), "{}", &l[0][..40]);
142 +
        assert!(lines("app.js", "var a = 1;\n")[0].contains("<span"), "a normal file is highlighted");
122 143
    }
123 144
}
modified docs/spec.md +1 −1
…
259 unchanged lines
260 260
261 261
### 6.6 File view and blame
262 262
263 -
Text files render with syntect line highlighting, line numbers as anchors (`#L12`, `#L12-L20` selects a range), a "raw", "history", "blame", "edit" toolbar, and the file's last change. Markdown renders by default with a toggle to source. Images and PDFs render inline; other binaries offer download. Files above `render_limit` (default 2 MiB) show the first part and a download link.
263 +
Text files render with syntect line highlighting (a text with a line over 4 000 bytes, a minified file, is shown plain: syntect's time grows much faster than a line), line numbers as anchors (`#L12`, `#L12-L20` selects a range), a "raw", "history", "blame", "edit" toolbar, and the file's last change. Markdown renders by default with a toggle to source. Images and PDFs render inline; other binaries offer download. Files above `render_limit` (default 2 MiB) show the first part and a download link.
264 264
265 265
Blame uses `Repo::blame`: every line is exact, because each line is an atom with one authoring change. The gutter shows the change's short hash, author and age, grouped into runs, with the change's signature glyph. Blame is exact through rebase and squash, so there is no "ignore revs" file.
266 266
…
278 unchanged lines