地圖 (Jido) is a lightweight Unix TUI file explorer designed for speed and simplicity.
at main 14 kB view raw
1const std = @import("std"); 2const App = @import("./app.zig"); 3const environment = @import("./environment.zig"); 4const zuid = @import("zuid"); 5const vaxis = @import("vaxis"); 6const Key = vaxis.Key; 7const config = &@import("./config.zig").config; 8const commands = @import("./commands.zig"); 9const Keybinds = @import("./config.zig").Keybinds; 10const events = @import("./events.zig"); 11 12pub fn handleGlobalEvent( 13 app: *App, 14 event: App.Event, 15) error{OutOfMemory}!void { 16 switch (event) { 17 .key_press => |key| { 18 if ((key.codepoint == 'c' and key.mods.ctrl)) { 19 app.should_quit = true; 20 return; 21 } 22 23 if ((key.codepoint == 'r' and key.mods.ctrl)) { 24 if (config.parse(app.alloc, app)) { 25 app.notification.write("Reloaded configuration file.", .info) catch {}; 26 } else |err| switch (err) { 27 error.SyntaxError => { 28 app.notification.write("Encountered a syntax error while parsing the config file.", .err) catch { 29 std.log.err("Encountered a syntax error while parsing the config file.", .{}); 30 }; 31 }, 32 error.InvalidCharacter => { 33 app.notification.write("One or more overriden keybinds are invalid.", .err) catch { 34 std.log.err("One or more overriden keybinds are invalid.", .{}); 35 }; 36 }, 37 error.DuplicateKeybind => { 38 // Error logged in function 39 }, 40 else => { 41 const message = try std.fmt.allocPrint(app.alloc, "Encountend an unknown error while parsing the config file - {}", .{err}); 42 defer app.alloc.free(message); 43 44 app.notification.write(message, .err) catch { 45 std.log.err("Encountend an unknown error while parsing the config file - {}", .{err}); 46 }; 47 }, 48 } 49 } 50 }, 51 else => {}, 52 } 53} 54 55pub fn handleNormalEvent( 56 app: *App, 57 event: App.Event, 58) !void { 59 switch (event) { 60 .key_press => |key| { 61 @setEvalBranchQuota( 62 std.meta.fields(Keybinds).len * 1000, 63 ); 64 65 const maybe_remap: ?std.meta.FieldEnum(Keybinds) = lbl: { 66 inline for (std.meta.fields(Keybinds)) |field| { 67 if (@field(config.keybinds, field.name)) |field_value| { 68 if (key.codepoint == @intFromEnum(field_value)) { 69 break :lbl comptime std.meta.stringToEnum(std.meta.FieldEnum(Keybinds), field.name) orelse unreachable; 70 } 71 } 72 } 73 break :lbl null; 74 }; 75 76 if (maybe_remap) |action| { 77 switch (action) { 78 .toggle_hidden_files => try events.toggleHiddenFiles(app), 79 .delete => try events.delete(app), 80 .rename => { 81 const entry = (app.directories.getSelected() catch { 82 app.notification.write("Can not rename item - no item selected.", .warn) catch {}; 83 return; 84 }) orelse return; 85 86 app.text_input.clearAndFree(); 87 88 // Try insert entry name into text input for a nicer experience. 89 // This failing shouldn't stop the user from entering a new name. 90 app.text_input.insertSliceAtCursor(entry.name) catch {}; 91 app.state = .rename; 92 }, 93 94 .create_dir => { 95 try app.repopulateDirectory(""); 96 app.text_input.clearAndFree(); 97 app.state = .new_dir; 98 }, 99 .create_file => { 100 try app.repopulateDirectory(""); 101 app.text_input.clearAndFree(); 102 app.state = .new_file; 103 }, 104 .fuzzy_find => { 105 app.text_input.clearAndFree(); 106 app.state = .fuzzy; 107 }, 108 .change_dir => { 109 app.text_input.clearAndFree(); 110 app.state = .change_dir; 111 }, 112 .enter_command_mode => { 113 app.text_input.clearAndFree(); 114 app.text_input.insertSliceAtCursor(":") catch {}; 115 app.state = .command; 116 }, 117 .jump_bottom => app.directories.entries.selectLast(), 118 .jump_top => app.directories.entries.selectFirst(), 119 .toggle_verbose_file_information => app.drawer.verbose = !app.drawer.verbose, 120 .force_delete => try events.forceDelete(app), 121 .yank => try events.yank(app), 122 .paste => try events.paste(app), 123 } 124 } else { 125 switch (key.codepoint) { 126 '-', 'h', Key.left => try events.traverseLeft(app), 127 Key.enter, 'l', Key.right => try events.traverseRight(app), 128 'j', Key.down => app.directories.entries.next(), 129 'k', Key.up => app.directories.entries.previous(), 130 'u' => try events.undo(app), 131 else => {}, 132 } 133 } 134 }, 135 .image_ready => {}, 136 .notification => {}, 137 .winsize => |ws| try app.vx.resize(app.alloc, app.tty.writer(), ws), 138 } 139} 140 141pub fn handleInputEvent(app: *App, event: App.Event) !void { 142 switch (event) { 143 .key_press => |key| { 144 switch (key.codepoint) { 145 Key.escape => { 146 switch (app.state) { 147 .fuzzy => { 148 try app.repopulateDirectory(""); 149 app.text_input.clearAndFree(); 150 }, 151 .command => app.command_history.cursor = null, 152 else => {}, 153 } 154 155 app.text_input.clearAndFree(); 156 app.state = .normal; 157 }, 158 Key.enter => { 159 const selected = app.directories.entries.selected; 160 switch (app.state) { 161 .new_dir => try events.createNewDir(app), 162 .new_file => try events.createNewFile(app), 163 .rename => try events.rename(app), 164 .change_dir => { 165 const path = app.inputToSlice(); 166 try commands.cd(app, path); 167 app.text_input.clearAndFree(); 168 }, 169 .command => { 170 const command = app.inputToSlice(); 171 172 // Push command to history if it's not empty. 173 if (!std.mem.eql(u8, std.mem.trim(u8, command, " "), ":")) { 174 app.command_history.add(command, app.alloc) catch |err| { 175 const message = try std.fmt.allocPrint(app.alloc, "Failed to add command to history - {}.", .{err}); 176 defer app.alloc.free(message); 177 if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 178 }; 179 } 180 181 supported: { 182 if (std.mem.eql(u8, command, ":q")) { 183 app.should_quit = true; 184 return; 185 } 186 187 if (std.mem.eql(u8, command, ":config")) { 188 try commands.config(app); 189 break :supported; 190 } 191 192 if (std.mem.eql(u8, command, ":trash")) { 193 try commands.trash(app); 194 break :supported; 195 } 196 197 if (std.mem.startsWith(u8, command, ":cd ")) { 198 try commands.cd(app, command[":cd ".len..]); 199 break :supported; 200 } 201 202 if (std.mem.eql(u8, command, ":empty_trash")) { 203 try commands.emptyTrash(app); 204 break :supported; 205 } 206 207 if (std.mem.eql(u8, command, ":h")) { 208 app.state = .help_menu; 209 break :supported; 210 } 211 212 app.text_input.clearAndFree(); 213 try app.text_input.insertSliceAtCursor(":UnsupportedCommand"); 214 } 215 216 app.command_history.cursor = null; 217 }, 218 else => {}, 219 } 220 221 if (app.state != .help_menu) app.state = .normal; 222 app.directories.entries.selected = selected; 223 }, 224 Key.left => app.text_input.cursorLeft(), 225 Key.right => app.text_input.cursorRight(), 226 Key.up => { 227 if (app.state == .command) { 228 if (app.command_history.previous()) |command| { 229 app.text_input.clearAndFree(); 230 app.text_input.insertSliceAtCursor(command) catch |err| { 231 const message = try std.fmt.allocPrint(app.alloc, "Failed to get previous command history - {}.", .{err}); 232 defer app.alloc.free(message); 233 app.notification.write(message, .err) catch {}; 234 if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 235 }; 236 } 237 } 238 }, 239 Key.down => { 240 if (app.state == .command) { 241 app.text_input.clearAndFree(); 242 if (app.command_history.next()) |command| { 243 app.text_input.insertSliceAtCursor(command) catch |err| { 244 const message = try std.fmt.allocPrint(app.alloc, "Failed to get next command history - {}.", .{err}); 245 defer app.alloc.free(message); 246 app.notification.write(message, .err) catch {}; 247 if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 248 }; 249 } else { 250 app.text_input.insertSliceAtCursor(":") catch |err| { 251 const message = try std.fmt.allocPrint(app.alloc, "Failed to get next command history - {}.", .{err}); 252 defer app.alloc.free(message); 253 app.notification.write(message, .err) catch {}; 254 if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 255 }; 256 } 257 } 258 }, 259 else => { 260 try app.text_input.update(.{ .key_press = key }); 261 262 switch (app.state) { 263 .fuzzy => { 264 const fuzzy = app.inputToSlice(); 265 try app.repopulateDirectory(fuzzy); 266 }, 267 .command => { 268 const command = app.inputToSlice(); 269 if (!std.mem.startsWith(u8, command, ":")) { 270 app.text_input.clearAndFree(); 271 app.text_input.insertSliceAtCursor(":") catch |err| { 272 app.state = .normal; 273 274 const message = try std.fmt.allocPrint(app.alloc, "An input error occurred while attempting to enter a command - {}.", .{err}); 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 }; 279 } 280 }, 281 else => {}, 282 } 283 }, 284 } 285 }, 286 .image_ready => {}, 287 .notification => {}, 288 .winsize => |ws| try app.vx.resize(app.alloc, app.tty.writer(), ws), 289 } 290} 291 292pub fn handleHelpMenuEvent(app: *App, event: App.Event) !void { 293 switch (event) { 294 .key_press => |key| { 295 switch (key.codepoint) { 296 Key.escape, 'q' => app.state = .normal, 297 'j', Key.down => app.help_menu.next(), 298 'k', Key.up => app.help_menu.previous(), 299 else => {}, 300 } 301 }, 302 .image_ready => {}, 303 .notification => {}, 304 .winsize => |ws| try app.vx.resize(app.alloc, app.tty.writer(), ws), 305 } 306}