feat: render home screen based on current input mode

This commit is contained in:
CJ van den Berg 2024-12-05 19:48:17 +01:00
parent c827972e98
commit f8dff2a7bb
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
5 changed files with 132 additions and 83 deletions

View file

@ -145,6 +145,16 @@ pub fn get_id_cache(name: []const u8, id: *?ID) ?ID {
return null; 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 = .{ const suppressed_errors = .{
"enable_fast_scroll", "enable_fast_scroll",
"disable_fast_scroll", "disable_fast_scroll",

View file

@ -155,6 +155,15 @@
"home": { "home": {
"on_match_failure": "ignore", "on_match_failure": "ignore",
"press": [ "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+f>ctrl+f>ctrl+f>ctrl+f>ctrl+f", "home_sheeran"],
["ctrl+j", "toggle_panel"], ["ctrl+j", "toggle_panel"],
["ctrl+q", "quit"], ["ctrl+q", "quit"],
@ -175,15 +184,6 @@
["alt+l", "toggle_panel"], ["alt+l", "toggle_panel"],
["alt+i", "toggle_inputview"], ["alt+i", "toggle_inputview"],
["alt+x", "open_command_palette"], ["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"], ["f1", "open_help"],
["f2", "toggle_input_mode"], ["f2", "toggle_input_mode"],
["ctrl+f2", "insert_command_name"], ["ctrl+f2", "insert_command_name"],

View file

@ -88,5 +88,17 @@
["<BS>", "delete_backward"], ["<BS>", "delete_backward"],
["<CR>", "insert_line_after"] ["<CR>", "insert_line_after"]
] ]
},
"home": {
"syntax": "vim",
"on_match_failure": "ignore",
"press": [
[";", "open_command_palette"],
["<S-;>", "open_command_palette"],
["b", "open_keybind_config"],
["j", "home_menu_down"],
["k", "home_menu_up"],
["<Space>", "home_menu_activate"]
]
} }
} }

View file

@ -18,14 +18,31 @@ parent: Plane,
fire: ?Fire = null, fire: ?Fire = null,
commands: Commands = undefined, commands: Commands = undefined,
menu: *Menu.State(*Self), menu: *Menu.State(*Self),
menu_w: usize = 0,
max_desc_len: usize = 0,
const Self = @This(); 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 { pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
const self: *Self = try allocator.create(Self); const self: *Self = try allocator.create(Self);
var n = try Plane.init(&(Widget.Box{}).opts("editor"), parent.plane.*); var n = try Plane.init(&(Widget.Box{}).opts("editor"), parent.plane.*);
errdefer n.deinit(); 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); const w = Widget.to(self);
self.* = .{ self.* = .{
.allocator = allocator, .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 }), .menu = try Menu.create(*Self, allocator, w, .{ .ctx = self, .on_render = menu_on_render }),
}; };
try self.commands.init(self); try self.commands.init(self);
try self.menu.add_item_with_handler("Help ······················· :h", menu_action_help); self.get_max_desc_len(keybind_mode.keybind_hints);
try self.menu.add_item_with_handler("Open file ·················· :o", menu_action_open_file); inline for (menu_commands) |command_name| try self.add_menu_command(command_name, self.menu, keybind_mode.keybind_hints);
try self.menu.add_item_with_handler("Open recent file ··········· :e", menu_action_open_recent_file); self.menu.resize(.{ .y = 15, .x = 9, .w = self.menu_w });
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 {};
return w; return w;
} }
@ -56,6 +65,34 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
allocator.destroy(self); 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 { pub fn update(self: *Self) void {
self.menu.update(); self.menu.update();
} }
@ -102,40 +139,12 @@ fn menu_on_render(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *c
return false; return false;
} }
fn menu_action_help(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { fn menu_action(comptime command_name: []const u8) *const fn (_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
command.executeName("open_help", .{}) catch {}; return struct {
} fn action(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
command.executeName(command_name, .{}) catch {};
fn menu_action_open_file(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void { }
command.executeName("open_file", .{}) catch {}; }.action;
}
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 {};
} }
pub fn render(self: *Self, theme: *const Widget.Theme) bool { 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; self.plane.cursor_move_yx(10, 8) catch return false;
fonts.print_string_medium(&self.plane, root.application_subtext, style_subtext) 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) { } else if (self.plane.dim_x() > 55 and self.plane.dim_y() > 16) {
self.plane.cursor_move_yx(2, 4) catch return false; self.plane.cursor_move_yx(2, 4) catch return false;
fonts.print_string_medium(&self.plane, root.application_title, style_title) 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.print(root.application_subtext, .{}) catch {};
self.plane.set_style(theme.editor); 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 { } else {
self.plane.set_style_bg_transparent(style_title); self.plane.set_style_bg_transparent(style_title);
self.plane.cursor_move_yx(1, 4) catch return false; 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); self.plane.set_style(theme.editor);
const x = @min(self.plane.dim_x() -| 32, 8); 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); const more = self.menu.render(theme);

View file

@ -31,7 +31,7 @@ mainview: Widget,
message_filters: MessageFilter.List, message_filters: MessageFilter.List,
input_mode: ?Mode = null, input_mode: ?Mode = null,
delayed_init_done: bool = false, delayed_init_done: bool = false,
delayed_init_input_mode: ?[]const u8 = null, delayed_init_input_mode: ?Mode = null,
input_mode_outer: ?Mode = null, input_mode_outer: ?Mode = null,
input_listeners: EventHandler.List, input_listeners: EventHandler.List,
keyboard_focus: ?Widget = null, keyboard_focus: ?Widget = null,
@ -146,23 +146,30 @@ fn init(allocator: Allocator) !*Self {
self.logger.print("session restored", .{}); self.logger.print("session restored", .{});
} }
need_render(); need_render();
try self.init_input_namespace();
return self; 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 { fn init_delayed(self: *Self) !void {
self.delayed_init_done = true; self.delayed_init_done = true;
if (self.input_mode) |_| {} else { if (self.input_mode) |_| {} else {
var mode_parts = std.mem.splitScalar(u8, self.config.input_mode, '/'); if (self.delayed_init_input_mode) |delayed_init_input_mode| {
const namespace_name = mode_parts.first(); try enter_input_mode(self, delayed_init_input_mode);
keybind.set_namespace(namespace_name) catch { self.delayed_init_input_mode = null;
self.logger.print_err("keybind", "unknown mode {s}", .{namespace_name}); } else {
try keybind.set_namespace("flow"); try cmds.enter_mode(self, command.Context.fmt(.{keybind.default_mode}));
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,
}));
} }
} }
@ -181,7 +188,10 @@ fn deinit(self: *Self) void {
m.deinit(); m.deinit();
self.input_mode = null; 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.commands.deinit();
self.mainview.deinit(self.allocator); self.mainview.deinit(self.allocator);
self.message_filters.deinit(); 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, .{}); 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 { const cmds = struct {
pub const Target = Self; pub const Target = Self;
const Ctx = command.Context; const Ctx = command.Context;
@ -679,10 +699,6 @@ const cmds = struct {
var mode: []const u8 = undefined; var mode: []const u8 = undefined;
if (!try ctx.args.match(.{tp.extract(&mode)})) if (!try ctx.args.match(.{tp.extract(&mode)}))
return tp.exit_error(error.InvalidArgument, null); 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: { var new_mode = self.get_input_mode(mode) catch ret: {
self.logger.print("unknown mode {s}", .{mode}); self.logger.print("unknown mode {s}", .{mode});
@ -690,14 +706,11 @@ const cmds = struct {
}; };
errdefer new_mode.deinit(); errdefer new_mode.deinit();
if (self.mini_mode) |_| try exit_mini_mode(self, .{}); if (!self.delayed_init_done) {
if (self.input_mode_outer) |_| try exit_overlay_mode(self, .{}); self.delayed_init_input_mode = new_mode;
if (self.input_mode) |*m| { return;
m.deinit();
self.input_mode = null;
} }
self.input_mode = new_mode; return self.enter_input_mode(new_mode);
// self.logger.print("input mode: {s}", .{(self.input_mode orelse return).description});
} }
pub const enter_mode_meta = .{ .arguments = &.{.string} }; pub const enter_mode_meta = .{ .arguments = &.{.string} };
@ -709,7 +722,7 @@ const cmds = struct {
pub fn open_command_palette(self: *Self, _: Ctx) Result { pub fn open_command_palette(self: *Self, _: Ctx) Result {
return self.enter_overlay_mode(@import("mode/overlay/command_palette.zig").Type); 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 { pub fn insert_command_name(self: *Self, _: Ctx) Result {
return self.enter_overlay_mode(@import("mode/overlay/list_all_commands_palette.zig").Type); 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"; "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 { pub fn reset_drag_context() void {
const self = current(); const self = current();
self.drag_source = null; self.drag_source = null;