feat: add caching of tree-sitter query objects
This commit is contained in:
		
							parent
							
								
									7b133c04fb
								
							
						
					
					
						commit
						47a6024c80
					
				
					 4 changed files with 152 additions and 15 deletions
				
			
		
							
								
								
									
										125
									
								
								src/syntax/src/QueryCache.zig
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/syntax/src/QueryCache.zig
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,125 @@
 | 
				
			||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const build_options = @import("build_options");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const treez = if (build_options.use_tree_sitter)
 | 
				
			||||||
 | 
					    @import("treez")
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					    @import("treez_dummy.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const FileType = @import("file_type.zig");
 | 
				
			||||||
 | 
					const Query = treez.Query;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					allocator: std.mem.Allocator,
 | 
				
			||||||
 | 
					mutex: ?std.Thread.Mutex,
 | 
				
			||||||
 | 
					highlights: std.StringHashMapUnmanaged(*Query) = .{},
 | 
				
			||||||
 | 
					injections: std.StringHashMapUnmanaged(*Query) = .{},
 | 
				
			||||||
 | 
					ref_count: usize = 1,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const QueryType = enum {
 | 
				
			||||||
 | 
					    highlights,
 | 
				
			||||||
 | 
					    injections,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const QueryParseError = error{
 | 
				
			||||||
 | 
					    InvalidSyntax,
 | 
				
			||||||
 | 
					    InvalidNodeType,
 | 
				
			||||||
 | 
					    InvalidField,
 | 
				
			||||||
 | 
					    InvalidCapture,
 | 
				
			||||||
 | 
					    InvalidStructure,
 | 
				
			||||||
 | 
					    InvalidLanguage,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Error = (error{
 | 
				
			||||||
 | 
					    NotFound,
 | 
				
			||||||
 | 
					    OutOfMemory,
 | 
				
			||||||
 | 
					} || QueryParseError);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn create(allocator: std.mem.Allocator, opts: struct { lock: bool = false }) !*Self {
 | 
				
			||||||
 | 
					    const self = try allocator.create(Self);
 | 
				
			||||||
 | 
					    self.* = .{
 | 
				
			||||||
 | 
					        .allocator = allocator,
 | 
				
			||||||
 | 
					        .mutex = if (opts.lock) .{} else null,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    return self;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
					    self.release_ref_unlocked_and_maybe_destroy();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn add_ref_locked(self: *Self) void {
 | 
				
			||||||
 | 
					    std.debug.assert(self.ref_count > 0);
 | 
				
			||||||
 | 
					    self.ref_count += 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn release_ref_unlocked_and_maybe_destroy(self: *Self) void {
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (self.mutex) |*mtx| mtx.lock();
 | 
				
			||||||
 | 
					        defer if (self.mutex) |*mtx| mtx.unlock();
 | 
				
			||||||
 | 
					        self.ref_count -= 1;
 | 
				
			||||||
 | 
					        if (self.ref_count > 0) return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var iter_highlights = self.highlights.iterator();
 | 
				
			||||||
 | 
					    while (iter_highlights.next()) |p| {
 | 
				
			||||||
 | 
					        self.allocator.free(p.key_ptr.*);
 | 
				
			||||||
 | 
					        p.value_ptr.*.destroy();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    var iter_injections = self.injections.iterator();
 | 
				
			||||||
 | 
					    while (iter_injections.next()) |p| {
 | 
				
			||||||
 | 
					        self.allocator.free(p.key_ptr.*);
 | 
				
			||||||
 | 
					        p.value_ptr.*.destroy();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    self.highlights.deinit(self.allocator);
 | 
				
			||||||
 | 
					    self.injections.deinit(self.allocator);
 | 
				
			||||||
 | 
					    self.allocator.destroy(self);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn ReturnType(comptime query_type: QueryType) type {
 | 
				
			||||||
 | 
					    return switch (query_type) {
 | 
				
			||||||
 | 
					        .highlights => *Query,
 | 
				
			||||||
 | 
					        .injections => ?*Query,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn get_or_add_internal(self: *Self, file_type: *const FileType, comptime query_type: QueryType) Error!ReturnType(query_type) {
 | 
				
			||||||
 | 
					    const hash = switch (query_type) {
 | 
				
			||||||
 | 
					        .highlights => &self.highlights,
 | 
				
			||||||
 | 
					        .injections => &self.injections,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return if (hash.get(file_type.name)) |query| query else blk: {
 | 
				
			||||||
 | 
					        const lang = file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{file_type.name});
 | 
				
			||||||
 | 
					        const query = try Query.create(lang, switch (query_type) {
 | 
				
			||||||
 | 
					            .highlights => file_type.highlights,
 | 
				
			||||||
 | 
					            .injections => if (file_type.injections) |injections| injections else return null,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        errdefer query.destroy();
 | 
				
			||||||
 | 
					        try hash.put(self.allocator, try self.allocator.dupe(u8, file_type.name), query);
 | 
				
			||||||
 | 
					        break :blk query;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn pre_load(self: *Self, lang_name: []const u8) Error!void {
 | 
				
			||||||
 | 
					    if (self.mutex) |*mtx| mtx.lock();
 | 
				
			||||||
 | 
					    defer if (self.mutex) |*mtx| mtx.unlock();
 | 
				
			||||||
 | 
					    const file_type = FileType.get_by_name(lang_name) orelse return;
 | 
				
			||||||
 | 
					    _ = try self.get_or_add_internal(file_type, .highlights);
 | 
				
			||||||
 | 
					    _ = try self.get_or_add_internal(file_type, .injections);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn get(self: *Self, file_type: *const FileType, comptime query_type: QueryType) Error!ReturnType(query_type) {
 | 
				
			||||||
 | 
					    if (self.mutex) |*mtx| mtx.lock();
 | 
				
			||||||
 | 
					    defer if (self.mutex) |*mtx| mtx.unlock();
 | 
				
			||||||
 | 
					    const query = try self.get_or_add_internal(file_type, query_type);
 | 
				
			||||||
 | 
					    self.add_ref_locked();
 | 
				
			||||||
 | 
					    return query;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn release(self: *Self, query: *Query, comptime query_type: QueryType) void {
 | 
				
			||||||
 | 
					    _ = query;
 | 
				
			||||||
 | 
					    _ = query_type;
 | 
				
			||||||
 | 
					    self.release_ref_unlocked_and_maybe_destroy();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const Edit = treez.InputEdit;
 | 
					pub const Edit = treez.InputEdit;
 | 
				
			||||||
pub const FileType = @import("file_type.zig");
 | 
					pub const FileType = @import("file_type.zig");
 | 
				
			||||||
 | 
					pub const QueryCache = @import("QueryCache.zig");
 | 
				
			||||||
pub const Range = treez.Range;
 | 
					pub const Range = treez.Range;
 | 
				
			||||||
pub const Point = treez.Point;
 | 
					pub const Point = treez.Point;
 | 
				
			||||||
const Input = treez.Input;
 | 
					const Input = treez.Input;
 | 
				
			||||||
| 
						 | 
					@ -26,34 +27,37 @@ query: *Query,
 | 
				
			||||||
injections: ?*Query,
 | 
					injections: ?*Query,
 | 
				
			||||||
tree: ?*treez.Tree = null,
 | 
					tree: ?*treez.Tree = null,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn create(file_type: *const FileType, allocator: std.mem.Allocator) !*Self {
 | 
					pub fn create(file_type: *const FileType, allocator: std.mem.Allocator, query_cache: *QueryCache) !*Self {
 | 
				
			||||||
 | 
					    const query = try query_cache.get(file_type, .highlights);
 | 
				
			||||||
 | 
					    const injections = try query_cache.get(file_type, .injections);
 | 
				
			||||||
    const self = try allocator.create(Self);
 | 
					    const self = try allocator.create(Self);
 | 
				
			||||||
    self.* = .{
 | 
					    self.* = .{
 | 
				
			||||||
        .allocator = allocator,
 | 
					        .allocator = allocator,
 | 
				
			||||||
        .lang = file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{file_type.name}),
 | 
					        .lang = file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{file_type.name}),
 | 
				
			||||||
        .file_type = file_type,
 | 
					        .file_type = file_type,
 | 
				
			||||||
        .parser = try Parser.create(),
 | 
					        .parser = try Parser.create(),
 | 
				
			||||||
        .query = try Query.create(self.lang, file_type.highlights),
 | 
					        .query = query,
 | 
				
			||||||
        .injections = if (file_type.injections) |injections| try Query.create(self.lang, injections) else null,
 | 
					        .injections = injections,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    errdefer self.destroy();
 | 
					    errdefer self.destroy(query_cache);
 | 
				
			||||||
    try self.parser.setLanguage(self.lang);
 | 
					    try self.parser.setLanguage(self.lang);
 | 
				
			||||||
    return self;
 | 
					    return self;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn create_file_type(allocator: std.mem.Allocator, lang_name: []const u8) !*Self {
 | 
					pub fn create_file_type(allocator: std.mem.Allocator, lang_name: []const u8, query_cache: *QueryCache) !*Self {
 | 
				
			||||||
    const file_type = FileType.get_by_name(lang_name) orelse return error.NotFound;
 | 
					    const file_type = FileType.get_by_name(lang_name) orelse return error.NotFound;
 | 
				
			||||||
    return create(file_type, allocator);
 | 
					    return create(file_type, allocator, query_cache);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn create_guess_file_type(allocator: std.mem.Allocator, content: []const u8, file_path: ?[]const u8) !*Self {
 | 
					pub fn create_guess_file_type(allocator: std.mem.Allocator, content: []const u8, file_path: ?[]const u8, query_cache: *QueryCache) !*Self {
 | 
				
			||||||
    const file_type = FileType.guess(file_path, content) orelse return error.NotFound;
 | 
					    const file_type = FileType.guess(file_path, content) orelse return error.NotFound;
 | 
				
			||||||
    return create(file_type, allocator);
 | 
					    return create(file_type, allocator, query_cache);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn destroy(self: *Self) void {
 | 
					pub fn destroy(self: *Self, query_cache: *QueryCache) void {
 | 
				
			||||||
    if (self.tree) |tree| tree.destroy();
 | 
					    if (self.tree) |tree| tree.destroy();
 | 
				
			||||||
    self.query.destroy();
 | 
					    query_cache.release(self.query, .highlights);
 | 
				
			||||||
 | 
					    if (self.injections) |injections| query_cache.release(injections, .injections);
 | 
				
			||||||
    self.parser.destroy();
 | 
					    self.parser.destroy();
 | 
				
			||||||
    self.allocator.destroy(self);
 | 
					    self.allocator.destroy(self);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -464,7 +464,7 @@ pub const Editor = struct {
 | 
				
			||||||
        if (self.buffer) |_| self.write_state(meta.writer()) catch {};
 | 
					        if (self.buffer) |_| self.write_state(meta.writer()) catch {};
 | 
				
			||||||
        for (self.diagnostics.items) |*d| d.deinit(self.diagnostics.allocator);
 | 
					        for (self.diagnostics.items) |*d| d.deinit(self.diagnostics.allocator);
 | 
				
			||||||
        self.diagnostics.deinit();
 | 
					        self.diagnostics.deinit();
 | 
				
			||||||
        if (self.syntax) |syn| syn.destroy();
 | 
					        if (self.syntax) |syn| syn.destroy(tui.query_cache());
 | 
				
			||||||
        self.cursels.deinit();
 | 
					        self.cursels.deinit();
 | 
				
			||||||
        self.matches.deinit();
 | 
					        self.matches.deinit();
 | 
				
			||||||
        self.handlers.deinit();
 | 
					        self.handlers.deinit();
 | 
				
			||||||
| 
						 | 
					@ -588,7 +588,7 @@ pub const Editor = struct {
 | 
				
			||||||
                const frame_ = tracy.initZone(@src(), .{ .name = "create" });
 | 
					                const frame_ = tracy.initZone(@src(), .{ .name = "create" });
 | 
				
			||||||
                defer frame_.deinit();
 | 
					                defer frame_.deinit();
 | 
				
			||||||
                break :blk if (syn_file_type) |ft|
 | 
					                break :blk if (syn_file_type) |ft|
 | 
				
			||||||
                    syntax.create(ft, self.allocator) catch null
 | 
					                    syntax.create(ft, self.allocator, tui.query_cache()) catch null
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
                    null;
 | 
					                    null;
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
| 
						 | 
					@ -4301,7 +4301,7 @@ pub const Editor = struct {
 | 
				
			||||||
            var content = std.ArrayList(u8).init(self.allocator);
 | 
					            var content = std.ArrayList(u8).init(self.allocator);
 | 
				
			||||||
            defer content.deinit();
 | 
					            defer content.deinit();
 | 
				
			||||||
            try root.store(content.writer(), eol_mode);
 | 
					            try root.store(content.writer(), eol_mode);
 | 
				
			||||||
            self.syntax = syntax.create_guess_file_type(self.allocator, content.items, self.file_path) catch |e| switch (e) {
 | 
					            self.syntax = syntax.create_guess_file_type(self.allocator, content.items, self.file_path, tui.query_cache()) catch |e| switch (e) {
 | 
				
			||||||
                error.NotFound => null,
 | 
					                error.NotFound => null,
 | 
				
			||||||
                else => return e,
 | 
					                else => return e,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
| 
						 | 
					@ -5447,7 +5447,7 @@ pub const Editor = struct {
 | 
				
			||||||
        if (!try ctx.args.match(.{tp.extract(&file_type)}))
 | 
					        if (!try ctx.args.match(.{tp.extract(&file_type)}))
 | 
				
			||||||
            return error.InvalidSetFileTypeArgument;
 | 
					            return error.InvalidSetFileTypeArgument;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (self.syntax) |syn| syn.destroy();
 | 
					        if (self.syntax) |syn| syn.destroy(tui.query_cache());
 | 
				
			||||||
        self.syntax_last_rendered_root = null;
 | 
					        self.syntax_last_rendered_root = null;
 | 
				
			||||||
        self.syntax_refresh_full = true;
 | 
					        self.syntax_refresh_full = true;
 | 
				
			||||||
        self.syntax_incremental_reparse = false;
 | 
					        self.syntax_incremental_reparse = false;
 | 
				
			||||||
| 
						 | 
					@ -5457,7 +5457,7 @@ pub const Editor = struct {
 | 
				
			||||||
            defer content.deinit();
 | 
					            defer content.deinit();
 | 
				
			||||||
            const root = try self.buf_root();
 | 
					            const root = try self.buf_root();
 | 
				
			||||||
            try root.store(content.writer(), try self.buf_eol_mode());
 | 
					            try root.store(content.writer(), try self.buf_eol_mode());
 | 
				
			||||||
            const syn = syntax.create_file_type(self.allocator, file_type) catch null;
 | 
					            const syn = syntax.create_file_type(self.allocator, file_type, tui.query_cache()) catch null;
 | 
				
			||||||
            if (syn) |syn_| if (self.file_path) |file_path|
 | 
					            if (syn) |syn_| if (self.file_path) |file_path|
 | 
				
			||||||
                project_manager.did_open(
 | 
					                project_manager.did_open(
 | 
				
			||||||
                    file_path,
 | 
					                    file_path,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ pub const renderer = @import("renderer");
 | 
				
			||||||
const command = @import("command");
 | 
					const command = @import("command");
 | 
				
			||||||
const EventHandler = @import("EventHandler");
 | 
					const EventHandler = @import("EventHandler");
 | 
				
			||||||
const keybind = @import("keybind");
 | 
					const keybind = @import("keybind");
 | 
				
			||||||
 | 
					const syntax = @import("syntax");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Widget = @import("Widget.zig");
 | 
					const Widget = @import("Widget.zig");
 | 
				
			||||||
const MessageFilter = @import("MessageFilter.zig");
 | 
					const MessageFilter = @import("MessageFilter.zig");
 | 
				
			||||||
| 
						 | 
					@ -56,6 +57,7 @@ default_cursor: keybind.CursorShape = .default,
 | 
				
			||||||
fontface_: []const u8 = "",
 | 
					fontface_: []const u8 = "",
 | 
				
			||||||
fontfaces_: std.ArrayListUnmanaged([]const u8) = .{},
 | 
					fontfaces_: std.ArrayListUnmanaged([]const u8) = .{},
 | 
				
			||||||
enable_mouse_idle_timer: bool = false,
 | 
					enable_mouse_idle_timer: bool = false,
 | 
				
			||||||
 | 
					query_cache_: *syntax.QueryCache,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const keepalive = std.time.us_per_day * 365; // one year
 | 
					const keepalive = std.time.us_per_day * 365; // one year
 | 
				
			||||||
const idle_frames = 0;
 | 
					const idle_frames = 0;
 | 
				
			||||||
| 
						 | 
					@ -120,6 +122,7 @@ fn init(allocator: Allocator) !*Self {
 | 
				
			||||||
        )),
 | 
					        )),
 | 
				
			||||||
        .theme_ = theme_,
 | 
					        .theme_ = theme_,
 | 
				
			||||||
        .no_sleep = tp.env.get().is("no-sleep"),
 | 
					        .no_sleep = tp.env.get().is("no-sleep"),
 | 
				
			||||||
 | 
					        .query_cache_ = try syntax.QueryCache.create(allocator, .{}),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    instance_ = self;
 | 
					    instance_ = self;
 | 
				
			||||||
    defer instance_ = null;
 | 
					    defer instance_ = null;
 | 
				
			||||||
| 
						 | 
					@ -211,6 +214,7 @@ fn deinit(self: *Self) void {
 | 
				
			||||||
    self.rdr_.stop();
 | 
					    self.rdr_.stop();
 | 
				
			||||||
    self.rdr_.deinit();
 | 
					    self.rdr_.deinit();
 | 
				
			||||||
    self.logger.deinit();
 | 
					    self.logger.deinit();
 | 
				
			||||||
 | 
					    self.query_cache_.deinit();
 | 
				
			||||||
    self.allocator.destroy(self);
 | 
					    self.allocator.destroy(self);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1052,6 +1056,10 @@ pub fn mini_mode() ?*MiniMode {
 | 
				
			||||||
    return if (current().mini_mode_) |*p| p else null;
 | 
					    return if (current().mini_mode_) |*p| p else null;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn query_cache() *syntax.QueryCache {
 | 
				
			||||||
 | 
					    return current().query_cache_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn config() *const @import("config") {
 | 
					pub fn config() *const @import("config") {
 | 
				
			||||||
    return ¤t().config_;
 | 
					    return ¤t().config_;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue