From dc914ba562b1613d72cf7a4aebad14366383c488 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 19 Nov 2024 18:11:22 +0100 Subject: [PATCH] feat: load and edit key bindings in config directory --- src/keybind/builtin/flow.json | 1 + src/keybind/keybind.zig | 20 +++++++++++++++++++- src/main.zig | 27 +++++++++++++++++++++++++++ src/tui/home.zig | 5 +++++ src/tui/tui.zig | 9 +++++++++ 5 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 926e5fe..5a64382 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -175,6 +175,7 @@ ["r", "open_recent_project"], ["p", "open_command_palette"], ["c", "open_config"], + ["k", "open_keybind_config"], ["t", "change_theme"], ["q", "quit"], ["f1", "open_help"], diff --git a/src/keybind/keybind.zig b/src/keybind/keybind.zig index 97e5a1c..312a193 100644 --- a/src/keybind/keybind.zig +++ b/src/keybind/keybind.zig @@ -7,6 +7,7 @@ const tp = @import("thespian"); const cbor = @import("cbor"); const builtin = @import("builtin"); const log = @import("log"); +const root = @import("root"); const input = @import("input"); const command = @import("command"); @@ -235,7 +236,12 @@ const BindingSet = struct { .command = self.allocator.dupe(u8, "toggle_input_mode") catch @panic("failed to add toggle_input_mode fallback"), .args = "", }) catch {}; - const json_string: []const u8 = builtin_keybinds.get(namespace_name) orelse return error.NotFound; + var free_json_string = true; + const json_string = root.read_keybind_namespace(self.allocator, namespace_name) orelse blk: { + free_json_string = false; + break :blk builtin_keybinds.get(namespace_name) orelse return error.NotFound; + }; + defer if (free_json_string) self.allocator.free(json_string); const parsed = try std.json.parseFromSlice(std.json.Value, self.allocator, json_string, .{}); defer parsed.deinit(); if (parsed.value != .object) return error.NotAnObject; @@ -504,6 +510,18 @@ pub const CursorShape = enum { beam, }; +pub fn get_or_create_namespace_config_file(allocator: std.mem.Allocator, namespace_name: []const u8) ![]const u8 { + if (root.read_keybind_namespace(allocator, namespace_name)) |content| { + allocator.free(content); + } else { + try root.write_keybind_namespace( + namespace_name, + builtin_keybinds.get(namespace_name) orelse builtin_keybinds.get("flow").?, + ); + } + return try root.get_keybind_namespace_file_name(namespace_name); +} + const expectEqual = std.testing.expectEqual; const parse_test_cases = .{ diff --git a/src/main.zig b/src/main.zig index d64560f..6cb1a26 100644 --- a/src/main.zig +++ b/src/main.zig @@ -438,6 +438,20 @@ fn write_json_file(comptime T: type, data: T, allocator: std.mem.Allocator, file try cbor.JsonStream(std.fs.File).jsonWriteValue(&s, &iter); } +pub fn read_keybind_namespace(allocator: std.mem.Allocator, namespace_name: []const u8) ?[]const u8 { + const file_name = get_keybind_namespace_file_name(namespace_name) catch return null; + var file = std.fs.openFileAbsolute(file_name, .{ .mode = .read_only }) catch return null; + defer file.close(); + return file.readToEndAlloc(allocator, 64 * 1024) catch null; +} + +pub fn write_keybind_namespace(namespace_name: []const u8, content: []const u8) !void { + const file_name = try get_keybind_namespace_file_name(namespace_name); + var file = try std.fs.createFileAbsolute(file_name, .{ .truncate = true }); + defer file.close(); + return file.writeAll(content); +} + pub fn get_config_dir() ![]const u8 { return get_app_config_dir(application_name); } @@ -478,6 +492,10 @@ fn get_app_config_dir(appname: []const u8) ![]const u8 { error.PathAlreadyExists => {}, else => return e, }; + + var keybind_dir_buffer: [std.posix.PATH_MAX]u8 = undefined; + std.fs.makeDirAbsolute(try std.fmt.bufPrint(&keybind_dir_buffer, "{s}/{s}", .{ config_dir, keybind_dir })) catch {}; + return config_dir; } @@ -604,6 +622,15 @@ pub fn get_restore_file_name() ![]const u8 { return restore_file; } +const keybind_dir = "keys"; + +pub fn get_keybind_namespace_file_name(namespace_name: []const u8) ![]const u8 { + const local = struct { + var file_buffer: [std.posix.PATH_MAX]u8 = undefined; + }; + return try std.fmt.bufPrint(&local.file_buffer, "{s}/{s}/{s}.json", .{ try get_app_config_dir(application_name), keybind_dir, namespace_name }); +} + fn restart() noreturn { const argv = [_]?[*:0]const u8{ std.os.argv[0], diff --git a/src/tui/home.zig b/src/tui/home.zig index 5ec6a1a..fd5b733 100644 --- a/src/tui/home.zig +++ b/src/tui/home.zig @@ -40,6 +40,7 @@ pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget { 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 }); @@ -125,6 +126,10 @@ fn menu_action_open_config(_: **Menu.State(*Self), _: *Button.State(*Menu.State( 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 {}; } diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 175a43c..076a3fe 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -807,6 +807,15 @@ const cmds = struct { self.mini_mode = null; } pub const exit_mini_mode_meta = .{ .interactive = false }; + + pub fn open_keybind_config(self: *Self, _: Ctx) Result { + var mode_parts = std.mem.splitScalar(u8, self.config.input_mode, '/'); + const namespace_name = mode_parts.first(); + const file_name = try keybind.get_or_create_namespace_config_file(self.allocator, namespace_name); + try tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_name } }); + self.logger.print("restart flow to use changed key bindings", .{}); + } + pub const open_keybind_config_meta = .{ .description = "Edit key bindings" }; }; pub const MiniMode = struct {