From f489fda8ea6d32441af47cc88718a1abb91125e6 Mon Sep 17 00:00:00 2001 From: brookjeynes Date: Tue, 27 Jan 2026 15:01:51 +1000 Subject: [PATCH] feat: refactor file opening logic outside of the renderer --- src/app.zig | 44 +++++-- src/commands.zig | 1 + src/drawer.zig | 261 ++++++++++++----------------------------- src/event_handlers.zig | 76 +++--------- src/events.zig | 70 ++++++----- src/notification.zig | 6 +- src/path_utils.zig | 8 ++ src/preview.zig | 70 +++++++++-- 8 files changed, 236 insertions(+), 300 deletions(-) create mode 100644 src/path_utils.zig diff --git a/src/app.zig b/src/app.zig index ce7be6d..2e46b9a 100644 --- a/src/app.zig +++ b/src/app.zig @@ -216,6 +216,13 @@ pub fn readInput(self: *App) []const u8 { } pub fn repopulateDirectory(self: *App, fuzzy: []const u8) error{OutOfMemory}!void { + // Save current selection name to restore cursor position after repopulation + const prev_name = if (self.directories.getSelected() catch null) |entry| + try self.alloc.dupe(u8, entry.name) + else + null; + defer if (prev_name) |name| self.alloc.free(name); + self.directories.clearEntries(); self.directories.populateEntries(fuzzy) catch |err| { const message = try std.fmt.allocPrint(self.alloc, "Failed to read directory entries - {}.", .{err}); @@ -223,6 +230,26 @@ pub fn repopulateDirectory(self: *App, fuzzy: []const u8) error{OutOfMemory}!voi self.notification.write(message, .err) catch {}; if (self.file_logger) |file_logger| file_logger.write(message, .err) catch {}; }; + + // Try to restore cursor to the same file by name + if (prev_name) |name| { + for (self.directories.entries.all(), 0..) |entry, i| { + if (std.mem.eql(u8, entry.name, name)) { + self.directories.entries.selected = i; + break; + } + } + } + + // Revalidate current entry for display + self.preview_cache.invalidate(); + Preview.loadPreviewForCurrentEntry(self) catch |err| { + if (self.file_logger) |file_logger| { + const msg = std.fmt.allocPrint(self.alloc, "Failed to load preview after repopulate: {}", .{err}) catch return; + defer self.alloc.free(msg); + file_logger.write(msg, .err) catch {}; + } + }; } pub fn run(self: *App) !void { @@ -237,7 +264,15 @@ pub fn run(self: *App) !void { while (!self.should_quit) { self.loop.pollEvent(); while (self.loop.tryEvent()) |event| { - if (self.directories.getSelected()) |entry| err: { + const selected = self.directories.getSelected() catch |err| err: { + const message = try std.fmt.allocPrint(self.alloc, "Can not display file - {}", .{err}); + defer self.alloc.free(message); + self.notification.write(message, .err) catch {}; + if (self.file_logger) |file_logger| file_logger.write(message, .err) catch {}; + break :err null; + }; + + if (selected) |entry| err: { @memcpy(&self.last_item_path_buf, &self.current_item_path_buf); self.last_item_path = self.last_item_path_buf[0..self.current_item_path.len]; self.current_item_path = try std.fmt.bufPrint( @@ -249,13 +284,8 @@ pub fn run(self: *App) !void { self.notification.write(message, .err) catch {}; if (self.file_logger) |file_logger| file_logger.write(message, .err) catch {}; break :err; - }, entry.?.name }, + }, entry.name }, ); - } else |err| { - const message = try std.fmt.allocPrint(self.alloc, "Can not display file - {}", .{err}); - defer self.alloc.free(message); - self.notification.write(message, .err) catch {}; - if (self.file_logger) |file_logger| file_logger.write(message, .err) catch {}; } // Global keybinds. diff --git a/src/commands.zig b/src/commands.zig index c93ecad..254a24b 100644 --- a/src/commands.zig +++ b/src/commands.zig @@ -2,6 +2,7 @@ const std = @import("std"); const App = @import("app.zig"); const environment = @import("environment.zig"); +const Preview = @import("preview.zig"); const user_config = &@import("./config.zig").config; diff --git a/src/drawer.zig b/src/drawer.zig index 81e96ed..594514a 100644 --- a/src/drawer.zig +++ b/src/drawer.zig @@ -11,6 +11,8 @@ const Git = @import("./git.zig"); const Image = @import("./image.zig"); const List = @import("./list.zig").List; const Notification = @import("./notification.zig"); +const path_utils = @import("./path_utils.zig"); +const Preview = @import("./preview.zig"); const sort = @import("./sort.zig"); const config = &@import("./config.zig").config; @@ -113,204 +115,88 @@ fn drawFilePreview( if (entry) |e| break :lbl e else return; }; - switch (entry.kind) { - .directory => { - for (app.directories.child_entries.all(), 0..) |item, i| { + const clean_name = path_utils.getCleanName(entry); + const abs_path = app.directories.fullPath(clean_name) catch { + _ = preview_win.print(&.{.{ .text = "Unable to get file path." }}, .{}); + return; + }; + + const preview_data = app.preview_cache.get(abs_path); + if (preview_data == null) { + _ = preview_win.print(&.{.{ .text = "Loading preview..." }}, .{}); + return; + } + + switch (preview_data.?.*) { + .none => { + _ = preview_win.print(&.{.{ .text = "No preview available." }}, .{}); + }, + .text, .pdf => |text| { + _ = preview_win.print(&.{.{ .text = text }}, .{}); + }, + .directory => |entries| { + for (entries.items, 0..) |item, i| { if (std.mem.startsWith(u8, item, ".") and config.show_hidden == false) { continue; } - if (i > preview_win.height) continue; + if (i >= preview_win.height) break; const w = preview_win.child(.{ .y_off = @intCast(i), .height = 1 }); w.fill(vaxis.Cell{ .style = config.styles.list_item }); _ = w.print(&.{.{ .text = item, .style = config.styles.list_item }}, .{}); } }, - .file => file: { - // Handle image. - if (config.show_images == true) unsupported: { - var match = false; - inline for (@typeInfo(vaxis.zigimg.Image.Format).@"enum".fields) |field| { - const entry_ext = std.mem.trimLeft(u8, std.fs.path.extension(entry.name), "."); - if (std.mem.eql(u8, entry_ext, field.name)) match = true; - } - if (!match) break :unsupported; - - app.images.mutex.lock(); - defer app.images.mutex.unlock(); - - if (app.images.cache.getPtr(app.current_item_path)) |cache_entry| { - if (cache_entry.status == .processing) { - _ = preview_win.print(&.{ - .{ .text = "Image still processing." }, - }, .{}); - break :file; - } - - if (cache_entry.status == .failed) { - _ = preview_win.print(&.{ - .{ .text = "Failed to process image." }, - }, .{}); - break :file; - } - - if (cache_entry.image) |img| { - img.draw(preview_win, .{ .scale = .contain }) catch |err| { - const message = try std.fmt.allocPrint(app.alloc, "Failed to draw image to screen - {}.", .{err}); - defer app.alloc.free(message); - app.notification.write(message, .err) catch {}; - if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; - - _ = preview_win.print(&.{ - .{ .text = "Failed to draw image to screen. No preview available." }, - }, .{}); - cache_entry.image = null; - break :file; - }; - } else { - if (cache_entry.data == null) { - const path = try app.alloc.dupe(u8, app.current_item_path); - Image.processImage(app.alloc, app, path) catch { - app.alloc.free(path); - break :unsupported; - }; - _ = preview_win.print(&.{ - .{ .text = "Image still processing." }, - }, .{}); - break :file; - } + .archive => |entries| { + for (entries.items, 0..) |item, i| { + if (i >= preview_win.height) break; + const w = preview_win.child(.{ .y_off = @intCast(i), .height = 1 }); + w.fill(vaxis.Cell{ .style = config.styles.list_item }); + _ = w.print(&.{.{ .text = item, .style = config.styles.list_item }}, .{}); + } + }, + .image => |img_info| { + if (!config.show_images) { + _ = preview_win.print(&.{.{ .text = "Image preview disabled." }}, .{}); + return; + } + + app.images.mutex.lock(); + defer app.images.mutex.unlock(); - if (app.vx.transmitImage(app.alloc, app.tty.writer(), &cache_entry.data.?, .rgba)) |img| { - img.draw(preview_win, .{ .scale = .contain }) catch |err| { - const message = try std.fmt.allocPrint(app.alloc, "Failed to draw image to screen - {}.", .{err}); - defer app.alloc.free(message); - app.notification.write(message, .err) catch {}; - if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; - - _ = preview_win.print(&.{ - .{ .text = "Failed to draw image to screen. No preview available." }, - }, .{}); - break :file; + if (app.images.cache.getPtr(img_info.cache_path)) |cache_entry| { + switch (cache_entry.status) { + .processing => { + _ = preview_win.print(&.{.{ .text = "Image still processing..." }}, .{}); + }, + .failed => { + _ = preview_win.print(&.{.{ .text = "Failed to process image." }}, .{}); + }, + .ready => { + if (cache_entry.image) |image| { + image.draw(preview_win, .{ .scale = .contain }) catch { + _ = preview_win.print(&.{.{ .text = "Failed to draw image." }}, .{}); + return; }; - cache_entry.image = img; - if (cache_entry.data) |data| { - var d = data; + } else if (cache_entry.data) |*data| { + if (app.vx.transmitImage(app.alloc, app.tty.writer(), data, .rgba)) |image| { + image.draw(preview_win, .{ .scale = .contain }) catch { + _ = preview_win.print(&.{.{ .text = "Failed to draw image." }}, .{}); + return; + }; + cache_entry.image = image; + var d = data.*; d.deinit(app.alloc); + cache_entry.data = null; + } else |_| { + _ = preview_win.print(&.{.{ .text = "Failed to transmit image." }}, .{}); } - cache_entry.data = null; - } else |_| { - break :unsupported; + } else { + _ = preview_win.print(&.{.{ .text = "Image processing..." }}, .{}); } - } - - break :file; - } else { - _ = preview_win.print(&.{ - .{ .text = "Processing image." }, - }, .{}); - - const path = try app.alloc.dupe(u8, app.current_item_path); - Image.processImage(app.alloc, app, path) catch { - app.alloc.free(path); - break :unsupported; - }; - } - - break :file; - } - - // Handle pdf. - if (std.mem.eql(u8, std.fs.path.extension(entry.name), ".pdf")) { - const output = std.process.Child.run(.{ - .allocator = app.alloc, - .argv = &[_][]const u8{ - "pdftotext", - "-f", - "0", - "-l", - "5", - app.current_item_path, - "-", }, - .cwd_dir = app.directories.dir, - }) catch { - _ = preview_win.print(&.{.{ - .text = "No preview available. Install pdftotext to get PDF previews.", - }}, .{}); - break :file; - }; - defer app.alloc.free(output.stderr); - defer app.alloc.free(output.stdout); - - if (output.term.Exited != 0) { - _ = preview_win.print(&.{.{ - .text = "No preview available. Install pdftotext to get PDF previews.", - }}, .{}); - break :file; - } - - if (app.directories.pdf_contents) |contents| app.alloc.free(contents); - app.directories.pdf_contents = try app.alloc.dupe(u8, output.stdout); - - _ = preview_win.print(&.{ - .{ .text = app.directories.pdf_contents.? }, - }, .{}); - break :file; - } - - // Handle archives - if (Archive.ArchiveType.fromPath(entry.name)) |archive_type| { - if (app.archive_files) |*files| { - files.deinit(app.alloc); - app.archive_files = null; - } - - if (app.directories.file.handle) |file| { - app.archive_files = Archive.listArchiveContents( - app.alloc, - file, - archive_type, - config.archive_traversal_limit, - ) catch |err| { - const message = try std.fmt.allocPrint(app.alloc, "Failed to read archive: {s}", .{@errorName(err)}); - defer app.alloc.free(message); - app.notification.write(message, .err) catch {}; - if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; - _ = preview_win.print(&.{.{ .text = "Failed to read archive." }}, .{}); - break :file; - }; - } - - if (config.sort_dirs) { - std.mem.sort([]const u8, app.archive_files.?.entries.items, {}, sort.string); - } - - for (app.archive_files.?.entries.items, 0..) |path, i| { - if (i >= preview_win.height) break; - const w = preview_win.child(.{ .y_off = @intCast(i), .height = 1 }); - w.fill(vaxis.Cell{ .style = config.styles.list_item }); - _ = w.print(&.{.{ .text = path, .style = config.styles.list_item }}, .{}); - } - break :file; - } - - // Handle utf-8. - if (app.directories.file.bytes_read > 0) { - const file_contents = app.directories.file.data[0..app.directories.file.bytes_read]; - if (std.unicode.utf8ValidateSlice(file_contents)) { - _ = preview_win.print(&.{ - .{ .text = file_contents }, - }, .{}); - break :file; } + } else { + _ = preview_win.print(&.{.{ .text = "Image not found in cache." }}, .{}); } - - // Fallback to no preview. - _ = preview_win.print(&.{.{ .text = "No preview available." }}, .{}); - }, - else => { - _ = preview_win.print(&.{ - vaxis.Segment{ .text = app.current_item_path }, - }, .{}); }, } } @@ -355,7 +241,8 @@ fn drawFileInfo( if (entry.kind == .directory) { maybe_meta = directories.dir.stat() catch break :lbl; } else if (entry.kind == .file) { - var file = directories.dir.openFile(entry.name, .{}) catch break :lbl; + const clean_name = path_utils.getCleanName(entry); + var file = directories.dir.openFile(clean_name, .{}) catch break :lbl; maybe_meta = file.stat() catch break :lbl; } @@ -395,7 +282,8 @@ fn drawFileInfo( "r--", "r-x", "rw-", "rwx", }; - const stat = directories.dir.statFile(entry.name) catch { + const clean_name = path_utils.getCleanName(entry); + const stat = directories.dir.statFile(clean_name) catch { _ = try file_perm_fbs.write("---------\n"); break :lbl 10; }; @@ -424,13 +312,14 @@ fn drawFileInfo( // Size. const size: ?usize = lbl: { - const stat = directories.dir.statFile(entry.name) catch break :lbl null; + const clean_name = path_utils.getCleanName(entry); + const stat = directories.dir.statFile(clean_name) catch break :lbl null; if (entry.kind == .file) { break :lbl stat.size; } else if (entry.kind == .directory) { if (config.true_dir_size) { var dir = directories.dir.openDir( - entry.name, + clean_name, .{ .iterate = true }, ) catch break :lbl null; defer dir.close(); diff --git a/src/event_handlers.zig b/src/event_handlers.zig index e39f6aa..2fa8a43 100644 --- a/src/event_handlers.zig +++ b/src/event_handlers.zig @@ -9,6 +9,7 @@ const commands = @import("./commands.zig"); const Keybinds = @import("./config.zig").Keybinds; const environment = @import("./environment.zig"); const events = @import("./events.zig"); +const Preview = @import("./preview.zig"); const config = &@import("./config.zig").config; @@ -93,7 +94,6 @@ pub fn handleNormalEvent( app.text_input.insertSliceAtCursor(entry.name) catch {}; app.state = .rename; }, - .create_dir => { try app.repopulateDirectory(""); app.text_input.clearAndFree(); @@ -117,8 +117,16 @@ pub fn handleNormalEvent( app.text_input.insertSliceAtCursor(":") catch {}; app.state = .command; }, - .jump_bottom => app.directories.entries.selectLast(), - .jump_top => app.directories.entries.selectFirst(), + .jump_bottom => { + app.directories.entries.selectLast(); + app.preview_cache.invalidate(); + Preview.loadPreviewForCurrentEntry(app) catch {}; + }, + .jump_top => { + app.directories.entries.selectFirst(); + app.preview_cache.invalidate(); + Preview.loadPreviewForCurrentEntry(app) catch {}; + }, .toggle_verbose_file_information => app.drawer.verbose = !app.drawer.verbose, .force_delete => try events.forceDelete(app), .yank => try events.yank(app), @@ -129,65 +137,13 @@ pub fn handleNormalEvent( switch (key.codepoint) { '-', 'h', Key.left => try events.traverseLeft(app), Key.enter, 'l', Key.right => try events.traverseRight(app), - 'j', 'k', Key.down, Key.up => { - switch (key.codepoint) { - 'j', Key.down => app.directories.entries.next(), - 'k', Key.up => app.directories.entries.previous(), - else => {}, - } - - if (app.directories.entries.len() == 0 or !config.preview_file) return; - const entry = (app.directories.getSelected() catch |err| { - const message = try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err}); - defer app.alloc.free(message); - app.notification.write(message, .err) catch {}; - if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; - return; - }) orelse return; - - switch (entry.kind) { - .directory => { - app.directories.clearChildEntries(); - app.directories.populateChildEntries(entry.name) catch |err| { - const message = try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err}); - defer app.alloc.free(message); - app.notification.write(message, .err) catch {}; - if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; - }; - }, - .file => { - if (!std.mem.eql(u8, app.last_item_path, app.current_item_path)) { - if (app.directories.file.handle) |*previous_file| { - previous_file.close(); - } - - var file = app.directories.dir.openFile( - entry.name, - .{ .mode = .read_only }, - ) catch |err| { - const message = try std.fmt.allocPrint(app.alloc, "Failed to open file - {}.", .{err}); - defer app.alloc.free(message); - app.notification.write("Failed to open file. No preview available.", .err) catch {}; - if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; - return; - }; - const bytes = file.readAll(&app.directories.file.data) catch |err| { - const message = try std.fmt.allocPrint(app.alloc, "Failed to read file contents - {}.", .{err}); - defer app.alloc.free(message); - app.notification.write("Failed to read file contents. No preview available.", .err) catch {}; - if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; - return; - }; - app.directories.file.handle = file; - app.directories.file.bytes_read = bytes; - } - }, - else => {}, - } - }, + 'j', Key.down => app.directories.entries.next(), + 'k', Key.up => app.directories.entries.previous(), 'u' => try events.undo(app), else => {}, } + app.preview_cache.invalidate(); + Preview.loadPreviewForCurrentEntry(app) catch {}; } }, .image_ready => {}, @@ -214,7 +170,6 @@ pub fn handleInputEvent(app: *App, event: App.Event) !void { app.state = .normal; }, Key.enter => { - const selected = app.directories.entries.selected; switch (app.state) { .new_dir => try events.createNewDir(app), .new_file => try events.createNewFile(app), @@ -282,7 +237,6 @@ pub fn handleInputEvent(app: *App, event: App.Event) !void { } if (app.state != .help_menu) app.state = .normal; - app.directories.entries.selected = selected; }, Key.up => { if (app.state == .command) { diff --git a/src/events.zig b/src/events.zig index f1e6bc6..1d7b7e2 100644 --- a/src/events.zig +++ b/src/events.zig @@ -6,6 +6,8 @@ const zuid = @import("zuid"); const App = @import("./app.zig"); const Archive = @import("./archive.zig"); const environment = @import("./environment.zig"); +const path_utils = @import("./path_utils.zig"); +const Preview = @import("./preview.zig"); const config = &@import("./config.zig").config; @@ -17,9 +19,10 @@ pub fn delete(app: *App) error{OutOfMemory}!void { app.notification.write("Can not to delete item - no item selected.", .warn) catch {}; return; }) orelse return; + const clean_name = path_utils.getCleanName(entry); var prev_path_buf: [std.fs.max_path_bytes]u8 = undefined; - const prev_path = app.directories.dir.realpath(entry.name, &prev_path_buf) catch { + const prev_path = app.directories.dir.realpath(clean_name, &prev_path_buf) catch { message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - unable to retrieve absolute path.", .{entry.name}); app.notification.write(message.?, .err) catch {}; if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; @@ -53,8 +56,8 @@ pub fn delete(app: *App) error{OutOfMemory}!void { return; } - const tmp_path = try std.fmt.allocPrint(app.alloc, "{s}/{s}-{f}", .{ trash_dir_path, entry.name, zuid.new.v4() }); - if (app.directories.dir.rename(entry.name, tmp_path)) { + const tmp_path = try std.fmt.allocPrint(app.alloc, "{s}/{s}-{f}", .{ trash_dir_path, clean_name, zuid.new.v4() }); + if (app.directories.dir.rename(clean_name, tmp_path)) { if (app.actions.push(.{ .delete = .{ .prev_path = prev_path_alloc, .new_path = tmp_path }, })) |prev_elem| { @@ -65,6 +68,7 @@ pub fn delete(app: *App) error{OutOfMemory}!void { app.notification.write(message.?, .info) catch {}; app.directories.removeSelected(); + Preview.loadPreviewForCurrentEntry(app) catch {}; } else |err| { app.alloc.free(prev_path_alloc); app.alloc.free(tmp_path); @@ -115,7 +119,35 @@ pub fn rename(app: *App) error{OutOfMemory}!void { app.alloc.free(prev_elem.rename.new_path); } - try app.repopulateDirectory(""); + app.directories.clearEntries(); + app.directories.populateEntries("") catch |err| { + const m = try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err}); + defer app.alloc.free(m); + app.notification.write(m, .err) catch {}; + if (app.file_logger) |file_logger| file_logger.write(m, .err) catch {}; + }; + + const target_name = if (entry.kind == .directory) + try std.fmt.allocPrint(app.alloc, "{s}/", .{new_path}) + else + new_path; + defer if (entry.kind == .directory) app.alloc.free(target_name); + + for (app.directories.entries.all(), 0..) |e, i| { + if (std.mem.eql(u8, e.name, target_name)) { + app.directories.entries.selected = i; + break; + } + } + + // No need to revalidate cache as we're viewing the same file + Preview.loadPreviewForCurrentEntry(app) catch |err| { + if (app.file_logger) |file_logger| { + const msg = std.fmt.allocPrint(app.alloc, "Failed to load preview after repopulate: {}", .{err}) catch return; + defer app.alloc.free(msg); + file_logger.write(msg, .err) catch {}; + } + }; message = try std.fmt.allocPrint(app.alloc, "Renamed '{s}' to '{s}'.", .{ entry.name, new_path }); app.notification.write(message.?, .info) catch {}; @@ -140,24 +172,8 @@ pub fn forceDelete(app: *App) error{OutOfMemory}!void { pub fn toggleHiddenFiles(app: *App) error{OutOfMemory}!void { config.show_hidden = !config.show_hidden; - const prev_selected_name: []const u8, const prev_selected_err: bool = lbl: { - const selected = app.directories.getSelected() catch break :lbl .{ "", true }; - if (selected == null) break :lbl .{ "", true }; - - break :lbl .{ try app.alloc.dupe(u8, selected.?.name), false }; - }; - defer if (!prev_selected_err) app.alloc.free(prev_selected_name); - try app.repopulateDirectory(""); app.text_input.clearAndFree(); - - for (app.directories.entries.all()) |entry| { - if (std.mem.eql(u8, entry.name, prev_selected_name)) return; - app.directories.entries.selected += 1; - } - - // If it didn't find entry, reset selected. - app.directories.entries.selected = 0; } pub fn yank(app: *App) error{OutOfMemory}!void { @@ -497,8 +513,6 @@ pub fn undo(app: *App) error{OutOfMemory}!void { return; }; - const selected = app.directories.entries.selected; - switch (action) { .delete => |a| { defer app.alloc.free(a.new_path); @@ -519,9 +533,6 @@ pub fn undo(app: *App) error{OutOfMemory}!void { return; }; - try app.repopulateDirectory(""); - app.text_input.clearAndFree(); - message = try std.fmt.allocPrint(app.alloc, "Restored '{s}' as '{s}'.", .{ a.prev_path, new_path_res.path }); app.notification.write(message.?, .info) catch {}; }, @@ -544,9 +555,6 @@ pub fn undo(app: *App) error{OutOfMemory}!void { return; }; - try app.repopulateDirectory(""); - app.text_input.clearAndFree(); - message = try std.fmt.allocPrint(app.alloc, "Reverted renaming of '{s}', now '{s}'.", .{ a.new_path, new_path_res.path }); app.notification.write(message.?, .info) catch {}; }, @@ -559,13 +567,11 @@ pub fn undo(app: *App) error{OutOfMemory}!void { if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; return; }; - - try app.repopulateDirectory(""); - app.text_input.clearAndFree(); }, } - app.directories.entries.selected = selected; + try app.repopulateDirectory(""); + app.text_input.clearAndFree(); } pub fn extractArchive(app: *App) error{OutOfMemory}!void { diff --git a/src/notification.zig b/src/notification.zig index ea0cd4b..fcec962 100644 --- a/src/notification.zig +++ b/src/notification.zig @@ -30,9 +30,9 @@ pub fn write(self: *Self, text: []const u8, style: Style) !void { self.timer = std.time.timestamp(); self.style = style; - // if (self.loop) |loop| { - // loop.postEvent(.notification); - // } + if (self.loop) |loop| { + loop.postEvent(.notification); + } } pub fn reset(self: *Self) void { diff --git a/src/path_utils.zig b/src/path_utils.zig new file mode 100644 index 0000000..d6c3f51 --- /dev/null +++ b/src/path_utils.zig @@ -0,0 +1,8 @@ +const std = @import("std"); + +pub fn getCleanName(entry: std.fs.Dir.Entry) []const u8 { + if (entry.kind == .directory and entry.name.len > 0 and entry.name[entry.name.len - 1] == '/') { + return entry.name[0 .. entry.name.len - 1]; + } + return entry.name; +} diff --git a/src/preview.zig b/src/preview.zig index 9d6f7bf..7beab66 100644 --- a/src/preview.zig +++ b/src/preview.zig @@ -3,6 +3,7 @@ const std = @import("std"); const App = @import("./app.zig"); const Archive = @import("./archive.zig"); const Image = @import("./image.zig"); +const path_utils = @import("./path_utils.zig"); const config = &@import("./config.zig").config; pub const PreviewType = enum { @@ -76,6 +77,31 @@ pub const PreviewCache = struct { self.current = null; } + pub fn updatePath(self: *PreviewCache, app: *App, old_path: []const u8, new_path: []const u8) error{OutOfMemory}!void { + if (self.current) |*entry| { + if (std.mem.eql(u8, entry.file_path, old_path)) { + if (entry.preview == .image) { + app.images.mutex.lock(); + defer app.images.mutex.unlock(); + + if (app.images.cache.fetchRemove(old_path)) |kv| { + app.images.cache.put(new_path, kv.value) catch |err| { + kv.value.deinit(app.alloc, app.vx, &app.tty); + self.clear(); + return err; + }; + } + + self.alloc.free(entry.preview.image.cache_path); + entry.preview.image.cache_path = try self.alloc.dupe(u8, new_path); + } + + self.alloc.free(entry.file_path); + entry.file_path = try self.alloc.dupe(u8, new_path); + } + } + } + pub fn get(self: *PreviewCache, path: []const u8) ?*const PreviewData { if (self.current) |*entry| { if (entry.is_valid and std.mem.eql(u8, entry.file_path, path)) { @@ -101,9 +127,10 @@ pub fn loadPreviewForCurrentEntry(app: *App) !void { const entry = (try app.directories.getSelected()) orelse return; + const clean_name = path_utils.getCleanName(entry); const path = try app.directories.dir.realpathAlloc( app.alloc, - entry.name, + clean_name, ); defer app.alloc.free(path); @@ -123,7 +150,8 @@ pub fn loadPreviewForCurrentEntry(app: *App) !void { fn loadDirectoryPreview(app: *App, entry: std.fs.Dir.Entry) !PreviewData { app.directories.clearChildEntries(); - app.directories.populateChildEntries(entry.name) catch |err| { + const clean_name = path_utils.getCleanName(entry); + app.directories.populateChildEntries(clean_name) catch |err| { const message = try std.fmt.allocPrint( app.alloc, "Failed to read directory entries - {}.", @@ -137,10 +165,10 @@ fn loadDirectoryPreview(app: *App, entry: std.fs.Dir.Entry) !PreviewData { return PreviewData{ .none = {} }; }; - var list = std.ArrayList([]const u8).init(app.alloc); + var list: std.ArrayList([]const u8) = .empty; for (app.directories.child_entries.all()) |child| { const owned = try app.alloc.dupe(u8, child); - try list.append(owned); + try list.append(app.alloc, owned); } return PreviewData{ .directory = list }; @@ -167,8 +195,9 @@ fn loadFilePreview(app: *App, entry: std.fs.Dir.Entry) !PreviewData { } fn loadTextPreview(app: *App, entry: std.fs.Dir.Entry) !PreviewData { + const clean_name = path_utils.getCleanName(entry); var file = app.directories.dir.openFile( - entry.name, + clean_name, .{ .mode = .read_only }, ) catch |err| { const message = try std.fmt.allocPrint( @@ -209,9 +238,10 @@ fn loadTextPreview(app: *App, entry: std.fs.Dir.Entry) !PreviewData { } fn loadImagePreview(app: *App, entry: std.fs.Dir.Entry) !PreviewData { + const clean_name = path_utils.getCleanName(entry); const path = try app.directories.dir.realpathAlloc( app.alloc, - entry.name, + clean_name, ); defer app.alloc.free(path); @@ -235,9 +265,10 @@ fn loadImagePreview(app: *App, entry: std.fs.Dir.Entry) !PreviewData { } fn loadPdfPreview(app: *App, entry: std.fs.Dir.Entry) !PreviewData { + const clean_name = path_utils.getCleanName(entry); const path = try app.directories.dir.realpathAlloc( app.alloc, - entry.name, + clean_name, ); defer app.alloc.free(path); @@ -274,8 +305,9 @@ fn loadArchivePreview( entry: std.fs.Dir.Entry, archive_type: Archive.ArchiveType, ) !PreviewData { + const clean_name = path_utils.getCleanName(entry); var file = app.directories.dir.openFile( - entry.name, + clean_name, .{ .mode = .read_only }, ) catch |err| { const message = try std.fmt.allocPrint( @@ -326,9 +358,25 @@ fn loadArchivePreview( fn isImageExtension(ext: []const u8) bool { const supported = [_][]const u8{ - ".png", ".jpg", ".jpeg", ".gif", - ".bmp", ".tga", ".qoi", ".pam", - ".pbm", ".pgm", ".ppm", + ".bmp", + ".farbfeld", + ".gif", + ".iff", + ".ilbm", + ".jpeg", + ".jpg", + ".pam", + ".pbm", + ".pcx", + ".pgm", + ".png", + ".ppm", + ".qoi", + ".ras", + ".sgi", + ".tga", + ".tif", + ".tiff", }; for (supported) |supported_ext| { -- 2.51.2