diff --git a/build.zig b/build.zig index 926e476..1b194ed 100644 --- a/build.zig +++ b/build.zig @@ -509,6 +509,7 @@ pub fn build_exe( .{ .name = "thespian", .module = thespian_mod }, .{ .name = "log", .module = log_mod }, .{ .name = "Buffer", .module = Buffer_mod }, + .{ .name = "config", .module = config_mod }, }, }); @@ -527,6 +528,7 @@ pub fn build_exe( tests.root_module.addImport("thespian", thespian_mod); tests.root_module.addImport("log", log_mod); tests.root_module.addImport("Buffer", Buffer_mod); + tests.root_module.addImport("config", config_mod); // b.installArtifact(tests); break :blk b.addRunArtifact(tests); }; diff --git a/src/config.zig b/src/config.zig index 2e62b42..bb6367b 100644 --- a/src/config.zig +++ b/src/config.zig @@ -24,7 +24,7 @@ idle_actions: []const IdleAction = &default_actions, enable_format_on_save: bool = false, restore_last_cursor_position: bool = true, follow_cursor_on_buffer_switch: bool = false, //scroll cursor into view on buffer switch -default_cursor: []const u8 = "default", +default_cursor: CursorShape = .default, enable_auto_save: bool = false, limit_auto_save_file_types: ?[]const []const u8 = null, // null means *all* @@ -114,3 +114,13 @@ pub const WhitespaceMode = enum { full, none, }; + +pub const CursorShape = enum { + default, + block_blink, + block, + underline_blink, + underline, + beam_blink, + beam, +}; diff --git a/src/keybind/builtin/helix.json b/src/keybind/builtin/helix.json index a76828c..23ae2cf 100644 --- a/src/keybind/builtin/helix.json +++ b/src/keybind/builtin/helix.json @@ -27,10 +27,12 @@ ["ctrl+^", "open_previous_file"], ["ctrl+w v", "add_split"], + ["ctrl+w c", "toggle_centered_view"], ["ctrl+w h", "goto_left_split"], ["ctrl+w l", "goto_right_split"], ["ctrl+w H", "swap_left_split"], ["ctrl+w L", "swap_right_split"], + ["ctrl+w F", "goto_file_split"], ["ctrl+w q", "close_split"], ["ctrl+w o", "close_other_splits"], diff --git a/src/keybind/keybind.zig b/src/keybind/keybind.zig index 4fe2d67..16485d0 100644 --- a/src/keybind/keybind.zig +++ b/src/keybind/keybind.zig @@ -14,6 +14,7 @@ const command = @import("command"); const EventHandler = @import("EventHandler"); const KeyEvent = input.KeyEvent; const SelectionStyle = @import("Buffer").Selection.Style; +pub const CursorShape = @import("config").CursorShape; const parse_flow = @import("parse_flow.zig"); const parse_vim = @import("parse_vim.zig"); @@ -743,16 +744,6 @@ pub const LineNumbers = enum { relative, }; -pub const CursorShape = enum { - default, - block_blink, - block, - underline_blink, - underline, - beam_blink, - 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); diff --git a/src/main.zig b/src/main.zig index 763bd8d..5171ff4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -698,6 +698,7 @@ pub fn write_config_to_writer(comptime T: type, data: T, writer: *std.Io.Writer) const default: T = .{}; inline for (@typeInfo(T).@"struct".fields) |field_info| { if (config_eql( + T, field_info.type, @field(data, field_info.name), @field(default, field_info.name), @@ -718,9 +719,57 @@ pub fn write_config_to_writer(comptime T: type, data: T, writer: *std.Io.Writer) }, } try writer.print("\n", .{}); + try writer.print("# value type: ", .{}); + try write_config_value_description(T, field_info.type, writer); + try writer.print("\n", .{}); + try writer.print("\n", .{}); } } +fn write_config_value_description(T: type, field_type: type, writer: *std.Io.Writer) !void { + switch (@typeInfo(field_type)) { + .int => switch (field_type) { + u24 => try writer.print("24 bit hex color value in quotes", .{}), + usize => try writer.print("positive integer", .{}), + u8 => try writer.print("positive integer up to 255", .{}), + else => unsupported_error(T, field_type), + }, + .bool => try writer.print("true or false", .{}), + .@"enum" => { + var first = true; + try writer.print("one of ", .{}); + for (std.meta.tags(field_type)) |tag| { + if (first) first = false else try writer.print(", ", .{}); + try writer.print("\"{s}\"", .{@tagName(tag)}); + } + }, + .optional => |info| switch (@typeInfo(info.child)) { + else => { + try write_config_value_description(T, info.child, writer); + try writer.print(" or null", .{}); + }, + }, + .pointer => |info| switch (info.size) { + .slice => if (info.child == u8) + try writer.print("quoted string", .{}) + else if (info.child == u16) + try writer.print("quoted string (u16)", .{}) + else if (info.child == []const u8) + try writer.print("list of quoted strings", .{}) + else if (info.child == @import("config").IdleAction) + try writer.print("list of idle actions", .{}) + else + unsupported_error(T, info.child), + else => unsupported_error(T, info.child), + }, + else => unsupported_error(T, field_type), + } +} + +fn unsupported_error(config_type: type, value_type: type) void { + @compileError("unsupported config type in " ++ @typeName(config_type) ++ ": " ++ @typeName(value_type)); +} + fn write_color_value(value: u24, writer: *std.Io.Writer) std.Io.Writer.Error!void { var hex: [7]u8 = undefined; try writer.writeByte('"'); @@ -728,12 +777,12 @@ fn write_color_value(value: u24, writer: *std.Io.Writer) std.Io.Writer.Error!voi try writer.writeByte('"'); } -fn config_eql(comptime T: type, a: T, b: T) bool { +fn config_eql(config_type: type, T: type, a: T, b: T) bool { switch (T) { []const u8 => return std.mem.eql(u8, a, b), []const []const u8 => { if (a.len != b.len) return false; - for (a, 0..) |x, i| if (!config_eql([]const u8, x, b[i])) return false; + for (a, 0..) |x, i| if (!config_eql(config_type, []const u8, x, b[i])) return false; return true; }, else => {}, @@ -745,19 +794,19 @@ fn config_eql(comptime T: type, a: T, b: T) bool { return true; if (a == null or b == null) return false; - return config_eql(info.child, a.?, b.?); + return config_eql(config_type, info.child, a.?, b.?); }, .pointer => |info| switch (info.size) { .slice => { if (a.len != b.len) return false; - for (a, 0..) |x, i| if (!config_eql(info.child, x, b[i])) return false; + for (a, 0..) |x, i| if (!config_eql(config_type, info.child, x, b[i])) return false; return true; }, - else => @compileError("unsupported config type " ++ @typeName(T)), + else => unsupported_error(config_type, T), }, else => {}, } - @compileError("unsupported config type " ++ @typeName(T)); + unsupported_error(config_type, T); } fn write_json_file(comptime T: type, data: T, allocator: std.mem.Allocator, file_name: []const u8) !void { diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 338dbfc..9a58b2d 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -73,7 +73,6 @@ render_pending: bool = false, keepalive_timer: ?tp.Cancellable = null, input_idle_timer: ?tp.Cancellable = null, mouse_idle_timer: ?tp.Cancellable = null, -default_cursor: keybind.CursorShape = .default, fontface_: []const u8 = "", fontfaces_: std.ArrayListUnmanaged([]const u8) = .{}, input_is_idle: bool = false, @@ -183,8 +182,6 @@ fn init(allocator: Allocator) InitError!*Self { instance_ = self; defer instance_ = null; - self.default_cursor = std.meta.stringToEnum(keybind.CursorShape, conf.default_cursor) orelse .default; - self.config_.default_cursor = @tagName(self.default_cursor); self.rdr_.handler_ctx = self; self.rdr_.dispatch_input = dispatch_input; self.rdr_.dispatch_mouse = dispatch_mouse; @@ -1745,7 +1742,8 @@ fn set_terminal_style(self: *Self, theme_: *const Widget.Theme) void { pub fn get_cursor_shape() renderer.CursorShape { const self = current(); - const shape_ = if (self.input_mode_) |mode| mode.cursor_shape orelse self.default_cursor else self.default_cursor; + const default_cursor = self.config_.default_cursor; + const shape_ = if (self.input_mode_) |mode| mode.cursor_shape orelse default_cursor else default_cursor; const shape = if (self.rdr_.vx.caps.multi_cursor and shape_ == .default) .beam_blink else shape_; return switch (shape) { .default => .default,