refactor: move dynamic keybindings to keybind module

This commit is contained in:
CJ van den Berg 2024-11-13 18:15:00 +01:00
parent d33bb955f9
commit 7fff8fc529
3 changed files with 64 additions and 36 deletions

View file

@ -8,6 +8,7 @@ pub fn build(b: *std.Build) void {
const use_tree_sitter = b.option(bool, "use_tree_sitter", "Enable tree-sitter (default: yes)") orelse true; const use_tree_sitter = b.option(bool, "use_tree_sitter", "Enable tree-sitter (default: yes)") orelse true;
const strip = b.option(bool, "strip", "Disable debug information (default: no)") orelse false; const strip = b.option(bool, "strip", "Disable debug information (default: no)") orelse false;
const pie = b.option(bool, "pie", "Produce an executable with position independent code (default: no)") orelse false; const pie = b.option(bool, "pie", "Produce an executable with position independent code (default: no)") orelse false;
const dynamic_keybind = b.option(bool, "dynamic_keybind", "Build with dynamic keybinding support (default: no) (EXPERIMENTAL)") orelse false;
const options = b.addOptions(); const options = b.addOptions();
options.addOption(bool, "enable_tracy", tracy_enabled); options.addOption(bool, "enable_tracy", tracy_enabled);
@ -16,6 +17,7 @@ pub fn build(b: *std.Build) void {
options.addOption(bool, "use_tree_sitter", use_tree_sitter); options.addOption(bool, "use_tree_sitter", use_tree_sitter);
options.addOption(bool, "strip", strip); options.addOption(bool, "strip", strip);
options.addOption(bool, "pie", pie); options.addOption(bool, "pie", pie);
options.addOption(bool, "dynamic_keybind", dynamic_keybind);
const options_mod = options.createModule(); const options_mod = options.createModule();
@ -169,6 +171,33 @@ pub fn build(b: *std.Build) void {
.{ .name = "thespian", .module = thespian_mod }, .{ .name = "thespian", .module = thespian_mod },
}, },
}); });
const keybind_dynamic_mod = b.createModule(.{
.root_source_file = b.path("src/keybind/dynamic/keybind.zig"),
.imports = &.{
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "command", .module = command_mod },
.{ .name = "EventHandler", .module = EventHandler_mod },
.{ .name = "renderer", .module = renderer_mod },
.{ .name = "thespian", .module = thespian_mod },
},
});
const keybind_mod = if (dynamic_keybind) keybind_dynamic_mod else keybind_static_mod;
const keybind_test_run_cmd = blk: {
const tests = b.addTest(.{
.root_source_file = b.path("src/keybind/dynamic/keybind.zig"),
.target = target,
.optimize = optimize,
});
tests.root_module.addImport("cbor", cbor_mod);
tests.root_module.addImport("command", command_mod);
tests.root_module.addImport("EventHandler", EventHandler_mod);
tests.root_module.addImport("renderer", renderer_mod);
tests.root_module.addImport("thespian", thespian_mod);
// b.installArtifact(tests);
break :blk b.addRunArtifact(tests);
};
const ripgrep_mod = b.createModule(.{ const ripgrep_mod = b.createModule(.{
.root_source_file = b.path("src/ripgrep.zig"), .root_source_file = b.path("src/ripgrep.zig"),
@ -232,7 +261,7 @@ pub fn build(b: *std.Build) void {
.{ .name = "syntax", .module = syntax_mod }, .{ .name = "syntax", .module = syntax_mod },
.{ .name = "text_manip", .module = text_manip_mod }, .{ .name = "text_manip", .module = text_manip_mod },
.{ .name = "Buffer", .module = Buffer_mod }, .{ .name = "Buffer", .module = Buffer_mod },
.{ .name = "keybind", .module = keybind_static_mod }, .{ .name = "keybind", .module = keybind_mod },
.{ .name = "ripgrep", .module = ripgrep_mod }, .{ .name = "ripgrep", .module = ripgrep_mod },
.{ .name = "theme", .module = themes_dep.module("theme") }, .{ .name = "theme", .module = themes_dep.module("theme") },
.{ .name = "themes", .module = themes_dep.module("themes") }, .{ .name = "themes", .module = themes_dep.module("themes") },
@ -301,24 +330,6 @@ pub fn build(b: *std.Build) void {
const check = b.step("check", "Check the app"); const check = b.step("check", "Check the app");
check.dependOn(&check_exe.step); check.dependOn(&check_exe.step);
const keybinding_tests = b.addTest(.{
.root_source_file = b.path("src/tui/keybindings.zig"),
.target = target,
.optimize = optimize,
});
keybinding_tests.root_module.addImport("renderer", renderer_mod);
keybinding_tests.root_module.addImport("thespian", thespian_mod);
keybinding_tests.root_module.addImport("tui", tui_mod);
keybinding_tests.root_module.addImport("keybind", keybind_static_mod);
keybinding_tests.root_module.addImport("config", config_mod);
keybinding_tests.root_module.addImport("command", command_mod);
keybinding_tests.root_module.addImport("EventHandler", EventHandler_mod);
keybinding_tests.root_module.addImport("build_options", options_mod);
keybinding_tests.root_module.addImport("log", log_mod);
keybinding_tests.root_module.addImport("color", color_mod);
keybinding_tests.root_module.addImport("theme", themes_dep.module("theme"));
keybinding_tests.root_module.addImport("Buffer", Buffer_mod);
const tests = b.addTest(.{ const tests = b.addTest(.{
.root_source_file = b.path("test/tests.zig"), .root_source_file = b.path("test/tests.zig"),
.target = target, .target = target,
@ -336,11 +347,10 @@ pub fn build(b: *std.Build) void {
// b.installArtifact(tests); // b.installArtifact(tests);
const test_run_cmd = b.addRunArtifact(tests); const test_run_cmd = b.addRunArtifact(tests);
const keybinding_test_run_cmd = b.addRunArtifact(keybinding_tests);
const test_step = b.step("test", "Run unit tests"); const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&test_run_cmd.step); test_step.dependOn(&test_run_cmd.step);
test_step.dependOn(&keybinding_test_run_cmd.step); test_step.dependOn(&keybind_test_run_cmd.step);
const lints_step = b.step("lint", "Run lints"); const lints_step = b.step("lint", "Run lints");

View file

@ -12,7 +12,23 @@ const mod = @import("renderer").input.modifier;
const event_type = @import("renderer").input.event_type; const event_type = @import("renderer").input.event_type;
const command = @import("command"); const command = @import("command");
const EventHandler = @import("EventHandler"); const EventHandler = @import("EventHandler");
const tui = @import("tui.zig");
pub const Mode = struct {
input_handler: EventHandler,
event_handler: ?EventHandler = null,
name: []const u8 = "",
line_numbers: enum { absolute, relative } = .absolute,
keybind_hints: ?*const KeybindHints = null,
cursor_shape: renderer.CursorShape = .block,
pub fn deinit(self: *Mode) void {
self.input_handler.deinit();
if (self.event_handler) |eh| eh.deinit();
}
};
pub const KeybindHints = std.static_string_map.StaticStringMap([]const u8);
//A single key event, such as Ctrl-E //A single key event, such as Ctrl-E
pub const KeyEvent = struct { pub const KeyEvent = struct {
@ -361,7 +377,7 @@ pub const Hint = struct {
}; };
//A Collection of keybindings //A Collection of keybindings
pub const Mode = struct { const BindingSet = struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
bindings: std.ArrayList(Binding), bindings: std.ArrayList(Binding),
on_match_failure: OnMatchFailure = .ignore, on_match_failure: OnMatchFailure = .ignore,
@ -369,7 +385,7 @@ pub const Mode = struct {
current_sequence_egc: std.ArrayList(u8), current_sequence_egc: std.ArrayList(u8),
last_key_event_timestamp_ms: i64 = 0, last_key_event_timestamp_ms: i64 = 0,
input_buffer: std.ArrayList(u8), input_buffer: std.ArrayList(u8),
tui_mode: tui.Mode, tui_mode: Mode,
const OnMatchFailure = enum { insert, ignore }; const OnMatchFailure = enum { insert, ignore };
@ -377,8 +393,8 @@ pub const Mode = struct {
bindings: []const []const []const u8, bindings: []const []const []const u8,
on_match_failure: OnMatchFailure, on_match_failure: OnMatchFailure,
pub fn toMode(self: *const @This(), allocator: std.mem.Allocator) !*Mode { pub fn toMode(self: *const @This(), allocator: std.mem.Allocator) !*BindingSet {
var result = try Mode.init(allocator); var result = try init(allocator);
result.on_match_failure = self.on_match_failure; result.on_match_failure = self.on_match_failure;
var state: enum { key_event, command, args } = .key_event; var state: enum { key_event, command, args } = .key_event;
for (self.bindings) |entry| { for (self.bindings) |entry| {
@ -437,8 +453,8 @@ pub const Mode = struct {
.last_key_event_timestamp_ms = std.time.milliTimestamp(), .last_key_event_timestamp_ms = std.time.milliTimestamp(),
.input_buffer = try std.ArrayList(u8).initCapacity(allocator, 16), .input_buffer = try std.ArrayList(u8).initCapacity(allocator, 16),
.bindings = std.ArrayList(Binding).init(allocator), .bindings = std.ArrayList(Binding).init(allocator),
.tui_mode = tui.Mode{ .tui_mode = Mode{
.handler = EventHandler.to_owned(self), .input_handler = EventHandler.to_owned(self),
.name = "INSERT", .name = "INSERT",
//.description = "vim", //.description = "vim",
.line_numbers = .relative, .line_numbers = .relative,
@ -448,7 +464,7 @@ pub const Mode = struct {
return self; return self;
} }
pub fn deinit(self: *const Mode) void { pub fn deinit(self: *const BindingSet) void {
for (self.bindings.items) |binding| { for (self.bindings.items) |binding| {
binding.deinit(); binding.deinit();
} }
@ -530,7 +546,7 @@ pub const Mode = struct {
} }
//register a key press and try to match it with a binding //register a key press and try to match it with a binding
pub fn registerKeyEvent(self: *Mode, egc: u8, event: KeyEvent) !void { pub fn registerKeyEvent(self: *BindingSet, egc: u8, event: KeyEvent) !void {
//clear key history if enough time has passed since last key press //clear key history if enough time has passed since last key press
const timestamp = std.time.milliTimestamp(); const timestamp = std.time.milliTimestamp();
@ -595,7 +611,7 @@ pub const Mode = struct {
}; };
//A collection of various modes under a single namespace, such as "vim" or "emacs" //A collection of various modes under a single namespace, such as "vim" or "emacs"
pub const Namespace = HashMap(*Mode); const Namespace = HashMap(*BindingSet);
const HashMap = std.StringArrayHashMap; const HashMap = std.StringArrayHashMap;
//Data structure for mapping key events to keybindings //Data structure for mapping key events to keybindings
@ -614,7 +630,7 @@ pub const Bindings = struct {
return self.namespaces.values()[self.active_namespace]; return self.namespaces.values()[self.active_namespace];
} }
pub fn activeMode(self: *Bindings) *Mode { pub fn activeMode(self: *Bindings) *BindingSet {
return self.activeNamespace().values()[self.active_mode]; return self.activeNamespace().values()[self.active_mode];
} }
@ -629,7 +645,7 @@ pub const Bindings = struct {
return self; return self;
} }
pub fn addMode(self: *@This(), namespace_name: []const u8, mode_name: []const u8, mode: *Mode) !void { pub fn addMode(self: *@This(), namespace_name: []const u8, mode_name: []const u8, mode: *BindingSet) !void {
const namespace = self.namespaces.getPtr(namespace_name) orelse blk: { const namespace = self.namespaces.getPtr(namespace_name) orelse blk: {
try self.namespaces.putNoClobber(namespace_name, Namespace.init(self.allocator)); try self.namespaces.putNoClobber(namespace_name, Namespace.init(self.allocator));
break :blk self.namespaces.getPtr(namespace_name).?; break :blk self.namespaces.getPtr(namespace_name).?;
@ -648,7 +664,7 @@ pub const Bindings = struct {
self.allocator.destroy(self); self.allocator.destroy(self);
} }
pub fn addNamespace(self: *Bindings, name: []const u8, modes: []const Mode) !void { pub fn addNamespace(self: *Bindings, name: []const u8, modes: []const BindingSet) !void {
try self.namespaces.put(name, .{ .name = name, .modes = modes }); try self.namespaces.put(name, .{ .name = name, .modes = modes });
} }
@ -659,7 +675,7 @@ pub const Bindings = struct {
for (parsed.value.object.values(), 0..) |namespace, i| { for (parsed.value.object.values(), 0..) |namespace, i| {
if (namespace != .object) return error.namespaceNotObject; if (namespace != .object) return error.namespaceNotObject;
for (namespace.object.values(), 0..) |mode, j| { for (namespace.object.values(), 0..) |mode, j| {
const mode_config = try std.json.parseFromValue(Mode.JsonConfig, self.allocator, mode, .{}); const mode_config = try std.json.parseFromValue(BindingSet.JsonConfig, self.allocator, mode, .{});
defer mode_config.deinit(); defer mode_config.deinit();
const parsed_mode = try mode_config.value.toMode(self.allocator); const parsed_mode = try mode_config.value.toMode(self.allocator);
try self.addMode(parsed.value.object.keys()[i], namespace.object.keys()[j], parsed_mode); try self.addMode(parsed.value.object.keys()[i], namespace.object.keys()[j], parsed_mode);
@ -668,7 +684,6 @@ pub const Bindings = struct {
} }
}; };
const alloc = std.testing.allocator;
const expectEqual = std.testing.expectEqual; const expectEqual = std.testing.expectEqual;
const parse_test_cases = .{ const parse_test_cases = .{
@ -682,6 +697,7 @@ const parse_test_cases = .{
}; };
test "parse" { test "parse" {
const alloc = std.testing.allocator;
inline for (parse_test_cases) |case| { inline for (parse_test_cases) |case| {
var parsed = Sequence.init(alloc); var parsed = Sequence.init(alloc);
defer parsed.deinit(); defer parsed.deinit();
@ -708,6 +724,7 @@ const match_test_cases = .{
}; };
test "match" { test "match" {
const alloc = std.testing.allocator;
inline for (match_test_cases) |case| { inline for (match_test_cases) |case| {
var input = Sequence.init(alloc); var input = Sequence.init(alloc);
defer input.deinit(); defer input.deinit();
@ -721,6 +738,7 @@ test "match" {
} }
test "json" { test "json" {
const alloc = std.testing.allocator;
var bindings = try Bindings.init(alloc); var bindings = try Bindings.init(alloc);
defer bindings.deinit(); defer bindings.deinit();
try bindings.loadJson(@embedFile("keybindings.json")); try bindings.loadJson(@embedFile("keybindings.json"));