From 550834a6262d711b5ad70de859bdc3745a87ae6a Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 16 Jul 2025 15:38:31 +0200 Subject: [PATCH 01/11] feat: add toggle_format_on_save command --- src/tui/editor.zig | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 74fafd9..1a3f314 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -350,6 +350,8 @@ pub const Editor = struct { completions: std.ArrayListUnmanaged(u8) = .empty, + enable_format_on_save: bool, + need_save_after_filter: ?struct { then: ?struct { cmd: []const u8, @@ -458,6 +460,7 @@ pub const Editor = struct { .animation_lag = get_animation_max_lag(), .animation_frame_rate = frame_rate, .animation_last_time = time.microTimestamp(), + .enable_format_on_save = tui.config().enable_format_on_save, .enable_terminal_cursor = tui.config().enable_terminal_cursor, .render_whitespace = from_whitespace_mode(tui.config().whitespace_mode), }; @@ -4767,6 +4770,11 @@ pub const Editor = struct { pub const SaveOption = enum { default, format, no_format }; + pub fn toggle_format_on_save(self: *Self, _: Context) Result { + self.enable_format_on_save = !self.enable_format_on_save; + } + pub const toggle_format_on_save_meta: Meta = .{ .description = "Toggle format on save" }; + pub fn save_file(self: *Self, ctx: Context) Result { var option: SaveOption = .default; var then = false; @@ -4780,7 +4788,7 @@ pub const Editor = struct { _ = ctx.args.match(.{tp.extract(&option)}) catch false; } - if ((option == .default and tui.config().enable_format_on_save) or option == .format) if (self.get_formatter()) |_| { + if ((option == .default and self.enable_format_on_save) or option == .format) if (self.get_formatter()) |_| { self.need_save_after_filter = .{ .then = if (then) .{ .cmd = cmd, .args = args } else null }; const primary = self.get_primary(); const sel = primary.selection; From f8d3bbf6436e388f15f5df59f320beaf386d7b08 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 16 Jul 2025 16:32:37 +0200 Subject: [PATCH 02/11] feat: save and restore enable_format_on_save in editor state --- src/tui/editor.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 1a3f314..a6b1cad 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -367,10 +367,11 @@ pub const Editor = struct { const Result = command.Result; pub fn write_state(self: *const Self, writer: Buffer.MetaWriter) !void { - try cbor.writeArrayHeader(writer, 6); + try cbor.writeArrayHeader(writer, 7); try cbor.writeValue(writer, self.file_path orelse ""); try cbor.writeValue(writer, self.clipboard orelse ""); try cbor.writeValue(writer, self.last_find_query orelse ""); + try cbor.writeValue(writer, self.enable_format_on_save); if (self.find_history) |history| { try cbor.writeArrayHeader(writer, history.items.len); for (history.items) |item| @@ -403,6 +404,7 @@ pub const Editor = struct { tp.extract(&file_path), tp.extract(&clipboard), tp.extract(&query), + tp.extract(&self.enable_format_on_save), tp.extract_cbor(&find_history), tp.extract_cbor(&view_cbor), tp.extract_cbor(&cursels_cbor), From 7bdbe60776848eee66a7f4bf5111345c45ea4967 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 16 Jul 2025 16:33:45 +0200 Subject: [PATCH 03/11] fix: restoring of last_find_query from editor state --- src/tui/editor.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index a6b1cad..20639bf 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -398,12 +398,12 @@ pub const Editor = struct { var view_cbor: []const u8 = undefined; var cursels_cbor: []const u8 = undefined; var clipboard: []const u8 = undefined; - var query: []const u8 = undefined; + var last_find_query: []const u8 = undefined; var find_history: []const u8 = undefined; if (!try cbor.match(buf, .{ tp.extract(&file_path), tp.extract(&clipboard), - tp.extract(&query), + tp.extract(&last_find_query), tp.extract(&self.enable_format_on_save), tp.extract_cbor(&find_history), tp.extract_cbor(&view_cbor), @@ -413,7 +413,7 @@ pub const Editor = struct { if (op == .open_file) try self.open(file_path); self.clipboard = if (clipboard.len > 0) try self.allocator.dupe(u8, clipboard) else null; - self.last_find_query = if (query.len > 0) try self.allocator.dupe(u8, clipboard) else null; + self.last_find_query = if (last_find_query.len > 0) try self.allocator.dupe(u8, last_find_query) else null; const rows = self.view.rows; const cols = self.view.cols; if (!try self.view.extract(&view_cbor)) From c300c4f7f2ab33dd2b8f50eb7be90d89696ba9cd Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 16 Jul 2025 16:35:03 +0200 Subject: [PATCH 04/11] feat: add enable_auto_save configuration option --- src/config.zig | 2 ++ src/tui/editor.zig | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/config.zig b/src/config.zig index 22c233c..9f4949a 100644 --- a/src/config.zig +++ b/src/config.zig @@ -21,6 +21,8 @@ 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", +enable_auto_save: bool = false, +auto_save_file_types: []const []const u8 = &.{}, indent_size: usize = 4, tab_width: usize = 8, diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 20639bf..5a3452f 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -350,6 +350,7 @@ pub const Editor = struct { completions: std.ArrayListUnmanaged(u8) = .empty, + enable_auto_save: bool, enable_format_on_save: bool, need_save_after_filter: ?struct { @@ -462,6 +463,7 @@ pub const Editor = struct { .animation_lag = get_animation_max_lag(), .animation_frame_rate = frame_rate, .animation_last_time = time.microTimestamp(), + .enable_auto_save = tui.config().enable_auto_save, .enable_format_on_save = tui.config().enable_format_on_save, .enable_terminal_cursor = tui.config().enable_terminal_cursor, .render_whitespace = from_whitespace_mode(tui.config().whitespace_mode), From c5fe992f75fc855e16f4fe8a17924620fa112d58 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 16 Jul 2025 16:35:22 +0200 Subject: [PATCH 05/11] feat: add toggle_auto_save command --- src/tui/editor.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 5a3452f..76b4a5f 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -4774,6 +4774,11 @@ pub const Editor = struct { pub const SaveOption = enum { default, format, no_format }; + pub fn toggle_auto_save(self: *Self, _: Context) Result { + self.enable_auto_save = !self.enable_auto_save; + } + pub const toggle_auto_save_meta: Meta = .{ .description = "Toggle auto save" }; + pub fn toggle_format_on_save(self: *Self, _: Context) Result { self.enable_format_on_save = !self.enable_format_on_save; } From 35e36089a8f02993ef0cc37b0750a4329a355f9c Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 16 Jul 2025 16:40:28 +0200 Subject: [PATCH 06/11] feat: save and restore enable_auto_save in editor state --- src/tui/editor.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 76b4a5f..7cbf096 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -368,11 +368,12 @@ pub const Editor = struct { const Result = command.Result; pub fn write_state(self: *const Self, writer: Buffer.MetaWriter) !void { - try cbor.writeArrayHeader(writer, 7); + try cbor.writeArrayHeader(writer, 8); try cbor.writeValue(writer, self.file_path orelse ""); try cbor.writeValue(writer, self.clipboard orelse ""); try cbor.writeValue(writer, self.last_find_query orelse ""); try cbor.writeValue(writer, self.enable_format_on_save); + try cbor.writeValue(writer, self.enable_auto_save); if (self.find_history) |history| { try cbor.writeArrayHeader(writer, history.items.len); for (history.items) |item| @@ -406,6 +407,7 @@ pub const Editor = struct { tp.extract(&clipboard), tp.extract(&last_find_query), tp.extract(&self.enable_format_on_save), + tp.extract(&self.enable_auto_save), tp.extract_cbor(&find_history), tp.extract_cbor(&view_cbor), tp.extract_cbor(&cursels_cbor), From 99664742c3cdd4548e3e0eb601ad838890a395e6 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 16 Jul 2025 17:22:06 +0200 Subject: [PATCH 07/11] feat: save file if changed if auto save is enabled --- src/tui/editor.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 7cbf096..ea5c1ae 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -1693,6 +1693,8 @@ pub const Editor = struct { _ = try self.handlers.msg(.{ "E", "update", token_from(new_root), token_from(old_root), @intFromEnum(eol_mode) }); if (self.syntax) |_| if (self.file_path) |file_path| if (old_root != null and new_root != null) project_manager.did_change(file_path, self.lsp_version, try text_from_root(new_root, eol_mode), try text_from_root(old_root, eol_mode), eol_mode) catch {}; + if (self.enable_auto_save) + tp.self_pid().send(.{ "cmd", "save_file", .{} }) catch {}; } fn send_editor_eol_mode(self: *const Self, eol_mode: Buffer.EolMode, utf8_sanitized: bool) !void { From ada40b989cf51b7c9cc5ba7075c3759ac0edabc7 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 16 Jul 2025 19:38:27 +0200 Subject: [PATCH 08/11] feat: add limit_auto_save_file_types config option closes #77 --- src/config.zig | 2 +- src/tui/editor.zig | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/config.zig b/src/config.zig index 9f4949a..91e3cdd 100644 --- a/src/config.zig +++ b/src/config.zig @@ -22,7 +22,7 @@ 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", enable_auto_save: bool = false, -auto_save_file_types: []const []const u8 = &.{}, +limit_auto_save_file_types: ?[]const []const u8 = null, // null means *all* indent_size: usize = 4, tab_width: usize = 8, diff --git a/src/tui/editor.zig b/src/tui/editor.zig index ea5c1ae..c4bd75b 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -353,6 +353,8 @@ pub const Editor = struct { enable_auto_save: bool, enable_format_on_save: bool, + restored_state: bool = false, + need_save_after_filter: ?struct { then: ?struct { cmd: []const u8, @@ -396,6 +398,7 @@ pub const Editor = struct { pub fn extract_state(self: *Self, buf: []const u8, comptime op: enum { none, open_file }) !void { tp.trace(tp.channel.debug, .{ "extract_state", self.file_path }); tp.trace(tp.channel.debug, tp.message{ .buf = buf }); + self.restored_state = true; var file_path: []const u8 = undefined; var view_cbor: []const u8 = undefined; var cursels_cbor: []const u8 = undefined; @@ -599,6 +602,8 @@ pub const Editor = struct { file_type_config.guess_file_type(self.file_path, content.items); }; + self.maybe_enable_auto_save(); + const syn = blk: { const frame_ = tracy.initZone(@src(), .{ .name = "create" }); defer frame_.deinit(); @@ -642,6 +647,22 @@ pub const Editor = struct { try self.send_editor_open(file_path, new_buf.file_exists, ftn, fti, ftc); } + fn maybe_enable_auto_save(self: *Self) void { + if (self.restored_state) return; + self.enable_auto_save = false; + if (!tui.config().enable_auto_save) return; + const self_file_type = self.file_type orelse return; + + enable: { + const file_types = tui.config().limit_auto_save_file_types orelse break :enable; + for (file_types) |file_type| + if (std.mem.eql(u8, file_type, self_file_type.name)) + break :enable; + return; + } + self.enable_auto_save = true; + } + fn close(self: *Self) !void { var meta = std.ArrayListUnmanaged(u8).empty; defer meta.deinit(self.allocator); From 489c4027cba591db59340d1452854af6f742a0c4 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 16 Jul 2025 19:38:56 +0200 Subject: [PATCH 09/11] feat: allow arrays of scalar values as config options --- src/main.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main.zig b/src/main.zig index 01641d9..07e1cf5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -687,6 +687,14 @@ fn config_eql(comptime T: type, a: T, b: T) bool { return false; return config_eql(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; + return true; + }, + else => @compileError("unsupported config type " ++ @typeName(T)), + }, else => {}, } @compileError("unsupported config type " ++ @typeName(T)); From 46dfde76857834c62d3b3e4005012132a4739f4c Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 16 Jul 2025 19:40:11 +0200 Subject: [PATCH 10/11] refactor: simplify and improve management of config buffers --- src/tui/tui.zig | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 23633c8..844af57 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -23,8 +23,9 @@ const Allocator = std.mem.Allocator; allocator: Allocator, rdr_: renderer, config_: @import("config"), -highlight_columns_: []u16, -highlight_columns_configured: []u16, +config_bufs: [][]const u8, +highlight_columns_: []const u16, +highlight_columns_configured: []const u16, frame_time: usize, // in microseconds frame_clock: tp.metronome, frame_clock_running: bool = false, @@ -103,19 +104,12 @@ const InitError = error{ fn init(allocator: Allocator) InitError!*Self { var conf, const conf_bufs = root.read_config(@import("config"), allocator); - defer root.free_config(allocator, conf_bufs); if (@hasDecl(renderer, "install_crash_handler") and conf.start_debugger_on_crash) renderer.jit_debugger_enabled = true; const theme_, const parsed_theme = get_theme_by_name(allocator, conf.theme) orelse get_theme_by_name(allocator, "dark_modern") orelse return error.UnknownTheme; conf.theme = theme_.name; - conf.whitespace_mode = try allocator.dupe(u8, conf.whitespace_mode); - conf.input_mode = try allocator.dupe(u8, conf.input_mode); - conf.top_bar = try allocator.dupe(u8, conf.top_bar); - conf.bottom_bar = try allocator.dupe(u8, conf.bottom_bar); - conf.include_files = try allocator.dupe(u8, conf.include_files); - conf.highlight_columns = try allocator.dupe(u8, conf.highlight_columns); if (build_options.gui) conf.enable_terminal_cursor = false; const frame_rate: usize = @intCast(tp.env.get().num("frame-rate")); @@ -138,8 +132,9 @@ fn init(allocator: Allocator) InitError!*Self { self.* = .{ .allocator = allocator, .config_ = conf, - .highlight_columns_ = if (conf.highlight_columns_enabled) highlight_columns__ else &.{}, - .highlight_columns_configured = highlight_columns__, + .config_bufs = conf_bufs, + .highlight_columns_ = if (conf.highlight_columns_enabled) conf.highlight_columns else &.{}, + .highlight_columns_configured = conf.highlight_columns, .rdr_ = try renderer.init(allocator, self, tp.env.get().is("no-alternate"), dispatch_initialized), .frame_time = frame_time, .frame_clock = frame_clock, @@ -225,7 +220,6 @@ fn init_delayed(self: *Self) command.Result { } fn deinit(self: *Self) void { - self.allocator.free(self.highlight_columns_configured); if (self.mouse_idle_timer) |*t| { t.cancel() catch {}; t.deinit(); @@ -256,6 +250,7 @@ fn deinit(self: *Self) void { self.rdr_.deinit(); self.logger.deinit(); self.query_cache_.deinit(); + root.free_config(self.allocator, self.config_bufs); self.allocator.destroy(self); } From 30c6ac0e82c94915aae5020dde930e2b13556cda Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 16 Jul 2025 19:40:42 +0200 Subject: [PATCH 11/11] feat: BREAKING make highlight_columns a list of integers --- src/config.zig | 2 +- src/tui/tui.zig | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/config.zig b/src/config.zig index 91e3cdd..257f6bb 100644 --- a/src/config.zig +++ b/src/config.zig @@ -10,7 +10,7 @@ enable_terminal_cursor: bool = true, enable_terminal_color_scheme: bool = builtin.os.tag != .windows, highlight_current_line: bool = true, highlight_current_line_gutter: bool = true, -highlight_columns: []const u8 = "80 100 120", +highlight_columns: []const u16 = &.{ 80, 100, 120 }, highlight_columns_alpha: u8 = 240, highlight_columns_enabled: bool = false, whitespace_mode: []const u8 = "none", diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 844af57..6472742 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -119,15 +119,6 @@ fn init(allocator: Allocator) InitError!*Self { const frame_time = std.time.us_per_s / conf.frame_rate; const frame_clock = try tp.metronome.init(frame_time); - const hl_cols: usize = blk: { - var it = std.mem.splitScalar(u8, conf.highlight_columns, ' '); - var idx: usize = 0; - while (it.next()) |_| - idx += 1; - break :blk idx; - }; - const highlight_columns__ = try allocator.alloc(u16, hl_cols); - var self = try allocator.create(Self); self.* = .{ .allocator = allocator, @@ -154,13 +145,6 @@ fn init(allocator: Allocator) InitError!*Self { instance_ = self; defer instance_ = null; - var it = std.mem.splitScalar(u8, conf.highlight_columns, ' '); - var idx: usize = 0; - while (it.next()) |arg| { - highlight_columns__[idx] = std.fmt.parseInt(u16, arg, 10) catch 0; - idx += 1; - } - 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;