地圖 (Jido) is a lightweight Unix TUI file explorer designed for speed and simplicity.

refactor: audit `try` usage to improve system resiliance (#18)

* refactor: Audit try usage

* fix: Ensure error notifications are called with `.err`.

* refactor: Removed need for enum based notifications.

* fix: Ensure complete branch is displayed.

* chore: Update changelog.

* refactor: Fix warning message wording.

* docs: update changelog.

authored by brookjeynes.dev and committed by GitHub f3ee77e5 5087a7b3

+5
CHANGELOG.md
··· 1 1 # Changelog 2 2 3 + ## v0.9.8 (2025-04-04) 4 + - fix: Ensure complete Git branch is displayed. 5 + - refactor: Audit try usage to improve system resiliance. 6 + - refactor: Removed need for enum based notifications. 7 + 3 8 ## v0.9.7 (2025-04-01) 4 9 - feat: Added ability to copy folders. 5 10 This is done by (y)anking the file, then (p)asting in the desired directory.
+1 -1
PROJECT_BOARD.md
··· 16 16 - [x] Ability to unbind keys. 17 17 18 18 ### Refactors 19 - - [ ] Better error logging. 19 + - [x] Better error logging. 20 20 There are many places errors could be caught, logged, and handled instead 21 21 of crashing. 22 22 - [ ] Improve image reading.
+1 -1
build.zig
··· 2 2 const builtin = @import("builtin"); 3 3 4 4 ///Must match the `version` in `build.zig.zon`. 5 - const version = std.SemanticVersion{ .major = 0, .minor = 9, .patch = 7 }; 5 + const version = std.SemanticVersion{ .major = 0, .minor = 9, .patch = 8 }; 6 6 7 7 const targets: []const std.Target.Query = &.{ 8 8 .{ .cpu_arch = .aarch64, .os_tag = .macos },
+1 -1
build.zig.zon
··· 1 1 .{ 2 2 .name = .jido, 3 3 .fingerprint = 0xee45eabe36cafb57, 4 - .version = "0.9.7", 4 + .version = "0.9.8", 5 5 .minimum_zig_version = "0.14.0", 6 6 7 7 .dependencies = .{
+54 -9
src/app.zig
··· 162 162 if (self.file_logger) |file_logger| file_logger.deinit(); 163 163 } 164 164 165 + pub fn inputToSlice(self: *App) []const u8 { 166 + self.text_input.buf.cursor = self.text_input.buf.realLength(); 167 + return self.text_input.sliceToCursor(&self.text_input_buf); 168 + } 169 + 170 + pub fn repopulateDirectory(self: *App, fuzzy: []const u8) error{OutOfMemory}!void { 171 + self.directories.clearEntries(); 172 + self.directories.populateEntries(fuzzy) catch |err| { 173 + const message = try std.fmt.allocPrint(self.alloc, "Failed to read directory entries - {}.", .{err}); 174 + defer self.alloc.free(message); 175 + self.notification.write(message, .err) catch {}; 176 + if (self.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 177 + }; 178 + } 179 + 165 180 pub fn run(self: *App) !void { 166 - try self.directories.populateEntries(""); 181 + try self.repopulateDirectory(""); 167 182 168 183 var loop: vaxis.Loop(Event) = .{ 169 184 .vaxis = &self.vx, ··· 188 203 189 204 if ((key.codepoint == 'r' and key.mods.ctrl)) { 190 205 if (config.parse(self.alloc, self)) { 191 - try self.notification.writeInfo(.ConfigReloaded); 206 + self.notification.write("Reloaded configuration file.", .info) catch {}; 192 207 } else |err| switch (err) { 193 208 error.SyntaxError => { 194 - try self.notification.writeErr(.ConfigSyntaxError); 209 + self.notification.write("Encountered a syntax error while parsing the config file.", .err) catch { 210 + std.log.err("Encountered a syntax error while parsing the config file.", .{}); 211 + }; 195 212 }, 196 213 error.InvalidCharacter => { 197 - try self.notification.writeErr(.InvalidKeybind); 214 + self.notification.write("One or more overriden keybinds are invalid.", .err) catch { 215 + std.log.err("One or more overriden keybinds are invalid.", .{}); 216 + }; 198 217 }, 199 218 error.DuplicateKeybind => { 200 219 // Error logged in function 201 220 }, 202 221 else => { 203 - try self.notification.writeErr(.ConfigUnknownError); 222 + const message = try std.fmt.allocPrint(self.alloc, "Encountend an unknown error while parsing the config file - {}", .{err}); 223 + defer self.alloc.free(message); 224 + 225 + self.notification.write(message, .err) catch { 226 + std.log.err("Encountend an unknown error while parsing the config file - {}", .{err}); 227 + }; 204 228 }, 205 229 } 206 230 } ··· 230 254 } 231 255 232 256 if (config.empty_trash_on_exit) { 233 - if (try config.trashDir()) |dir| { 234 - var trash_dir = dir; 235 - defer trash_dir.close(); 236 - _ = try environment.deleteContents(trash_dir); 257 + var trash_dir = dir: { 258 + notfound: { 259 + break :dir (config.trashDir() catch break :notfound) orelse break :notfound; 260 + } 261 + if (self.file_logger) |file_logger| file_logger.write("Failed to open trash directory.", .err) catch { 262 + std.log.err("Failed to open trash directory.", .{}); 263 + }; 264 + return; 265 + }; 266 + defer trash_dir.close(); 267 + 268 + const failed = environment.deleteContents(trash_dir) catch |err| { 269 + const message = try std.fmt.allocPrint(self.alloc, "Failed to empty trash - {}.", .{err}); 270 + defer self.alloc.free(message); 271 + if (self.file_logger) |file_logger| file_logger.write(message, .err) catch { 272 + std.log.err("Failed to empty trash - {}.", .{err}); 273 + }; 274 + return; 275 + }; 276 + if (failed > 0) { 277 + const message = try std.fmt.allocPrint(self.alloc, "Failed to empty {d} items from the trash.", .{failed}); 278 + defer self.alloc.free(message); 279 + if (self.file_logger) |file_logger| file_logger.write(message, .err) catch { 280 + std.log.err("Failed to empty {d} items from the trash.", .{failed}); 281 + }; 237 282 } 238 283 } 239 284 }
+59 -62
src/commands.zig
··· 1 1 const std = @import("std"); 2 2 const App = @import("app.zig"); 3 3 const environment = @import("environment.zig"); 4 - const _config = &@import("./config.zig").config; 4 + const user_config = &@import("./config.zig").config; 5 5 6 6 pub const CommandHistory = struct { 7 7 const history_len = 10; ··· 45 45 }; 46 46 47 47 ///Navigate the user to the config dir. 48 - pub fn config(app: *App) !void { 48 + pub fn config(app: *App) error{OutOfMemory}!void { 49 49 const dir = dir: { 50 50 notfound: { 51 - break :dir (_config.configDir() catch break :notfound) orelse break :notfound; 51 + break :dir (user_config.configDir() catch break :notfound) orelse break :notfound; 52 52 } 53 - try app.notification.writeErr(.ConfigPathNotFound); 53 + const message = try std.fmt.allocPrint(app.alloc, "Failed to navigate to config directory - unable to retrieve config directory.", .{}); 54 + defer app.alloc.free(message); 55 + app.notification.write(message, .err) catch {}; 56 + if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 54 57 return; 55 58 }; 56 - app.directories.clearEntries(); 59 + 57 60 app.directories.dir.close(); 58 61 app.directories.dir = dir; 59 - app.directories.populateEntries("") catch |err| { 60 - switch (err) { 61 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 62 - else => try app.notification.writeErr(.UnknownError), 63 - } 64 - }; 62 + try app.repopulateDirectory(""); 65 63 } 66 64 67 65 ///Navigate the user to the trash dir. 68 - pub fn trash(app: *App) !void { 66 + pub fn trash(app: *App) error{OutOfMemory}!void { 69 67 const dir = dir: { 70 68 notfound: { 71 - break :dir (_config.trashDir() catch break :notfound) orelse break :notfound; 69 + break :dir (user_config.trashDir() catch break :notfound) orelse break :notfound; 72 70 } 73 - try app.notification.writeErr(.ConfigPathNotFound); 71 + const message = try std.fmt.allocPrint(app.alloc, "Failed to navigate to trash directory - unable to retrieve trash directory.", .{}); 72 + defer app.alloc.free(message); 73 + app.notification.write(message, .err) catch {}; 74 + if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 74 75 return; 75 76 }; 76 - app.directories.clearEntries(); 77 + 77 78 app.directories.dir.close(); 78 79 app.directories.dir = dir; 79 - app.directories.populateEntries("") catch |err| { 80 - switch (err) { 81 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 82 - else => try app.notification.writeErr(.UnknownError), 83 - } 84 - }; 80 + try app.repopulateDirectory(""); 85 81 } 86 82 87 83 ///Empty the trash. 88 - pub fn emptyTrash(app: *App) !void { 89 - const dir = dir: { 84 + pub fn emptyTrash(app: *App) error{OutOfMemory}!void { 85 + var message: ?[]const u8 = null; 86 + defer if (message) |msg| app.alloc.free(msg); 87 + 88 + var dir = dir: { 90 89 notfound: { 91 - break :dir (_config.trashDir() catch break :notfound) orelse break :notfound; 90 + break :dir (user_config.trashDir() catch break :notfound) orelse break :notfound; 92 91 } 93 - try app.notification.writeErr(.ConfigPathNotFound); 92 + message = try std.fmt.allocPrint(app.alloc, "Failed to navigate to trash directory - unable to retrieve trash directory.", .{}); 93 + app.notification.write(message.?, .err) catch {}; 94 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 94 95 return; 95 96 }; 97 + defer dir.close(); 96 98 97 - var trash_dir = dir; 98 - defer trash_dir.close(); 99 - const failed = try environment.deleteContents(trash_dir); 100 - if (failed > 0) try app.notification.writeErr(.FailedToDeleteSomeItems); 99 + const failed = environment.deleteContents(dir) catch |err| lbl: { 100 + message = try std.fmt.allocPrint(app.alloc, "Failed to empty trash - {}.", .{err}); 101 + app.notification.write(message.?, .err) catch {}; 102 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 103 + break :lbl 0; 104 + }; 105 + if (failed > 0) { 106 + message = try std.fmt.allocPrint(app.alloc, "Failed to empty {d} items from the trash.", .{failed}); 107 + app.notification.write(message.?, .err) catch {}; 108 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 109 + } 101 110 102 - app.directories.clearEntries(); 103 - app.directories.populateEntries("") catch |err| { 104 - switch (err) { 105 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 106 - else => try app.notification.writeErr(.UnknownError), 107 - } 108 - }; 111 + try app.repopulateDirectory(""); 109 112 } 110 113 111 114 ///Change directory. 112 - pub fn cd(app: *App, path: []const u8) !void { 115 + pub fn cd(app: *App, path: []const u8) error{OutOfMemory}!void { 116 + var message: ?[]const u8 = null; 117 + defer if (message) |msg| app.alloc.free(msg); 118 + 113 119 var path_buf: [std.fs.max_path_bytes]u8 = undefined; 114 120 const resolved_path = lbl: { 115 121 const resolved_path = if (std.mem.startsWith(u8, path, "~")) path: { ··· 125 131 break :lbl app.directories.dir.realpath(resolved_path, &path_buf) catch path; 126 132 }; 127 133 128 - if (app.directories.dir.openDir(resolved_path, .{ .iterate = true })) |dir| { 129 - app.directories.dir.close(); 130 - app.directories.dir = dir; 131 - 132 - try app.notification.writeInfo(.ChangedDir); 133 - 134 - app.directories.clearEntries(); 135 - app.directories.populateEntries("") catch |err| { 136 - switch (err) { 137 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 138 - else => try app.notification.writeErr(.UnknownError), 139 - } 134 + const dir = app.directories.dir.openDir(resolved_path, .{ .iterate = true }) catch |err| { 135 + message = switch (err) { 136 + error.FileNotFound => try std.fmt.allocPrint(app.alloc, "Failed to navigate to '{s}' - directory does not exist.", .{resolved_path}), 137 + error.NotDir => try std.fmt.allocPrint(app.alloc, "Failed to navigate to '{s}' - item is not a directory.", .{resolved_path}), 138 + else => try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err}), 140 139 }; 141 - app.directories.history.reset(); 142 - } else |err| { 143 - switch (err) { 144 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 145 - error.FileNotFound => try app.notification.writeErr(.IncorrectPath), 146 - error.NotDir => try app.notification.writeErr(.NotADir), 147 - else => try app.notification.writeErr(.UnknownError), 148 - } 149 - } 150 - } 140 + app.notification.write(message.?, .err) catch {}; 141 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 142 + return; 143 + }; 144 + app.directories.dir.close(); 145 + app.directories.dir = dir; 146 + 147 + message = try std.fmt.allocPrint(app.alloc, "Navigated to directory '{s}'.", .{resolved_path}); 148 + app.notification.write(message.?, .info) catch {}; 151 149 152 - ///Display help menu. 153 - pub fn displayHelpMenu(app: *App) !void { 154 - app.state = .help_menu; 150 + try app.repopulateDirectory(""); 151 + app.directories.history.reset(); 155 152 }
-2
src/config.zig
··· 18 18 preview_file: bool = true, 19 19 empty_trash_on_exit: bool = false, 20 20 true_dir_size: bool = false, 21 - // TODO(10-01-25): This needs to be implemented. 22 - // command_history_len: usize = 10, 23 21 styles: Styles = .{}, 24 22 keybinds: Keybinds = .{}, 25 23
+109 -75
src/drawer.zig
··· 1 1 const std = @import("std"); 2 2 const App = @import("./app.zig"); 3 + const FileLogger = @import("./file_logger.zig"); 3 4 const Notification = @import("./notification.zig"); 4 5 const Directories = @import("./directories.zig"); 5 6 const config = &@import("./config.zig").config; 6 7 const vaxis = @import("vaxis"); 7 8 const Git = @import("./git.zig"); 8 9 const List = @import("./list.zig").List; 9 - const inputToSlice = @import("./event_handlers.zig").inputToSlice; 10 10 const zeit = @import("zeit"); 11 11 12 12 const Drawer = @This(); ··· 24 24 git_branch: [1024]u8 = undefined, 25 25 verbose: bool = false, 26 26 27 - pub fn draw(self: *Drawer, app: *App) !void { 27 + pub fn draw(self: *Drawer, app: *App) error{ OutOfMemory, NoSpaceLeft }!void { 28 28 const win = app.vx.window(); 29 29 win.clear(); 30 30 ··· 48 48 return; 49 49 } 50 50 51 - const abs_file_path_bar = try self.drawAbsFilePath(app.alloc, &app.directories, win); 51 + const abs_file_path_bar = try self.drawAbsFilePath(app, win); 52 52 const file_info_bar = try self.drawFileInfo(app.alloc, &app.directories, win); 53 - app.last_known_height = try drawDirList( 53 + app.last_known_height = drawDirList( 54 54 win, 55 55 app.directories.entries, 56 56 abs_file_path_bar, ··· 62 62 try self.drawFilePreview(app, win, file_name_bar); 63 63 } 64 64 65 - const input = inputToSlice(app); 66 - try drawUserInput(app.state, &app.text_input, input, win); 65 + const input = app.inputToSlice(); 66 + drawUserInput(app.state, &app.text_input, input, win); 67 67 68 68 // Notification should be drawn last. 69 - try drawNotification(&app.notification, win); 69 + drawNotification(&app.notification, &app.file_logger, win); 70 70 } 71 71 72 72 fn drawFileName( 73 73 self: *Drawer, 74 74 directories: *Directories, 75 75 win: vaxis.Window, 76 - ) !vaxis.Window { 76 + ) error{NoSpaceLeft}!vaxis.Window { 77 77 const file_name_bar = win.child(.{ 78 78 .x_off = win.width / 2, 79 79 .y_off = 0, ··· 81 81 .height = top_div, 82 82 }); 83 83 84 - lbl: { 85 - const entry = directories.getSelected() catch break :lbl; 86 - if (entry) |e| { 87 - const file_name = try std.fmt.bufPrint( 88 - &self.file_name_buf, 89 - "[{s}]", 90 - .{e.name}, 91 - ); 92 - _ = file_name_bar.print(&.{vaxis.Segment{ 93 - .text = file_name, 94 - .style = config.styles.file_name, 95 - }}, .{}); 96 - } 97 - } 84 + const entry = lbl: { 85 + const entry = directories.getSelected() catch return file_name_bar; 86 + if (entry) |e| break :lbl e else return file_name_bar; 87 + }; 88 + 89 + const file_name = try std.fmt.bufPrint(&self.file_name_buf, "[{s}]", .{entry.name}); 90 + _ = file_name_bar.printSegment(.{ .text = file_name, .style = config.styles.file_name }, .{}); 98 91 99 92 return file_name_bar; 100 93 } ··· 104 97 app: *App, 105 98 win: vaxis.Window, 106 99 file_name_win: vaxis.Window, 107 - ) !void { 100 + ) error{ OutOfMemory, NoSpaceLeft }!void { 108 101 const bottom_div: u16 = 1; 109 102 110 103 const preview_win = win.child(.{ ··· 126 119 self.current_item_path = try std.fmt.bufPrint( 127 120 &self.current_item_path_buf, 128 121 "{s}/{s}", 129 - .{ try app.directories.fullPath("."), entry.name }, 122 + .{ app.directories.fullPath(".") catch { 123 + const message = try std.fmt.allocPrint(app.alloc, "Can not display file - unable to retrieve directory path.", .{}); 124 + defer app.alloc.free(message); 125 + app.notification.write(message, .err) catch {}; 126 + if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 127 + 128 + _ = preview_win.print(&.{ 129 + .{ .text = "Can not display file - unable to retrieve directory path. No preview available." }, 130 + }, .{}); 131 + return; 132 + }, entry.name }, 130 133 ); 131 134 132 135 switch (entry.kind) { 133 136 .directory => { 134 137 app.directories.clearChildEntries(); 135 - if (app.directories.populateChildEntries(entry.name)) { 136 - for (app.directories.child_entries.all(), 0..) |item, i| { 137 - if (std.mem.startsWith(u8, item, ".") and config.show_hidden == false) { 138 - continue; 139 - } 140 - if (i > preview_win.height) continue; 141 - const w = preview_win.child(.{ .y_off = @intCast(i), .height = 1 }); 142 - w.fill(vaxis.Cell{ .style = config.styles.list_item }); 143 - _ = w.print(&.{.{ .text = item, .style = config.styles.list_item }}, .{}); 144 - } 145 - } else |err| { 146 - switch (err) { 147 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 148 - else => try app.notification.writeErr(.UnknownError), 138 + app.directories.populateChildEntries(entry.name) catch |err| { 139 + const message = try std.fmt.allocPrint(app.alloc, "Failed to populate child directory entries - {}.", .{err}); 140 + defer app.alloc.free(message); 141 + app.notification.write(message, .err) catch {}; 142 + if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 143 + 144 + _ = preview_win.print(&.{ 145 + .{ .text = "Failed to populate child directory entries. No preview available." }, 146 + }, .{}); 147 + 148 + return; 149 + }; 150 + 151 + for (app.directories.child_entries.all(), 0..) |item, i| { 152 + if (std.mem.startsWith(u8, item, ".") and config.show_hidden == false) { 153 + continue; 149 154 } 155 + if (i > preview_win.height) continue; 156 + const w = preview_win.child(.{ .y_off = @intCast(i), .height = 1 }); 157 + w.fill(vaxis.Cell{ .style = config.styles.list_item }); 158 + _ = w.print(&.{.{ .text = item, .style = config.styles.list_item }}, .{}); 150 159 } 151 160 }, 152 161 .file => file: { ··· 154 163 entry.name, 155 164 .{ .mode = .read_only }, 156 165 ) catch |err| { 157 - switch (err) { 158 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 159 - else => try app.notification.writeErr(.UnknownError), 160 - } 166 + const message = try std.fmt.allocPrint(app.alloc, "Failed to open file - {}.", .{err}); 167 + defer app.alloc.free(message); 168 + app.notification.write(message, .err) catch {}; 169 + if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 161 170 162 171 _ = preview_win.print(&.{ 163 - .{ .text = "No preview available." }, 172 + .{ .text = "Failed to open file. No preview available." }, 164 173 }, .{}); 165 174 166 175 break :file; 167 176 }; 168 177 defer file.close(); 169 - const bytes = try file.readAll(&app.directories.file_contents); 178 + const bytes = file.readAll(&app.directories.file_contents) catch |err| { 179 + const message = try std.fmt.allocPrint(app.alloc, "Failed to read file contents - {}.", .{err}); 180 + defer app.alloc.free(message); 181 + app.notification.write(message, .err) catch {}; 182 + if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 183 + 184 + _ = preview_win.print(&.{ 185 + .{ .text = "Failed to read file contents. No preview available." }, 186 + }, .{}); 187 + 188 + break :file; 189 + }; 170 190 171 191 // Handle image. 172 192 if (config.show_images == true) unsupported: { 173 193 var match = false; 174 194 inline for (@typeInfo(vaxis.zigimg.Image.Format).@"enum".fields) |field| { 175 - const entry_ext = std.mem.trimLeft( 176 - u8, 177 - std.fs.path.extension(entry.name), 178 - ".", 179 - ); 180 - if (std.mem.eql(u8, entry_ext, field.name)) { 181 - match = true; 182 - } 195 + const entry_ext = std.mem.trimLeft(u8, std.fs.path.extension(entry.name), "."); 196 + if (std.mem.eql(u8, entry_ext, field.name)) match = true; 183 197 } 184 198 if (!match) break :unsupported; 185 199 ··· 204 218 } 205 219 206 220 if (app.image) |img| { 207 - try img.draw(preview_win, .{ .scale = .contain }); 221 + img.draw(preview_win, .{ .scale = .contain }) catch |err| { 222 + const message = try std.fmt.allocPrint(app.alloc, "Failed to draw image to screen - {}.", .{err}); 223 + defer app.alloc.free(message); 224 + app.notification.write(message, .err) catch {}; 225 + if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 226 + 227 + _ = preview_win.print(&.{ 228 + .{ .text = "Failed to draw image to screen. No preview available." }, 229 + }, .{}); 230 + 231 + break :file; 232 + }; 208 233 } 209 234 210 235 break :file; ··· 273 298 alloc: std.mem.Allocator, 274 299 directories: *Directories, 275 300 win: vaxis.Window, 276 - ) !vaxis.Window { 301 + ) error{NoSpaceLeft}!vaxis.Window { 277 302 const bottom_div: u16 = if (self.verbose) 6 else 1; 278 303 279 304 const file_info_win = win.child(.{ ··· 306 331 if (self.verbose) lbl: { 307 332 var maybe_meta: ?std.fs.File.Metadata = null; 308 333 if (entry.kind == .directory) { 309 - maybe_meta = try directories.dir.metadata(); 334 + maybe_meta = directories.dir.metadata() catch break :lbl; 310 335 } else if (entry.kind == .file) { 311 - var file = try directories.dir.openFile(entry.name, .{}); 312 - maybe_meta = try file.metadata(); 336 + var file = directories.dir.openFile(entry.name, .{}) catch break :lbl; 337 + maybe_meta = file.metadata() catch break :lbl; 313 338 } 314 339 315 340 const meta = maybe_meta orelse break :lbl; 316 - var env = try std.process.getEnvMap(alloc); 341 + var env = std.process.getEnvMap(alloc) catch break :lbl; 317 342 defer env.deinit(); 318 - const local = try zeit.local(alloc, &env); 343 + const local = zeit.local(alloc, &env) catch break :lbl; 319 344 defer local.deinit(); 320 345 321 - const ctime_instant = try zeit.instant(.{ 346 + const ctime_instant = zeit.instant(.{ 322 347 .source = .{ .unix_nano = meta.created().? }, 323 348 .timezone = &local, 324 - }); 349 + }) catch break :lbl; 325 350 const ctime = ctime_instant.time(); 326 - try ctime.strftime(fbs.writer().any(), "Created: %Y-%m-%d %H:%M:%S\n"); 351 + ctime.strftime(fbs.writer().any(), "Created: %Y-%m-%d %H:%M:%S\n") catch break :lbl; 327 352 328 - const mtime_instant = try zeit.instant(.{ 353 + const mtime_instant = zeit.instant(.{ 329 354 .source = .{ .unix_nano = meta.modified() }, 330 355 .timezone = &local, 331 - }); 356 + }) catch break :lbl; 332 357 const mtime = mtime_instant.time(); 333 - try mtime.strftime(fbs.writer().any(), "Last modified: %Y-%m-%d %H:%M:%S\n"); 358 + mtime.strftime(fbs.writer().any(), "Last modified: %Y-%m-%d %H:%M:%S\n") catch break :lbl; 334 359 } 335 360 336 361 // File permissions. ··· 427 452 list: List(std.fs.Dir.Entry), 428 453 abs_file_path: vaxis.Window, 429 454 file_information: vaxis.Window, 430 - ) !u16 { 455 + ) u16 { 431 456 const bottom_div: u16 = 1; 432 457 433 458 const current_dir_list_win = win.child(.{ ··· 470 495 471 496 fn drawAbsFilePath( 472 497 self: *Drawer, 473 - alloc: std.mem.Allocator, 474 - directories: *Directories, 498 + app: *App, 475 499 win: vaxis.Window, 476 - ) !vaxis.Window { 500 + ) error{ OutOfMemory, NoSpaceLeft }!vaxis.Window { 477 501 const abs_file_path_bar = win.child(.{ 478 502 .x_off = 0, 479 503 .y_off = 0, ··· 481 505 .height = top_div, 482 506 }); 483 507 484 - const branch_alloc = try Git.getGitBranch(alloc, directories.dir); 485 - defer if (branch_alloc) |b| alloc.free(b); 508 + const branch_alloc = Git.getGitBranch(app.alloc, app.directories.dir) catch null; 509 + defer if (branch_alloc) |b| app.alloc.free(b); 486 510 const branch = if (branch_alloc) |b| 487 511 try std.fmt.bufPrint( 488 512 &self.git_branch, ··· 493 517 ""; 494 518 495 519 _ = abs_file_path_bar.print(&.{ 496 - vaxis.Segment{ .text = try directories.fullPath(".") }, 520 + vaxis.Segment{ .text = app.directories.fullPath(".") catch { 521 + const message = try std.fmt.allocPrint(app.alloc, "Can not display absolute file path - unable to retrieve full path.", .{}); 522 + defer app.alloc.free(message); 523 + app.notification.write(message, .err) catch {}; 524 + if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 525 + return abs_file_path_bar; 526 + } }, 497 527 vaxis.Segment{ .text = if (branch_alloc != null) " on " else "" }, 498 528 vaxis.Segment{ .text = branch, .style = config.styles.git_branch }, 499 529 }, .{}); ··· 506 536 text_input: *vaxis.widgets.TextInput, 507 537 input: []const u8, 508 538 win: vaxis.Window, 509 - ) !void { 539 + ) void { 510 540 const user_input_win = win.child(.{ 511 541 .x_off = 0, 512 542 .y_off = top_div, ··· 540 570 541 571 fn drawNotification( 542 572 notification: *Notification, 573 + file_logger: *?FileLogger, 543 574 win: vaxis.Window, 544 - ) !void { 575 + ) void { 545 576 if (notification.len() == 0) return; 546 577 if (notification.clearIfEnded()) return; 547 578 ··· 552 583 const max_width = win.width / 4; 553 584 const width = notification.len() + width_padding; 554 585 const calculated_width = if (width > max_width) max_width else width; 555 - const height = try std.math.divCeil(usize, notification.len(), calculated_width) + height_padding; 586 + const height = (std.math.divCeil(usize, notification.len(), calculated_width) catch { 587 + if (file_logger.*) |fl| fl.write("Unable to display notification - failed to calculate notification height.", .err) catch {}; 588 + return; 589 + }) + height_padding; 556 590 557 591 const notification_win = win.child(.{ 558 592 .x_off = @intCast(win.width - (calculated_width + screen_pos_padding)),
+1 -1
src/environment.zig
··· 28 28 buf: []u8, 29 29 dir: std.fs.Dir, 30 30 relative_path: []const u8, 31 - ) std.fmt.BufPrintError!struct { 31 + ) error{NoSpaceLeft}!struct { 32 32 path: []const u8, 33 33 had_duplicate: bool, 34 34 } {
+64 -556
src/event_handlers.zig
··· 7 7 const config = &@import("./config.zig").config; 8 8 const commands = @import("./commands.zig"); 9 9 const Keybinds = @import("./config.zig").Keybinds; 10 - 11 - pub fn inputToSlice(self: *App) []const u8 { 12 - self.text_input.buf.cursor = self.text_input.buf.realLength(); 13 - return self.text_input.sliceToCursor(&self.text_input_buf); 14 - } 10 + const events = @import("./events.zig"); 15 11 16 12 pub fn handleNormalEvent( 17 13 app: *App, ··· 37 33 38 34 if (maybe_remap) |action| { 39 35 switch (action) { 40 - .toggle_hidden_files => { 41 - config.show_hidden = !config.show_hidden; 42 - 43 - const prev_selected_name: []const u8, const prev_selected_err: bool = lbl: { 44 - const selected = app.directories.getSelected() catch break :lbl .{ "", true }; 45 - if (selected == null) break :lbl .{ "", true }; 36 + .toggle_hidden_files => try events.toggleHiddenFiles(app), 37 + .delete => try events.delete(app), 38 + .rename => { 39 + const entry = (app.directories.getSelected() catch { 40 + app.notification.write("Can not rename item - no item selected.", .warn) catch {}; 41 + return; 42 + }) orelse return; 46 43 47 - break :lbl .{ try app.alloc.dupe(u8, selected.?.name), false }; 48 - }; 49 - defer if (!prev_selected_err) app.alloc.free(prev_selected_name); 50 - 51 - app.directories.clearEntries(); 52 44 app.text_input.clearAndFree(); 53 - app.directories.populateEntries("") catch |err| { 54 - switch (err) { 55 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 56 - else => try app.notification.writeErr(.UnknownError), 57 - } 58 - }; 59 45 60 - for (app.directories.entries.all()) |entry| { 61 - if (std.mem.eql(u8, entry.name, prev_selected_name)) return; 62 - app.directories.entries.selected += 1; 63 - } 64 - 65 - // If it didn't find entry, reset selected. 66 - app.directories.entries.selected = 0; 46 + // Try insert entry name into text input for a nicer experience. 47 + // This failing shouldn't stop the user from entering a new name. 48 + app.text_input.insertSliceAtCursor(entry.name) catch {}; 49 + app.state = .rename; 67 50 }, 68 - .delete => { 69 - const entry = lbl: { 70 - const entry = app.directories.getSelected() catch { 71 - try app.notification.writeErr(.UnableToDelete); 72 - return; 73 - }; 74 - if (entry) |e| break :lbl e else return; 75 - }; 76 51 77 - var prev_path_buf: [std.fs.max_path_bytes]u8 = undefined; 78 - const prev_path = try app.alloc.dupe(u8, try app.directories.dir.realpath(entry.name, &prev_path_buf)); 79 - 80 - var trash_dir = dir: { 81 - notfound: { 82 - break :dir (config.trashDir() catch break :notfound) orelse break :notfound; 83 - } 84 - app.alloc.free(prev_path); 85 - try app.notification.writeErr(.ConfigPathNotFound); 86 - return; 87 - }; 88 - defer trash_dir.close(); 89 - var trash_dir_path_buf: [std.fs.max_path_bytes]u8 = undefined; 90 - const trash_dir_path = try trash_dir.realpath(".", &trash_dir_path_buf); 91 - 92 - if (std.mem.eql(u8, prev_path, trash_dir_path)) { 93 - try app.notification.writeErr(.CannotDeleteTrashDir); 94 - app.alloc.free(prev_path); 95 - return; 96 - } 97 - 98 - var tmp_path_buf: [std.fs.max_path_bytes]u8 = undefined; 99 - const tmp_path = try app.alloc.dupe(u8, try std.fmt.bufPrint(&tmp_path_buf, "{s}/{s}-{s}", .{ trash_dir_path, entry.name, zuid.new.v4() })); 100 - 101 - if (app.directories.dir.rename(entry.name, tmp_path)) { 102 - if (app.actions.push(.{ 103 - .delete = .{ .prev_path = prev_path, .new_path = tmp_path }, 104 - })) |prev_elem| { 105 - app.alloc.free(prev_elem.delete.prev_path); 106 - app.alloc.free(prev_elem.delete.new_path); 107 - } 108 - 109 - try app.notification.writeInfo(.Deleted); 110 - app.directories.removeSelected(); 111 - } else |err| { 112 - switch (err) { 113 - error.RenameAcrossMountPoints => try app.notification.writeErr(.UnableToDeleteAcrossMountPoints), 114 - else => try app.notification.writeErr(.UnableToDelete), 115 - } 116 - app.alloc.free(prev_path); 117 - app.alloc.free(tmp_path); 118 - } 119 - }, 120 - .rename => { 121 - app.text_input.clearAndFree(); 122 - app.state = .rename; 123 - 124 - const entry = lbl: { 125 - const entry = app.directories.getSelected() catch { 126 - app.state = .normal; 127 - try app.notification.writeErr(.UnableToRename); 128 - return; 129 - }; 130 - if (entry) |e| break :lbl e else { 131 - app.state = .normal; 132 - return; 133 - } 134 - }; 135 - 136 - app.text_input.insertSliceAtCursor(entry.name) catch { 137 - app.state = .normal; 138 - try app.notification.writeErr(.UnableToRename); 139 - return; 140 - }; 141 - }, 142 52 .create_dir => { 53 + try app.repopulateDirectory(""); 143 54 app.text_input.clearAndFree(); 144 - app.directories.clearEntries(); 145 - app.directories.populateEntries("") catch |err| { 146 - switch (err) { 147 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 148 - else => try app.notification.writeErr(.UnknownError), 149 - } 150 - }; 151 55 app.state = .new_dir; 152 56 }, 153 57 .create_file => { 58 + try app.repopulateDirectory(""); 154 59 app.text_input.clearAndFree(); 155 - app.directories.clearEntries(); 156 - app.directories.populateEntries("") catch |err| { 157 - switch (err) { 158 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 159 - else => try app.notification.writeErr(.UnknownError), 160 - } 161 - }; 162 60 app.state = .new_file; 163 61 }, 164 62 .fuzzy_find => { ··· 177 75 .jump_bottom => app.directories.entries.selectLast(), 178 76 .jump_top => app.directories.entries.selectFirst(), 179 77 .toggle_verbose_file_information => app.drawer.verbose = !app.drawer.verbose, 180 - .force_delete => { 181 - const entry = lbl: { 182 - const entry = app.directories.getSelected() catch { 183 - try app.notification.writeErr(.UnableToDelete); 184 - return; 185 - }; 186 - if (entry) |e| break :lbl e else return; 187 - }; 188 - 189 - app.directories.dir.deleteTree(entry.name) catch { 190 - try app.notification.writeErr(.UnableToDelete); 191 - return; 192 - }; 193 - app.directories.removeSelected(); 194 - }, 195 - .yank => { 196 - if (app.yanked) |yanked| { 197 - app.alloc.free(yanked.dir); 198 - app.alloc.free(yanked.entry.name); 199 - } 200 - 201 - app.yanked = lbl: { 202 - const entry = (app.directories.getSelected() catch { 203 - app.notification.write("Failed to yank item - no item selected.", .warn) catch {}; 204 - break :lbl null; 205 - }) orelse break :lbl null; 206 - 207 - switch (entry.kind) { 208 - .file => { 209 - break :lbl .{ 210 - .dir = try app.alloc.dupe(u8, app.directories.fullPath(".") catch { 211 - const message = try std.fmt.allocPrint( 212 - app.alloc, 213 - "Failed to yank '{s}' - unable to retrieve directory path.", 214 - .{entry.name}, 215 - ); 216 - defer app.alloc.free(message); 217 - app.notification.write(message, .err) catch {}; 218 - if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 219 - break :lbl null; 220 - }), 221 - .entry = .{ 222 - .kind = entry.kind, 223 - .name = try app.alloc.dupe(u8, entry.name), 224 - }, 225 - }; 226 - }, 227 - else => { 228 - const message = try std.fmt.allocPrint( 229 - app.alloc, 230 - "Failed to yank '{s}' - unsupported file type '{s}'.", 231 - .{ entry.name, @tagName(entry.kind) }, 232 - ); 233 - defer app.alloc.free(message); 234 - app.notification.write(message, .warn) catch {}; 235 - break :lbl null; 236 - }, 237 - } 238 - }; 239 - if (app.yanked) |y| { 240 - const message = try std.fmt.allocPrint(app.alloc, "Yanked '{s}'.", .{y.entry.name}); 241 - defer app.alloc.free(message); 242 - app.notification.write(message, .info) catch {}; 243 - } 244 - }, 245 - .paste => { 246 - const yanked = if (app.yanked) |y| y else return; 247 - 248 - var new_path_buf: [std.fs.max_path_bytes]u8 = undefined; 249 - const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, yanked.entry.name) catch { 250 - const message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - path too long.", .{yanked.entry.name}); 251 - defer app.alloc.free(message); 252 - app.notification.write(message, .err) catch {}; 253 - return; 254 - }; 255 - 256 - switch (yanked.entry.kind) { 257 - .file => { 258 - var source_dir = std.fs.openDirAbsolute(yanked.dir, .{ .iterate = true }) catch { 259 - const message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to open directory '{s}'.", .{ yanked.entry.name, yanked.dir }); 260 - defer app.alloc.free(message); 261 - app.notification.write(message, .err) catch {}; 262 - if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 263 - return; 264 - }; 265 - defer source_dir.close(); 266 - std.fs.Dir.copyFile( 267 - source_dir, 268 - yanked.entry.name, 269 - app.directories.dir, 270 - new_path_res.path, 271 - .{}, 272 - ) catch |err| switch (err) { 273 - error.FileNotFound => { 274 - const message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - the original file was deleted or moved.", .{yanked.entry.name}); 275 - defer app.alloc.free(message); 276 - app.notification.write(message, .err) catch {}; 277 - if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 278 - return; 279 - }, 280 - else => { 281 - const message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - {}.", .{ yanked.entry.name, err }); 282 - defer app.alloc.free(message); 283 - app.notification.write(message, .err) catch {}; 284 - if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 285 - return; 286 - }, 287 - }; 288 - 289 - // Append action to undo history. 290 - { 291 - var new_path_abs_buf: [std.fs.max_path_bytes]u8 = undefined; 292 - const new_path_abs = app.directories.dir.realpath(new_path_res.path, &new_path_abs_buf) catch { 293 - const message = try std.fmt.allocPrint( 294 - app.alloc, 295 - "Failed to push copy action for '{s}' to undo history - unable to retrieve absolute directory path for '{s}'. This action will not be able to be undone via the `undo` keybind.", 296 - .{ new_path_res.path, yanked.entry.name }, 297 - ); 298 - defer app.alloc.free(message); 299 - app.notification.write(message, .err) catch {}; 300 - if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 301 - return; 302 - }; 303 - 304 - if (app.actions.push(.{ 305 - .paste = try app.alloc.dupe(u8, new_path_abs), 306 - })) |prev_elem| { 307 - app.alloc.free(prev_elem.delete.prev_path); 308 - app.alloc.free(prev_elem.delete.new_path); 309 - } 310 - } 311 - 312 - const message = try std.fmt.allocPrint(app.alloc, "Copied '{s}'.", .{yanked.entry.name}); 313 - defer app.alloc.free(message); 314 - app.notification.write(message, .info) catch {}; 315 - }, 316 - else => { 317 - const message = try std.fmt.allocPrint( 318 - app.alloc, 319 - "Failed to copy '{s}' - unsupported file type '{s}'.", 320 - .{ yanked.entry.name, @tagName(yanked.entry.kind) }, 321 - ); 322 - defer app.alloc.free(message); 323 - app.notification.write(message, .warn) catch {}; 324 - }, 325 - } 326 - 327 - app.directories.clearEntries(); 328 - app.directories.populateEntries("") catch |err| { 329 - switch (err) { 330 - error.AccessDenied => app.notification.writeErr(.PermissionDenied) catch {}, 331 - else => app.notification.writeErr(.UnknownError) catch {}, 332 - } 333 - }; 334 - }, 78 + .force_delete => try events.forceDelete(app), 79 + .yank => try events.yank(app), 80 + .paste => try events.paste(app), 335 81 } 336 82 } else { 337 83 switch (key.codepoint) { 338 - '-', 'h', Key.left => { 339 - app.text_input.clearAndFree(); 340 - 341 - if (app.directories.dir.openDir("../", .{ .iterate = true })) |dir| { 342 - app.directories.dir.close(); 343 - app.directories.dir = dir; 344 - 345 - app.directories.clearEntries(); 346 - const fuzzy = inputToSlice(app); 347 - app.directories.populateEntries(fuzzy) catch |err| { 348 - switch (err) { 349 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 350 - else => try app.notification.writeErr(.UnknownError), 351 - } 352 - }; 353 - 354 - if (app.directories.history.pop()) |history| { 355 - if (history < app.directories.entries.len()) { 356 - app.directories.entries.selected = history; 357 - } 358 - } 359 - } else |err| { 360 - switch (err) { 361 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 362 - else => try app.notification.writeErr(.UnknownError), 363 - } 364 - } 365 - }, 366 - Key.enter, 'l', Key.right => { 367 - const entry = lbl: { 368 - const entry = app.directories.getSelected() catch return; 369 - if (entry) |e| break :lbl e else return; 370 - }; 371 - 372 - switch (entry.kind) { 373 - .directory => { 374 - app.text_input.clearAndFree(); 375 - 376 - if (app.directories.dir.openDir(entry.name, .{ .iterate = true })) |dir| { 377 - app.directories.dir.close(); 378 - app.directories.dir = dir; 379 - 380 - _ = app.directories.history.push(app.directories.entries.selected); 381 - 382 - app.directories.clearEntries(); 383 - const fuzzy = inputToSlice(app); 384 - app.directories.populateEntries(fuzzy) catch |err| { 385 - switch (err) { 386 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 387 - else => try app.notification.writeErr(.UnknownError), 388 - } 389 - }; 390 - } else |err| { 391 - switch (err) { 392 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 393 - else => try app.notification.writeErr(.UnknownError), 394 - } 395 - } 396 - }, 397 - .file => { 398 - if (environment.getEditor()) |editor| { 399 - try app.vx.exitAltScreen(app.tty.anyWriter()); 400 - try app.vx.resetState(app.tty.anyWriter()); 401 - loop.stop(); 402 - 403 - environment.openFile(app.alloc, app.directories.dir, entry.name, editor) catch { 404 - try app.notification.writeErr(.UnableToOpenFile); 405 - }; 406 - 407 - try loop.start(); 408 - try app.vx.enterAltScreen(app.tty.anyWriter()); 409 - try app.vx.enableDetectedFeatures(app.tty.anyWriter()); 410 - app.vx.queueRefresh(); 411 - } else { 412 - try app.notification.writeErr(.EditorNotSet); 413 - } 414 - }, 415 - else => {}, 416 - } 417 - }, 418 - 'j', Key.down => { 419 - app.directories.entries.next(); 420 - }, 421 - 'k', Key.up => { 422 - app.directories.entries.previous(); 423 - }, 424 - 'u' => { 425 - if (app.actions.pop()) |action| { 426 - const selected = app.directories.entries.selected; 427 - 428 - switch (action) { 429 - .delete => |a| { 430 - defer app.alloc.free(a.new_path); 431 - defer app.alloc.free(a.prev_path); 432 - 433 - var new_path_buf: [std.fs.max_path_bytes]u8 = undefined; 434 - const new_path_res = try environment.checkDuplicatePath(&new_path_buf, app.directories.dir, a.prev_path); 435 - 436 - if (app.directories.dir.rename(a.new_path, new_path_res.path)) { 437 - app.directories.clearEntries(); 438 - app.directories.populateEntries("") catch |err| { 439 - switch (err) { 440 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 441 - else => try app.notification.writeErr(.UnknownError), 442 - } 443 - }; 444 - if (new_path_res.had_duplicate) { 445 - try app.notification.writeWarn(.DuplicateFileOnUndo); 446 - } else { 447 - try app.notification.writeInfo(.RestoredDelete); 448 - } 449 - } else |_| { 450 - try app.notification.writeErr(.UnableToUndo); 451 - } 452 - }, 453 - .rename => |a| { 454 - defer app.alloc.free(a.new_path); 455 - defer app.alloc.free(a.prev_path); 456 - 457 - var new_path_buf: [std.fs.max_path_bytes]u8 = undefined; 458 - const new_path_res = try environment.checkDuplicatePath(&new_path_buf, app.directories.dir, a.prev_path); 459 - 460 - if (app.directories.dir.rename(a.new_path, new_path_res.path)) { 461 - app.directories.clearEntries(); 462 - app.directories.populateEntries("") catch |err| { 463 - switch (err) { 464 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 465 - else => try app.notification.writeErr(.UnknownError), 466 - } 467 - }; 468 - if (new_path_res.had_duplicate) { 469 - try app.notification.writeWarn(.DuplicateFileOnUndo); 470 - } else { 471 - try app.notification.writeInfo(.RestoredRename); 472 - } 473 - } else |_| { 474 - try app.notification.writeErr(.UnableToUndo); 475 - } 476 - }, 477 - .paste => |path| { 478 - defer app.alloc.free(path); 479 - 480 - app.directories.dir.deleteTree(path) catch { 481 - try app.notification.writeErr(.UnableToUndo); 482 - return; 483 - }; 484 - 485 - app.directories.clearEntries(); 486 - app.directories.populateEntries("") catch |err| { 487 - switch (err) { 488 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 489 - else => try app.notification.writeErr(.UnknownError), 490 - } 491 - }; 492 - }, 493 - } 494 - 495 - app.directories.entries.selected = selected; 496 - } else { 497 - try app.notification.writeInfo(.EmptyUndo); 498 - } 499 - }, 84 + '-', 'h', Key.left => try events.traverseLeft(app), 85 + Key.enter, 'l', Key.right => try events.traverseRight(app, loop), 86 + 'j', Key.down => app.directories.entries.next(), 87 + 'k', Key.up => app.directories.entries.previous(), 88 + 'u' => try events.undo(app), 500 89 else => {}, 501 90 } 502 91 } ··· 512 101 Key.escape => { 513 102 switch (app.state) { 514 103 .fuzzy => { 515 - app.directories.clearEntries(); 516 - app.directories.populateEntries("") catch |err| { 517 - switch (err) { 518 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 519 - else => try app.notification.writeErr(.UnknownError), 520 - } 521 - }; 104 + try app.repopulateDirectory(""); 105 + app.text_input.clearAndFree(); 522 106 }, 523 107 .command => app.command_history.resetSelected(), 524 108 else => {}, ··· 530 114 Key.enter => { 531 115 const selected = app.directories.entries.selected; 532 116 switch (app.state) { 533 - .new_dir => { 534 - const dir = inputToSlice(app); 535 - if (app.directories.dir.makeDir(dir)) { 536 - try app.notification.writeInfo(.CreatedFolder); 537 - 538 - app.directories.clearEntries(); 539 - app.directories.populateEntries("") catch |err| { 540 - switch (err) { 541 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 542 - else => try app.notification.writeErr(.UnknownError), 543 - } 544 - }; 545 - } else |err| { 546 - switch (err) { 547 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 548 - error.PathAlreadyExists => try app.notification.writeErr(.ItemAlreadyExists), 549 - else => try app.notification.writeErr(.UnknownError), 550 - } 551 - } 552 - app.text_input.clearAndFree(); 553 - }, 554 - .new_file => { 555 - const file = inputToSlice(app); 556 - if (environment.fileExists(app.directories.dir, file)) { 557 - try app.notification.writeErr(.ItemAlreadyExists); 558 - } else { 559 - if (app.directories.dir.createFile(file, .{})) |f| { 560 - f.close(); 561 - 562 - try app.notification.writeInfo(.CreatedFile); 563 - 564 - app.directories.clearEntries(); 565 - app.directories.populateEntries("") catch |err| { 566 - switch (err) { 567 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 568 - else => try app.notification.writeErr(.UnknownError), 569 - } 570 - }; 571 - } else |err| { 572 - switch (err) { 573 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 574 - else => try app.notification.writeErr(.UnknownError), 575 - } 576 - } 577 - } 578 - app.text_input.clearAndFree(); 579 - }, 580 - .rename => { 581 - var dir_prefix_buf: [std.fs.max_path_bytes]u8 = undefined; 582 - const dir_prefix = try app.directories.dir.realpath(".", &dir_prefix_buf); 583 - 584 - const entry = lbl: { 585 - const entry = app.directories.getSelected() catch { 586 - try app.notification.writeErr(.UnableToRename); 587 - return; 588 - }; 589 - if (entry) |e| break :lbl e else return; 590 - }; 591 - const new_path = inputToSlice(app); 592 - 593 - if (environment.fileExists(app.directories.dir, new_path)) { 594 - try app.notification.writeErr(.ItemAlreadyExists); 595 - } else { 596 - app.directories.dir.rename(entry.name, new_path) catch |err| switch (err) { 597 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 598 - error.PathAlreadyExists => try app.notification.writeErr(.ItemAlreadyExists), 599 - else => try app.notification.writeErr(.UnknownError), 600 - }; 601 - if (app.actions.push(.{ 602 - .rename = .{ 603 - .prev_path = try std.fs.path.join(app.alloc, &.{ dir_prefix, entry.name }), 604 - .new_path = try std.fs.path.join(app.alloc, &.{ dir_prefix, new_path }), 605 - }, 606 - })) |prev_elem| { 607 - app.alloc.free(prev_elem.rename.prev_path); 608 - app.alloc.free(prev_elem.rename.new_path); 609 - } 610 - 611 - try app.notification.writeInfo(.Renamed); 612 - 613 - app.directories.clearEntries(); 614 - app.directories.populateEntries("") catch |err| { 615 - switch (err) { 616 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 617 - else => try app.notification.writeErr(.UnknownError), 618 - } 619 - }; 620 - } 621 - app.text_input.clearAndFree(); 622 - }, 117 + .new_dir => try events.createNewDir(app), 118 + .new_file => try events.createNewFile(app), 119 + .rename => try events.rename(app), 623 120 .change_dir => { 624 - const path = inputToSlice(app); 121 + const path = app.inputToSlice(); 625 122 try commands.cd(app, path); 626 123 app.text_input.clearAndFree(); 627 124 }, 628 125 .command => { 629 - const command = inputToSlice(app); 126 + const command = app.inputToSlice(); 127 + 128 + // Push command to history if it's not empty. 630 129 if (!std.mem.eql(u8, std.mem.trim(u8, command, " "), ":")) { 631 130 if (app.command_history.push(try app.alloc.dupe(u8, command))) |deleted| { 632 131 app.alloc.free(deleted); ··· 650 149 } 651 150 652 151 if (std.mem.startsWith(u8, command, ":cd ")) { 653 - const path = std.mem.trim(u8, command, ":cd \n\r"); 654 - try commands.cd(app, path); 152 + try commands.cd(app, command[":cd ".len..]); 655 153 break :supported; 656 154 } 657 155 ··· 661 159 } 662 160 663 161 if (std.mem.eql(u8, command, ":h")) { 664 - try commands.displayHelpMenu(app); 162 + app.state = .help_menu; 665 163 break :supported; 666 164 } 667 165 ··· 674 172 else => {}, 675 173 } 676 174 677 - // TODO(2025-03-29): Could there be a better way to check 678 - // this? 679 175 if (app.state != .help_menu) app.state = .normal; 680 176 app.directories.entries.selected = selected; 681 177 }, ··· 685 181 if (app.state == .command) { 686 182 if (app.command_history.next()) |command| { 687 183 app.text_input.clearAndFree(); 688 - try app.text_input.insertSliceAtCursor(command); 184 + app.text_input.insertSliceAtCursor(command) catch |err| { 185 + const message = try std.fmt.allocPrint(app.alloc, "Failed to get previous command history - {}.", .{err}); 186 + defer app.alloc.free(message); 187 + app.notification.write(message, .err) catch {}; 188 + if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 189 + }; 689 190 } 690 191 } 691 192 }, ··· 693 194 if (app.state == .command) { 694 195 app.text_input.clearAndFree(); 695 196 if (app.command_history.previous()) |command| { 696 - try app.text_input.insertSliceAtCursor(command); 197 + app.text_input.insertSliceAtCursor(command) catch |err| { 198 + const message = try std.fmt.allocPrint(app.alloc, "Failed to get next command history - {}.", .{err}); 199 + defer app.alloc.free(message); 200 + app.notification.write(message, .err) catch {}; 201 + if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 202 + }; 697 203 } else { 698 - try app.text_input.insertSliceAtCursor(":"); 204 + app.text_input.insertSliceAtCursor(":") catch |err| { 205 + const message = try std.fmt.allocPrint(app.alloc, "Failed to get next command history - {}.", .{err}); 206 + defer app.alloc.free(message); 207 + app.notification.write(message, .err) catch {}; 208 + if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 209 + }; 699 210 } 700 211 } 701 212 }, ··· 704 215 705 216 switch (app.state) { 706 217 .fuzzy => { 707 - app.directories.clearEntries(); 708 - const fuzzy = inputToSlice(app); 709 - app.directories.populateEntries(fuzzy) catch |err| { 710 - switch (err) { 711 - error.AccessDenied => try app.notification.writeErr(.PermissionDenied), 712 - else => try app.notification.writeErr(.UnknownError), 713 - } 714 - }; 218 + const fuzzy = app.inputToSlice(); 219 + try app.repopulateDirectory(fuzzy); 715 220 }, 716 221 .command => { 717 - const command = inputToSlice(app); 222 + const command = app.inputToSlice(); 718 223 if (!std.mem.startsWith(u8, command, ":")) { 719 224 app.text_input.clearAndFree(); 720 - try app.text_input.insertSliceAtCursor(":"); 225 + app.text_input.insertSliceAtCursor(":") catch |err| { 226 + app.state = .normal; 227 + 228 + const message = try std.fmt.allocPrint(app.alloc, "An input error occurred while attempting to enter a command - {}.", .{err}); 229 + defer app.alloc.free(message); 230 + app.notification.write(message, .err) catch {}; 231 + if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 232 + }; 721 233 } 722 234 }, 723 235 else => {}, ··· 734 246 .key_press => |key| { 735 247 switch (key.codepoint) { 736 248 Key.escape, 'q' => app.state = .normal, 737 - 'j', Key.down => { 738 - app.help_menu.next(); 739 - }, 740 - 'k', Key.up => { 741 - app.help_menu.previous(); 742 - }, 249 + 'j', Key.down => app.help_menu.next(), 250 + 'k', Key.up => app.help_menu.previous(), 743 251 else => {}, 744 252 } 745 253 },
+488
src/events.zig
··· 1 + const std = @import("std"); 2 + const App = @import("./app.zig"); 3 + const config = &@import("./config.zig").config; 4 + const zuid = @import("zuid"); 5 + const environment = @import("./environment.zig"); 6 + const vaxis = @import("vaxis"); 7 + 8 + pub fn delete(app: *App) error{OutOfMemory}!void { 9 + var message: ?[]const u8 = null; 10 + defer if (message) |msg| app.alloc.free(msg); 11 + 12 + const entry = (app.directories.getSelected() catch { 13 + app.notification.write("Can not to delete item - no item selected.", .warn) catch {}; 14 + return; 15 + }) orelse return; 16 + 17 + var prev_path_buf: [std.fs.max_path_bytes]u8 = undefined; 18 + const prev_path = app.directories.dir.realpath(entry.name, &prev_path_buf) catch { 19 + message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - unable to retrieve absolute path.", .{entry.name}); 20 + app.notification.write(message.?, .err) catch {}; 21 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 22 + return; 23 + }; 24 + const prev_path_alloc = try app.alloc.dupe(u8, prev_path); 25 + 26 + var trash_dir = dir: { 27 + notfound: { 28 + break :dir (config.trashDir() catch break :notfound) orelse break :notfound; 29 + } 30 + app.alloc.free(prev_path_alloc); 31 + message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - unable to retrieve trash directory.", .{entry.name}); 32 + app.notification.write(message.?, .err) catch {}; 33 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 34 + return; 35 + }; 36 + defer trash_dir.close(); 37 + 38 + var trash_dir_path_buf: [std.fs.max_path_bytes]u8 = undefined; 39 + const trash_dir_path = trash_dir.realpath(".", &trash_dir_path_buf) catch { 40 + message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - unable to retrieve absolute path for trash directory.", .{entry.name}); 41 + app.notification.write(message.?, .err) catch {}; 42 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 43 + return; 44 + }; 45 + 46 + if (std.mem.eql(u8, prev_path_alloc, trash_dir_path)) { 47 + app.notification.write("Can not delete trash directory.", .warn) catch {}; 48 + app.alloc.free(prev_path_alloc); 49 + return; 50 + } 51 + 52 + const tmp_path = try std.fmt.allocPrint(app.alloc, "{s}/{s}-{s}", .{ trash_dir_path, entry.name, zuid.new.v4() }); 53 + if (app.directories.dir.rename(entry.name, tmp_path)) { 54 + if (app.actions.push(.{ 55 + .delete = .{ .prev_path = prev_path_alloc, .new_path = tmp_path }, 56 + })) |prev_elem| { 57 + app.alloc.free(prev_elem.delete.prev_path); 58 + app.alloc.free(prev_elem.delete.new_path); 59 + } 60 + message = try std.fmt.allocPrint(app.alloc, "Deleted '{s}'.", .{entry.name}); 61 + app.notification.write(message.?, .info) catch {}; 62 + 63 + app.directories.removeSelected(); 64 + } else |err| { 65 + app.alloc.free(prev_path_alloc); 66 + app.alloc.free(tmp_path); 67 + 68 + message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - {}.", .{ entry.name, err }); 69 + app.notification.write(message.?, .err) catch {}; 70 + } 71 + } 72 + 73 + pub fn rename(app: *App) error{OutOfMemory}!void { 74 + var message: ?[]const u8 = null; 75 + defer if (message) |msg| app.alloc.free(msg); 76 + 77 + const entry = (app.directories.getSelected() catch { 78 + app.notification.write("Can not to rename item - no item selected.", .warn) catch {}; 79 + return; 80 + }) orelse return; 81 + 82 + var dir_prefix_buf: [std.fs.max_path_bytes]u8 = undefined; 83 + const dir_prefix = app.directories.dir.realpath(".", &dir_prefix_buf) catch { 84 + message = try std.fmt.allocPrint(app.alloc, "Failed to rename '{s}' - unable to retrieve absolute path.", .{entry.name}); 85 + app.notification.write(message.?, .err) catch {}; 86 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 87 + return; 88 + }; 89 + 90 + const new_path = app.inputToSlice(); 91 + 92 + if (environment.fileExists(app.directories.dir, new_path)) { 93 + message = try std.fmt.allocPrint(app.alloc, "Can not rename file - '{s}' already exists.", .{new_path}); 94 + app.notification.write(message.?, .warn) catch {}; 95 + } else { 96 + app.directories.dir.rename(entry.name, new_path) catch |err| { 97 + message = try std.fmt.allocPrint(app.alloc, "Failed to rename '{s}' - {}.", .{ new_path, err }); 98 + app.notification.write(message.?, .err) catch {}; 99 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 100 + return; 101 + }; 102 + 103 + if (app.actions.push(.{ 104 + .rename = .{ 105 + .prev_path = try std.fs.path.join(app.alloc, &.{ dir_prefix, entry.name }), 106 + .new_path = try std.fs.path.join(app.alloc, &.{ dir_prefix, new_path }), 107 + }, 108 + })) |prev_elem| { 109 + app.alloc.free(prev_elem.rename.prev_path); 110 + app.alloc.free(prev_elem.rename.new_path); 111 + } 112 + 113 + try app.repopulateDirectory(""); 114 + app.text_input.clearAndFree(); 115 + 116 + message = try std.fmt.allocPrint(app.alloc, "Renamed '{s}' to '{s}'.", .{ entry.name, new_path }); 117 + app.notification.write(message.?, .info) catch {}; 118 + } 119 + 120 + app.text_input.clearAndFree(); 121 + } 122 + 123 + pub fn forceDelete(app: *App) error{OutOfMemory}!void { 124 + const entry = (app.directories.getSelected() catch { 125 + app.notification.write("Can not force delete item - no item selected.", .warn) catch {}; 126 + return; 127 + }) orelse return; 128 + 129 + app.directories.dir.deleteTree(entry.name) catch |err| { 130 + const error_message = try std.fmt.allocPrint(app.alloc, "Failed to force delete '{s}' - {}.", .{ entry.name, err }); 131 + app.notification.write(error_message, .err) catch {}; 132 + return; 133 + }; 134 + 135 + app.directories.removeSelected(); 136 + } 137 + 138 + pub fn toggleHiddenFiles(app: *App) error{OutOfMemory}!void { 139 + config.show_hidden = !config.show_hidden; 140 + 141 + const prev_selected_name: []const u8, const prev_selected_err: bool = lbl: { 142 + const selected = app.directories.getSelected() catch break :lbl .{ "", true }; 143 + if (selected == null) break :lbl .{ "", true }; 144 + 145 + break :lbl .{ try app.alloc.dupe(u8, selected.?.name), false }; 146 + }; 147 + defer if (!prev_selected_err) app.alloc.free(prev_selected_name); 148 + 149 + try app.repopulateDirectory(""); 150 + app.text_input.clearAndFree(); 151 + 152 + for (app.directories.entries.all()) |entry| { 153 + if (std.mem.eql(u8, entry.name, prev_selected_name)) return; 154 + app.directories.entries.selected += 1; 155 + } 156 + 157 + // If it didn't find entry, reset selected. 158 + app.directories.entries.selected = 0; 159 + } 160 + 161 + pub fn yank(app: *App) error{OutOfMemory}!void { 162 + var message: ?[]const u8 = null; 163 + defer if (message) |msg| app.alloc.free(msg); 164 + 165 + if (app.yanked) |yanked| { 166 + app.alloc.free(yanked.dir); 167 + app.alloc.free(yanked.entry.name); 168 + } 169 + 170 + app.yanked = lbl: { 171 + const entry = (app.directories.getSelected() catch { 172 + app.notification.write("Can not yank item - no item selected.", .warn) catch {}; 173 + break :lbl null; 174 + }) orelse break :lbl null; 175 + 176 + switch (entry.kind) { 177 + .file => { 178 + break :lbl .{ 179 + .dir = try app.alloc.dupe(u8, app.directories.fullPath(".") catch { 180 + message = try std.fmt.allocPrint( 181 + app.alloc, 182 + "Failed to yank '{s}' - unable to retrieve directory path.", 183 + .{entry.name}, 184 + ); 185 + app.notification.write(message.?, .err) catch {}; 186 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 187 + break :lbl null; 188 + }), 189 + .entry = .{ 190 + .kind = entry.kind, 191 + .name = try app.alloc.dupe(u8, entry.name), 192 + }, 193 + }; 194 + }, 195 + else => { 196 + message = try std.fmt.allocPrint(app.alloc, "Can not yank '{s}' - unsupported file type '{s}'.", .{ entry.name, @tagName(entry.kind) }); 197 + app.notification.write(message.?, .warn) catch {}; 198 + break :lbl null; 199 + }, 200 + } 201 + }; 202 + 203 + if (app.yanked) |y| { 204 + message = try std.fmt.allocPrint(app.alloc, "Yanked '{s}'.", .{y.entry.name}); 205 + app.notification.write(message.?, .info) catch {}; 206 + } 207 + } 208 + 209 + pub fn paste(app: *App) error{OutOfMemory}!void { 210 + var message: ?[]const u8 = null; 211 + defer if (message) |msg| app.alloc.free(msg); 212 + 213 + const yanked = if (app.yanked) |y| y else return; 214 + 215 + var new_path_buf: [std.fs.max_path_bytes]u8 = undefined; 216 + const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, yanked.entry.name) catch { 217 + message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - path too long.", .{yanked.entry.name}); 218 + app.notification.write(message.?, .err) catch {}; 219 + return; 220 + }; 221 + 222 + switch (yanked.entry.kind) { 223 + .file => { 224 + var source_dir = std.fs.openDirAbsolute(yanked.dir, .{ .iterate = true }) catch { 225 + message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to open directory '{s}'.", .{ yanked.entry.name, yanked.dir }); 226 + app.notification.write(message.?, .err) catch {}; 227 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 228 + return; 229 + }; 230 + defer source_dir.close(); 231 + 232 + std.fs.Dir.copyFile( 233 + source_dir, 234 + yanked.entry.name, 235 + app.directories.dir, 236 + new_path_res.path, 237 + .{}, 238 + ) catch |err| switch (err) { 239 + error.FileNotFound => { 240 + message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - the original file was deleted or moved.", .{yanked.entry.name}); 241 + app.notification.write(message.?, .err) catch {}; 242 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 243 + return; 244 + }, 245 + else => { 246 + message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - {}.", .{ yanked.entry.name, err }); 247 + app.notification.write(message.?, .err) catch {}; 248 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 249 + return; 250 + }, 251 + }; 252 + 253 + // Append action to undo history. 254 + var new_path_abs_buf: [std.fs.max_path_bytes]u8 = undefined; 255 + const new_path_abs = app.directories.dir.realpath(new_path_res.path, &new_path_abs_buf) catch { 256 + message = try std.fmt.allocPrint( 257 + app.alloc, 258 + "Failed to push copy action for '{s}' to undo history - unable to retrieve absolute directory path for '{s}'. This action will not be able to be undone via the `undo` keybind.", 259 + .{ new_path_res.path, yanked.entry.name }, 260 + ); 261 + app.notification.write(message.?, .err) catch {}; 262 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 263 + return; 264 + }; 265 + 266 + if (app.actions.push(.{ 267 + .paste = try app.alloc.dupe(u8, new_path_abs), 268 + })) |prev_elem| { 269 + app.alloc.free(prev_elem.delete.prev_path); 270 + app.alloc.free(prev_elem.delete.new_path); 271 + } 272 + 273 + message = try std.fmt.allocPrint(app.alloc, "Copied '{s}'.", .{yanked.entry.name}); 274 + app.notification.write(message.?, .info) catch {}; 275 + }, 276 + else => { 277 + message = try std.fmt.allocPrint(app.alloc, "Can not copy '{s}' - unsupported file type '{s}'.", .{ yanked.entry.name, @tagName(yanked.entry.kind) }); 278 + app.notification.write(message.?, .warn) catch {}; 279 + return; 280 + }, 281 + } 282 + 283 + try app.repopulateDirectory(""); 284 + app.text_input.clearAndFree(); 285 + } 286 + 287 + pub fn traverseLeft(app: *App) error{OutOfMemory}!void { 288 + app.text_input.clearAndFree(); 289 + 290 + const dir = app.directories.dir.openDir("../", .{ .iterate = true }) catch |err| { 291 + const message = try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err}); 292 + defer app.alloc.free(message); 293 + app.notification.write(message, .err) catch {}; 294 + if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 295 + return; 296 + }; 297 + 298 + app.directories.dir.close(); 299 + app.directories.dir = dir; 300 + 301 + try app.repopulateDirectory(""); 302 + app.text_input.clearAndFree(); 303 + 304 + if (app.directories.history.pop()) |history| { 305 + if (history < app.directories.entries.len()) { 306 + app.directories.entries.selected = history; 307 + } 308 + } 309 + } 310 + 311 + pub fn traverseRight(app: *App, loop: *vaxis.Loop(App.Event)) !void { 312 + var message: ?[]const u8 = null; 313 + defer if (message) |msg| app.alloc.free(msg); 314 + 315 + const entry = (app.directories.getSelected() catch { 316 + app.notification.write("Can not rename item - no item selected.", .warn) catch {}; 317 + return; 318 + }) orelse return; 319 + 320 + switch (entry.kind) { 321 + .directory => { 322 + app.text_input.clearAndFree(); 323 + 324 + const dir = app.directories.dir.openDir(entry.name, .{ .iterate = true }) catch |err| { 325 + message = try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err}); 326 + app.notification.write(message.?, .err) catch {}; 327 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 328 + return; 329 + }; 330 + 331 + app.directories.dir.close(); 332 + app.directories.dir = dir; 333 + _ = app.directories.history.push(app.directories.entries.selected); 334 + try app.repopulateDirectory(""); 335 + app.text_input.clearAndFree(); 336 + }, 337 + .file => { 338 + if (environment.getEditor()) |editor| { 339 + try app.vx.exitAltScreen(app.tty.anyWriter()); 340 + try app.vx.resetState(app.tty.anyWriter()); 341 + loop.stop(); 342 + 343 + environment.openFile(app.alloc, app.directories.dir, entry.name, editor) catch |err| { 344 + message = try std.fmt.allocPrint(app.alloc, "Failed to open file '{s}' - {}.", .{ entry.name, err }); 345 + app.notification.write(message.?, .err) catch {}; 346 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 347 + }; 348 + 349 + try loop.start(); 350 + try app.vx.enterAltScreen(app.tty.anyWriter()); 351 + try app.vx.enableDetectedFeatures(app.tty.anyWriter()); 352 + app.vx.queueRefresh(); 353 + } else { 354 + app.notification.write("Can not open file - $EDITOR not set.", .warn) catch {}; 355 + } 356 + }, 357 + else => {}, 358 + } 359 + } 360 + 361 + pub fn createNewDir(app: *App) error{OutOfMemory}!void { 362 + var message: ?[]const u8 = null; 363 + defer if (message) |msg| app.alloc.free(msg); 364 + 365 + const dir = app.inputToSlice(); 366 + 367 + app.directories.dir.makeDir(dir) catch |err| { 368 + message = try std.fmt.allocPrint(app.alloc, "Failed to create directory '{s}' - {}", .{ dir, err }); 369 + app.notification.write(message.?, .err) catch {}; 370 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 371 + app.text_input.clearAndFree(); 372 + return; 373 + }; 374 + 375 + try app.repopulateDirectory(""); 376 + app.text_input.clearAndFree(); 377 + 378 + message = try std.fmt.allocPrint(app.alloc, "Created new directory '{s}'.", .{dir}); 379 + app.notification.write(message.?, .info) catch {}; 380 + } 381 + 382 + pub fn createNewFile(app: *App) error{OutOfMemory}!void { 383 + var message: ?[]const u8 = null; 384 + defer if (message) |msg| app.alloc.free(msg); 385 + 386 + const file = app.inputToSlice(); 387 + 388 + if (environment.fileExists(app.directories.dir, file)) { 389 + message = try std.fmt.allocPrint(app.alloc, "Can not create file - '{s}' already exists.", .{file}); 390 + app.notification.write(message.?, .warn) catch {}; 391 + } else { 392 + _ = app.directories.dir.createFile(file, .{}) catch |err| { 393 + message = try std.fmt.allocPrint(app.alloc, "Failed to create file '{s}' - {}", .{ file, err }); 394 + app.notification.write(message.?, .err) catch {}; 395 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 396 + app.text_input.clearAndFree(); 397 + return; 398 + }; 399 + 400 + try app.repopulateDirectory(""); 401 + app.text_input.clearAndFree(); 402 + 403 + message = try std.fmt.allocPrint(app.alloc, "Created new file '{s}'.", .{file}); 404 + app.notification.write(message.?, .info) catch {}; 405 + } 406 + 407 + app.text_input.clearAndFree(); 408 + } 409 + 410 + pub fn undo(app: *App) error{OutOfMemory}!void { 411 + var message: ?[]const u8 = null; 412 + defer if (message) |msg| app.alloc.free(msg); 413 + 414 + const action = app.actions.pop() orelse { 415 + app.notification.write("There is nothing to undo.", .info) catch {}; 416 + return; 417 + }; 418 + 419 + const selected = app.directories.entries.selected; 420 + 421 + switch (action) { 422 + .delete => |a| { 423 + defer app.alloc.free(a.new_path); 424 + defer app.alloc.free(a.prev_path); 425 + 426 + var new_path_buf: [std.fs.max_path_bytes]u8 = undefined; 427 + const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, a.prev_path) catch { 428 + message = try std.fmt.allocPrint(app.alloc, "Failed to undo delete '{s}' - path too long.", .{a.prev_path}); 429 + app.notification.write(message.?, .err) catch {}; 430 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 431 + return; 432 + }; 433 + 434 + app.directories.dir.rename(a.new_path, new_path_res.path) catch |err| { 435 + message = try std.fmt.allocPrint(app.alloc, "Failed to undo delete for '{s}' - {}.", .{ a.prev_path, err }); 436 + app.notification.write(message.?, .err) catch {}; 437 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 438 + return; 439 + }; 440 + 441 + try app.repopulateDirectory(""); 442 + app.text_input.clearAndFree(); 443 + 444 + message = try std.fmt.allocPrint(app.alloc, "Restored '{s}' as '{s}'.", .{ a.prev_path, new_path_res.path }); 445 + app.notification.write(message.?, .info) catch {}; 446 + }, 447 + .rename => |a| { 448 + defer app.alloc.free(a.new_path); 449 + defer app.alloc.free(a.prev_path); 450 + 451 + var new_path_buf: [std.fs.max_path_bytes]u8 = undefined; 452 + const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, a.prev_path) catch { 453 + message = try std.fmt.allocPrint(app.alloc, "Failed to undo rename '{s}' - path too long.", .{a.prev_path}); 454 + app.notification.write(message.?, .err) catch {}; 455 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 456 + return; 457 + }; 458 + 459 + app.directories.dir.rename(a.new_path, new_path_res.path) catch |err| { 460 + message = try std.fmt.allocPrint(app.alloc, "Failed to undo rename for '{s}' - {}.", .{ a.new_path, err }); 461 + app.notification.write(message.?, .err) catch {}; 462 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 463 + return; 464 + }; 465 + 466 + try app.repopulateDirectory(""); 467 + app.text_input.clearAndFree(); 468 + 469 + message = try std.fmt.allocPrint(app.alloc, "Reverted renaming of '{s}', now '{s}'.", .{ a.new_path, new_path_res.path }); 470 + app.notification.write(message.?, .info) catch {}; 471 + }, 472 + .paste => |path| { 473 + defer app.alloc.free(path); 474 + 475 + app.directories.dir.deleteTree(path) catch |err| { 476 + message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - {}.", .{ path, err }); 477 + app.notification.write(message.?, .err) catch {}; 478 + if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 479 + return; 480 + }; 481 + 482 + try app.repopulateDirectory(""); 483 + app.text_input.clearAndFree(); 484 + }, 485 + } 486 + 487 + app.directories.entries.selected = selected; 488 + }
+5 -6
src/git.zig
··· 2 2 3 3 /// Callers owns memory returned. 4 4 pub fn getGitBranch(alloc: std.mem.Allocator, dir: std.fs.Dir) !?[]const u8 { 5 - var file = dir.openFile(".git/HEAD", .{}) catch return null; 5 + var file = try dir.openFile(".git/HEAD", .{}); 6 6 defer file.close(); 7 7 8 8 var buf: [1024]u8 = undefined; 9 - const bytes = file.readAll(&buf) catch return null; 9 + const bytes = try file.readAll(&buf); 10 + if (bytes == 0) return null; 10 11 11 - var it = std.mem.splitBackwardsSequence(u8, buf[0..bytes], "/"); 12 - const branch = it.next() orelse return null; 13 - if (std.mem.eql(u8, branch, "")) return null; 12 + const preamble = "ref: refs/heads/"; 14 13 15 - return try alloc.dupe(u8, branch); 14 + return try alloc.dupe(u8, buf[preamble.len..]); 16 15 }
+12 -3
src/main.zig
··· 53 53 54 54 config.parse(alloc, &app) catch |err| switch (err) { 55 55 error.SyntaxError => { 56 - try app.notification.writeErr(.ConfigSyntaxError); 56 + app.notification.write("Encountered a syntax error while parsing the config file.", .err) catch { 57 + std.log.err("Encountered a syntax error while parsing the config file.", .{}); 58 + }; 57 59 }, 58 60 error.InvalidCharacter => { 59 - try app.notification.writeErr(.InvalidKeybind); 61 + app.notification.write("One or more overriden keybinds are invalid.", .err) catch { 62 + std.log.err("One or more overriden keybinds are invalid.", .{}); 63 + }; 60 64 }, 61 65 error.DuplicateKeybind => { 62 66 // Error logged in function 63 67 }, 64 68 else => { 65 - try app.notification.writeErr(.ConfigUnknownError); 69 + const message = try std.fmt.allocPrint(alloc, "Encountend an unknown error while parsing the config file - {}", .{err}); 70 + defer alloc.free(message); 71 + 72 + app.notification.write(message, .err) catch { 73 + std.log.err("Encountend an unknown error while parsing the config file - {}", .{err}); 74 + }; 66 75 }, 67 76 }; 68 77
-79
src/notification.zig
··· 12 12 warn, 13 13 }; 14 14 15 - const Error = enum { 16 - PermissionDenied, 17 - UnknownError, 18 - UnableToUndo, 19 - UnableToOpenFile, 20 - UnableToDelete, 21 - FailedToDeleteSomeItems, 22 - UnableToDeleteAcrossMountPoints, 23 - UnsupportedImageFormat, 24 - EditorNotSet, 25 - ItemAlreadyExists, 26 - UnableToRename, 27 - IncorrectPath, 28 - ConfigSyntaxError, 29 - ConfigUnknownError, 30 - ConfigPathNotFound, 31 - CannotDeleteTrashDir, 32 - InvalidKeybind, 33 - NotADir, 34 - }; 35 - 36 - const Info = enum { 37 - CreatedFile, 38 - CreatedFolder, 39 - Deleted, 40 - Renamed, 41 - RestoredDelete, 42 - RestoredRename, 43 - EmptyUndo, 44 - ChangedDir, 45 - ConfigReloaded, 46 - }; 47 - 48 - const Warn = enum { DeprecatedConfigPath, DuplicateFileOnUndo }; 49 - 50 15 var buf: [1024]u8 = undefined; 51 16 52 17 style: Style = Style.info, ··· 59 24 _ = try self.fbs.write(text); 60 25 self.timer = std.time.timestamp(); 61 26 self.style = style; 62 - } 63 - 64 - pub fn writeErr(self: *Self, err: Error) !void { 65 - try switch (err) { 66 - .PermissionDenied => self.write("Permission denied.", .err), 67 - .UnknownError => self.write("An unknown error occurred.", .err), 68 - .UnableToOpenFile => self.write("Unable to open file.", .err), 69 - .UnableToDelete => self.write("Unable to delete item.", .err), 70 - .FailedToDeleteSomeItems => self.write("Failed to delete some items..", .err), 71 - .UnableToDeleteAcrossMountPoints => self.write("Unable to move item to /tmp. Failed to delete.", .err), 72 - .UnableToUndo => self.write("Unable to undo previous action.", .err), 73 - .ItemAlreadyExists => self.write("Item already exists.", .err), 74 - .UnableToRename => self.write("Unable to rename item.", .err), 75 - .IncorrectPath => self.write("Unable to find path.", .err), 76 - .NotADir => self.write("Path is not a directory.", .err), 77 - .EditorNotSet => self.write("$EDITOR is not set.", .err), 78 - .UnsupportedImageFormat => self.write("Unsupported image format.", .err), 79 - .ConfigSyntaxError => self.write("Could not read config due to a syntax error.", .err), 80 - .ConfigUnknownError => self.write("Could not read config due to an unknown error.", .err), 81 - .ConfigPathNotFound => self.write("Could not read config due to unset env variables. Please set either $HOME or $XDG_CONFIG_HOME.", .err), 82 - .CannotDeleteTrashDir => self.write("Cannot delete trash directory.", .err), 83 - .InvalidKeybind => self.write("Config has keybind(s) with invalid key(s).", .err), 84 - }; 85 - } 86 - 87 - pub fn writeInfo(self: *Self, info: Info) !void { 88 - try switch (info) { 89 - .CreatedFile => self.write("Successfully created file.", .info), 90 - .CreatedFolder => self.write("Successfully created folder.", .info), 91 - .Deleted => self.write("Successfully deleted item.", .info), 92 - .Renamed => self.write("Successfully renamed item.", .info), 93 - .RestoredDelete => self.write("Successfully restored deleted item.", .info), 94 - .RestoredRename => self.write("Successfully restored renamed item.", .info), 95 - .EmptyUndo => self.write("Nothing to undo.", .info), 96 - .ChangedDir => self.write("Successfully changed directory.", .info), 97 - .ConfigReloaded => self.write("Successfully reloaded config.", .info), 98 - }; 99 - } 100 - 101 - pub fn writeWarn(self: *Self, warning: Warn) !void { 102 - try switch (warning) { 103 - .DeprecatedConfigPath => self.write("You are using a deprecated config path. Please move your config to either `$XDG_CONFIG_HOME/jido` or `$HOME/.jido`", .warn), 104 - .DuplicateFileOnUndo => self.write("A file with the same name already exists. A unique identifier has been appending to the duplicated item.", .warn), 105 - }; 106 27 } 107 28 108 29 pub fn reset(self: *Self) void {