feat: save and restore file MRU state per project
This commit is contained in:
		
							parent
							
								
									1d9155864d
								
							
						
					
					
						commit
						1288021682
					
				
					 2 changed files with 93 additions and 1 deletions
				
			
		| 
						 | 
					@ -20,6 +20,7 @@ const File = struct {
 | 
				
			||||||
    mtime: i128,
 | 
					    mtime: i128,
 | 
				
			||||||
    row: usize = 0,
 | 
					    row: usize = 0,
 | 
				
			||||||
    col: usize = 0,
 | 
					    col: usize = 0,
 | 
				
			||||||
 | 
					    visited: bool = false,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn init(a: std.mem.Allocator, name: []const u8) error{OutOfMemory}!Self {
 | 
					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);
 | 
					    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 {
 | 
					fn get_lsp(self: *Self, language_server: []const u8) !LSP {
 | 
				
			||||||
    if (self.language_servers.get(language_server)) |lsp| return lsp;
 | 
					    if (self.language_servers.get(language_server)) |lsp| return lsp;
 | 
				
			||||||
    const lsp = try LSP.open(self.a, .{ .buf = language_server });
 | 
					    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 {
 | 
					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();
 | 
					    defer self.sort_files_by_mtime();
 | 
				
			||||||
    for (self.files.items) |*file| {
 | 
					    for (self.files.items) |*file| {
 | 
				
			||||||
        if (!std.mem.eql(u8, file.path, file_path)) continue;
 | 
					        if (!std.mem.eql(u8, file.path, file_path)) continue;
 | 
				
			||||||
        file.mtime = std.time.nanoTimestamp();
 | 
					        file.mtime = mtime;
 | 
				
			||||||
        if (row != 0) {
 | 
					        if (row != 0) {
 | 
				
			||||||
            file.row = row;
 | 
					            file.row = row;
 | 
				
			||||||
            file.col = col;
 | 
					            file.col = col;
 | 
				
			||||||
 | 
					            file.visited = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ const cbor = @import("cbor");
 | 
				
			||||||
const log = @import("log");
 | 
					const log = @import("log");
 | 
				
			||||||
const tracy = @import("tracy");
 | 
					const tracy = @import("tracy");
 | 
				
			||||||
const FileType = @import("syntax").FileType;
 | 
					const FileType = @import("syntax").FileType;
 | 
				
			||||||
 | 
					const root = @import("root");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Project = @import("Project.zig");
 | 
					const Project = @import("Project.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -154,6 +155,7 @@ const Process = struct {
 | 
				
			||||||
            if (self.walker) |pid| pid.deinit();
 | 
					            if (self.walker) |pid| pid.deinit();
 | 
				
			||||||
            self.walker = null;
 | 
					            self.walker = null;
 | 
				
			||||||
            const project = self.projects.get(project_directory) orelse return;
 | 
					            const project = self.projects.get(project_directory) orelse return;
 | 
				
			||||||
 | 
					            self.restore_project(project) catch {};
 | 
				
			||||||
            project.sort_files_by_mtime();
 | 
					            project.sort_files_by_mtime();
 | 
				
			||||||
            self.logger.print("opened: {s} with {d} files in {d} ms", .{
 | 
					            self.logger.print("opened: {s} with {d} files in {d} ms", .{
 | 
				
			||||||
                project_directory,
 | 
					                project_directory,
 | 
				
			||||||
| 
						 | 
					@ -177,6 +179,7 @@ const Process = struct {
 | 
				
			||||||
            self.get_mru_position(from, project_directory, path) catch |e| return from.forward_error(e);
 | 
					            self.get_mru_position(from, project_directory, path) catch |e| return from.forward_error(e);
 | 
				
			||||||
        } else if (try m.match(.{"shutdown"})) {
 | 
					        } else if (try m.match(.{"shutdown"})) {
 | 
				
			||||||
            if (self.walker) |pid| pid.send(.{"stop"}) catch {};
 | 
					            if (self.walker) |pid| pid.send(.{"stop"}) catch {};
 | 
				
			||||||
 | 
					            self.persist_projects();
 | 
				
			||||||
            try from.send(.{ "project_manager", "shutdown" });
 | 
					            try from.send(.{ "project_manager", "shutdown" });
 | 
				
			||||||
            return tp.exit_normal();
 | 
					            return tp.exit_normal();
 | 
				
			||||||
        } else if (try m.match(.{ "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");
 | 
					        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);
 | 
					        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 {
 | 
					fn walk_tree_async(a_: std.mem.Allocator, root_path_: []const u8) error{Exit}!tp.pid {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue