mirror of
https://github.com/sbrow/thor.git
synced 2026-10-25 02:01:56 -04:00
fix: Fixed race-conditions related to treesitter parsers.
This commit is contained in:
+13
-7
@@ -125,16 +125,21 @@ unescape_html :: proc(s: string) -> string {
|
||||
}
|
||||
|
||||
highlight_block :: proc(code: string, lang: string, file_path: string) -> string {
|
||||
gc := ts.load_grammar(lang)
|
||||
if gc == nil {
|
||||
g, ok := ts.grammar(lang)
|
||||
if !ok {
|
||||
return code
|
||||
}
|
||||
parser := ts.open_parser(g)
|
||||
if parser == nil {
|
||||
return code
|
||||
}
|
||||
defer ts.parser_delete(parser)
|
||||
|
||||
raw_code := unescape_html(code)
|
||||
raw_c := strings.clone_to_cstring(raw_code)
|
||||
defer delete(raw_c)
|
||||
|
||||
tree := ts.parser_parse_string(gc.parser, nil, raw_c, u32(len(raw_code)))
|
||||
tree := ts.parser_parse_string(parser, nil, raw_c, u32(len(raw_code)))
|
||||
if tree == nil {
|
||||
return code
|
||||
}
|
||||
@@ -156,12 +161,13 @@ highlight_block :: proc(code: string, lang: string, file_path: string) -> string
|
||||
}
|
||||
}
|
||||
|
||||
cursor := gc.cursor
|
||||
if cursor == nil {
|
||||
if g.query == nil {
|
||||
return code
|
||||
}
|
||||
cursor := ts.query_cursor_new()
|
||||
defer ts.query_cursor_delete(cursor)
|
||||
|
||||
ts.query_cursor_exec(cursor, gc.query, root)
|
||||
ts.query_cursor_exec(cursor, g.query, root)
|
||||
|
||||
captures := make([dynamic]Capture, 0, 64, context.temp_allocator)
|
||||
defer delete(captures)
|
||||
@@ -174,7 +180,7 @@ highlight_block :: proc(code: string, lang: string, file_path: string) -> string
|
||||
}
|
||||
cap := match.captures[capture_idx]
|
||||
name_len: u32
|
||||
name_c := ts.query_capture_name_for_id(gc.query, cap.index, &name_len)
|
||||
name_c := ts.query_capture_name_for_id(g.query, cap.index, &name_len)
|
||||
if name_c == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
#+test
|
||||
package markdown
|
||||
|
||||
import ts "../treesitter"
|
||||
|
||||
import "core:mem"
|
||||
import "core:strings"
|
||||
import "core:testing"
|
||||
|
||||
// highlight_block returns the input string unchanged (borrowed) when it can't
|
||||
// highlight — no grammar, no query, or zero captures — and a freshly-allocated
|
||||
// string when it does. Free only the latter; identity (raw_data) tells them
|
||||
// apart regardless of content.
|
||||
@(private = "file")
|
||||
free_highlight :: proc(out, code: string) {
|
||||
if raw_data(out) != raw_data(code) {
|
||||
delete(out)
|
||||
}
|
||||
}
|
||||
|
||||
// A builtin grammar (css) highlights into hl- spans, preserving the source text.
|
||||
@(test)
|
||||
test_highlight_block_css :: proc(t: ^testing.T) {
|
||||
code := "a{color:red}"
|
||||
out := highlight_block(code, "css", "test")
|
||||
defer free_highlight(out, code)
|
||||
|
||||
testing.expect(t, strings.contains(out, "<span class=\"hl-"), "css should be highlighted")
|
||||
testing.expect(t, strings.contains(out, "</span>"))
|
||||
testing.expect(t, strings.contains(out, "color"))
|
||||
testing.expect(t, strings.contains(out, "red"))
|
||||
}
|
||||
|
||||
// The other builtin grammar (html) highlights, and the source's own '<'/'>' are
|
||||
// HTML-escaped in the output (so they can't be confused with the wrapping spans).
|
||||
@(test)
|
||||
test_highlight_block_html_escapes_source :: proc(t: ^testing.T) {
|
||||
code := "<p>hi</p>"
|
||||
out := highlight_block(code, "html", "test")
|
||||
defer free_highlight(out, code)
|
||||
|
||||
testing.expect(t, strings.contains(out, "<span class=\"hl-"), "html should be highlighted")
|
||||
testing.expect(t, strings.contains(out, "hi"))
|
||||
// The literal angle brackets of the source are escaped in the output.
|
||||
testing.expect(t, strings.contains(out, "<"))
|
||||
testing.expect(t, strings.contains(out, ">"))
|
||||
}
|
||||
|
||||
// A language with no available grammar returns the input verbatim (best-effort:
|
||||
// highlighting is optional). The returned string must be the same buffer, so
|
||||
// free_highlight leaves it alone.
|
||||
@(test)
|
||||
test_highlight_block_unknown_lang_passthrough :: proc(t: ^testing.T) {
|
||||
code := "fn main() {}"
|
||||
out := highlight_block(code, "no-such-language", "test")
|
||||
defer free_highlight(out, code)
|
||||
|
||||
testing.expect_value(t, out, code)
|
||||
testing.expect(t, raw_data(out) == raw_data(code), "passthrough must return the input buffer")
|
||||
}
|
||||
|
||||
// End-to-end: highlight_code finds a fenced code block in rendered HTML and
|
||||
// rewrites its body with highlight spans while preserving the <pre><code> shell.
|
||||
// Run under a scratch arena (as production does) so the intermediate allocations
|
||||
// highlight_code doesn't individually free are reclaimed in one shot.
|
||||
@(test)
|
||||
test_highlight_code_wraps_css_block :: proc(t: ^testing.T) {
|
||||
arena: mem.Dynamic_Arena
|
||||
mem.dynamic_arena_init(&arena)
|
||||
defer mem.dynamic_arena_destroy(&arena)
|
||||
context.allocator = mem.dynamic_arena_allocator(&arena)
|
||||
|
||||
html := `<pre><code class="language-css">a{color:red}</code></pre>`
|
||||
out := highlight_code(html, "test")
|
||||
|
||||
testing.expect(t, strings.contains(out, `<pre><code class="language-css">`))
|
||||
testing.expect(t, strings.contains(out, "</code></pre>"))
|
||||
testing.expect(
|
||||
t,
|
||||
strings.contains(out, "<span class=\"hl-"),
|
||||
"block body should be highlighted",
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user