From 157e1ba7d3f517f9b81f4e69f4707afb0286c1a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20T=C3=A1mara?= Date: Wed, 18 Mar 2026 13:07:27 -0500 Subject: [PATCH 1/3] feat: [hx] surround add --- src/tui/mode/helix.zig | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 2721df22..155199b7 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -430,6 +430,17 @@ const cmds_ = struct { } pub const select_textobject_inner_meta: Meta = .{ .description = "select inside object helix" }; + pub fn surround_add(_: *void, ctx: Ctx) Result { + const mv = tui.mainview() orelse return; + const ed = mv.get_active_editor() orelse return; + var root = ed.buf_root() catch return; + root = try ed.with_cursels_mut_once_arg(root, surround_cursel_add, ed.allocator, ctx); + try ed.update_buf(root); + ed.clamp(); + ed.need_render(); + } + pub const surround_add_meta: Meta = .{ .description = "surround add" }; + pub fn select_textobject_around(_: *void, ctx: Ctx) Result { var action: []const u8 = ""; @@ -865,6 +876,48 @@ fn replace_cursel_with_character(ed: *Editor, root: Buffer.Root, cursel: *CurSel return insert_replace_selection(ed, root, cursel, replacement, allocator) catch return error.Stop; } +fn find_open_close_pair(bracket: []const u8) struct { left: []const u8, right: []const u8 } { + for (Buffer.unicode.open_close_pairs) |pair| { + if (std.mem.eql(u8, bracket, pair[0]) or std.mem.eql(u8, bracket, pair[1])) { + return .{ .left = pair[0], .right = pair[1] }; + } + } + return .{ .left = bracket, .right = bracket }; +} + +fn surround_cursel_add(ed: *Editor, root: Buffer.Root, cursel: *CurSel, allocator: Allocator, ctx: command.Context) error{Stop}!Buffer.Root { + var encloser: []const u8 = undefined; + if (!(ctx.args.match(.{tp.extract(&encloser)}) catch return error.Stop)) + return error.Stop; + + const enclose_pair = find_open_close_pair(encloser); + var root_: Buffer.Root = root; + cursel.check_selection(root, ed.metrics); + + const sel = cursel.enable_selection(root_, ed.metrics); + var begin = sel.begin; + var end = sel.end; + if (sel.is_reversed()) { + end = sel.begin; + begin = sel.end; + } + if (begin.row == end.row and end.col == begin.col) { + end.move_right(root_, ed.metrics) catch {}; + } + _, _, root_ = root_.insert_chars(end.row, end.col, enclose_pair.right, allocator, ed.metrics) catch return error.Stop; + _, _, root_ = root_.insert_chars(begin.row, begin.col, enclose_pair.left, allocator, ed.metrics) catch return error.Stop; + + try end.move_right(root_, ed.metrics); + try end.move_right(root_, ed.metrics); + cursel.selection = Selection{ .begin = begin, .end = end }; + ed.nudge_insert(.{ .begin = begin, .end = end }, cursel, encloser.len * 2); + + if (end.right_of(begin)) { + try cursel.*.cursor.move_right(root_, ed.metrics); + } + return root_; +} + fn move_noop(_: Buffer.Root, _: *Cursor, _: Buffer.Metrics) error{Stop}!void {} fn move_cursor_word_right_end_helix(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { From 34d99a17d501efc96034c8567828fd4a46a49aa0 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 13 Apr 2026 19:28:43 +0200 Subject: [PATCH 2/3] fix: use buf_for_update consistently in helix mode --- src/tui/mode/helix.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 155199b7..2f9736ec 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -433,8 +433,8 @@ const cmds_ = struct { pub fn surround_add(_: *void, ctx: Ctx) Result { const mv = tui.mainview() orelse return; const ed = mv.get_active_editor() orelse return; - var root = ed.buf_root() catch return; - root = try ed.with_cursels_mut_once_arg(root, surround_cursel_add, ed.allocator, ctx); + const b = try ed.buf_for_update(); + const root = try ed.with_cursels_mut_once_arg(b.root, surround_cursel_add, ed.allocator, ctx); try ed.update_buf(root); ed.clamp(); ed.need_render(); @@ -492,8 +492,8 @@ const cmds_ = struct { pub fn replace_with_character_helix(_: *void, ctx: Ctx) Result { const mv = tui.mainview() orelse return; const ed = mv.get_active_editor() orelse return; - var root = ed.buf_root() catch return; - root = try ed.with_cursels_mut_once_arg(root, replace_cursel_with_character, ed.allocator, ctx); + const b = try ed.buf_for_update(); + const root = try ed.with_cursels_mut_once_arg(b.root, replace_cursel_with_character, ed.allocator, ctx); try ed.update_buf(root); ed.clamp(); ed.need_render(); From 16377e37005624cd3186f57a7c78272300612886 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 13 Apr 2026 19:32:01 +0200 Subject: [PATCH 3/3] fix: surround add overshoots end cursor on multi-row selections --- src/tui/mode/helix.zig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 2f9736ec..0a5bd4e5 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -907,8 +907,10 @@ fn surround_cursel_add(ed: *Editor, root: Buffer.Root, cursel: *CurSel, allocato _, _, root_ = root_.insert_chars(end.row, end.col, enclose_pair.right, allocator, ed.metrics) catch return error.Stop; _, _, root_ = root_.insert_chars(begin.row, begin.col, enclose_pair.left, allocator, ed.metrics) catch return error.Stop; - try end.move_right(root_, ed.metrics); - try end.move_right(root_, ed.metrics); + if (begin.row == end.row) { + try end.move_right(root_, ed.metrics); // for left-bracket column shift on same row + } + try end.move_right(root_, ed.metrics); // skip past right bracket cursel.selection = Selection{ .begin = begin, .end = end }; ed.nudge_insert(.{ .begin = begin, .end = end }, cursel, encloser.len * 2);