refactor: lazy parse injections

This commit is contained in:
CJ van den Berg 2026-02-21 20:50:21 +01:00
parent 7f18430932
commit 02c26ba48f
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9

View file

@ -28,15 +28,19 @@ errors_query: *Query,
injections: ?*Query, injections: ?*Query,
tree: ?*treez.Tree = null, tree: ?*treez.Tree = null,
injection_list: std.ArrayListUnmanaged(Injection) = .{}, injection_list: std.ArrayListUnmanaged(Injection) = .{},
content: ?[]u8 = null,
pub const Injection = struct { pub const Injection = struct {
lang_name: []const u8, lang_name: []const u8,
file_type: FileType,
start_point: Point, start_point: Point,
end_row: u32, end_row: u32,
syntax: *Self, start_byte: u32,
end_byte: u32,
syntax: ?*Self = null,
fn deinit(self: *Injection, allocator: std.mem.Allocator) void { fn deinit(self: *Injection, allocator: std.mem.Allocator) void {
self.syntax.destroy(); if (self.syntax) |syn| syn.destroy();
allocator.free(self.lang_name); allocator.free(self.lang_name);
} }
}; };
@ -78,6 +82,7 @@ pub fn create_guess_file_type_static(allocator: std.mem.Allocator, content: []co
pub fn destroy(self: *Self) void { pub fn destroy(self: *Self) void {
self.clear_injections(); self.clear_injections();
self.injection_list.deinit(self.allocator); self.injection_list.deinit(self.allocator);
if (self.content) |c| self.allocator.free(c);
if (self.tree) |tree| tree.destroy(); if (self.tree) |tree| tree.destroy();
self.query_cache.release(self.query, .highlights); self.query_cache.release(self.query, .highlights);
self.query_cache.release(self.errors_query, .highlights); self.query_cache.release(self.errors_query, .highlights);
@ -88,6 +93,8 @@ pub fn destroy(self: *Self) void {
pub fn reset(self: *Self) void { pub fn reset(self: *Self) void {
self.clear_injections(); self.clear_injections();
if (self.content) |c| self.allocator.free(c);
self.content = null;
if (self.tree) |tree| { if (self.tree) |tree| {
tree.destroy(); tree.destroy();
self.tree = null; self.tree = null;
@ -101,8 +108,12 @@ fn clear_injections(self: *Self) void {
pub fn refresh_full(self: *Self, content: []const u8) !void { pub fn refresh_full(self: *Self, content: []const u8) !void {
self.clear_injections(); self.clear_injections();
if (self.content) |c| self.allocator.free(c);
self.content = null;
if (self.tree) |tree| tree.destroy(); if (self.tree) |tree| tree.destroy();
self.tree = try self.parser.parseString(null, content); self.tree = try self.parser.parseString(null, content);
const content_copy = try self.allocator.dupe(u8, content);
self.content = content_copy;
try self.refresh_injections(content); try self.refresh_injections(content);
} }
@ -167,6 +178,10 @@ pub fn refresh_from_string(self: *Self, content: [:0]const u8) !void {
.encoding = .utf_8, .encoding = .utf_8,
}; };
self.tree = try self.parser.parse(old_tree, input); self.tree = try self.parser.parse(old_tree, input);
if (self.content) |c| self.allocator.free(c);
self.content = null;
const content_copy = try self.allocator.dupe(u8, content);
self.content = content_copy;
try self.refresh_injections(content); try self.refresh_injections(content);
} }
@ -195,8 +210,6 @@ pub fn refresh_injections(self: *Self, content: []const u8) !void {
const crange = content_range orelse continue; const crange = content_range orelse continue;
// Determine language name: dynamic @injection.language capture takes priority,
// then fall back to a static #set! injection.language predicate.
const lang_name: []const u8 = if (lang_range) |lr| const lang_name: []const u8 = if (lang_range) |lr|
extract_node_text(content, lr) orelse continue extract_node_text(content, lr) orelse continue
else else
@ -211,21 +224,17 @@ pub fn refresh_injections(self: *Self, content: []const u8) !void {
const start_byte = crange.start_byte; const start_byte = crange.start_byte;
const end_byte = crange.end_byte; const end_byte = crange.end_byte;
if (start_byte >= end_byte or end_byte > content.len) continue; if (start_byte >= end_byte or end_byte > content.len) continue;
const child_content = content[start_byte..end_byte];
const child = try Self.create(file_type, self.allocator, self.query_cache);
errdefer child.destroy();
if (child.tree) |t| t.destroy();
child.tree = try child.parser.parseString(null, child_content);
const lang_name_owned = try self.allocator.dupe(u8, lang_name); const lang_name_owned = try self.allocator.dupe(u8, lang_name);
errdefer self.allocator.free(lang_name_owned); errdefer self.allocator.free(lang_name_owned);
try self.injection_list.append(self.allocator, .{ try self.injection_list.append(self.allocator, .{
.lang_name = lang_name_owned, .lang_name = lang_name_owned,
.file_type = file_type,
.start_point = crange.start_point, .start_point = crange.start_point,
.end_row = crange.end_point.row, .end_row = crange.end_point.row,
.syntax = child, .start_byte = start_byte,
.end_byte = end_byte,
}); });
} }
} }
@ -350,15 +359,26 @@ fn CallBack(comptime T: type) type {
return fn (ctx: T, sel: Range, scope: []const u8, id: u32, capture_idx: usize, node: *const Node) error{Stop}!void; return fn (ctx: T, sel: Range, scope: []const u8, id: u32, capture_idx: usize, node: *const Node) error{Stop}!void;
} }
pub fn render(self: *const Self, ctx: anytype, comptime cb: CallBack(@TypeOf(ctx)), range: ?Range) !void { pub fn render(self: *Self, ctx: anytype, comptime cb: CallBack(@TypeOf(ctx)), range: ?Range) !void {
try self.render_highlights_only(ctx, cb, range); try self.render_highlights_only(ctx, cb, range);
const content = self.content orelse return;
for (self.injection_list.items) |*inj| { for (self.injection_list.items) |*inj| {
if (range) |r| { if (range) |r| {
if (inj.end_row < r.start_point.row) continue; if (inj.end_row < r.start_point.row) continue;
if (inj.start_point.row > r.end_point.row) continue; if (inj.start_point.row > r.end_point.row) continue;
} }
if (inj.syntax == null) {
const child_content = content[inj.start_byte..inj.end_byte];
const child = try Self.create(inj.file_type, self.allocator, self.query_cache);
errdefer child.destroy();
if (child.tree) |t| t.destroy();
child.tree = try child.parser.parseString(null, child_content);
inj.syntax = child;
}
const child_syn = inj.syntax.?;
const child_range: ?Range = if (range) |r| blk: { const child_range: ?Range = if (range) |r| blk: {
const child_start_row: u32 = if (r.start_point.row > inj.start_point.row) const child_start_row: u32 = if (r.start_point.row > inj.start_point.row)
r.start_point.row - inj.start_point.row r.start_point.row - inj.start_point.row
@ -373,8 +393,7 @@ pub fn render(self: *const Self, ctx: anytype, comptime cb: CallBack(@TypeOf(ctx
}; };
} else null; } else null;
// Wrap the context so we can translate local ranges to absolute // Wrap the context to translate local ranges to document coordinates
// document coordinates before forwarding to the callback
const InjCtx = struct { const InjCtx = struct {
parent_ctx: @TypeOf(ctx), parent_ctx: @TypeOf(ctx),
inj: *const Injection, inj: *const Injection,
@ -389,12 +408,10 @@ pub fn render(self: *const Self, ctx: anytype, comptime cb: CallBack(@TypeOf(ctx
) error{Stop}!void { ) error{Stop}!void {
const start_row = child_sel.start_point.row + self_.inj.start_point.row; const start_row = child_sel.start_point.row + self_.inj.start_point.row;
const end_row = child_sel.end_point.row + self_.inj.start_point.row; const end_row = child_sel.end_point.row + self_.inj.start_point.row;
// Column offset only applies on the very first line of the injection
const start_col = child_sel.start_point.column + const start_col = child_sel.start_point.column +
if (child_sel.start_point.row == 0) self_.inj.start_point.column else 0; if (child_sel.start_point.row == 0) self_.inj.start_point.column else 0;
const end_col = child_sel.end_point.column + const end_col = child_sel.end_point.column +
if (child_sel.end_point.row == 0) self_.inj.start_point.column else 0; if (child_sel.end_point.row == 0) self_.inj.start_point.column else 0;
const doc_range: Range = .{ const doc_range: Range = .{
.start_point = .{ .row = start_row, .column = start_col }, .start_point = .{ .row = start_row, .column = start_col },
.end_point = .{ .row = end_row, .column = end_col }, .end_point = .{ .row = end_row, .column = end_col },
@ -406,7 +423,7 @@ pub fn render(self: *const Self, ctx: anytype, comptime cb: CallBack(@TypeOf(ctx
}; };
var inj_ctx: InjCtx = .{ .parent_ctx = ctx, .inj = inj }; var inj_ctx: InjCtx = .{ .parent_ctx = ctx, .inj = inj };
try inj.syntax.render_highlights_only(&inj_ctx, InjCtx.translated_cb, child_range); try child_syn.render_highlights_only(&inj_ctx, InjCtx.translated_cb, child_range);
} }
} }