From 7410435c4f632d6be3092f79f3f552d404cdc52b Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 2 Dec 2025 16:21:46 +0100 Subject: [PATCH 01/25] Reapply "Initial attempt to fix prev and next word movement" This reverts commit 6f1806cd9597950f69c40742a8baafa72c3e89b4. --- src/tui/mode/helix.zig | 60 +++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 4ecaca11..21b32587 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -13,6 +13,8 @@ const Buffer = @import("Buffer"); const Cursor = Buffer.Cursor; const Selection = Buffer.Selection; +const Direction = enum { backwards, forwards }; + var commands: Commands = undefined; pub fn init() !void { @@ -242,62 +244,62 @@ const cmds_ = struct { pub const extend_line_below_meta: Meta = .{ .arguments = &.{.integer}, .description = "Select current line, if already selected, extend to next line" }; pub fn move_next_word_start(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, Editor.move_cursor_word_right_vim); + try move_to_word(ctx, Editor.move_cursor_word_right_vim, .forwards); } pub const move_next_word_start_meta: Meta = .{ .description = "Move next word start", .arguments = &.{.integer} }; pub fn extend_next_word_start(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, Editor.move_cursor_word_right_vim); + try extend_to_word(ctx, Editor.move_cursor_word_right_vim, .forwards); } pub const extend_next_word_start_meta: Meta = .{ .description = "Extend next word start", .arguments = &.{.integer} }; pub fn move_next_long_word_start(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_long_word_right); + try move_to_word(ctx, move_cursor_long_word_right, .forwards); } pub const move_next_long_word_start_meta: Meta = .{ .description = "Move next long word start", .arguments = &.{.integer} }; pub fn extend_next_long_word_start(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, move_cursor_long_word_right); + try extend_to_word(ctx, move_cursor_long_word_right, .forwards); } pub const extend_next_long_word_start_meta: Meta = .{ .description = "Extend next long word start", .arguments = &.{.integer} }; pub fn move_prev_word_start(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_word_left_helix); + try move_to_word(ctx, move_cursor_word_left_helix, .backwards); } 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 extend_to_word(ctx, move_cursor_word_left_helix); + 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} }; pub fn move_prev_long_word_start(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_long_word_left); + try move_to_word(ctx, move_cursor_long_word_left, .backwards); } pub const move_prev_long_word_start_meta: Meta = .{ .description = "Move previous long word start", .arguments = &.{.integer} }; pub fn extend_prev_long_word_start(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, move_cursor_long_word_left); + try extend_to_word(ctx, move_cursor_long_word_left, .backwards); } pub const extend_prev_long_word_start_meta: Meta = .{ .description = "Extend previous word start", .arguments = &.{.integer} }; pub fn move_next_word_end(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_word_right_end_helix); + try move_to_word(ctx, move_cursor_word_right_end_helix, .forwards); } pub const move_next_word_end_meta: Meta = .{ .description = "Move next word end", .arguments = &.{.integer} }; pub fn extend_next_word_end(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, move_cursor_word_right_end_helix); + try extend_to_word(ctx, move_cursor_word_right_end_helix, .forwards); } pub const extend_next_word_end_meta: Meta = .{ .description = "Extend next word end", .arguments = &.{.integer} }; pub fn move_next_long_word_end(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_long_word_right_end); + try move_to_word(ctx, move_cursor_long_word_right_end, .forwards); } pub const move_next_long_word_end_meta: Meta = .{ .description = "Move next long word end", .arguments = &.{.integer} }; pub fn extend_next_long_word_end(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, move_cursor_long_word_right_end); + try extend_to_word(ctx, move_cursor_long_word_right_end, .forwards); } pub const extend_next_long_word_end_meta: Meta = .{ .description = "Extend next long word end", .arguments = &.{.integer} }; @@ -526,7 +528,7 @@ fn match_bracket(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metri } } -fn move_to_word(ctx: command.Context, move: Editor.cursor_operator_const) command.Result { +fn move_to_word(ctx: command.Context, move: Editor.cursor_operator_const, direction: Direction) command.Result { const mv = tui.mainview() orelse return; const ed = mv.get_active_editor() orelse return; const root = try ed.buf_root(); @@ -537,17 +539,44 @@ fn move_to_word(ctx: command.Context, move: Editor.cursor_operator_const) comman if (repeat > 1) ed.with_cursors_const_repeat(root, move, command.fmt(.{repeat - 1})) catch {}; for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - cursel.selection = null; + var sel = Selection.from_cursor(&cursel.cursor); + const cur = sel.begin.test_at(root, is_not_whitespace_or_eol, ed.metrics); + if (direction == .backwards) { + sel.begin.move_left(root, ed.metrics) catch continue; + const prev = sel.begin.test_at(root, Editor.is_not_word_char, ed.metrics); + sel.begin = sel.end; + if (!cur or cur != prev) + sel.begin.move_right(root, ed.metrics) catch continue; + } else { + sel.end.move_right(root, ed.metrics) catch continue; + const next = sel.end.test_at(root, Editor.is_not_word_char, ed.metrics); + if (!cur and cur != next) + sel.begin = sel.end; + } + cursel.cursor = sel.end; + cursel.selection = sel; }; ed.with_selections_const_repeat(root, move, command.fmt(.{1})) catch {}; ed.clamp(); } -fn extend_to_word(ctx: command.Context, move: Editor.cursor_operator_const) command.Result { +fn extend_to_word(ctx: command.Context, move: Editor.cursor_operator_const, direction: Direction) command.Result { const mv = tui.mainview() orelse return; const ed = mv.get_active_editor() orelse return; const root = try ed.buf_root(); + var repeat: usize = 1; + _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; + for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + if (cursel.selection == null) { + cursel.selection = Selection.from_cursor(cursel.cursor); + } + const sel = &cursel.selection.?; + const pivot = if (sel.is_reversed()) cursel.begin - 1 else cursel.begin; + var i: usize = repeat; + while (i > 0) : (i -= 1) {} + }; + ed.with_selections_const_repeat(root, move, ctx) catch {}; ed.clamp(); } @@ -830,7 +859,6 @@ 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 { try Editor.move_cursor_right(root, cursor, metrics); Editor.move_cursor_right_until(root, cursor, Editor.is_word_boundary_right_vim, 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 { From 77b2afdd0c9b938abcb206a8b78e75eca1e24766 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 2 Dec 2025 16:21:58 +0100 Subject: [PATCH 02/25] Reapply "Fixed selection extensions with new helper functions" This reverts commit 989557fb6d6a7dc1a474dd40429d353ab053dc6e. --- src/tui/mode/helix.zig | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 21b32587..4a429912 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -560,7 +560,7 @@ fn move_to_word(ctx: command.Context, move: Editor.cursor_operator_const, direct ed.clamp(); } -fn extend_to_word(ctx: command.Context, move: Editor.cursor_operator_const, direction: Direction) command.Result { +fn extend_to_word(ctx: command.Context, move: Editor.cursor_operator_const, _: Direction) command.Result { const mv = tui.mainview() orelse return; const ed = mv.get_active_editor() orelse return; const root = try ed.buf_root(); @@ -568,16 +568,16 @@ fn extend_to_word(ctx: command.Context, move: Editor.cursor_operator_const, dire var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - if (cursel.selection == null) { - cursel.selection = Selection.from_cursor(cursel.cursor); - } - const sel = &cursel.selection.?; - const pivot = if (sel.is_reversed()) cursel.begin - 1 else cursel.begin; + const sel = try cursel.enable_selection(root, ed.metrics); + const pivot: usize = if (sel.is_reversed()) sel.begin.col -| 1 else sel.begin.col; var i: usize = repeat; - while (i > 0) : (i -= 1) {} + while (i > 0) : (i -= 1) { + try move(root, &sel.end, ed.metrics); + } + sel.begin.col = if (sel.is_reversed()) pivot +| 1 else pivot; + cursel.cursor = sel.end; }; - ed.with_selections_const_repeat(root, move, ctx) catch {}; ed.clamp(); } From 3db11a43c9e0110059546a153f62a32735a4cec6 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 2 Dec 2025 16:22:06 +0100 Subject: [PATCH 03/25] Reapply "fix: build fix after rebase/merge" This reverts commit fd9fa4ee8f9a6a9aff4577dae39ba05461537df0. --- src/tui/mode/helix.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 4a429912..cceab4e7 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -568,7 +568,7 @@ fn extend_to_word(ctx: command.Context, move: Editor.cursor_operator_const, _: D var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - const sel = try cursel.enable_selection(root, ed.metrics); + const sel = cursel.enable_selection(root, ed.metrics); const pivot: usize = if (sel.is_reversed()) sel.begin.col -| 1 else sel.begin.col; var i: usize = repeat; while (i > 0) : (i -= 1) { From bb53ba0fc1bf72cc36e3039430bfb229cc8c1f83 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 16:11:53 +0100 Subject: [PATCH 04/25] refactor: add Cursor char_at, char_left/_right, test_left/_right functions --- src/buffer/Cursor.zig | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/buffer/Cursor.zig b/src/buffer/Cursor.zig index fea7aa0c..d288c8de 100644 --- a/src/buffer/Cursor.zig +++ b/src/buffer/Cursor.zig @@ -193,10 +193,39 @@ pub fn egc_at(self: *const Self, root: Buffer.Root, metrics: Metrics) error{NotF return root.egc_at(self.row, self.col, metrics); } +pub fn char_at(self: *const Self, root: Buffer.Root, metrics: Metrics) []const u8 { + const char, _, _ = root.egc_at(self.row, self.col, metrics) catch return &.{}; + return char; +} + +pub fn char_left(self: *const Self, root: Buffer.Root, metrics: Metrics) []const u8 { + var tmp = self.*; + tmp.move_left(root, metrics) catch return &.{}; + return tmp.char_at(root, metrics); +} + +pub fn char_right(self: *const Self, root: Buffer.Root, metrics: Metrics) []const u8 { + var tmp = self.*; + tmp.move_right(root, metrics) catch return &.{}; + return tmp.char_at(root, metrics); +} + pub fn test_at(self: *const Self, root: Buffer.Root, pred: *const fn (c: []const u8) bool, metrics: Metrics) bool { return root.test_at(pred, self.row, self.col, metrics); } +pub fn test_left(self: *const Self, root: Buffer.Root, pred: *const fn (c: []const u8) bool, metrics: Metrics) bool { + var tmp = self.*; + tmp.move_left(root, metrics) catch return false; + return root.test_at(pred, tmp.row, tmp.col, metrics); +} + +pub fn test_right(self: *const Self, root: Buffer.Root, pred: *const fn (c: []const u8) bool, metrics: Metrics) bool { + var tmp = self.*; + tmp.move_right(root, metrics) catch return false; + return root.test_at(pred, tmp.row, tmp.col, metrics); +} + pub fn write(self: *const Self, writer: *std.Io.Writer) !void { try cbor.writeValue(writer, .{ self.row, From 1615cd37e8197fec9a4f26825581aecf3903e9cd Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 16:12:38 +0100 Subject: [PATCH 05/25] refactor: add Selection.from_cursor_inclusive --- src/buffer/Selection.zig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/buffer/Selection.zig b/src/buffer/Selection.zig index 78ec58aa..8d9057e5 100644 --- a/src/buffer/Selection.zig +++ b/src/buffer/Selection.zig @@ -19,6 +19,12 @@ pub fn from_cursor(cursor: *const Cursor) Self { return .{ .begin = cursor.*, .end = cursor.* }; } +pub fn from_cursor_inclusive(cursor: *const Cursor, root: Buffer.Root, metrics: Buffer.Metrics) Self { + var sel: Self = .{ .begin = cursor.*, .end = cursor.* }; + sel.end.move_right(root, metrics) catch {}; + return sel; +} + pub fn from_pos(sel: Self, root: Buffer.Root, metrics: Buffer.Metrics) Self { return .{ .begin = .{ From a15ceeb4a7c6cf2e8db835942f4d6b7fc1005669 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 16:13:57 +0100 Subject: [PATCH 06/25] refactor: split is_not_word_char into char_class and CharClass --- src/tui/editor.zig | 84 ++++++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 33 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 5e34ca39..58f2f894 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -2220,42 +2220,54 @@ pub const Editor = struct { const cursel_operator_mut = *const fn (self: *Self, root: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root; const cursel_operator_mut_arg = *const fn (self: *Self, root: Buffer.Root, cursel: *CurSel, allocator: Allocator, ctx: Context) error{Stop}!Buffer.Root; - pub fn is_not_word_char(c: []const u8) bool { - if (c.len == 0) return true; + pub const CharClass = enum { + whitespace, + word, + non_word, + eol, + end, + }; + + pub fn char_class(c: []const u8) CharClass { + if (c.len == 0) return .end; return switch (c[0]) { - ' ' => true, - '=' => true, - '"' => true, - '\'' => true, - '\t' => true, - '\n' => true, - '/' => true, - '\\' => true, - '*' => true, - ':' => true, - '.' => true, - ',' => true, - '(' => true, - ')' => true, - '{' => true, - '}' => true, - '[' => true, - ']' => true, - ';' => true, - '|' => true, - '!' => true, - '?' => true, - '&' => true, - '@' => true, - '-' => true, - '<' => true, - '>' => true, - else => false, + '=' => .non_word, + '"' => .non_word, + '\'' => .non_word, + '/' => .non_word, + '\\' => .non_word, + '*' => .non_word, + ':' => .non_word, + '.' => .non_word, + ',' => .non_word, + '(' => .non_word, + ')' => .non_word, + '{' => .non_word, + '}' => .non_word, + '[' => .non_word, + ']' => .non_word, + ';' => .non_word, + '|' => .non_word, + '!' => .non_word, + '?' => .non_word, + '&' => .non_word, + '@' => .non_word, + '-' => .non_word, + '<' => .non_word, + '>' => .non_word, + ' ' => .whitespace, + '\t' => .whitespace, + '\n' => .eol, + else => .word, }; } + pub fn is_not_word_char(c: []const u8) bool { + return char_class(c) != .word; + } + pub fn is_word_char(c: []const u8) bool { - return !is_not_word_char(c); + return char_class(c) == .word; } fn is_word_char_at_cursor(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { @@ -2279,11 +2291,17 @@ pub const Editor = struct { } pub fn is_whitespace(c: []const u8) bool { - return (c.len == 0) or (c[0] == ' ') or (c[0] == '\t'); + return switch (char_class(c)) { + .whitespace, .end => true, + else => false, + }; } pub fn is_whitespace_or_eol(c: []const u8) bool { - return is_whitespace(c) or c[0] == '\n'; + return switch (char_class(c)) { + .whitespace, .end, .eol => true, + else => false, + }; } pub fn is_whitespace_at_cursor(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { From 1755ecb3dd44f75f2972769c9c9b1e4af6b4ea4e Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 16:16:05 +0100 Subject: [PATCH 07/25] refactor: simplify disable_selection to never move the cursor --- src/tui/editor.zig | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 58f2f894..cd094fff 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -137,37 +137,19 @@ pub const CurSel = struct { } fn to_selection_inclusive(self: *const Self, root: Buffer.Root, metrics: Buffer.Metrics) Selection { - return if (self.selection) |sel| - sel - else cod: { - var sel = Selection.from_cursor(&self.cursor); - sel.end.move_right(root, metrics) catch {}; - break :cod sel; - }; + return self.selection orelse Selection.from_cursor_inclusive(&self.cursor, root, metrics); } - pub fn disable_selection(self: *Self, root: Buffer.Root, metrics: Buffer.Metrics) void { - switch (tui.get_selection_style()) { - .normal => self.disable_selection_normal(), - .inclusive => self.disable_selection_inclusive(root, metrics), - } + pub fn disable_selection(self: *Self, _: Buffer.Root, _: Buffer.Metrics) void { + self.selection = null; } pub fn disable_selection_normal(self: *Self) void { self.selection = null; } - fn disable_selection_inclusive(self: *Self, root: Buffer.Root, metrics: Buffer.Metrics) void { - if (self.selection) |sel| { - if (!sel.is_reversed()) self.cursor.move_left(root, metrics) catch {}; - self.selection = null; - } - } - - pub fn check_selection(self: *Self, root: Buffer.Root, metrics: Buffer.Metrics) void { - if (self.selection) |sel| if (sel.empty()) { - self.disable_selection(root, metrics); - }; + pub fn check_selection(self: *Self, _: Buffer.Root, _: Buffer.Metrics) void { + self.selection = if (self.selection) |sel| if (sel.empty()) null else sel else null; } fn expand_selection_to_line(self: *Self, root: Buffer.Root, metrics: Buffer.Metrics) *Selection { From 7bf532bdfd692a6697031e22c96ffba021baebf5 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 16:44:16 +0100 Subject: [PATCH 08/25] fix: make helix move_prev_word_start an exact match to real helix --- src/tui/editor.zig | 2 +- src/tui/mode/helix.zig | 72 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index cd094fff..ea2a0594 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -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; diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index cceab4e7..ee708407 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -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} }; From 0ab260a1653380d255bafdde32bc0582e2797a44 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 16:45:42 +0100 Subject: [PATCH 09/25] refactor: reduce duplication of context getting code in helix mode --- src/tui/mode/helix.zig | 227 +++++++++++++++++++---------------------- src/tui/tui.zig | 2 +- 2 files changed, 108 insertions(+), 121 deletions(-) diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index ee708407..65a0db41 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -7,6 +7,7 @@ const command = @import("command"); const cmd = command.executeName; const tui = @import("../tui.zig"); +const MainView = tui.MainView; const Editor = @import("../editor.zig").Editor; const CurSel = @import("../editor.zig").CurSel; const Buffer = @import("Buffer"); @@ -29,11 +30,12 @@ pub fn deinit() void { } const Commands = command.Collection(cmds_); +const Ctx = command.Context; +const Meta = command.Metadata; +const Result = command.Result; + const cmds_ = struct { pub const Target = void; - const Ctx = command.Context; - const Meta = command.Metadata; - const Result = command.Result; pub fn w(_: *void, _: Ctx) Result { try cmd("save_file", .{}); @@ -201,9 +203,7 @@ const cmds_ = struct { pub const save_selection_meta: Meta = .{ .description = "Save current selection to location history" }; pub fn split_selection_on_newline(_: *void, _: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = try ed.buf_root(); + const ed, const root = get_buf() orelse return; const cursels = try ed.cursels.toOwnedSlice(ed.allocator); defer ed.allocator.free(cursels); for (cursels) |*cursel_| if (cursel_.*) |*cursel| { @@ -214,19 +214,14 @@ const cmds_ = struct { pub const split_selection_on_newline_meta: Meta = .{ .description = "Add cursor to each line in selection helix" }; pub fn match_brackets(_: *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; + const ed, const root = get_buf() orelse return; try ed.with_cursels_const_once_arg(root, &match_bracket, ctx); ed.clamp(); } pub const match_brackets_meta: Meta = .{ .description = "Goto matching bracket" }; pub fn extend_line_below(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = try ed.buf_root(); - + const ed, const root = get_buf() orelse return; var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; while (repeat > 0) : (repeat -= 1) { @@ -265,73 +260,6 @@ 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_cursels_const_repeat(move_cursor_prev_word_start, ctx); } @@ -374,9 +302,7 @@ const cmds_ = struct { pub const extend_next_long_word_end_meta: Meta = .{ .description = "Extend next long word end", .arguments = &.{.integer} }; pub fn cut_forward_internal_inclusive(_: *void, _: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const b = try ed.buf_for_update(); + const ed, const b = get_buf_for_update() orelse return; tui.clipboard_start_group(); const root = try ed.cut_to(move_noop, b.root); try ed.update_buf(root); @@ -385,10 +311,7 @@ const cmds_ = struct { pub const cut_forward_internal_inclusive_meta: Meta = .{ .description = "Cut next character to internal clipboard (inclusive)" }; pub fn select_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(); - + const ed, const root = get_buf() orelse return; var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; while (repeat > 0) : (repeat -= 1) { @@ -413,10 +336,7 @@ const cmds_ = struct { pub const select_right_helix_meta: Meta = .{ .description = "Select right", .arguments = &.{.integer} }; pub fn select_left_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(); - + const ed, const root = get_buf() orelse return; var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; while (repeat > 0) : (repeat -= 1) { @@ -484,12 +404,10 @@ const cmds_ = struct { pub const extend_to_char_right_helix_meta: Meta = .{ .description = "Extend Selection to char right" }; pub fn select_textobject_inner(_: *void, ctx: Ctx) Result { - var action: []const u8 = ""; + const ed, const root = get_buf() orelse return error.Stop; + var action: []const u8 = ""; if (!try ctx.args.match(.{tp.extract(&action)})) return error.Stop; - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; if (std.mem.eql(u8, action, "w")) { try ed.with_cursels_const(root, select_inner_word, ed.metrics); @@ -503,12 +421,10 @@ const cmds_ = struct { pub const select_textobject_inner_meta: Meta = .{ .description = "select inside object helix" }; pub fn select_textobject_around(_: *void, ctx: Ctx) Result { - var action: []const u8 = ""; + const ed, const root = get_buf() orelse return; + var action: []const u8 = ""; if (!try ctx.args.match(.{tp.extract(&action)})) return error.Stop; - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; if (std.mem.eql(u8, action, "w")) { try ed.with_cursels_const(root, select_around_word, ed.metrics); @@ -522,10 +438,7 @@ const cmds_ = struct { pub const select_textobject_around_meta: Meta = .{ .description = "select around object helix" }; pub fn copy_helix(_: *void, _: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - + const ed, const root = get_buf() orelse return; tui.clipboard_start_group(); for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| if (cursel.selection) |sel| @@ -551,9 +464,8 @@ const cmds_ = struct { pub const paste_clipboard_before_meta: Meta = .{ .description = "Paste from clipboard before selection" }; 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; + const ed, const b = get_buf_for_update() orelse return; + var root = b.root; root = try ed.with_cursels_mut_once_arg(root, replace_cursel_with_character, ed.allocator, ctx); try ed.update_buf(root); ed.clamp(); @@ -562,6 +474,87 @@ const cmds_ = struct { pub const replace_with_character_helix_meta: Meta = .{ .description = "Replace with character" }; }; +fn get_context() ?struct { *MainView, *Editor } { + const mv = tui.mainview() orelse return null; + const ed = mv.get_active_editor() orelse return null; + return .{ mv, ed }; +} + +fn get_buf() ?struct { *Editor, Buffer.Root } { + _, const ed = get_context() orelse return null; + return .{ ed, ed.buf_root() catch return null }; +} + +fn get_buf_for_update() ?struct { *Editor, *const Buffer } { + _, const ed = get_context() orelse return null; + return .{ ed, ed.buf_for_update() catch return null }; +} + +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 ed, const root = get_buf() orelse return; + try ed.with_cursels_const_repeat(root, move, ctx); + ed.clamp(); +} + fn match_bracket(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metrics: Buffer.Metrics) error{Stop}!void { var symbol: []const u8 = undefined; const mode: enum { helix_sel_mode, helix_nor_mode } = if ((ctx.args.match(.{tp.extract(&symbol)}) catch false) and @@ -599,9 +592,7 @@ fn match_bracket(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metri } fn move_to_word(ctx: command.Context, move: Editor.cursor_operator_const, direction: Direction) command.Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = try ed.buf_root(); + const ed, const root = get_buf() orelse return; // NOR mode moves n words selecting the last one var repeat: usize = 0; @@ -624,6 +615,8 @@ fn move_to_word(ctx: command.Context, move: Editor.cursor_operator_const, direct sel.begin = sel.end; } cursel.cursor = sel.end; + if (direction == .forwards) + sel.end.move_right(root, ed.metrics) catch {}; cursel.selection = sel; }; ed.with_selections_const_repeat(root, move, command.fmt(.{1})) catch {}; @@ -631,9 +624,7 @@ fn move_to_word(ctx: command.Context, move: Editor.cursor_operator_const, direct } fn extend_to_word(ctx: command.Context, move: Editor.cursor_operator_const, _: Direction) command.Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = try ed.buf_root(); + const ed, const root = get_buf() orelse return; var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; @@ -652,9 +643,7 @@ fn extend_to_word(ctx: command.Context, move: Editor.cursor_operator_const, _: D } 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; + const ed, const root = get_buf() orelse return; try ed.with_cursels_const_once_arg(root, move, ctx); ed.clamp(); } @@ -1229,8 +1218,9 @@ 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; - const ed = mv.get_active_editor() orelse return; + const ed, const b = get_buf_for_update() orelse return; + var root = b.root; + var text_: []const u8 = undefined; const clipboard: []const tui.ClipboardEntry = if (ctx.args.buf.len > 0 and try ctx.args.match(.{tp.extract(&text_)})) @@ -1243,9 +1233,6 @@ fn paste_helix(ctx: command.Context, do_paste: pasting_function) command.Result return; } - const b = try ed.buf_for_update(); - var root = b.root; - // Chunks from clipboard are paired to selections // If more selections than chunks in the clipboard, the exceding selections // use the last chunk in the clipboard diff --git a/src/tui/tui.zig b/src/tui/tui.zig index b9f1f2d4..d73c2165 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -18,7 +18,7 @@ const syntax = @import("syntax"); const Widget = @import("Widget.zig"); const MessageFilter = @import("MessageFilter.zig"); -const MainView = @import("mainview.zig"); +pub const MainView = @import("mainview.zig"); // exports for unittesting pub const exports = struct { From c65583b1aeeeb765d346d15ccb86172e647a022e Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 16:56:54 +0100 Subject: [PATCH 10/25] fix: remove typo --- src/tui/mode/helix.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 65a0db41..20ca66fe 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -267,7 +267,6 @@ const cmds_ = struct { 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} }; From dd88be893e85c7d3b43c89846fd25e33a72902e2 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 16:57:59 +0100 Subject: [PATCH 11/25] refactor: add explicit command for initializing helix select mode --- src/keybind/builtin/helix.json | 2 +- src/tui/mode/helix.zig | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/keybind/builtin/helix.json b/src/keybind/builtin/helix.json index a7847173..3e5bbd87 100644 --- a/src/keybind/builtin/helix.json +++ b/src/keybind/builtin/helix.json @@ -282,7 +282,7 @@ "line_numbers": "relative", "cursor": "block", "selection": "inclusive", - "init_command": ["enable_selection"], + "init_command": ["init_helix_select_mode"], "press": [ ["ctrl+b", "select_page_up"], diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 20ca66fe..8580ac13 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -240,6 +240,12 @@ const cmds_ = struct { } pub const extend_line_below_meta: Meta = .{ .arguments = &.{.integer}, .description = "Select current line, if already selected, extend to next line" }; + pub fn init_helix_select_mode(_: *void, _: Ctx) Result { + _, const ed = get_context() orelse return; + try ed.enable_selection(.{}); + } + pub const init_helix_select_mode_meta: Meta = .{}; + pub fn move_next_word_start(_: *void, ctx: Ctx) Result { try move_to_word(ctx, Editor.move_cursor_word_right_vim, .forwards); } From 8d8f4b82cb34627e9b6722697263d44472df767c Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 17:22:58 +0100 Subject: [PATCH 12/25] refactor: completely remove inclusive selection mode Having inclusive mode change a few critical functions behind the sceans is not a good way to share functionality. Basically every function is broken in one or the other mode. So we remove it entirely and instead will rely on different functions for different behaviors. --- src/buffer/Selection.zig | 2 - src/keybind/builtin/helix.json | 2 - src/keybind/builtin/vim.json | 4 -- src/keybind/keybind.zig | 12 +--- src/tui/editor.zig | 112 ++++++++++++++------------------- src/tui/mode/helix.zig | 50 +++++++-------- src/tui/tui.zig | 4 -- 7 files changed, 75 insertions(+), 111 deletions(-) diff --git a/src/buffer/Selection.zig b/src/buffer/Selection.zig index 8d9057e5..3497163f 100644 --- a/src/buffer/Selection.zig +++ b/src/buffer/Selection.zig @@ -9,8 +9,6 @@ end: Cursor = Cursor{}, const Self = @This(); -pub const Style = enum { normal, inclusive }; - pub inline fn eql(self: Self, other: Self) bool { return self.begin.eql(other.begin) and self.end.eql(other.end); } diff --git a/src/keybind/builtin/helix.json b/src/keybind/builtin/helix.json index 3e5bbd87..32d8c243 100644 --- a/src/keybind/builtin/helix.json +++ b/src/keybind/builtin/helix.json @@ -8,7 +8,6 @@ "name": "NOR", "line_numbers": "relative", "cursor": "block", - "selection": "inclusive", "press": [ ["ctrl+b", "move_scroll_page_up"], ["ctrl+f", "move_scroll_page_down"], @@ -281,7 +280,6 @@ "name": "SEL", "line_numbers": "relative", "cursor": "block", - "selection": "inclusive", "init_command": ["init_helix_select_mode"], "press": [ diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index 54291a0c..626cae60 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -9,7 +9,6 @@ "name": "NORMAL", "line_numbers": "relative", "cursor": "block", - "selection": "normal", "press": [ ["b", "move_word_left_vim"], ["w", "move_word_right_vim"], @@ -121,7 +120,6 @@ "name": "VISUAL", "line_numbers": "relative", "cursor": "block", - "selection": "normal", "init_command": ["enable_selection"], "press": [ ["", ["cancel"], ["enter_mode", "normal"]], @@ -186,7 +184,6 @@ "name": "VISUAL LINE", "line_numbers": "relative", "cursor": "block", - "selection": "normal", "press": [ ["", ["cancel"], ["enter_mode", "normal"]], ["k", "select_up"], @@ -234,7 +231,6 @@ "inherit": "visual", "line_numbers": "relative", "cursor": "block", - "selection": "normal", "init_command": ["enable_selection"], "press": [ ["k", "add_cursor_up"], diff --git a/src/keybind/keybind.zig b/src/keybind/keybind.zig index e20a1a0f..cffa1327 100644 --- a/src/keybind/keybind.zig +++ b/src/keybind/keybind.zig @@ -12,7 +12,6 @@ const input = @import("input"); const command = @import("command"); const EventHandler = @import("EventHandler"); const KeyEvent = input.KeyEvent; -const SelectionStyle = @import("Buffer").Selection.Style; pub const CursorShape = @import("config").CursorShape; const log = std.log.scoped(.keybind); @@ -87,7 +86,6 @@ const Handler = struct { .name = self.bindings.name, .line_numbers = self.bindings.line_numbers, .cursor_shape = self.bindings.cursor_shape, - .selection_style = self.bindings.selection_style, .init_command = self.bindings.init_command, .deinit_command = self.bindings.deinit_command, .insert_command = try allocator.dupe(u8, insert_command), @@ -112,7 +110,6 @@ const Handler = struct { mode_.name = self.bindings.name; mode_.line_numbers = self.bindings.line_numbers; mode_.cursor_shape = self.bindings.cursor_shape; - mode_.selection_style = self.bindings.selection_style; mode_.init_command = self.bindings.init_command; mode_.deinit_command = self.bindings.deinit_command; if (mode_.init_command) |init_command| init_command.execute_const(); @@ -136,7 +133,6 @@ pub const Mode = struct { bindings: *const BindingSet, keybind_hints: *const KeybindHints, cursor_shape: ?CursorShape = null, - selection_style: SelectionStyle, init_command: ?Command = null, deinit_command: ?Command = null, initialized: bool = false, @@ -172,7 +168,6 @@ pub const Mode = struct { self.line_numbers = .inherit; self.keybind_hints = &.{}; self.cursor_shape = null; - self.selection_style = .normal; self.init_command = null; self.deinit_command = null; self.initialized = false; @@ -468,7 +463,6 @@ const BindingSet = struct { config_section: []const u8, line_numbers: LineNumbers = .inherit, cursor_shape: ?CursorShape = null, - selection_style: SelectionStyle, insert_command: []const u8 = "", hints_map: KeybindHints = .{}, init_command: ?Command = null, @@ -478,7 +472,7 @@ const BindingSet = struct { const OnMatchFailure = enum { insert, ignore }; fn load(allocator: std.mem.Allocator, namespace_name: []const u8, config_section: []const u8, mode_bindings: std.json.Value, fallback: ?*const BindingSet, namespace: *Namespace) (error{ OutOfMemory, WriteFailed } || parse_flow.ParseError || parse_vim.ParseError || std.json.ParseFromValueError)!@This() { - var self: @This() = .{ .name = undefined, .config_section = config_section, .selection_style = undefined }; + var self: @This() = .{ .name = undefined, .config_section = config_section }; const JsonConfig = struct { press: []const []const std.json.Value = &[_][]std.json.Value{}, @@ -490,7 +484,6 @@ const BindingSet = struct { cursor: ?CursorShape = null, inherit: ?[]const u8 = null, inherits: ?[][]const u8 = null, - selection: ?SelectionStyle = null, init_command: ?[]const std.json.Value = null, deinit_command: ?[]const std.json.Value = null, }; @@ -503,7 +496,6 @@ const BindingSet = struct { self.name = try allocator.dupe(u8, parsed.value.name orelse namespace_name); self.line_numbers = parsed.value.line_numbers; self.cursor_shape = parsed.value.cursor; - self.selection_style = parsed.value.selection orelse .normal; if (parsed.value.init_command) |cmd| self.init_command = try Command.load(allocator, cmd); if (parsed.value.deinit_command) |cmd| self.deinit_command = try Command.load(allocator, cmd); try self.load_event(allocator, &self.press, input.event.press, parsed.value.press); @@ -575,7 +567,7 @@ const BindingSet = struct { } fn copy(allocator: std.mem.Allocator, config_section: []const u8, fallback: *const BindingSet) error{OutOfMemory}!@This() { - var self: @This() = .{ .name = fallback.name, .config_section = config_section, .selection_style = fallback.selection_style }; + var self: @This() = .{ .name = fallback.name, .config_section = config_section }; self.on_match_failure = fallback.on_match_failure; for (fallback.press.items) |binding| try self.press.append(allocator, binding); for (fallback.release.items) |binding| try self.release.append(allocator, binding); diff --git a/src/tui/editor.zig b/src/tui/editor.zig index ea2a0594..6f88b772 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -115,24 +115,12 @@ pub const CurSel = struct { self.* = .{}; } - pub fn enable_selection(self: *Self, root: Buffer.Root, metrics: Buffer.Metrics) *Selection { - self.selection = self.to_selection(root, metrics); + pub fn enable_selection(self: *Self) *Selection { + self.selection = self.to_selection(); return if (self.selection) |*sel| sel else unreachable; } - pub fn enable_selection_normal(self: *Self) *Selection { - self.selection = self.to_selection_normal(); - return if (self.selection) |*sel| sel else unreachable; - } - - fn to_selection(self: *const Self, root: Buffer.Root, metrics: Buffer.Metrics) Selection { - return switch (tui.get_selection_style()) { - .normal => self.to_selection_normal(), - .inclusive => self.to_selection_inclusive(root, metrics), - }; - } - - fn to_selection_normal(self: *const Self) Selection { + fn to_selection(self: *const Self) Selection { return if (self.selection) |sel| sel else Selection.from_cursor(&self.cursor); } @@ -140,7 +128,7 @@ pub const CurSel = struct { return self.selection orelse Selection.from_cursor_inclusive(&self.cursor, root, metrics); } - pub fn disable_selection(self: *Self, _: Buffer.Root, _: Buffer.Metrics) void { + pub fn disable_selection(self: *Self) void { self.selection = null; } @@ -148,12 +136,12 @@ pub const CurSel = struct { self.selection = null; } - pub fn check_selection(self: *Self, _: Buffer.Root, _: Buffer.Metrics) void { + pub fn check_selection(self: *Self) void { self.selection = if (self.selection) |sel| if (sel.empty()) null else sel else null; } fn expand_selection_to_line(self: *Self, root: Buffer.Root, metrics: Buffer.Metrics) *Selection { - const sel = self.enable_selection(root, metrics); + const sel = self.enable_selection(); sel.normalize(); sel.begin.move_begin(); if (!(sel.end.row > sel.begin.row and sel.end.col == 0)) { @@ -1883,7 +1871,7 @@ pub const Editor = struct { fn cancel_all_selections(self: *Self) void { var primary = self.get_primary().*; - primary.disable_selection(self.buf_root() catch return, self.metrics); + primary.disable_selection(); self.cursels.clearRetainingCapacity(); self.cursels.addOneAssumeCapacity().* = primary; for (self.matches.items) |*match_| if (match_.*) |*match| { @@ -1937,7 +1925,7 @@ pub const Editor = struct { fn with_cursors_const_once(self: *Self, root: Buffer.Root, move: cursor_operator_const) error{Stop}!void { for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - cursel.disable_selection(root, self.metrics); + cursel.disable_selection(); try with_cursor_const(root, move, cursel, self.metrics); }; self.collapse_cursors(); @@ -1948,7 +1936,7 @@ pub const Editor = struct { _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; while (repeat > 0) : (repeat -= 1) { for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - cursel.disable_selection(root, self.metrics); + cursel.disable_selection(); try with_cursor_const(root, move, cursel, self.metrics); }; self.collapse_cursors(); @@ -1961,7 +1949,7 @@ pub const Editor = struct { fn with_cursors_const_arg(self: *Self, root: Buffer.Root, move: cursor_operator_const_arg, ctx: Context) error{Stop}!void { for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - cursel.disable_selection(root, self.metrics); + cursel.disable_selection(); try with_cursor_const_arg(root, move, cursel, ctx, self.metrics); }; self.collapse_cursors(); @@ -1986,10 +1974,10 @@ pub const Editor = struct { } pub fn with_selection_const(root: Buffer.Root, move: cursor_operator_const, cursel: *CurSel, metrics: Buffer.Metrics) error{Stop}!void { - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); try move(root, &sel.end, metrics); cursel.cursor = sel.end; - cursel.check_selection(root, metrics); + cursel.check_selection(); } pub fn with_selections_const_once(self: *Self, root: Buffer.Root, move: cursor_operator_const) error{Stop}!void { @@ -2018,10 +2006,10 @@ pub const Editor = struct { } fn with_selection_const_arg(root: Buffer.Root, move: cursor_operator_const_arg, cursel: *CurSel, ctx: Context, metrics: Buffer.Metrics) error{Stop}!void { - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); try move(root, &sel.end, ctx, metrics); cursel.cursor = sel.end; - cursel.check_selection(root, metrics); + cursel.check_selection(); } fn with_selections_const_arg(self: *Self, root: Buffer.Root, move: cursor_operator_const_arg, ctx: Context) error{Stop}!void { @@ -2035,7 +2023,7 @@ pub const Editor = struct { } fn with_selection_and_view_const(root: Buffer.Root, move: cursor_view_operator_const, cursel: *CurSel, view: *const View, metrics: Buffer.Metrics) error{Stop}!void { - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); try move(root, &sel.end, view, metrics); cursel.cursor = sel.end; } @@ -2500,7 +2488,7 @@ pub const Editor = struct { self.cancel_all_selections(); } const primary = self.get_primary(); - primary.disable_selection(root, self.metrics); + primary.disable_selection(); self.selection_mode = .char; try self.send_editor_jump_source(); primary.cursor.move_abs(root, &self.view, @intCast(y), @intCast(x), self.metrics) catch return; @@ -2514,7 +2502,7 @@ pub const Editor = struct { pub fn primary_double_click(self: *Self, y: c_int, x: c_int) !void { const primary = self.get_primary(); const root = self.buf_root() catch return; - primary.disable_selection(root, self.metrics); + primary.disable_selection(); self.selection_mode = .word; primary.cursor.move_abs(root, &self.view, @intCast(y), @intCast(x), self.metrics) catch return; _ = try self.select_word_at_cursor(primary); @@ -2526,7 +2514,7 @@ pub const Editor = struct { pub fn primary_triple_click(self: *Self, y: c_int, x: c_int) !void { const primary = self.get_primary(); const root = self.buf_root() catch return; - primary.disable_selection(root, self.metrics); + primary.disable_selection(); self.selection_mode = .line; primary.cursor.move_abs(root, &self.view, @intCast(y), @intCast(x), self.metrics) catch return; try self.select_line_at_cursor(root, primary, .exclude_eol); @@ -2540,7 +2528,7 @@ pub const Editor = struct { const x_ = if (x < 0) 0 else x; const primary = self.get_primary(); const root = self.buf_root() catch return; - const sel = primary.enable_selection(root, self.metrics); + const sel = primary.enable_selection(); sel.end.move_abs(root, &self.view, @intCast(y_), @intCast(x_), self.metrics) catch return; const initial = self.selection_drag_initial orelse sel.*; switch (self.selection_mode) { @@ -2568,7 +2556,7 @@ pub const Editor = struct { }, } primary.cursor = sel.end; - primary.check_selection(root, self.metrics); + primary.check_selection(); self.collapse_cursors(); self.clamp_mouse(); } @@ -2758,7 +2746,7 @@ pub const Editor = struct { } pub fn insert(self: *Self, root: Buffer.Root, cursel: *CurSel, s: []const u8, allocator: Allocator) !Buffer.Root { - cursel.check_selection(root, self.metrics); + cursel.check_selection(); var root_ = if (cursel.selection) |_| try self.delete_selection(root, cursel, allocator) else root; const cursor = &cursel.cursor; const begin = cursel.cursor; @@ -2911,7 +2899,7 @@ pub const Editor = struct { const primary = self.get_primary(); const root = self.buf_root() catch return; if (primary.selection) |_| {} else { - const sel = primary.enable_selection(root, self.metrics); + const sel = primary.enable_selection(); try move_cursor_begin(root, &sel.begin, self.metrics); try move_cursor_end(root, &sel.end, self.metrics); try move_cursor_right(root, &sel.end, self.metrics); @@ -3237,7 +3225,7 @@ pub const Editor = struct { .left => if (sel.is_reversed()) sel.end else sel.begin, .right => if (sel.is_reversed()) sel.begin else sel.end, }; - cursel.disable_selection(root, self.metrics); + cursel.disable_selection(); } else { try with_cursor_const(root, switch (direction) { .left => move_cursor_left, @@ -3657,7 +3645,7 @@ pub const Editor = struct { pub const add_cursor_all_matches_meta: Meta = .{ .description = "Add cursors to all highlighted matches" }; fn add_cursors_to_cursel_line_ends(self: *Self, root: Buffer.Root, cursel: *CurSel) !void { - const sel = cursel.enable_selection(root, self.metrics); + const sel = cursel.enable_selection(); sel.normalize(); var row = sel.begin.row; while (row <= sel.end.row) : (row += 1) { @@ -3751,7 +3739,7 @@ pub const Editor = struct { cursel.cursor = sel.begin; var add_eol = false; if (cursel.selection) |_| { - cursel.disable_selection(root, self.metrics); + cursel.disable_selection(); } else { var test_eof = sel.end; test_eof.move_right(root, self.metrics) catch { // test for EOF @@ -3786,7 +3774,7 @@ pub const Editor = struct { cursel.cursor = sel.end; if (cursel.selection) |_| { - cursel.disable_selection(root, self.metrics); + cursel.disable_selection(); } else { var test_eof = sel.end; test_eof.move_right(root, self.metrics) catch { // test for EOF @@ -3840,7 +3828,7 @@ pub const Editor = struct { cursel.cursor = sel.end; if (cursel.selection) |_| { - cursel.disable_selection(root, self.metrics); + cursel.disable_selection(); } else { var test_eof = sel.end; test_eof.move_right(root, self.metrics) catch { // test for EOF @@ -3944,7 +3932,7 @@ pub const Editor = struct { if (first == 0) return root; const off = first % self.indent_size; const cols = if (off == 0) self.indent_size else off; - const sel = cursel.enable_selection(root, self.metrics); + const sel = cursel.enable_selection(); try sel.begin.move_to(root, sel.begin.row, first, self.metrics); try sel.end.move_to(root, sel.end.row, first - cols, self.metrics); var saved = false; @@ -4169,8 +4157,7 @@ pub const Editor = struct { pub const cancel_meta: Meta = .{ .description = "Cancel current action" }; pub fn enable_selection(self: *Self, _: Context) Result { - const root = try self.buf_root(); - _ = self.get_primary().enable_selection(root, self.metrics); + _ = self.get_primary().enable_selection(); } pub const enable_selection_meta: Meta = .{ .description = "Enable selection" }; @@ -4399,7 +4386,7 @@ pub const Editor = struct { self.cancel_all_selections(); const primary = self.get_primary(); const root = try self.buf_root(); - const sel = primary.enable_selection(root, self.metrics); + const sel = primary.enable_selection(); try expand_selection_to_all(root, sel, self.metrics); primary.cursor = sel.end; self.clamp(); @@ -4409,8 +4396,8 @@ pub const Editor = struct { fn select_word_at_cursor(self: *Self, cursel: *CurSel) !*Selection { const root = try self.buf_root(); - const sel = cursel.enable_selection(root, self.metrics); - defer cursel.check_selection(root, self.metrics); + const sel = cursel.enable_selection(); + defer cursel.check_selection(); sel.normalize(); try move_cursor_word_begin(root, &sel.begin, self.metrics); move_cursor_word_end(root, &sel.end, self.metrics) catch {}; @@ -4419,7 +4406,7 @@ pub const Editor = struct { } pub fn select_line_at_cursor(self: *Self, root: Buffer.Root, cursel: *CurSel, mode: enum { include_eol, exclude_eol, hold_cursor }) !void { - const sel = cursel.enable_selection(root, self.metrics); + const sel = cursel.enable_selection(); sel.normalize(); try move_cursor_begin(root, &sel.begin, self.metrics); move_cursor_end(root, &sel.end, self.metrics) catch {}; @@ -4483,12 +4470,12 @@ pub const Editor = struct { } fn top_node_at_cursel(self: *const Self, cursel: *const CurSel, root: Buffer.Root, metrics: Buffer.Metrics) error{Stop}!syntax.Node { - const sel = cursel.to_selection(root, metrics); + const sel = cursel.to_selection(); return try self.top_node_at_selection(sel, root, metrics); } fn expand_selection_to_parent_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { - const sel = cursel.enable_selection(root, metrics).*; + const sel = cursel.enable_selection().*; var node = try self.top_node_at_selection(sel, root, metrics); if (node.isNull()) return error.Stop; var node_sel = CurSel.selection_from_node(node, root, metrics); @@ -4502,7 +4489,7 @@ pub const Editor = struct { try self.send_editor_jump_source(); const root = try self.buf_root(); const cursel = self.get_primary(); - cursel.check_selection(root, self.metrics); + cursel.check_selection(); try if (cursel.selection) |_| self.expand_selection_to_parent_node(root, cursel, self.metrics) else @@ -4513,7 +4500,7 @@ pub const Editor = struct { pub const expand_selection_meta: Meta = .{ .description = "Expand selection to AST parent node" }; fn shrink_selection_to_child_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { - const sel = cursel.enable_selection(root, metrics).*; + const sel = cursel.enable_selection().*; const node = try self.node_at_selection(sel, root, metrics); if (node.isNull() or node.getChildCount() == 0) return error.Stop; const child = node.getChild(0); @@ -4522,7 +4509,7 @@ pub const Editor = struct { } fn shrink_selection_to_named_child_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { - const sel = cursel.enable_selection(root, metrics).*; + const sel = cursel.enable_selection().*; const node = try self.node_at_selection(sel, root, metrics); if (node.isNull() or node.getNamedChildCount() == 0) return error.Stop; const child = node.getNamedChild(0); @@ -4536,7 +4523,7 @@ pub const Editor = struct { try self.send_editor_jump_source(); const root = try self.buf_root(); const cursel = self.get_primary(); - cursel.check_selection(root, self.metrics); + cursel.check_selection(); if (cursel.selection) |_| { try if (unnamed) self.shrink_selection_to_child_node(root, cursel, self.metrics) @@ -4570,7 +4557,7 @@ pub const Editor = struct { try self.send_editor_jump_source(); const root = try self.buf_root(); const cursel = self.get_primary(); - cursel.check_selection(root, self.metrics); + cursel.check_selection(); if (cursel.selection) |_| { try if (unnamed) self.select_next_sibling_node(root, cursel, self.metrics) @@ -4604,7 +4591,7 @@ pub const Editor = struct { try self.send_editor_jump_source(); const root = try self.buf_root(); const cursel = self.get_primary(); - cursel.check_selection(root, self.metrics); + cursel.check_selection(); try if (unnamed) self.select_prev_sibling_node(root, cursel, self.metrics) else @@ -4680,7 +4667,7 @@ pub const Editor = struct { root_ = self.collapse_trailing_ws_line(root_, row, b_allocator); const leading_ws_ = find_first_non_ws(root_, cursel.cursor.row, self.metrics); if (leading_ws_ > leading_ws and leading_ws_ > cursel.cursor.col) { - const sel = cursel.enable_selection(root_, self.metrics); + const sel = cursel.enable_selection(); sel.* = .{ .begin = .{ .row = cursel.cursor.row, .col = cursel.cursor.col }, .end = .{ .row = cursel.cursor.row, .col = leading_ws_ }, @@ -4866,7 +4853,7 @@ pub const Editor = struct { root = try self.insert(root, &begin, chars_begin, b.allocator); var end: CurSel = .{ .cursor = sel.end }; root = try self.insert(root, &end, chars_end, b.allocator); - cursel.disable_selection(root, self.metrics); + cursel.disable_selection(); } else blk: { const egc, _, _ = cursel.cursor.egc_at(root, self.metrics) catch { root = try self.insert(root, cursel, chars_right, b.allocator); @@ -5469,10 +5456,7 @@ pub const Editor = struct { const col = cursor.col; for (self.matches.items) |*match_| if (match_.*) |*match| { if (match.has_selection) continue; - switch (tui.get_selection_style()) { - .normal => if (cursor.within(match.to_selection())) return match, - .inclusive => {}, - } + if (cursor.within(match.to_selection())) return match; if (row < match.begin.row or (row == match.begin.row and col < match.begin.col)) return match; }; return null; @@ -6161,7 +6145,7 @@ pub const Editor = struct { state.chunks = 1; primary.cursor = state.old_primary.cursor; } else { - const sel = primary.enable_selection(root, self.metrics); + const sel = primary.enable_selection(); sel.begin = state.begin; sel.end = state.pos.cursor; if (state.old_primary_reversed) sel.reverse(); @@ -6190,7 +6174,7 @@ pub const Editor = struct { var root = root_; const saved = cursel.*; const sel = if (cursel.selection) |*sel| sel else ret: { - var sel = cursel.enable_selection(root, self.metrics); + var sel = cursel.enable_selection(); move_cursor_word_begin(root, &sel.begin, self.metrics) catch return error.Stop; move_cursor_word_end(root, &sel.end, self.metrics) catch return error.Stop; break :ret sel; @@ -6219,7 +6203,7 @@ pub const Editor = struct { var root = root_; const saved = cursel.*; const sel = if (cursel.selection) |*sel| sel else ret: { - var sel = cursel.enable_selection(root, self.metrics); + var sel = cursel.enable_selection(); move_cursor_word_begin(root, &sel.begin, self.metrics) catch return error.Stop; move_cursor_word_end(root, &sel.end, self.metrics) catch return error.Stop; break :ret sel; @@ -6248,7 +6232,7 @@ pub const Editor = struct { var root = root_; var saved = cursel.*; const sel = if (cursel.selection) |*sel| sel else ret: { - var sel = cursel.enable_selection(root, self.metrics); + var sel = cursel.enable_selection(); move_cursor_right(root, &sel.end, self.metrics) catch return error.Stop; saved.cursor = sel.end; break :ret sel; diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 8580ac13..248f2ba3 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -226,7 +226,7 @@ const cmds_ = struct { _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; while (repeat > 0) : (repeat -= 1) { for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - const sel = cursel.enable_selection_normal(); + const sel = cursel.enable_selection(); sel.normalize(); try Editor.move_cursor_begin(root, &sel.begin, ed.metrics); @@ -321,7 +321,7 @@ const cmds_ = struct { _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; while (repeat > 0) : (repeat -= 1) { for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - const sel = cursel.enable_selection(root, ed.metrics); + const sel = cursel.enable_selection(); // handling left to right transition const sel_begin: i32 = @intCast(sel.begin.col); @@ -333,7 +333,7 @@ const cmds_ = struct { try Editor.move_cursor_right(root, &sel.end, ed.metrics); cursel.cursor = sel.end; - cursel.check_selection(root, ed.metrics); + cursel.check_selection(); }; } ed.clamp(); @@ -361,7 +361,7 @@ const cmds_ = struct { } } - cursel.check_selection(root, ed.metrics); + cursel.check_selection(); }; } ed.clamp(); @@ -634,7 +634,7 @@ fn extend_to_word(ctx: command.Context, move: Editor.cursor_operator_const, _: D var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - const sel = cursel.enable_selection(root, ed.metrics); + const sel = cursel.enable_selection(); const pivot: usize = if (sel.is_reversed()) sel.begin.col -| 1 else sel.begin.col; var i: usize = repeat; while (i > 0) : (i -= 1) { @@ -660,7 +660,7 @@ fn select_inner_word(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics Editor.move_cursor_left_until(root, &prev, Editor.is_word_boundary_left, metrics); Editor.move_cursor_right_until(root, &next, Editor.is_word_boundary_right, metrics); try next.move_right(root, metrics); - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); sel.begin = prev; sel.end = next; cursel.*.cursor = next; @@ -673,7 +673,7 @@ fn select_inner_long_word(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Me Editor.move_cursor_left_until(root, &prev, is_long_word_boundary_left, metrics); Editor.move_cursor_right_until(root, &next, is_long_word_boundary_right, metrics); try next.move_right(root, metrics); - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); sel.begin = prev; sel.end = next; cursel.*.cursor = next; @@ -694,7 +694,7 @@ fn select_around_word(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metric if (!cursel.cursor.test_at(root, Editor.is_word_char, metrics)) return; var expander = cursel.*; try select_inner_word(root, &expander, metrics); - const sel_e = expander.enable_selection(root, metrics); + const sel_e = expander.enable_selection(); var prev = sel_e.begin; var next = sel_e.end; if (next.test_at(root, is_tab_or_space, metrics)) { @@ -709,7 +709,7 @@ fn select_around_word(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metric prev = sel_e.begin; } } - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); sel.begin = prev; sel.end = next; cursel.*.cursor = next; @@ -750,7 +750,7 @@ fn select_cursel_to_char_left_helix(root: Buffer.Root, cursel: *CurSel, ctx: com //At end of file, it's ok }; moving_cursor.target = moving_cursor.col; - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); sel.begin = begin; sel.end = moving_cursor; cursel.cursor = moving_cursor; @@ -763,7 +763,7 @@ fn extend_cursel_to_char_left_helix(root: Buffer.Root, cursel: *CurSel, ctx: com //Character found, selecting moving_cursor.target = moving_cursor.col; - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); if (sel.empty()) sel.begin = begin; sel.end = moving_cursor; @@ -780,7 +780,7 @@ fn select_cursel_till_char_left_helix(root: Buffer.Root, cursel: *CurSel, ctx: c //At end of file, it's ok }; moving_cursor.target = moving_cursor.col; - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); sel.begin = begin; sel.end = moving_cursor; cursel.cursor = moving_cursor; @@ -793,7 +793,7 @@ fn extend_cursel_till_char_left_helix(root: Buffer.Root, cursel: *CurSel, ctx: c //Character found, selecting moving_cursor.target = moving_cursor.col; - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); if (sel.empty()) sel.begin = begin; sel.end = moving_cursor; @@ -807,7 +807,7 @@ fn select_cursel_till_char_right_helix(root: Buffer.Root, cursel: *CurSel, ctx: //Character found, selecting moving_cursor.target = moving_cursor.col; - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); sel.begin = begin; sel.end = moving_cursor; cursel.cursor = moving_cursor; @@ -820,7 +820,7 @@ fn extend_cursel_till_char_right_helix(root: Buffer.Root, cursel: *CurSel, ctx: //Character found, selecting moving_cursor.target = moving_cursor.col; - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); if (sel.empty()) sel.begin = begin; sel.end = moving_cursor; @@ -837,7 +837,7 @@ fn select_cursel_to_char_right_helix(root: Buffer.Root, cursel: *CurSel, ctx: co // We might be at end of file }; moving_cursor.target = moving_cursor.col; - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); sel.begin = begin; sel.end = moving_cursor; cursel.cursor = moving_cursor; @@ -853,7 +853,7 @@ fn extend_cursel_to_char_right_helix(root: Buffer.Root, cursel: *CurSel, ctx: co // We might be at end of file }; moving_cursor.target = moving_cursor.col; - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); if (sel.empty()) sel.begin = begin; sel.end = moving_cursor; @@ -891,7 +891,7 @@ fn replace_cursel_with_character(ed: *Editor, root: Buffer.Root, cursel: *CurSel var egc: []const u8 = undefined; if (!(ctx.args.match(.{tp.extract(&egc)}) catch return error.Stop)) return error.Stop; - const no_selection = try select_char_if_no_selection(cursel, root, ed.metrics); + const no_selection = try select_char_if_no_selection(cursel); var begin: Cursor = undefined; var sel_length: usize = 1; if (cursel.selection) |*sel| { @@ -911,7 +911,7 @@ fn replace_cursel_with_character(ed: *Editor, root: Buffer.Root, cursel: *CurSel if (no_selection) { try cursel.cursor.move_left(root, ed.metrics); - cursel.disable_selection(root, ed.metrics); + cursel.disable_selection(); } else { cursel.selection = Selection{ .begin = begin, .end = cursel.cursor }; } @@ -1004,7 +1004,7 @@ fn move_cursor_till_char_right_beyond_eol(root: Buffer.Root, cursor: *Cursor, me fn add_cursors_to_cursel_line_ends_helix(ed: *Editor, root: Buffer.Root, cursel: *CurSel) !void { const original_cursel = cursel.*; - const sel = cursel.enable_selection(root, ed.metrics); + const sel = cursel.enable_selection(); sel.normalize(); var row = sel.begin.row; const is_multiline = sel.begin.row != sel.end.row; @@ -1059,7 +1059,7 @@ fn insert_before(editor: *Editor, root: Buffer.Root, cursel: *CurSel, text: []co var root_: Buffer.Root = root; const cursor: *Cursor = &cursel.cursor; - cursel.check_selection(root, editor.metrics); + cursel.check_selection(); if (cursel.selection) |sel_| { var sel = sel_; sel.normalize(); @@ -1085,7 +1085,7 @@ fn insert_replace_selection(editor: *Editor, root: Buffer.Root, cursel: *CurSel, // replaces the selection, if no selection, replaces the current // character and sets the selection to the replacement text var root_: Buffer.Root = root; - cursel.check_selection(root, editor.metrics); + cursel.check_selection(); if (cursel.selection == null) { // Select current character to replace it @@ -1105,7 +1105,7 @@ fn insert_replace_selection(editor: *Editor, root: Buffer.Root, cursel: *CurSel, fn insert_after(editor: *Editor, root: Buffer.Root, cursel: *CurSel, text: []const u8, allocator: Allocator) !Buffer.Root { var root_: Buffer.Root = root; const cursor = &cursel.cursor; - cursel.check_selection(root, editor.metrics); + cursel.check_selection(); if (text[text.len - 1] == '\n') { move_cursor_carriage_return(root, cursel.*, cursor, editor.metrics) catch {}; } else { @@ -1270,7 +1270,7 @@ fn move_cursor_carriage_return(root: Buffer.Root, cursel: CurSel, cursor: *Curso try Editor.move_cursor_right(root, cursor, metrics); } -fn select_char_if_no_selection(cursel: *CurSel, root: Buffer.Root, metrics: Buffer.Metrics) !bool { +fn select_char_if_no_selection(cursel: *CurSel) !bool { if (cursel.selection) |*sel_| { const sel: *Selection = sel_; if (sel.*.empty()) { @@ -1279,7 +1279,7 @@ fn select_char_if_no_selection(cursel: *CurSel, root: Buffer.Root, metrics: Buff } return false; } else { - const sel = cursel.enable_selection(root, metrics); + const sel = cursel.enable_selection(); sel.begin = .{ .row = cursel.cursor.row, .col = cursel.cursor.col + 1, .target = cursel.cursor.target + 1 }; return true; } diff --git a/src/tui/tui.zig b/src/tui/tui.zig index d73c2165..f3e5d821 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -1850,10 +1850,6 @@ pub fn is_cursor_beam() bool { }; } -pub fn get_selection_style() @import("Buffer").Selection.Style { - return if (current().input_mode_) |mode| mode.selection_style else .normal; -} - pub fn message(comptime fmt: anytype, args: anytype) void { var buf: [256]u8 = undefined; tp.self_pid().send(.{ "message", std.fmt.bufPrint(&buf, fmt, args) catch @panic("too large") }) catch {}; From 66f8819a19492a159ea9a856340100d54c09f6e4 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 18:03:53 +0100 Subject: [PATCH 13/25] refactor: move more mode specific commands to helix & vim --- src/keybind/builtin/vim.json | 12 +-- src/tui/editor.zig | 172 ++------------------------------ src/tui/mode/helix.zig | 183 ++++++++++++++++++++++------------- src/tui/mode/vim.zig | 169 +++++++++++++++++++++++++++----- 4 files changed, 274 insertions(+), 262 deletions(-) diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index 626cae60..73898455 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -75,8 +75,8 @@ ["yy", ["copy_line_internal_vim"], ["cancel"]], - ["", "move_scroll_half_page_up_vim"], - ["", "move_scroll_half_page_down_vim"], + ["", "move_scroll_half_page_up"], + ["", "move_scroll_half_page_down"], ["zz", "scroll_view_center"], @@ -149,8 +149,8 @@ ["p", ["paste_internal_vim"], ["enter_mode", "normal"]], ["P", ["paste_internal_vim"], ["enter_mode", "normal"]], - ["", "move_scroll_half_page_up_vim"], - ["", "move_scroll_half_page_down_vim"], + ["", "move_scroll_half_page_up"], + ["", "move_scroll_half_page_down"], ["zz", "scroll_view_center"], ["", "indent"], @@ -196,8 +196,8 @@ ["p", ["paste_internal_vim"], ["enter_mode", "normal"]], ["P", ["paste_internal_vim"], ["enter_mode", "normal"]], - ["", "move_scroll_half_page_up_vim"], - ["", "move_scroll_half_page_down_vim"], + ["", "move_scroll_half_page_up"], + ["", "move_scroll_half_page_down"], ["", "indent"], ["", "unindent"], diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 6f88b772..7a9e7ace 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -1628,7 +1628,7 @@ pub const Editor = struct { return row < sel.begin.row or (row == sel.begin.row and col < sel.begin.col); } - inline fn screen_cursor(self: *const Self, cursor: *const Cursor) ?Cursor { + pub inline fn screen_cursor(self: *const Self, cursor: *const Cursor) ?Cursor { return if (self.view.is_visible(cursor)) .{ .row = cursor.row - self.view.row, .col = cursor.col - self.view.col, @@ -1741,7 +1741,7 @@ pub const Editor = struct { try self.send_editor_cursel_msg("jump_source", self.get_primary()); } - fn send_editor_jump_destination(self: *Self) !void { + pub fn send_editor_jump_destination(self: *Self) !void { try self.send_editor_cursel_msg("jump_destination", self.get_primary()); } @@ -1959,7 +1959,7 @@ pub const Editor = struct { try move(root, &cursel.cursor, view, metrics); } - fn with_cursors_and_view_const(self: *Self, root: Buffer.Root, move: cursor_view_operator_const, view: *const View) error{Stop}!void { + pub fn with_cursors_and_view_const(self: *Self, root: Buffer.Root, move: cursor_view_operator_const, view: *const View) error{Stop}!void { var someone_stopped = false; for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| with_cursor_and_view_const(root, move, cursel, view, self.metrics) catch { @@ -2028,7 +2028,7 @@ pub const Editor = struct { cursel.cursor = sel.end; } - fn with_selections_and_view_const(self: *Self, root: Buffer.Root, move: cursor_view_operator_const, view: *const View) error{Stop}!void { + pub fn with_selections_and_view_const(self: *Self, root: Buffer.Root, move: cursor_view_operator_const, view: *const View) error{Stop}!void { var someone_stopped = false; for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| with_selection_and_view_const(root, move, cursel, view, self.metrics) catch { @@ -2346,7 +2346,7 @@ pub const Editor = struct { return false; } - fn is_eol_left(_: Buffer.Root, cursor: *const Cursor, _: Buffer.Metrics) bool { + pub fn is_eol_left(_: Buffer.Root, cursor: *const Cursor, _: Buffer.Metrics) bool { if (cursor.col == 0) return true; return false; @@ -2359,22 +2359,6 @@ pub const Editor = struct { return false; } - fn is_eol_right_vim(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { - const line_width = root.line_width(cursor.row, metrics) catch return true; - if (line_width == 0) return true; - if (cursor.col >= line_width - 1) - return true; - return false; - } - - fn is_eol_vim(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { - const line_width = root.line_width(cursor.row, metrics) catch return true; - if (line_width == 0) return true; - if (cursor.col >= line_width) - return true; - return false; - } - pub fn move_cursor_left(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { try cursor.move_left(root, metrics); } @@ -2384,7 +2368,7 @@ pub const Editor = struct { move_cursor_left(root, cursor, metrics) catch return; } - fn move_cursor_left_unless(root: Buffer.Root, cursor: *Cursor, pred: cursor_predicate, metrics: Buffer.Metrics) void { + pub fn move_cursor_left_unless(root: Buffer.Root, cursor: *Cursor, pred: cursor_predicate, metrics: Buffer.Metrics) void { if (!pred(root, cursor, metrics)) move_cursor_left(root, cursor, metrics) catch return; } @@ -2407,7 +2391,7 @@ pub const Editor = struct { move_cursor_right(root, cursor, metrics) catch return; } - fn move_cursor_right_unless(root: Buffer.Root, cursor: *Cursor, pred: cursor_predicate, metrics: Buffer.Metrics) void { + pub fn move_cursor_right_unless(root: Buffer.Root, cursor: *Cursor, pred: cursor_predicate, metrics: Buffer.Metrics) void { if (!pred(root, cursor, metrics)) move_cursor_right(root, cursor, metrics) catch return; } @@ -2416,32 +2400,18 @@ pub const Editor = struct { cursor.move_end(root, metrics); } - fn move_cursor_end_vim(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) !void { - move_cursor_right_until(root, cursor, is_eol_vim, metrics); - } - fn move_cursor_up(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) !void { cursor.move_up(root, metrics) catch |e| switch (e) { error.Stop => cursor.move_begin(), }; } - fn move_cursor_up_vim(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) !void { - try cursor.move_up(root, metrics); - if (is_eol_vim(root, cursor, metrics)) try move_cursor_left_vim(root, cursor, metrics); - } - pub fn move_cursor_down(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) !void { cursor.move_down(root, metrics) catch |e| switch (e) { error.Stop => cursor.move_end(root, metrics), }; } - fn move_cursor_down_vim(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) !void { - try cursor.move_down(root, metrics); - if (is_eol_vim(root, cursor, metrics)) try move_cursor_left_vim(root, cursor, metrics); - } - fn move_cursor_buffer_begin(_: Buffer.Root, cursor: *Cursor, _: Buffer.Metrics) !void { cursor.move_buffer_begin(); } @@ -2458,24 +2428,6 @@ pub const Editor = struct { cursor.move_page_down(root, view, metrics); } - fn move_cursor_half_page_up(root: Buffer.Root, cursor: *Cursor, view: *const View, metrics: Buffer.Metrics) !void { - cursor.move_half_page_up(root, view, metrics); - } - - fn move_cursor_half_page_up_vim(root: Buffer.Root, cursor: *Cursor, view: *const View, metrics: Buffer.Metrics) !void { - cursor.move_half_page_up(root, view, metrics); - if (is_eol_vim(root, cursor, metrics)) try move_cursor_left_vim(root, cursor, metrics); - } - - fn move_cursor_half_page_down(root: Buffer.Root, cursor: *Cursor, view: *const View, metrics: Buffer.Metrics) !void { - cursor.move_half_page_down(root, view, metrics); - } - - fn move_cursor_half_page_down_vim(root: Buffer.Root, cursor: *Cursor, view: *const View, metrics: Buffer.Metrics) !void { - cursor.move_half_page_down(root, view, metrics); - if (is_eol_vim(root, cursor, metrics)) try move_cursor_left_vim(root, cursor, metrics); - } - pub fn primary_click(self: *Self, y: c_int, x: c_int) !void { const root = self.buf_root() catch return; if (self.fast_scroll) { @@ -3190,14 +3142,6 @@ pub const Editor = struct { } pub const delete_line_meta: Meta = .{ .description = "Delete current line", .arguments = &.{.integer} }; - pub fn cut_to_end_vim(self: *Self, _: Context) Result { - const b = try self.buf_for_update(); - const root = try self.cut_to(move_cursor_end_vim, b.root); - try self.update_buf(root); - self.clamp(); - } - pub const cut_to_end_vim_meta: Meta = .{ .description = "Cut to end of line (vim)" }; - pub fn join_next_line(self: *Self, ctx: Context) Result { const b = try self.buf_for_update(); try self.with_cursors_const_repeat(b.root, move_cursor_end, ctx); @@ -3248,28 +3192,6 @@ pub const Editor = struct { } pub const move_right_meta: Meta = .{ .description = "Move cursor right", .arguments = &.{.integer} }; - fn move_cursor_left_vim(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { - move_cursor_left_unless(root, cursor, is_eol_left, metrics); - } - - fn move_cursor_right_vim(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { - move_cursor_right_unless(root, cursor, is_eol_right_vim, metrics); - } - - pub fn move_left_vim(self: *Self, ctx: Context) Result { - const root = try self.buf_root(); - self.with_cursors_const_repeat(root, move_cursor_left_vim, ctx) catch {}; - self.clamp(); - } - pub const move_left_vim_meta: Meta = .{ .description = "Move cursor left (vim)", .arguments = &.{.integer} }; - - pub fn move_right_vim(self: *Self, ctx: Context) Result { - const root = try self.buf_root(); - self.with_cursors_const_repeat(root, move_cursor_right_vim, ctx) catch {}; - self.clamp(); - } - pub const move_right_vim_meta: Meta = .{ .description = "Move cursor right (vim)", .arguments = &.{.integer} }; - fn move_cursor_word_begin(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { if (is_non_word_char_at_cursor(root, cursor, metrics)) { move_cursor_left_until(root, cursor, is_word_boundary_right, metrics); @@ -3556,13 +3478,6 @@ pub const Editor = struct { } pub const move_up_meta: Meta = .{ .description = "Move cursor up", .arguments = &.{.integer} }; - pub fn move_up_vim(self: *Self, ctx: Context) Result { - const root = try self.buf_root(); - self.with_cursors_const_repeat(root, move_cursor_up_vim, ctx) catch {}; - self.clamp(); - } - pub const move_up_vim_meta: Meta = .{ .description = "Move cursor up (vim)", .arguments = &.{.integer} }; - pub fn add_cursor_up(self: *Self, ctx: Context) Result { const root = try self.buf_root(); var repeat: usize = 1; @@ -3583,13 +3498,6 @@ pub const Editor = struct { } pub const move_down_meta: Meta = .{ .description = "Move cursor down", .arguments = &.{.integer} }; - pub fn move_down_vim(self: *Self, ctx: Context) Result { - const root = try self.buf_root(); - self.with_cursors_const_repeat(root, move_cursor_down_vim, ctx) catch {}; - self.clamp(); - } - pub const move_down_vim_meta: Meta = .{ .description = "Move cursor down (vim)", .arguments = &.{.integer} }; - pub fn add_cursor_down(self: *Self, ctx: Context) Result { var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; @@ -4027,54 +3935,6 @@ pub const Editor = struct { } pub const move_scroll_page_down_meta: Meta = .{ .description = "Move and scroll page down" }; - pub fn move_scroll_half_page_up(self: *Self, _: Context) Result { - if (self.screen_cursor(&self.get_primary().cursor)) |cursor| { - const root = try self.buf_root(); - self.with_cursors_and_view_const(root, move_cursor_half_page_up, &self.view) catch {}; - const new_cursor_row = self.get_primary().cursor.row; - self.update_scroll_dest_abs(if (cursor.row > new_cursor_row) 0 else new_cursor_row - cursor.row); - } else { - return self.move_half_page_up(.{}); - } - } - pub const move_scroll_half_page_up_meta: Meta = .{ .description = "Move and scroll half a page up" }; - - pub fn move_scroll_half_page_up_vim(self: *Self, _: Context) Result { - if (self.screen_cursor(&self.get_primary().cursor)) |cursor| { - const root = try self.buf_root(); - self.with_cursors_and_view_const(root, move_cursor_half_page_up_vim, &self.view) catch {}; - const new_cursor_row = self.get_primary().cursor.row; - self.update_scroll_dest_abs(if (cursor.row > new_cursor_row) 0 else new_cursor_row - cursor.row); - } else { - return self.move_half_page_up(.{}); - } - } - pub const move_scroll_half_page_up_vim_meta: Meta = .{ .description = "Move and scroll half a page up (vim)" }; - - pub fn move_scroll_half_page_down(self: *Self, _: Context) Result { - if (self.screen_cursor(&self.get_primary().cursor)) |cursor| { - const root = try self.buf_root(); - self.with_cursors_and_view_const(root, move_cursor_half_page_down, &self.view) catch {}; - const new_cursor_row = self.get_primary().cursor.row; - self.update_scroll_dest_abs(if (cursor.row > new_cursor_row) 0 else new_cursor_row - cursor.row); - } else { - return self.move_half_page_down(.{}); - } - } - pub const move_scroll_half_page_down_meta: Meta = .{ .description = "Move and scroll half a page down" }; - - pub fn move_scroll_half_page_down_vim(self: *Self, _: Context) Result { - if (self.screen_cursor(&self.get_primary().cursor)) |cursor| { - const root = try self.buf_root(); - self.with_cursors_and_view_const(root, move_cursor_half_page_down_vim, &self.view) catch {}; - const new_cursor_row = self.get_primary().cursor.row; - self.update_scroll_dest_abs(if (cursor.row > new_cursor_row) 0 else new_cursor_row - cursor.row); - } else { - return self.move_half_page_down(.{}); - } - } - pub const move_scroll_half_page_down_vim_meta: Meta = .{ .description = "Move and scroll half a page down (vim)" }; - pub fn smart_move_begin(self: *Self, _: Context) Result { const root = try self.buf_root(); try self.with_cursors_const_once(root, smart_move_cursor_begin); @@ -4363,24 +4223,6 @@ pub const Editor = struct { } pub const select_page_down_meta: Meta = .{ .description = "Select page down" }; - pub fn select_half_page_up(self: *Self, _: Context) Result { - try self.send_editor_jump_source(); - const root = try self.buf_root(); - try self.with_selections_and_view_const(root, move_cursor_half_page_up, &self.view); - self.clamp(); - try self.send_editor_jump_destination(); - } - pub const select_half_page_up_meta: Meta = .{ .description = "Select half a page up" }; - - pub fn select_half_page_down(self: *Self, _: Context) Result { - try self.send_editor_jump_source(); - const root = try self.buf_root(); - try self.with_selections_and_view_const(root, move_cursor_half_page_down, &self.view); - self.clamp(); - try self.send_editor_jump_destination(); - } - pub const select_half_page_down_meta: Meta = .{ .description = "Select half a page down" }; - pub fn select_all(self: *Self, _: Context) Result { try self.send_editor_jump_source(); self.cancel_all_selections(); diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 248f2ba3..849700df 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -13,16 +13,19 @@ const CurSel = @import("../editor.zig").CurSel; const Buffer = @import("Buffer"); const Cursor = Buffer.Cursor; const Selection = Buffer.Selection; - +const View = Buffer.View; const char_class = Editor.char_class; const Direction = enum { backwards, forwards }; var commands: Commands = undefined; +const Self = Editor; + pub fn init() !void { - var v: void = {}; - try commands.init(&v); + const mv = tui.mainview() orelse return; + const ed = mv.get_active_editor() orelse return; + try commands.init(ed); } pub fn deinit() void { @@ -30,60 +33,60 @@ pub fn deinit() void { } const Commands = command.Collection(cmds_); -const Ctx = command.Context; +const Context = command.Context; const Meta = command.Metadata; const Result = command.Result; const cmds_ = struct { - pub const Target = void; + pub const Target = Self; - pub fn w(_: *void, _: Ctx) Result { + pub fn w(_: *Self, _: Context) Result { try cmd("save_file", .{}); } pub const w_meta: Meta = .{ .description = "w (write/save file)" }; - pub fn q(_: *void, _: Ctx) Result { + pub fn q(_: *Self, _: Context) Result { try cmd("quit", .{}); } pub const q_meta: Meta = .{ .description = "q (quit)" }; - pub fn qa(_: *void, _: Ctx) Result { + pub fn qa(_: *Self, _: Context) Result { try cmd("quit", .{}); } pub const qa_meta: Meta = .{ .description = "qa (close all)" }; - pub fn @"q!"(_: *void, _: Ctx) Result { + pub fn @"q!"(_: *Self, _: Context) Result { try cmd("quit_without_saving", .{}); } pub const @"q!_meta": Meta = .{ .description = "q! (quit without saving)" }; - pub fn @"qa!"(_: *void, _: Ctx) Result { + pub fn @"qa!"(_: *Self, _: Context) Result { try cmd("quit_without_saving", .{}); } pub const @"qa!_meta": Meta = .{ .description = "qa! (quit without saving)" }; - pub fn wq(_: *void, _: Ctx) Result { + pub fn wq(_: *Self, _: Context) Result { try cmd("save_file", command.fmt(.{ "then", .{ "quit", .{} } })); } pub const wq_meta: Meta = .{ .description = "wq (write/save file and quit)" }; - pub fn @"x!"(_: *void, _: Ctx) Result { + pub fn @"x!"(_: *Self, _: Context) Result { try cmd("save_file", command.fmt(.{ "then", .{ "quit_without_saving", .{} } })); } pub const @"x!_meta": Meta = .{ .description = "x! (write/save file and exit, ignoring other unsaved changes)" }; - pub fn x(_: *void, _: Ctx) Result { + pub fn x(_: *Self, _: Context) Result { try cmd("save_file", command.fmt(.{ "then", .{ "quit", .{} } })); } pub const x_meta: Meta = .{ .description = "x (write/save file and quit)" }; - pub fn wa(_: *void, _: Ctx) Result { + pub fn wa(_: *Self, _: Context) Result { if (tui.get_buffer_manager()) |bm| bm.save_all() catch |e| return tp.exit_error(e, @errorReturnTrace()); } pub const wa_meta: Meta = .{ .description = "wa (save all)" }; - pub fn xa(_: *void, _: Ctx) Result { + pub fn xa(_: *Self, _: Context) Result { if (tui.get_buffer_manager()) |bm| { bm.save_all() catch |e| return tp.exit_error(e, @errorReturnTrace()); try cmd("quit", .{}); @@ -91,7 +94,7 @@ const cmds_ = struct { } pub const xa_meta: Meta = .{ .description = "xa (write all and quit)" }; - pub fn @"xa!"(_: *void, _: Ctx) Result { + pub fn @"xa!"(_: *Self, _: Context) Result { if (tui.get_buffer_manager()) |bm| { bm.save_all() catch {}; try cmd("quit_without_saving", .{}); @@ -99,14 +102,14 @@ const cmds_ = struct { } pub const @"xa!_meta": Meta = .{ .description = "xa! (write all and exit, ignoring other unsaved changes)" }; - pub fn wqa(_: *void, _: Ctx) Result { + pub fn wqa(_: *Self, _: Context) Result { if (tui.get_buffer_manager()) |bm| bm.save_all() catch |e| return tp.exit_error(e, @errorReturnTrace()); try cmd("quit", .{}); } pub const wqa_meta: Meta = .{ .description = "wqa (write all and quit)" }; - pub fn @"wqa!"(_: *void, _: Ctx) Result { + pub fn @"wqa!"(_: *Self, _: Context) Result { if (tui.get_buffer_manager()) |bm| { bm.save_all() catch {}; try cmd("quit_without_saving", .{}); @@ -114,54 +117,54 @@ const cmds_ = struct { } pub const @"wqa!_meta": Meta = .{ .description = "wqa! (write all and exit, ignoring unsaved changes)" }; - pub fn rl(_: *void, _: Ctx) Result { + pub fn rl(_: *Self, _: Context) Result { try cmd("reload_file", .{}); } pub const rl_meta: Meta = .{ .description = "rl (reload current file)" }; - pub fn rla(_: *void, _: Ctx) Result { + pub fn rla(_: *Self, _: Context) Result { if (tui.get_buffer_manager()) |bm| bm.reload_all() catch |e| return tp.exit_error(e, @errorReturnTrace()); } pub const rla_meta: Meta = .{ .description = "rla (reload all files)" }; - pub fn o(_: *void, _: Ctx) Result { + pub fn o(_: *Self, _: Context) Result { try cmd("open_file", .{}); } pub const o_meta: Meta = .{ .description = "o (open file)" }; - pub fn @"wq!"(_: *void, _: Ctx) Result { + pub fn @"wq!"(_: *Self, _: Context) Result { cmd("save_file", .{}) catch {}; try cmd("quit_without_saving", .{}); } pub const @"wq!_meta": Meta = .{ .description = "wq! (write/save file and quit without saving)" }; - pub fn n(_: *void, _: Ctx) Result { + pub fn n(_: *Self, _: Context) Result { try cmd("create_new_file", .{}); } pub const n_meta: Meta = .{ .description = "n (Create new buffer/tab)" }; - pub fn bn(_: *void, _: Ctx) Result { + pub fn bn(_: *Self, _: Context) Result { try cmd("next_tab", .{}); } pub const bn_meta: Meta = .{ .description = "bn (Next buffer/tab)" }; - pub fn bp(_: *void, _: Ctx) Result { + pub fn bp(_: *Self, _: Context) Result { try cmd("previous_tab", .{}); } pub const bp_meta: Meta = .{ .description = "bp (Previous buffer/tab)" }; - pub fn bc(_: *void, _: Ctx) Result { + pub fn bc(_: *Self, _: Context) Result { try cmd("delete_buffer", .{}); } pub const bc_meta: Meta = .{ .description = "bc (Close buffer/tab)" }; - pub fn @"bc!"(_: *void, _: Ctx) Result { + pub fn @"bc!"(_: *Self, _: Context) Result { try cmd("close_file_without_saving", .{}); } pub const @"bc!_meta": Meta = .{ .description = "bc! (Close buffer/tab, ignoring changes)" }; - pub fn @"bco!"(_: *void, _: Ctx) Result { + pub fn @"bco!"(_: *Self, _: Context) Result { const mv = tui.mainview() orelse return; if (tui.get_buffer_manager()) |bm| { if (mv.get_active_buffer()) |buffer| try bm.delete_others(buffer); @@ -169,7 +172,7 @@ const cmds_ = struct { } pub const @"bco!_meta": Meta = .{ .description = "bco! (Close other buffers/tabs, discarding changes)" }; - pub fn bco(_: *void, _: Ctx) Result { + pub fn bco(_: *Self, _: Context) Result { const logger = log.logger("helix-mode"); defer logger.deinit(); const mv = tui.mainview() orelse return; @@ -184,7 +187,49 @@ const cmds_ = struct { } pub const bco_meta: Meta = .{ .description = "bco (Close other buffers/tabs)" }; - pub fn save_selection(_: *void, _: Ctx) Result { + pub fn move_scroll_half_page_up(self: *Self, _: Context) Result { + if (self.screen_cursor(&self.get_primary().cursor)) |cursor| { + const root = try self.buf_root(); + self.with_cursors_and_view_const(root, move_cursor_half_page_up, &self.view) catch {}; + const new_cursor_row = self.get_primary().cursor.row; + self.update_scroll_dest_abs(if (cursor.row > new_cursor_row) 0 else new_cursor_row - cursor.row); + } else { + return self.move_half_page_up(.{}); + } + } + pub const move_scroll_half_page_up_meta: Meta = .{ .description = "Move and scroll half a page up" }; + + pub fn move_scroll_half_page_down(self: *Self, _: Context) Result { + if (self.screen_cursor(&self.get_primary().cursor)) |cursor| { + const root = try self.buf_root(); + self.with_cursors_and_view_const(root, move_cursor_half_page_down, &self.view) catch {}; + const new_cursor_row = self.get_primary().cursor.row; + self.update_scroll_dest_abs(if (cursor.row > new_cursor_row) 0 else new_cursor_row - cursor.row); + } else { + return self.move_half_page_down(.{}); + } + } + pub const move_scroll_half_page_down_meta: Meta = .{ .description = "Move and scroll half a page down" }; + + pub fn select_half_page_up(self: *Self, _: Context) Result { + try self.send_editor_jump_source(); + const root = try self.buf_root(); + try self.with_selections_and_view_const(root, move_cursor_half_page_up, &self.view); + self.clamp(); + try self.send_editor_jump_destination(); + } + pub const select_half_page_up_meta: Meta = .{ .description = "Select half a page up" }; + + pub fn select_half_page_down(self: *Self, _: Context) Result { + try self.send_editor_jump_source(); + const root = try self.buf_root(); + try self.with_selections_and_view_const(root, move_cursor_half_page_down, &self.view); + self.clamp(); + try self.send_editor_jump_destination(); + } + pub const select_half_page_down_meta: Meta = .{ .description = "Select half a page down" }; + + pub fn save_selection(_: *Self, _: Context) Result { const logger = log.logger("helix-mode"); defer logger.deinit(); logger.print("saved location", .{}); @@ -202,7 +247,7 @@ const cmds_ = struct { } pub const save_selection_meta: Meta = .{ .description = "Save current selection to location history" }; - pub fn split_selection_on_newline(_: *void, _: Ctx) Result { + pub fn split_selection_on_newline(_: *Self, _: Context) Result { const ed, const root = get_buf() orelse return; const cursels = try ed.cursels.toOwnedSlice(ed.allocator); defer ed.allocator.free(cursels); @@ -213,14 +258,14 @@ const cmds_ = struct { } pub const split_selection_on_newline_meta: Meta = .{ .description = "Add cursor to each line in selection helix" }; - pub fn match_brackets(_: *void, ctx: Ctx) Result { + pub fn match_brackets(_: *Self, ctx: Context) Result { const ed, const root = get_buf() orelse return; try ed.with_cursels_const_once_arg(root, &match_bracket, ctx); ed.clamp(); } pub const match_brackets_meta: Meta = .{ .description = "Goto matching bracket" }; - pub fn extend_line_below(_: *void, ctx: Ctx) Result { + pub fn extend_line_below(_: *Self, ctx: Context) Result { const ed, const root = get_buf() orelse return; var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; @@ -240,73 +285,73 @@ const cmds_ = struct { } pub const extend_line_below_meta: Meta = .{ .arguments = &.{.integer}, .description = "Select current line, if already selected, extend to next line" }; - pub fn init_helix_select_mode(_: *void, _: Ctx) Result { + pub fn init_helix_select_mode(_: *Self, _: Context) Result { _, const ed = get_context() orelse return; try ed.enable_selection(.{}); } pub const init_helix_select_mode_meta: Meta = .{}; - pub fn move_next_word_start(_: *void, ctx: Ctx) Result { + pub fn move_next_word_start(_: *Self, ctx: Context) Result { try move_to_word(ctx, Editor.move_cursor_word_right_vim, .forwards); } pub const move_next_word_start_meta: Meta = .{ .description = "Move next word start", .arguments = &.{.integer} }; - pub fn extend_next_word_start(_: *void, ctx: Ctx) Result { + pub fn extend_next_word_start(_: *Self, ctx: Context) Result { try extend_to_word(ctx, Editor.move_cursor_word_right_vim, .forwards); } pub const extend_next_word_start_meta: Meta = .{ .description = "Extend next word start", .arguments = &.{.integer} }; - pub fn move_next_long_word_start(_: *void, ctx: Ctx) Result { + pub fn move_next_long_word_start(_: *Self, ctx: Context) Result { try move_to_word(ctx, move_cursor_long_word_right, .forwards); } pub const move_next_long_word_start_meta: Meta = .{ .description = "Move next long word start", .arguments = &.{.integer} }; - pub fn extend_next_long_word_start(_: *void, ctx: Ctx) Result { + pub fn extend_next_long_word_start(_: *Self, ctx: Context) Result { try extend_to_word(ctx, move_cursor_long_word_right, .forwards); } pub const extend_next_long_word_start_meta: Meta = .{ .description = "Extend next long word start", .arguments = &.{.integer} }; - pub fn move_prev_word_start(_: *void, ctx: Ctx) Result { + pub fn move_prev_word_start(_: *Self, ctx: Context) Result { 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 { + pub fn extend_prev_word_start(_: *Self, ctx: Context) Result { try move_cursels_const_repeat(move_cursor_prev_word_start_extend, ctx); } pub const extend_prev_word_start_meta: Meta = .{ .description = "Extend previous word start", .arguments = &.{.integer} }; - pub fn move_prev_long_word_start(_: *void, ctx: Ctx) Result { + pub fn move_prev_long_word_start(_: *Self, ctx: Context) Result { try move_to_word(ctx, move_cursor_long_word_left, .backwards); } pub const move_prev_long_word_start_meta: Meta = .{ .description = "Move previous long word start", .arguments = &.{.integer} }; - pub fn extend_prev_long_word_start(_: *void, ctx: Ctx) Result { + pub fn extend_prev_long_word_start(_: *Self, ctx: Context) Result { try extend_to_word(ctx, move_cursor_long_word_left, .backwards); } pub const extend_prev_long_word_start_meta: Meta = .{ .description = "Extend previous word start", .arguments = &.{.integer} }; - pub fn move_next_word_end(_: *void, ctx: Ctx) Result { + pub fn move_next_word_end(_: *Self, ctx: Context) Result { try move_to_word(ctx, move_cursor_word_right_end_helix, .forwards); } pub const move_next_word_end_meta: Meta = .{ .description = "Move next word end", .arguments = &.{.integer} }; - pub fn extend_next_word_end(_: *void, ctx: Ctx) Result { + pub fn extend_next_word_end(_: *Self, ctx: Context) Result { try extend_to_word(ctx, move_cursor_word_right_end_helix, .forwards); } pub const extend_next_word_end_meta: Meta = .{ .description = "Extend next word end", .arguments = &.{.integer} }; - pub fn move_next_long_word_end(_: *void, ctx: Ctx) Result { + pub fn move_next_long_word_end(_: *Self, ctx: Context) Result { try move_to_word(ctx, move_cursor_long_word_right_end, .forwards); } pub const move_next_long_word_end_meta: Meta = .{ .description = "Move next long word end", .arguments = &.{.integer} }; - pub fn extend_next_long_word_end(_: *void, ctx: Ctx) Result { + pub fn extend_next_long_word_end(_: *Self, ctx: Context) Result { try extend_to_word(ctx, move_cursor_long_word_right_end, .forwards); } pub const extend_next_long_word_end_meta: Meta = .{ .description = "Extend next long word end", .arguments = &.{.integer} }; - pub fn cut_forward_internal_inclusive(_: *void, _: Ctx) Result { + pub fn cut_forward_internal_inclusive(_: *Self, _: Context) Result { const ed, const b = get_buf_for_update() orelse return; tui.clipboard_start_group(); const root = try ed.cut_to(move_noop, b.root); @@ -315,7 +360,7 @@ const cmds_ = struct { } pub const cut_forward_internal_inclusive_meta: Meta = .{ .description = "Cut next character to internal clipboard (inclusive)" }; - pub fn select_right_helix(_: *void, ctx: Ctx) Result { + pub fn select_right_helix(_: *Self, ctx: Context) Result { const ed, const root = get_buf() orelse return; var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; @@ -340,7 +385,7 @@ const cmds_ = struct { } pub const select_right_helix_meta: Meta = .{ .description = "Select right", .arguments = &.{.integer} }; - pub fn select_left_helix(_: *void, ctx: Ctx) Result { + pub fn select_left_helix(_: *Self, ctx: Context) Result { const ed, const root = get_buf() orelse return; var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; @@ -368,47 +413,47 @@ 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 { + pub fn select_to_char_left_helix(_: *Self, ctx: Context) 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 { + pub fn select_till_char_left_helix(_: *Self, ctx: Context) 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 { + pub fn extend_to_char_left_helix(_: *Self, ctx: Context) 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 { + pub fn extend_till_char_left_helix(_: *Self, ctx: Context) 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 { + pub fn select_till_char_right_helix(_: *Self, ctx: Context) 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 { + pub fn select_to_char_right_helix(_: *Self, ctx: Context) Result { 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 { + pub fn extend_till_char_right_helix(_: *Self, ctx: Context) 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 { + pub fn extend_to_char_right_helix(_: *Self, ctx: Context) 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 select_textobject_inner(_: *void, ctx: Ctx) Result { + pub fn select_textobject_inner(_: *Self, ctx: Context) Result { const ed, const root = get_buf() orelse return error.Stop; var action: []const u8 = ""; @@ -425,7 +470,7 @@ const cmds_ = struct { } pub const select_textobject_inner_meta: Meta = .{ .description = "select inside object helix" }; - pub fn select_textobject_around(_: *void, ctx: Ctx) Result { + pub fn select_textobject_around(_: *Self, ctx: Context) Result { const ed, const root = get_buf() orelse return; var action: []const u8 = ""; @@ -442,7 +487,7 @@ const cmds_ = struct { } pub const select_textobject_around_meta: Meta = .{ .description = "select around object helix" }; - pub fn copy_helix(_: *void, _: Ctx) Result { + pub fn copy_helix(_: *Self, _: Context) Result { const ed, const root = get_buf() orelse return; tui.clipboard_start_group(); @@ -453,22 +498,22 @@ const cmds_ = struct { } pub const copy_helix_meta: Meta = .{ .description = "Copy selection to clipboard (helix)" }; - pub fn paste_after(_: *void, ctx: Ctx) Result { + pub fn paste_after(_: *Self, ctx: Context) Result { try paste_helix(ctx, insert_after); } pub const paste_after_meta: Meta = .{ .description = "Paste from clipboard after selection" }; - pub fn replace_selections_with_clipboard(_: *void, ctx: Ctx) Result { + pub fn replace_selections_with_clipboard(_: *Self, ctx: Context) Result { try paste_helix(ctx, insert_replace_selection); } pub const replace_selections_with_clipboard_meta: Meta = .{ .description = "Replace selection from clipboard" }; - pub fn paste_clipboard_before(_: *void, ctx: Ctx) Result { + pub fn paste_clipboard_before(_: *Self, ctx: Context) Result { try paste_helix(ctx, insert_before); } pub const paste_clipboard_before_meta: Meta = .{ .description = "Paste from clipboard before selection" }; - pub fn replace_with_character_helix(_: *void, ctx: Ctx) Result { + pub fn replace_with_character_helix(_: *Self, ctx: Context) Result { const ed, const b = get_buf_for_update() orelse return; var root = b.root; root = try ed.with_cursels_mut_once_arg(root, replace_cursel_with_character, ed.allocator, ctx); @@ -554,7 +599,7 @@ fn move_cursor_prev_word_start_extend(root: Buffer.Root, cursel: *CurSel, metric try move_cursor_prev_word_start(root, cursel, metrics); } -fn move_cursels_const_repeat(move: Editor.cursel_operator_const, ctx: Ctx) Result { +fn move_cursels_const_repeat(move: Editor.cursel_operator_const, ctx: Context) Result { const ed, const root = get_buf() orelse return; try ed.with_cursels_const_repeat(root, move, ctx); ed.clamp(); @@ -1294,6 +1339,14 @@ fn is_cursel_from_extend_line_below(cursel: CurSel) bool { return false; } +fn move_cursor_half_page_up(root: Buffer.Root, cursor: *Cursor, view: *const View, metrics: Buffer.Metrics) !void { + cursor.move_half_page_up(root, view, metrics); +} + +fn move_cursor_half_page_down(root: Buffer.Root, cursor: *Cursor, view: *const View, metrics: Buffer.Metrics) !void { + cursor.move_half_page_down(root, view, metrics); +} + const private = @This(); // exports for unittests pub const test_internal = struct { diff --git a/src/tui/mode/vim.zig b/src/tui/mode/vim.zig index 38eacbee..d59b858c 100644 --- a/src/tui/mode/vim.zig +++ b/src/tui/mode/vim.zig @@ -2,11 +2,19 @@ const std = @import("std"); const command = @import("command"); const cmd = command.executeName; +const tui = @import("../tui.zig"); +const Editor = @import("../editor.zig").Editor; +const Buffer = @import("Buffer"); +const Cursor = Buffer.Cursor; +const View = Buffer.View; + var commands: Commands = undefined; +const Self = Editor; pub fn init() !void { - var v: void = {}; - try commands.init(&v); + const mv = tui.mainview() orelse return; + const ed = mv.get_active_editor() orelse return; + try commands.init(ed); } pub fn deinit() void { @@ -14,74 +22,75 @@ pub fn deinit() void { } const Commands = command.Collection(cmds_); -const cmds_ = struct { - pub const Target = void; - const Ctx = command.Context; - const Meta = command.Metadata; - const Result = command.Result; +const Context = command.Context; +const Meta = command.Metadata; +const Result = command.Result; - pub fn w(_: *void, _: Ctx) Result { +const cmds_ = struct { + pub const Target = Self; + + pub fn w(_: *Self, _: Context) Result { try cmd("save_file", .{}); } pub const w_meta: Meta = .{ .description = "w (write file)" }; - pub fn q(_: *void, _: Ctx) Result { + pub fn q(_: *Self, _: Context) Result { try cmd("quit", .{}); } pub const q_meta: Meta = .{ .description = "q (quit)" }; - pub fn @"q!"(_: *void, _: Ctx) Result { + pub fn @"q!"(_: *Self, _: Context) Result { try cmd("quit_without_saving", .{}); } pub const @"q!_meta": Meta = .{ .description = "q! (quit without saving)" }; - pub fn @"qa!"(_: *void, _: Ctx) Result { + pub fn @"qa!"(_: *Self, _: Context) Result { try cmd("quit_without_saving", .{}); } pub const @"qa!_meta": Meta = .{ .description = "qa! (quit without saving anything)" }; - pub fn wq(_: *void, _: Ctx) Result { + pub fn wq(_: *Self, _: Context) Result { try cmd("save_file", command.fmt(.{ "then", .{ "quit", .{} } })); } pub const wq_meta: Meta = .{ .description = "wq (write file and quit)" }; - pub fn @"wq!"(_: *void, _: Ctx) Result { + pub fn @"wq!"(_: *Self, _: Context) Result { cmd("save_file", .{}) catch {}; try cmd("quit_without_saving", .{}); } pub const @"wq!_meta": Meta = .{ .description = "wq! (write file and quit without saving)" }; - pub fn @"e!"(_: *void, _: Ctx) Result { + pub fn @"e!"(_: *Self, _: Context) Result { try cmd("reload_file", .{}); } pub const @"e!_meta": Meta = .{ .description = "e! (force reload current file)" }; - pub fn bd(_: *void, _: Ctx) Result { + pub fn bd(_: *Self, _: Context) Result { try cmd("close_file", .{}); } pub const bd_meta: Meta = .{ .description = "bd (Close file)" }; - pub fn bw(_: *void, _: Ctx) Result { + pub fn bw(_: *Self, _: Context) Result { try cmd("delete_buffer", .{}); } pub const bw_meta: Meta = .{ .description = "bw (Delete buffer)" }; - pub fn bnext(_: *void, _: Ctx) Result { + pub fn bnext(_: *Self, _: Context) Result { try cmd("next_tab", .{}); } pub const bnext_meta: Meta = .{ .description = "bnext (Next buffer/tab)" }; - pub fn bprevious(_: *void, _: Ctx) Result { + pub fn bprevious(_: *Self, _: Context) Result { try cmd("next_tab", .{}); } pub const bprevious_meta: Meta = .{ .description = "bprevious (Previous buffer/tab)" }; - pub fn ls(_: *void, _: Ctx) Result { + pub fn ls(_: *Self, _: Context) Result { try cmd("switch_buffers", .{}); } pub const ls_meta: Meta = .{ .description = "ls (List/switch buffers)" }; - pub fn move_begin_or_add_integer_argument_zero(_: *void, _: Ctx) Result { + pub fn move_begin_or_add_integer_argument_zero(_: *Self, _: Context) Result { return if (@import("keybind").current_integer_argument()) |_| command.executeName("add_integer_argument_digit", command.fmt(.{0})) else @@ -89,7 +98,7 @@ const cmds_ = struct { } pub const move_begin_or_add_integer_argument_zero_meta: Meta = .{ .description = "Move cursor to beginning of line (vim)" }; - pub fn enter_mode_at_next_char(self: *void, ctx: Ctx) Result { + pub fn enter_mode_at_next_char(self: *Self, ctx: Context) Result { _ = self; // autofix _ = ctx; // autofix //TODO @@ -98,7 +107,7 @@ const cmds_ = struct { pub const enter_mode_at_next_char_meta: Meta = .{ .description = "Move forward one char and change mode" }; - pub fn enter_mode_on_newline_down(self: *void, ctx: Ctx) Result { + pub fn enter_mode_on_newline_down(self: *Self, ctx: Context) Result { _ = self; // autofix _ = ctx; // autofix //TODO @@ -107,7 +116,7 @@ const cmds_ = struct { pub const enter_mode_on_newline_down_meta: Meta = .{ .description = "Insert a newline and change mode" }; - pub fn enter_mode_on_newline_up(self: *void, ctx: Ctx) Result { + pub fn enter_mode_on_newline_up(self: *Self, ctx: Context) Result { _ = self; // autofix _ = ctx; // autofix //TODO @@ -115,7 +124,7 @@ const cmds_ = struct { } pub const enter_mode_on_newline_up_meta: Meta = .{ .description = "Insert a newline above the current line and change mode" }; - pub fn enter_mode_at_line_begin(self: *void, ctx: Ctx) Result { + pub fn enter_mode_at_line_begin(self: *Self, ctx: Context) Result { _ = self; // autofix _ = ctx; // autofix //TODO @@ -124,7 +133,7 @@ const cmds_ = struct { pub const enter_mode_at_line_begin_meta: Meta = .{ .description = "Goto line begin and change mode" }; - pub fn enter_mode_at_line_end(self: *void, ctx: Ctx) Result { + pub fn enter_mode_at_line_end(self: *Self, ctx: Context) Result { _ = self; // autofix _ = ctx; // autofix //TODO @@ -132,7 +141,7 @@ const cmds_ = struct { } pub const enter_mode_at_line_end_meta: Meta = .{ .description = "Goto line end and change mode" }; - pub fn copy_line(self: *void, ctx: Ctx) Result { + pub fn copy_line(self: *Self, ctx: Context) Result { _ = self; // autofix _ = ctx; // autofix //TODO @@ -140,4 +149,112 @@ const cmds_ = struct { } pub const copy_line_meta: Meta = .{ .description = "Copies the current line" }; + + pub fn move_scroll_half_page_up(self: *Self, _: Context) Result { + if (self.screen_cursor(&self.get_primary().cursor)) |cursor| { + const root = try self.buf_root(); + self.with_cursors_and_view_const(root, move_cursor_half_page_up, &self.view) catch {}; + const new_cursor_row = self.get_primary().cursor.row; + self.update_scroll_dest_abs(if (cursor.row > new_cursor_row) 0 else new_cursor_row - cursor.row); + } else { + return self.move_half_page_up(.{}); + } + } + pub const move_scroll_half_page_up_meta: Meta = .{ .description = "Move and scroll half a page up" }; + + pub fn move_scroll_half_page_down(self: *Self, _: Context) Result { + if (self.screen_cursor(&self.get_primary().cursor)) |cursor| { + const root = try self.buf_root(); + self.with_cursors_and_view_const(root, move_cursor_half_page_down, &self.view) catch {}; + const new_cursor_row = self.get_primary().cursor.row; + self.update_scroll_dest_abs(if (cursor.row > new_cursor_row) 0 else new_cursor_row - cursor.row); + } else { + return self.move_half_page_down(.{}); + } + } + pub const move_scroll_half_page_down_meta: Meta = .{ .description = "Move and scroll half a page down" }; + + pub fn move_left_vim(self: *Self, ctx: Context) Result { + const root = try self.buf_root(); + self.with_cursors_const_repeat(root, move_cursor_left_vim, ctx) catch {}; + self.clamp(); + } + pub const move_left_vim_meta: Meta = .{ .description = "Move cursor left (vim)", .arguments = &.{.integer} }; + + pub fn move_right_vim(self: *Self, ctx: Context) Result { + const root = try self.buf_root(); + self.with_cursors_const_repeat(root, move_cursor_right_vim, ctx) catch {}; + self.clamp(); + } + pub const move_right_vim_meta: Meta = .{ .description = "Move cursor right (vim)", .arguments = &.{.integer} }; + + pub fn cut_to_end_vim(self: *Self, _: Context) Result { + const b = try self.buf_for_update(); + const root = try self.cut_to(move_cursor_end_vim, b.root); + try self.update_buf(root); + self.clamp(); + } + pub const cut_to_end_vim_meta: Meta = .{ .description = "Cut to end of line (vim)" }; + + pub fn move_up_vim(self: *Self, ctx: Context) Result { + const root = try self.buf_root(); + self.with_cursors_const_repeat(root, move_cursor_up_vim, ctx) catch {}; + self.clamp(); + } + pub const move_up_vim_meta: Meta = .{ .description = "Move cursor up (vim)", .arguments = &.{.integer} }; + + pub fn move_down_vim(self: *Self, ctx: Context) Result { + const root = try self.buf_root(); + self.with_cursors_const_repeat(root, move_cursor_down_vim, ctx) catch {}; + self.clamp(); + } + pub const move_down_vim_meta: Meta = .{ .description = "Move cursor down (vim)", .arguments = &.{.integer} }; }; + +fn is_eol_right_vim(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { + const line_width = root.line_width(cursor.row, metrics) catch return true; + if (line_width == 0) return true; + if (cursor.col >= line_width - 1) + return true; + return false; +} + +fn is_eol_vim(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { + const line_width = root.line_width(cursor.row, metrics) catch return true; + if (line_width == 0) return true; + if (cursor.col >= line_width) + return true; + return false; +} + +fn move_cursor_half_page_up(root: Buffer.Root, cursor: *Cursor, view: *const View, metrics: Buffer.Metrics) !void { + cursor.move_half_page_up(root, view, metrics); + if (is_eol_vim(root, cursor, metrics)) try move_cursor_left_vim(root, cursor, metrics); +} + +fn move_cursor_half_page_down(root: Buffer.Root, cursor: *Cursor, view: *const View, metrics: Buffer.Metrics) !void { + cursor.move_half_page_down(root, view, metrics); + if (is_eol_vim(root, cursor, metrics)) try move_cursor_left_vim(root, cursor, metrics); +} + +fn move_cursor_up_vim(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) !void { + try cursor.move_up(root, metrics); + if (is_eol_vim(root, cursor, metrics)) try move_cursor_left_vim(root, cursor, metrics); +} + +fn move_cursor_down_vim(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) !void { + try cursor.move_down(root, metrics); + if (is_eol_vim(root, cursor, metrics)) try move_cursor_left_vim(root, cursor, metrics); +} + +fn move_cursor_end_vim(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) !void { + Editor.move_cursor_right_until(root, cursor, is_eol_vim, metrics); +} + +fn move_cursor_left_vim(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { + Editor.move_cursor_left_unless(root, cursor, Editor.is_eol_left, metrics); +} + +fn move_cursor_right_vim(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { + Editor.move_cursor_right_unless(root, cursor, is_eol_right_vim, metrics); +} From 5f242d6cf26acb2003a5f716c7516b2237f56be5 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 18:19:13 +0100 Subject: [PATCH 14/25] refactor: mark helix mode keybindings that are not implemented --- src/keybind/builtin/helix.json | 169 +++++++++++++++++---------------- 1 file changed, 85 insertions(+), 84 deletions(-) diff --git a/src/keybind/builtin/helix.json b/src/keybind/builtin/helix.json index 32d8c243..626e60ef 100644 --- a/src/keybind/builtin/helix.json +++ b/src/keybind/builtin/helix.json @@ -21,29 +21,29 @@ ["ctrl+i", "jump_forward"], ["ctrl+o", "jump_back"], ["ctrl+s", "save_selection"], - ["ctrl+a", "increment"], - ["ctrl+x", "decrement"], + ["ctrl+a", "increment-NOIMPL"], + ["ctrl+x", "decrement-NOIMPL"], ["ctrl+^", "open_previous_file"], ["ctrl+w v", "add_split"], ["ctrl+w c", "toggle_centered_view"], - ["ctrl+w h", "goto_left_split"], - ["ctrl+w l", "goto_right_split"], - ["ctrl+w H", "swap_left_split"], - ["ctrl+w L", "swap_right_split"], - ["ctrl+w F", "goto_file_split"], + ["ctrl+w h", "goto_left_split-NOIMPL"], + ["ctrl+w l", "goto_right_split-NOIMPL"], + ["ctrl+w H", "swap_left_split-NOIMPL"], + ["ctrl+w L", "swap_right_split-NOIMPL"], + ["ctrl+w F", "goto_file_split-NOIMPL"], ["ctrl+w q", "close_split"], - ["ctrl+w o", "close_other_splits"], + ["ctrl+w o", "close_other_splits-NOIMPL"], - ["alt+.", "repeat_last_motion"], + ["alt+.", "repeat_last_motion-NOIMPL"], ["alt+d", "delete_backward"], - ["alt+c", "change_backward_helix"], + ["alt+c", "change_backward_helix-NOIMPL"], ["alt+s", "split_selection_on_newline"], - ["alt+-", "merge_selections"], - ["alt+_", "merge_consecutive_selections"], + ["alt+-", "merge_selections-NOIMPL"], + ["alt+_", "merge_consecutive_selections-NOIMPL"], - ["alt+;", "flip_selections"], + ["alt+;", "flip_selections-NOIMPL"], ["alt+o", "expand_selection"], ["alt+up", "expand_selection"], ["alt+kp_up", "expand_selection"], @@ -57,22 +57,23 @@ ["alt+right", "select_next_sibling"], ["alt+kp_right", "select_next_sibling"], - ["alt+e", "move_parent_node_end"], - ["alt+b", "move_parent_node_start"], - ["alt+a", "select_all_siblings"], + ["alt+e", "move_parent_node_end-NOIMPL"], + ["alt+b", "move_parent_node_start-NOIMPL"], + ["alt+a", "select_all_siblings-NOIMPL"], - ["alt+x", "shrink_to_line_bounds"], + ["alt+x", "shrink_to_line_bounds-NOIMPL"], ["alt+u", "undo"], - ["alt+,", "remove_primary_selection"], + ["alt+,", "remove_primary_selection-NOIMPL"], - ["alt+C", "copy_selection_on_next_line"], - ["alt+I", "select_all_children"], - ["alt+shift+down", "select_all_children"], + ["alt+C", "copy_selection_on_next_line-NOIMPL"], + ["alt+I", "select_all_children-NOIMPL"], + ["alt+shift+down", "select_all_children-NOIMPL"], ["alt+U", "redo"], - ["alt+J", "join_selections_space"], - ["alt+(", "rotate_selection_contents_backward"], ["alt+)", "rotate_selection_contents_forward"], - ["alt+|", "shell_pipe_to"], - ["alt+!", "shell_append_output"], + ["alt+J", "join_selections_space-NOIMPL"], + ["alt+(", "rotate_selection_contents_backward-NOIMPL"], + ["alt+)", "rotate_selection_contents_forward-NOIMPL"], + ["alt+|", "shell_pipe_to-NOIMPL"], + ["alt+!", "shell_append_output-NOIMPL"], ["F", "move_to_char", "select_to_char_left_helix"], ["T", "move_to_char", "select_till_char_left_helix"], @@ -92,10 +93,10 @@ ["C", "add_cursor_down"], ["S", "split_selection"], - ["X", "extend_to_line_bounds"], - ["?", "rfind"], + ["X", "extend_to_line_bounds-NOIMPL"], + ["?", "rfind-NOIMPL"], ["N", "goto_prev_match"], - ["*", "search_selection"], + ["*", "search_selection-NOIMPL"], ["~", "switch_case"], ["`", "to_lower"], @@ -109,20 +110,20 @@ [">", "indent"], ["<", "unindent"], - ["J", "join_selections"], + ["J", "join_selections-NOIMPL"], [":", "open_command_palette"], - ["&", "align_selections"], - ["_", "trim_selections"], + ["&", "align_selections-NOIMPL"], + ["_", "trim_selections-NOIMPL"], - ["(", "rotate_selections_backward"], - [")", "rotate_selections_forward"], + ["(", "rotate_selections_backward-NOIMPL"], + [")", "rotate_selections_forward-NOIMPL"], - ["\"", "select_register"], - ["|", "shell_pipe"], - ["!", "shell_insert_output"], - ["$", "shell_keep_pipe"], + ["\"", "select_register-NOIMPL"], + ["|", "shell_pipe-NOIMPL"], + ["!", "shell_insert_output-NOIMPL"], + ["$", "shell_keep_pipe-NOIMPL"], ["h", "move_left"], ["j", "move_down"], @@ -429,9 +430,9 @@ ["kp_end", "extend_to_line_end"], ["v", "enter_mode", "normal"], - ["g g", "goto_line_vim"], + ["g g", "goto_line_helix"], ["g e", "move_buffer_end"], - ["g f", "goto_file"], + ["g f", "goto_file-NOIMPL"], ["g h", "move_begin"], ["g l", "select_end"], ["g s", "smart_move_begin"], @@ -439,16 +440,16 @@ ["g y", "goto_type_definition"], ["g r", "references"], ["g i", "goto_implementation"], - ["g t", "goto_window_top"], - ["g c", "goto_window_center"], - ["g b", "goto_window_bottom"], + ["g t", "goto_window_top-NOIMPL"], + ["g c", "goto_window_center-NOIMPL"], + ["g b", "goto_window_bottom-NOIMPL"], ["g a", "open_previous_file"], ["g m", "open_most_recent_file"], - ["g n", "goto_next_buffer"], - ["g p", "goto_previous_buffer"], - ["g k", "goto_previous_buffer"], - ["g .", "goto_last_modification"], - ["g w", "goto_word"], + ["g n", "goto_next_buffer-NOIMPL"], + ["g p", "goto_previous_buffer-NOIMPL"], + ["g k", "goto_previous_buffer-NOIMPL"], + ["g .", "goto_last_modification-NOIMPL"], + ["g w", "goto_word-NOIMPL"], ["g D", "goto_declaration"], ["i", "enter_mode", "insert"], @@ -458,8 +459,8 @@ ["d", ["cut"], ["enter_mode", "normal"]], ["c", ["enter_mode", "insert"], ["cut"]], - ["s", "select_regex"], - [";", "collapse_selections"], + ["s", "select_regex-NOIMPL"], + [";", "collapse_selections-NOIMPL"], ["x", "extend_line_below"], @@ -470,32 +471,32 @@ ["m r", "match", "surround_replace"], ["m s", "match", "surround_add"], - ["[ D", "goto_first_diag"], - ["[ G", "goto_first_change"], - ["[ T", "goto_prev_test"], + ["[ D", "goto_first_diag-NOIMPL"], + ["[ G", "goto_first_change-NOIMPL"], + ["[ T", "goto_prev_test-NOIMPL"], ["[ d", "goto_prev_diagnostic"], - ["[ g", "goto_prev_change"], - ["[ f", "goto_prev_function"], - ["[ t", "goto_prev_class"], - ["[ a", "goto_prev_parameter"], - ["[ c", "goto_prev_comment"], - ["[ e", "goto_prev_entry"], - ["[ p", "goto_prev_paragraph"], - ["[ space", "add_newline_above"], + ["[ g", "goto_prev_change-NOIMPL"], + ["[ f", "goto_prev_function-NOIMPL"], + ["[ t", "goto_prev_class-NOIMPL"], + ["[ a", "goto_prev_parameter-NOIMPL"], + ["[ c", "goto_prev_comment-NOIMPL"], + ["[ e", "goto_prev_entry-NOIMPL"], + ["[ p", "goto_prev_paragraph-NOIMPL"], + ["[ space", "add_newline_above-NOIMPL"], - ["] d", "goto_last_diag"], - ["] g", "goto_last_change"], - ["] t", "goto_next_test"], + ["] d", "goto_last_diag-NOIMPL"], + ["] g", "goto_last_change-NOIMPL"], + ["] t", "goto_next_test-NOIMPL"], ["] d", "goto_next_diagnostic"], - ["] g", "goto_next_change"], - ["] f", "goto_next_function"], - ["] t", "goto_next_class"], - ["] a", "goto_next_parameter"], - ["] c", "goto_next_comment"], - ["] e", "goto_next_entry"], - ["] p", "goto_next_paragraph"], - ["] space", "add_newline_below"], + ["] g", "goto_next_change-NOIMPL"], + ["] f", "goto_next_function-NOIMPL"], + ["] t", "goto_next_class-NOIMPL"], + ["] a", "goto_next_parameter-NOIMPL"], + ["] c", "goto_next_comment-NOIMPL"], + ["] e", "goto_next_entry-NOIMPL"], + ["] p", "goto_next_paragraph-NOIMPL"], + ["] space", "add_newline_below-NOIMPL"], ["/", "find"], ["n", "goto_next_match"], @@ -505,35 +506,35 @@ ["y", ["copy_helix"], ["enter_mode", "normal"]], - ["q", "record_macro"], - ["Q", "replay_macro"], + ["q", "record_macro-NOIMPL"], + ["Q", "replay_macro-NOIMPL"], - ["=", "format_selections"], + ["=", "format_selections-NOIMPL"], - [",", "keep_primary_selection"], + [",", "keep_primary_selection-NOIMPL"], ["escape", "enter_mode", "normal"], - ["space F", "file_picker_in_current_directory"], - ["space S", "workspace_symbol_picker"], - ["space D", "workspace_diagnostics_picker"], + ["space F", "file_picker_in_current_directory-NOIMPL"], + ["space S", "workspace_symbol_picker-NOIMPL"], + ["space D", "workspace_diagnostics_picker-NOIMPL"], ["space P", "system_paste"], ["space R", "replace_selections_with_clipboard"], ["space ?", "open_command_palette"], ["space f", "find_file"], ["space b", "switch_buffers"], - ["space j", "jumplist_picker"], - ["space s", "symbol_picker"], + ["space j", "jumplist_picker-NOIMPL"], + ["space s", "symbol_picker-NOIMPL"], ["space d", "show_diagnostics"], - ["space a", "code_action"], - ["space '", "last_picker"], + ["space a", "code_action-NOIMPL"], + ["space '", "last_picker-NOIMPL"], ["space y", "copy"], - ["space p", "system_paste_after"], + ["space p", "system_paste_after-NOIMPL"], ["space /", "find_in_files"], ["space k", "hover"], ["space r", "rename_symbol"], - ["space h", "select_references_to_symbol_under_cursor"], + ["space h", "select_references_to_symbol_under_cursor-NOIMPL"], ["space c", "toggle_comment"], ["0", "add_integer_argument_digit", 0], From 0cba416ec4116083e0aac5cf13dee68f1dc8b183 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 18:23:23 +0100 Subject: [PATCH 15/25] refactor: move goto_line_vim into mode specific commands files --- src/tui/editor.zig | 12 ------------ src/tui/mode/helix.zig | 12 ++++++++++++ src/tui/mode/vim.zig | 13 +++++++++++++ 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 7a9e7ace..35b8c14a 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -5460,18 +5460,6 @@ pub const Editor = struct { } pub const goto_line_meta: Meta = .{ .arguments = &.{.integer} }; - pub fn goto_line_vim(self: *Self, ctx: Context) Result { - try self.send_editor_jump_source(); - var line: usize = 0; - _ = ctx.args.match(.{tp.extract(&line)}) catch false; - const root = self.buf_root() catch return; - const primary = self.get_primary(); - try primary.cursor.move_to(root, @intCast(if (line < 1) 0 else line - 1), primary.cursor.col, self.metrics); - self.clamp(); - try self.send_editor_jump_destination(); - } - pub const goto_line_vim_meta: Meta = .{ .arguments = &.{.integer} }; - pub fn goto_column(self: *Self, ctx: Context) Result { const root = self.buf_root() catch return; const primary = self.get_primary(); diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 849700df..57e37969 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -522,6 +522,18 @@ const cmds_ = struct { ed.need_render(); } pub const replace_with_character_helix_meta: Meta = .{ .description = "Replace with character" }; + + pub fn goto_line_helix(self: *Self, ctx: Context) Result { + try self.send_editor_jump_source(); + var line: usize = 0; + _ = ctx.args.match(.{tp.extract(&line)}) catch false; + const root = self.buf_root() catch return; + const primary = self.get_primary(); + try primary.cursor.move_to(root, @intCast(if (line < 1) 0 else line - 1), primary.cursor.col, self.metrics); + self.clamp(); + try self.send_editor_jump_destination(); + } + pub const goto_line_helix_meta: Meta = .{ .arguments = &.{.integer} }; }; fn get_context() ?struct { *MainView, *Editor } { diff --git a/src/tui/mode/vim.zig b/src/tui/mode/vim.zig index d59b858c..22361345 100644 --- a/src/tui/mode/vim.zig +++ b/src/tui/mode/vim.zig @@ -1,5 +1,6 @@ const std = @import("std"); const command = @import("command"); +const tp = @import("thespian"); const cmd = command.executeName; const tui = @import("../tui.zig"); @@ -209,6 +210,18 @@ const cmds_ = struct { self.clamp(); } pub const move_down_vim_meta: Meta = .{ .description = "Move cursor down (vim)", .arguments = &.{.integer} }; + + pub fn goto_line_vim(self: *Self, ctx: Context) Result { + try self.send_editor_jump_source(); + var line: usize = 0; + _ = ctx.args.match(.{tp.extract(&line)}) catch false; + const root = self.buf_root() catch return; + const primary = self.get_primary(); + try primary.cursor.move_to(root, @intCast(if (line < 1) 0 else line - 1), primary.cursor.col, self.metrics); + self.clamp(); + try self.send_editor_jump_destination(); + } + pub const goto_line_vim_meta: Meta = .{ .arguments = &.{.integer} }; }; fn is_eol_right_vim(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { From 2b27ef333221bedbff2a3cfc7f5ff0dc1aa1376e Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 19:30:32 +0100 Subject: [PATCH 16/25] refactor: use orelse in CurSel.to_selection --- src/tui/editor.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 35b8c14a..f01342ff 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -121,7 +121,7 @@ pub const CurSel = struct { } fn to_selection(self: *const Self) Selection { - return if (self.selection) |sel| sel else Selection.from_cursor(&self.cursor); + return self.selection orelse Selection.from_cursor(&self.cursor); } fn to_selection_inclusive(self: *const Self, root: Buffer.Root, metrics: Buffer.Metrics) Selection { From 839f1b8e386a88aea7ae951e1475d7c924a844c4 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 19:41:34 +0100 Subject: [PATCH 17/25] fix: init all cursors in init_helix_select_mode --- src/tui/editor.zig | 4 ++-- src/tui/mode/helix.zig | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index f01342ff..a385b86b 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -120,11 +120,11 @@ pub const CurSel = struct { return if (self.selection) |*sel| sel else unreachable; } - fn to_selection(self: *const Self) Selection { + pub fn to_selection(self: *const Self) Selection { return self.selection orelse Selection.from_cursor(&self.cursor); } - fn to_selection_inclusive(self: *const Self, root: Buffer.Root, metrics: Buffer.Metrics) Selection { + pub fn to_selection_inclusive(self: *const Self, root: Buffer.Root, metrics: Buffer.Metrics) Selection { return self.selection orelse Selection.from_cursor_inclusive(&self.cursor, root, metrics); } diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 57e37969..b4260cf5 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -285,9 +285,12 @@ const cmds_ = struct { } pub const extend_line_below_meta: Meta = .{ .arguments = &.{.integer}, .description = "Select current line, if already selected, extend to next line" }; - pub fn init_helix_select_mode(_: *Self, _: Context) Result { - _, const ed = get_context() orelse return; - try ed.enable_selection(.{}); + pub fn init_helix_select_mode(self: *Self, _: Context) Result { + const root = try self.buf_root(); + for (self.cursels.items) |*cursel_| { + if (cursel_.*) |*cursel| + cursel.selection = Selection.from_cursor_inclusive(&cursel.cursor, root, self.metrics); + } } pub const init_helix_select_mode_meta: Meta = .{}; From faa54afb8b4aed2b7f8585936ba2435feceb5da0 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 20:11:22 +0100 Subject: [PATCH 18/25] refactor: re-write all helix commands to use bound self parameter This makes implementing commands in helix mode almost identical to implementing them inside the editor directly. --- src/tui/editor.zig | 5 + src/tui/mode/helix.zig | 316 ++++++++++++++++++++--------------------- 2 files changed, 157 insertions(+), 164 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index a385b86b..eaea46e9 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -120,6 +120,11 @@ pub const CurSel = struct { return if (self.selection) |*sel| sel else unreachable; } + pub fn enable_selection_inclusive(self: *Self, root: Buffer.Root, metrics: Buffer.Metrics) *Selection { + self.selection = self.to_selection_inclusive(root, metrics); + return if (self.selection) |*sel| sel else unreachable; + } + pub fn to_selection(self: *const Self) Selection { return self.selection orelse Selection.from_cursor(&self.cursor); } diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index b4260cf5..76dd3875 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -229,59 +229,57 @@ const cmds_ = struct { } pub const select_half_page_down_meta: Meta = .{ .description = "Select half a page down" }; - pub fn save_selection(_: *Self, _: Context) Result { + pub fn save_selection(self: *Self, _: Context) Result { const logger = log.logger("helix-mode"); defer logger.deinit(); logger.print("saved location", .{}); const mv = tui.mainview() orelse return; - const file_path = mv.get_active_file_path() orelse return; - const primary = (mv.get_active_editor() orelse return).get_primary(); + const primary = self.get_primary(); const sel: ?location_history.Selection = if (primary.selection) |sel| .{ .begin = .{ .row = sel.begin.row, .col = sel.begin.col }, .end = .{ .row = sel.end.row, .col = sel.end.col }, } else null; - mv.location_history_.update(file_path, .{ + mv.location_history_.update(self.file_path orelse return, .{ .row = primary.cursor.row + 1, .col = primary.cursor.col + 1, }, sel); } pub const save_selection_meta: Meta = .{ .description = "Save current selection to location history" }; - pub fn split_selection_on_newline(_: *Self, _: Context) Result { - const ed, const root = get_buf() orelse return; - const cursels = try ed.cursels.toOwnedSlice(ed.allocator); - defer ed.allocator.free(cursels); - for (cursels) |*cursel_| if (cursel_.*) |*cursel| { - try add_cursors_to_cursel_line_ends_helix(ed, root, cursel); - }; - ed.clamp(); + pub fn split_selection_on_newline(self: *Self, _: Context) Result { + const root = try self.buf_root(); + const cursels = try self.cursels.toOwnedSlice(self.allocator); + defer self.allocator.free(cursels); + for (cursels) |*cursel_| if (cursel_.*) |*cursel| + try add_cursors_to_cursel_line_ends(self, root, cursel); + self.clamp(); } pub const split_selection_on_newline_meta: Meta = .{ .description = "Add cursor to each line in selection helix" }; - pub fn match_brackets(_: *Self, ctx: Context) Result { - const ed, const root = get_buf() orelse return; - try ed.with_cursels_const_once_arg(root, &match_bracket, ctx); - ed.clamp(); + pub fn match_brackets(self: *Self, ctx: Context) Result { + const root = try self.buf_root(); + try self.with_cursels_const_once_arg(root, &match_bracket, ctx); + self.clamp(); } pub const match_brackets_meta: Meta = .{ .description = "Goto matching bracket" }; - pub fn extend_line_below(_: *Self, ctx: Context) Result { - const ed, const root = get_buf() orelse return; + pub fn extend_line_below(self: *Self, ctx: Context) Result { + const root = try self.buf_root(); var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; while (repeat > 0) : (repeat -= 1) { - for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { const sel = cursel.enable_selection(); sel.normalize(); - try Editor.move_cursor_begin(root, &sel.begin, ed.metrics); - try Editor.move_cursor_end(root, &sel.end, ed.metrics); - try Editor.move_cursor_right(root, &sel.end, ed.metrics); + try move_cursor_begin(root, &sel.begin, self.metrics); + try move_cursor_end(root, &sel.end, self.metrics); + try move_cursor_right(root, &sel.end, self.metrics); cursel.cursor = sel.end; }; } - ed.clamp(); + self.clamp(); } pub const extend_line_below_meta: Meta = .{ .arguments = &.{.integer}, .description = "Select current line, if already selected, extend to next line" }; @@ -294,117 +292,117 @@ const cmds_ = struct { } pub const init_helix_select_mode_meta: Meta = .{}; - pub fn move_next_word_start(_: *Self, ctx: Context) Result { - try move_to_word(ctx, Editor.move_cursor_word_right_vim, .forwards); + pub fn move_next_word_start(self: *Self, ctx: Context) Result { + try move_to_word(self, ctx, move_cursor_word_right_vim, .forwards); } pub const move_next_word_start_meta: Meta = .{ .description = "Move next word start", .arguments = &.{.integer} }; - pub fn extend_next_word_start(_: *Self, ctx: Context) Result { - try extend_to_word(ctx, Editor.move_cursor_word_right_vim, .forwards); + pub fn extend_next_word_start(self: *Self, ctx: Context) Result { + try extend_to_word(self, ctx, move_cursor_word_right_vim, .forwards); } pub const extend_next_word_start_meta: Meta = .{ .description = "Extend next word start", .arguments = &.{.integer} }; - pub fn move_next_long_word_start(_: *Self, ctx: Context) Result { - try move_to_word(ctx, move_cursor_long_word_right, .forwards); + pub fn move_next_long_word_start(self: *Self, ctx: Context) Result { + try move_to_word(self, ctx, move_cursor_long_word_right, .forwards); } pub const move_next_long_word_start_meta: Meta = .{ .description = "Move next long word start", .arguments = &.{.integer} }; - pub fn extend_next_long_word_start(_: *Self, ctx: Context) Result { - try extend_to_word(ctx, move_cursor_long_word_right, .forwards); + pub fn extend_next_long_word_start(self: *Self, ctx: Context) Result { + try extend_to_word(self, ctx, move_cursor_long_word_right, .forwards); } pub const extend_next_long_word_start_meta: Meta = .{ .description = "Extend next long word start", .arguments = &.{.integer} }; - pub fn move_prev_word_start(_: *Self, ctx: Context) Result { - try move_cursels_const_repeat(move_cursor_prev_word_start, ctx); + pub fn move_prev_word_start(self: *Self, ctx: Context) Result { + try move_cursels_const_repeat(self, 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(_: *Self, ctx: Context) Result { - try move_cursels_const_repeat(move_cursor_prev_word_start_extend, ctx); + pub fn extend_prev_word_start(self: *Self, ctx: Context) Result { + try move_cursels_const_repeat(self, move_cursor_prev_word_start_extend, ctx); } pub const extend_prev_word_start_meta: Meta = .{ .description = "Extend previous word start", .arguments = &.{.integer} }; - pub fn move_prev_long_word_start(_: *Self, ctx: Context) Result { - try move_to_word(ctx, move_cursor_long_word_left, .backwards); + pub fn move_prev_long_word_start(self: *Self, ctx: Context) Result { + try move_to_word(self, ctx, move_cursor_long_word_left, .backwards); } pub const move_prev_long_word_start_meta: Meta = .{ .description = "Move previous long word start", .arguments = &.{.integer} }; - pub fn extend_prev_long_word_start(_: *Self, ctx: Context) Result { - try extend_to_word(ctx, move_cursor_long_word_left, .backwards); + pub fn extend_prev_long_word_start(self: *Self, ctx: Context) Result { + try extend_to_word(self, ctx, move_cursor_long_word_left, .backwards); } pub const extend_prev_long_word_start_meta: Meta = .{ .description = "Extend previous word start", .arguments = &.{.integer} }; - pub fn move_next_word_end(_: *Self, ctx: Context) Result { - try move_to_word(ctx, move_cursor_word_right_end_helix, .forwards); + pub fn move_next_word_end(self: *Self, ctx: Context) Result { + try move_to_word(self, ctx, move_cursor_word_right_end_helix, .forwards); } pub const move_next_word_end_meta: Meta = .{ .description = "Move next word end", .arguments = &.{.integer} }; - pub fn extend_next_word_end(_: *Self, ctx: Context) Result { - try extend_to_word(ctx, move_cursor_word_right_end_helix, .forwards); + pub fn extend_next_word_end(self: *Self, ctx: Context) Result { + try extend_to_word(self, ctx, move_cursor_word_right_end_helix, .forwards); } pub const extend_next_word_end_meta: Meta = .{ .description = "Extend next word end", .arguments = &.{.integer} }; - pub fn move_next_long_word_end(_: *Self, ctx: Context) Result { - try move_to_word(ctx, move_cursor_long_word_right_end, .forwards); + pub fn move_next_long_word_end(self: *Self, ctx: Context) Result { + try move_to_word(self, ctx, move_cursor_long_word_right_end, .forwards); } pub const move_next_long_word_end_meta: Meta = .{ .description = "Move next long word end", .arguments = &.{.integer} }; - pub fn extend_next_long_word_end(_: *Self, ctx: Context) Result { - try extend_to_word(ctx, move_cursor_long_word_right_end, .forwards); + pub fn extend_next_long_word_end(self: *Self, ctx: Context) Result { + try extend_to_word(self, ctx, move_cursor_long_word_right_end, .forwards); } pub const extend_next_long_word_end_meta: Meta = .{ .description = "Extend next long word end", .arguments = &.{.integer} }; - pub fn cut_forward_internal_inclusive(_: *Self, _: Context) Result { - const ed, const b = get_buf_for_update() orelse return; + pub fn cut_forward_internal_inclusive(self: *Self, _: Context) Result { + const b = try self.buf_for_update(); tui.clipboard_start_group(); - const root = try ed.cut_to(move_noop, b.root); - try ed.update_buf(root); - ed.clamp(); + const root = try self.cut_to(move_noop, b.root); + try self.update_buf(root); + self.clamp(); } pub const cut_forward_internal_inclusive_meta: Meta = .{ .description = "Cut next character to internal clipboard (inclusive)" }; - pub fn select_right_helix(_: *Self, ctx: Context) Result { - const ed, const root = get_buf() orelse return; + pub fn select_right_helix(self: *Self, ctx: Context) Result { + const root = try self.buf_root(); var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; while (repeat > 0) : (repeat -= 1) { - for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - const sel = cursel.enable_selection(); + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + const sel = cursel.enable_selection_inclusive(root, self.metrics); // handling left to right transition const sel_begin: i32 = @intCast(sel.begin.col); const sel_end: i32 = @intCast(sel.end.col); if ((sel_begin - sel_end) == 1 and sel.begin.row == sel.end.row) { - try Editor.move_cursor_right(root, &sel.end, ed.metrics); + try move_cursor_right(root, &sel.end, self.metrics); sel.begin.col -= 1; } - try Editor.move_cursor_right(root, &sel.end, ed.metrics); + try move_cursor_right(root, &sel.end, self.metrics); cursel.cursor = sel.end; cursel.check_selection(); }; } - ed.clamp(); + self.clamp(); } pub const select_right_helix_meta: Meta = .{ .description = "Select right", .arguments = &.{.integer} }; - pub fn select_left_helix(_: *Self, ctx: Context) Result { - const ed, const root = get_buf() orelse return; + pub fn select_left_helix(self: *Self, ctx: Context) Result { + const root = try self.buf_root(); var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; while (repeat > 0) : (repeat -= 1) { - for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { if (cursel.selection == null) { cursel.selection = Selection.from_cursor(&cursel.cursor); - try cursel.selection.?.begin.move_right(root, ed.metrics); + try cursel.selection.?.begin.move_right(root, self.metrics); } if (cursel.selection) |*sel| { - try Editor.move_cursor_left(root, &sel.end, ed.metrics); + try move_cursor_left(root, &sel.end, self.metrics); cursel.cursor = sel.end; if (sel.begin.col == sel.end.col and sel.begin.row == sel.end.row) { - try sel.begin.move_right(root, ed.metrics); - try Editor.move_cursor_left(root, &sel.end, ed.metrics); + try sel.begin.move_right(root, self.metrics); + try move_cursor_left(root, &sel.end, self.metrics); cursel.cursor = sel.end; } } @@ -412,117 +410,117 @@ const cmds_ = struct { cursel.check_selection(); }; } - ed.clamp(); + self.clamp(); } pub const select_left_helix_meta: Meta = .{ .description = "Select left", .arguments = &.{.integer} }; - pub fn select_to_char_left_helix(_: *Self, ctx: Context) Result { - try to_char_helix(ctx, &select_cursel_to_char_left_helix); + pub fn select_to_char_left_helix(self: *Self, ctx: Context) Result { + try to_char_helix(self, 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(_: *Self, ctx: Context) Result { - try to_char_helix(ctx, &select_cursel_till_char_left_helix); + pub fn select_till_char_left_helix(self: *Self, ctx: Context) Result { + try to_char_helix(self, 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(_: *Self, ctx: Context) Result { - try to_char_helix(ctx, &extend_cursel_to_char_left_helix); + pub fn extend_to_char_left_helix(self: *Self, ctx: Context) Result { + try to_char_helix(self, 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(_: *Self, ctx: Context) Result { - try to_char_helix(ctx, &extend_cursel_till_char_left_helix); + pub fn extend_till_char_left_helix(self: *Self, ctx: Context) Result { + try to_char_helix(self, 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(_: *Self, ctx: Context) Result { - try to_char_helix(ctx, &select_cursel_till_char_right_helix); + pub fn select_till_char_right_helix(self: *Self, ctx: Context) Result { + try to_char_helix(self, 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(_: *Self, ctx: Context) Result { - try to_char_helix(ctx, &select_cursel_to_char_right_helix); + pub fn select_to_char_right_helix(self: *Self, ctx: Context) Result { + try to_char_helix(self, 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(_: *Self, ctx: Context) Result { - try to_char_helix(ctx, &extend_cursel_till_char_right_helix); + pub fn extend_till_char_right_helix(self: *Self, ctx: Context) Result { + try to_char_helix(self, 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(_: *Self, ctx: Context) Result { - try to_char_helix(ctx, &extend_cursel_to_char_right_helix); + pub fn extend_to_char_right_helix(self: *Self, ctx: Context) Result { + try to_char_helix(self, ctx, &extend_cursel_to_char_right_helix); } pub const extend_to_char_right_helix_meta: Meta = .{ .description = "Extend Selection to char right" }; - pub fn select_textobject_inner(_: *Self, ctx: Context) Result { - const ed, const root = get_buf() orelse return error.Stop; + pub fn select_textobject_inner(self: *Self, ctx: Context) Result { + const root = try self.buf_root(); var action: []const u8 = ""; if (!try ctx.args.match(.{tp.extract(&action)})) return error.Stop; if (std.mem.eql(u8, action, "w")) { - try ed.with_cursels_const(root, select_inner_word, ed.metrics); + try self.with_cursels_const(root, select_inner_word, self.metrics); } else if (std.mem.eql(u8, action, "W")) { - try ed.with_cursels_const(root, select_inner_long_word, ed.metrics); + try self.with_cursels_const(root, select_inner_long_word, self.metrics); } else { return; } - ed.clamp(); + self.clamp(); } pub const select_textobject_inner_meta: Meta = .{ .description = "select inside object helix" }; - pub fn select_textobject_around(_: *Self, ctx: Context) Result { - const ed, const root = get_buf() orelse return; + pub fn select_textobject_around(self: *Self, ctx: Context) Result { + const root = try self.buf_root(); var action: []const u8 = ""; if (!try ctx.args.match(.{tp.extract(&action)})) return error.Stop; if (std.mem.eql(u8, action, "w")) { - try ed.with_cursels_const(root, select_around_word, ed.metrics); + try self.with_cursels_const(root, select_around_word, self.metrics); } else if (std.mem.eql(u8, action, "W")) { - try ed.with_cursels_const(root, select_inner_long_word, ed.metrics); + try self.with_cursels_const(root, select_inner_long_word, self.metrics); } else { return; } - ed.clamp(); + self.clamp(); } pub const select_textobject_around_meta: Meta = .{ .description = "select around object helix" }; - pub fn copy_helix(_: *Self, _: Context) Result { - const ed, const root = get_buf() orelse return; + pub fn copy_helix(self: *Self, _: Context) Result { + const root = try self.buf_root(); tui.clipboard_start_group(); - for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| if (cursel.selection) |sel| - tui.clipboard_add_chunk(try Editor.copy_selection(root, sel, tui.clipboard_allocator(), ed.metrics)); + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| if (cursel.selection) |sel| + tui.clipboard_add_chunk(try Editor.copy_selection(root, sel, tui.clipboard_allocator(), self.metrics)); - ed.logger.print("copy: {d} selections", .{ed.cursels.items.len}); + self.logger.print("copy: {d} selections", .{self.cursels.items.len}); } pub const copy_helix_meta: Meta = .{ .description = "Copy selection to clipboard (helix)" }; - pub fn paste_after(_: *Self, ctx: Context) Result { - try paste_helix(ctx, insert_after); + pub fn paste_after(self: *Self, ctx: Context) Result { + try paste_helix(self, ctx, insert_after); } pub const paste_after_meta: Meta = .{ .description = "Paste from clipboard after selection" }; - pub fn replace_selections_with_clipboard(_: *Self, ctx: Context) Result { - try paste_helix(ctx, insert_replace_selection); + pub fn replace_selections_with_clipboard(self: *Self, ctx: Context) Result { + try paste_helix(self, ctx, insert_replace_selection); } pub const replace_selections_with_clipboard_meta: Meta = .{ .description = "Replace selection from clipboard" }; - pub fn paste_clipboard_before(_: *Self, ctx: Context) Result { - try paste_helix(ctx, insert_before); + pub fn paste_clipboard_before(self: *Self, ctx: Context) Result { + try paste_helix(self, ctx, insert_before); } pub const paste_clipboard_before_meta: Meta = .{ .description = "Paste from clipboard before selection" }; - pub fn replace_with_character_helix(_: *Self, ctx: Context) Result { - const ed, const b = get_buf_for_update() orelse return; + pub fn replace_with_character_helix(self: *Self, ctx: Context) Result { + const b = try self.buf_for_update(); var root = b.root; - root = try ed.with_cursels_mut_once_arg(root, replace_cursel_with_character, ed.allocator, ctx); - try ed.update_buf(root); - ed.clamp(); - ed.need_render(); + root = try self.with_cursels_mut_once_arg(root, replace_cursel_with_character, self.allocator, ctx); + try self.update_buf(root); + self.clamp(); + self.need_render(); } pub const replace_with_character_helix_meta: Meta = .{ .description = "Replace with character" }; @@ -539,22 +537,6 @@ const cmds_ = struct { pub const goto_line_helix_meta: Meta = .{ .arguments = &.{.integer} }; }; -fn get_context() ?struct { *MainView, *Editor } { - const mv = tui.mainview() orelse return null; - const ed = mv.get_active_editor() orelse return null; - return .{ mv, ed }; -} - -fn get_buf() ?struct { *Editor, Buffer.Root } { - _, const ed = get_context() orelse return null; - return .{ ed, ed.buf_root() catch return null }; -} - -fn get_buf_for_update() ?struct { *Editor, *const Buffer } { - _, const ed = get_context() orelse return null; - return .{ ed, ed.buf_for_update() catch return null }; -} - fn is_eol(c: []const u8) bool { return char_class(c) == .eol; } @@ -577,12 +559,18 @@ fn is_word_boundary(root: Buffer.Root, cursor: Cursor, metrics: Buffer.Metrics, }; } +const move_cursor_begin = Editor.move_cursor_begin; +const move_cursor_end = Editor.move_cursor_end; +const move_cursor_left = Editor.move_cursor_left; +const move_cursor_right = Editor.move_cursor_right; +const move_cursor_word_right_vim = Editor.move_cursor_word_right_vim; + 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); + var sel = cursel.to_selection_inclusive(root, metrics); defer { sel.begin = cursor; cursel.cursor = cursor; @@ -614,10 +602,10 @@ fn move_cursor_prev_word_start_extend(root: Buffer.Root, cursel: *CurSel, metric try move_cursor_prev_word_start(root, cursel, metrics); } -fn move_cursels_const_repeat(move: Editor.cursel_operator_const, ctx: Context) Result { - const ed, const root = get_buf() orelse return; - try ed.with_cursels_const_repeat(root, move, ctx); - ed.clamp(); +fn move_cursels_const_repeat(self: *Self, move: Editor.cursel_operator_const, ctx: Context) Result { + const root = try self.buf_root(); + try self.with_cursels_const_repeat(root, move, ctx); + self.clamp(); } fn match_bracket(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metrics: Buffer.Metrics) error{Stop}!void { @@ -656,61 +644,61 @@ fn match_bracket(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metri } } -fn move_to_word(ctx: command.Context, move: Editor.cursor_operator_const, direction: Direction) command.Result { - const ed, const root = get_buf() orelse return; +fn move_to_word(self: *Self, ctx: command.Context, move: Editor.cursor_operator_const, direction: Direction) command.Result { + const root = try self.buf_root(); // NOR mode moves n words selecting the last one var repeat: usize = 0; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; - if (repeat > 1) ed.with_cursors_const_repeat(root, move, command.fmt(.{repeat - 1})) catch {}; + if (repeat > 1) self.with_cursors_const_repeat(root, move, command.fmt(.{repeat - 1})) catch {}; - for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { var sel = Selection.from_cursor(&cursel.cursor); - const cur = sel.begin.test_at(root, is_not_whitespace_or_eol, ed.metrics); + const cur = sel.begin.test_at(root, is_not_whitespace_or_eol, self.metrics); if (direction == .backwards) { - sel.begin.move_left(root, ed.metrics) catch continue; - const prev = sel.begin.test_at(root, Editor.is_not_word_char, ed.metrics); + sel.begin.move_left(root, self.metrics) catch continue; + const prev = sel.begin.test_at(root, Editor.is_not_word_char, self.metrics); sel.begin = sel.end; if (!cur or cur != prev) - sel.begin.move_right(root, ed.metrics) catch continue; + sel.begin.move_right(root, self.metrics) catch continue; } else { - sel.end.move_right(root, ed.metrics) catch continue; - const next = sel.end.test_at(root, Editor.is_not_word_char, ed.metrics); + sel.end.move_right(root, self.metrics) catch continue; + const next = sel.end.test_at(root, Editor.is_not_word_char, self.metrics); if (!cur and cur != next) sel.begin = sel.end; } cursel.cursor = sel.end; if (direction == .forwards) - sel.end.move_right(root, ed.metrics) catch {}; + sel.end.move_right(root, self.metrics) catch {}; cursel.selection = sel; }; - ed.with_selections_const_repeat(root, move, command.fmt(.{1})) catch {}; - ed.clamp(); + self.with_selections_const_repeat(root, move, command.fmt(.{1})) catch {}; + self.clamp(); } -fn extend_to_word(ctx: command.Context, move: Editor.cursor_operator_const, _: Direction) command.Result { - const ed, const root = get_buf() orelse return; +fn extend_to_word(self: *Self, ctx: command.Context, move: Editor.cursor_operator_const, _: Direction) command.Result { + const root = try self.buf_root(); var repeat: usize = 1; _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; - for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { const sel = cursel.enable_selection(); const pivot: usize = if (sel.is_reversed()) sel.begin.col -| 1 else sel.begin.col; var i: usize = repeat; while (i > 0) : (i -= 1) { - try move(root, &sel.end, ed.metrics); + try move(root, &sel.end, self.metrics); } sel.begin.col = if (sel.is_reversed()) pivot +| 1 else pivot; cursel.cursor = sel.end; }; - ed.clamp(); + self.clamp(); } -fn to_char_helix(ctx: command.Context, move: Editor.cursel_operator_mut_once_arg) command.Result { - const ed, const root = get_buf() orelse return; - try ed.with_cursels_const_once_arg(root, move, ctx); - ed.clamp(); +fn to_char_helix(self: *Self, ctx: command.Context, move: Editor.cursel_operator_mut_once_arg) command.Result { + const root = try self.buf_root(); + try self.with_cursels_const_once_arg(root, move, ctx); + self.clamp(); } fn select_inner_word(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { @@ -1062,7 +1050,7 @@ fn move_cursor_till_char_right_beyond_eol(root: Buffer.Root, cursor: *Cursor, me } } -fn add_cursors_to_cursel_line_ends_helix(ed: *Editor, root: Buffer.Root, cursel: *CurSel) !void { +fn add_cursors_to_cursel_line_ends(ed: *Editor, root: Buffer.Root, cursel: *CurSel) !void { const original_cursel = cursel.*; const sel = cursel.enable_selection(); sel.normalize(); @@ -1282,8 +1270,8 @@ 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 ed, const b = get_buf_for_update() orelse return; +fn paste_helix(self: *Self, ctx: command.Context, do_paste: pasting_function) command.Result { + const b = try self.buf_for_update(); var root = b.root; var text_: []const u8 = undefined; @@ -1294,7 +1282,7 @@ fn paste_helix(ctx: command.Context, do_paste: pasting_function) command.Result tui.clipboard_get_group(0); if (clipboard.len == 0) { - ed.logger.print("paste: nothing to paste", .{}); + self.logger.print("paste: nothing to paste", .{}); return; } @@ -1303,20 +1291,20 @@ fn paste_helix(ctx: command.Context, do_paste: pasting_function) command.Result // use the last chunk in the clipboard var bytes: usize = 0; - for (ed.cursels.items, 0..) |*cursel_, idx| if (cursel_.*) |*cursel| { + for (self.cursels.items, 0..) |*cursel_, idx| if (cursel_.*) |*cursel| { if (idx < clipboard.len) { - root = try do_paste(ed, root, cursel, clipboard[idx].text, b.allocator); + root = try do_paste(self, root, cursel, clipboard[idx].text, b.allocator); bytes += clipboard[idx].text.len; } else { bytes += clipboard[clipboard.len - 1].text.len; - root = try do_paste(ed, root, cursel, clipboard[clipboard.len - 1].text, b.allocator); + root = try do_paste(self, root, cursel, clipboard[clipboard.len - 1].text, b.allocator); } }; - ed.logger.print("paste: {d} bytes", .{bytes}); + self.logger.print("paste: {d} bytes", .{bytes}); - try ed.update_buf(root); - ed.clamp(); - ed.need_render(); + try self.update_buf(root); + self.clamp(); + self.need_render(); } fn move_cursor_carriage_return(root: Buffer.Root, cursel: CurSel, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { From eba444c6fc804713d7cab81fbea84d0695e951d0 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 20:13:34 +0100 Subject: [PATCH 19/25] refactor: eliminate redundent healper function --- src/tui/mode/helix.zig | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 76dd3875..ca9a4f0b 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -313,12 +313,16 @@ const cmds_ = struct { pub const extend_next_long_word_start_meta: Meta = .{ .description = "Extend next long word start", .arguments = &.{.integer} }; pub fn move_prev_word_start(self: *Self, ctx: Context) Result { - try move_cursels_const_repeat(self, move_cursor_prev_word_start, ctx); + const root = try self.buf_root(); + try self.with_cursels_const_repeat(root, move_cursor_prev_word_start, ctx); + self.clamp(); } pub const move_prev_word_start_meta: Meta = .{ .description = "Move previous word start", .arguments = &.{.integer} }; pub fn extend_prev_word_start(self: *Self, ctx: Context) Result { - try move_cursels_const_repeat(self, move_cursor_prev_word_start_extend, ctx); + const root = try self.buf_root(); + try self.with_cursels_const_repeat(root, move_cursor_prev_word_start_extend, ctx); + self.clamp(); } pub const extend_prev_word_start_meta: Meta = .{ .description = "Extend previous word start", .arguments = &.{.integer} }; @@ -602,12 +606,6 @@ fn move_cursor_prev_word_start_extend(root: Buffer.Root, cursel: *CurSel, metric try move_cursor_prev_word_start(root, cursel, metrics); } -fn move_cursels_const_repeat(self: *Self, move: Editor.cursel_operator_const, ctx: Context) Result { - const root = try self.buf_root(); - try self.with_cursels_const_repeat(root, move, ctx); - self.clamp(); -} - fn match_bracket(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metrics: Buffer.Metrics) error{Stop}!void { var symbol: []const u8 = undefined; const mode: enum { helix_sel_mode, helix_nor_mode } = if ((ctx.args.match(.{tp.extract(&symbol)}) catch false) and From d410fabf1bad3be30973d99e0d6abf1c5e5a6c21 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 20:18:48 +0100 Subject: [PATCH 20/25] fix: don't stomp on existing selection when entering helix SEL mode --- src/tui/mode/helix.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index ca9a4f0b..f95a93d2 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -287,7 +287,7 @@ const cmds_ = struct { const root = try self.buf_root(); for (self.cursels.items) |*cursel_| { if (cursel_.*) |*cursel| - cursel.selection = Selection.from_cursor_inclusive(&cursel.cursor, root, self.metrics); + _ = cursel.enable_selection_inclusive(root, self.metrics); } } pub const init_helix_select_mode_meta: Meta = .{}; From 0ca1d372f63debafdad4da089746900f6dbf15d1 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 20:19:36 +0100 Subject: [PATCH 21/25] fix: use correct direction for selection in move_cursor_prev_word_start --- src/tui/mode/helix.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index f95a93d2..50368647 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -575,6 +575,7 @@ fn move_cursor_prev_word_start(root: Buffer.Root, cursel: *CurSel, metrics: Buff cursor.move_left(root, metrics) catch {}; var sel = cursel.to_selection_inclusive(root, metrics); + sel.reverse(); defer { sel.begin = cursor; cursel.cursor = cursor; From c65a4af30a0b87a708507418c27e6b22b36420d7 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 20:21:12 +0100 Subject: [PATCH 22/25] fix: helix g g command keybind --- src/keybind/builtin/helix.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keybind/builtin/helix.json b/src/keybind/builtin/helix.json index 626e60ef..9b64d3c7 100644 --- a/src/keybind/builtin/helix.json +++ b/src/keybind/builtin/helix.json @@ -138,7 +138,7 @@ ["v", "enter_mode", "select"], ["G", "goto_line"], - ["g g", "goto_line_vim"], + ["g g", "goto_line_helix"], ["g e", "move_buffer_end"], ["g f", "goto_file"], ["g h", "move_begin"], From f9642960515d29ef1e518100705cf70c16da3b54 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 27 Nov 2025 20:24:53 +0100 Subject: [PATCH 23/25] fix: extend the correct end of the selection in move_cursor_prev_word_start_extend --- src/tui/mode/helix.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 50368647..69daf70b 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -601,8 +601,7 @@ fn move_cursor_prev_word_start_extend(root: Buffer.Root, cursel: *CurSel, metric 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; + if (cursel.selection) |*sel| sel.begin = pre_sel.begin; }; try move_cursor_prev_word_start(root, cursel, metrics); } From a779c590bdfc9772f4f5f3774db99035f2edb491 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 1 Dec 2025 12:47:11 +0100 Subject: [PATCH 24/25] refactor: clean-up duplicate commands overlay_toggle_panel/_inputivew --- src/keybind/builtin/flow.json | 3 --- src/tui/mode/overlay/open_recent.zig | 10 ---------- src/tui/mode/overlay/palette.zig | 10 ---------- src/tui/mode/overlay/vcs_status.zig | 10 ---------- 4 files changed, 33 deletions(-) diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 67babab6..217b8a7f 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -27,7 +27,6 @@ ["ctrl+shift+p", "open_command_palette"], ["ctrl+shift+q", "quit_without_saving"], ["ctrl+shift+f", "find_in_files"], - ["ctrl+shift+l", "toggle_panel"], ["alt+shift+p", "open_command_palette"], ["alt+n", "goto_next_file_or_diagnostic"], ["alt+p", "goto_prev_file_or_diagnostic"], @@ -410,8 +409,6 @@ ["ctrl+shift+p", "palette_menu_down"], ["ctrl+shift+q", "quit_without_saving"], ["ctrl+shift+w", "close_file_without_saving"], - ["ctrl+shift+l", "overlay_toggle_panel"], - ["ctrl+shift+i", "overlay_toggle_inputview"], ["alt+shift+p", "palette_menu_down"], ["alt+p", "palette_menu_up"], ["alt+l", "toggle_panel"], diff --git a/src/tui/mode/overlay/open_recent.zig b/src/tui/mode/overlay/open_recent.zig index 6de797d9..5a41ea42 100644 --- a/src/tui/mode/overlay/open_recent.zig +++ b/src/tui/mode/overlay/open_recent.zig @@ -405,16 +405,6 @@ const cmds = struct { } pub const overlay_insert_bytes_meta: Meta = .{ .arguments = &.{.string} }; - pub fn overlay_toggle_panel(self: *Self, _: Ctx) Result { - return self.cmd_async("toggle_panel"); - } - pub const overlay_toggle_panel_meta: Meta = .{}; - - pub fn overlay_toggle_inputview(self: *Self, _: Ctx) Result { - return self.cmd_async("toggle_inputview"); - } - pub const overlay_toggle_inputview_meta: Meta = .{}; - pub fn overlay_next_widget_style(self: *Self, _: Ctx) Result { tui.set_next_style(widget_type); self.do_resize(); diff --git a/src/tui/mode/overlay/palette.zig b/src/tui/mode/overlay/palette.zig index d4a7124c..d9cd6387 100644 --- a/src/tui/mode/overlay/palette.zig +++ b/src/tui/mode/overlay/palette.zig @@ -598,16 +598,6 @@ pub fn Create(options: type) type { } pub const overlay_insert_bytes_meta: Meta = .{ .arguments = &.{.string} }; - pub fn overlay_toggle_panel(self: *Self, _: Ctx) Result { - return self.cmd_async("toggle_panel"); - } - pub const overlay_toggle_panel_meta: Meta = .{}; - - pub fn overlay_toggle_inputview(self: *Self, _: Ctx) Result { - return self.cmd_async("toggle_inputview"); - } - pub const overlay_toggle_inputview_meta: Meta = .{}; - pub fn overlay_next_widget_style(self: *Self, _: Ctx) Result { tui.set_next_style(widget_type); const padding = tui.get_widget_style(widget_type).padding; diff --git a/src/tui/mode/overlay/vcs_status.zig b/src/tui/mode/overlay/vcs_status.zig index 2ecfc412..b25b6981 100644 --- a/src/tui/mode/overlay/vcs_status.zig +++ b/src/tui/mode/overlay/vcs_status.zig @@ -366,16 +366,6 @@ const cmds = struct { } pub const overlay_insert_bytes_meta: Meta = .{ .arguments = &.{.string} }; - pub fn overlay_toggle_panel(self: *Self, _: Ctx) Result { - return self.cmd_async("toggle_panel"); - } - pub const overlay_toggle_panel_meta: Meta = .{}; - - pub fn overlay_toggle_inputview(self: *Self, _: Ctx) Result { - return self.cmd_async("toggle_inputview"); - } - pub const overlay_toggle_inputview_meta: Meta = .{}; - pub fn overlay_next_widget_style(self: *Self, _: Ctx) Result { tui.set_next_style(widget_type); self.do_resize(); From b5e1cce4b3cffa666445f260de0651676f20a034 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 2 Dec 2025 16:30:45 +0100 Subject: [PATCH 25/25] fix: rebase left overs --- src/keybind/keybind.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keybind/keybind.zig b/src/keybind/keybind.zig index cffa1327..67641ee2 100644 --- a/src/keybind/keybind.zig +++ b/src/keybind/keybind.zig @@ -963,7 +963,7 @@ test "match" { } test "json" { - var bindings: BindingSet = .{ .name = "test", .config_section = "test_section", .selection_style = .normal }; + var bindings: BindingSet = .{ .name = "test", .config_section = "test_section" }; _ = try bindings.process_key_event(input.KeyEvent.from_key('j')); _ = try bindings.process_key_event(input.KeyEvent.from_key('k')); _ = try bindings.process_key_event(input.KeyEvent.from_key('g'));