diff --git a/src/config.zig b/src/config.zig index 96b00c1..6468324 100644 --- a/src/config.zig +++ b/src/config.zig @@ -114,6 +114,7 @@ pub const WhitespaceMode = enum { leading, eol, tabs, + external, visible, full, none, diff --git a/src/project_manager.zig b/src/project_manager.zig index 7e9a309..48111ed 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -58,17 +58,36 @@ pub fn shutdown() void { pid.send(.{"shutdown"}) catch {}; } -pub fn open(rel_project_directory: []const u8) (ProjectManagerError || FileSystemError || std.fs.File.OpenError || SetCwdError)!void { +pub fn open(rel_project_directory: []const u8) (ProjectManagerError || FileSystemError || std.fs.File.OpenError || SetCwdError)!?[]const u8 { var path_buf: [std.fs.max_path_bytes]u8 = undefined; const project_directory = std.fs.cwd().realpath(rel_project_directory, &path_buf) catch "(none)"; const current_project = tp.env.get().str("project"); - if (std.mem.eql(u8, current_project, project_directory)) return; + if (std.mem.eql(u8, current_project, project_directory)) return get_project_state(project_directory); if (!root.is_directory(project_directory)) return error.InvalidProjectDirectory; var dir = try std.fs.openDirAbsolute(project_directory, .{}); try dir.setAsCwd(); dir.close(); tp.env.get().str_set("project", project_directory); - return send(.{ "open", project_directory }); + try send(.{ "open", project_directory }); + return get_project_state(project_directory); +} + +const project_state_allocator = std.heap.c_allocator; +var project_state_mutex: std.Thread.Mutex = .{}; +var project_state: ProjectStateMap = .empty; +const ProjectStateMap = std.StringHashMapUnmanaged(std.array_list.Managed(u8)); + +fn get_project_state(project_directory: []const u8) ?[]const u8 { + project_state_mutex.lock(); + defer project_state_mutex.unlock(); + return if (project_state.get(project_directory)) |state| state.items else null; +} + +pub fn store_state(project_directory: []const u8, state: std.array_list.Managed(u8)) error{OutOfMemory}!void { + project_state_mutex.lock(); + defer project_state_mutex.unlock(); + if (project_state.fetchRemove(project_directory)) |old_state| old_state.value.deinit(); + try project_state.put(project_state_allocator, try project_state_allocator.dupe(u8, project_directory), state); } pub fn close(project_directory: []const u8) (ProjectManagerError || error{CloseCurrentProject})!void { diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 09217e3..ddcda8f 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -1562,6 +1562,18 @@ pub const Editor = struct { cell.cell.char.grapheme = c; } }, + .external => { + if (leading) { + if (get_whitespace_char(cell_type, next_cell_type)) |c| + cell.cell.char.grapheme = c; + } + if (cell_type == .eol) + cell.cell.char.grapheme = char.eol; + if (cell_type == .tab or cell_type == .extension) { + if (get_whitespace_char(cell_type, next_cell_type)) |c| + cell.cell.char.grapheme = c; + } + }, .visible => { if (get_whitespace_char(cell_type, next_cell_type)) |c| cell.cell.char.grapheme = c; @@ -1573,7 +1585,7 @@ pub const Editor = struct { else => "#", }; }, - else => {}, + .none => {}, } if (tab_error) { cell.set_style_fg(theme.editor_error); diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index 0f7d9d3..131e558 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -356,7 +356,8 @@ const cmds = struct { pub const quit_without_saving_meta: Meta = .{ .description = "Quit without saving" }; pub fn open_project_cwd(self: *Self, _: Ctx) Result { - try project_manager.open("."); + if (try project_manager.open(".")) |state| + try self.restore_state(state); if (self.top_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" }); if (self.bottom_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" }); } @@ -366,7 +367,8 @@ const cmds = struct { var project_dir: []const u8 = undefined; if (!try ctx.args.match(.{tp.extract(&project_dir)})) return; - try project_manager.open(project_dir); + if (try project_manager.open(project_dir)) |state| + try self.restore_state(state); const project = tp.env.get().str("project"); tui.rdr().set_terminal_working_directory(project); if (self.top_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" }); @@ -394,7 +396,20 @@ const cmds = struct { if (!try ctx.args.match(.{tp.extract(&project_dir)})) return; try self.check_all_not_dirty(); - try project_manager.open(project_dir); + + { + var state_writer: std.Io.Writer.Allocating = .init(self.allocator); + defer state_writer.deinit(); + try self.write_state(&state_writer.writer); + try state_writer.writer.flush(); + const old_project = tp.env.get().str("project"); + var state_al = state_writer.toArrayList(); + const state = state_al.toManaged(self.allocator); + try project_manager.store_state(old_project, state); + } + + const project_state = try project_manager.open(project_dir); + try self.close_all_editors(); self.delete_all_buffers(); self.clear_find_in_files_results(.diagnostics); @@ -402,11 +417,15 @@ const cmds = struct { try self.toggle_panel_view(filelist_view, false); self.buffer_manager.deinit(); self.buffer_manager = Buffer.Manager.init(self.allocator); + const project = tp.env.get().str("project"); tui.rdr().set_terminal_working_directory(project); + if (project_state) |state| try self.restore_state(state); + if (self.top_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" }); if (self.bottom_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" }); - tp.self_pid().send(.{ "cmd", "open_recent" }) catch return; + if (project_state == null) + tp.self_pid().send(.{ "cmd", "open_recent" }) catch return; } pub const change_project_meta: Meta = .{ .arguments = &.{.string} }; @@ -1599,42 +1618,49 @@ fn create_home_split(self: *Self) !void { tui.resize(); } -pub fn write_restore_info(self: *Self) void { - var sfa = std.heap.stackFallback(512, self.allocator); - const a = sfa.get(); - var meta: std.Io.Writer.Allocating = .init(a); - defer meta.deinit(); - const writer = &meta.writer; - - if (self.get_active_editor()) |editor| { - cbor.writeValue(writer, editor.file_path) catch return; - editor.update_meta(); - } else { - cbor.writeValue(writer, null) catch return; - } - - if (tui.clipboard_get_history()) |clipboard| { - cbor.writeArrayHeader(writer, clipboard.len) catch return; - for (clipboard) |item| { - cbor.writeArrayHeader(writer, 2) catch return; - cbor.writeValue(writer, item.group) catch return; - cbor.writeValue(writer, item.text) catch return; - } - } else { - cbor.writeValue(writer, null) catch return; - } - - const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager"); - buffer_manager.write_state(writer) catch return; - - if (self.widgets.get("tabs")) |tabs_widget| - if (tabs_widget.dynamic_cast(@import("status/tabs.zig").TabBar)) |tabs| - tabs.write_state(writer) catch return; +pub const WriteStateError = error{ + OutOfMemory, + Stop, + WriteFailed, +}; +pub fn write_restore_info(self: *Self) WriteStateError!void { const file_name = root.get_restore_file_name() catch return; var file = std.fs.createFileAbsolute(file_name, .{ .truncate = true }) catch return; defer file.close(); - file.writeAll(meta.written()) catch return; + var buf: [32 + 1024]u8 = undefined; + var file_writer = file.writer(&buf); + const writer = &file_writer.interface; + + try self.write_state(writer); + try writer.flush(); +} + +pub fn write_state(self: *Self, writer: *std.Io.Writer) WriteStateError!void { + if (self.get_active_editor()) |editor| { + try cbor.writeValue(writer, editor.file_path); + editor.update_meta(); + } else { + try cbor.writeValue(writer, null); + } + + if (tui.clipboard_get_history()) |clipboard| { + try cbor.writeArrayHeader(writer, clipboard.len); + for (clipboard) |item| { + try cbor.writeArrayHeader(writer, 2); + try cbor.writeValue(writer, item.group); + try cbor.writeValue(writer, item.text); + } + } else { + try cbor.writeValue(writer, null); + } + + const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager"); + try buffer_manager.write_state(writer); + + if (self.widgets.get("tabs")) |tabs_widget| + if (tabs_widget.dynamic_cast(@import("status/tabs.zig").TabBar)) |tabs| + try tabs.write_state(writer); } fn read_restore_info(self: *Self) !void { @@ -1647,31 +1673,40 @@ fn read_restore_info(self: *Self) !void { const size = try file.readAll(buf); var iter: []const u8 = buf[0..size]; + try self.extract_state(&iter); +} + +fn restore_state(self: *Self, state: []const u8) !void { + var iter = state; + try self.extract_state(&iter); +} + +fn extract_state(self: *Self, iter: *[]const u8) !void { 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; + if (!try cbor.matchValue(iter, cbor.extract(&editor_file_path))) return error.MatchFilePathFailed; tui.clipboard_clear_all(); - var len = try cbor.decodeArrayHeader(&iter); + var len = try cbor.decodeArrayHeader(iter); var prev_group: usize = 0; const clipboard_allocator = tui.clipboard_allocator(); while (len > 0) : (len -= 1) { - const len_ = try cbor.decodeArrayHeader(&iter); - if (len_ != 2) return error.Stop; + const len_ = try cbor.decodeArrayHeader(iter); + if (len_ != 2) return error.MatchClipboardArrayFailed; var group: usize = 0; var text: []const u8 = undefined; - if (!try cbor.matchValue(&iter, cbor.extract(&group))) return error.Stop; - if (!try cbor.matchValue(&iter, cbor.extract(&text))) return error.Stop; + if (!try cbor.matchValue(iter, cbor.extract(&group))) return error.MatchClipboardGroupFailed; + if (!try cbor.matchValue(iter, cbor.extract(&text))) return error.MatchClipboardTextFailed; if (prev_group != group) tui.clipboard_start_group(); prev_group = group; tui.clipboard_add_chunk(try clipboard_allocator.dupe(u8, text)); } - try self.buffer_manager.extract_state(&iter); + try self.buffer_manager.extract_state(iter); if (self.widgets.get("tabs")) |tabs_widget| if (tabs_widget.dynamic_cast(@import("status/tabs.zig").TabBar)) |tabs| - tabs.extract_state(&iter) catch |e| { + tabs.extract_state(iter) catch |e| { const logger = log.logger("mainview"); defer logger.deinit(); logger.print_err("mainview", "failed to restore tabs: {}", .{e}); @@ -1683,10 +1718,10 @@ fn read_restore_info(self: *Self) !void { send_buffer_did_open(self.allocator, buffer) catch {}; if (editor_file_path) |file_path| { - try tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_path } }); - } else { - try tp.self_pid().send(.{ "cmd", "close_file" }); + if (self.buffer_manager.get_buffer_for_file(file_path)) |_| + return tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_path } }); } + try tp.self_pid().send(.{ "cmd", "close_file" }); } fn send_buffer_did_open(allocator: std.mem.Allocator, buffer: *Buffer) !void { diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 20e4b21..a61f193 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -397,7 +397,7 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void { } if (try m.match(.{"restart"})) { - if (mainview()) |mv| mv.write_restore_info(); + if (mainview()) |mv| try mv.write_restore_info(); project_manager.shutdown(); self.final_exit = "restart"; return; @@ -1002,7 +1002,8 @@ const cmds = struct { .indent => .leading, .leading => .eol, .eol => .tabs, - .tabs => .visible, + .tabs => .external, + .external => .visible, .visible => .full, .full => .none, };