diff --git a/build.zig.zon b/build.zig.zon index 7d9b927..0a34e23 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -15,8 +15,8 @@ .hash = "dizzy-1.0.0-AAAAAM1wAAAiDbx_6RwcVEOBk8p2XOu8t9WPNc3K7kBK", }, .thespian = .{ - .url = "https://github.com/neurocyte/thespian/archive/ccdcbbff09f945eec063ebf889581db3e1312107.tar.gz", - .hash = "thespian-0.0.1-owFOjlgaBgCqc3FCnB4Xyg8-9jyIDWgHSJMGx_nt5Kcc", + .url = "git+https://github.com/neurocyte/thespian#f2980d3a747abdf0d18a01596dd8b953dd3e6243", + .hash = "thespian-0.0.1-owFOjk0aBgC8w9ibeiVdhftyEIaVIHCnubsJWfkktE8v", }, .themes = .{ .url = "https://github.com/neurocyte/flow-themes/releases/download/master-952f9f630ea9544088fd30293666ee0650b7a690/flow-themes.tar.gz", @@ -27,8 +27,8 @@ .hash = "fuzzig-0.1.1-AAAAALNIAQBmbHr-MPalGuR393Vem2pTQXI7_LXeNJgX", }, .vaxis = .{ - .url = "https://github.com/neurocyte/libvaxis/archive/6137cb4c44a7350996f0946a069739e5075d1f23.tar.gz", - .hash = "vaxis-0.1.0-BWNV_HwOCQCw5wTV63hQGSc1QJzsNcytH6sGf1GBc0hP", + .url = "git+https://github.com/neurocyte/libvaxis?ref=main#846ddb8bf483e8a7eb25628d6c34ba7e781155b6", + .hash = "vaxis-0.5.1-BWNV_AsQCQDvfb-li1CZEOBG_YsteinP9qI-PpV47-jf", }, .zeit = .{ .url = "https://github.com/rockorager/zeit/archive/8fd203f85f597f16e0a525c1f1ca1e0bffded809.tar.gz", diff --git a/src/EventHandler.zig b/src/EventHandler.zig index 879ba12..0d5f1d7 100644 --- a/src/EventHandler.zig +++ b/src/EventHandler.zig @@ -118,6 +118,7 @@ pub fn send(self: Self, from_: tp.pid_ref, m: tp.message) tp.result { pub fn empty(allocator: Allocator) !Self { const child: type = struct {}; const widget = try allocator.create(child); + errdefer allocator.destroy(widget); widget.* = .{}; return .{ .ptr = widget, diff --git a/src/LSP.zig b/src/LSP.zig index b224f4f..58012a3 100644 --- a/src/LSP.zig +++ b/src/LSP.zig @@ -17,22 +17,34 @@ const OutOfMemoryError = error{OutOfMemory}; const SendError = error{SendFailed}; const SpawnError = error{ThespianSpawnFailed}; -pub fn open(allocator: std.mem.Allocator, project: []const u8, cmd: tp.message) (error{ ThespianSpawnFailed, InvalidLspCommand } || cbor.Error)!Self { - return .{ .allocator = allocator, .pid = try Process.create(allocator, project, cmd) }; +pub fn open( + allocator: std.mem.Allocator, + project: []const u8, + cmd: tp.message, +) (error{ ThespianSpawnFailed, InvalidLspCommand } || cbor.Error)!*const Self { + const self = try allocator.create(Self); + errdefer allocator.destroy(self); + self.* = .{ + .allocator = allocator, + .pid = try Process.create(allocator, project, cmd), + }; + return self; } -pub fn deinit(self: Self) void { +pub fn deinit(self: *const Self) void { self.pid.send(.{"close"}) catch {}; self.pid.deinit(); + self.allocator.destroy(self); } -pub fn term(self: Self) void { +pub fn term(self: *const Self) void { self.pid.send(.{"term"}) catch {}; self.pid.deinit(); + self.allocator.destroy(self); } pub fn send_request( - self: Self, + self: *const Self, allocator: std.mem.Allocator, method: []const u8, m: anytype, @@ -44,17 +56,56 @@ pub fn send_request( return RequestContext(@TypeOf(ctx)).send(allocator, self.pid.ref(), ctx, tp.message.fmt(.{ "REQ", method, cb.items })); } -pub fn send_notification(self: Self, method: []const u8, m: anytype) (OutOfMemoryError || SendError)!void { +pub fn send_notification(self: *const Self, method: []const u8, m: anytype) (OutOfMemoryError || SendError)!void { var cb = std.ArrayList(u8).init(self.allocator); defer cb.deinit(); try cbor.writeValue(cb.writer(), m); return self.send_notification_raw(method, cb.items); } -pub fn send_notification_raw(self: Self, method: []const u8, cb: []const u8) SendError!void { +pub fn send_notification_raw(self: *const Self, method: []const u8, cb: []const u8) SendError!void { self.pid.send(.{ "NTFY", method, cb }) catch return error.SendFailed; } +pub const ErrorCode = enum(i32) { + + // Defined by JSON-RPC + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + + // Defined by LSP + RequestFailed = -32803, + ServerCancelled = -32802, + ContentModified = -32801, + RequestCancelled = -32800, +}; + +pub fn send_response(allocator: std.mem.Allocator, to: tp.pid_ref, cbor_id: []const u8, result: anytype) (SendError || OutOfMemoryError)!void { + var cb = std.ArrayList(u8).init(allocator); + defer cb.deinit(); + const writer = cb.writer(); + try cbor.writeArrayHeader(writer, 3); + try cbor.writeValue(writer, "RSP"); + try writer.writeAll(cbor_id); + try cbor.writeValue(cb.writer(), result); + to.send_raw(.{ .buf = cb.items }) catch return error.SendFailed; +} + +pub fn send_error_response(allocator: std.mem.Allocator, to: tp.pid_ref, cbor_id: []const u8, code: ErrorCode, message: []const u8) (SendError || OutOfMemoryError)!void { + var cb = std.ArrayList(u8).init(allocator); + defer cb.deinit(); + const writer = cb.writer(); + try cbor.writeArrayHeader(writer, 4); + try cbor.writeValue(writer, "ERR"); + try writer.writeAll(cbor_id); + try cbor.writeValue(cb.writer(), code); + try cbor.writeValue(cb.writer(), message); + to.send_raw(.{ .buf = cb.items }) catch return error.SendFailed; +} + pub fn close(self: *Self) void { self.deinit(); } @@ -144,6 +195,7 @@ const Process = struct { return error.InvalidLspCommand; } const self = try allocator.create(Process); + errdefer allocator.destroy(self); var sp_tag_ = std.ArrayList(u8).init(allocator); defer sp_tag_.deinit(); try sp_tag_.appendSlice(tag); @@ -239,6 +291,8 @@ const Process = struct { var err: []const u8 = ""; var code: u32 = 0; var cbor_id: []const u8 = ""; + var error_code: ErrorCode = undefined; + var message: []const u8 = ""; if (try cbor.match(m.buf, .{ "REQ", "initialize", tp.extract(&bytes) })) { try self.send_request(from, "initialize", bytes); @@ -249,6 +303,8 @@ const Process = struct { } } else if (try cbor.match(m.buf, .{ "RSP", tp.extract_cbor(&cbor_id), tp.extract_cbor(&bytes) })) { try self.send_response(cbor_id, bytes); + } else if (try cbor.match(m.buf, .{ "ERR", tp.extract_cbor(&cbor_id), tp.extract(&error_code), tp.extract(&message) })) { + try self.send_error_response(cbor_id, error_code, message); } else if (try cbor.match(m.buf, .{ "NTFY", "initialized", tp.extract(&bytes) })) { self.state = .running; try self.send_notification("initialized", bytes); @@ -469,6 +525,39 @@ const Process = struct { self.write_log("### SEND response:\n{s}\n###\n", .{output.items}); } + fn send_error_response(self: *Process, cbor_id: []const u8, error_code: ErrorCode, message: []const u8) (error{Closed} || SendError || cbor.Error || cbor.JsonEncodeError)!void { + const sp = if (self.sp) |*sp| sp else return error.Closed; + + var msg = std.ArrayList(u8).init(self.allocator); + defer msg.deinit(); + const msg_writer = msg.writer(); + try cbor.writeMapHeader(msg_writer, 3); + try cbor.writeValue(msg_writer, "jsonrpc"); + try cbor.writeValue(msg_writer, "2.0"); + try cbor.writeValue(msg_writer, "id"); + try msg_writer.writeAll(cbor_id); + try cbor.writeValue(msg_writer, "error"); + try cbor.writeMapHeader(msg_writer, 2); + try cbor.writeValue(msg_writer, "code"); + try cbor.writeValue(msg_writer, @intFromEnum(error_code)); + try cbor.writeValue(msg_writer, "message"); + try cbor.writeValue(msg_writer, message); + + const json = try cbor.toJsonAlloc(self.allocator, msg.items); + defer self.allocator.free(json); + var output = std.ArrayList(u8).init(self.allocator); + defer output.deinit(); + const writer = output.writer(); + const terminator = "\r\n"; + const content_length = json.len + terminator.len; + try writer.print("Content-Length: {d}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n", .{content_length}); + _ = try writer.write(json); + _ = try writer.write(terminator); + + sp.send(output.items) catch return error.SendFailed; + self.write_log("### SEND error response:\n{s}\n###\n", .{output.items}); + } + fn send_notification(self: *Process, method: []const u8, params_cb: []const u8) Error!void { const sp = if (self.sp) |*sp| sp else return error.Closed; diff --git a/src/Project.zig b/src/Project.zig index 19e4399..8e97119 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -19,8 +19,8 @@ files: std.ArrayListUnmanaged(File) = .empty, pending: std.ArrayListUnmanaged(File) = .empty, longest_file_path: usize = 0, open_time: i64, -language_servers: std.StringHashMap(LSP), -file_language_server: std.StringHashMap(LSP), +language_servers: std.StringHashMap(*const LSP), +file_language_server: std.StringHashMap(*const LSP), tasks: std.ArrayList(Task), persistent: bool = false, logger: log.Logger, @@ -74,8 +74,8 @@ pub fn init(allocator: std.mem.Allocator, name: []const u8) OutOfMemoryError!Sel .allocator = allocator, .name = try allocator.dupe(u8, name), .open_time = std.time.milliTimestamp(), - .language_servers = std.StringHashMap(LSP).init(allocator), - .file_language_server = std.StringHashMap(LSP).init(allocator), + .language_servers = std.StringHashMap(*const LSP).init(allocator), + .file_language_server = std.StringHashMap(*const LSP).init(allocator), .tasks = std.ArrayList(Task).init(allocator), .logger = log.logger("project"), .logger_lsp = log.logger("lsp"), @@ -231,6 +231,8 @@ pub fn restore_state_v0(self: *Self, data: []const u8) error{ JsonIncompatibleType, NotAnObject, BadArrayAllocExtract, + InvalidMapType, + InvalidUnion, }!void { tp.trace(tp.channel.debug, .{"restore_state_v0"}); defer self.sort_files_by_mtime(); @@ -261,11 +263,14 @@ pub fn restore_state_v0(self: *Self, data: []const u8) error{ } } -fn get_language_server_instance(self: *Self, language_server: []const u8) StartLspError!LSP { +fn get_language_server_instance(self: *Self, language_server: []const u8) StartLspError!*const LSP { if (self.language_servers.get(language_server)) |lsp| { - if (!lsp.pid.expired()) return lsp; - lsp.deinit(); - _ = self.language_servers.remove(language_server); + if (lsp.pid.expired()) { + _ = self.language_servers.remove(language_server); + lsp.deinit(); + } else { + return lsp; + } } const lsp = try LSP.open(self.allocator, self.name, .{ .buf = language_server }); errdefer lsp.deinit(); @@ -279,7 +284,7 @@ fn get_language_server_instance(self: *Self, language_server: []const u8) StartL return lsp; } -fn get_or_start_language_server(self: *Self, file_path: []const u8, language_server: []const u8) StartLspError!LSP { +fn get_or_start_language_server(self: *Self, file_path: []const u8, language_server: []const u8) StartLspError!*const LSP { const lsp = self.file_language_server.get(file_path) orelse blk: { const new_lsp = try self.get_language_server_instance(language_server); const key = try self.allocator.dupe(u8, file_path); @@ -289,7 +294,7 @@ fn get_or_start_language_server(self: *Self, file_path: []const u8, language_ser return lsp; } -fn get_language_server(self: *Self, file_path: []const u8) LspError!LSP { +fn get_language_server(self: *Self, file_path: []const u8) LspError!*const LSP { const lsp = self.file_language_server.get(file_path) orelse return error.NoLsp; if (lsp.pid.expired()) { if (self.file_language_server.fetchRemove(file_path)) |kv| @@ -1491,26 +1496,19 @@ pub fn show_message(self: *Self, _: tp.pid_ref, params_cb: []const u8) !void { pub fn register_capability(self: *Self, from: tp.pid_ref, cbor_id: []const u8, params_cb: []const u8) ClientError!void { _ = params_cb; - return self.send_lsp_response(from, cbor_id, null); + return LSP.send_response(self.allocator, from, cbor_id, null) catch error.ClientFailed; } pub fn workDoneProgress_create(self: *Self, from: tp.pid_ref, cbor_id: []const u8, params_cb: []const u8) ClientError!void { _ = params_cb; - return self.send_lsp_response(from, cbor_id, null); + return LSP.send_response(self.allocator, from, cbor_id, null) catch error.ClientFailed; } -pub fn send_lsp_response(self: *Self, from: tp.pid_ref, cbor_id: []const u8, result: anytype) ClientError!void { - var cb = std.ArrayList(u8).init(self.allocator); - defer cb.deinit(); - const writer = cb.writer(); - try cbor.writeArrayHeader(writer, 3); - try cbor.writeValue(writer, "RSP"); - try writer.writeAll(cbor_id); - try cbor.writeValue(cb.writer(), result); - from.send_raw(.{ .buf = cb.items }) catch return error.ClientFailed; +pub fn unsupported_lsp_request(self: *Self, from: tp.pid_ref, cbor_id: []const u8, method: []const u8) ClientError!void { + return LSP.send_error_response(self.allocator, from, cbor_id, LSP.ErrorCode.MethodNotFound, method) catch error.ClientFailed; } -fn send_lsp_init_request(self: *Self, lsp: LSP, project_path: []const u8, project_basename: []const u8, project_uri: []const u8, language_server: []const u8) !void { +fn send_lsp_init_request(self: *Self, lsp: *const LSP, project_path: []const u8, project_basename: []const u8, project_uri: []const u8, language_server: []const u8) !void { const handler: struct { language_server: []const u8, lsp: LSP, diff --git a/src/buffer/Buffer.zig b/src/buffer/Buffer.zig index 0c8e5c4..9e8f09b 100644 --- a/src/buffer/Buffer.zig +++ b/src/buffer/Buffer.zig @@ -159,6 +159,7 @@ pub const Leaf = struct { if (piece.len == 0) return if (!bol and !eol) &empty_leaf else if (bol and !eol) &empty_bol_leaf else if (!bol and eol) &empty_eol_leaf else &empty_line_leaf; const node = try allocator.create(Node); + errdefer allocator.destroy(node); node.* = .{ .leaf = .{ .buf = piece, .bol = bol, .eol = eol } }; return node; } @@ -267,6 +268,7 @@ const Node = union(enum) { fn new(allocator: Allocator, l: *const Node, r: *const Node) !*const Node { const node = try allocator.create(Node); + errdefer allocator.destroy(node); const l_weights_sum = l.weights_sum(); var weights_sum_ = Weights{}; weights_sum_.add(l_weights_sum); @@ -1065,6 +1067,7 @@ const Node = union(enum) { pub fn create(allocator: Allocator) error{OutOfMemory}!*Self { const self = try allocator.create(Self); + errdefer allocator.destroy(self); const arena_a = if (builtin.is_test) allocator else std.heap.page_allocator; self.* = .{ .arena = std.heap.ArenaAllocator.init(arena_a), diff --git a/src/config.zig b/src/config.zig index 9efce2c..7436e84 100644 --- a/src/config.zig +++ b/src/config.zig @@ -26,6 +26,7 @@ limit_auto_save_file_types: ?[]const []const u8 = null, // null means *all* indent_size: usize = 4, tab_width: usize = 8, +indent_mode: IndentMode = .auto, top_bar: []const u8 = "tabs", bottom_bar: []const u8 = "mode file log selection diagnostics keybind branch linenumber clock spacer", @@ -48,3 +49,9 @@ pub const LineNumberMode = enum { relative, absolute, }; + +pub const IndentMode = enum { + auto, + spaces, + tabs, +}; diff --git a/src/diff.zig b/src/diff.zig index abb2bd6..6c2a970 100644 --- a/src/diff.zig +++ b/src/diff.zig @@ -66,6 +66,7 @@ const Process = struct { pub fn create() !tp.pid { const self = try allocator.create(Process); + errdefer allocator.destroy(self); self.* = .{ .receiver = Receiver.init(Process.receive, self), }; diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 2a6641b..339972b 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -89,8 +89,8 @@ ["ctrl+shift+d", "dupe_down"], ["ctrl+shift+z", "redo"], ["ctrl+shift+w", "close_file_without_saving"], - ["ctrl+shift+l", "run_async", "add_cursor_all_matches"], - ["ctrl+shift+i", "run_async", "toggle_inspector_view"], + ["ctrl+shift+l", "add_cursor_all_matches"], + ["ctrl+shift+i", "toggle_inspector_view"], ["ctrl+shift+m", "show_diagnostics"], ["ctrl+shift+enter", "smart_insert_line_before"], ["ctrl+shift+end", "select_buffer_end"], @@ -249,6 +249,19 @@ "inherit": "project", "on_match_failure": "ignore", "press": [ + ["ctrl+e", "find_file"], + ["f", "find_file"], + ["e", "find_file"], + ["ctrl+shift+n", "create_new_file"], + ["n", "create_new_file"], + ["ctrl+o", "open_file"], + ["o", "open_file"], + ["ctrl+r", "open_recent_project"], + ["r", "open_recent_project"], + ["ctrl+shift+p", "open_command_palette"], + ["p", "open_command_palette"], + ["t", "change_theme"], + ["a", "add_task"], ["c", "open_config"], ["g", "open_gui_config"], ["k", "open_keybind_config"], @@ -256,6 +269,9 @@ ["ctrl+f ctrl+f ctrl+f ctrl+f ctrl+f", "home_sheeran"], ["ctrl+shift+r", "restart"], ["f6", "open_config"], + ["v", "open_version_info"], + ["ctrl+q", "quit"], + ["q", "quit"], ["up", "home_menu_up"], ["down", "home_menu_down"], ["enter", "home_menu_activate"] diff --git a/src/keybind/keybind.zig b/src/keybind/keybind.zig index 9ffb43b..f45ef52 100644 --- a/src/keybind/keybind.zig +++ b/src/keybind/keybind.zig @@ -65,7 +65,7 @@ const Handler = struct { bindings: *const BindingSet, fn create(mode_name: []const u8, allocator: std.mem.Allocator, opts: anytype) !Mode { - const self: *@This() = try allocator.create(@This()); + const self = try allocator.create(@This()); errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, diff --git a/src/project_manager.zig b/src/project_manager.zig index 78dde81..a179c1e 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -263,6 +263,7 @@ const Process = struct { fn create() SpawnError!tp.pid { const allocator = std.heap.c_allocator; const self = try allocator.create(Process); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .parent = tp.self_pid().clone(), @@ -619,11 +620,11 @@ const Process = struct { project.register_capability(from, cbor_id, params_cb) else if (std.mem.eql(u8, method, "window/workDoneProgress/create")) project.workDoneProgress_create(from, cbor_id, params_cb) - else blk: { + else { const params = try cbor.toJsonAlloc(self.allocator, params_cb); defer self.allocator.free(params); - self.logger.print_err("lsp", "unsupported LSP request: {s} -> {s}", .{ method, params }); - break :blk error.Unsupported; + self.logger.print("unsupported LSP request: {s} -> {s}", .{ method, params }); + project.unsupported_lsp_request(from, cbor_id, method) catch {}; }; } @@ -755,6 +756,7 @@ fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_ fn spawn_link(allocator: std.mem.Allocator, parent: tp.pid_ref, project: *Project, max: usize, path: []const u8) (SpawnError || std.fs.Dir.OpenError)!void { const self = try allocator.create(path_files); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .project_name = try allocator.dupe(u8, project.name), diff --git a/src/renderer/vaxis/renderer.zig b/src/renderer/vaxis/renderer.zig index 2ac6f8c..3141e34 100644 --- a/src/renderer/vaxis/renderer.zig +++ b/src/renderer/vaxis/renderer.zig @@ -58,6 +58,8 @@ pub const Error = error{ JsonIncompatibleType, NotAnObject, BadArrayAllocExtract, + InvalidMapType, + InvalidUnion, } || std.Thread.SpawnError; pub fn init(allocator: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: bool, _: *const fn (ctx: *anyopaque) void) Error!Self { @@ -180,7 +182,7 @@ fn handleSegfaultPosixNoAbort(sig: i32, info: *const std.posix.siginfo_t, ctx_pt pub fn run(self: *Self) Error!void { self.vx.sgr = .legacy; - self.vx.conpty_hacks = true; + self.vx.enable_workarounds = true; panic_cleanup = .{ .allocator = self.allocator, .tty = &self.tty, .vx = &self.vx }; if (!self.no_alternate) self.vx.enterAltScreen(self.tty.anyWriter()) catch return error.TtyWriteError; @@ -639,7 +641,7 @@ const Loop = struct { switch (builtin.os.tag) { .windows => { var parser: vaxis.Parser = .{ - .graphemes = &self.vaxis.unicode.graphemes, + .grapheme_data = &self.vaxis.unicode.width_data.graphemes, }; const a = self.vaxis.opts.system_clipboard_allocator orelse @panic("no tty allocator"); while (!self.should_quit) { @@ -648,7 +650,7 @@ const Loop = struct { }, else => { var parser: vaxis.Parser = .{ - .graphemes = &self.vaxis.unicode.graphemes, + .grapheme_data = &self.vaxis.unicode.width_data.graphemes, }; const a = self.vaxis.opts.system_clipboard_allocator orelse @panic("no tty allocator"); diff --git a/src/renderer/win32/renderer.zig b/src/renderer/win32/renderer.zig index a56adb7..a4d8ed8 100644 --- a/src/renderer/win32/renderer.zig +++ b/src/renderer/win32/renderer.zig @@ -36,6 +36,8 @@ pub const Error = error{ JsonIncompatibleType, NotAnObject, BadArrayAllocExtract, + InvalidMapType, + InvalidUnion, } || std.Thread.SpawnError; pub const panic = messageBoxThenPanic(.{ .title = "Flow Panic" }); diff --git a/src/ripgrep.zig b/src/ripgrep.zig index c6c14a3..4ea7bd6 100644 --- a/src/ripgrep.zig +++ b/src/ripgrep.zig @@ -85,6 +85,7 @@ const Process = struct { pub fn create(allocator: std.mem.Allocator, query: []const u8, tag: [:0]const u8, stdin_behavior: std.process.Child.StdIo) !tp.pid { const self = try allocator.create(Process); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .query = try allocator.dupe(u8, query), diff --git a/src/service_template.zig b/src/service_template.zig index 17f0a83..92cc265 100644 --- a/src/service_template.zig +++ b/src/service_template.zig @@ -45,6 +45,7 @@ const Process = struct { pub fn create(allocator: std.mem.Allocator) Error!tp.pid { const self = try allocator.create(Process); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .parent = tp.self_pid().clone(), diff --git a/src/shell.zig b/src/shell.zig index cb27221..3e316ab 100644 --- a/src/shell.zig +++ b/src/shell.zig @@ -27,6 +27,8 @@ pub const Error = error{ JsonIncompatibleType, NotAnObject, BadArrayAllocExtract, + InvalidMapType, + InvalidUnion, }; pub const OutputHandler = fn (context: usize, parent: tp.pid_ref, arg0: []const u8, output: []const u8) void; @@ -156,6 +158,7 @@ const Process = struct { return error.InvalidShellArg0; const self = try allocator.create(Process); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .argv = argv, diff --git a/src/syntax/src/QueryCache.zig b/src/syntax/src/QueryCache.zig index 816d8bc..7b011d5 100644 --- a/src/syntax/src/QueryCache.zig +++ b/src/syntax/src/QueryCache.zig @@ -62,6 +62,7 @@ pub const Error = CacheError || QueryParseError || QuerySerializeError; pub fn create(allocator: std.mem.Allocator, opts: struct { lock: bool = false }) !*Self { const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .mutex = if (opts.lock) .{} else null, diff --git a/src/syntax/src/syntax.zig b/src/syntax/src/syntax.zig index bd67ccf..5225155 100644 --- a/src/syntax/src/syntax.zig +++ b/src/syntax/src/syntax.zig @@ -29,9 +29,13 @@ tree: ?*treez.Tree = null, pub fn create(file_type: FileType, allocator: std.mem.Allocator, query_cache: *QueryCache) !*Self { const query = try query_cache.get(file_type, .highlights); + errdefer query_cache.release(query, .highlights); const errors_query = try query_cache.get(file_type, .errors); + errdefer query_cache.release(errors_query, .highlights); const injections = try query_cache.get(file_type, .injections); + errdefer if (injections) |injections_| query_cache.release(injections_, .injections); const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .lang = file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{file_type.name}), @@ -40,7 +44,6 @@ pub fn create(file_type: FileType, allocator: std.mem.Allocator, query_cache: *Q .errors_query = errors_query, .injections = injections, }; - errdefer self.destroy(query_cache); try self.parser.setLanguage(self.lang); return self; } @@ -58,6 +61,7 @@ pub fn static_create_guess_file_type_static(allocator: std.mem.Allocator, conten pub fn destroy(self: *Self, query_cache: *QueryCache) void { if (self.tree) |tree| tree.destroy(); query_cache.release(self.query, .highlights); + query_cache.release(self.errors_query, .highlights); if (self.injections) |injections| query_cache.release(injections, .injections); self.parser.destroy(); self.allocator.destroy(self); diff --git a/src/syntax/src/ts_serializer.zig b/src/syntax/src/ts_serializer.zig index 90c5865..928f60b 100644 --- a/src/syntax/src/ts_serializer.zig +++ b/src/syntax/src/ts_serializer.zig @@ -285,6 +285,7 @@ pub const DeserializeError = error{ pub fn fromCbor(cb: []const u8, allocator: std.mem.Allocator) DeserializeError!struct { *TSQuery, *std.heap.ArenaAllocator } { var arena = try allocator.create(std.heap.ArenaAllocator); + errdefer allocator.destroy(arena); arena.* = std.heap.ArenaAllocator.init(allocator); errdefer arena.deinit(); const query = try arena.allocator().create(TSQuery); diff --git a/src/tui/Button.zig b/src/tui/Button.zig index 60fbc41..1ca9704 100644 --- a/src/tui/Button.zig +++ b/src/tui/Button.zig @@ -48,6 +48,7 @@ pub fn Options(context: type) type { pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts: Options(ctx_type)) error{OutOfMemory}!*State(ctx_type) { const Self = State(ctx_type); const self = try allocator.create(Self); + errdefer allocator.destroy(self); var n = try Plane.init(&opts.pos.opts(@typeName(Self)), parent); errdefer n.deinit(); self.* = .{ @@ -57,6 +58,7 @@ pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts: .opts = opts, }; self.opts.label = try self.allocator.dupe(u8, opts.label); + errdefer allocator.free(self.opts.label); try self.init(); return self; } diff --git a/src/tui/InputBox.zig b/src/tui/InputBox.zig index 71d79b7..1efd3d2 100644 --- a/src/tui/InputBox.zig +++ b/src/tui/InputBox.zig @@ -58,9 +58,10 @@ pub fn Options(context: type) type { pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts: Options(ctx_type)) !Widget { const Self = State(ctx_type); - const self = try allocator.create(Self); var n = try Plane.init(&opts.pos.opts(@typeName(Self)), parent); errdefer n.deinit(); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .parent = parent, .plane = n, diff --git a/src/tui/Menu.zig b/src/tui/Menu.zig index 14f68f1..284dc03 100644 --- a/src/tui/Menu.zig +++ b/src/tui/Menu.zig @@ -56,6 +56,7 @@ pub fn Options(context: type) type { pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts: Options(ctx_type)) !*State(ctx_type) { const self = try allocator.create(State(ctx_type)); + errdefer allocator.destroy(self); const container = try WidgetList.createH(allocator, parent, @typeName(@This()), .dynamic); self.* = .{ .allocator = allocator, diff --git a/src/tui/ModalBackground.zig b/src/tui/ModalBackground.zig index 63444c0..4bc1630 100644 --- a/src/tui/ModalBackground.zig +++ b/src/tui/ModalBackground.zig @@ -59,6 +59,7 @@ pub fn Options(context: type) type { pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Widget, opts: Options(ctx_type)) !*State(ctx_type) { const self = try allocator.create(State(ctx_type)); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .plane = parent.plane.*, diff --git a/src/tui/Widget.zig b/src/tui/Widget.zig index 40060f6..0f0c96f 100644 --- a/src/tui/Widget.zig +++ b/src/tui/Widget.zig @@ -224,6 +224,7 @@ pub fn hover(self: *Self) bool { pub fn empty(allocator: Allocator, parent: Plane, layout_: Layout) !Self { const child: type = struct { plane: Plane, layout: Layout }; const widget = try allocator.create(child); + errdefer allocator.destroy(widget); const n = try Plane.init(&(Box{}).opts("empty"), parent); widget.* = .{ .plane = n, .layout = layout_ }; return .{ diff --git a/src/tui/WidgetList.zig b/src/tui/WidgetList.zig index 98808a9..1024ece 100644 --- a/src/tui/WidgetList.zig +++ b/src/tui/WidgetList.zig @@ -32,21 +32,24 @@ after_render: *const fn (ctx: ?*anyopaque, theme: *const Widget.Theme) void = on on_resize: *const fn (ctx: ?*anyopaque, self: *Self, pos_: Widget.Box) void = on_resize_default, pub fn createH(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout) error{OutOfMemory}!*Self { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = try init(allocator, parent, name, .horizontal, layout_, Box{}); self.plane.hide(); return self; } pub fn createV(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout) !*Self { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = try init(allocator, parent, name, .vertical, layout_, Box{}); self.plane.hide(); return self; } pub fn createBox(allocator: Allocator, parent: Plane, name: [:0]const u8, dir: Direction, layout_: Layout, box: Box) !*Self { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = try init(allocator, parent, name, dir, layout_, box); self.plane.hide(); return self; diff --git a/src/tui/editor.zig b/src/tui/editor.zig index c4bd75b..e432ab8 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -23,6 +23,7 @@ const editor_gutter = @import("editor_gutter.zig"); const Widget = @import("Widget.zig"); const WidgetList = @import("WidgetList.zig"); const tui = @import("tui.zig"); +const IndentMode = @import("config").IndentMode; pub const Cursor = Buffer.Cursor; pub const View = Buffer.View; @@ -318,6 +319,7 @@ pub const Editor = struct { render_whitespace: WhitespaceMode, indent_size: usize, tab_width: usize, + indent_mode: IndentMode, last: struct { root: ?Buffer.Root = null, @@ -370,12 +372,16 @@ pub const Editor = struct { const Result = command.Result; pub fn write_state(self: *const Self, writer: Buffer.MetaWriter) !void { - try cbor.writeArrayHeader(writer, 8); + try cbor.writeArrayHeader(writer, 12); try cbor.writeValue(writer, self.file_path orelse ""); try cbor.writeValue(writer, self.clipboard orelse ""); try cbor.writeValue(writer, self.last_find_query orelse ""); try cbor.writeValue(writer, self.enable_format_on_save); try cbor.writeValue(writer, self.enable_auto_save); + try cbor.writeValue(writer, self.indent_size); + try cbor.writeValue(writer, self.tab_width); + try cbor.writeValue(writer, self.indent_mode); + try cbor.writeValue(writer, self.syntax_no_render); if (self.find_history) |history| { try cbor.writeArrayHeader(writer, history.items.len); for (history.items) |item| @@ -411,6 +417,10 @@ pub const Editor = struct { tp.extract(&last_find_query), tp.extract(&self.enable_format_on_save), tp.extract(&self.enable_auto_save), + tp.extract(&self.indent_size), + tp.extract(&self.tab_width), + tp.extract(&self.indent_mode), + tp.extract(&self.syntax_no_render), tp.extract_cbor(&find_history), tp.extract_cbor(&view_cbor), tp.extract_cbor(&cursels_cbor), @@ -452,13 +462,15 @@ pub const Editor = struct { 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.config().indent_size; const tab_width = tui.config().tab_width; + const indent_mode = tui.config().indent_mode; + const indent_size = if (indent_mode == .tabs) tab_width else tui.config().indent_size; self.* = Self{ .allocator = allocator, .plane = n, .indent_size = indent_size, .tab_width = tab_width, + .indent_mode = indent_mode, .metrics = self.plane.metrics(tab_width), .logger = logger, .file_path = null, @@ -574,6 +586,7 @@ pub const Editor = struct { if (self.buffer) |_| try self.close(); self.buffer = new_buf; const file_type = file_type_ orelse new_buf.file_type_name; + const buffer_meta = if (self.buffer) |buffer| buffer.get_meta() else null; if (new_buf.root.lines() > root_mod.max_syntax_lines) { self.logger.print("large file threshold {d} lines < file size {d} lines", .{ @@ -583,15 +596,19 @@ pub const Editor = struct { self.logger.print("syntax highlighting disabled", .{}); self.syntax_no_render = true; } + + var content = std.ArrayListUnmanaged(u8).empty; + defer content.deinit(std.heap.c_allocator); + { + const frame_ = tracy.initZone(@src(), .{ .name = "store" }); + defer frame_.deinit(); + try new_buf.root.store(content.writer(std.heap.c_allocator), new_buf.file_eol_mode); + } + if (self.indent_mode == .auto) + self.detect_indent_mode(content.items); + self.syntax = syntax: { const lang_override = file_type orelse tp.env.get().str("language"); - var content = std.ArrayListUnmanaged(u8).empty; - defer content.deinit(std.heap.c_allocator); - { - const frame_ = tracy.initZone(@src(), .{ .name = "store" }); - defer frame_.deinit(); - try new_buf.root.store(content.writer(std.heap.c_allocator), new_buf.file_eol_mode); - } self.file_type = blk: { const frame_ = tracy.initZone(@src(), .{ .name = "guess" }); @@ -613,7 +630,7 @@ pub const Editor = struct { null; }; - if (self.file_type) |ft| { + if (buffer_meta == null) if (self.file_type) |ft| { const frame_ = tracy.initZone(@src(), .{ .name = "did_open" }); defer frame_.deinit(); project_manager.did_open( @@ -624,7 +641,7 @@ pub const Editor = struct { new_buf.is_ephemeral(), ) catch |e| self.logger.print("project_manager.did_open failed: {any}", .{e}); - } + }; break :syntax syn; }; self.syntax_no_render = tp.env.get().is("no-syntax"); @@ -639,11 +656,11 @@ pub const Editor = struct { buffer.file_type_color = ftc; } - if (self.buffer) |buffer| if (buffer.get_meta()) |meta| { + if (buffer_meta) |meta| { const frame_ = tracy.initZone(@src(), .{ .name = "extract_state" }); defer frame_.deinit(); try self.extract_state(meta, .none); - }; + } try self.send_editor_open(file_path, new_buf.file_exists, ftn, fti, ftc); } @@ -663,6 +680,21 @@ pub const Editor = struct { self.enable_auto_save = true; } + fn detect_indent_mode(self: *Self, content: []const u8) void { + var it = std.mem.splitScalar(u8, content, '\n'); + while (it.next()) |line| { + if (line.len == 0) continue; + if (line[0] == '\t') { + self.indent_size = self.tab_width; + self.indent_mode = .tabs; + return; + } + } + self.indent_size = tui.config().indent_size; + self.indent_mode = .spaces; + return; + } + fn close(self: *Self) !void { var meta = std.ArrayListUnmanaged(u8).empty; defer meta.deinit(self.allocator); @@ -836,6 +868,33 @@ pub const Editor = struct { } pub const resume_undo_history_meta: Meta = .{ .description = "Resume undo history" }; + fn collapse_trailing_ws_line(self: *Self, root: Buffer.Root, row: usize, allocator: Allocator) Buffer.Root { + const last = find_last_non_ws(root, row, self.metrics); + var cursel: CurSel = .{ .cursor = .{ .row = row, .col = last } }; + with_selection_const(root, move_cursor_end, &cursel, self.metrics) catch return root; + return self.delete_selection(root, &cursel, allocator) catch root; + } + + fn find_last_non_ws(root: Buffer.Root, row: usize, metrics: Buffer.Metrics) usize { + const Ctx = struct { + col: usize = 0, + last_non_ws: usize = 0, + fn walker(ctx_: *anyopaque, egc: []const u8, wcwidth: usize, _: Buffer.Metrics) Buffer.Walker { + const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_))); + ctx.col += wcwidth; + switch (egc[0]) { + ' ', '\t' => {}, + '\n' => return Buffer.Walker.stop, + else => ctx.last_non_ws = ctx.col, + } + return Buffer.Walker.keep_walking; + } + }; + var ctx: Ctx = .{}; + root.walk_egc_forward(row, Ctx.walker, &ctx, metrics) catch return 0; + return ctx.last_non_ws; + } + fn find_first_non_ws(root: Buffer.Root, row: usize, metrics: Buffer.Metrics) usize { const Ctx = struct { col: usize = 0, @@ -3651,9 +3710,16 @@ pub const Editor = struct { const space = " "; var cursel: CurSel = .{}; cursel.cursor = cursor; - const cols = self.indent_size - find_first_non_ws(root, cursel.cursor.row, self.metrics) % self.indent_size; try move_cursor_begin(root, &cursel.cursor, self.metrics); - return self.insert(root, &cursel, space[0..cols], allocator) catch return error.Stop; + switch (self.indent_mode) { + .spaces, .auto => { + const cols = self.indent_size - find_first_non_ws(root, cursel.cursor.row, self.metrics) % self.indent_size; + return self.insert(root, &cursel, space[0..cols], allocator) catch return error.Stop; + }, + .tabs => { + return self.insert(root, &cursel, "\t", allocator) catch return error.Stop; + }, + } } fn indent_cursel(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root { @@ -3688,16 +3754,16 @@ pub const Editor = struct { const off = first % self.indent_size; const cols = if (off == 0) self.indent_size else off; const sel = cursel.enable_selection(root, self.metrics) catch return error.Stop; - sel.begin.move_begin(); - try sel.end.move_to(root, sel.end.row, cols, self.metrics); + try sel.begin.move_to(root, sel.begin.row, first, self.metrics); + try sel.end.move_to(root, sel.end.row, first - cols, self.metrics); var saved = false; - if (cursor_protect) |cp| if (cp.row == cursor.row and cp.col < cols) { - cp.col = cols + 1; + if (cursor_protect) |cp| if (cp.row == cursor.row and cp.col < first and cp.col >= first - cols) { + cp.col = first + 1; saved = true; }; newroot = try self.delete_selection(root, &cursel, allocator); if (cursor_protect) |cp| if (saved) { - try cp.move_to(root, cp.row, 0, self.metrics); + try cp.move_to(root, cp.row, first - cols, self.metrics); cp.clamp_to_buffer(newroot, self.metrics); }; return newroot; @@ -4368,17 +4434,52 @@ pub const Editor = struct { } pub const insert_line_meta: Meta = .{ .description = "Insert line" }; + fn generate_leading_ws(self: *Self, writer: anytype, leading_ws: usize) !void { + return switch (self.indent_mode) { + .spaces, .auto => generate_leading_spaces(writer, leading_ws), + .tabs => generate_leading_tabs(writer, leading_ws, self.tab_width), + }; + } + + fn generate_leading_spaces(writer: anytype, leading_ws: usize) !void { + var width = leading_ws; + while (width > 0) : (width -= 1) + try writer.writeByte(' '); + } + + fn generate_leading_tabs(writer: anytype, leading_ws: usize, tab_width: usize) !void { + var width = leading_ws; + while (width > 0) if (width >= tab_width) { + width -= tab_width; + try writer.writeByte('\t'); + } else { + width -= 1; + try writer.writeByte(' '); + }; + } + fn cursel_smart_insert_line(self: *Self, root: Buffer.Root, cursel: *CurSel, b_allocator: std.mem.Allocator) !Buffer.Root { - var leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col); + const row = cursel.cursor.row; + const leading_ws = @min(find_first_non_ws(root, row, self.metrics), cursel.cursor.col); var sfa = std.heap.stackFallback(512, self.allocator); const allocator = sfa.get(); var stream = std.ArrayListUnmanaged(u8).empty; defer stream.deinit(allocator); var writer = stream.writer(allocator); _ = try writer.write("\n"); - while (leading_ws > 0) : (leading_ws -= 1) - _ = try writer.write(" "); - return self.insert(root, cursel, stream.items, b_allocator); + try self.generate_leading_ws(&writer, leading_ws); + var root_ = try self.insert(root, cursel, stream.items, b_allocator); + root_ = self.collapse_trailing_ws_line(root_, row, b_allocator); + const leading_ws_ = find_first_non_ws(root_, cursel.cursor.row, self.metrics); + if (leading_ws_ > leading_ws and leading_ws_ > cursel.cursor.col) { + const sel = try cursel.enable_selection(root_, self.metrics); + sel.* = .{ + .begin = .{ .row = cursel.cursor.row, .col = cursel.cursor.col }, + .end = .{ .row = cursel.cursor.row, .col = leading_ws_ }, + }; + root_ = self.delete_selection(root_, cursel, b_allocator) catch root_; + } + return root_; } pub fn smart_insert_line(self: *Self, _: Context) Result { @@ -4431,19 +4532,20 @@ pub const Editor = struct { const b = try self.buf_for_update(); var root = b.root; for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - var leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col); + const leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col); try move_cursor_begin(root, &cursel.cursor, self.metrics); root = try self.insert(root, cursel, "\n", b.allocator); + const row = cursel.cursor.row; try move_cursor_left(root, &cursel.cursor, self.metrics); var sfa = std.heap.stackFallback(512, self.allocator); const allocator = sfa.get(); var stream = std.ArrayListUnmanaged(u8).empty; defer stream.deinit(allocator); var writer = stream.writer(self.allocator); - while (leading_ws > 0) : (leading_ws -= 1) - _ = try writer.write(" "); + try self.generate_leading_ws(&writer, leading_ws); if (stream.items.len > 0) root = try self.insert(root, cursel, stream.items, b.allocator); + root = self.collapse_trailing_ws_line(root, row, b.allocator); }; try self.update_buf(root); self.clamp(); @@ -4466,7 +4568,8 @@ pub const Editor = struct { const b = try self.buf_for_update(); var root = b.root; for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - var leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col); + const leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col); + const row = cursel.cursor.row; try move_cursor_end(root, &cursel.cursor, self.metrics); var sfa = std.heap.stackFallback(512, self.allocator); const allocator = sfa.get(); @@ -4474,10 +4577,10 @@ pub const Editor = struct { defer stream.deinit(allocator); var writer = stream.writer(allocator); _ = try writer.write("\n"); - while (leading_ws > 0) : (leading_ws -= 1) - _ = try writer.write(" "); + try self.generate_leading_ws(&writer, leading_ws); if (stream.items.len > 0) root = try self.insert(root, cursel, stream.items, b.allocator); + root = self.collapse_trailing_ws_line(root, row, b.allocator); }; try self.update_buf(root); self.clamp(); @@ -5906,9 +6009,14 @@ pub const Editor = struct { self.syntax_no_render = tp.env.get().is("no-syntax"); self.syntax_report_timing = tp.env.get().is("syntax-report-timing"); - const ftn = if (self.file_type) |ft| ft.name else "text"; - const fti = if (self.file_type) |ft| ft.icon orelse "🖹" else "🖹"; - const ftc = if (self.file_type) |ft| ft.color orelse 0x000000 else 0x000000; + const ftn = if (self.file_type) |ft| ft.name else file_type_config.default.name; + const fti = if (self.file_type) |ft| ft.icon orelse file_type_config.default.icon else file_type_config.default.icon; + const ftc = if (self.file_type) |ft| ft.color orelse file_type_config.default.color else file_type_config.default.color; + if (self.buffer) |buffer| { + buffer.file_type_name = ftn; + buffer.file_type_icon = fti; + buffer.file_type_color = ftc; + } const file_exists = if (self.buffer) |b| b.file_exists else false; try self.send_editor_open(self.file_path orelse "", file_exists, ftn, fti, ftc); self.logger.print("file type {s}", .{file_type}); @@ -5941,7 +6049,8 @@ pub const EditorWidget = struct { fn create(allocator: Allocator, parent: Plane, buffer_manager: *Buffer.Manager) !Widget { const container = try WidgetList.createH(allocator, parent, "editor.container", .dynamic); - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); try self.init(allocator, container.plane, buffer_manager); try self.commands.init(&self.editor); const editorWidget = Widget.to(self); diff --git a/src/tui/editor_gutter.zig b/src/tui/editor_gutter.zig index f8045d8..77ed19b 100644 --- a/src/tui/editor_gutter.zig +++ b/src/tui/editor_gutter.zig @@ -43,7 +43,8 @@ const Kind = enum { insert, modified, delete }; const Symbol = struct { kind: Kind, line: usize }; pub fn create(allocator: Allocator, parent: Widget, event_source: Widget, editor: *ed.Editor) !Widget { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent.plane.*), diff --git a/src/tui/filelist_view.zig b/src/tui/filelist_view.zig index 4465e8a..6f4e824 100644 --- a/src/tui/filelist_view.zig +++ b/src/tui/filelist_view.zig @@ -47,7 +47,8 @@ const Entry = struct { }; pub fn create(allocator: Allocator, parent: Plane) !Widget { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .plane = try Plane.init(&(Widget.Box{}).opts(name), parent), diff --git a/src/tui/home.zig b/src/tui/home.zig index 42406d0..e00a16a 100644 --- a/src/tui/home.zig +++ b/src/tui/home.zig @@ -81,7 +81,8 @@ const Self = @This(); pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget { const logger = log.logger("home"); - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); var n = try Plane.init(&(Widget.Box{}).opts("editor"), parent.plane.*); errdefer n.deinit(); @@ -303,6 +304,8 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool { self.position_menu(self.v_center(5, self.menu_len, 5), self.center(x, self.menu_w)); } + if (self.plane.dim_y() < 3 or self.plane.dim_x() < root.version.len + 4) return false; + self.plane.cursor_move_yx( @intCast(self.plane.dim_y() - 2), @intCast(@max(self.plane.dim_x(), root.version.len + 3) - root.version.len - 3), @@ -311,6 +314,7 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool { _ = self.plane.print("{s}", .{root.version}) catch return false; if (builtin.mode == .Debug) { const debug_warning_text = "debug build"; + if (self.plane.dim_y() < 4 or self.plane.dim_x() < debug_warning_text.len + 4) return false; self.plane.cursor_move_yx( @intCast(self.plane.dim_y() - 3), @intCast(@max(self.plane.dim_x(), debug_warning_text.len + 3) - debug_warning_text.len - 3), diff --git a/src/tui/info_view.zig b/src/tui/info_view.zig index de43f50..f67e2df 100644 --- a/src/tui/info_view.zig +++ b/src/tui/info_view.zig @@ -14,7 +14,8 @@ view_rows: usize = 0, lines: std.ArrayList([]const u8), pub fn create(allocator: Allocator, parent: Plane) !Widget { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .plane = try Plane.init(&(Widget.Box{}).opts(name), parent), diff --git a/src/tui/inputview.zig b/src/tui/inputview.zig index a8be882..a955189 100644 --- a/src/tui/inputview.zig +++ b/src/tui/inputview.zig @@ -30,9 +30,10 @@ const Entry = struct { const Buffer = ArrayList(Entry); pub fn create(allocator: Allocator, parent: Plane) !Widget { - const self: *Self = try allocator.create(Self); var n = try Plane.init(&(Widget.Box{}).opts_vscroll(@typeName(Self)), parent); errdefer n.deinit(); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .parent = parent, diff --git a/src/tui/inspector_view.zig b/src/tui/inspector_view.zig index 967648e..6d314d7 100644 --- a/src/tui/inspector_view.zig +++ b/src/tui/inspector_view.zig @@ -26,7 +26,8 @@ const Self = @This(); pub fn create(allocator: Allocator, parent: Plane) !Widget { const editor = tui.get_active_editor() orelse return error.NotFound; - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .plane = try Plane.init(&(Widget.Box{}).opts_vscroll(name), parent), .editor = editor, diff --git a/src/tui/logview.zig b/src/tui/logview.zig index 02776e0..4c70457 100644 --- a/src/tui/logview.zig +++ b/src/tui/logview.zig @@ -38,7 +38,8 @@ const Level = enum { }; pub fn create(allocator: Allocator, parent: Plane) !Widget { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .plane = try Plane.init(&(Widget.Box{}).opts(name), parent) }; return Widget.to(self); } diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index 7fafc9d..f32b90a 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -66,6 +66,7 @@ pub const CreateError = error{ OutOfMemory, ThespianSpawnFailed }; pub fn create(allocator: std.mem.Allocator) CreateError!Widget { const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .plane = tui.plane(), diff --git a/src/tui/mode/mini/buffer.zig b/src/tui/mode/mini/buffer.zig index 35af902..3875d1a 100644 --- a/src/tui/mode/mini/buffer.zig +++ b/src/tui/mode/mini/buffer.zig @@ -21,7 +21,8 @@ pub fn Create(options: type) type { const Self = @This(); pub fn create(allocator: std.mem.Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .input = std.ArrayList(u8).init(allocator), diff --git a/src/tui/mode/mini/file_browser.zig b/src/tui/mode/mini/file_browser.zig index 4b5da15..ea6fee7 100644 --- a/src/tui/mode/mini/file_browser.zig +++ b/src/tui/mode/mini/file_browser.zig @@ -35,7 +35,8 @@ pub fn Create(options: type) type { }; pub fn create(allocator: std.mem.Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .file_path = std.ArrayList(u8).init(allocator), diff --git a/src/tui/mode/mini/find.zig b/src/tui/mode/mini/find.zig index 30b4976..e04adc0 100644 --- a/src/tui/mode/mini/find.zig +++ b/src/tui/mode/mini/find.zig @@ -28,7 +28,8 @@ commands: Commands = undefined, pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } { const editor = tui.get_active_editor() orelse return error.NotFound; - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .input_ = ArrayList(u8).init(allocator), diff --git a/src/tui/mode/mini/find_in_files.zig b/src/tui/mode/mini/find_in_files.zig index 4680d22..839cd1b 100644 --- a/src/tui/mode/mini/find_in_files.zig +++ b/src/tui/mode/mini/find_in_files.zig @@ -25,7 +25,8 @@ last_input: []u8 = "", commands: Commands = undefined, pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator }; try self.commands.init(self); if (tui.get_active_selection(self.allocator)) |text| { diff --git a/src/tui/mode/mini/goto.zig b/src/tui/mode/mini/goto.zig index c71a0bd..94d5c6d 100644 --- a/src/tui/mode/mini/goto.zig +++ b/src/tui/mode/mini/goto.zig @@ -24,8 +24,9 @@ start: usize, commands: Commands = undefined, pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } { - const self: *Self = try allocator.create(Self); const editor = tui.get_active_editor() orelse return error.NotFound; + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .start = editor.get_primary().cursor.row + 1, diff --git a/src/tui/mode/mini/move_to_char.zig b/src/tui/mode/mini/move_to_char.zig index 950b35e..d32f00c 100644 --- a/src/tui/mode/mini/move_to_char.zig +++ b/src/tui/mode/mini/move_to_char.zig @@ -38,7 +38,8 @@ pub fn create(allocator: Allocator, ctx: command.Context) !struct { tui.Mode, tu const direction: Direction = if (std.mem.indexOf(u8, operation_command, "_left")) |_| .left else .right; const operation: Operation = if (tui.get_active_editor()) |editor| if (editor.get_primary().selection) |_| .select else .move else .move; - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .direction = direction, diff --git a/src/tui/mode/overlay/command_palette.zig b/src/tui/mode/overlay/command_palette.zig index 1f0ba2b..9e0be4b 100644 --- a/src/tui/mode/overlay/command_palette.zig +++ b/src/tui/mode/overlay/command_palette.zig @@ -21,11 +21,13 @@ pub const Entry = struct { pub fn load_entries(palette: *Type) !usize { const hints = if (tui.input_mode()) |m| m.keybind_hints else @panic("no keybind hints"); - var longest_hint: usize = 0; + var longest_description: usize = 0; + var longest_total: usize = 0; for (command.commands.items) |cmd_| if (cmd_) |p| { if (p.meta.description.len > 0) { const hint = hints.get(p.name) orelse ""; - longest_hint = @max(longest_hint, hint.len); + longest_description = @max(longest_description, p.meta.description.len); + longest_total = @max(longest_total, p.meta.description.len + hint.len + 1); (try palette.entries.addOne()).* = .{ .label = if (p.meta.description.len > 0) p.meta.description else p.name, .name = p.name, @@ -35,7 +37,7 @@ pub fn load_entries(palette: *Type) !usize { }; } }; - return longest_hint; + return longest_total - longest_description; } pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !void { diff --git a/src/tui/mode/overlay/open_recent.zig b/src/tui/mode/overlay/open_recent.zig index e6a22e9..0c5d687 100644 --- a/src/tui/mode/overlay/open_recent.zig +++ b/src/tui/mode/overlay/open_recent.zig @@ -38,7 +38,8 @@ buffer_manager: ?*BufferManager, pub fn create(allocator: std.mem.Allocator) !tui.Mode { const mv = tui.mainview() orelse return error.NotFound; - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .modal = try ModalBackground.create(*Self, allocator, tui.mainview_widget(), .{ .ctx = self }), diff --git a/src/tui/mode/overlay/palette.zig b/src/tui/mode/overlay/palette.zig index bde1217..926dc65 100644 --- a/src/tui/mode/overlay/palette.zig +++ b/src/tui/mode/overlay/palette.zig @@ -47,7 +47,8 @@ pub fn Create(options: type) type { pub fn create(allocator: std.mem.Allocator) !tui.Mode { const mv = tui.mainview() orelse return error.NotFound; - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .modal = try ModalBackground.create(*Self, allocator, tui.mainview_widget(), .{ diff --git a/src/tui/scrollbar_v.zig b/src/tui/scrollbar_v.zig index 4f83b1b..8f0911a 100644 --- a/src/tui/scrollbar_v.zig +++ b/src/tui/scrollbar_v.zig @@ -27,7 +27,8 @@ style_factory: ?*const fn (self: *Self, theme: *const Widget.Theme) Widget.Theme const Self = @This(); pub fn create(allocator: Allocator, parent: Plane, event_source: ?Widget, event_sink: EventHandler) !Widget { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), .event_sink = event_sink, diff --git a/src/tui/status/blank.zig b/src/tui/status/blank.zig index 8901fd0..50e143b 100644 --- a/src/tui/status/blank.zig +++ b/src/tui/status/blank.zig @@ -20,7 +20,8 @@ pub fn Create(comptime layout_: Widget.Layout) @import("widget.zig").CreateFunct break :blk Widget.Layout{ .static = size }; } else break :blk layout_; } else layout_; - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), .layout_ = layout__, diff --git a/src/tui/status/branch.zig b/src/tui/status/branch.zig index ce368ef..3ad6457 100644 --- a/src/tui/status/branch.zig +++ b/src/tui/status/branch.zig @@ -44,6 +44,7 @@ pub fn create( .on_click = on_click, .on_layout = layout, .on_render = render, + .on_receive = receive, .on_event = event_handler, }); } @@ -60,18 +61,46 @@ pub fn ctx_deinit(self: *Self) void { if (self.behind) |p| self.allocator.free(p); } -fn on_click(_: *Self, _: *Button.State(Self)) void { - git.status(0) catch {}; +fn on_click(self: *Self, _: *Button.State(Self)) void { + self.refresh_git_status(); command.executeName("show_git_status", .{}) catch {}; } +fn refresh_git_status(_: *Self) void { + git.status(0) catch {}; +} + +pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message) error{Exit}!bool { + if (try m.match(.{ "E", tp.more })) + return self.process_event(m); + if (try m.match(.{ "PRJ", "open" })) + self.refresh_git_status(); + return false; +} + +fn process_event(self: *Self, m: tp.message) error{Exit}!bool { + if (try m.match(.{ tp.any, "dirty", tp.more }) or + try m.match(.{ tp.any, "save", tp.more }) or + try m.match(.{ tp.any, "open", tp.more }) or + try m.match(.{ tp.any, "close" })) + self.refresh_git_status(); + return false; +} + fn receive_git(self: *Self, _: tp.pid_ref, m: tp.message) MessageFilter.Error!bool { return if (try match(m.buf, .{ "git", more })) self.process_git(m) + else if (try match(m.buf, .{"focus_in"})) + self.process_focus_in() else false; } +fn process_focus_in(self: *Self) MessageFilter.Error!bool { + self.refresh_git_status(); + return false; +} + fn process_git(self: *Self, m: tp.message) MessageFilter.Error!bool { var value: []const u8 = undefined; if (try match(m.buf, .{ any, any, "workspace_path", null_ })) { diff --git a/src/tui/status/clock.zig b/src/tui/status/clock.zig index d6e8391..fe5b6cd 100644 --- a/src/tui/status/clock.zig +++ b/src/tui/status/clock.zig @@ -30,7 +30,8 @@ pub fn create(allocator: std.mem.Allocator, parent: Plane, event_handler: ?Event return error.WidgetInitFailed; }; defer env.deinit(); - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), diff --git a/src/tui/status/filestate.zig b/src/tui/status/filestate.zig index e9a92e0..9493d07 100644 --- a/src/tui/status/filestate.zig +++ b/src/tui/status/filestate.zig @@ -200,24 +200,34 @@ fn render_terminal_title(self: *Self) void { } pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message) error{Exit}!bool { + if (try m.match(.{ "E", tp.more })) + return self.process_event(m); + if (try m.match(.{ "PRJ", "open" })) { + if (!self.file) + self.show_project(); + } + return false; +} + +fn process_event(self: *Self, m: tp.message) error{Exit}!bool { var file_path: []const u8 = undefined; var file_type: []const u8 = undefined; var file_icon: []const u8 = undefined; var file_dirty: bool = undefined; var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf); - if (try m.match(.{ "E", "pos", tp.extract(&self.lines), tp.extract(&self.line), tp.extract(&self.column) })) + if (try m.match(.{ tp.any, "pos", tp.extract(&self.lines), tp.extract(&self.line), tp.extract(&self.column) })) return false; - if (try m.match(.{ "E", "dirty", tp.extract(&file_dirty) })) { + if (try m.match(.{ tp.any, "dirty", tp.extract(&file_dirty) })) { self.file_dirty = file_dirty; - } else if (try m.match(.{ "E", "eol_mode", tp.extract(&eol_mode), tp.extract(&self.utf8_sanitized) })) { + } else if (try m.match(.{ tp.any, "eol_mode", tp.extract(&eol_mode), tp.extract(&self.utf8_sanitized) })) { self.eol_mode = @enumFromInt(eol_mode); - } else if (try m.match(.{ "E", "save", tp.extract(&file_path) })) { + } else if (try m.match(.{ tp.any, "save", tp.extract(&file_path) })) { @memcpy(self.name_buf[0..file_path.len], file_path); self.name = self.name_buf[0..file_path.len]; self.file_exists = true; self.file_dirty = false; self.name = project_manager.abbreviate_home(&self.name_buf, self.name); - } else if (try m.match(.{ "E", "open", tp.extract(&file_path), tp.extract(&self.file_exists), tp.extract(&file_type), tp.extract(&file_icon), tp.extract(&self.file_color) })) { + } else if (try m.match(.{ tp.any, "open", tp.extract(&file_path), tp.extract(&self.file_exists), tp.extract(&file_type), tp.extract(&file_icon), tp.extract(&self.file_color) })) { self.eol_mode = .lf; @memcpy(self.name_buf[0..file_path.len], file_path); self.name = self.name_buf[0..file_path.len]; @@ -229,7 +239,7 @@ pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message self.file_dirty = false; self.name = project_manager.abbreviate_home(&self.name_buf, self.name); self.file = true; - } else if (try m.match(.{ "E", "close" })) { + } else if (try m.match(.{ tp.any, "close" })) { self.name = ""; self.lines = 0; self.line = 0; @@ -238,9 +248,6 @@ pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message self.file = false; self.eol_mode = .lf; self.show_project(); - } else if (try m.match(.{ "PRJ", "open" })) { - if (!self.file) - self.show_project(); } return false; } diff --git a/src/tui/status/keybindstate.zig b/src/tui/status/keybindstate.zig index 3253c19..ea6e709 100644 --- a/src/tui/status/keybindstate.zig +++ b/src/tui/status/keybindstate.zig @@ -12,7 +12,8 @@ plane: Plane, const Self = @This(); pub fn create(allocator: std.mem.Allocator, parent: Plane, _: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), diff --git a/src/tui/status/keystate.zig b/src/tui/status/keystate.zig index ca674a8..afa9ff3 100644 --- a/src/tui/status/keystate.zig +++ b/src/tui/status/keystate.zig @@ -31,7 +31,8 @@ pub const width = idle_msg.len + 20; pub fn create(allocator: Allocator, parent: Plane, _: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget { const frame_rate = tp.env.get().num("frame-rate"); - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), .wipe_after_frames = @divTrunc(frame_rate, 2), diff --git a/src/tui/status/minilog.zig b/src/tui/status/minilog.zig index a2644fb..400ac60 100644 --- a/src/tui/status/minilog.zig +++ b/src/tui/status/minilog.zig @@ -27,7 +27,8 @@ const Level = enum { }; pub fn create(allocator: std.mem.Allocator, parent: Plane, event_handler: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), .msg = std.ArrayList(u8).init(allocator), diff --git a/src/tui/status/modstate.zig b/src/tui/status/modstate.zig index 4555cef..2136e7c 100644 --- a/src/tui/status/modstate.zig +++ b/src/tui/status/modstate.zig @@ -20,7 +20,8 @@ const Self = @This(); pub const width = 8; pub fn create(allocator: Allocator, parent: Plane, _: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), }; diff --git a/src/tui/status/selectionstate.zig b/src/tui/status/selectionstate.zig index f606a5c..6e0fec9 100644 --- a/src/tui/status/selectionstate.zig +++ b/src/tui/status/selectionstate.zig @@ -19,7 +19,8 @@ on_event: ?EventHandler, const Self = @This(); pub fn create(allocator: Allocator, parent: Plane, event_handler: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget { - const self: *Self = try allocator.create(Self); + const self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), .on_event = event_handler, diff --git a/src/tui/status/tabs.zig b/src/tui/status/tabs.zig index 45a2b69..f3af3d1 100644 --- a/src/tui/status/tabs.zig +++ b/src/tui/status/tabs.zig @@ -54,6 +54,7 @@ pub const Style = @"style.config"; pub fn create(allocator: std.mem.Allocator, parent: Plane, event_handler: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget { const self = try allocator.create(TabBar); + errdefer allocator.destroy(self); self.* = try TabBar.init(allocator, parent, event_handler); return Widget.to(self); } @@ -447,6 +448,7 @@ const spacer = struct { event_handler: ?EventHandler, ) @import("widget.zig").CreateError!Widget { const self: *Self = try allocator.create(Self); + errdefer allocator.destroy(self); self.* = .{ .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), .layout_ = .{ .static = self.plane.egc_chunk_width(content, 0, 1) }, diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 6472742..9b82887 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -120,6 +120,9 @@ fn init(allocator: Allocator) InitError!*Self { const frame_clock = try tp.metronome.init(frame_time); var self = try allocator.create(Self); + // don't destroy + // if tui fails it is catastrophic anyway and we don't want to cause nock-on errors + // errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .config_ = conf, diff --git a/src/walk_tree.zig b/src/walk_tree.zig index 5b9a264..ee10d22 100644 --- a/src/walk_tree.zig +++ b/src/walk_tree.zig @@ -21,6 +21,7 @@ pub fn start(a_: std.mem.Allocator, root_path_: []const u8) (SpawnError || std.f fn spawn_link(allocator: std.mem.Allocator, root_path: []const u8) (SpawnError || std.fs.Dir.OpenError)!tp.pid { const self = try allocator.create(tree_walker); + errdefer allocator.destroy(self); self.* = .{ .allocator = allocator, .root_path = try allocator.dupe(u8, root_path),