From d4019d00b249df245f6f312079d36f681f82e6d6 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 20 Jun 2024 22:26:09 +0200 Subject: [PATCH] feat: add keybind hints to command palette --- src/tui/home.zig | 4 +- src/tui/mode/input/flow.zig | 115 ++++++++++++++- src/tui/mode/input/home.zig | 33 ++++- src/tui/mode/input/vim/insert.zig | 5 +- src/tui/mode/input/vim/normal.zig | 178 ++++++++++++++++------- src/tui/mode/input/vim/visual.zig | 176 +++++++++++++++------- src/tui/mode/overlay/command_palette.zig | 18 ++- src/tui/status/filestate.zig | 2 +- src/tui/status/modestate.zig | 2 +- src/tui/tui.zig | 28 ++-- 10 files changed, 422 insertions(+), 139 deletions(-) diff --git a/src/tui/home.zig b/src/tui/home.zig index 318b136..2567c12 100644 --- a/src/tui/home.zig +++ b/src/tui/home.zig @@ -111,7 +111,7 @@ fn menu_action_open_file(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*S } fn menu_action_open_recent_file(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { - command.executeName("enter_overlay_mode", command.fmt(.{"open_recent"})) catch {}; + command.executeName("open_recent", .{}) catch {}; } fn menu_action_open_recent_project(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { @@ -119,7 +119,7 @@ fn menu_action_open_recent_project(_: **Menu.State(*Self), _: *Button.State(*Men } fn menu_action_show_commands(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { - command.executeName("enter_overlay_mode", command.fmt(.{"command_palette"})) catch {}; + command.executeName("open_command_palette", .{}) catch {}; } fn menu_action_open_config(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { diff --git a/src/tui/mode/input/flow.zig b/src/tui/mode/input/flow.zig index 0698198..00defba 100644 --- a/src/tui/mode/input/flow.zig +++ b/src/tui/mode/input/flow.zig @@ -33,6 +33,7 @@ pub fn create(a: Allocator) !tui.Mode { .handler = EventHandler.to_owned(self), .name = root.application_logo ++ root.application_name, .description = "default", + .keybind_hints = &hints, }; } @@ -79,7 +80,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { } return switch (modifiers) { mod.CTRL => switch (keynormal) { - 'E' => self.cmd("enter_overlay_mode", command.fmt(.{"open_recent"})), + 'E' => self.cmd("open_recent", .{}), 'J' => self.cmd("toggle_logview", .{}), 'Z' => self.cmd("undo", .{}), 'Y' => self.cmd("redo", .{}), @@ -120,7 +121,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { else => {}, }, mod.CTRL | mod.SHIFT => switch (keynormal) { - 'P' => self.cmd("enter_overlay_mode", command.fmt(.{"command_palette"})), + 'P' => self.cmd("open_command_palette", .{}), 'D' => self.cmd("dupe_down", .{}), 'Z' => self.cmd("redo", .{}), 'Q' => self.cmd("quit_without_saving", .{}), @@ -129,7 +130,6 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { 'F' => self.cmd("enter_find_in_files_mode", .{}), 'L' => self.cmd_async("add_cursor_all_matches"), 'I' => self.cmd_async("toggle_inspector_view"), - '/' => self.cmd("log_widgets", .{}), key.ENTER => self.cmd("smart_insert_line_before", .{}), key.END => self.cmd("select_buffer_end", .{}), key.HOME => self.cmd("select_buffer_begin", .{}), @@ -302,3 +302,112 @@ fn cmd_async(self: *Self, name_: []const u8) tp.result { self.last_cmd = name_; return tp.self_pid().send(.{ "cmd", name_ }); } + +const hints = tui.KeybindHints.initComptime(.{ + .{ "add_cursor_all_matches", "C-S-l" }, + .{ "add_cursor_down", "S-A-down" }, + .{ "add_cursor_next_match", "C-d" }, + .{ "add_cursors_to_line_ends", "S-A-i" }, + .{ "add_cursor_up", "S-A-up" }, + .{ "cancel", "esc" }, + .{ "close_file", "C-w" }, + .{ "close_file_without_saving", "C-S-w" }, + .{ "copy", "C-c" }, + .{ "cut", "C-x" }, + .{ "delete_backward", "backspace" }, + .{ "delete_forward", "del" }, + .{ "delete_to_begin", "C-k C-u" }, + .{ "delete_to_end", "C-k C-k" }, + .{ "delete_word_left", "C-backspace" }, + .{ "delete_word_right", "C-del" }, + .{ "dump_current_line", "F7" }, + .{ "dump_current_line_tree", "F6" }, + .{ "dupe_down", "C-S-d" }, + .{ "dupe_up", "S-A-d" }, + .{ "enable_fast_scroll", "hold Ctrl" }, + .{ "enable_jump_mode", "hold Alt" }, + .{ "enter_find_in_files_mode", "C-S-f" }, + .{ "enter_find_mode", "C-f" }, + .{ "enter_goto_mode", "C-g" }, + .{ "enter_move_to_char_mode", "C-b, C-t" }, // true/false + .{ "enter_open_file_mode", "C-o" }, + .{ "filter", "A-s" }, // self.cmd("filter", command.fmt(.{"sort"})), + // .{ "filter", "S-A-s" }, // self.cmd("filter", command.fmt(.{ "sort", "-u" })), + .{ "format", "S-A-f" }, + .{ "goto_definition", "F12" }, + .{ "goto_next_diagnostic", "A-n" }, + .{ "goto_next_match", "C-n, F3" }, + .{ "goto_prev_diagnostic", "A-p" }, + .{ "goto_prev_match", "C-p, S-F3" }, + .{ "gutter_mode_next", "A-F10" }, + .{ "indent", "tab" }, + .{ "insert_line", "A-enter" }, + .{ "join_next_line", "A-j" }, + .{ "jump_back", "A-left" }, + .{ "jump_forward", "A-right" }, + .{ "move_buffer_begin", "C-home" }, + .{ "move_buffer_end", "C-end" }, + .{ "move_cursor_next_match", "C-k C-d" }, + .{ "move_down", "down" }, + .{ "move_end", "end" }, + .{ "move_left", "left" }, + .{ "move_page_down", "pgdn" }, + .{ "move_page_up", "pgup" }, + .{ "move_right", "right" }, + .{ "move_scroll_down", "C-down" }, + .{ "move_scroll_left", "S-A-left" }, + .{ "move_scroll_page_down", "C-pgdn" }, + .{ "move_scroll_page_up", "C-pgup" }, + .{ "move_scroll_right", "S-A-right" }, + .{ "move_scroll_up", "C-up" }, + .{ "move_up", "up" }, + .{ "move_word_left", "C-left, A-b" }, + .{ "move_word_right", "C-right, A-f" }, + .{ "open_command_palette", "C-S-p" }, + .{ "open_recent", "C-e" }, + .{ "paste", "A-v" }, + .{ "pop_cursor", "C-u" }, + .{ "pull_down", "A-down" }, + .{ "pull_up", "A-up" }, + .{ "quit", "C-q" }, + .{ "quit_without_saving", "C-S-q" }, + .{ "redo", "C-S-z, C-y" }, + .{ "restart", "C-S-r" }, + .{ "save_file", "C-s" }, + .{ "scroll_view_bottom", "C-l" }, + .{ "scroll_view_center", "C-l" }, + .{ "scroll_view_top", "C-l" }, + .{ "select_all", "C-a" }, + .{ "select_buffer_begin", "C-S-home" }, + .{ "select_buffer_end", "C-S-end" }, + .{ "select_down", "S-down" }, + .{ "select_end", "S-end" }, + .{ "selections_reverse", "C-space" }, + .{ "select_left", "S-left" }, + .{ "select_page_down", "S-pgdn" }, + .{ "select_page_up", "S-pgup" }, + .{ "select_right", "S-right" }, + .{ "select_scroll_down", "C-S-down" }, + .{ "select_scroll_up", "C-S-up" }, + .{ "select_up", "S-up" }, + .{ "select_word_left", "C-S-left" }, + .{ "select_word_right", "C-S-right" }, + .{ "smart_insert_line_after", "C-enter" }, + .{ "smart_insert_line_before", "S-enter, C-S-enter" }, + .{ "smart_insert_line", "enter" }, + .{ "smart_move_begin", "home" }, + .{ "smart_select_begin", "S-home" }, + .{ "system_paste", "C-v" }, + .{ "theme_next", "F10" }, + .{ "theme_prev", "F9" }, + .{ "toggle_comment", "C-/" }, + .{ "toggle_input_mode", "F2" }, + .{ "toggle_inputview", "A-i" }, + .{ "toggle_inspector_view", "F5, C-F5, C-S-i" }, + .{ "toggle_logview", "C-j, F11" }, + .{ "toggle_whitespace", "C-F10" }, + .{ "to_lower", "A-l" }, + .{ "to_upper", "A-u" }, + .{ "undo", "C-z" }, + .{ "unindent", "S-tab" }, +}); diff --git a/src/tui/mode/input/home.zig b/src/tui/mode/input/home.zig index 5fb560c..1a7fbb7 100644 --- a/src/tui/mode/input/home.zig +++ b/src/tui/mode/input/home.zig @@ -24,6 +24,7 @@ pub fn create(a: std.mem.Allocator) !tui.Mode { .handler = EventHandler.to_owned(self), .name = root.application_logo ++ root.application_name, .description = "home", + .keybind_hints = &hints, }; } @@ -59,13 +60,13 @@ fn mapPress(self: *Self, keypress: u32, modifiers: u32) tp.result { 'Q' => self.cmd("quit", .{}), 'W' => self.cmd("quit", .{}), 'O' => self.cmd("enter_open_file_mode", .{}), - 'E' => self.cmd("enter_overlay_mode", command.fmt(.{"open_recent"})), - 'P' => self.cmd("enter_overlay_mode", command.fmt(.{"command_palette"})), + 'E' => self.cmd("open_recent", .{}), + 'P' => self.cmd("open_command_palette", .{}), '/' => self.cmd("open_help", .{}), else => {}, }, mod.CTRL | mod.SHIFT => switch (keynormal) { - 'P' => self.cmd("enter_overlay_mode", command.fmt(.{"command_palette"})), + 'P' => self.cmd("open_command_palette", .{}), 'Q' => self.cmd("quit_without_saving", .{}), 'R' => self.cmd("restart", .{}), 'F' => self.cmd("enter_find_in_files_mode", .{}), @@ -84,9 +85,9 @@ fn mapPress(self: *Self, keypress: u32, modifiers: u32) tp.result { 0 => switch (keypress) { 'h' => self.cmd("open_help", .{}), 'o' => self.cmd("enter_open_file_mode", .{}), - 'e' => self.cmd("enter_overlay_mode", command.fmt(.{"open_recent"})), + 'e' => self.cmd("open_recent", .{}), 'r' => self.msg("open recent project not implemented"), - 'p' => self.cmd("enter_overlay_mode", command.fmt(.{"command_palette"})), + 'p' => self.cmd("open_command_palette", .{}), 'c' => self.cmd("open_config", .{}), 'q' => self.cmd("quit", .{}), @@ -124,3 +125,25 @@ fn sheeran(self: *Self) void { self.cmd("home_sheeran", .{}) catch {}; } } + +const hints = tui.KeybindHints.initComptime(.{ + .{ "enter_find_in_files_mode", "C-S-f" }, + .{ "enter_open_file_mode", "o, C-o" }, + .{ "open_recent", "e, C-e" }, + .{ "open_command_palette", "p, C-S-p" }, + .{ "home_menu_activate", "enter" }, + .{ "home_menu_down", "down" }, + .{ "home_menu_up", "up" }, + .{ "jump_back", "A-left" }, + .{ "jump_forward", "A-right" }, + .{ "open_config", "c, F6" }, + .{ "open_help", "C-/, C-S-/" }, + .{ "open_help", "h, F1" }, + .{ "quit", "q, C-q, C-w" }, + .{ "quit_without_saving", "C-S-q" }, + .{ "restart", "C-S-r" }, + .{ "theme_next", "F10" }, + .{ "theme_prev", "F9" }, + .{ "toggle_inputview", "F12, A-i, C-S-i" }, + .{ "toggle_logview", "F11, C-j, A-l, C-S-l" }, +}); diff --git a/src/tui/mode/input/vim/insert.zig b/src/tui/mode/input/vim/insert.zig index 7f91f62..c6969e1 100644 --- a/src/tui/mode/input/vim/insert.zig +++ b/src/tui/mode/input/vim/insert.zig @@ -82,7 +82,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { } return switch (modifiers) { mod.CTRL => switch (keynormal) { - 'E' => self.cmd("enter_overlay_mode", command.fmt(.{"open_recent"})), + 'E' => self.cmd("open_recent", .{}), 'U' => self.cmd("move_scroll_page_up", .{}), 'D' => self.cmd("move_scroll_page_down", .{}), 'J' => self.cmd("toggle_logview", .{}), @@ -123,7 +123,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { else => {}, }, mod.CTRL | mod.SHIFT => switch (keynormal) { - 'P' => self.cmd("enter_overlay_mode", command.fmt(.{"command_palette"})), + 'P' => self.cmd("open_command_palette", .{}), 'D' => self.cmd("dupe_down", .{}), 'Z' => self.cmd("redo", .{}), 'Q' => self.cmd("quit_without_saving", .{}), @@ -132,7 +132,6 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { 'F' => self.cmd("enter_find_in_files_mode", .{}), 'L' => self.cmd_async("add_cursor_all_matches"), 'I' => self.cmd_async("toggle_inspector_view"), - '/' => self.cmd("log_widgets", .{}), key.ENTER => self.cmd("smart_insert_line_before", .{}), key.END => self.cmd("select_buffer_end", .{}), key.HOME => self.cmd("select_buffer_begin", .{}), diff --git a/src/tui/mode/input/vim/normal.zig b/src/tui/mode/input/vim/normal.zig index 9e70328..4c8f371 100644 --- a/src/tui/mode/input/vim/normal.zig +++ b/src/tui/mode/input/vim/normal.zig @@ -35,6 +35,7 @@ pub fn create(a: Allocator) !tui.Mode { .name = root.application_logo ++ "NORMAL", .description = "vim", .line_numbers = if (tui.current().config.vim_normal_gutter_line_numbers_relative) .relative else .absolute, + .keybind_hints = &hints, }; } @@ -74,6 +75,7 @@ fn mapEvent(self: *Self, evtype: u32, keypress: u32, egc: u32, modifiers: u32) t } fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { + if (self.count > 0 and modifiers == 0 and '0' <= keypress and keypress <= '9') return self.add_count(keypress - '0'); const keynormal = if ('a' <= keypress and keypress <= 'z') keypress - ('a' - 'A') else keypress; if (self.leader) |_| return self.mapFollower(keynormal, egc, modifiers); switch (keypress) { @@ -83,7 +85,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { } return switch (modifiers) { mod.CTRL => switch (keynormal) { - 'E' => self.cmd("enter_overlay_mode", command.fmt(.{"open_recent"})), + 'E' => self.cmd("open_recent", .{}), 'U' => self.cmd("move_scroll_page_up", .{}), 'D' => self.cmd("move_scroll_page_down", .{}), 'R' => self.cmd("redo", .{}), @@ -126,7 +128,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { else => {}, }, mod.CTRL | mod.SHIFT => switch (keynormal) { - 'P' => self.cmd("enter_overlay_mode", command.fmt(.{"command_palette"})), + 'P' => self.cmd("open_command_palette", .{}), 'D' => self.cmd("dupe_down", .{}), 'Z' => self.cmd("redo", .{}), 'Q' => self.cmd("quit_without_saving", .{}), @@ -135,7 +137,6 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { 'F' => self.cmd("enter_find_in_files_mode", .{}), 'L' => self.cmd_async("add_cursor_all_matches"), 'I' => self.cmd_async("toggle_inspector_view"), - '/' => self.cmd("log_widgets", .{}), key.ENTER => self.cmd("smart_insert_line_before", .{}), key.END => self.cmd("select_buffer_end", .{}), key.HOME => self.cmd("select_buffer_begin", .{}), @@ -166,8 +167,6 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { }, mod.ALT | mod.SHIFT => switch (keynormal) { 'D' => self.cmd("dupe_up", .{}), - // 'B' => self.cmd("select_word_left", .{}), - // 'F' => self.cmd("select_word_right", .{}), 'F' => self.cmd("filter", command.fmt(.{ "zig", "fmt", "--stdin" })), 'S' => self.cmd("filter", command.fmt(.{ "sort", "-u" })), 'V' => self.cmd("paste", .{}), @@ -192,11 +191,11 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { key.BACKSPACE => self.cmd("delete_backward", .{}), key.TAB => self.cmd("unindent", .{}), - ';' => self.cmd("enter_overlay_mode", command.fmt(.{"command_palette"})), - 'N' => self.cmd("goto_prev_match", .{}), - 'A' => self.seq(.{ "move_end", "enter_mode" }, command.fmt(.{"vim/insert"})), + ';' => self.cmd("open_command_palette", .{}), + 'n' => self.cmd("goto_prev_match", .{}), + 'a' => self.seq(.{ "move_end", "enter_mode" }, command.fmt(.{"vim/insert"})), '4' => self.cmd("move_end", .{}), - 'G' => if (self.count == 0) + 'g' => if (self.count == 0) self.cmd("move_buffer_end", .{}) else { const count = self.count; @@ -206,7 +205,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { try self.cmd_count("move_down", .{}); }, - 'O' => self.seq(.{ "smart_insert_line_before", "enter_mode" }, command.fmt(.{"vim/insert"})), + 'o' => self.seq(.{ "smart_insert_line_before", "enter_mode" }, command.fmt(.{"vim/insert"})), else => {}, }, @@ -228,7 +227,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { key.DEL => self.cmd("delete_forward", .{}), key.BACKSPACE => self.cmd("delete_backward", .{}), - ':' => self.cmd("enter_overlay_mode", command.fmt(.{"command_palette"})), + ':' => self.cmd("open_command_palette", .{}), 'i' => self.cmd("enter_mode", command.fmt(.{"vim/insert"})), 'a' => self.seq(.{ "move_right", "enter_mode" }, command.fmt(.{"vim/insert"})), 'v' => self.cmd("enter_mode", command.fmt(.{"vim/visual"})), @@ -297,49 +296,6 @@ fn mapFollower(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { keypress == key.LSUPER or keypress == key.RSUPER) return; - switch (modifiers) { - 0 => switch (keypress) { - '1' => { - self.add_count(1); - return; - }, - '2' => { - self.add_count(2); - return; - }, - '3' => { - self.add_count(3); - return; - }, - '4' => { - self.add_count(4); - return; - }, - '5' => { - self.add_count(5); - return; - }, - '6' => { - self.add_count(6); - return; - }, - '7' => { - self.add_count(7); - return; - }, - '8' => { - self.add_count(8); - return; - }, - '9' => { - self.add_count(9); - return; - }, - else => {}, - }, - else => {}, - } - defer self.leader = null; const ldr = if (self.leader) |leader| leader else return; return switch (ldr.modifiers) { @@ -531,3 +487,117 @@ fn seq_count(self: *Self, cmds: anytype, ctx: command.Context) tp.result { inline for (fields_info) |field_info| try self.cmd(@field(cmds, field_info.name), ctx); } + +const hints = tui.KeybindHints.initComptime(.{ + .{ "add_cursor_all_matches", "C-S-l" }, + .{ "add_cursor_down", "S-A-down" }, + .{ "add_cursor_next_match", "C-d" }, + .{ "add_cursors_to_line_ends", "S-A-i" }, + .{ "add_cursor_up", "S-A-up" }, + .{ "cancel", "esc" }, + .{ "close_file", "C-w" }, + .{ "close_file_without_saving", "C-S-w" }, + .{ "copy", "C-c" }, + .{ "cut", "C-x" }, + .{ "delete_backward", "backspace" }, + .{ "delete_forward", "del, x" }, + .{ "delete_to_begin", "C-k C-u" }, + .{ "delete_to_end", "C-k C-k, d $" }, + .{ "delete_word_left", "C-backspace" }, + .{ "delete_word_right", "C-del" }, + .{ "dump_current_line", "F7" }, + .{ "dump_current_line_tree", "F6" }, + .{ "dupe_down", "C-S-d" }, + .{ "dupe_up", "S-A-d" }, + .{ "enable_fast_scroll", "hold Ctrl" }, + .{ "enable_jump_mode", "hold Alt" }, + .{ "enter_find_in_files_mode", "C-S-f" }, + .{ "enter_find_mode", "C-f, /" }, + .{ "enter_goto_mode", "C-g" }, + .{ "enter_move_to_char_mode", "C-b, C-t" }, // true/false + .{ "enter_open_file_mode", "C-o" }, + .{ "filter", "A-s" }, // self.cmd("filter", command.fmt(.{"sort"})), + // .{ "filter", "S-A-s" }, // self.cmd("filter", command.fmt(.{ "sort", "-u" })), + .{ "format", "S-A-f" }, + .{ "goto_definition", "F12" }, + .{ "goto_next_diagnostic", "A-n" }, + .{ "goto_next_match", "C-n, F3, n" }, + .{ "goto_prev_diagnostic", "A-p" }, + .{ "goto_prev_match", "C-p, S-F3, N" }, + .{ "gutter_mode_next", "A-F10" }, + .{ "indent", "tab" }, + .{ "insert_line", "A-enter" }, + .{ "join_next_line", "A-j" }, + .{ "jump_back", "A-left" }, + .{ "jump_forward", "A-right" }, + .{ "move_begin", "0" }, + .{ "move_buffer_begin", "C-home, g g" }, + .{ "move_buffer_end", "C-end, G" }, + .{ "move_cursor_next_match", "C-k C-d" }, + .{ "move_down", "down, j" }, + .{ "move_end", "end, $, S-4" }, + .{ "move_left", "left" }, + .{ "move_left_vim", "h" }, + .{ "move_page_down", "pgdn" }, + .{ "move_page_up", "pgup" }, + .{ "move_right", "right" }, + .{ "move_right_vim", "l, space" }, + .{ "move_scroll_down", "C-down" }, + .{ "move_scroll_left", "S-A-left" }, + .{ "move_scroll_page_down", "C-pgdn" }, + .{ "move_scroll_page_up", "C-pgup" }, + .{ "move_scroll_right", "S-A-right" }, + .{ "move_scroll_up", "C-up" }, + .{ "move_up", "up, k" }, + .{ "move_word_left", "C-left, A-b, b" }, + .{ "move_word_right", "C-right, A-f, e" }, + .{ "move_word_right_vim", "w" }, + .{ "open_command_palette", "C-S-p, :, S-;" }, + .{ "open_recent", "C-e" }, + .{ "paste", "A-v, p" }, + .{ "pop_cursor", "C-u" }, + .{ "pull_down", "A-down" }, + .{ "pull_up", "A-up" }, + .{ "quit", "C-q" }, + .{ "quit_without_saving", "C-S-q" }, + .{ "redo", "C-S-z, C-y" }, + .{ "restart", "C-S-r" }, + .{ "save_file", "C-s" }, + .{ "scroll_view_bottom", "C-l, z z" }, + .{ "scroll_view_center", "C-l, z z" }, + .{ "scroll_view_top", "C-l, z z" }, + .{ "select_all", "C-a" }, + .{ "select_buffer_begin", "C-S-home" }, + .{ "select_buffer_end", "C-S-end" }, + .{ "select_down", "S-down" }, + .{ "select_end", "S-end" }, + .{ "selections_reverse", "C-space" }, + .{ "select_left", "S-left" }, + .{ "select_page_down", "S-pgdn" }, + .{ "select_page_up", "S-pgup" }, + .{ "select_right", "S-right" }, + .{ "select_scroll_down", "C-S-down" }, + .{ "select_scroll_up", "C-S-up" }, + .{ "select_up", "S-up" }, + .{ "select_word_left", "C-S-left" }, + .{ "select_word_right", "C-S-right" }, + .{ "smart_insert_line_after", "C-enter, o" }, + .{ "smart_insert_line_before", "S-enter, C-S-enter, O" }, + .{ "smart_insert_line", "enter" }, + .{ "smart_move_begin", "home" }, + .{ "smart_select_begin", "S-home" }, + .{ "system_paste", "C-v" }, + .{ "theme_next", "F10" }, + .{ "theme_prev", "F9" }, + .{ "toggle_comment", "C-/" }, + .{ "toggle_input_mode", "F2" }, + .{ "toggle_inputview", "A-i" }, + .{ "toggle_inspector_view", "F5, C-F5, C-S-i" }, + .{ "toggle_logview", "C-j, F11" }, + .{ "toggle_whitespace", "C-F10" }, + .{ "to_lower", "A-l" }, + .{ "to_upper", "A-u" }, + .{ "undo", "C-z" }, + .{ "undo", "u" }, + .{ "unindent", "S-tab" }, +}); diff --git a/src/tui/mode/input/vim/visual.zig b/src/tui/mode/input/vim/visual.zig index 95ffefd..8cf707a 100644 --- a/src/tui/mode/input/vim/visual.zig +++ b/src/tui/mode/input/vim/visual.zig @@ -35,6 +35,7 @@ pub fn create(a: Allocator) !tui.Mode { .name = root.application_logo ++ "VISUAL", .description = "vim", .line_numbers = if (tui.current().config.vim_visual_gutter_line_numbers_relative) .relative else .absolute, + .keybind_hints = &hints, }; } @@ -74,6 +75,7 @@ fn mapEvent(self: *Self, evtype: u32, keypress: u32, egc: u32, modifiers: u32) t } fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { + if (self.count > 0 and modifiers == 0 and '0' <= keypress and keypress <= '9') return self.add_count(keypress - '0'); const keynormal = if ('a' <= keypress and keypress <= 'z') keypress - ('a' - 'A') else keypress; if (self.leader) |_| return self.mapFollower(keynormal, egc, modifiers); switch (keypress) { @@ -83,7 +85,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { } return switch (modifiers) { mod.CTRL => switch (keynormal) { - 'E' => self.cmd("enter_overlay_mode", command.fmt(.{"open_recent"})), + 'E' => self.cmd("open_recent", .{}), 'U' => self.cmd("move_scroll_page_up", .{}), 'D' => self.cmd("move_scroll_page_down", .{}), 'R' => self.cmd("redo", .{}), @@ -126,7 +128,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { else => {}, }, mod.CTRL | mod.SHIFT => switch (keynormal) { - 'P' => self.cmd("enter_overlay_mode", command.fmt(.{"command_palette"})), + 'P' => self.cmd("open_command_palette", .{}), 'D' => self.cmd("dupe_down", .{}), 'Z' => self.cmd("redo", .{}), 'Q' => self.cmd("quit_without_saving", .{}), @@ -135,7 +137,6 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { 'F' => self.cmd("enter_find_in_files_mode", .{}), 'L' => self.cmd_async("add_cursor_all_matches"), 'I' => self.cmd_async("toggle_inspector_view"), - '/' => self.cmd("log_widgets", .{}), key.ENTER => self.cmd("smart_insert_line_before", .{}), key.END => self.cmd("select_buffer_end", .{}), key.HOME => self.cmd("select_buffer_begin", .{}), @@ -188,11 +189,11 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { key.BACKSPACE => self.cmd("delete_backward", .{}), key.TAB => self.cmd("unindent", .{}), - ';' => self.cmd("enter_overlay_mode", command.fmt(.{"command_palette"})), - 'N' => self.cmd("goto_prev_match", .{}), - 'A' => self.seq(.{ "move_end", "enter_mode" }, command.fmt(.{"vim/insert"})), + ';' => self.cmd("open_command_palette", .{}), + 'n' => self.cmd("goto_prev_match", .{}), + 'a' => self.seq(.{ "move_end", "enter_mode" }, command.fmt(.{"vim/insert"})), '4' => self.cmd("select_end", .{}), - 'G' => if (self.count == 0) + 'g' => if (self.count == 0) self.cmd("move_buffer_end", .{}) else { const count = self.count; @@ -202,7 +203,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { try self.cmd_count("move_down", .{}); }, - 'O' => self.seq(.{ "smart_insert_line_before", "enter_mode" }, command.fmt(.{"vim/insert"})), + 'o' => self.seq(.{ "smart_insert_line_before", "enter_mode" }, command.fmt(.{"vim/insert"})), else => {}, }, @@ -224,7 +225,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { key.DEL => self.cmd("delete_forward", .{}), key.BACKSPACE => self.cmd("delete_backward", .{}), - ':' => self.cmd("enter_overlay_mode", command.fmt(.{"command_palette"})), + ':' => self.cmd("open_command_palette", .{}), 'i' => self.cmd("enter_mode", command.fmt(.{"vim/insert"})), 'a' => self.seq(.{ "move_right", "enter_mode" }, command.fmt(.{"vim/insert"})), 'v' => self.cmd("enter_mode", command.fmt(.{"vim/visual"})), @@ -293,49 +294,6 @@ fn mapFollower(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { keypress == key.LSUPER or keypress == key.RSUPER) return; - switch (modifiers) { - 0 => switch (keypress) { - '1' => { - self.add_count(1); - return; - }, - '2' => { - self.add_count(2); - return; - }, - '3' => { - self.add_count(3); - return; - }, - '4' => { - self.add_count(4); - return; - }, - '5' => { - self.add_count(5); - return; - }, - '6' => { - self.add_count(6); - return; - }, - '7' => { - self.add_count(7); - return; - }, - '8' => { - self.add_count(8); - return; - }, - '9' => { - self.add_count(9); - return; - }, - else => {}, - }, - else => {}, - } - defer self.leader = null; const ldr = if (self.leader) |leader| leader else return; return switch (ldr.modifiers) { @@ -489,3 +447,117 @@ fn seq_count(self: *Self, cmds: anytype, ctx: command.Context) tp.result { inline for (fields_info) |field_info| try self.cmd(@field(cmds, field_info.name), ctx); } + +const hints = tui.KeybindHints.initComptime(.{ + .{ "add_cursor_all_matches", "C-S-l" }, + .{ "add_cursor_down", "S-A-down" }, + .{ "add_cursor_next_match", "C-d" }, + .{ "add_cursors_to_line_ends", "S-A-i" }, + .{ "add_cursor_up", "S-A-up" }, + .{ "cancel", "esc" }, + .{ "close_file", "C-w" }, + .{ "close_file_without_saving", "C-S-w" }, + .{ "copy", "C-c" }, + .{ "cut", "C-x" }, + .{ "delete_backward", "backspace" }, + .{ "delete_forward", "del, x" }, + .{ "delete_to_begin", "C-k C-u" }, + .{ "delete_to_end", "C-k C-k, d $" }, + .{ "delete_word_left", "C-backspace" }, + .{ "delete_word_right", "C-del" }, + .{ "dump_current_line", "F7" }, + .{ "dump_current_line_tree", "F6" }, + .{ "dupe_down", "C-S-d" }, + .{ "dupe_up", "S-A-d" }, + .{ "enable_fast_scroll", "hold Ctrl" }, + .{ "enable_jump_mode", "hold Alt" }, + .{ "enter_find_in_files_mode", "C-S-f" }, + .{ "enter_find_mode", "C-f, /" }, + .{ "enter_goto_mode", "C-g" }, + .{ "enter_move_to_char_mode", "C-b, C-t" }, // true/false + .{ "enter_open_file_mode", "C-o" }, + .{ "filter", "A-s" }, // self.cmd("filter", command.fmt(.{"sort"})), + // .{ "filter", "S-A-s" }, // self.cmd("filter", command.fmt(.{ "sort", "-u" })), + .{ "format", "S-A-f" }, + .{ "goto_definition", "F12" }, + .{ "goto_next_diagnostic", "A-n" }, + .{ "goto_next_match", "C-n, F3, n" }, + .{ "goto_prev_diagnostic", "A-p" }, + .{ "goto_prev_match", "C-p, S-F3, N" }, + .{ "gutter_mode_next", "A-F10" }, + .{ "indent", "tab" }, + .{ "insert_line", "A-enter" }, + .{ "join_next_line", "A-j" }, + .{ "jump_back", "A-left" }, + .{ "jump_forward", "A-right" }, + .{ "move_begin", "0" }, + .{ "move_buffer_begin", "C-home, g g" }, + .{ "move_buffer_end", "C-end, G" }, + .{ "move_cursor_next_match", "C-k C-d" }, + .{ "move_down", "down, j" }, + .{ "move_end", "end, $, S-4" }, + .{ "move_left", "left" }, + .{ "move_left_vim", "h" }, + .{ "move_page_down", "pgdn" }, + .{ "move_page_up", "pgup" }, + .{ "move_right", "right" }, + .{ "move_right_vim", "l, space" }, + .{ "move_scroll_down", "C-down" }, + .{ "move_scroll_left", "S-A-left" }, + .{ "move_scroll_page_down", "C-pgdn" }, + .{ "move_scroll_page_up", "C-pgup" }, + .{ "move_scroll_right", "S-A-right" }, + .{ "move_scroll_up", "C-up" }, + .{ "move_up", "up, k" }, + .{ "move_word_left", "C-left, A-b, b" }, + .{ "move_word_right", "C-right, A-f, e" }, + .{ "move_word_right_vim", "w" }, + .{ "open_command_palette", "C-S-p, :, S-;" }, + .{ "open_recent", "C-e" }, + .{ "paste", "A-v, p" }, + .{ "pop_cursor", "C-u" }, + .{ "pull_down", "A-down" }, + .{ "pull_up", "A-up" }, + .{ "quit", "C-q" }, + .{ "quit_without_saving", "C-S-q" }, + .{ "redo", "C-S-z, C-y" }, + .{ "restart", "C-S-r" }, + .{ "save_file", "C-s" }, + .{ "scroll_view_bottom", "C-l, z z" }, + .{ "scroll_view_center", "C-l, z z" }, + .{ "scroll_view_top", "C-l, z z" }, + .{ "select_all", "C-a" }, + .{ "select_buffer_begin", "C-S-home" }, + .{ "select_buffer_end", "C-S-end" }, + .{ "select_down", "S-down" }, + .{ "select_end", "S-end" }, + .{ "selections_reverse", "C-space" }, + .{ "select_left", "S-left" }, + .{ "select_page_down", "S-pgdn" }, + .{ "select_page_up", "S-pgup" }, + .{ "select_right", "S-right" }, + .{ "select_scroll_down", "C-S-down" }, + .{ "select_scroll_up", "C-S-up" }, + .{ "select_up", "S-up" }, + .{ "select_word_left", "C-S-left" }, + .{ "select_word_right", "C-S-right" }, + .{ "smart_insert_line_after", "C-enter, o" }, + .{ "smart_insert_line_before", "S-enter, C-S-enter, O" }, + .{ "smart_insert_line", "enter" }, + .{ "smart_move_begin", "home" }, + .{ "smart_select_begin", "S-home" }, + .{ "system_paste", "C-v" }, + .{ "theme_next", "F10" }, + .{ "theme_prev", "F9" }, + .{ "toggle_comment", "C-/" }, + .{ "toggle_input_mode", "F2" }, + .{ "toggle_inputview", "A-i" }, + .{ "toggle_inspector_view", "F5, C-F5, C-S-i" }, + .{ "toggle_logview", "C-j, F11" }, + .{ "toggle_whitespace", "C-F10" }, + .{ "to_lower", "A-l" }, + .{ "to_upper", "A-u" }, + .{ "undo", "C-z" }, + .{ "undo", "u" }, + .{ "unindent", "S-tab" }, +}); diff --git a/src/tui/mode/overlay/command_palette.zig b/src/tui/mode/overlay/command_palette.zig index 65191a3..7227b87 100644 --- a/src/tui/mode/overlay/command_palette.zig +++ b/src/tui/mode/overlay/command_palette.zig @@ -28,6 +28,8 @@ inputbox: *InputBox.State(*Self), logger: log.Logger, longest: usize = 0, commands: Commands = undefined, +hints: ?*const tui.KeybindHints = null, +longest_hint: usize = 0, pub fn create(a: std.mem.Allocator) !tui.Mode { const mv = if (tui.current().mainview.dynamic_cast(mainview)) |mv_| mv_ else return error.NotFound; @@ -40,7 +42,12 @@ pub fn create(a: std.mem.Allocator) !tui.Mode { .ctx = self, .label = "Search commands", }))).dynamic_cast(InputBox.State(*Self)) orelse unreachable, + .hints = if (tui.current().input_mode) |m| m.keybind_hints else null, }; + if (self.hints) |hints| { + for (hints.values()) |val| + self.longest_hint = @max(self.longest_hint, val.len); + } try self.commands.init(self); try self.start_query(); try mv.floating_views.add(self.menu.menu_widget); @@ -62,18 +69,24 @@ pub fn deinit(self: *Self) void { fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool { const style_base = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget; + const style_keybind = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_base; button.plane.set_base_style(" ", style_base); button.plane.erase(); button.plane.home(); var command_name: []const u8 = undefined; + var keybind_hint: []const u8 = undefined; var iter = button.opts.label; // label contains cbor, first the file name, then multiple match indexes if (!(cbor.matchString(&iter, &command_name) catch false)) command_name = "#ERROR#"; + if (!(cbor.matchString(&iter, &keybind_hint) catch false)) + keybind_hint = ""; const pointer = if (selected) "⏵" else " "; _ = button.plane.print("{s}{s} ", .{ pointer, command_name, }) catch {}; + button.plane.set_style(style_keybind); + _ = button.plane.print_aligned_right(0, "{s} ", .{keybind_hint}) catch {}; var index: usize = 0; var len = cbor.decodeArrayHeader(&iter) catch return false; while (len > 0) : (len -= 1) { @@ -139,7 +152,6 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { 'J' => self.cmd("toggle_logview", .{}), 'Q' => self.cmd("quit", .{}), 'W' => self.cmd("close_file", .{}), - 'E' => self.cmd("command_palette_menu_down", .{}), 'P' => self.cmd("command_palette_menu_up", .{}), 'N' => self.cmd("command_palette_menu_down", .{}), 'V' => self.cmd("system_paste", .{}), @@ -153,13 +165,12 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { else => {}, }, mod.CTRL | mod.SHIFT => switch (keynormal) { - 'P' => self.cmd("exit_overlay_mode", .{}), + 'P' => self.cmd("command_palette_menu_down", .{}), 'Q' => self.cmd("quit_without_saving", .{}), 'W' => self.cmd("close_file_without_saving", .{}), 'R' => self.cmd("restart", .{}), 'L' => self.cmd_async("toggle_logview"), 'I' => self.cmd_async("toggle_inputview"), - 'E' => self.cmd("command_palette_menu_up", .{}), else => {}, }, mod.ALT => switch (keynormal) { @@ -260,6 +271,7 @@ fn add_item(self: *Self, command_name: []const u8, matches: ?[]const usize) !voi defer label.deinit(); const writer = label.writer(); try cbor.writeValue(writer, command_name); + try cbor.writeValue(writer, if (self.hints) |hints| hints.get(command_name) else ""); if (matches) |matches_| try cbor.writeValue(writer, matches_); try self.menu.add_item_with_handler(label.items, menu_action_execute_command); diff --git a/src/tui/status/filestate.zig b/src/tui/status/filestate.zig index e22e8ad..6ee92fc 100644 --- a/src/tui/status/filestate.zig +++ b/src/tui/status/filestate.zig @@ -58,7 +58,7 @@ pub fn create(a: Allocator, parent: Plane) !Widget { } fn on_click(_: *Self, _: *Button.State(Self)) void { - command.executeName("enter_overlay_mode", command.fmt(.{"open_recent"})) catch {}; + command.executeName("open_recent", .{}) catch {}; } fn on_click2(_: *Self, _: *Button.State(Self)) void { diff --git a/src/tui/status/modestate.zig b/src/tui/status/modestate.zig index 24777eb..031eccc 100644 --- a/src/tui/status/modestate.zig +++ b/src/tui/status/modestate.zig @@ -63,6 +63,6 @@ fn on_click(_: *void, _: *Button.State(void)) void { } else if (is_overlay_mode()) { command.executeName("exit_overlay_mode", .{}) catch {}; } else { - command.executeName("enter_overlay_mode", command.fmt(.{"command_palette"})) catch {}; + command.executeName("open_command_palette", .{}) catch {}; } } diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 318f292..a65c010 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -559,23 +559,18 @@ const cmds = struct { return enter_mode(self, Ctx.fmt(.{self.config.input_mode})); } - pub fn enter_overlay_mode(self: *Self, ctx: Ctx) tp.result { - var mode: []const u8 = undefined; - if (!try ctx.args.match(.{tp.extract(&mode)})) - return tp.exit_error(error.InvalidArgument); + pub fn open_command_palette(self: *Self, _: Ctx) tp.result { if (self.mini_mode) |_| try exit_mini_mode(self, .{}); if (self.input_mode_outer) |_| try exit_overlay_mode(self, .{}); - self.input_mode = if (std.mem.eql(u8, mode, "command_palette")) ret: { - self.input_mode_outer = self.input_mode; - break :ret @import("mode/overlay/command_palette.zig").create(self.a) catch |e| return tp.exit_error(e); - } else if (std.mem.eql(u8, mode, "open_recent")) ret: { - self.input_mode_outer = self.input_mode; - break :ret @import("mode/overlay/open_recent.zig").create(self.a) catch |e| return tp.exit_error(e); - } else { - self.logger.print("unknown mode {s}", .{mode}); - return; - }; - // self.logger.print("input mode: {s}", .{(self.input_mode orelse return).description}); + self.input_mode_outer = self.input_mode; + self.input_mode = @import("mode/overlay/command_palette.zig").create(self.a) catch |e| return tp.exit_error(e); + } + + pub fn open_recent(self: *Self, _: Ctx) tp.result { + if (self.mini_mode) |_| try exit_mini_mode(self, .{}); + if (self.input_mode_outer) |_| try exit_overlay_mode(self, .{}); + self.input_mode_outer = self.input_mode; + self.input_mode = @import("mode/overlay/open_recent.zig").create(self.a) catch |e| return tp.exit_error(e); } pub fn exit_overlay_mode(self: *Self, _: Ctx) tp.result { @@ -643,6 +638,7 @@ pub const Mode = struct { name: []const u8, description: []const u8, line_numbers: enum { absolute, relative } = .absolute, + keybind_hints: ?*const KeybindHints = null, fn deinit(self: *Mode) void { self.handler.deinit(); @@ -654,6 +650,8 @@ pub const MiniModeState = struct { cursor: ?usize = null, }; +pub const KeybindHints = std.static_string_map.StaticStringMap([]const u8); + threadlocal var instance_: ?*Self = null; pub fn current() *Self {