From 693ed91b12247817b538cc0e655595a0064ede5d Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 10 Dec 2025 12:23:27 +0100 Subject: [PATCH 1/5] fix: don't mark document as dirty if a filter makes no changes closes #424 --- src/tui/editor.zig | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 5a2a3be..559d9ad 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -325,6 +325,7 @@ pub const Editor = struct { chunks: usize = 0, eol_mode: Buffer.EolMode = .lf, utf8_sanitized: bool = false, + no_changes: bool = false, } = null, matches: Match.List = .empty, match_token: usize = 0, @@ -6280,10 +6281,21 @@ pub const Editor = struct { self.cancel_all_selections(); self.cancel_all_matches(); if (state.whole_file) |buf| { - state.work_root = try b.load_from_string(buf.items, &state.eol_mode, &state.utf8_sanitized); - state.bytes = buf.items.len; - state.chunks = 1; - primary.cursor = state.old_primary.cursor; + const old_hash = blk: { + var content: std.Io.Writer.Allocating = .init(self.allocator); + defer content.deinit(); + try root.store(&content.writer, try self.buf_eol_mode()); + break :blk std.hash.XxHash3.hash(0, content.written()); + }; + const new_hash = std.hash.XxHash3.hash(0, buf.items); + if (old_hash == new_hash) { + state.no_changes = true; + } else { + state.work_root = try b.load_from_string(buf.items, &state.eol_mode, &state.utf8_sanitized); + state.bytes = buf.items.len; + state.chunks = 1; + primary.cursor = state.old_primary.cursor; + } } else { const sel = primary.enable_selection(root, self.metrics); sel.begin = state.begin; @@ -6291,10 +6303,14 @@ pub const Editor = struct { if (state.old_primary_reversed) sel.reverse(); primary.cursor = sel.end; } - try self.update_buf_and_eol_mode(state.work_root, state.eol_mode, state.utf8_sanitized); - primary.cursor.clamp_to_buffer(state.work_root, self.metrics); self.logger.print("filter: done (bytes:{d} chunks:{d})", .{ state.bytes, state.chunks }); - self.reset_syntax(); + if (state.no_changes) { + self.logger.print("filter: no changes", .{}); + } else { + try self.update_buf_and_eol_mode(state.work_root, state.eol_mode, state.utf8_sanitized); + primary.cursor.clamp_to_buffer(state.work_root, self.metrics); + self.reset_syntax(); + } self.clamp(); self.need_render(); if (self.need_save_after_filter) |info| { From 0974935ced4bae3be09fb2f3b3b20841f20eca8e Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 10 Dec 2025 12:25:36 +0100 Subject: [PATCH 2/5] fix: prevent whole file filter commands from erasing the entire file --- src/tui/editor.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 559d9ad..46a52a1 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -6281,6 +6281,10 @@ pub const Editor = struct { self.cancel_all_selections(); self.cancel_all_matches(); if (state.whole_file) |buf| { + if (buf.items.len == 0) { + self.logger.print_err("filter", "empty filter result", .{}); + return; + } const old_hash = blk: { var content: std.Io.Writer.Allocating = .init(self.allocator); defer content.deinit(); From b9b49c41641006f87d8a94a49dcfaa4284875c4e Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 10 Dec 2025 12:26:21 +0100 Subject: [PATCH 3/5] fix: do not try to auto save ephemeral buffers --- src/tui/editor.zig | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 46a52a1..39fb611 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -722,7 +722,7 @@ pub const Editor = struct { buffer.file_type_icon = fti; buffer.file_type_color = ftc; } - const auto_save = if (self.buffer) |b| b.is_auto_save() else false; + const auto_save = if (self.buffer) |b| b.is_auto_save() and !b.is_ephemeral() else false; if (buffer_meta) |meta| { const frame_ = tracy.initZone(@src(), .{ .name = "extract_state" }); @@ -804,7 +804,7 @@ pub const Editor = struct { if (self.file_path) |file_path| { if (self.buffer) |b_mut| try b_mut.store_to_file_and_clean(file_path); } else return error.SaveNoFileName; - try self.send_editor_save(self.file_path.?, b.is_auto_save()); + try self.send_editor_save(self.file_path.?, b.is_auto_save() and !b.is_ephemeral()); self.last.dirty = false; self.update_event() catch {}; } @@ -1850,7 +1850,7 @@ pub const Editor = struct { _ = try self.handlers.msg(.{ "E", "update", token_from(new_root), token_from(old_root), @intFromEnum(eol_mode) }); if (self.buffer) |buffer| if (self.syntax) |_| if (self.file_path) |file_path| if (old_root != null and new_root != null) project_manager.did_change(file_path, buffer.lsp_version, try text_from_root(new_root, eol_mode), try text_from_root(old_root, eol_mode), eol_mode) catch {}; - if (self.buffer) |b| if (b.is_auto_save()) + if (self.buffer) |b| if (b.is_auto_save() and !b.is_ephemeral()) tp.self_pid().send(.{ "cmd", "save_file", .{} }) catch {}; } @@ -6509,7 +6509,7 @@ pub const Editor = struct { buffer.file_type_color = ftc; } const file_exists = if (self.buffer) |b| b.file_exists else false; - const auto_save = if (self.buffer) |b| b.is_auto_save() else false; + const auto_save = if (self.buffer) |b| b.is_auto_save() and !b.is_ephemeral() else false; try self.send_editor_open(self.file_path orelse "", file_exists, ftn, fti, ftc, auto_save); self.logger.print("file type {s}", .{file_type}); } @@ -6520,7 +6520,7 @@ pub const Editor = struct { const fti = if (self.file_type) |ft| ft.icon orelse file_type_config.default.icon else file_type_config.default.icon; const ftc = if (self.file_type) |ft| ft.color orelse file_type_config.default.color else file_type_config.default.color; const file_exists = if (self.buffer) |b| b.file_exists else false; - const auto_save = if (self.buffer) |b| b.is_auto_save() else false; + const auto_save = if (self.buffer) |b| b.is_auto_save() and !b.is_ephemeral() else false; try self.send_editor_open(self.file_path orelse "", file_exists, ftn, fti, ftc, auto_save); self.last = .{}; } From 6bb6141ef3fe3c10b657246d0772a84f3b8b4d80 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 10 Dec 2025 12:27:58 +0100 Subject: [PATCH 4/5] fix: avoid resetting cursors and matches if formatter makes no changes --- src/tui/editor.zig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 39fb611..8bcdfde 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -6278,8 +6278,6 @@ pub const Editor = struct { if (state.before_root != root) return error.Stop; defer self.filter_deinit(); const primary = self.get_primary(); - self.cancel_all_selections(); - self.cancel_all_matches(); if (state.whole_file) |buf| { if (buf.items.len == 0) { self.logger.print_err("filter", "empty filter result", .{}); @@ -6295,12 +6293,16 @@ pub const Editor = struct { if (old_hash == new_hash) { state.no_changes = true; } else { + self.cancel_all_selections(); + self.cancel_all_matches(); state.work_root = try b.load_from_string(buf.items, &state.eol_mode, &state.utf8_sanitized); state.bytes = buf.items.len; state.chunks = 1; primary.cursor = state.old_primary.cursor; } } else { + self.cancel_all_selections(); + self.cancel_all_matches(); const sel = primary.enable_selection(root, self.metrics); sel.begin = state.begin; sel.end = state.pos.cursor; From 4cb0f7df02a5e2428518ffdc3dc0a10ec7491197 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 10 Dec 2025 12:38:47 +0100 Subject: [PATCH 5/5] feat: activated selected entry on tab in the completion palette closes #423 --- src/tui/mode/overlay/completion_palette.zig | 4 ++++ src/tui/mode/overlay/palette.zig | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/src/tui/mode/overlay/completion_palette.zig b/src/tui/mode/overlay/completion_palette.zig index a0ee283..5c5cef6 100644 --- a/src/tui/mode/overlay/completion_palette.zig +++ b/src/tui/mode/overlay/completion_palette.zig @@ -205,6 +205,10 @@ fn get_replace_selection(replace: Buffer.Selection) ?Buffer.Selection { replace; } +pub fn complete(palette: *Type, _: ?*Type.ButtonType) !void { + palette.menu.activate_selected(); +} + fn select(menu: **Type.MenuType, button: *Type.ButtonType, _: Type.Pos) void { const values = get_values(button.opts.label); const editor = tui.get_active_editor() orelse return; diff --git a/src/tui/mode/overlay/palette.zig b/src/tui/mode/overlay/palette.zig index 4b4d1cf..e19e907 100644 --- a/src/tui/mode/overlay/palette.zig +++ b/src/tui/mode/overlay/palette.zig @@ -554,6 +554,12 @@ pub fn Create(options: type) type { .icon = "󰗨", }; + pub fn palette_menu_complete(self: *Self, _: Ctx) Result { + if (@hasDecl(options, "complete")) + options.complete(self, self.menu.get_selected()) catch {}; + } + pub const palette_menu_complete_meta: Meta = .{}; + pub fn palette_menu_activate(self: *Self, _: Ctx) Result { self.menu.activate_selected(); }