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

feat: Added keybind `h` to view help / keybind menu.

+91 -1
+1
README.md
··· 63 63 Command mode: 64 64 <Up> / <Down> :Cycle previous commands. 65 65 :q :Exit. 66 + :h :View available keybinds. 'q' to return to app. 66 67 :config :Navigate to config directory if it exists. 67 68 :trash :Navigate to trash directory if it exists. 68 69 :empty_trash :Empty trash if it exists. This action cannot be undone.
+49
src/app.zig
··· 14 14 const EventHandlers = @import("./event_handlers.zig"); 15 15 const CommandHistory = @import("./commands.zig").CommandHistory; 16 16 17 + const help_menu_items = [_][]const u8{ 18 + "Global:", 19 + "<CTRL-c> :Exit.", 20 + "<CTRL-r> :Reload config.", 21 + "", 22 + "Normal mode:", 23 + "j / <Down> :Go down.", 24 + "k / <Up> :Go up.", 25 + "h / <Left> / - :Go to the parent directory.", 26 + "l / <Right> :Open item or change directory.", 27 + "g :Go to the top.", 28 + "G :Go to the bottom.", 29 + "c :Change directory via path. Will enter input mode.", 30 + "R :Rename item. Will enter input mode.", 31 + "D :Delete item.", 32 + "u :Undo delete/rename.", 33 + "d :Create directory. Will enter input mode.", 34 + "% :Create file. Will enter input mode.", 35 + "/ :Fuzzy search directory. Will enter input mode.", 36 + ". :Toggle hidden files.", 37 + ": :Allows for Jido commands to be entered. Please refer to the ", 38 + " \"Command mode\" section for available commands. Will enter ", 39 + " input mode.", 40 + "v :Verbose mode. Provides more information about selected entry. ", 41 + "", 42 + "Input mode:", 43 + "<Esc> :Cancel input.", 44 + "<CR> :Confirm input.", 45 + "", 46 + "Command mode:", 47 + "<Up> / <Down> :Cycle previous commands.", 48 + ":q :Exit.", 49 + ":h :View available keybinds. 'q' to return to app.", 50 + ":config :Navigate to config directory if it exists.", 51 + ":trash :Navigate to trash directory if it exists.", 52 + ":empty_trash :Empty trash if it exists. This action cannot be undone.", 53 + ":cd <path> :Change directory via path. Will enter input mode.", 54 + }; 55 + 17 56 pub const State = enum { 18 57 normal, 19 58 fuzzy, ··· 22 61 change_dir, 23 62 rename, 24 63 command, 64 + help_menu, 25 65 }; 26 66 27 67 const ActionPaths = struct { ··· 54 94 command_history: CommandHistory = CommandHistory{}, 55 95 drawer: Drawer = Drawer{}, 56 96 97 + help_menu: List([]const u8), 57 98 directories: Directories, 58 99 notification: Notification = Notification{}, 59 100 // Assigned in main after config parsing. ··· 76 117 }, 77 118 }); 78 119 120 + var help_menu = List([]const u8).init(alloc); 121 + try help_menu.fromArray(&help_menu_items); 122 + 79 123 return App{ 80 124 .alloc = alloc, 81 125 .should_quit = false, 82 126 .vx = vx, 83 127 .tty = try vaxis.Tty.init(), 84 128 .directories = try Directories.init(alloc), 129 + .help_menu = help_menu, 85 130 .text_input = vaxis.widgets.TextInput.init(alloc, &vx.unicode), 86 131 .actions = CircStack(Action, actions_len).init(), 87 132 .last_known_height = vx.window().height, ··· 103 148 self.alloc.free(command); 104 149 } 105 150 151 + self.help_menu.deinit(); 106 152 self.directories.deinit(); 107 153 self.text_input.deinit(); 108 154 self.vx.deinit(self.alloc, self.tty.anyWriter()); ··· 160 206 switch (self.state) { 161 207 .normal => { 162 208 try EventHandlers.handleNormalEvent(self, event, &loop); 209 + }, 210 + .help_menu => { 211 + try EventHandlers.handleHelpMenuEvent(self, event); 163 212 }, 164 213 else => { 165 214 try EventHandlers.handleInputEvent(self, event);
+5
src/commands.zig
··· 148 148 } 149 149 } 150 150 } 151 + 152 + ///Display help menu. 153 + pub fn displayHelpMenu(app: *App) !void { 154 + app.state = .help_menu; 155 + }
+3
src/drawer.zig
··· 532 532 533 533 win.hideCursor(); 534 534 }, 535 + .help_menu => { 536 + win.hideCursor(); 537 + }, 535 538 } 536 539 } 537 540
+27 -1
src/event_handlers.zig
··· 518 518 break :supported; 519 519 } 520 520 521 + if (std.mem.eql(u8, command, ":h")) { 522 + try commands.displayHelpMenu(app); 523 + break :supported; 524 + } 525 + 521 526 app.text_input.clearAndFree(); 522 527 try app.text_input.insertSliceAtCursor(":UnsupportedCommand"); 523 528 } ··· 526 531 }, 527 532 else => {}, 528 533 } 529 - app.state = .normal; 534 + 535 + // TODO(2025-03-29): Could there be a better way to check 536 + // this? 537 + if (app.state != .help_menu) app.state = .normal; 530 538 app.directories.entries.selected = selected; 531 539 }, 532 540 Key.up => { ··· 576 584 .winsize => |ws| try app.vx.resize(app.alloc, app.tty.anyWriter(), ws), 577 585 } 578 586 } 587 + 588 + pub fn handleHelpMenuEvent(app: *App, event: App.Event) !void { 589 + switch (event) { 590 + .key_press => |key| { 591 + switch (key.codepoint) { 592 + Key.escape, 'q' => app.state = .normal, 593 + 'j', Key.down => { 594 + app.help_menu.next(); 595 + }, 596 + 'k', Key.up => { 597 + app.help_menu.previous(); 598 + }, 599 + else => {}, 600 + } 601 + }, 602 + .winsize => |ws| try app.vx.resize(app.alloc, app.tty.anyWriter(), ws), 603 + } 604 + }
+6
src/list.zig
··· 30 30 self.selected = 0; 31 31 } 32 32 33 + pub fn fromArray(self: *Self, array: []const T) !void { 34 + for (array) |item| { 35 + try self.append(item); 36 + } 37 + } 38 + 33 39 pub fn get(self: Self, index: usize) !T { 34 40 if (index + 1 > self.len()) { 35 41 return error.OutOfBounds;