From ed69048e913d61b4fd4b4b6b06257386db115e98 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 2 Feb 2026 21:33:52 +0100 Subject: [PATCH] refactor: add document plain text caching to Buffer --- src/buffer/Buffer.zig | 71 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/src/buffer/Buffer.zig b/src/buffer/Buffer.zig index c36a6be..86624e0 100644 --- a/src/buffer/Buffer.zig +++ b/src/buffer/Buffer.zig @@ -60,6 +60,9 @@ vcs_content: ?ArrayList(u8) = null, vcs_blame: VcsBlame = .{}, last_view: ?usize = null, +cache: ?StringCache = null, +last_save_cache: ?StringCache = null, + undo_head: ?*UndoNode = null, redo_head: ?*UndoNode = null, @@ -963,7 +966,11 @@ const Node = union(enum) { return .{ line, col, self }; } - pub fn store(self: *const Node, writer: *std.Io.Writer, eol_mode: EolMode) !void { + pub fn store_node(self: *const Node, writer: *std.Io.Writer, eol_mode: EolMode) !void { + return self.store(writer, eol_mode); + } + + fn store(self: *const Node, writer: *std.Io.Writer, eol_mode: EolMode) !void { switch (self.*) { .node => |*node| { try node.left.store(writer, eol_mode); @@ -1228,6 +1235,8 @@ pub fn deinit(self: *Self) void { self.clear_vcs_content(); self.clear_vcs_blame(); self.vcs_blame.reset(self.external_allocator); + if (self.cache) |*cache| cache.deinit(self.external_allocator); + if (self.last_save_cache) |*cache| cache.deinit(self.external_allocator); if (self.vcs_id) |buf| self.external_allocator.free(buf); if (self.meta) |buf| self.external_allocator.free(buf); if (self.file_buf) |buf| self.external_allocator.free(buf); @@ -1498,12 +1507,64 @@ pub fn refresh_from_file(self: *Self) LoadFromFileError!void { self.update_last_used_time(); } -pub fn store_to_string(self: *const Self, allocator: Allocator, eol_mode: EolMode) ![]u8 { - var s: std.Io.Writer.Allocating = try .initCapacity(allocator, self.root.weights_sum().len); - try self.root.store(&s.writer, eol_mode); - return s.toOwnedSlice(); +pub fn store_to_string_cached(self: *Self, root: *const Node, eol_mode: EolMode) [:0]const u8 { + std.log.debug("BEGIN store_to_string_cached 0x{x}", .{root.to_ref()}); + defer std.log.debug("END store_to_string_cached 0x{x}", .{root.to_ref()}); + + if (get_cached_text(self.cache, root.to_ref(), eol_mode)) |text| return text; + var s: std.Io.Writer.Allocating = std.Io.Writer.Allocating.initCapacity(self.external_allocator, root.weights_sum().len) catch @panic("OOM store_to_string_cached"); + root.store(&s.writer, eol_mode) catch @panic("store_to_string_cached"); + return self.store_cached_text(&self.cache, root.to_ref(), eol_mode, s.toOwnedSliceSentinel(0) catch @panic("OOM store_to_string_cached")); } +pub fn store_last_save_to_string_cached(self: *Self, eol_mode: EolMode) ?[]const u8 { + const root = self.last_save orelse return null; + std.log.debug("BEGIN store_last_save_to_string_cached 0x{x}", .{root.to_ref()}); + defer std.log.debug("END store_last_save_to_string_cached 0x{x}", .{root.to_ref()}); + + if (get_cached_text(self.last_save_cache, root.to_ref(), eol_mode)) |text| return text; + var s: std.Io.Writer.Allocating = std.Io.Writer.Allocating.initCapacity(self.external_allocator, root.weights_sum().len) catch @panic("OOM store_last_save_to_string_cached"); + root.store(&s.writer, eol_mode) catch @panic("store_last_save_to_string_cached"); + return self.store_cached_text(&self.last_save_cache, root.to_ref(), eol_mode, s.toOwnedSliceSentinel(0) catch @panic("OOM store_last_save_to_string_cached")); +} + +fn get_cached_text(cache_: ?StringCache, ref: Node.Ref, eol_mode: EolMode) ?[:0]const u8 { + const cache = cache_ orelse return null; + return if (cache.ref == ref and cache.eol_mode == eol_mode) blk: { + std.log.debug("fetched string cache 0x{x}", .{cache.ref}); + break :blk cache.text; + } else blk: { + std.log.debug("cache miss for 0x{x} (was 0x{x})", .{ ref, cache.ref }); + break :blk null; + }; +} + +fn store_cached_text(self: *Self, cache: *?StringCache, ref: Node.Ref, eol_mode: EolMode, text: [:0]const u8) [:0]const u8 { + if (cache.*) |*c| { + std.log.debug("0x{x} updated string cache 0x{x} -> 0x{x}", .{ @intFromPtr(self), c.ref, ref }); + c.deinit(self.external_allocator); + } else std.log.debug("0x{x} stored string cache 0x{x}", .{ @intFromPtr(self), ref }); + cache.* = .{ + .ref = ref, + .eol_mode = eol_mode, + .text = text, + }; + return text; +} + +const StringCache = struct { + ref: Node.Ref, + eol_mode: EolMode, + text: [:0]const u8, + code_folded: ?[:0]const u8 = null, + + fn deinit(self: *@This(), allocator: std.mem.Allocator) void { + std.log.debug("cleared string cache 0x{x}", .{self.ref}); + allocator.free(self.text); + if (self.code_folded) |text| allocator.free(text); + } +}; + fn store_to_file_const(self: *const Self, writer: *std.Io.Writer) StoreToFileError!void { try self.root.store(writer, self.file_eol_mode); try writer.flush();