From 5127b3d93446ae0aac0e2a55e08cb15e0b3ceacd Mon Sep 17 00:00:00 2001 From: BrookJeynes Date: Sat, 29 Mar 2025 13:52:35 +1000 Subject: [PATCH] feat: Added keybind `h` to view help / keybind menu. --- README.md | 1 + src/app.zig | 49 ++++++++++++++++++++++++++++++++++++++++++ src/commands.zig | 5 +++++ src/drawer.zig | 3 +++ src/event_handlers.zig | 28 +++++++++++++++++++++++- src/list.zig | 6 ++++++ 6 files changed, 91 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ca41dba..956889f 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ Input mode: Command mode: / :Cycle previous commands. :q :Exit. +:h :View available keybinds. 'q' to return to app. :config :Navigate to config directory if it exists. :trash :Navigate to trash directory if it exists. :empty_trash :Empty trash if it exists. This action cannot be undone. diff --git a/src/app.zig b/src/app.zig index e779a71..ab3a7cd 100644 --- a/src/app.zig +++ b/src/app.zig @@ -14,6 +14,45 @@ const Key = vaxis.Key; const EventHandlers = @import("./event_handlers.zig"); const CommandHistory = @import("./commands.zig").CommandHistory; +const help_menu_items = [_][]const u8{ + "Global:", + " :Exit.", + " :Reload config.", + "", + "Normal mode:", + "j / :Go down.", + "k / :Go up.", + "h / / - :Go to the parent directory.", + "l / :Open item or change directory.", + "g :Go to the top.", + "G :Go to the bottom.", + "c :Change directory via path. Will enter input mode.", + "R :Rename item. Will enter input mode.", + "D :Delete item.", + "u :Undo delete/rename.", + "d :Create directory. Will enter input mode.", + "% :Create file. Will enter input mode.", + "/ :Fuzzy search directory. Will enter input mode.", + ". :Toggle hidden files.", + ": :Allows for Jido commands to be entered. Please refer to the ", + " \"Command mode\" section for available commands. Will enter ", + " input mode.", + "v :Verbose mode. Provides more information about selected entry. ", + "", + "Input mode:", + " :Cancel input.", + " :Confirm input.", + "", + "Command mode:", + " / :Cycle previous commands.", + ":q :Exit.", + ":h :View available keybinds. 'q' to return to app.", + ":config :Navigate to config directory if it exists.", + ":trash :Navigate to trash directory if it exists.", + ":empty_trash :Empty trash if it exists. This action cannot be undone.", + ":cd :Change directory via path. Will enter input mode.", +}; + pub const State = enum { normal, fuzzy, @@ -22,6 +61,7 @@ pub const State = enum { change_dir, rename, command, + help_menu, }; const ActionPaths = struct { @@ -54,6 +94,7 @@ actions: CircStack(Action, actions_len), command_history: CommandHistory = CommandHistory{}, drawer: Drawer = Drawer{}, +help_menu: List([]const u8), directories: Directories, notification: Notification = Notification{}, // Assigned in main after config parsing. @@ -76,12 +117,16 @@ pub fn init(alloc: std.mem.Allocator) !App { }, }); + var help_menu = List([]const u8).init(alloc); + try help_menu.fromArray(&help_menu_items); + return App{ .alloc = alloc, .should_quit = false, .vx = vx, .tty = try vaxis.Tty.init(), .directories = try Directories.init(alloc), + .help_menu = help_menu, .text_input = vaxis.widgets.TextInput.init(alloc, &vx.unicode), .actions = CircStack(Action, actions_len).init(), .last_known_height = vx.window().height, @@ -103,6 +148,7 @@ pub fn deinit(self: *App) void { self.alloc.free(command); } + self.help_menu.deinit(); self.directories.deinit(); self.text_input.deinit(); self.vx.deinit(self.alloc, self.tty.anyWriter()); @@ -161,6 +207,9 @@ pub fn run(self: *App) !void { .normal => { try EventHandlers.handleNormalEvent(self, event, &loop); }, + .help_menu => { + try EventHandlers.handleHelpMenuEvent(self, event); + }, else => { try EventHandlers.handleInputEvent(self, event); }, diff --git a/src/commands.zig b/src/commands.zig index 50e1ea7..79c9fa6 100644 --- a/src/commands.zig +++ b/src/commands.zig @@ -148,3 +148,8 @@ pub fn cd(app: *App, path: []const u8) !void { } } } + +///Display help menu. +pub fn displayHelpMenu(app: *App) !void { + app.state = .help_menu; +} diff --git a/src/drawer.zig b/src/drawer.zig index a2b314d..bb512e8 100644 --- a/src/drawer.zig +++ b/src/drawer.zig @@ -532,6 +532,9 @@ fn drawUserInput( win.hideCursor(); }, + .help_menu => { + win.hideCursor(); + }, } } diff --git a/src/event_handlers.zig b/src/event_handlers.zig index 51cd487..ca76d01 100644 --- a/src/event_handlers.zig +++ b/src/event_handlers.zig @@ -518,6 +518,11 @@ pub fn handleInputEvent(app: *App, event: App.Event) !void { break :supported; } + if (std.mem.eql(u8, command, ":h")) { + try commands.displayHelpMenu(app); + break :supported; + } + app.text_input.clearAndFree(); try app.text_input.insertSliceAtCursor(":UnsupportedCommand"); } @@ -526,7 +531,10 @@ pub fn handleInputEvent(app: *App, event: App.Event) !void { }, else => {}, } - app.state = .normal; + + // TODO(2025-03-29): Could there be a better way to check + // this? + if (app.state != .help_menu) app.state = .normal; app.directories.entries.selected = selected; }, Key.up => { @@ -576,3 +584,21 @@ pub fn handleInputEvent(app: *App, event: App.Event) !void { .winsize => |ws| try app.vx.resize(app.alloc, app.tty.anyWriter(), ws), } } + +pub fn handleHelpMenuEvent(app: *App, event: App.Event) !void { + switch (event) { + .key_press => |key| { + switch (key.codepoint) { + Key.escape, 'q' => app.state = .normal, + 'j', Key.down => { + app.help_menu.next(); + }, + 'k', Key.up => { + app.help_menu.previous(); + }, + else => {}, + } + }, + .winsize => |ws| try app.vx.resize(app.alloc, app.tty.anyWriter(), ws), + } +} diff --git a/src/list.zig b/src/list.zig index 22ba18c..a8e49d7 100644 --- a/src/list.zig +++ b/src/list.zig @@ -30,6 +30,12 @@ pub fn List(comptime T: type) type { self.selected = 0; } + pub fn fromArray(self: *Self, array: []const T) !void { + for (array) |item| { + try self.append(item); + } + } + pub fn get(self: Self, index: usize) !T { if (index + 1 > self.len()) { return error.OutOfBounds; -- 2.51.2