refactor: move dynamic keybindings to keybind module
This commit is contained in:
parent
d33bb955f9
commit
7fff8fc529
3 changed files with 64 additions and 36 deletions
52
build.zig
52
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 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");
|
||||||
|
|
||||||
|
|
|
@ -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"));
|
Loading…
Add table
Reference in a new issue