From 7fff8fc52928e0a298b06f22167e9f6a8910c4e8 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 13 Nov 2024 18:15:00 +0100 Subject: [PATCH] refactor: move dynamic keybindings to keybind module --- build.zig | 52 +++++++++++-------- .../dynamic/keybind.zig} | 48 +++++++++++------ src/{tui => keybind/dynamic}/keybindings.json | 0 3 files changed, 64 insertions(+), 36 deletions(-) rename src/{tui/keybindings.zig => keybind/dynamic/keybind.zig} (95%) rename src/{tui => keybind/dynamic}/keybindings.json (100%) diff --git a/build.zig b/build.zig index 3ba16ea..57ee08b 100644 --- a/build.zig +++ b/build.zig @@ -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 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 dynamic_keybind = b.option(bool, "dynamic_keybind", "Build with dynamic keybinding support (default: no) (EXPERIMENTAL)") orelse false; const options = b.addOptions(); 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, "strip", strip); options.addOption(bool, "pie", pie); + options.addOption(bool, "dynamic_keybind", dynamic_keybind); const options_mod = options.createModule(); @@ -169,6 +171,33 @@ pub fn build(b: *std.Build) void { .{ .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(.{ .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 = "text_manip", .module = text_manip_mod }, .{ .name = "Buffer", .module = Buffer_mod }, - .{ .name = "keybind", .module = keybind_static_mod }, + .{ .name = "keybind", .module = keybind_mod }, .{ .name = "ripgrep", .module = ripgrep_mod }, .{ .name = "theme", .module = themes_dep.module("theme") }, .{ .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"); 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(.{ .root_source_file = b.path("test/tests.zig"), .target = target, @@ -336,11 +347,10 @@ pub fn build(b: *std.Build) void { // b.installArtifact(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"); 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"); diff --git a/src/tui/keybindings.zig b/src/keybind/dynamic/keybind.zig similarity index 95% rename from src/tui/keybindings.zig rename to src/keybind/dynamic/keybind.zig index 64eeca7..0215029 100644 --- a/src/tui/keybindings.zig +++ b/src/keybind/dynamic/keybind.zig @@ -12,7 +12,23 @@ const mod = @import("renderer").input.modifier; const event_type = @import("renderer").input.event_type; const command = @import("command"); 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 pub const KeyEvent = struct { @@ -361,7 +377,7 @@ pub const Hint = struct { }; //A Collection of keybindings -pub const Mode = struct { +const BindingSet = struct { allocator: std.mem.Allocator, bindings: std.ArrayList(Binding), on_match_failure: OnMatchFailure = .ignore, @@ -369,7 +385,7 @@ pub const Mode = struct { current_sequence_egc: std.ArrayList(u8), last_key_event_timestamp_ms: i64 = 0, input_buffer: std.ArrayList(u8), - tui_mode: tui.Mode, + tui_mode: Mode, const OnMatchFailure = enum { insert, ignore }; @@ -377,8 +393,8 @@ pub const Mode = struct { bindings: []const []const []const u8, on_match_failure: OnMatchFailure, - pub fn toMode(self: *const @This(), allocator: std.mem.Allocator) !*Mode { - var result = try Mode.init(allocator); + pub fn toMode(self: *const @This(), allocator: std.mem.Allocator) !*BindingSet { + var result = try init(allocator); result.on_match_failure = self.on_match_failure; var state: enum { key_event, command, args } = .key_event; for (self.bindings) |entry| { @@ -437,8 +453,8 @@ pub const Mode = struct { .last_key_event_timestamp_ms = std.time.milliTimestamp(), .input_buffer = try std.ArrayList(u8).initCapacity(allocator, 16), .bindings = std.ArrayList(Binding).init(allocator), - .tui_mode = tui.Mode{ - .handler = EventHandler.to_owned(self), + .tui_mode = Mode{ + .input_handler = EventHandler.to_owned(self), .name = "INSERT", //.description = "vim", .line_numbers = .relative, @@ -448,7 +464,7 @@ pub const Mode = struct { return self; } - pub fn deinit(self: *const Mode) void { + pub fn deinit(self: *const BindingSet) void { for (self.bindings.items) |binding| { binding.deinit(); } @@ -530,7 +546,7 @@ pub const Mode = struct { } //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 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" -pub const Namespace = HashMap(*Mode); +const Namespace = HashMap(*BindingSet); const HashMap = std.StringArrayHashMap; //Data structure for mapping key events to keybindings @@ -614,7 +630,7 @@ pub const Bindings = struct { 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]; } @@ -629,7 +645,7 @@ pub const Bindings = struct { 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: { try self.namespaces.putNoClobber(namespace_name, Namespace.init(self.allocator)); break :blk self.namespaces.getPtr(namespace_name).?; @@ -648,7 +664,7 @@ pub const Bindings = struct { 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 }); } @@ -659,7 +675,7 @@ pub const Bindings = struct { for (parsed.value.object.values(), 0..) |namespace, i| { if (namespace != .object) return error.namespaceNotObject; 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(); const parsed_mode = try mode_config.value.toMode(self.allocator); 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 parse_test_cases = .{ @@ -682,6 +697,7 @@ const parse_test_cases = .{ }; test "parse" { + const alloc = std.testing.allocator; inline for (parse_test_cases) |case| { var parsed = Sequence.init(alloc); defer parsed.deinit(); @@ -708,6 +724,7 @@ const match_test_cases = .{ }; test "match" { + const alloc = std.testing.allocator; inline for (match_test_cases) |case| { var input = Sequence.init(alloc); defer input.deinit(); @@ -721,6 +738,7 @@ test "match" { } test "json" { + const alloc = std.testing.allocator; var bindings = try Bindings.init(alloc); defer bindings.deinit(); try bindings.loadJson(@embedFile("keybindings.json")); diff --git a/src/tui/keybindings.json b/src/keybind/dynamic/keybindings.json similarity index 100% rename from src/tui/keybindings.json rename to src/keybind/dynamic/keybindings.json