From 288e23e8b0fa65605b187967278a7c6debeda50f Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 18:32:41 +0100 Subject: [PATCH 01/15] fix: completion with no replacements causes OOM --- src/tui/mode/overlay/completion_palette.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tui/mode/overlay/completion_palette.zig b/src/tui/mode/overlay/completion_palette.zig index 5b141c5..585f5ba 100644 --- a/src/tui/mode/overlay/completion_palette.zig +++ b/src/tui/mode/overlay/completion_palette.zig @@ -192,10 +192,10 @@ const Range = struct { start: Position, end: Position }; const Position = struct { line: usize, character: usize }; fn get_replace_selection(replace: Buffer.Selection) ?Buffer.Selection { - return if (tui.get_active_editor()) |edt| - replace.from_pos(edt.buf_root() catch return null, edt.metrics) - else if (replace.empty()) + return if (replace.empty()) null + else if (tui.get_active_editor()) |edt| + replace.from_pos(edt.buf_root() catch return null, edt.metrics) else replace; } From 19751e7fd45db0bbf4e14b867559b25c7c27a4be Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 18:52:01 +0100 Subject: [PATCH 02/15] fix: incorrect use of a labeled switch in snippet --- src/snippet.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/snippet.zig b/src/snippet.zig index 2f6f8e0..b3c4a68 100644 --- a/src/snippet.zig +++ b/src/snippet.zig @@ -70,7 +70,7 @@ pub fn parse(allocator: std.mem.Allocator, snippet: []const u8) Error!Snippet { max_id = @max(id orelse unreachable, max_id); id = null; state = .initial; - break :fsm; + continue :fsm .initial; }, }, .placeholder => switch (c) { From b22337a2b3e7e736aa1ffa22ced36afd442a572f Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 18:52:46 +0100 Subject: [PATCH 03/15] refactor: extract insertText from completion responses --- src/Project.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Project.zig b/src/Project.zig index c83dec0..920e3dc 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -1362,6 +1362,7 @@ fn send_completion_item(to: tp.pid_ref, file_path: []const u8, row: usize, col: var documentation: []const u8 = ""; var documentation_kind: []const u8 = ""; var sortText: []const u8 = ""; + var insertText: []const u8 = ""; var insertTextFormat: usize = 0; var textEdit: TextEdit = .{}; var additionalTextEdits: [32]TextEdit = undefined; @@ -1402,6 +1403,8 @@ fn send_completion_item(to: tp.pid_ref, file_path: []const u8, row: usize, col: try cbor.skipValue(&iter); } } + } else if (std.mem.eql(u8, field_name, "insertText")) { + if (!(try cbor.matchValue(&iter, cbor.extract(&insertText)))) return invalid_field("insertText"); } else if (std.mem.eql(u8, field_name, "sortText")) { if (!(try cbor.matchValue(&iter, cbor.extract(&sortText)))) return invalid_field("sortText"); } else if (std.mem.eql(u8, field_name, "insertTextFormat")) { @@ -1437,6 +1440,7 @@ fn send_completion_item(to: tp.pid_ref, file_path: []const u8, row: usize, col: documentation, documentation_kind, sortText, + insertText, insertTextFormat, textEdit.newText, insert.start.line, From 62bc86d2db2443c7b441cf16c30a1fbcd151add6 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 18:54:04 +0100 Subject: [PATCH 04/15] refactor: add insertText to completion_palette get_values function --- src/tui/mode/overlay/completion_palette.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tui/mode/overlay/completion_palette.zig b/src/tui/mode/overlay/completion_palette.zig index 585f5ba..307641c 100644 --- a/src/tui/mode/overlay/completion_palette.zig +++ b/src/tui/mode/overlay/completion_palette.zig @@ -131,6 +131,7 @@ const Values = struct { label_description: []const u8, detail: []const u8, documentation: []const u8, + insertText: []const u8, insertTextFormat: usize, textEdit_newText: []const u8, }; @@ -143,6 +144,7 @@ fn get_values(item_cbor: []const u8) Values { var documentation: []const u8 = ""; var sort_text: []const u8 = ""; var kind: u8 = 0; + var insertText: []const u8 = ""; var insertTextFormat: usize = 0; var textEdit_newText: []const u8 = ""; var replace: Buffer.Selection = .{}; @@ -160,6 +162,7 @@ fn get_values(item_cbor: []const u8) Values { cbor.extract(&documentation), // documentation cbor.any, // documentation_kind cbor.extract(&sort_text), // sortText + cbor.extract(&insertText), // insertText cbor.extract(&insertTextFormat), // insertTextFormat cbor.extract(&textEdit_newText), // textEdit_newText cbor.any, // insert.begin.row @@ -183,6 +186,7 @@ fn get_values(item_cbor: []const u8) Values { .detail = detail, .documentation = documentation, .insertTextFormat = insertTextFormat, + .insertText = insertText, .textEdit_newText = textEdit_newText, }; } From c71ddb29000a0730c9a8f4b800a75c5767869710 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 18:54:48 +0100 Subject: [PATCH 05/15] refactor: add newText and insertText to info panel in debug builds --- src/tui/mode/overlay/completion_palette.zig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tui/mode/overlay/completion_palette.zig b/src/tui/mode/overlay/completion_palette.zig index 307641c..305295f 100644 --- a/src/tui/mode/overlay/completion_palette.zig +++ b/src/tui/mode/overlay/completion_palette.zig @@ -4,6 +4,7 @@ const tp = @import("thespian"); const root = @import("soft_root").root; const command = @import("command"); const Buffer = @import("Buffer"); +const builtin = @import("builtin"); const tui = @import("../../tui.zig"); pub const Type = @import("palette.zig").Create(@This()); @@ -231,7 +232,12 @@ pub fn updated(palette: *Type, button_: ?*Type.ButtonType) !void { try mv.set_info_content(values.label, .replace); try mv.set_info_content(" ", .append); // blank line try mv.set_info_content(values.detail, .append); - // try mv.set_info_content(values.textEdit_newText, .append); + if (builtin.mode == .Debug) { + try mv.set_info_content("newText:", .append); // blank line + try mv.set_info_content(values.textEdit_newText, .append); + try mv.set_info_content("insertText:", .append); // blank line + try mv.set_info_content(values.insertText, .append); + } try mv.set_info_content(" ", .append); // blank line try mv.set_info_content(values.documentation, .append); } From 387a3416c3f2d2116bf5601addae59fa9c9f140f Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 18:56:58 +0100 Subject: [PATCH 06/15] refactor: add cursels_tabstops member to editor --- src/tui/editor.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 00d056f..6a7a738 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -308,6 +308,7 @@ pub const Editor = struct { cursels: CurSel.List = .empty, cursels_saved: CurSel.List = .empty, + cursels_tabstops: std.ArrayList([]CurSel) = .empty, selection_mode: SelectMode = .char, selection_drag_initial: ?Selection = null, target_column: ?Cursor = null, @@ -432,6 +433,11 @@ pub const Editor = struct { return count; } + fn cancel_all_tabstops(self: *Self) void { + for (self.cursels_tabstops.items) |ts_list| self.allocator.free(ts_list); + self.cursels_tabstops.clearRetainingCapacity(); + } + pub fn write_state(self: *const Self, writer: *std.Io.Writer) !void { try cbor.writeArrayHeader(writer, 10); try cbor.writeValue(writer, self.file_path orelse ""); @@ -544,6 +550,7 @@ pub const Editor = struct { self.diagnostics.deinit(self.allocator); self.completions.deinit(self.allocator); if (self.syntax) |syn| syn.destroy(tui.query_cache()); + self.cancel_all_tabstops(); self.cursels.deinit(self.allocator); self.matches.deinit(self.allocator); self.handlers.deinit(); @@ -4166,6 +4173,7 @@ pub const Editor = struct { pub const move_buffer_end_meta: Meta = .{ .description = "Move cursor to end of file" }; pub fn cancel(self: *Self, _: Context) Result { + self.cancel_all_tabstops(); self.cancel_all_selections(); self.cancel_all_matches(); @import("keybind").clear_integer_argument(); From 8152de3df99abd5930dee8db7b4e16455c68aa0d Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 19:17:30 +0100 Subject: [PATCH 07/15] refactor: prefer completion insertText over newText or label --- src/tui/mode/overlay/completion_palette.zig | 23 +++++++++++---------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/tui/mode/overlay/completion_palette.zig b/src/tui/mode/overlay/completion_palette.zig index 305295f..a0ee283 100644 --- a/src/tui/mode/overlay/completion_palette.zig +++ b/src/tui/mode/overlay/completion_palette.zig @@ -47,9 +47,9 @@ pub fn load_entries(palette: *Type) !usize { var max_label_len: usize = 0; for (palette.entries.items) |*item| { const values = get_values(item.cbor); - if (get_replace_selection(values.replace)) |replace| { - if (palette.value.replace == null) palette.value.replace = replace; - } + if (palette.value.replace == null) if (get_replace_selection(values.replace)) |replace| { + palette.value.replace = replace; + }; item.label = values.label; item.sort_text = values.sort_text; @@ -207,15 +207,16 @@ fn get_replace_selection(replace: Buffer.Selection) ?Buffer.Selection { 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; + const text = if (values.insertText.len > 0) + values.insertText + else if (values.textEdit_newText.len > 0) + values.textEdit_newText + else + values.label; switch (values.insertTextFormat) { - 2 => { - const snippet = @import("snippet").parse(menu.*.opts.ctx.allocator, values.textEdit_newText) catch return; - defer snippet.deinit(menu.*.opts.ctx.allocator); - tp.self_pid().send(.{ "cmd", "insert_chars", .{snippet.text} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e); - }, - else => { - tp.self_pid().send(.{ "cmd", "insert_chars", .{values.label} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e); - }, + 2 => editor.insert_snippet(text) catch |e| menu.*.opts.ctx.logger.err(module_name, e), + else => editor.insert_cursels(text) catch |e| menu.*.opts.ctx.logger.err(module_name, e), } const mv = tui.mainview() orelse return; mv.cancel_info_content() catch {}; From 098d9253582b263723701bdafca2574738490d79 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 19:18:26 +0100 Subject: [PATCH 08/15] refactor: add editor.add_match_from_selection function --- src/tui/editor.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 6a7a738..6aeaf02 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -5460,6 +5460,13 @@ pub const Editor = struct { (self.matches.addOne(self.allocator) catch return).* = match; } + fn add_match_from_selection(self: *Self, sel: Selection) void { + var match: Match = Match.from_selection(sel); + if (match.end.eql(self.get_primary().cursor)) + match.has_selection = true; + (self.matches.addOne(self.allocator) catch return).* = match; + } + fn find_selection_match(self: *const Self, sel: Selection) ?*Match { for (self.matches.items) |*match_| if (match_.*) |*match| { if (match.to_selection().eql(sel)) From c462e3abda8efd36f954e9212da4d71cbd695379 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 19:19:39 +0100 Subject: [PATCH 09/15] refactor: split editor.insert_chars into insert_chars and insert_cursels --- src/tui/editor.zig | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 6aeaf02..c078c1c 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -4626,10 +4626,7 @@ pub const Editor = struct { } pub const select_prev_sibling_meta: Meta = .{ .description = "Move selection to previous AST sibling node" }; - pub fn insert_chars(self: *Self, ctx: Context) Result { - var chars: []const u8 = undefined; - if (!try ctx.args.match(.{tp.extract(&chars)})) - return error.InvalidInsertCharsArgument; + pub fn insert_cursels(self: *Self, chars: []const u8) Result { const b = try self.buf_for_update(); var root = b.root; for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { @@ -4638,6 +4635,13 @@ pub const Editor = struct { try self.update_buf(root); self.clamp(); } + + pub fn insert_chars(self: *Self, ctx: Context) Result { + var chars: []const u8 = undefined; + if (!try ctx.args.match(.{tp.extract(&chars)})) + return error.InvalidInsertCharsArgument; + return self.insert_cursels(chars); + } pub const insert_chars_meta: Meta = .{ .arguments = &.{.string} }; pub fn insert_line(self: *Self, _: Context) Result { From 025ef9c7684facbba32abcf0ec80a669c614d88d Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 19:20:21 +0100 Subject: [PATCH 10/15] refactor: add editor.insert_snippet --- src/tui/editor.zig | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index c078c1c..9185778 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -18,6 +18,7 @@ const Cell = @import("renderer").Cell; const input = @import("input"); const command = @import("command"); const EventHandler = @import("EventHandler"); +const snippet = @import("snippet"); const scrollbar_v = @import("scrollbar_v.zig"); const editor_gutter = @import("editor_gutter.zig"); @@ -4626,6 +4627,51 @@ pub const Editor = struct { } pub const select_prev_sibling_meta: Meta = .{ .description = "Move selection to previous AST sibling node" }; + pub fn insert_snippet(self: *Self, snippet_text: []const u8) Result { + self.logger.print("snippet: {s}", .{snippet_text}); + const value = try snippet.parse(self.allocator, snippet_text); + defer value.deinit(self.allocator); + + const root_ = try self.buf_root(); + const primary = self.get_primary(); + const eol_mode = try self.buf_eol_mode(); + var cursor_pos: usize = 0; + _ = try root_.get_range(.{ + .begin = .{ .row = 0, .col = 0 }, + .end = primary.cursor, + }, null, &cursor_pos, null, self.metrics); + + try self.insert_cursels(value.text); + const root = try self.buf_root(); + + if (self.count_cursels() > 1) + return; + + self.cancel_all_tabstops(); + for (value.tabstops) |ts| { + var cursels: std.ArrayList(CurSel) = .empty; + for (ts) |placeholder| { + const ts_begin_pos = cursor_pos + placeholder.begin.@"0"; + const ts_begin = root.byte_offset_to_line_and_col(ts_begin_pos, self.metrics, eol_mode); + const ts_end = if (placeholder.end) |end| blk: { + const ts_end_pos = cursor_pos + end.@"0"; + break :blk root.byte_offset_to_line_and_col(ts_end_pos, self.metrics, eol_mode); + } else null; + const p = (try cursels.addOne(self.allocator)); + p.* = if (ts_end) |ts_end_| .{ + .cursor = ts_end_, + .selection = .{ .begin = ts_begin, .end = ts_end_ }, + } else .{ + .cursor = ts_begin, + }; + self.logger.print("placeholder: {}, cursor_pos:{d}", .{ placeholder, cursor_pos }); + self.logger.print("tabstop: {}", .{p.*}); + if (p.selection) |sel| self.add_match_from_selection(sel); + } + (try self.cursels_tabstops.addOne(self.allocator)).* = try cursels.toOwnedSlice(self.allocator); + } + } + pub fn insert_cursels(self: *Self, chars: []const u8) Result { const b = try self.buf_for_update(); var root = b.root; From 50db9082d879ae3ddea9cfd91874bffa1dec94df Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 19:21:20 +0100 Subject: [PATCH 11/15] refactor: attempt to render tabstops --- src/tui/editor.zig | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 9185778..bc7f227 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -1107,6 +1107,7 @@ pub const Editor = struct { if (ctx.hl_row) |hl_row| if (hl_row == ctx.buf_row) self_.render_line_highlight_cell(ctx.theme, c_); self_.render_matches(&ctx.match_idx, ctx.theme, c_); + self_.render_tabstops(ctx.theme, c_); self_.render_selections(ctx.theme, c_); _ = n.putc(c_) catch {}; ctx.cell_map.set_yx(ctx.y, ctx.x, .{ .cell_type = cell_map_val }); @@ -1124,6 +1125,7 @@ pub const Editor = struct { if (ctx.hl_row) |hl_row| if (hl_row == ctx.buf_row) self_.render_line_highlight_cell(ctx.theme, &c); self_.render_matches(&ctx.match_idx, ctx.theme, &c); + self_.render_tabstops(ctx.theme, &c); self_.render_selections(ctx.theme, &c); _ = n.putc(&c) catch {}; var term_cell = render_terminator(n, ctx.theme); @@ -1317,6 +1319,24 @@ pub const Editor = struct { }; } + fn render_tabstops(self: *const Self, theme: *const Widget.Theme, cell: *Cell) void { + var y: c_uint = undefined; + var x: c_uint = undefined; + self.plane.cursor_yx(&y, &x); + + for (self.cursels_tabstops.items) |tabstop| for (tabstop) |cursel| { + const sel: Selection = cursel.selection orelse .{ + .begin = cursel.cursor, + .end = .{ + .row = cursel.cursor.row, + .col = cursel.cursor.col + 1, + }, + }; + if (self.is_point_in_selection(sel, y, x)) + return self.render_tabstop_cell(theme, cell); + }; + } + fn render_diagnostics(self: *Self, theme: *const Widget.Theme, hl_row: ?usize, cell_map: CellMap) !void { for (self.diagnostics.items) |*diag| self.render_diagnostic(diag, theme, hl_row, cell_map); } @@ -1374,6 +1394,10 @@ pub const Editor = struct { cell.set_style_bg(if (match.style) |style| style else theme.editor_match); } + inline fn render_tabstop_cell(_: *const Self, theme: *const Widget.Theme, cell: *Cell) void { + cell.set_style_bg(theme.editor_match); + } + inline fn render_line_highlight_cell(_: *const Self, theme: *const Widget.Theme, cell: *Cell) void { cell.set_style_bg(theme.editor_line_highlight); } From a897e6bf87975ea86f77788e77dbcf66a9d9dcb0 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 20:05:06 +0100 Subject: [PATCH 12/15] fix: support for $0 in snippets --- src/snippet.zig | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/snippet.zig b/src/snippet.zig index b3c4a68..f8cd437 100644 --- a/src/snippet.zig +++ b/src/snippet.zig @@ -136,8 +136,16 @@ pub fn parse(allocator: std.mem.Allocator, snippet: []const u8) Error!Snippet { for (tabstops.items) |item| if (item.id == n) { (try tabstop.addOne(allocator)).* = item.range; }; - (try result.addOne(allocator)).* = try tabstop.toOwnedSlice(allocator); + if (tabstop.items.len > 0) + (try result.addOne(allocator)).* = try tabstop.toOwnedSlice(allocator); } + var tabstop: std.ArrayList(Range) = .empty; + errdefer tabstop.deinit(allocator); + for (tabstops.items) |item| if (item.id == 0) { + (try tabstop.addOne(allocator)).* = item.range; + }; + if (tabstop.items.len > 0) + (try result.addOne(allocator)).* = try tabstop.toOwnedSlice(allocator); return .{ .text = try text.toOwnedSlice(), .tabstops = try result.toOwnedSlice(allocator), From 94109da73e32106f5be7f7c6c519abf879a94f95 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 20:05:34 +0100 Subject: [PATCH 13/15] feat: add editor.pop_tabstop --- src/tui/editor.zig | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index bc7f227..179df0b 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -439,6 +439,29 @@ pub const Editor = struct { self.cursels_tabstops.clearRetainingCapacity(); } + fn pop_tabstop(self: *Self) bool { + if (self.cursels_tabstops.items.len == 0) return false; + + const tabstops = self.cursels_tabstops.toOwnedSlice(self.allocator) catch return false; + defer { + self.allocator.free(tabstops[0]); + self.allocator.free(tabstops); + } + + self.cancel_all_matches(); + self.cancel_all_selections(); + self.cursels.clearRetainingCapacity(); + + for (tabstops[0]) |cursel| { + (self.cursels.addOne(self.allocator) catch return false).* = cursel; + if (builtin.mode == .Debug) + self.logger.print("pop tabstop 1 of {}", .{tabstops.len}); + } + for (tabstops[1..]) |tabstop| (self.cursels_tabstops.addOne(self.allocator) catch return false).* = tabstop; + + return true; + } + pub fn write_state(self: *const Self, writer: *std.Io.Writer) !void { try cbor.writeArrayHeader(writer, 10); try cbor.writeValue(writer, self.file_path orelse ""); @@ -3966,11 +3989,12 @@ pub const Editor = struct { } pub fn indent(self: *Self, ctx: Context) Result { + if (self.pop_tabstop()) return; const b = try self.buf_for_update(); const root = try self.with_cursels_mut_repeat(b.root, indent_cursel, b.allocator, ctx); try self.update_buf(root); } - pub const indent_meta: Meta = .{ .description = "Indent current line", .arguments = &.{.integer} }; + pub const indent_meta: Meta = .{ .description = "Indent current line (or pop tabstop)", .arguments = &.{.integer} }; fn unindent_cursor(self: *Self, root: Buffer.Root, cursor: *Cursor, cursor_protect: ?*Cursor, allocator: Allocator) error{Stop}!Buffer.Root { var newroot = root; @@ -4658,11 +4682,12 @@ pub const Editor = struct { const root_ = try self.buf_root(); const primary = self.get_primary(); + const cursor = if (primary.selection) |sel| sel.begin else primary.cursor; const eol_mode = try self.buf_eol_mode(); var cursor_pos: usize = 0; _ = try root_.get_range(.{ .begin = .{ .row = 0, .col = 0 }, - .end = primary.cursor, + .end = cursor, }, null, &cursor_pos, null, self.metrics); try self.insert_cursels(value.text); @@ -4688,12 +4713,11 @@ pub const Editor = struct { } else .{ .cursor = ts_begin, }; - self.logger.print("placeholder: {}, cursor_pos:{d}", .{ placeholder, cursor_pos }); - self.logger.print("tabstop: {}", .{p.*}); if (p.selection) |sel| self.add_match_from_selection(sel); } (try self.cursels_tabstops.addOne(self.allocator)).* = try cursels.toOwnedSlice(self.allocator); } + _ = self.pop_tabstop(); } pub fn insert_cursels(self: *Self, chars: []const u8) Result { From b472300b3dbc8851872e9d6b46dd7bf09ce0a4a2 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 20:16:16 +0100 Subject: [PATCH 14/15] refactor: remove render_tabstops --- src/tui/editor.zig | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 179df0b..2b9e637 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -1130,7 +1130,6 @@ pub const Editor = struct { if (ctx.hl_row) |hl_row| if (hl_row == ctx.buf_row) self_.render_line_highlight_cell(ctx.theme, c_); self_.render_matches(&ctx.match_idx, ctx.theme, c_); - self_.render_tabstops(ctx.theme, c_); self_.render_selections(ctx.theme, c_); _ = n.putc(c_) catch {}; ctx.cell_map.set_yx(ctx.y, ctx.x, .{ .cell_type = cell_map_val }); @@ -1148,7 +1147,6 @@ pub const Editor = struct { if (ctx.hl_row) |hl_row| if (hl_row == ctx.buf_row) self_.render_line_highlight_cell(ctx.theme, &c); self_.render_matches(&ctx.match_idx, ctx.theme, &c); - self_.render_tabstops(ctx.theme, &c); self_.render_selections(ctx.theme, &c); _ = n.putc(&c) catch {}; var term_cell = render_terminator(n, ctx.theme); @@ -1342,24 +1340,6 @@ pub const Editor = struct { }; } - fn render_tabstops(self: *const Self, theme: *const Widget.Theme, cell: *Cell) void { - var y: c_uint = undefined; - var x: c_uint = undefined; - self.plane.cursor_yx(&y, &x); - - for (self.cursels_tabstops.items) |tabstop| for (tabstop) |cursel| { - const sel: Selection = cursel.selection orelse .{ - .begin = cursel.cursor, - .end = .{ - .row = cursel.cursor.row, - .col = cursel.cursor.col + 1, - }, - }; - if (self.is_point_in_selection(sel, y, x)) - return self.render_tabstop_cell(theme, cell); - }; - } - fn render_diagnostics(self: *Self, theme: *const Widget.Theme, hl_row: ?usize, cell_map: CellMap) !void { for (self.diagnostics.items) |*diag| self.render_diagnostic(diag, theme, hl_row, cell_map); } @@ -1417,10 +1397,6 @@ pub const Editor = struct { cell.set_style_bg(if (match.style) |style| style else theme.editor_match); } - inline fn render_tabstop_cell(_: *const Self, theme: *const Widget.Theme, cell: *Cell) void { - cell.set_style_bg(theme.editor_match); - } - inline fn render_line_highlight_cell(_: *const Self, theme: *const Widget.Theme, cell: *Cell) void { cell.set_style_bg(theme.editor_line_highlight); } From 4a44838b8842f11cf5810205df7ec16519dcc419 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 20:16:38 +0100 Subject: [PATCH 15/15] refactor: nudge tabstops --- src/tui/editor.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 2b9e637..5a2a3be 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -2198,6 +2198,8 @@ pub const Editor = struct { cursel.nudge_insert(nudge); for (self.matches.items) |*match_| if (match_.*) |*match| match.nudge_insert(nudge); + for (self.cursels_tabstops.items) |tabstop| for (tabstop) |*cursel| + cursel.nudge_insert(nudge); } fn nudge_delete(self: *Self, nudge: Selection, exclude: *const CurSel, _: usize) void { @@ -2210,6 +2212,11 @@ pub const Editor = struct { if (!match.nudge_delete(nudge)) { self.matches.items[i] = null; }; + for (self.cursels_tabstops.items) |tabstop| for (tabstop) |*cursel| + if (!cursel.nudge_delete(nudge)) { + self.cancel_all_tabstops(); + break; + }; } pub fn delete_selection(self: *Self, root: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root {