feat: generate hints for dynamic keybindings
This commit is contained in:
parent
271f45e78a
commit
3af2b09891
10 changed files with 98 additions and 64 deletions
|
@ -55,7 +55,7 @@ fn Handler(namespace_name: []const u8, mode_name: []const u8) type {
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
bindings: BindingSet,
|
bindings: BindingSet,
|
||||||
|
|
||||||
pub fn create(allocator: std.mem.Allocator, opts: anytype) !EventHandler {
|
pub fn create(allocator: std.mem.Allocator, opts: anytype) !struct { EventHandler, *const KeybindHints } {
|
||||||
const self: *@This() = try allocator.create(@This());
|
const self: *@This() = try allocator.create(@This());
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
|
@ -69,7 +69,7 @@ fn Handler(namespace_name: []const u8, mode_name: []const u8) type {
|
||||||
"insert_chars",
|
"insert_chars",
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
return EventHandler.to_owned(self);
|
return .{ EventHandler.to_owned(self), self.bindings.hints() };
|
||||||
}
|
}
|
||||||
pub fn deinit(self: *@This()) void {
|
pub fn deinit(self: *@This()) void {
|
||||||
self.bindings.deinit();
|
self.bindings.deinit();
|
||||||
|
@ -78,7 +78,6 @@ fn Handler(namespace_name: []const u8, mode_name: []const u8) type {
|
||||||
pub fn receive(self: *@This(), from: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
pub fn receive(self: *@This(), from: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
||||||
return self.bindings.receive(from, m);
|
return self.bindings.receive(from, m);
|
||||||
}
|
}
|
||||||
pub const hints = KeybindHints.initComptime(.{});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +87,7 @@ pub const Mode = struct {
|
||||||
|
|
||||||
name: []const u8 = "",
|
name: []const u8 = "",
|
||||||
line_numbers: enum { absolute, relative } = .absolute,
|
line_numbers: enum { absolute, relative } = .absolute,
|
||||||
keybind_hints: ?*const KeybindHints = null,
|
keybind_hints: *const KeybindHints,
|
||||||
cursor_shape: CursorShape = .block,
|
cursor_shape: CursorShape = .block,
|
||||||
|
|
||||||
pub fn deinit(self: *Mode) void {
|
pub fn deinit(self: *Mode) void {
|
||||||
|
@ -97,8 +96,6 @@ pub const Mode = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const KeybindHints = std.static_string_map.StaticStringMap([]const u8);
|
|
||||||
|
|
||||||
//An association of an command with a triggering key chord
|
//An association of an command with a triggering key chord
|
||||||
const Binding = struct {
|
const Binding = struct {
|
||||||
keys: []KeyEvent,
|
keys: []KeyEvent,
|
||||||
|
@ -136,11 +133,7 @@ const Binding = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Hint = struct {
|
pub const KeybindHints = std.StringHashMap([]u8);
|
||||||
keys: []const u8,
|
|
||||||
command: []const u8,
|
|
||||||
description: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
//A Collection of keybindings
|
//A Collection of keybindings
|
||||||
const BindingSet = struct {
|
const BindingSet = struct {
|
||||||
|
@ -158,28 +151,44 @@ const BindingSet = struct {
|
||||||
mode_name: []const u8,
|
mode_name: []const u8,
|
||||||
insert_command: []const u8,
|
insert_command: []const u8,
|
||||||
insert_command_id: ?command.ID = null,
|
insert_command_id: ?command.ID = null,
|
||||||
|
hints_map: ?KeybindHints = null,
|
||||||
|
|
||||||
const KeySyntax = enum { flow, vim };
|
const KeySyntax = enum { flow, vim };
|
||||||
const OnMatchFailure = enum { insert, ignore };
|
const OnMatchFailure = enum { insert, ignore };
|
||||||
|
|
||||||
fn hints(self: *@This()) ![]const Hint {
|
fn hints(self: *@This()) *const KeybindHints {
|
||||||
if (self.hints == null) {
|
if (self.hints_map) |*hints_map| return hints_map;
|
||||||
self.hints = try std.ArrayList(Hint).init(self.allocator);
|
|
||||||
|
self.hints_map = KeybindHints.init(self.allocator);
|
||||||
|
self.build_hints() catch {};
|
||||||
|
return &self.hints_map.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.hints.?.len == self.bindings.items.len) {
|
fn build_hints(self: *@This()) !void {
|
||||||
return self.hints.?.items;
|
const hints_map = &self.hints_map.?;
|
||||||
} else {
|
|
||||||
self.hints.?.clearRetainingCapacity();
|
for (self.press.items) |binding| {
|
||||||
for (self.bindings.items) |binding| {
|
var hint = if (hints_map.get(binding.command)) |previous|
|
||||||
const hint: Hint = .{
|
std.ArrayList(u8).fromOwnedSlice(self.allocator, previous)
|
||||||
.keys = binding.KeyEvent.toString(self.allocator),
|
else
|
||||||
.command = binding.command,
|
std.ArrayList(u8).init(self.allocator);
|
||||||
.description = "", //TODO lookup command description here
|
defer hint.deinit();
|
||||||
};
|
const writer = hint.writer();
|
||||||
try self.hints.?.append(hint);
|
if (hint.items.len > 0) try writer.writeAll(", ");
|
||||||
|
const count = binding.keys.len;
|
||||||
|
for (binding.keys, 0..) |key_, n| {
|
||||||
|
var key = key_;
|
||||||
|
key.event = 0;
|
||||||
|
switch (self.syntax) {
|
||||||
|
// .flow => {
|
||||||
|
else => {
|
||||||
|
try writer.print("{}", .{key});
|
||||||
|
if (n < count - 1)
|
||||||
|
try writer.writeAll(" ");
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return self.hints.?.items;
|
}
|
||||||
|
try hints_map.put(binding.command, try hint.toOwnedSlice());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +210,12 @@ const BindingSet = struct {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(self: *const BindingSet) void {
|
fn deinit(self: *BindingSet) void {
|
||||||
|
if (self.hints_map) |*h| {
|
||||||
|
var i = h.iterator();
|
||||||
|
while (i.next()) |p| self.allocator.free(p.value_ptr.*);
|
||||||
|
h.deinit();
|
||||||
|
}
|
||||||
for (self.press.items) |binding| binding.deinit(self.allocator);
|
for (self.press.items) |binding| binding.deinit(self.allocator);
|
||||||
self.press.deinit();
|
self.press.deinit();
|
||||||
for (self.release.items) |binding| binding.deinit(self.allocator);
|
for (self.release.items) |binding| binding.deinit(self.allocator);
|
||||||
|
|
|
@ -66,7 +66,10 @@ pub const KeyEvent = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(self: @This(), comptime _: []const u8, _: FormatOptions, writer: anytype) !void {
|
pub fn format(self: @This(), comptime _: []const u8, _: FormatOptions, writer: anytype) !void {
|
||||||
try writer.print("{}:{}{}", .{ event_fmt(self.event), mod_fmt(self.modifiers), key_fmt(self.key) });
|
return if (self.event > 0)
|
||||||
|
writer.print("{}:{}{}", .{ event_fmt(self.event), mod_fmt(self.modifiers), key_fmt(self.key) })
|
||||||
|
else
|
||||||
|
writer.print("{}{}", .{ mod_fmt(self.modifiers), key_fmt(self.key) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -190,6 +193,7 @@ pub fn event_fmt(evt: Event) struct {
|
||||||
event.press => writer.writeAll("press"),
|
event.press => writer.writeAll("press"),
|
||||||
event.repeat => writer.writeAll("repeat"),
|
event.repeat => writer.writeAll("repeat"),
|
||||||
event.release => writer.writeAll("release"),
|
event.release => writer.writeAll("release"),
|
||||||
|
else => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} {
|
} {
|
||||||
|
|
|
@ -48,12 +48,14 @@ pub fn Create(options: type) type {
|
||||||
try options.load_entries(self);
|
try options.load_entries(self);
|
||||||
if (@hasDecl(options, "restore_state"))
|
if (@hasDecl(options, "restore_state"))
|
||||||
options.restore_state(self) catch {};
|
options.restore_state(self) catch {};
|
||||||
|
const input_handler, const keybind_hints = try keybind.mode.mini.file_browser.create(allocator, .{
|
||||||
|
.insert_command = "mini_mode_insert_bytes",
|
||||||
|
});
|
||||||
return .{
|
return .{
|
||||||
.{
|
.{
|
||||||
.input_handler = try keybind.mode.mini.file_browser.create(allocator, .{
|
.input_handler = input_handler,
|
||||||
.insert_command = "mini_mode_insert_bytes",
|
|
||||||
}),
|
|
||||||
.event_handler = EventHandler.to_owned(self),
|
.event_handler = EventHandler.to_owned(self),
|
||||||
|
.keybind_hints = keybind_hints,
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.name = options.name(self),
|
.name = options.name(self),
|
||||||
|
|
|
@ -44,12 +44,14 @@ pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.
|
||||||
defer self.allocator.free(text);
|
defer self.allocator.free(text);
|
||||||
try self.input.appendSlice(text);
|
try self.input.appendSlice(text);
|
||||||
}
|
}
|
||||||
|
const input_handler, const keybind_hints = try keybind.mode.mini.find.create(allocator, .{
|
||||||
|
.insert_command = "mini_mode_insert_bytes",
|
||||||
|
});
|
||||||
return .{
|
return .{
|
||||||
.{
|
.{
|
||||||
.input_handler = try keybind.mode.mini.find.create(allocator, .{
|
.input_handler = input_handler,
|
||||||
.insert_command = "mini_mode_insert_bytes",
|
|
||||||
}),
|
|
||||||
.event_handler = EventHandler.to_owned(self),
|
.event_handler = EventHandler.to_owned(self),
|
||||||
|
.keybind_hints = keybind_hints,
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.name = name,
|
.name = name,
|
||||||
|
|
|
@ -38,12 +38,14 @@ pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.
|
||||||
@memcpy(self.buf[0..text.len], text);
|
@memcpy(self.buf[0..text.len], text);
|
||||||
self.input = self.buf[0..text.len];
|
self.input = self.buf[0..text.len];
|
||||||
};
|
};
|
||||||
|
const input_handler, const keybind_hints = try keybind.mode.mini.find_in_files.create(allocator, .{
|
||||||
|
.insert_command = "mini_mode_insert_bytes",
|
||||||
|
});
|
||||||
return .{
|
return .{
|
||||||
.{
|
.{
|
||||||
.input_handler = try keybind.mode.mini.find_in_files.create(allocator, .{
|
.input_handler = input_handler,
|
||||||
.insert_command = "mini_mode_insert_bytes",
|
|
||||||
}),
|
|
||||||
.event_handler = EventHandler.to_owned(self),
|
.event_handler = EventHandler.to_owned(self),
|
||||||
|
.keybind_hints = keybind_hints,
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.name = name,
|
.name = name,
|
||||||
|
|
|
@ -32,12 +32,14 @@ pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.
|
||||||
.start = editor.get_primary().cursor.row + 1,
|
.start = editor.get_primary().cursor.row + 1,
|
||||||
};
|
};
|
||||||
try self.commands.init(self);
|
try self.commands.init(self);
|
||||||
|
const input_handler, const keybind_hints = try keybind.mode.mini.goto.create(allocator, .{
|
||||||
|
.insert_command = "mini_mode_insert_bytes",
|
||||||
|
});
|
||||||
return .{
|
return .{
|
||||||
.{
|
.{
|
||||||
.input_handler = try keybind.mode.mini.goto.create(allocator, .{
|
.input_handler = input_handler,
|
||||||
.insert_command = "mini_mode_insert_bytes",
|
|
||||||
}),
|
|
||||||
.event_handler = EventHandler.to_owned(self),
|
.event_handler = EventHandler.to_owned(self),
|
||||||
|
.keybind_hints = keybind_hints,
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.name = name,
|
.name = name,
|
||||||
|
|
|
@ -41,12 +41,14 @@ pub fn create(allocator: Allocator, ctx: command.Context) !struct { tui.Mode, tu
|
||||||
.operation = if (select) .select else .move,
|
.operation = if (select) .select else .move,
|
||||||
};
|
};
|
||||||
try self.commands.init(self);
|
try self.commands.init(self);
|
||||||
|
const input_handler, const keybind_hints = try keybind.mode.mini.move_to_char.create(allocator, .{
|
||||||
|
.insert_command = "mini_mode_insert_bytes",
|
||||||
|
});
|
||||||
return .{
|
return .{
|
||||||
.{
|
.{
|
||||||
.input_handler = try keybind.mode.mini.move_to_char.create(allocator, .{
|
.input_handler = input_handler,
|
||||||
.insert_command = "mini_mode_insert_bytes",
|
|
||||||
}),
|
|
||||||
.event_handler = EventHandler.to_owned(self),
|
.event_handler = EventHandler.to_owned(self),
|
||||||
|
.keybind_hints = keybind_hints,
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.name = self.name(),
|
.name = self.name(),
|
||||||
|
|
|
@ -59,11 +59,13 @@ pub fn create(allocator: std.mem.Allocator) !tui.Mode {
|
||||||
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = max_menu_width() + 2 });
|
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = max_menu_width() + 2 });
|
||||||
try mv.floating_views.add(self.modal.widget());
|
try mv.floating_views.add(self.modal.widget());
|
||||||
try mv.floating_views.add(self.menu.container_widget);
|
try mv.floating_views.add(self.menu.container_widget);
|
||||||
return .{
|
const input_handler, const keybind_hints = try keybind.mode.overlay.palette.create(allocator, .{
|
||||||
.input_handler = try keybind.mode.overlay.palette.create(allocator, .{
|
|
||||||
.insert_command = "overlay_insert_bytes",
|
.insert_command = "overlay_insert_bytes",
|
||||||
}),
|
});
|
||||||
|
return .{
|
||||||
|
.input_handler = input_handler,
|
||||||
.event_handler = EventHandler.to_owned(self),
|
.event_handler = EventHandler.to_owned(self),
|
||||||
|
.keybind_hints = keybind_hints,
|
||||||
.name = " open recent",
|
.name = " open recent",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,8 +71,9 @@ pub fn Create(options: type) type {
|
||||||
};
|
};
|
||||||
self.menu.scrollbar.?.style_factory = scrollbar_style;
|
self.menu.scrollbar.?.style_factory = scrollbar_style;
|
||||||
if (self.hints) |hints| {
|
if (self.hints) |hints| {
|
||||||
for (hints.values()) |val|
|
var iter = hints.iterator();
|
||||||
self.longest_hint = @max(self.longest_hint, val.len);
|
while (iter.next()) |p|
|
||||||
|
self.longest_hint = @max(self.longest_hint, p.value_ptr.len);
|
||||||
}
|
}
|
||||||
try options.load_entries(self);
|
try options.load_entries(self);
|
||||||
if (@hasDecl(options, "restore_state"))
|
if (@hasDecl(options, "restore_state"))
|
||||||
|
@ -81,11 +82,13 @@ pub fn Create(options: type) type {
|
||||||
try self.start_query();
|
try self.start_query();
|
||||||
try mv.floating_views.add(self.modal.widget());
|
try mv.floating_views.add(self.modal.widget());
|
||||||
try mv.floating_views.add(self.menu.container_widget);
|
try mv.floating_views.add(self.menu.container_widget);
|
||||||
return .{
|
const input_handler, const keybind_hints = try keybind.mode.overlay.palette.create(allocator, .{
|
||||||
.input_handler = try keybind.mode.overlay.palette.create(allocator, .{
|
|
||||||
.insert_command = "overlay_insert_bytes",
|
.insert_command = "overlay_insert_bytes",
|
||||||
}),
|
});
|
||||||
|
return .{
|
||||||
|
.input_handler = input_handler,
|
||||||
.event_handler = EventHandler.to_owned(self),
|
.event_handler = EventHandler.to_owned(self),
|
||||||
|
.keybind_hints = keybind_hints,
|
||||||
.name = options.name,
|
.name = options.name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -572,11 +572,12 @@ fn enter_overlay_mode(self: *Self, mode: type) command.Result {
|
||||||
self.refresh_hover();
|
self.refresh_hover();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn static_mode(self: *Self, mode: anytype, name: []const u8, opts: anytype) !Mode {
|
fn get_input_mode(self: *Self, mode: anytype, name: []const u8, opts: anytype) !Mode {
|
||||||
|
const input_handler, const keybind_hints = try mode.create(self.allocator, opts);
|
||||||
return .{
|
return .{
|
||||||
.input_handler = try mode.create(self.allocator, opts),
|
.input_handler = input_handler,
|
||||||
|
.keybind_hints = keybind_hints,
|
||||||
.name = name,
|
.name = name,
|
||||||
.keybind_hints = &mode.hints,
|
|
||||||
.line_numbers = if (@hasField(@TypeOf(opts), "line_numbers_relative"))
|
.line_numbers = if (@hasField(@TypeOf(opts), "line_numbers_relative"))
|
||||||
if (opts.line_numbers_relative)
|
if (opts.line_numbers_relative)
|
||||||
.relative
|
.relative
|
||||||
|
@ -682,43 +683,43 @@ const cmds = struct {
|
||||||
self.input_mode = null;
|
self.input_mode = null;
|
||||||
}
|
}
|
||||||
self.input_mode = if (std.mem.eql(u8, mode, "vim/normal"))
|
self.input_mode = if (std.mem.eql(u8, mode, "vim/normal"))
|
||||||
try self.static_mode(keybind.mode.input.vim.normal, "NORMAL", .{
|
try self.get_input_mode(keybind.mode.input.vim.normal, "NORMAL", .{
|
||||||
.line_numbers_relative = self.config.vim_normal_gutter_line_numbers_relative,
|
.line_numbers_relative = self.config.vim_normal_gutter_line_numbers_relative,
|
||||||
.cursor_shape = .block,
|
.cursor_shape = .block,
|
||||||
})
|
})
|
||||||
else if (std.mem.eql(u8, mode, "vim/insert"))
|
else if (std.mem.eql(u8, mode, "vim/insert"))
|
||||||
try self.static_mode(keybind.mode.input.vim.insert, "INSERT", .{
|
try self.get_input_mode(keybind.mode.input.vim.insert, "INSERT", .{
|
||||||
.enable_chording = self.config.vim_insert_chording_keybindings,
|
.enable_chording = self.config.vim_insert_chording_keybindings,
|
||||||
.line_numbers_relative = self.config.vim_insert_gutter_line_numbers_relative,
|
.line_numbers_relative = self.config.vim_insert_gutter_line_numbers_relative,
|
||||||
.cursor_shape = .beam,
|
.cursor_shape = .beam,
|
||||||
})
|
})
|
||||||
else if (std.mem.eql(u8, mode, "vim/visual"))
|
else if (std.mem.eql(u8, mode, "vim/visual"))
|
||||||
try self.static_mode(keybind.mode.input.vim.visual, "VISUAL", .{
|
try self.get_input_mode(keybind.mode.input.vim.visual, "VISUAL", .{
|
||||||
.line_numbers_relative = self.config.vim_visual_gutter_line_numbers_relative,
|
.line_numbers_relative = self.config.vim_visual_gutter_line_numbers_relative,
|
||||||
.cursor_shape = .underline,
|
.cursor_shape = .underline,
|
||||||
})
|
})
|
||||||
else if (std.mem.eql(u8, mode, "helix/normal"))
|
else if (std.mem.eql(u8, mode, "helix/normal"))
|
||||||
try self.static_mode(keybind.mode.input.helix.normal, "NOR", .{
|
try self.get_input_mode(keybind.mode.input.helix.normal, "NOR", .{
|
||||||
.line_numbers_relative = self.config.vim_normal_gutter_line_numbers_relative,
|
.line_numbers_relative = self.config.vim_normal_gutter_line_numbers_relative,
|
||||||
.cursor_shape = .block,
|
.cursor_shape = .block,
|
||||||
})
|
})
|
||||||
else if (std.mem.eql(u8, mode, "helix/insert"))
|
else if (std.mem.eql(u8, mode, "helix/insert"))
|
||||||
try self.static_mode(keybind.mode.input.helix.insert, "INS", .{
|
try self.get_input_mode(keybind.mode.input.helix.insert, "INS", .{
|
||||||
.line_numbers_relative = self.config.vim_insert_gutter_line_numbers_relative,
|
.line_numbers_relative = self.config.vim_insert_gutter_line_numbers_relative,
|
||||||
.cursor_shape = .beam,
|
.cursor_shape = .beam,
|
||||||
})
|
})
|
||||||
else if (std.mem.eql(u8, mode, "helix/select"))
|
else if (std.mem.eql(u8, mode, "helix/select"))
|
||||||
try self.static_mode(keybind.mode.input.helix.visual, "SEL", .{
|
try self.get_input_mode(keybind.mode.input.helix.visual, "SEL", .{
|
||||||
.line_numbers_relative = self.config.vim_visual_gutter_line_numbers_relative,
|
.line_numbers_relative = self.config.vim_visual_gutter_line_numbers_relative,
|
||||||
.cursor_shape = .block,
|
.cursor_shape = .block,
|
||||||
})
|
})
|
||||||
else if (std.mem.eql(u8, mode, "flow"))
|
else if (std.mem.eql(u8, mode, "flow"))
|
||||||
try self.static_mode(keybind.mode.input.flow, "flow", .{})
|
try self.get_input_mode(keybind.mode.input.flow, "flow", .{})
|
||||||
else if (std.mem.eql(u8, mode, "home"))
|
else if (std.mem.eql(u8, mode, "home"))
|
||||||
try self.static_mode(keybind.mode.input.home, "flow", .{})
|
try self.get_input_mode(keybind.mode.input.home, "flow", .{})
|
||||||
else ret: {
|
else ret: {
|
||||||
self.logger.print("unknown mode {s}", .{mode});
|
self.logger.print("unknown mode {s}", .{mode});
|
||||||
break :ret try self.static_mode(keybind.mode.input.flow, "flow", .{});
|
break :ret try self.get_input_mode(keybind.mode.input.flow, "flow", .{});
|
||||||
};
|
};
|
||||||
// self.logger.print("input mode: {s}", .{(self.input_mode orelse return).description});
|
// self.logger.print("input mode: {s}", .{(self.input_mode orelse return).description});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue