From 991c47f3b3f72ef276864c237a2669e27b8bc180 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 5 Aug 2025 20:08:29 +0200 Subject: [PATCH] feat: restore buffer manager state on restart --- build.zig | 1 + src/buffer/Buffer.zig | 57 ++++++++++++++++++++++++++++++++++++++++++ src/buffer/Manager.zig | 31 ++++++++++++++++++++++- src/main.zig | 2 +- src/tui/editor.zig | 23 +++++++++++------ src/tui/mainview.zig | 21 +++++++++++++--- 6 files changed, 121 insertions(+), 14 deletions(-) diff --git a/build.zig b/build.zig index 313e84b..d698061 100644 --- a/build.zig +++ b/build.zig @@ -361,6 +361,7 @@ pub fn build_exe( .{ .name = "cbor", .module = cbor_mod }, .{ .name = "thespian", .module = thespian_mod }, .{ .name = "LetterCasing", .module = zg_dep.module("LetterCasing") }, + .{ .name = "file_type_config", .module = file_type_config_mod }, }, }); diff --git a/src/buffer/Buffer.zig b/src/buffer/Buffer.zig index 7f265f0..6d5a1d2 100644 --- a/src/buffer/Buffer.zig +++ b/src/buffer/Buffer.zig @@ -1,5 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); +const cbor = @import("cbor"); +const file_type_config = @import("file_type_config"); const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; const cwd = std.fs.cwd; @@ -1487,3 +1489,58 @@ pub fn redo(self: *Self) error{Stop}![]const u8 { self.mtime = std.time.milliTimestamp(); return h.meta; } + +pub fn write_state(self: *const Self, writer: MetaWriter) error{ Stop, OutOfMemory }!void { + var content = std.ArrayListUnmanaged(u8).empty; + defer content.deinit(self.external_allocator); + try self.root.store(content.writer(self.external_allocator), self.file_eol_mode); + + try cbor.writeArrayHeader(writer, 8); + try cbor.writeValue(writer, self.get_file_path()); + try cbor.writeValue(writer, self.file_exists); + try cbor.writeValue(writer, self.file_eol_mode); + try cbor.writeValue(writer, self.hidden); + try cbor.writeValue(writer, self.ephemeral); + try cbor.writeValue(writer, self.meta orelse &.{}); + try cbor.writeValue(writer, self.file_type_name); + try cbor.writeValue(writer, content.items); +} + +pub const ExtractStateOperation = enum { none, open_file }; + +pub fn extract_state(self: *Self, iter: *[]const u8) !void { + var file_path: []const u8 = undefined; + var file_type_name: []const u8 = undefined; + var meta: []const u8 = &.{}; + var content: []const u8 = undefined; + + if (!try cbor.matchValue(iter, .{ + cbor.extract(&file_path), + cbor.extract(&self.file_exists), + cbor.extract(&self.file_eol_mode), + cbor.extract(&self.hidden), + cbor.extract(&self.ephemeral), + cbor.extract(&meta), + cbor.extract(&file_type_name), + cbor.extract(&content), + })) + return error.Stop; + + self.set_file_path(file_path); + + if (try file_type_config.get(try self.allocator.dupe(u8, file_type_name))) |config| { + self.file_type_name = config.name; + self.file_type_icon = config.icon; + self.file_type_color = config.color; + } else { + self.file_type_name = file_type_config.default.name; + self.file_type_icon = file_type_config.default.icon; + self.file_type_color = file_type_config.default.color; + } + + if (meta.len > 0) { + if (self.meta) |buf| self.external_allocator.free(buf); + self.meta = if (self.meta) |buf| try self.external_allocator.dupe(u8, buf) else null; + } + try self.reset_from_string_and_update(content); +} diff --git a/src/buffer/Manager.zig b/src/buffer/Manager.zig index d2484ee..9097d58 100644 --- a/src/buffer/Manager.zig +++ b/src/buffer/Manager.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const cbor = @import("cbor"); const tp = @import("thespian"); const Buffer = @import("Buffer.zig"); @@ -51,6 +52,34 @@ pub fn open_scratch(self: *Self, file_path: []const u8, content: []const u8) Buf return buffer; } +pub fn write_state(self: *const Self, writer: Buffer.MetaWriter) error{ Stop, OutOfMemory }!void { + const buffers = self.list_unordered(self.allocator) catch return; + defer self.allocator.free(buffers); + try cbor.writeArrayHeader(writer, buffers.len); + for (buffers) |buffer| { + tp.trace(tp.channel.debug, .{ @typeName(Self), "write_state", buffer.get_file_path(), buffer.file_type_name }); + buffer.write_state(writer) catch |e| { + tp.trace(tp.channel.debug, .{ @typeName(Self), "write_state", "failed", e }); + return; + }; + } +} + +pub fn extract_state(self: *Self, iter: *[]const u8) !void { + var len = try cbor.decodeArrayHeader(iter); + tp.trace(tp.channel.debug, .{ @typeName(Self), "extract_state", len }); + while (len > 0) : (len -= 1) { + var buffer = try Buffer.create(self.allocator); + errdefer |e| { + tp.trace(tp.channel.debug, .{ "buffer", "extract", "failed", buffer.get_file_path(), e }); + buffer.deinit(); + } + try buffer.extract_state(iter); + try self.buffers.put(self.allocator, try self.allocator.dupe(u8, buffer.get_file_path()), buffer); + tp.trace(tp.channel.debug, .{ "buffer", "extract", buffer.get_file_path(), buffer.file_type_name }); + } +} + pub fn get_buffer_for_file(self: *Self, file_path: []const u8) ?*Buffer { return self.buffers.get(file_path); } @@ -89,7 +118,7 @@ pub fn list_most_recently_used(self: *Self, allocator: std.mem.Allocator) error{ return result; } -pub fn list_unordered(self: *Self, allocator: std.mem.Allocator) error{OutOfMemory}![]*Buffer { +pub fn list_unordered(self: *const Self, allocator: std.mem.Allocator) error{OutOfMemory}![]*Buffer { var buffers = try std.ArrayListUnmanaged(*Buffer).initCapacity(allocator, self.buffers.size); var i = self.buffers.iterator(); while (i.next()) |kv| diff --git a/src/main.zig b/src/main.zig index 07e1cf5..f337409 100644 --- a/src/main.zig +++ b/src/main.zig @@ -942,7 +942,7 @@ pub fn get_restore_file_name() ![]const u8 { const restore_file = if (local.restore_file) |file| file else - try std.fmt.bufPrint(&local.restore_file_buffer, "{s}/{s}", .{ try get_app_cache_dir(application_name), restore_file_name }); + try std.fmt.bufPrint(&local.restore_file_buffer, "{s}/{s}", .{ try get_app_state_dir(application_name), restore_file_name }); local.restore_file = restore_file; return restore_file; } diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 09e232c..c73168a 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -371,6 +371,14 @@ pub const Editor = struct { const Meta = command.Metadata; const Result = command.Result; + pub fn update_meta(self: *const Self) void { + var meta = std.ArrayListUnmanaged(u8).empty; + defer meta.deinit(self.allocator); + if (self.buffer) |_| self.write_state(meta.writer(self.allocator)) catch {}; + if (self.buffer) |_| self.write_state(meta.writer(self.allocator)) catch {}; + if (self.buffer) |p| p.set_meta(meta.items) catch {}; + } + pub fn write_state(self: *const Self, writer: Buffer.MetaWriter) !void { try cbor.writeArrayHeader(writer, 12); try cbor.writeValue(writer, self.file_path orelse ""); @@ -401,9 +409,7 @@ pub const Editor = struct { }; } - pub fn extract_state(self: *Self, buf: []const u8, comptime op: enum { none, open_file }) !void { - tp.trace(tp.channel.debug, .{ "extract_state", self.file_path }); - tp.trace(tp.channel.debug, tp.message{ .buf = buf }); + pub fn extract_state(self: *Self, iter: *[]const u8, comptime op: Buffer.ExtractStateOperation) !void { self.restored_state = true; var file_path: []const u8 = undefined; var view_cbor: []const u8 = undefined; @@ -411,7 +417,7 @@ pub const Editor = struct { var clipboard: []const u8 = undefined; var last_find_query: []const u8 = undefined; var find_history: []const u8 = undefined; - if (!try cbor.match(buf, .{ + if (!try cbor.matchValue(iter, .{ tp.extract(&file_path), tp.extract(&clipboard), tp.extract(&last_find_query), @@ -440,11 +446,11 @@ pub const Editor = struct { if (cursels_cbor.len > 0) self.clear_all_cursors(); - var iter = cursels_cbor; - var len = cbor.decodeArrayHeader(&iter) catch return error.RestoreCurSels; + var cursels_iter = cursels_cbor; + var len = cbor.decodeArrayHeader(&cursels_iter) catch return error.RestoreCurSels; while (len > 0) : (len -= 1) { var cursel: CurSel = .{}; - if (!(cursel.extract(&iter) catch false)) break; + if (!(cursel.extract(&cursels_iter) catch false)) break; (try self.cursels.addOne(self.allocator)).* = cursel; } @@ -659,7 +665,8 @@ pub const Editor = struct { if (buffer_meta) |meta| { const frame_ = tracy.initZone(@src(), .{ .name = "extract_state" }); defer frame_.deinit(); - try self.extract_state(meta, .none); + var iter = meta; + try self.extract_state(&iter, .none); } try self.send_editor_open(file_path, new_buf.file_exists, ftn, fti, ftc); } diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index c210882..e9e5a4f 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -1288,11 +1288,18 @@ fn create_home_split(self: *Self) !void { } pub fn write_restore_info(self: *Self) void { - const editor = self.get_active_editor() orelse return; var sfa = std.heap.stackFallback(512, self.allocator); const a = sfa.get(); var meta = std.ArrayListUnmanaged(u8).empty; - editor.write_state(meta.writer(a)) catch return; + const writer = meta.writer(a); + + const editor = self.get_active_editor() orelse return; + cbor.writeValue(writer, editor.file_path) catch return; + editor.update_meta(); + + const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager"); + buffer_manager.write_state(writer) catch return; + const file_name = root.get_restore_file_name() catch return; var file = std.fs.createFileAbsolute(file_name, .{ .truncate = true }) catch return; defer file.close(); @@ -1300,7 +1307,6 @@ pub fn write_restore_info(self: *Self) void { } fn read_restore_info(self: *Self) !void { - const editor = self.get_active_editor() orelse return; const file_name = try root.get_restore_file_name(); const file = try std.fs.cwd().openFile(file_name, .{ .mode = .read_only }); defer file.close(); @@ -1308,7 +1314,14 @@ fn read_restore_info(self: *Self) !void { var buf = try self.allocator.alloc(u8, @intCast(stat.size)); defer self.allocator.free(buf); const size = try file.readAll(buf); - try editor.extract_state(buf[0..size], .open_file); + var iter: []const u8 = buf[0..size]; + + tp.trace(tp.channel.debug, .{ "mainview", "extract" }); + var editor_file_path: []const u8 = undefined; + if (!try cbor.matchValue(&iter, cbor.extract(&editor_file_path))) return error.Stop; + try self.buffer_manager.extract_state(&iter); + + try tp.self_pid().send(.{ "cmd", "navigate", .{ .file = editor_file_path } }); } fn get_next_mru_buffer(self: *Self) ?[]const u8 {