diff --git a/.gitignore b/.gitignore index e73c965..3389c86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -zig-cache/ +.zig-cache/ zig-out/ diff --git a/build.zig.zon b/build.zig.zon index cd2ce2b..42f1842 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -7,8 +7,8 @@ .dependencies = .{ .vaxis = .{ - .url = "git+https://github.com/rockorager/libvaxis#75ac36ca61999c2f29467e02016322551e98bbd7", - .hash = "1220fbb1b748ced787d6a19d3be8a28839e000548b76bae0f183a6e13504e6bc7b20", + .url = "git+https://github.com/rockorager/libvaxis#fcdeb321feccc1b2b62391c1e257c385a799810e", + .hash = "1220be1b2c1cf8809459629fde0d7ff2eb1bca854a99e4ac69fa19e03afec9428460", }, .fuzzig = .{ .url = "git+https://github.com/fjebaker/fuzzig#0fd156d5097365151e85a85eef9d8cf0eebe7b00", diff --git a/src/app.zig b/src/app.zig index d970a81..c1f718e 100644 --- a/src/app.zig +++ b/src/app.zig @@ -154,7 +154,7 @@ pub fn run(self: *App) !void { } pub fn inputToSlice(self: *App) []const u8 { - self.text_input.cursor_idx = self.text_input.grapheme_count; + self.text_input.buf.cursor = self.text_input.buf.realLength(); return self.text_input.sliceToCursor(&self.text_input_buf); } @@ -202,7 +202,7 @@ pub fn handle_normal_event(self: *App, event: Event, loop: *vaxis.Loop(Event)) ! if (self.directories.dir.openDir(entry.name, .{ .iterate = true })) |dir| { self.directories.dir = dir; - self.directories.history.push(.{ + _ = self.directories.history.push(.{ .selected = self.directories.entries.selected, .offset = self.directories.entries.offset, }); @@ -268,21 +268,44 @@ pub fn handle_normal_event(self: *App, event: Event, loop: *vaxis.Loop(Event)) ! try self.notification.write("Deleting item...", .info); if (self.directories.dir.rename(entry.name, tmp_path)) { - // TODO: Will leak memory if pushing to a full stack. - self.actions.push(.{ + if (self.actions.push(.{ .delete = .{ .old = old_path, .new = tmp_path }, - }); + })) |prev_elem| { + self.alloc.free(prev_elem.delete.old); + self.alloc.free(prev_elem.delete.new); + } try self.notification.write("Deleted item.", .info); self.directories.remove_selected(); - } else |_| { - try self.notification.write_err(.UnableToDeleteItem); + } else |err| { + switch (err) { + error.RenameAcrossMountPoints => try self.notification.write_err(.UnableToDeleteAcrossMountPoints), + else => try self.notification.write_err(.UnableToDeleteItem), + } + self.alloc.free(old_path); + self.alloc.free(tmp_path); } }, 'd' => { + self.text_input.clearAndFree(); + self.directories.cleanup(); + self.directories.populate_entries("") catch |err| { + switch (err) { + error.AccessDenied => try self.notification.write_err(.PermissionDenied), + else => try self.notification.write_err(.UnknownError), + } + }; self.state = .new_dir; }, '%' => { + self.text_input.clearAndFree(); + self.directories.cleanup(); + self.directories.populate_entries("") catch |err| { + switch (err) { + error.AccessDenied => try self.notification.write_err(.PermissionDenied), + else => try self.notification.write_err(.UnknownError), + } + }; self.state = .new_file; }, 'u' => { @@ -366,7 +389,7 @@ pub fn handle_normal_event(self: *App, event: Event, loop: *vaxis.Loop(Event)) ! pub fn handle_input_event(self: *App, event: Event) !InputReturnStatus { switch (event) { .key_press => |key| { - if ((key.codepoint == 'c' and key.mods.ctrl) or key.codepoint == 'q') { + if ((key.codepoint == 'c' and key.mods.ctrl)) { return .exit; } @@ -448,13 +471,15 @@ pub fn handle_input_event(self: *App, event: Event) !InputReturnStatus { error.PathAlreadyExists => try self.notification.write_err(.ItemAlreadyExists), else => try self.notification.write_err(.UnknownError), }; - // TODO: Will leak memory if pushing to a full stack. - self.actions.push(.{ + if (self.actions.push(.{ .rename = .{ .old = try std.fs.path.join(self.alloc, &.{ dir_prefix, old.name }), .new = try std.fs.path.join(self.alloc, &.{ dir_prefix, new }), }, - }); + })) |prev_elem| { + self.alloc.free(prev_elem.rename.old); + self.alloc.free(prev_elem.rename.new); + } self.directories.cleanup(); self.directories.populate_entries("") catch |err| { @@ -609,31 +634,27 @@ fn draw_preview(self: *App, win: vaxis.Window, file_name_win: vaxis.Window) !voi // Handle image. if (config.show_images == true) unsupported_terminal: { - const supported: [1][]const u8 = .{".png"}; - - for (supported) |ext| { - if (std.mem.eql(u8, std.fs.path.extension(entry.name), ext)) { - if (!std.mem.eql(u8, self.last_item_path, self.current_item_path)) { - if (self.vx.loadImage(self.alloc, self.tty.anyWriter(), .{ .path = self.current_item_path })) |img| { - self.image = img; - } else |_| { - self.image = null; - break :unsupported_terminal; - } - } - - if (self.image) |img| { - try img.draw(preview_win, .{ .scale = .fit }); - } - - break :file; - } else { - // Free any image we might have already. + if (!std.mem.eql(u8, self.last_item_path, self.current_item_path)) { + var image = vaxis.zigimg.Image.fromFilePath(self.alloc, self.current_item_path) catch { + break :unsupported_terminal; + }; + defer image.deinit(); + if (self.vx.transmitImage(self.alloc, self.tty.anyWriter(), &image, .rgba)) |img| { + self.image = img; + } else |_| { if (self.image) |img| { self.vx.freeImage(self.tty.anyWriter(), img.id); } + self.image = null; + break :unsupported_terminal; + } + + if (self.image) |img| { + try img.draw(preview_win, .{ .scale = .contain }); } } + + break :file; } // Handle pdf. @@ -745,7 +766,7 @@ fn draw_info(self: *App, win: vaxis.Window) !void { // Display info box. if (self.notification.len > 0) { - if (self.text_input.grapheme_count > 0) { + if (self.text_input.buf.realLength() > 0) { self.text_input.clearAndFree(); } @@ -767,7 +788,7 @@ fn draw_info(self: *App, win: vaxis.Window) !void { self.text_input.draw(info_win); }, .normal => { - if (self.text_input.grapheme_count > 0) { + if (self.text_input.buf.realLength() > 0) { self.text_input.draw(info_win); } diff --git a/src/circ_stack.zig b/src/circ_stack.zig index d017b65..8580e55 100644 --- a/src/circ_stack.zig +++ b/src/circ_stack.zig @@ -17,10 +17,14 @@ pub fn CircularStack(comptime T: type, comptime capacity: usize) type { self.count = 0; } - pub fn push(self: *Self, v: T) void { + pub fn push(self: *Self, v: T) ?T { + const prev_elem = if (self.count == capacity) self.buf[self.head] else null; + self.buf[self.head] = v; self.head = (self.head + 1) % capacity; if (self.count != capacity) self.count += 1; + + return prev_elem; } pub fn pop(self: *Self) ?T { diff --git a/src/notification.zig b/src/notification.zig index faf4b46..0209c49 100644 --- a/src/notification.zig +++ b/src/notification.zig @@ -13,6 +13,8 @@ const Error = enum { UnableToUndo, UnableToOpenFile, UnableToDeleteItem, + UnableToDeleteAcrossMountPoints, + UnsupportedImageFormat, EditorNotSet, ItemAlreadyExists, UnableToRename, @@ -41,11 +43,13 @@ pub fn write_err(self: *Self, err: Error) !void { .UnknownError => self.write("An unknown error occurred.", .err), .UnableToOpenFile => self.write("Unable to open file.", .err), .UnableToDeleteItem => self.write("Unable to delete item.", .err), + .UnableToDeleteAcrossMountPoints => self.write("Unable to move item to /tmp. Failed to delete.", .err), .UnableToUndo => self.write("Unable to undo previous action.", .err), .ItemAlreadyExists => self.write("Item already exists.", .err), .UnableToRename => self.write("Unable to rename item.", .err), .IncorrectPath => self.write("Unable to find path.", .err), .EditorNotSet => self.write("$EDITOR is not set.", .err), + .UnsupportedImageFormat => self.write("Unsupported image format.", .err), }; }