refactor: reduce duplication of context getting code in helix mode

This commit is contained in:
CJ van den Berg 2025-11-27 16:45:42 +01:00
parent b3db0922ed
commit 631e8fca41
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
2 changed files with 108 additions and 121 deletions

View file

@ -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