From 151f108b80aff39d3179a7d59fc85b9f14f86f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20T=C3=A1mara?= Date: Mon, 27 Oct 2025 16:18:33 -0500 Subject: [PATCH 1/4] feat: [hx] add group function to move cursor beyond eol In Helix, F, T, t, f bring the cursor to the character being searched for, if not found, the cursor is not moved at all. --- src/tui/mode/helix.zig | 86 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 4cf8bce..9faf5d5 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -418,6 +418,10 @@ const cmds_ = struct { pub const paste_clipboard_before_meta: Meta = .{ .description = "Paste from clipboard before selection" }; }; +fn move_cursor_find_egc_beyond_eol(root: Buffer.Root, cursor: *Cursor, ctx: command.Context, metrics: Buffer.Metrics, move: find_char_function) error{Stop}!void { + move(root, cursor, metrics, ctx); +} + fn move_cursor_word_left_helix(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { try Editor.move_cursor_left(root, cursor, metrics); @@ -449,6 +453,83 @@ fn move_cursor_word_right_end_helix(root: Buffer.Root, cursor: *Cursor, metrics: try cursor.move_right(root, metrics); } +fn move_cursor_to_char_left_beyond_eol(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics, ctx: command.Context) error{Stop}!void { + var egc: []const u8 = undefined; + if (!(ctx.args.match(.{tp.extract(&egc)}) catch return error.Stop)) + return error.Stop; + var test_cursor = cursor.*; + try test_cursor.move_left(root, metrics); + while (true) { + const curr_egc, _, _ = root.egc_at(test_cursor.row, test_cursor.col, metrics) catch return error.Stop; + if (std.mem.eql(u8, curr_egc, egc)) { + cursor.row = test_cursor.row; + cursor.col = test_cursor.col; + cursor.target = cursor.col; + return; + } + test_cursor.move_left(root, metrics) catch return error.Stop; + } +} + +fn move_cursor_to_char_right_beyond_eol(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics, ctx: command.Context) error{Stop}!void { + var egc: []const u8 = undefined; + if (!(ctx.args.match(.{tp.extract(&egc)}) catch return error.Stop)) + return error.Stop; + var test_cursor = cursor.*; + while (true) { + const curr_egc, _, _ = root.egc_at(test_cursor.row, test_cursor.col, metrics) catch return error.Stop; + if (std.mem.eql(u8, curr_egc, egc)) { + cursor.row = test_cursor.row; + cursor.col = test_cursor.col; + cursor.target = cursor.col; + return; + } + test_cursor.move_right(root, metrics) catch return error.Stop; + } +} + +fn move_cursor_till_char_left_beyond_eol(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics, ctx: command.Context) error{Stop}!void { + var egc: []const u8 = undefined; + if (!(ctx.args.match(.{tp.extract(&egc)}) catch return error.Stop)) + return error.Stop; + var test_cursor = cursor; + try test_cursor.move_left(root, metrics); + var prev = test_cursor.*; + try prev.move_left(root, metrics); + while (true) { + const prev_egc, _, _ = root.egc_at(prev.row, prev.col, metrics) catch return error.Stop; + if (std.mem.eql(u8, prev_egc, egc)) { + cursor.row = test_cursor.row; + cursor.col = test_cursor.col; + cursor.target = cursor.col; + return; + } + test_cursor.move_left(root, metrics) catch return error.Stop; + prev.move_left(root, metrics) catch return error.Stop; + } +} + +fn move_cursor_till_char_right_beyond_eol(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics, ctx: command.Context) error{Stop}!void { + var egc: []const u8 = undefined; + if (!(ctx.args.match(.{tp.extract(&egc)}) catch return error.Stop)) + return error.Stop; + var test_cursor = cursor; + try test_cursor.move_right(root, metrics); + var next = test_cursor.*; + try next.move_right(root, metrics); + while (true) { + const next_egc, _, _ = root.egc_at(next.row, next.col, metrics) catch return error.Stop; + if (std.mem.eql(u8, next_egc, egc)) { + cursor.row = test_cursor.row; + cursor.col = test_cursor.col; + cursor.target = cursor.col; + return; + } + test_cursor.move_right(root, metrics) catch return error.Stop; + next.move_right(root, metrics) catch return error.Stop; + } +} + fn insert_before(editor: *Editor, root: Buffer.Root, cursel: *CurSel, text: []const u8, allocator: Allocator) !Buffer.Root { var root_: Buffer.Root = root; const cursor: *Cursor = &cursel.cursor; @@ -607,6 +688,7 @@ fn move_cursor_long_word_right_end(root: Buffer.Root, cursor: *Cursor, metrics: } const pasting_function = @TypeOf(insert_before); +const find_char_function = @TypeOf(move_cursor_to_char_left_beyond_eol); fn paste_helix(ctx: command.Context, do_paste: pasting_function) command.Result { const mv = tui.mainview() orelse return; @@ -670,6 +752,10 @@ pub const test_internal = struct { pub const move_cursor_long_word_right_end = private.move_cursor_long_word_right_end; pub const move_cursor_word_left_helix = private.move_cursor_word_left_helix; pub const move_cursor_word_right_end_helix = private.move_cursor_word_right_end_helix; + pub const move_cursor_to_char_left_beyond_eol = private.move_cursor_to_char_left_beyond_eol; + pub const move_cursor_to_char_right_beyond_eol = private.move_cursor_to_char_right_beyond_eol; + pub const move_cursor_till_char_left_beyond_eol = private.move_cursor_till_char_left_beyond_eol; + pub const move_cursor_till_char_right_beyond_eol = private.move_cursor_till_char_right_beyond_eol; pub const insert_before = private.insert_before; pub const insert_replace_selection = private.insert_replace_selection; pub const insert_after = private.insert_after; From 65665fb28b85cf00b165ed4fb07e59b6535bbb11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20T=C3=A1mara?= Date: Mon, 27 Oct 2025 16:54:34 -0500 Subject: [PATCH 2/4] feat: [hx] in Normal mode select to char right f j in normal mode selects to the char j in the buffer if it exists, else the cursor stays in place --- src/keybind/builtin/helix.json | 2 +- src/tui/editor.zig | 15 +++++++++++++++ src/tui/mode/helix.zig | 29 +++++++++++++++++++---------- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/keybind/builtin/helix.json b/src/keybind/builtin/helix.json index 6282250..103f577 100644 --- a/src/keybind/builtin/helix.json +++ b/src/keybind/builtin/helix.json @@ -117,7 +117,7 @@ ["l", "move_right"], ["t", "find_till_char"], - ["f", "move_to_char", "move_to_char_right"], + ["f", "move_to_char", "select_to_char_right_helix"], ["home", "move_begin"], ["end", "move_end"], diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 01dec2a..398318c 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -2017,6 +2017,20 @@ pub const Editor = struct { return if (someone_stopped) error.Stop else root; } + fn with_cursel_const_once_arg(root: Buffer.Root, move: cursel_operator_mut_once_arg, cursel: *CurSel, ctx: Context, metrics: Buffer.Metrics) error{Stop}!void { + try move(root, cursel, ctx, metrics); + } + + pub fn with_cursels_const_once_arg(self: *Self, root: Buffer.Root, move: cursel_operator_mut_once_arg, ctx: Context) error{Stop}!void { + var someone_stopped = false; + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| + with_cursel_const_once_arg(root, move, cursel, ctx, self.metrics) catch { + someone_stopped = true; + }; + self.collapse_cursors(); + return if (someone_stopped) error.Stop else {}; + } + fn with_cursel_const(root: Buffer.Root, op: cursel_operator_const, cursel: *CurSel) error{Stop}!void { return op(root, cursel); } @@ -2080,6 +2094,7 @@ pub const Editor = struct { const cursor_predicate = *const fn (root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) bool; const cursor_operator_const = *const fn (root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void; const cursor_operator_const_arg = *const fn (root: Buffer.Root, cursor: *Cursor, ctx: Context, metrics: Buffer.Metrics) error{Stop}!void; + const cursel_operator_mut_once_arg = *const fn (root: Buffer.Root, cursel: *CurSel, ctx: Context, metrics: Buffer.Metrics) error{Stop}!void; const cursor_view_operator_const = *const fn (root: Buffer.Root, cursor: *Cursor, view: *const View, metrics: Buffer.Metrics) error{Stop}!void; const cursel_operator_const = *const fn (root: Buffer.Root, cursel: *CurSel) error{Stop}!void; const cursor_operator = *const fn (root: Buffer.Root, cursor: *Cursor, allocator: Allocator) error{Stop}!Buffer.Root; diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 9faf5d5..db58645 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -375,18 +375,11 @@ const cmds_ = struct { pub fn select_to_char_right_helix(_: *void, ctx: Ctx) Result { const mv = tui.mainview() orelse return; const ed = mv.get_active_editor() orelse return; - const root = try ed.buf_root(); - - for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - const sel = try cursel.enable_selection(root, ed.metrics); - try Editor.move_cursor_to_char_right(root, &sel.end, ctx, ed.metrics); - try Editor.move_cursor_right(root, &sel.end, ed.metrics); - cursel.cursor = sel.end; - cursel.check_selection(root, ed.metrics); - }; + const root = ed.buf_root() catch return; + try ed.with_cursels_const_once_arg(root, &select_cursel_to_char_right_helix, ctx); ed.clamp(); } - pub const select_to_char_right_helix_meta: Meta = .{ .description = "Move to char right" }; + pub const select_to_char_right_helix_meta: Meta = .{ .description = "Select to char right" }; pub fn copy_helix(_: *void, _: Ctx) Result { const mv = tui.mainview() orelse return; @@ -418,6 +411,22 @@ const cmds_ = struct { pub const paste_clipboard_before_meta: Meta = .{ .description = "Paste from clipboard before selection" }; }; +fn select_cursel_to_char_right_helix(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metrics: Buffer.Metrics) error{Stop}!void { + var moving_cursor: Cursor = cursel.*.cursor; + const begin = cursel.*.cursor; + move_cursor_to_char_right_beyond_eol(root, &moving_cursor, metrics, ctx) catch return; + + //Character found, selecting + Editor.move_cursor_right(root, &moving_cursor, metrics) catch { + // We might be at end of file + }; + moving_cursor.target = moving_cursor.col; + const sel = try cursel.enable_selection(root, metrics); + sel.begin = begin; + sel.end = moving_cursor; + cursel.cursor = moving_cursor; +} + fn move_cursor_find_egc_beyond_eol(root: Buffer.Root, cursor: *Cursor, ctx: command.Context, metrics: Buffer.Metrics, move: find_char_function) error{Stop}!void { move(root, cursor, metrics, ctx); } From f5efe8e94f18bc3fe61d251580b8612fc50538ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20T=C3=A1mara?= Date: Mon, 27 Oct 2025 17:49:59 -0500 Subject: [PATCH 3/4] feat: [hx] Add F, T, t, f select and extension movements --- src/keybind/builtin/helix.json | 19 ++-- src/tui/editor.zig | 2 +- src/tui/mode/helix.zig | 153 +++++++++++++++++++++++++++++++-- 3 files changed, 157 insertions(+), 17 deletions(-) diff --git a/src/keybind/builtin/helix.json b/src/keybind/builtin/helix.json index 103f577..af3314e 100644 --- a/src/keybind/builtin/helix.json +++ b/src/keybind/builtin/helix.json @@ -66,11 +66,13 @@ ["alt+|", "shell_pipe_to"], ["alt+!", "shell_append_output"], - ["T", "till_prev_char"], - ["F", "move_to_char", "move_to_char_left"], + ["F", "move_to_char", "select_to_char_left_helix"], + ["T", "move_to_char", "select_till_char_left_helix"], ["W", "move_next_long_word_start"], ["B", "move_prev_long_word_start"], ["E", "move_next_long_word_end"], + ["t", "move_to_char", "select_till_char_right_helix"], + ["f", "move_to_char", "select_to_char_right_helix"], ["I", ["enter_mode", "insert"], ["smart_move_begin"]], ["A", ["enter_mode", "insert"], ["move_end"]], @@ -116,9 +118,6 @@ ["k", "move_up"], ["l", "move_right"], - ["t", "find_till_char"], - ["f", "move_to_char", "select_to_char_right_helix"], - ["home", "move_begin"], ["end", "move_end"], ["kp_home", "move_begin"], @@ -354,12 +353,13 @@ ["~", "switch_case"], - ["T", "extend_till_prev_char"], - ["F", "move_to_char", "select_to_char_left_vim"], - + ["F", "move_to_char", "extend_to_char_left_helix"], + ["T", "move_to_char", "extend_till_char_left_helix"], ["W", "extend_next_long_word_start"], ["B", "extend_prev_long_word_start"], ["E", "extend_next_long_word_end"], + ["t", "move_to_char", "extend_till_char_right_helix"], + ["f", "move_to_char", "extend_to_char_right_helix"], ["G", "goto_line"], @@ -415,9 +415,6 @@ ["kp_right", "select_right"], ["%", "select_all"], - ["t", "extend_till_char"], - ["f", "move_to_char", "select_to_char_right_helix"], - ["`", "switch_to_lowercase"], ["home", "extend_to_line_start"], diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 398318c..ca0ed0f 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -2094,7 +2094,7 @@ pub const Editor = struct { const cursor_predicate = *const fn (root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) bool; const cursor_operator_const = *const fn (root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void; const cursor_operator_const_arg = *const fn (root: Buffer.Root, cursor: *Cursor, ctx: Context, metrics: Buffer.Metrics) error{Stop}!void; - const cursel_operator_mut_once_arg = *const fn (root: Buffer.Root, cursel: *CurSel, ctx: Context, metrics: Buffer.Metrics) error{Stop}!void; + pub const cursel_operator_mut_once_arg = *const fn (root: Buffer.Root, cursel: *CurSel, ctx: Context, metrics: Buffer.Metrics) error{Stop}!void; const cursor_view_operator_const = *const fn (root: Buffer.Root, cursor: *Cursor, view: *const View, metrics: Buffer.Metrics) error{Stop}!void; const cursel_operator_const = *const fn (root: Buffer.Root, cursel: *CurSel) error{Stop}!void; const cursor_operator = *const fn (root: Buffer.Root, cursor: *Cursor, allocator: Allocator) error{Stop}!Buffer.Root; diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index db58645..5187425 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -372,15 +372,46 @@ const cmds_ = struct { } pub const select_left_helix_meta: Meta = .{ .description = "Select left", .arguments = &.{.integer} }; + pub fn select_to_char_left_helix(_: *void, ctx: Ctx) Result { + try to_char_helix(ctx, &select_cursel_to_char_left_helix); + } + pub const select_to_char_left_helix_meta: Meta = .{ .description = "Select to char left" }; + + pub fn select_till_char_left_helix(_: *void, ctx: Ctx) Result { + try to_char_helix(ctx, &select_cursel_till_char_left_helix); + } + pub const select_till_char_left_helix_meta: Meta = .{ .description = "Select until char left" }; + + pub fn extend_to_char_left_helix(_: *void, ctx: Ctx) Result { + try to_char_helix(ctx, &extend_cursel_to_char_left_helix); + } + pub const extend_to_char_left_helix_meta: Meta = .{ .description = "Extend Selection to char left" }; + + pub fn extend_till_char_left_helix(_: *void, ctx: Ctx) Result { + try to_char_helix(ctx, &extend_cursel_till_char_left_helix); + } + pub const extend_till_char_left_helix_meta: Meta = .{ .description = "Extend Selection until char left" }; + + pub fn select_till_char_right_helix(_: *void, ctx: Ctx) Result { + try to_char_helix(ctx, &select_cursel_till_char_right_helix); + } + pub const select_till_char_right_helix_meta: Meta = .{ .description = "Select until char right" }; + pub fn select_to_char_right_helix(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - try ed.with_cursels_const_once_arg(root, &select_cursel_to_char_right_helix, ctx); - ed.clamp(); + try to_char_helix(ctx, &select_cursel_to_char_right_helix); } pub const select_to_char_right_helix_meta: Meta = .{ .description = "Select to char right" }; + pub fn extend_till_char_right_helix(_: *void, ctx: Ctx) Result { + try to_char_helix(ctx, &extend_cursel_till_char_right_helix); + } + pub const extend_till_char_right_helix_meta: Meta = .{ .description = "Extend Selection until char right" }; + + pub fn extend_to_char_right_helix(_: *void, ctx: Ctx) Result { + try to_char_helix(ctx, &extend_cursel_to_char_right_helix); + } + pub const extend_to_char_right_helix_meta: Meta = .{ .description = "Extend Selection to char right" }; + pub fn copy_helix(_: *void, _: Ctx) Result { const mv = tui.mainview() orelse return; const ed = mv.get_active_editor() orelse return; @@ -411,6 +442,101 @@ const cmds_ = struct { pub const paste_clipboard_before_meta: Meta = .{ .description = "Paste from clipboard before selection" }; }; +fn to_char_helix(ctx: command.Context, move: Editor.cursel_operator_mut_once_arg) command.Result { + const mv = tui.mainview() orelse return; + const ed = mv.get_active_editor() orelse return; + const root = ed.buf_root() catch return; + try ed.with_cursels_const_once_arg(root, move, ctx); + ed.clamp(); +} + +fn select_cursel_to_char_left_helix(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metrics: Buffer.Metrics) error{Stop}!void { + var moving_cursor: Cursor = cursel.*.cursor; + var begin = cursel.*.cursor; + move_cursor_to_char_left_beyond_eol(root, &moving_cursor, metrics, ctx) catch return; + + // Character found, selecting + Editor.move_cursor_right(root, &begin, metrics) catch { + //At end of file, it's ok + }; + moving_cursor.target = moving_cursor.col; + const sel = try cursel.enable_selection(root, metrics); + sel.begin = begin; + sel.end = moving_cursor; + cursel.cursor = moving_cursor; +} + +fn extend_cursel_to_char_left_helix(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metrics: Buffer.Metrics) error{Stop}!void { + var moving_cursor: Cursor = cursel.*.cursor; + const begin = if (cursel.*.selection) |sel| sel.end else cursel.*.cursor; + move_cursor_to_char_left_beyond_eol(root, &moving_cursor, metrics, ctx) catch return; + + //Character found, selecting + moving_cursor.target = moving_cursor.col; + const sel = try cursel.enable_selection(root, metrics); + if (sel.empty()) + sel.begin = begin; + sel.end = moving_cursor; + cursel.cursor = moving_cursor; +} + +fn select_cursel_till_char_left_helix(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metrics: Buffer.Metrics) error{Stop}!void { + var moving_cursor: Cursor = cursel.*.cursor; + var begin = cursel.*.cursor; + move_cursor_till_char_left_beyond_eol(root, &moving_cursor, metrics, ctx) catch return; + Editor.move_cursor_right(root, &begin, metrics) catch { + //At end of file, it's ok + }; + + // Character found, selecting + moving_cursor.target = moving_cursor.col; + const sel = try cursel.enable_selection(root, metrics); + sel.begin = begin; + sel.end = moving_cursor; + cursel.cursor = moving_cursor; +} + +fn extend_cursel_till_char_left_helix(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metrics: Buffer.Metrics) error{Stop}!void { + var moving_cursor: Cursor = cursel.*.cursor; + const begin = if (cursel.*.selection) |sel| sel.end else cursel.*.cursor; + move_cursor_till_char_left_beyond_eol(root, &moving_cursor, metrics, ctx) catch return; + + //Character found, selecting + moving_cursor.target = moving_cursor.col; + const sel = try cursel.enable_selection(root, metrics); + if (sel.empty()) + sel.begin = begin; + sel.end = moving_cursor; + cursel.cursor = moving_cursor; +} + +fn select_cursel_till_char_right_helix(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metrics: Buffer.Metrics) error{Stop}!void { + var moving_cursor: Cursor = cursel.*.cursor; + const begin = cursel.*.cursor; + move_cursor_to_char_right_beyond_eol(root, &moving_cursor, metrics, ctx) catch return; + + //Character found, selecting + moving_cursor.target = moving_cursor.col; + const sel = try cursel.enable_selection(root, metrics); + sel.begin = begin; + sel.end = moving_cursor; + cursel.cursor = moving_cursor; +} + +fn extend_cursel_till_char_right_helix(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metrics: Buffer.Metrics) error{Stop}!void { + var moving_cursor: Cursor = cursel.*.cursor; + const begin = cursel.*.cursor; + move_cursor_to_char_right_beyond_eol(root, &moving_cursor, metrics, ctx) catch return; + + //Character found, selecting + moving_cursor.target = moving_cursor.col; + const sel = try cursel.enable_selection(root, metrics); + if (sel.empty()) + sel.begin = begin; + sel.end = moving_cursor; + cursel.cursor = moving_cursor; +} + fn select_cursel_to_char_right_helix(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metrics: Buffer.Metrics) error{Stop}!void { var moving_cursor: Cursor = cursel.*.cursor; const begin = cursel.*.cursor; @@ -427,6 +553,23 @@ fn select_cursel_to_char_right_helix(root: Buffer.Root, cursel: *CurSel, ctx: co cursel.cursor = moving_cursor; } +fn extend_cursel_to_char_right_helix(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metrics: Buffer.Metrics) error{Stop}!void { + var moving_cursor: Cursor = cursel.*.cursor; + const begin = cursel.*.cursor; + move_cursor_to_char_right_beyond_eol(root, &moving_cursor, metrics, ctx) catch return; + + //Character found, selecting + Editor.move_cursor_right(root, &moving_cursor, metrics) catch { + // We might be at end of file + }; + moving_cursor.target = moving_cursor.col; + const sel = try cursel.enable_selection(root, metrics); + if (sel.empty()) + sel.begin = begin; + sel.end = moving_cursor; + cursel.cursor = moving_cursor; +} + fn move_cursor_find_egc_beyond_eol(root: Buffer.Root, cursor: *Cursor, ctx: command.Context, metrics: Buffer.Metrics, move: find_char_function) error{Stop}!void { move(root, cursor, metrics, ctx); } From e76c47e1a6c9c30846449c967bc1f073783d7cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20T=C3=A1mara?= Date: Tue, 28 Oct 2025 12:30:33 -0500 Subject: [PATCH 4/4] feat: bar status shows extend or select when on helix mode --- src/tui/mode/helix.zig | 4 +- src/tui/mode/mini/move_to_char.zig | 18 ++++++++- test/tests_helix.zig | 60 ++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 5187425..dcd3289 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -484,11 +484,11 @@ fn select_cursel_till_char_left_helix(root: Buffer.Root, cursel: *CurSel, ctx: c var moving_cursor: Cursor = cursel.*.cursor; var begin = cursel.*.cursor; move_cursor_till_char_left_beyond_eol(root, &moving_cursor, metrics, ctx) catch return; + + // Character found, selecting Editor.move_cursor_right(root, &begin, metrics) catch { //At end of file, it's ok }; - - // Character found, selecting moving_cursor.target = moving_cursor.col; const sel = try cursel.enable_selection(root, metrics); sel.begin = begin; diff --git a/src/tui/mode/mini/move_to_char.zig b/src/tui/mode/mini/move_to_char.zig index d32f00c..1d76631 100644 --- a/src/tui/mode/mini/move_to_char.zig +++ b/src/tui/mode/mini/move_to_char.zig @@ -29,6 +29,7 @@ const Direction = enum { const Operation = enum { move, select, + extend, }; pub fn create(allocator: Allocator, ctx: command.Context) !struct { tui.Mode, tui.MiniMode } { @@ -36,7 +37,18 @@ pub fn create(allocator: Allocator, ctx: command.Context) !struct { tui.Mode, tu _ = ctx.args.match(.{tp.extract(&operation_command)}) catch return error.InvalidMoveToCharArgument; const direction: Direction = if (std.mem.indexOf(u8, operation_command, "_left")) |_| .left else .right; - const operation: Operation = if (tui.get_active_editor()) |editor| if (editor.get_primary().selection) |_| .select else .move else .move; + var operation: Operation = undefined; + if (std.mem.indexOf(u8, operation_command, "extend_")) |_| { + operation = .extend; + } else if (std.mem.indexOf(u8, operation_command, "select_")) |_| { + operation = .select; + } else if (tui.get_active_editor()) |editor| if (editor.get_primary().selection) |_| { + operation = .select; + } else { + operation = .move; + } else { + operation = .move; + } const self = try allocator.create(Self); errdefer allocator.destroy(self); @@ -70,6 +82,10 @@ fn name(self: *Self) []const u8 { .left => "󰒅 ↶ select", .right => "󰒅 ↷ select", }, + .extend => switch (self.direction) { + .left => "󰒅 ↶ extend", + .right => "󰒅 ↷ extend", + }, }; } diff --git a/test/tests_helix.zig b/test/tests_helix.zig index f94e63a..a69501f 100644 --- a/test/tests_helix.zig +++ b/test/tests_helix.zig @@ -2,6 +2,7 @@ const std = @import("std"); const Buffer = @import("Buffer"); const Cursor = @import("Buffer").Cursor; const Result = @import("command").Result; +const fmt_command = @import("command").fmt; const helix = @import("tui").exports.mode.helix; const Editor = @import("tui").exports.editor.Editor; @@ -156,3 +157,62 @@ test "long_words_movement" { try apply_movements(move.moves, root, &the_cursor, metrics(), move.row, move.col); } } + +test "to_char_right_beyond_eol" { + const buffer = try Buffer.create(a); + defer buffer.deinit(); + buffer.update(try buffer.load_from_string(doc, &eol_mode, &sanitized)); + const root: Buffer.Root = buffer.root; + the_cursor.col = 0; + the_cursor.row = 0; + const expected_error = error.Stop; + + // Not found to begin of file + var result = helix.test_internal.move_cursor_to_char_left_beyond_eol(root, &the_cursor, metrics(), fmt_command(.{"a"})); + try std.testing.expectError(expected_error, result); + try std.testing.expectEqual(0, the_cursor.row); + try std.testing.expectEqual(0, the_cursor.col); + + // Move found in the next line + try helix.test_internal.move_cursor_to_char_right_beyond_eol(root, &the_cursor, metrics(), fmt_command(.{"T"})); + try std.testing.expectEqual(1, the_cursor.row); + try std.testing.expectEqual(11, the_cursor.col); + + // Move found in the previous line + try helix.test_internal.move_cursor_to_char_left_beyond_eol(root, &the_cursor, metrics(), fmt_command(.{"t"})); + try std.testing.expectEqual(0, the_cursor.row); + try std.testing.expectEqual(51, the_cursor.col); + + // Not found to end of buffer, cursor not moved + result = helix.test_internal.move_cursor_to_char_right_beyond_eol(root, &the_cursor, metrics(), fmt_command(.{"Z"})); + try std.testing.expectError(expected_error, result); + try std.testing.expectEqual(0, the_cursor.row); + try std.testing.expectEqual(51, the_cursor.col); + + // Not found to begin of buffer + result = helix.test_internal.move_cursor_to_char_left_beyond_eol(root, &the_cursor, metrics(), fmt_command(.{"Z"})); + try std.testing.expectError(expected_error, result); + try std.testing.expectEqual(0, the_cursor.row); + try std.testing.expectEqual(51, the_cursor.col); + + // till char difference + // Move found in the next line + try helix.test_internal.move_cursor_till_char_right_beyond_eol(root, &the_cursor, metrics(), fmt_command(.{"T"})); + try std.testing.expectEqual(1, the_cursor.row); + try std.testing.expectEqual(10, the_cursor.col); + + // Move found in the previous line + try helix.test_internal.move_cursor_till_char_left_beyond_eol(root, &the_cursor, metrics(), fmt_command(.{"t"})); + try std.testing.expectEqual(0, the_cursor.row); + try std.testing.expectEqual(52, the_cursor.col); + + // Move found in the same line + try helix.test_internal.move_cursor_till_char_left_beyond_eol(root, &the_cursor, metrics(), fmt_command(.{"x"})); + try std.testing.expectEqual(0, the_cursor.row); + try std.testing.expectEqual(46, the_cursor.col); + + // Move found in the same line + try helix.test_internal.move_cursor_till_char_right_beyond_eol(root, &the_cursor, metrics(), fmt_command(.{"t"})); + try std.testing.expectEqual(0, the_cursor.row); + try std.testing.expectEqual(50, the_cursor.col); +}