feat(buffers): add support for ephemeral buffers
Ephemeral buffers are not hidden and kept when closed. Ephemeral buffers can be turned into regular buffers by saving them with save_as.
This commit is contained in:
parent
8062923068
commit
939537ed84
7 changed files with 92 additions and 18 deletions
|
@ -40,6 +40,7 @@ file_eol_mode: EolMode = .lf,
|
||||||
last_save_eol_mode: EolMode = .lf,
|
last_save_eol_mode: EolMode = .lf,
|
||||||
file_utf8_sanitized: bool = false,
|
file_utf8_sanitized: bool = false,
|
||||||
hidden: bool = false,
|
hidden: bool = false,
|
||||||
|
ephemeral: bool = false,
|
||||||
|
|
||||||
undo_history: ?*UndoNode = null,
|
undo_history: ?*UndoNode = null,
|
||||||
redo_history: ?*UndoNode = null,
|
redo_history: ?*UndoNode = null,
|
||||||
|
@ -1279,6 +1280,7 @@ pub const StoreToFileError = error{
|
||||||
NotDir,
|
NotDir,
|
||||||
NotOpenForWriting,
|
NotOpenForWriting,
|
||||||
OperationAborted,
|
OperationAborted,
|
||||||
|
OutOfMemory,
|
||||||
PathAlreadyExists,
|
PathAlreadyExists,
|
||||||
PipeBusy,
|
PipeBusy,
|
||||||
ProcessFdQuotaExceeded,
|
ProcessFdQuotaExceeded,
|
||||||
|
@ -1315,12 +1317,24 @@ pub fn store_to_file_and_clean(self: *Self, file_path: []const u8) StoreToFileEr
|
||||||
self.last_save_eol_mode = self.file_eol_mode;
|
self.last_save_eol_mode = self.file_eol_mode;
|
||||||
self.file_exists = true;
|
self.file_exists = true;
|
||||||
self.file_utf8_sanitized = false;
|
self.file_utf8_sanitized = false;
|
||||||
|
if (self.ephemeral) {
|
||||||
|
self.ephemeral = false;
|
||||||
|
self.file_path = try self.allocator.dupe(u8, file_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mark_clean(self: *Self) void {
|
pub fn mark_clean(self: *Self) void {
|
||||||
self.last_save = self.root;
|
self.last_save = self.root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_hidden(self: *const Self) bool {
|
||||||
|
return self.hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_ephemeral(self: *const Self) bool {
|
||||||
|
return self.ephemeral;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_dirty(self: *const Self) bool {
|
pub fn is_dirty(self: *const Self) bool {
|
||||||
return if (!self.file_exists)
|
return if (!self.file_exists)
|
||||||
self.root.length() > 0
|
self.root.length() > 0
|
||||||
|
|
|
@ -47,6 +47,7 @@ pub fn open_scratch(self: *Self, file_path: []const u8, content: []const u8) Buf
|
||||||
};
|
};
|
||||||
buffer.update_last_used_time();
|
buffer.update_last_used_time();
|
||||||
buffer.hidden = false;
|
buffer.hidden = false;
|
||||||
|
buffer.ephemeral = true;
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,11 +57,23 @@ pub fn get_buffer_for_file(self: *Self, file_path: []const u8) ?*Buffer {
|
||||||
|
|
||||||
pub fn delete_buffer(self: *Self, file_path: []const u8) bool {
|
pub fn delete_buffer(self: *Self, file_path: []const u8) bool {
|
||||||
const buffer = self.buffers.get(file_path) orelse return false;
|
const buffer = self.buffers.get(file_path) orelse return false;
|
||||||
|
const did_remove = self.buffers.remove(file_path);
|
||||||
buffer.deinit();
|
buffer.deinit();
|
||||||
return self.buffers.remove(file_path);
|
return did_remove;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn retire(_: *Self, _: *Buffer) void {}
|
pub fn retire(_: *Self, buffer: *Buffer) void {
|
||||||
|
tp.trace(tp.channel.debug, .{ "buffer", "retire", buffer.file_path, "hidden", buffer.hidden, "ephemeral", buffer.ephemeral });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close_buffer(self: *Self, buffer: *Buffer) void {
|
||||||
|
buffer.hidden = true;
|
||||||
|
tp.trace(tp.channel.debug, .{ "buffer", "close", buffer.file_path, "hidden", buffer.hidden, "ephemeral", buffer.ephemeral });
|
||||||
|
if (buffer.is_ephemeral()) {
|
||||||
|
_ = self.buffers.remove(buffer.file_path);
|
||||||
|
buffer.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn list_most_recently_used(self: *Self, allocator: std.mem.Allocator) error{OutOfMemory}![]*Buffer {
|
pub fn list_most_recently_used(self: *Self, allocator: std.mem.Allocator) error{OutOfMemory}![]*Buffer {
|
||||||
const result = try self.list_unordered(allocator);
|
const result = try self.list_unordered(allocator);
|
||||||
|
@ -98,6 +111,9 @@ pub fn save_all(self: *const Self) Buffer.StoreToFileError!void {
|
||||||
var i = self.buffers.iterator();
|
var i = self.buffers.iterator();
|
||||||
while (i.next()) |kv| {
|
while (i.next()) |kv| {
|
||||||
const buffer = kv.value_ptr.*;
|
const buffer = kv.value_ptr.*;
|
||||||
|
if (buffer.is_ephemeral())
|
||||||
|
buffer.mark_clean()
|
||||||
|
else
|
||||||
try buffer.store_to_file_and_clean(buffer.file_path);
|
try buffer.store_to_file_and_clean(buffer.file_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,8 @@ pub fn delete_task(task: []const u8) (ProjectManagerError || ProjectError)!void
|
||||||
return send(.{ "delete_task", project, task });
|
return send(.{ "delete_task", project, task });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn did_open(file_path: []const u8, file_type: *const FileType, version: usize, text: []const u8) (ProjectManagerError || ProjectError)!void {
|
pub fn did_open(file_path: []const u8, file_type: *const FileType, version: usize, text: []const u8, ephemeral: bool) (ProjectManagerError || ProjectError)!void {
|
||||||
|
if (ephemeral) return;
|
||||||
const project = tp.env.get().str("project");
|
const project = tp.env.get().str("project");
|
||||||
if (project.len == 0)
|
if (project.len == 0)
|
||||||
return error.NoProject;
|
return error.NoProject;
|
||||||
|
@ -211,7 +212,8 @@ pub fn hover(file_path: []const u8, row: usize, col: usize) (ProjectManagerError
|
||||||
return send(.{ "hover", project, file_path, row, col });
|
return send(.{ "hover", project, file_path, row, col });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_mru(file_path: []const u8, row: usize, col: usize) (ProjectManagerError || ProjectError)!void {
|
pub fn update_mru(file_path: []const u8, row: usize, col: usize, ephemeral: bool) (ProjectManagerError || ProjectError)!void {
|
||||||
|
if (ephemeral) return;
|
||||||
const project = tp.env.get().str("project");
|
const project = tp.env.get().str("project");
|
||||||
if (project.len == 0)
|
if (project.len == 0)
|
||||||
return error.NoProject;
|
return error.NoProject;
|
||||||
|
|
|
@ -82,6 +82,11 @@ pub fn State(ctx_type: type) type {
|
||||||
allocator.destroy(self);
|
allocator.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_label(self: *Self, label: []const u8) error{OutOfMemory}!void {
|
||||||
|
self.allocator.free(self.opts.label);
|
||||||
|
self.opts.label = try self.allocator.dupe(u8, label);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn layout(self: *Self) Widget.Layout {
|
pub fn layout(self: *Self) Widget.Layout {
|
||||||
return self.opts.on_layout(&self.opts.ctx, self);
|
return self.opts.on_layout(&self.opts.ctx, self);
|
||||||
}
|
}
|
||||||
|
|
|
@ -537,7 +537,13 @@ pub const Editor = struct {
|
||||||
else
|
else
|
||||||
syntax.create_guess_file_type(self.allocator, content.items, self.file_path) catch null;
|
syntax.create_guess_file_type(self.allocator, content.items, self.file_path) catch null;
|
||||||
if (syn) |syn_|
|
if (syn) |syn_|
|
||||||
project_manager.did_open(file_path, syn_.file_type, self.lsp_version, try content.toOwnedSlice()) catch |e|
|
project_manager.did_open(
|
||||||
|
file_path,
|
||||||
|
syn_.file_type,
|
||||||
|
self.lsp_version,
|
||||||
|
try content.toOwnedSlice(),
|
||||||
|
new_buf.is_ephemeral(),
|
||||||
|
) catch |e|
|
||||||
self.logger.print("project_manager.did_open failed: {any}", .{e});
|
self.logger.print("project_manager.did_open failed: {any}", .{e});
|
||||||
break :syntax syn;
|
break :syntax syn;
|
||||||
};
|
};
|
||||||
|
@ -563,6 +569,7 @@ pub const Editor = struct {
|
||||||
|
|
||||||
fn save(self: *Self) !void {
|
fn save(self: *Self) !void {
|
||||||
const b = self.buffer orelse return error.Stop;
|
const b = self.buffer orelse return error.Stop;
|
||||||
|
if (b.is_ephemeral()) return self.logger.print_err("save", "ephemeral buffer, use save as", .{});
|
||||||
if (!b.is_dirty()) return self.logger.print("no changes to save", .{});
|
if (!b.is_dirty()) return self.logger.print("no changes to save", .{});
|
||||||
if (self.file_path) |file_path| {
|
if (self.file_path) |file_path| {
|
||||||
if (self.buffer) |b_mut| try b_mut.store_to_file_and_clean(file_path);
|
if (self.buffer) |b_mut| try b_mut.store_to_file_and_clean(file_path);
|
||||||
|
@ -3785,23 +3792,24 @@ pub const Editor = struct {
|
||||||
pub const save_file_as_meta = .{ .arguments = &.{.string} };
|
pub const save_file_as_meta = .{ .arguments = &.{.string} };
|
||||||
|
|
||||||
pub fn close_file(self: *Self, _: Context) Result {
|
pub fn close_file(self: *Self, _: Context) Result {
|
||||||
self.cancel_all_selections();
|
const buffer_ = self.buffer;
|
||||||
if (self.buffer) |buffer| {
|
if (buffer_) |buffer| if (buffer.is_dirty())
|
||||||
if (buffer.is_dirty())
|
|
||||||
return tp.exit("unsaved changes");
|
return tp.exit("unsaved changes");
|
||||||
buffer.hidden = true;
|
self.cancel_all_selections();
|
||||||
}
|
|
||||||
try self.close();
|
try self.close();
|
||||||
|
if (buffer_) |buffer|
|
||||||
|
self.buffer_manager.close_buffer(buffer);
|
||||||
}
|
}
|
||||||
pub const close_file_meta = .{ .description = "Close file" };
|
pub const close_file_meta = .{ .description = "Close file" };
|
||||||
|
|
||||||
pub fn close_file_without_saving(self: *Self, _: Context) Result {
|
pub fn close_file_without_saving(self: *Self, _: Context) Result {
|
||||||
self.cancel_all_selections();
|
self.cancel_all_selections();
|
||||||
if (self.buffer) |buffer| {
|
const buffer_ = self.buffer;
|
||||||
|
if (buffer_) |buffer|
|
||||||
buffer.reset_to_last_saved();
|
buffer.reset_to_last_saved();
|
||||||
buffer.hidden = true;
|
|
||||||
}
|
|
||||||
try self.close();
|
try self.close();
|
||||||
|
if (buffer_) |buffer|
|
||||||
|
self.buffer_manager.close_buffer(buffer);
|
||||||
}
|
}
|
||||||
pub const close_file_without_saving_meta = .{ .description = "Close file without saving" };
|
pub const close_file_without_saving_meta = .{ .description = "Close file without saving" };
|
||||||
|
|
||||||
|
@ -4781,7 +4789,13 @@ pub const Editor = struct {
|
||||||
try root.store(content.writer(), try self.buf_eol_mode());
|
try root.store(content.writer(), try self.buf_eol_mode());
|
||||||
const syn = syntax.create_file_type(self.allocator, file_type) catch null;
|
const syn = syntax.create_file_type(self.allocator, file_type) catch null;
|
||||||
if (syn) |syn_| if (self.file_path) |file_path|
|
if (syn) |syn_| if (self.file_path) |file_path|
|
||||||
project_manager.did_open(file_path, syn_.file_type, self.lsp_version, try content.toOwnedSlice()) catch |e|
|
project_manager.did_open(
|
||||||
|
file_path,
|
||||||
|
syn_.file_type,
|
||||||
|
self.lsp_version,
|
||||||
|
try content.toOwnedSlice(),
|
||||||
|
if (self.buffer) |p| p.is_ephemeral() else true,
|
||||||
|
) catch |e|
|
||||||
self.logger.print("project_manager.did_open failed: {any}", .{e});
|
self.logger.print("project_manager.did_open failed: {any}", .{e});
|
||||||
break :syntax syn;
|
break :syntax syn;
|
||||||
};
|
};
|
||||||
|
|
|
@ -412,6 +412,22 @@ const cmds = struct {
|
||||||
}
|
}
|
||||||
pub const delete_buffer_meta = .{ .arguments = &.{.string} };
|
pub const delete_buffer_meta = .{ .arguments = &.{.string} };
|
||||||
|
|
||||||
|
pub fn close_buffer(self: *Self, ctx: Ctx) Result {
|
||||||
|
var file_path: []const u8 = undefined;
|
||||||
|
if (!(ctx.args.match(.{tp.extract(&file_path)}) catch false))
|
||||||
|
return error.InvalidDeleteBufferArgument;
|
||||||
|
const buffer = self.buffer_manager.get_buffer_for_file(file_path) orelse return;
|
||||||
|
if (buffer.is_dirty())
|
||||||
|
return tp.exit("unsaved changes");
|
||||||
|
if (self.get_active_editor()) |editor| if (editor.buffer == buffer) {
|
||||||
|
editor.close_file(.{}) catch |e| return e;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
_ = self.buffer_manager.close_buffer(buffer);
|
||||||
|
tui.need_render();
|
||||||
|
}
|
||||||
|
pub const close_buffer_meta = .{ .arguments = &.{.string} };
|
||||||
|
|
||||||
pub fn restore_session(self: *Self, _: Ctx) Result {
|
pub fn restore_session(self: *Self, _: Ctx) Result {
|
||||||
if (tp.env.get().str("project").len == 0) {
|
if (tp.env.get().str("project").len == 0) {
|
||||||
try open_project_cwd(self, .{});
|
try open_project_cwd(self, .{});
|
||||||
|
@ -859,16 +875,17 @@ pub fn location_update(self: *Self, m: tp.message) tp.result {
|
||||||
var row: usize = 0;
|
var row: usize = 0;
|
||||||
var col: usize = 0;
|
var col: usize = 0;
|
||||||
const file_path = self.get_active_file_path() orelse return;
|
const file_path = self.get_active_file_path() orelse return;
|
||||||
|
const ephemeral = if (self.get_active_buffer()) |buffer| buffer.is_ephemeral() else false;
|
||||||
|
|
||||||
if (try m.match(.{ tp.any, tp.any, tp.any, tp.extract(&row), tp.extract(&col) })) {
|
if (try m.match(.{ tp.any, tp.any, tp.any, tp.extract(&row), tp.extract(&col) })) {
|
||||||
if (row == 0 and col == 0) return;
|
if (row == 0 and col == 0) return;
|
||||||
project_manager.update_mru(file_path, row, col) catch {};
|
project_manager.update_mru(file_path, row, col, ephemeral) catch {};
|
||||||
return self.location_history.update(file_path, .{ .row = row + 1, .col = col + 1 }, null);
|
return self.location_history.update(file_path, .{ .row = row + 1, .col = col + 1 }, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sel: location_history.Selection = .{};
|
var sel: location_history.Selection = .{};
|
||||||
if (try m.match(.{ tp.any, tp.any, tp.any, tp.extract(&row), tp.extract(&col), tp.extract(&sel.begin.row), tp.extract(&sel.begin.col), tp.extract(&sel.end.row), tp.extract(&sel.end.col) })) {
|
if (try m.match(.{ tp.any, tp.any, tp.any, tp.extract(&row), tp.extract(&col), tp.extract(&sel.begin.row), tp.extract(&sel.begin.col), tp.extract(&sel.end.row), tp.extract(&sel.end.col) })) {
|
||||||
project_manager.update_mru(file_path, row, col) catch {};
|
project_manager.update_mru(file_path, row, col, ephemeral) catch {};
|
||||||
return self.location_history.update(file_path, .{ .row = row + 1, .col = col + 1 }, sel);
|
return self.location_history.update(file_path, .{ .row = row + 1, .col = col + 1 }, sel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -911,6 +928,10 @@ pub fn get_active_file_path(self: *Self) ?[]const u8 {
|
||||||
return if (self.get_active_editor()) |editor| editor.file_path orelse null else null;
|
return if (self.get_active_editor()) |editor| editor.file_path orelse null else null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_active_buffer(self: *Self) ?*Buffer {
|
||||||
|
return if (self.get_active_editor()) |editor| editor.buffer orelse null else null;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn, w: *Widget) bool {
|
pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn, w: *Widget) bool {
|
||||||
return self.floating_views.walk(ctx, f) or self.widgets.walk(ctx, f, &self.widgets_widget) or f(ctx, w);
|
return self.floating_views.walk(ctx, f) or self.widgets.walk(ctx, f, &self.widgets_widget) or f(ctx, w);
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,6 +117,8 @@ const TabBar = struct {
|
||||||
try self.widget_list.add(try self.make_spacer());
|
try self.widget_list.add(try self.make_spacer());
|
||||||
}
|
}
|
||||||
try self.widget_list.add(tab.widget);
|
try self.widget_list.add(tab.widget);
|
||||||
|
if (tab.widget.dynamic_cast(Button.State(Tab))) |btn|
|
||||||
|
try btn.update_label(Tab.name_from_buffer(tab.buffer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +214,7 @@ const Tab = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_click2(self: *@This(), _: *Button.State(@This())) void {
|
fn on_click2(self: *@This(), _: *Button.State(@This())) void {
|
||||||
tp.self_pid().send(.{ "cmd", "delete_buffer", .{self.buffer.file_path} }) catch {};
|
tp.self_pid().send(.{ "cmd", "close_buffer", .{self.buffer.file_path} }) catch {};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(self: *@This(), btn: *Button.State(@This()), theme: *const Widget.Theme) bool {
|
fn render(self: *@This(), btn: *Button.State(@This()), theme: *const Widget.Theme) bool {
|
||||||
|
|
Loading…
Add table
Reference in a new issue