From 1288021682aeff21c98abab209fce21e3ee22b17 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 11 Apr 2024 18:36:54 +0200 Subject: [PATCH] feat: save and restore file MRU state per project --- src/Project.zig | 37 +++++++++++++++++++++++++- src/project_manager.zig | 57 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/Project.zig b/src/Project.zig index 5951624..20a34eb 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -20,6 +20,7 @@ const File = struct { mtime: i128, row: usize = 0, col: usize = 0, + visited: bool = false, }; pub fn init(a: std.mem.Allocator, name: []const u8) error{OutOfMemory}!Self { @@ -48,6 +49,34 @@ pub fn deinit(self: *Self) void { self.a.free(self.name); } +pub fn write_state(self: *Self, writer: anytype) !void { + for (self.files.items) |file| { + if (!file.visited) continue; + try cbor.writeArrayHeader(writer, 4); + try cbor.writeValue(writer, file.path); + try cbor.writeValue(writer, file.mtime); + try cbor.writeValue(writer, file.row); + try cbor.writeValue(writer, file.col); + } +} + +pub fn restore_state(self: *Self, data: []const u8) !void { + var path: []const u8 = undefined; + var mtime: i128 = undefined; + var row: usize = undefined; + var col: usize = undefined; + defer self.sort_files_by_mtime(); + var iter: []const u8 = data; + while (try cbor.matchValue(&iter, .{ + tp.extract(&path), + tp.extract(&mtime), + tp.extract(&row), + tp.extract(&col), + })) { + try self.update_mru_internal(path, mtime, row, col); + } +} + fn get_lsp(self: *Self, language_server: []const u8) !LSP { if (self.language_servers.get(language_server)) |lsp| return lsp; const lsp = try LSP.open(self.a, .{ .buf = language_server }); @@ -113,13 +142,19 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co } pub fn update_mru(self: *Self, file_path: []const u8, row: usize, col: usize) !void { + defer self.sort_files_by_mtime(); + try self.update_mru_internal(file_path, std.time.nanoTimestamp(), row, col); +} + +fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usize, col: usize) !void { defer self.sort_files_by_mtime(); for (self.files.items) |*file| { if (!std.mem.eql(u8, file.path, file_path)) continue; - file.mtime = std.time.nanoTimestamp(); + file.mtime = mtime; if (row != 0) { file.row = row; file.col = col; + file.visited = true; } return; } diff --git a/src/project_manager.zig b/src/project_manager.zig index 05370d8..76a3a4b 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -4,6 +4,7 @@ const cbor = @import("cbor"); const log = @import("log"); const tracy = @import("tracy"); const FileType = @import("syntax").FileType; +const root = @import("root"); const Project = @import("Project.zig"); @@ -154,6 +155,7 @@ const Process = struct { if (self.walker) |pid| pid.deinit(); self.walker = null; const project = self.projects.get(project_directory) orelse return; + self.restore_project(project) catch {}; project.sort_files_by_mtime(); self.logger.print("opened: {s} with {d} files in {d} ms", .{ project_directory, @@ -177,6 +179,7 @@ const Process = struct { self.get_mru_position(from, project_directory, path) catch |e| return from.forward_error(e); } else if (try m.match(.{"shutdown"})) { if (self.walker) |pid| pid.send(.{"stop"}) catch {}; + self.persist_projects(); try from.send(.{ "project_manager", "shutdown" }); return tp.exit_normal(); } else if (try m.match(.{ "exit", "normal" })) { @@ -236,6 +239,60 @@ const Process = struct { const project = if (self.projects.get(project_directory)) |p| p else return tp.exit("No project"); return project.update_mru(file_path, row, col) catch |e| tp.exit_error(e); } + + fn persist_projects(self: *Process) void { + var i = self.projects.iterator(); + while (i.next()) |p| self.persist_project(p.value_ptr.*) catch {}; + } + + fn persist_project(self: *Process, project: *Project) !void { + self.logger.print("saving: {s}", .{project.name}); + const file_name = try get_project_cache_file_path(self.a, project); + defer self.a.free(file_name); + var file = try std.fs.createFileAbsolute(file_name, .{ .truncate = true }); + defer file.close(); + var buffer = std.io.bufferedWriter(file.writer()); + try project.write_state(buffer.writer()); + return buffer.flush(); + } + + fn restore_project(self: *Process, project: *Project) !void { + self.logger.print("restoring: {s}", .{project.name}); + const file_name = try get_project_cache_file_path(self.a, project); + defer self.a.free(file_name); + var file = std.fs.openFileAbsolute(file_name, .{ .mode = .read_only }) catch |e| switch (e) { + error.FileNotFound => return, + else => return e, + }; + defer file.close(); + const stat = try file.stat(); + var buffer = try self.a.alloc(u8, stat.size); + defer self.a.free(buffer); + const size = try file.readAll(buffer); + try project.restore_state(buffer[0..size]); + } + + fn get_project_cache_file_path(a: std.mem.Allocator, project: *Project) ![]const u8 { + const path = project.name; + var stream = std.ArrayList(u8).init(a); + const writer = stream.writer(); + _ = try writer.write(try root.get_cache_dir()); + _ = try writer.writeByte(std.fs.path.sep); + _ = try writer.write("projects"); + _ = try writer.writeByte(std.fs.path.sep); + std.fs.makeDirAbsolute(stream.items) catch |e| switch (e) { + error.PathAlreadyExists => {}, + else => return e, + }; + var pos: usize = 0; + while (std.mem.indexOfScalarPos(u8, path, pos, std.fs.path.sep)) |next| { + _ = try writer.write(path[pos..next]); + _ = try writer.write("__"); + pos = next + 1; + } + _ = try writer.write(path[pos..]); + return stream.toOwnedSlice(); + } }; fn walk_tree_async(a_: std.mem.Allocator, root_path_: []const u8) error{Exit}!tp.pid {