fix: make helix move_prev_word_start an exact match to real helix

This commit is contained in:
CJ van den Berg 2025-11-27 16:44:16 +01:00
parent 1755ecb3dd
commit 7bf532bdfd
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
2 changed files with 72 additions and 2 deletions

View file

@ -2196,7 +2196,7 @@ pub const Editor = struct {
const cursor_operator_const_arg = *const fn (root: Buffer.Root, cursor: *Cursor, 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, metrics: Buffer.Metrics) error{Stop}!void;
pub const cursel_operator_const = *const fn (root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) error{Stop}!void;
const cursor_operator = *const fn (root: Buffer.Root, cursor: *Cursor, allocator: Allocator) error{Stop}!Buffer.Root;
const cursel_operator = *const fn (root: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root;
const cursel_operator_mut = *const fn (self: *Self, root: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root;

View file

@ -13,6 +13,8 @@ const Buffer = @import("Buffer");
const Cursor = Buffer.Cursor;
const Selection = Buffer.Selection;
const char_class = Editor.char_class;
const Direction = enum { backwards, forwards };
var commands: Commands = undefined;
@ -263,12 +265,80 @@ const cmds_ = struct {
}
pub const extend_next_long_word_start_meta: Meta = .{ .description = "Extend next long word start", .arguments = &.{.integer} };
fn is_eol(c: []const u8) bool {
return char_class(c) == .eol;
}
fn is_whitespace(c: []const u8) bool {
return char_class(c) == .whitespace;
}
fn is_word_boundary(root: Buffer.Root, cursor: Cursor, metrics: Buffer.Metrics, comptime direction: enum { left, right }) bool {
const nxt = char_class(switch (direction) {
.left => cursor.char_left(root, metrics),
.right => cursor.char_right(root, metrics),
});
const cur = char_class(cursor.char_at(root, metrics));
if (cur == .eol) return false;
return switch (nxt) {
.end, .eol => true,
.whitespace => cur != .whitespace,
else => nxt != cur,
};
}
fn move_cursor_prev_word_start(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) error{Stop}!void {
var cursor = cursel.cursor;
if (is_word_boundary(root, cursor, metrics, .left))
cursor.move_left(root, metrics) catch {};
var sel = Selection.from_cursor_inclusive(&cursor, root, metrics);
defer {
sel.begin = cursor;
cursel.cursor = cursor;
cursel.selection = sel;
}
// Consume whitespace
while (cursor.test_at(root, is_whitespace, metrics)) {
cursor.move_left(root, metrics) catch return;
// Stop at beginning of line
if (cursor.test_left(root, is_eol, metrics)) return;
}
// Consume word/non-word chars
while (!is_word_boundary(root, cursor, metrics, .left)) {
cursor.move_left(root, metrics) catch return;
// Stop at beginning of line
if (cursor.test_left(root, is_eol, metrics)) return;
}
}
fn move_cursor_prev_word_start_extend(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) error{Stop}!void {
var selection = cursel.selection;
// check if we already had a selection and extend it
defer if (selection) |*pre_sel| {
pre_sel.normalize();
if (cursel.selection) |*sel| sel.end = pre_sel.end;
};
try move_cursor_prev_word_start(root, cursel, metrics);
}
fn move_cursels_const_repeat(move: Editor.cursel_operator_const, ctx: Ctx) Result {
const mv = tui.mainview() orelse return;
const ed = mv.get_active_editor() orelse return;
const root = try ed.buf_root();
try ed.with_cursels_const_repeat(root, move, ctx);
ed.clamp();
}
pub fn move_prev_word_start(_: *void, ctx: Ctx) Result {
try move_to_word(ctx, move_cursor_word_left_helix, .backwards);
try move_cursels_const_repeat(move_cursor_prev_word_start, ctx);
}
pub const move_prev_word_start_meta: Meta = .{ .description = "Move previous word start", .arguments = &.{.integer} };
pub fn extend_prev_word_start(_: *void, ctx: Ctx) Result {
try move_cursels_const_repeat(move_cursor_prev_word_start_extend, ctx);
try extend_to_word(ctx, move_cursor_word_left_helix, .backwards);
}
pub const extend_prev_word_start_meta: Meta = .{ .description = "Extend previous word start", .arguments = &.{.integer} };