From f8dff2a7bba0a45a6ca6443486cff4f24c018822 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 5 Dec 2024 19:48:17 +0100 Subject: [PATCH] feat: render home screen based on current input mode --- src/command.zig | 10 ++++ src/keybind/builtin/flow.json | 18 +++--- src/keybind/builtin/vim.json | 14 ++++- src/tui/home.zig | 105 ++++++++++++++++++---------------- src/tui/tui.zig | 68 ++++++++++++++-------- 5 files changed, 132 insertions(+), 83 deletions(-) diff --git a/src/command.zig b/src/command.zig index efbad11..f2d32d3 100644 --- a/src/command.zig +++ b/src/command.zig @@ -145,6 +145,16 @@ pub fn get_id_cache(name: []const u8, id: *?ID) ?ID { return null; } +pub fn get_description(id: ID) ?[]const u8 { + if (id >= commands.items.len) return null; + return (commands.items[id] orelse return null).meta.description; +} + +pub fn get_arguments(id: ID) ?[]const ArgumentType { + if (id >= commands.items.len) return null; + return (commands.items[id] orelse return null).meta.arguments; +} + const suppressed_errors = .{ "enable_fast_scroll", "disable_fast_scroll", diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 69c9699..9e1141a 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -155,6 +155,15 @@ "home": { "on_match_failure": "ignore", "press": [ + ["h", "open_help"], + ["o", "open_file"], + ["e", "open_recent"], + ["r", "open_recent_project"], + ["p", "open_command_palette"], + ["c", "open_config"], + ["k", "open_keybind_config"], + ["t", "change_theme"], + ["q", "quit"], ["ctrl+f>ctrl+f>ctrl+f>ctrl+f>ctrl+f", "home_sheeran"], ["ctrl+j", "toggle_panel"], ["ctrl+q", "quit"], @@ -175,15 +184,6 @@ ["alt+l", "toggle_panel"], ["alt+i", "toggle_inputview"], ["alt+x", "open_command_palette"], - ["h", "open_help"], - ["o", "open_file"], - ["e", "open_recent"], - ["r", "open_recent_project"], - ["p", "open_command_palette"], - ["c", "open_config"], - ["k", "open_keybind_config"], - ["t", "change_theme"], - ["q", "quit"], ["f1", "open_help"], ["f2", "toggle_input_mode"], ["ctrl+f2", "insert_command_name"], diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index aa6d383..fc70c45 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -88,5 +88,17 @@ ["", "delete_backward"], ["", "insert_line_after"] ] - } + }, + "home": { + "syntax": "vim", + "on_match_failure": "ignore", + "press": [ + [";", "open_command_palette"], + ["", "open_command_palette"], + ["b", "open_keybind_config"], + ["j", "home_menu_down"], + ["k", "home_menu_up"], + ["", "home_menu_activate"] + ] + } } diff --git a/src/tui/home.zig b/src/tui/home.zig index 32dd696..abb31f7 100644 --- a/src/tui/home.zig +++ b/src/tui/home.zig @@ -18,14 +18,31 @@ parent: Plane, fire: ?Fire = null, commands: Commands = undefined, menu: *Menu.State(*Self), +menu_w: usize = 0, +max_desc_len: usize = 0, const Self = @This(); +const menu_commands = &[_][]const u8{ + "open_help", + "open_file", + "open_recent", + "open_recent_project", + "open_command_palette", + "open_config", + "open_keybind_config", + "change_theme", + "quit", +}; + pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget { const self: *Self = try allocator.create(Self); var n = try Plane.init(&(Widget.Box{}).opts("editor"), parent.plane.*); errdefer n.deinit(); + command.executeName("enter_mode", command.Context.fmt(.{"home"})) catch {}; + const keybind_mode = tui.get_keybind_mode() orelse @panic("no active keybind mode"); + const w = Widget.to(self); self.* = .{ .allocator = allocator, @@ -34,17 +51,9 @@ pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget { .menu = try Menu.create(*Self, allocator, w, .{ .ctx = self, .on_render = menu_on_render }), }; try self.commands.init(self); - try self.menu.add_item_with_handler("Help ······················· :h", menu_action_help); - try self.menu.add_item_with_handler("Open file ·················· :o", menu_action_open_file); - try self.menu.add_item_with_handler("Open recent file ··········· :e", menu_action_open_recent_file); - try self.menu.add_item_with_handler("Open recent project ········ :r", menu_action_open_recent_project); - try self.menu.add_item_with_handler("Show/Run commands ·········· :p", menu_action_show_commands); - try self.menu.add_item_with_handler("Open config file ··········· :c", menu_action_open_config); - try self.menu.add_item_with_handler("Open key bindings file ····· :k", menu_action_open_keybind_config); - try self.menu.add_item_with_handler("Change theme ··············· :t", menu_action_change_theme); - try self.menu.add_item_with_handler("Quit/Close ················· :q", menu_action_quit); - self.menu.resize(.{ .y = 15, .x = 9, .w = 32 }); - command.executeName("enter_mode", command.Context.fmt(.{"home"})) catch {}; + self.get_max_desc_len(keybind_mode.keybind_hints); + inline for (menu_commands) |command_name| try self.add_menu_command(command_name, self.menu, keybind_mode.keybind_hints); + self.menu.resize(.{ .y = 15, .x = 9, .w = self.menu_w }); return w; } @@ -56,6 +65,34 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { allocator.destroy(self); } +fn get_max_desc_len(self: *Self, hints_map: anytype) void { + inline for (menu_commands) |command_name| { + const id = command.get_id(command_name) orelse @panic(command_name ++ " is not defined"); + const description = command.get_description(id) orelse @panic(command_name ++ " has no description"); + var hints = std.mem.splitScalar(u8, hints_map.get(command_name) orelse "", ','); + const hint = hints.first(); + self.max_desc_len = @max(self.max_desc_len, description.len + hint.len + 5); + } +} + +fn add_menu_command(self: *Self, comptime command_name: []const u8, menu: anytype, hints_map: anytype) !void { + const id = command.get_id(command_name) orelse @panic(command_name ++ " is not defined"); + const description = command.get_description(id) orelse @panic(command_name ++ " has no description"); + var hints = std.mem.splitScalar(u8, hints_map.get(command_name) orelse "", ','); + const hint = hints.first(); + const label_len = description.len + hint.len; + var buf: [64]u8 = undefined; + var fis = std.io.fixedBufferStream(&buf); + const writer = fis.writer(); + try writer.print("{s} ..", .{description}); + for (0..(self.max_desc_len - label_len - 5)) |_| + try writer.print(".", .{}); + try writer.print(" :{s}", .{hint}); + const label = fis.getWritten(); + try menu.add_item_with_handler(label, menu_action(command_name)); + self.menu_w = @max(self.menu_w, label.len + 1); +} + pub fn update(self: *Self) void { self.menu.update(); } @@ -102,40 +139,12 @@ fn menu_on_render(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *c return false; } -fn menu_action_help(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { - command.executeName("open_help", .{}) catch {}; -} - -fn menu_action_open_file(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { - command.executeName("open_file", .{}) catch {}; -} - -fn menu_action_open_recent_file(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { - command.executeName("open_recent", .{}) catch {}; -} - -fn menu_action_open_recent_project(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { - command.executeName("open_recent_project", .{}) catch {}; -} - -fn menu_action_show_commands(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { - command.executeName("open_command_palette", .{}) catch {}; -} - -fn menu_action_open_config(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { - command.executeName("open_config", .{}) catch {}; -} - -fn menu_action_open_keybind_config(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { - command.executeName("open_keybind_config", .{}) catch {}; -} - -fn menu_action_change_theme(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { - command.executeName("change_theme", .{}) catch {}; -} - -fn menu_action_quit(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { - command.executeName("quit", .{}) catch {}; +fn menu_action(comptime command_name: []const u8) *const fn (_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { + return struct { + fn action(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { + command.executeName(command_name, .{}) catch {}; + } + }.action; } pub fn render(self: *Self, theme: *const Widget.Theme) bool { @@ -154,7 +163,7 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool { self.plane.cursor_move_yx(10, 8) catch return false; fonts.print_string_medium(&self.plane, root.application_subtext, style_subtext) catch return false; - self.menu.resize(.{ .y = 15, .x = 10, .w = 32 }); + self.menu.resize(.{ .y = 15, .x = 10, .w = self.menu_w }); } else if (self.plane.dim_x() > 55 and self.plane.dim_y() > 16) { self.plane.cursor_move_yx(2, 4) catch return false; fonts.print_string_medium(&self.plane, root.application_title, style_title) catch return false; @@ -164,7 +173,7 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool { _ = self.plane.print(root.application_subtext, .{}) catch {}; self.plane.set_style(theme.editor); - self.menu.resize(.{ .y = 9, .x = 8, .w = 32 }); + self.menu.resize(.{ .y = 9, .x = 8, .w = self.menu_w }); } else { self.plane.set_style_bg_transparent(style_title); self.plane.cursor_move_yx(1, 4) catch return false; @@ -176,7 +185,7 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool { self.plane.set_style(theme.editor); const x = @min(self.plane.dim_x() -| 32, 8); - self.menu.resize(.{ .y = 5, .x = x, .w = 32 }); + self.menu.resize(.{ .y = 5, .x = x, .w = self.menu_w }); } const more = self.menu.render(theme); diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 9bf45e4..0f934ec 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -31,7 +31,7 @@ mainview: Widget, message_filters: MessageFilter.List, input_mode: ?Mode = null, delayed_init_done: bool = false, -delayed_init_input_mode: ?[]const u8 = null, +delayed_init_input_mode: ?Mode = null, input_mode_outer: ?Mode = null, input_listeners: EventHandler.List, keyboard_focus: ?Widget = null, @@ -146,23 +146,30 @@ fn init(allocator: Allocator) !*Self { self.logger.print("session restored", .{}); } need_render(); + try self.init_input_namespace(); return self; } +fn init_input_namespace(self: *Self) !void { + var mode_parts = std.mem.splitScalar(u8, self.config.input_mode, '/'); + const namespace_name = mode_parts.first(); + keybind.set_namespace(namespace_name) catch { + self.logger.print_err("keybind", "unknown mode {s}", .{namespace_name}); + try keybind.set_namespace("flow"); + self.config.input_mode = "flow"; + try self.save_config(); + }; +} + fn init_delayed(self: *Self) !void { self.delayed_init_done = true; if (self.input_mode) |_| {} else { - var mode_parts = std.mem.splitScalar(u8, self.config.input_mode, '/'); - const namespace_name = mode_parts.first(); - keybind.set_namespace(namespace_name) catch { - self.logger.print_err("keybind", "unknown mode {s}", .{namespace_name}); - try keybind.set_namespace("flow"); - self.config.input_mode = "flow"; - try self.save_config(); - }; - return cmds.enter_mode(self, command.Context.fmt(.{ - self.delayed_init_input_mode orelse keybind.default_mode, - })); + if (self.delayed_init_input_mode) |delayed_init_input_mode| { + try enter_input_mode(self, delayed_init_input_mode); + self.delayed_init_input_mode = null; + } else { + try cmds.enter_mode(self, command.Context.fmt(.{keybind.default_mode})); + } } } @@ -181,7 +188,10 @@ fn deinit(self: *Self) void { m.deinit(); self.input_mode = null; } - if (self.delayed_init_input_mode) |mode| self.allocator.free(mode); + if (self.delayed_init_input_mode) |*m| { + m.deinit(); + self.delayed_init_input_mode = null; + } self.commands.deinit(); self.mainview.deinit(self.allocator); self.message_filters.deinit(); @@ -587,6 +597,16 @@ fn get_input_mode(self: *Self, mode_name: []const u8) !Mode { return keybind.mode(mode_name, self.allocator, .{}); } +fn enter_input_mode(self: *Self, new_mode: Mode, mode_name: []const u8) command.Result { + if (self.mini_mode) |_| try cmds.exit_mini_mode(self, .{}); + if (self.input_mode_outer) |_| try cmds.exit_overlay_mode(self, .{}); + if (self.input_mode) |*m| { + m.deinit(); + self.input_mode = null; + } + self.input_mode = new_mode; +} + const cmds = struct { pub const Target = Self; const Ctx = command.Context; @@ -679,10 +699,6 @@ const cmds = struct { var mode: []const u8 = undefined; if (!try ctx.args.match(.{tp.extract(&mode)})) return tp.exit_error(error.InvalidArgument, null); - if (!self.delayed_init_done) { - self.delayed_init_input_mode = try self.allocator.dupe(u8, mode); - return; - } var new_mode = self.get_input_mode(mode) catch ret: { self.logger.print("unknown mode {s}", .{mode}); @@ -690,14 +706,11 @@ const cmds = struct { }; errdefer new_mode.deinit(); - if (self.mini_mode) |_| try exit_mini_mode(self, .{}); - if (self.input_mode_outer) |_| try exit_overlay_mode(self, .{}); - if (self.input_mode) |*m| { - m.deinit(); - self.input_mode = null; + if (!self.delayed_init_done) { + self.delayed_init_input_mode = new_mode; + return; } - self.input_mode = new_mode; - // self.logger.print("input mode: {s}", .{(self.input_mode orelse return).description}); + return self.enter_input_mode(new_mode); } pub const enter_mode_meta = .{ .arguments = &.{.string} }; @@ -709,7 +722,7 @@ const cmds = struct { pub fn open_command_palette(self: *Self, _: Ctx) Result { return self.enter_overlay_mode(@import("mode/overlay/command_palette.zig").Type); } - pub const open_command_palette_meta = .{}; + pub const open_command_palette_meta = .{ .description = "Show/Run commands" }; pub fn insert_command_name(self: *Self, _: Ctx) Result { return self.enter_overlay_mode(@import("mode/overlay/list_all_commands_palette.zig").Type); @@ -888,6 +901,11 @@ pub fn get_mode() []const u8 { "INI"; } +pub fn get_keybind_mode() ?Mode { + const self = current(); + return self.input_mode orelse self.delayed_init_input_mode; +} + pub fn reset_drag_context() void { const self = current(); self.drag_source = null;