diff --git a/src/buffer/Buffer.zig b/src/buffer/Buffer.zig index f3a17c7..b5779e4 100644 --- a/src/buffer/Buffer.zig +++ b/src/buffer/Buffer.zig @@ -10,6 +10,7 @@ const max_imbalance = 7; pub const Root = *const Node; pub const unicode = @import("unicode.zig"); +pub const Manager = @import("Manager.zig"); pub const Cursor = @import("Cursor.zig"); pub const View = @import("View.zig"); pub const Selection = @import("Selection.zig"); diff --git a/src/buffer/Manager.zig b/src/buffer/Manager.zig new file mode 100644 index 0000000..910e389 --- /dev/null +++ b/src/buffer/Manager.zig @@ -0,0 +1,79 @@ +const std = @import("std"); +const Buffer = @import("Buffer.zig"); + +const Self = @This(); + +allocator: std.mem.Allocator, +buffers: std.StringHashMapUnmanaged(*Buffer), + +pub fn init(allocator: std.mem.Allocator) Self { + return .{ + .allocator = allocator, + .buffers = .{}, + }; +} + +pub fn deinit(self: *Self) void { + var i = self.buffers.iterator(); + while (i.next()) |p| { + self.allocator.free(p.key_ptr.*); + p.value_ptr.*.deinit(); + } + self.buffers.deinit(self.allocator); +} + +pub fn open_file(self: *Self, file_path: []const u8) Buffer.LoadFromFileError!*Buffer { + if (self.buffers.get(file_path)) |buffer| { + return buffer; + } else { + var buffer = try Buffer.create(self.allocator); + errdefer buffer.deinit(); + try buffer.load_from_file_and_update(file_path); + try self.buffers.put(self.allocator, try self.allocator.dupe(u8, file_path), buffer); + return buffer; + } +} + +pub fn open_scratch(self: *Self, file_path: []const u8, content: []const u8) Buffer.LoadFromStringError!*Buffer { + if (self.buffers.get(file_path)) |buffer| { + return buffer; + } else { + var buffer = try Buffer.create(self.allocator); + errdefer buffer.deinit(); + try buffer.load_from_string_and_update(file_path, content); + buffer.file_exists = true; + try self.buffers.put(self.allocator, try self.allocator.dupe(u8, file_path), buffer); + return buffer; + } +} + +pub fn retire(self: *Self, buffer: *Buffer) void { + _ = self; + _ = buffer; +} + +pub fn list(self: *Self, allocator: std.mem.Allocator) []*const Buffer { + _ = self; + _ = allocator; + unreachable; +} + +pub fn is_dirty(self: *const Self) bool { + var i = self.buffers.iterator(); + while (i.next()) |kv| + if (kv.value_ptr.*.is_dirty()) + return true; + return false; +} + +pub fn is_buffer_dirty(self: *const Self, file_path: []const u8) bool { + return if (self.buffers.get(file_path)) |buffer| buffer.is_dirty() else false; +} + +pub fn save_all(self: *const Self) Buffer.StoreToFileError!void { + var i = self.buffers.iterator(); + while (i.next()) |kv| { + const buffer = kv.value_ptr.*; + try buffer.store_to_file_and_clean(buffer.file_path); + } +} diff --git a/src/tui/editor.zig b/src/tui/editor.zig index c9a64b3..b5665dc 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -218,6 +218,7 @@ pub const Editor = struct { file_path: ?[]const u8, buffer: ?*Buffer, + buffer_manager: *Buffer.Manager, lsp_version: usize = 1, pause_undo: bool = false, @@ -358,7 +359,7 @@ pub const Editor = struct { self.clamp(); } - fn init(self: *Self, allocator: Allocator, n: Plane) void { + fn init(self: *Self, allocator: Allocator, n: Plane, buffer_manager: *Buffer.Manager) void { const logger = log.logger("editor"); const frame_rate = tp.env.get().num("frame-rate"); const indent_size = tui.current().config.indent_size; @@ -372,6 +373,7 @@ pub const Editor = struct { .logger = logger, .file_path = null, .buffer = null, + .buffer_manager = buffer_manager, .handlers = EventHandler.List.init(allocator), .animation_lag = get_animation_max_lag(), .animation_frame_rate = frame_rate, @@ -393,7 +395,7 @@ pub const Editor = struct { self.matches.deinit(); self.handlers.deinit(); self.logger.deinit(); - if (self.buffer) |p| p.deinit(); + if (self.buffer) |p| self.buffer_manager.retire(p); if (self.case_data) |cd| cd.deinit(); } @@ -459,28 +461,16 @@ pub const Editor = struct { self.view.cols = pos.w; } - pub fn is_dirty(self: *Self) bool { - const b = self.buffer orelse return false; - return b.is_dirty(); - } - fn open(self: *Self, file_path: []const u8) !void { - var new_buf = try Buffer.create(self.allocator); - errdefer new_buf.deinit(); - try new_buf.load_from_file_and_update(file_path); - return self.open_buffer(file_path, new_buf); + return self.open_buffer(file_path, try self.buffer_manager.open_file(file_path)); } fn open_scratch(self: *Self, file_path: []const u8, content: []const u8) !void { - var new_buf = try Buffer.create(self.allocator); - errdefer new_buf.deinit(); - try new_buf.load_from_string_and_update(file_path, content); - new_buf.file_exists = true; - return self.open_buffer(file_path, new_buf); + return self.open_buffer(file_path, try self.buffer_manager.open_scratch(file_path, content)); } fn open_buffer(self: *Self, file_path: []const u8, new_buf: *Buffer) !void { - errdefer new_buf.deinit(); + errdefer self.buffer_manager.retire(new_buf); self.cancel_all_selections(); self.get_primary().reset(); self.file_path = try self.allocator.dupe(u8, file_path); @@ -519,17 +509,7 @@ pub const Editor = struct { } fn close(self: *Self) !void { - return self.close_internal(false); - } - - fn close_dirty(self: *Self) !void { - return self.close_internal(true); - } - - fn close_internal(self: *Self, allow_dirty_close: bool) !void { - const b = self.buffer orelse return error.Stop; - if (!allow_dirty_close and b.is_dirty()) return tp.exit("unsaved changes"); - if (self.buffer) |b_mut| b_mut.deinit(); + if (self.buffer) |b_mut| self.buffer_manager.retire(b_mut); self.buffer = null; self.plane.erase(); self.plane.home(); @@ -3770,7 +3750,8 @@ pub const Editor = struct { pub fn close_file_without_saving(self: *Self, _: Context) Result { self.cancel_all_selections(); - try self.close_dirty(); + if (self.buffer) |buffer| buffer.reset_to_last_saved(); + try self.close(); } pub const close_file_without_saving_meta = .{ .description = "Close file without saving" }; @@ -4759,8 +4740,8 @@ pub const Editor = struct { pub const set_file_type_meta = .{ .arguments = &.{.string} }; }; -pub fn create(allocator: Allocator, parent: Widget) !Widget { - return EditorWidget.create(allocator, parent); +pub fn create(allocator: Allocator, parent: Widget, buffer_manager: *Buffer.Manager) !Widget { + return EditorWidget.create(allocator, parent, buffer_manager); } pub const EditorWidget = struct { @@ -4782,10 +4763,10 @@ pub const EditorWidget = struct { const Self = @This(); const Commands = command.Collection(Editor); - fn create(allocator: Allocator, parent: Widget) !Widget { + fn create(allocator: Allocator, parent: Widget, buffer_manager: *Buffer.Manager) !Widget { const container = try WidgetList.createH(allocator, parent, "editor.container", .dynamic); const self: *Self = try allocator.create(Self); - try self.init(allocator, container.widget()); + try self.init(allocator, container.widget(), buffer_manager); try self.commands.init(&self.editor); const editorWidget = Widget.to(self); try container.add(try editor_gutter.create(allocator, container.widget(), editorWidget, &self.editor)); @@ -4794,7 +4775,7 @@ pub const EditorWidget = struct { return container.widget(); } - fn init(self: *Self, allocator: Allocator, parent: Widget) !void { + fn init(self: *Self, allocator: Allocator, parent: Widget, buffer_manager: *Buffer.Manager) !void { var n = try Plane.init(&(Widget.Box{}).opts("editor"), parent.plane.*); errdefer n.deinit(); @@ -4803,7 +4784,7 @@ pub const EditorWidget = struct { .plane = n, .editor = undefined, }; - self.editor.init(allocator, n); + self.editor.init(allocator, n, buffer_manager); errdefer self.editor.deinit(); try self.editor.push_cursor(); } diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index 9727898..d1cdcdf 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -14,6 +14,7 @@ const build_options = @import("build_options"); const Plane = @import("renderer").Plane; const input = @import("input"); const command = @import("command"); +const BufferManager = @import("Buffer").Manager; const tui = @import("tui.zig"); const Box = @import("Box.zig"); @@ -47,6 +48,7 @@ active_view: ?usize = 0, panels: ?*WidgetList = null, last_match_text: ?[]const u8 = null, location_history: location_history, +buffer_manager: BufferManager, file_stack: std.ArrayList([]const u8), find_in_files_state: enum { init, adding, done } = .done, file_list_type: FileListType = .find_in_files, @@ -70,6 +72,7 @@ pub fn create(allocator: std.mem.Allocator) !Widget { .file_stack = std.ArrayList([]const u8).init(allocator), .views = undefined, .views_widget = undefined, + .buffer_manager = BufferManager.init(allocator), }; try self.commands.init(self); const w = Widget.to(self); @@ -104,6 +107,7 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { self.commands.deinit(); self.widgets.deinit(allocator); self.floating_views.deinit(); + self.buffer_manager.deinit(); allocator.destroy(self); } @@ -237,15 +241,8 @@ fn toggle_view(self: *Self, view: anytype) !void { } fn check_all_not_dirty(self: *const Self) command.Result { - for (self.editors.items) |editor| - if (editor.is_dirty()) - return tp.exit("unsaved changes"); -} - -fn check_active_not_dirty(self: *const Self) command.Result { - if (self.active_editor) |idx| - if (self.editors.items[idx].is_dirty()) - return tp.exit("unsaved changes"); + if (self.buffer_manager.is_dirty()) + return tp.exit("unsaved changes"); } const cmds = struct { @@ -296,6 +293,8 @@ const cmds = struct { self.clear_find_in_files_results(.diagnostics); if (self.file_list_type == .diagnostics and self.is_panel_view_showing(filelist_view)) try self.toggle_panel_view(filelist_view, false); + self.buffer_manager.deinit(); + self.buffer_manager = BufferManager.init(self.allocator); try project_manager.open(project_dir); const project = tp.env.get().str("project"); tui.current().rdr.set_terminal_working_directory(project); @@ -352,7 +351,6 @@ const cmds = struct { if (!same_file) { if (self.get_active_editor()) |editor| { - try self.check_active_not_dirty(); editor.send_editor_jump_source() catch {}; } try self.create_editor(); @@ -395,7 +393,6 @@ const cmds = struct { pub const open_gui_config_meta = .{ .description = "Edit gui configuration file" }; pub fn create_scratch_buffer(self: *Self, ctx: Ctx) Result { - try self.check_all_not_dirty(); tui.reset_drag_context(); try self.create_editor(); try command.executeName("open_scratch_buffer", ctx); @@ -863,7 +860,7 @@ fn create_editor(self: *Self) !void { if (self.get_active_file_path()) |file_path| self.push_file_stack(file_path) catch {}; try self.delete_active_view(); command.executeName("enter_mode_default", .{}) catch {}; - var editor_widget = try ed.create(self.allocator, Widget.to(self)); + var editor_widget = try ed.create(self.allocator, Widget.to(self), &self.buffer_manager); errdefer editor_widget.deinit(self.allocator); const editor = editor_widget.get("editor") orelse @panic("mainview editor not found"); if (self.top_bar) |bar| editor.subscribe(EventHandler.to_unowned(bar)) catch @panic("subscribe unsupported"); diff --git a/src/tui/mode/mini/open_file.zig b/src/tui/mode/mini/open_file.zig index 407ffeb..de60c07 100644 --- a/src/tui/mode/mini/open_file.zig +++ b/src/tui/mode/mini/open_file.zig @@ -11,7 +11,6 @@ pub const create = Type.create; pub fn load_entries(self: *Type) error{ Exit, OutOfMemory }!void { const editor = tui.get_active_editor() orelse return; - if (editor.is_dirty()) return tp.exit("unsaved changes"); if (editor.file_path) |old_path| if (std.mem.lastIndexOf(u8, old_path, "/")) |pos| try self.file_path.appendSlice(old_path[0 .. pos + 1]); diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 7677103..2b1d28c 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -991,6 +991,10 @@ pub fn get_active_selection(allocator: std.mem.Allocator) ?[]u8 { return editor.get_selection(sel, allocator) catch null; } +pub fn get_buffer_manager() ?*@import("Buffer").Manager { + return if (current().mainview.dynamic_cast(mainview)) |mv_| &mv_.buffer_manager else null; +} + fn context_check() void { if (instance_ == null) @panic("tui call out of context"); }