From fb985a703affbf22ce83b46777e734d7b8be08e6 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 8 Apr 2025 05:28:29 -0300 Subject: [PATCH] feat: Helix & Vim mode: adding more commands (#218) * Helix mode: select_left * select_to_char_right implementation * Vim select_to_char_left * Helix select_to_char_left * Helix & Vim: select_end * select_to_char_left: Avoid panic with no selection * select_left_helix: handling panic and shrinking code * Correcting helix left and right select * Helix mode: select_left * select_to_char_right implementation * Vim select_to_char_left * Helix select_to_char_left * Helix & Vim: select_end * select_to_char_left: Avoid panic with no selection * select_left_helix: handling panic and shrinking code * Correcting helix left and right select * Enable_selection on init_command * move_to_char modification * move_or_select --------- Co-authored-by: CJ van den Berg --- src/keybind/builtin/flow.json | 4 +- src/keybind/builtin/helix.json | 15 ++++--- src/keybind/builtin/vim.json | 10 +++-- src/tui/editor.zig | 34 +++++++++++++-- src/tui/mode/helix.zig | 68 ++++++++++++++++++++++++++++++ src/tui/mode/mini/move_to_char.zig | 38 +++++------------ 6 files changed, 126 insertions(+), 43 deletions(-) diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 24effd9..c6e4b21 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -50,8 +50,8 @@ ["ctrl+l", "scroll_view_center_cycle"], ["ctrl+n", "goto_next_match"], ["ctrl+p", "goto_prev_match"], - ["ctrl+b", "move_to_char", "left"], - ["ctrl+t", "move_to_char", "right"], + ["ctrl+b", "move_to_char", "move_or_select_to_char_left"], + ["ctrl+t", "move_to_char", "move_or_select_to_char_right"], ["ctrl+x", "cut"], ["ctrl+c", "copy"], ["ctrl+v", "system_paste"], diff --git a/src/keybind/builtin/helix.json b/src/keybind/builtin/helix.json index 331559b..5da5755 100644 --- a/src/keybind/builtin/helix.json +++ b/src/keybind/builtin/helix.json @@ -62,7 +62,7 @@ ["shift+`", "switch_case"], ["shift+t", "till_prev_char"], - ["shift+f", "move_to_char", "left"], + ["shift+f", "move_to_char", "move_to_char_left"], ["shift+w", "move_next_long_word_start"], ["shift+b", "move_prev_long_word_start"], ["shift+e", "move_next_long_word_end"], @@ -107,7 +107,7 @@ ["l", "move_right"], ["t", "find_till_char"], - ["f", "move_to_char", "right"], + ["f", "move_to_char", "move_to_char_right"], ["`", "to_lower"], @@ -256,6 +256,7 @@ "line_numbers": "relative", "cursor": "block", "selection": "inclusive", + "init_command": ["enable_selection"], "press": [ ["ctrl+b", "select_page_up"], ["ctrl+f", "select_page_down"], @@ -322,7 +323,7 @@ ["shift+`", "switch_case"], ["shift+t", "extend_till_prev_char"], - ["shift+f", "extend_prev_char"], + ["shift+f", "move_to_char", "select_to_char_left_vim"], ["shift+w", "extend_next_long_word_start"], ["shift+b", "extend_prev_long_word_start"], @@ -371,17 +372,17 @@ ["shift+1", "shell_insert_output"], ["shift+4", "shell_keep_pipe"], - ["h", "select_left"], + ["h", "select_left_helix"], ["j", "select_down"], ["k", "select_up"], - ["l", "select_right"], + ["l", "select_right_helix"], ["left", "select_left"], ["down", "select_down"], ["up", "select_up"], ["right", "select_right"], ["t", "extend_till_char"], - ["f", "extend_next_char"], + ["f", "move_to_char", "select_to_char_right_helix"], ["`", "switch_to_lowercase"], @@ -398,7 +399,7 @@ ["g e", "move_buffer_end"], ["g f", "goto_file"], ["g h", "move_begin"], - ["g l", "move_end"], + ["g l", "select_end"], ["g s", "smart_move_begin"], ["g d", "goto_definition"], ["g y", "goto_type_definition"], diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index 9482da5..020f386 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -86,8 +86,8 @@ ["", "TODO"], - ["F", "move_to_char", "left"], - ["f", "move_to_char", "right"], + ["F", "move_to_char", "move_to_char_left"], + ["f", "move_to_char", "move_to_char_right"], ["", ["move_down"], ["move_begin"]], ["", ["move_down"], ["move_begin"]] @@ -100,6 +100,7 @@ "line_numbers": "relative", "cursor": "block", "selection": "normal", + "init_command": ["enable_selection"], "press": [ ["", ["cancel"], ["enter_mode", "normal"]], ["k", "select_up"], @@ -115,9 +116,12 @@ ["0", "move_begin"], ["^", "smart_move_begin"], - ["$", "move_end"], + ["$", "select_end"], [":", "open_command_palette"], + ["f", "move_to_char", "select_to_char_right"], + ["F", "move_to_char", "select_to_char_left_vim"], + ["p", ["paste_internal_vim"], ["enter_mode", "normal"]], ["P", ["paste_internal_vim"], ["enter_mode", "normal"]], diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 93ee94e..359f749 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -96,7 +96,7 @@ pub const CurSel = struct { self.* = .{}; } - fn enable_selection(self: *Self, root: Buffer.Root, metrics: Buffer.Metrics) !*Selection { + pub fn enable_selection(self: *Self, root: Buffer.Root, metrics: Buffer.Metrics) !*Selection { return switch (tui.get_selection_style()) { .normal => self.enable_selection_normal(), .inclusive => try self.enable_selection_inclusive(root, metrics), @@ -147,7 +147,7 @@ pub const CurSel = struct { } } - fn check_selection(self: *Self, root: Buffer.Root, metrics: Buffer.Metrics) void { + 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); }; @@ -2123,7 +2123,7 @@ pub const Editor = struct { return if (cursor.col == first) cursor.move_begin() else cursor.move_to(root, cursor.row, first, metrics); } - fn move_cursor_right(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { + pub fn move_cursor_right(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { try cursor.move_right(root, metrics); } @@ -3089,6 +3089,18 @@ pub const Editor = struct { } pub const move_to_char_right_meta: Meta = .{ .arguments = &.{.integer} }; + pub fn move_or_select_to_char_left(self: *Self, ctx: Context) Result { + const selected = if (self.get_primary().selection) |_| true else false; + if (selected) try self.select_to_char_left(ctx) else try self.move_to_char_left(ctx); + } + pub const move_or_select_to_char_left_meta: Meta = .{ .arguments = &.{.integer} }; + + pub fn move_or_select_to_char_right(self: *Self, ctx: Context) Result { + const selected = if (self.get_primary().selection) |_| true else false; + if (selected) try self.select_to_char_right(ctx) else try self.move_to_char_right(ctx); + } + pub const move_or_select_to_char_right_meta: Meta = .{ .arguments = &.{.integer} }; + pub fn move_up(self: *Self, _: Context) Result { const root = try self.buf_root(); self.with_cursors_const(root, move_cursor_up) catch {}; @@ -3592,6 +3604,12 @@ 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(); + _ = try self.get_primary().enable_selection(root, self.metrics); + } + pub const enable_selection_meta: Meta = .{ .description = "Enable selection" }; + pub fn select_line_vim(self: *Self, _: Context) Result { self.selection_mode = .line; for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| @@ -3702,6 +3720,16 @@ pub const Editor = struct { } pub const select_to_char_left_meta: Meta = .{ .arguments = &.{.integer} }; + pub fn select_to_char_left_vim(self: *Self, ctx: Context) Result { + const root = try self.buf_root(); + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + if (cursel.selection) |*sel| try sel.begin.move_right(root, self.metrics); + }; + self.with_selections_const_arg(root, move_cursor_to_char_left, ctx) catch {}; + self.clamp(); + } + pub const select_to_char_left_vim_meta: Meta = .{ .arguments = &.{.integer} }; + pub fn select_to_char_right(self: *Self, ctx: Context) Result { const root = try self.buf_root(); self.with_selections_const_arg(root, move_cursor_to_char_right, ctx) catch {}; diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 85e15e2..bb7d250 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -8,6 +8,7 @@ const tui = @import("../tui.zig"); const Editor = @import("../editor.zig").Editor; const Buffer = @import("Buffer"); const Cursor = Buffer.Cursor; +const Selection = Buffer.Selection; var commands: Commands = undefined; @@ -135,6 +136,73 @@ const cmds_ = struct { ed.clamp(); } pub const cut_forward_internal_inclusive_meta: Meta = .{ .description = "Cut next character to internal clipboard (inclusive)" }; + + pub fn select_right_helix(_: *void, _: Ctx) Result { + const mv = tui.mainview() orelse return; + const ed = mv.get_active_editor() orelse return; + const root = try ed.buf_root(); + + for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + const sel = try cursel.enable_selection(root, ed.metrics); + + // 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); + sel.begin.col -= 1; + } + + try Editor.move_cursor_right(root, &sel.end, ed.metrics); + cursel.cursor = sel.end; + cursel.check_selection(root, ed.metrics); + }; + ed.clamp(); + } + pub const select_right_helix_meta: Meta = .{ .description = "Select right" }; + + pub fn select_left_helix(_: *void, _: Ctx) Result { + const mv = tui.mainview() orelse return; + const ed = mv.get_active_editor() orelse return; + const root = try ed.buf_root(); + + for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + if (cursel.selection == null) { + cursel.selection = Selection.from_cursor(&cursel.cursor); + try cursel.selection.?.begin.move_right(root, ed.metrics); + } + if (cursel.selection) |*sel| { + try Editor.move_cursor_left(root, &sel.end, ed.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); + cursel.cursor = sel.end; + } + } + + cursel.check_selection(root, ed.metrics); + }; + ed.clamp(); + } + pub const select_left_helix_meta: Meta = .{ .description = "Select left" }; + + pub fn select_to_char_right_helix(_: *void, ctx: Ctx) Result { + const mv = tui.mainview() orelse return; + const ed = mv.get_active_editor() orelse return; + const root = try ed.buf_root(); + + for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + const sel = try cursel.enable_selection(root, ed.metrics); + try Editor.move_cursor_to_char_right(root, &sel.end, ctx, ed.metrics); + try Editor.move_cursor_right(root, &sel.end, ed.metrics); + cursel.cursor = sel.end; + cursel.check_selection(root, ed.metrics); + }; + ed.clamp(); + } + pub const select_to_char_right_helix_meta: Meta = .{ .description = "Move to char right" }; }; fn move_cursor_word_left_helix(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { diff --git a/src/tui/mode/mini/move_to_char.zig b/src/tui/mode/mini/move_to_char.zig index 4703327..e5604a0 100644 --- a/src/tui/mode/mini/move_to_char.zig +++ b/src/tui/mode/mini/move_to_char.zig @@ -1,3 +1,4 @@ +const std = @import("std"); const tp = @import("thespian"); const input = @import("input"); @@ -15,28 +16,24 @@ const Commands = command.Collection(cmds); allocator: Allocator, key: [6]u8 = undefined, -direction: Direction, +operation_command: []const u8, operation: Operation, commands: Commands = undefined, -const Direction = enum { - left, - right, -}; - const Operation = enum { move, select, }; pub fn create(allocator: Allocator, ctx: command.Context) !struct { tui.Mode, tui.MiniMode } { - var direction: Direction = undefined; + var egc: []const u8 = undefined; + const select = if (tui.get_active_editor()) |editor| if (editor.get_primary().selection) |_| true else false else false; - _ = ctx.args.match(.{tp.extract(&direction)}) catch return error.InvalidMoveToCharArgument; + _ = ctx.args.match(.{tp.extract(&egc)}) catch return error.InvalidMoveToCharArgument; const self: *Self = try allocator.create(Self); self.* = .{ .allocator = allocator, - .direction = direction, + .operation_command = try allocator.dupe(u8, egc), .operation = if (select) .select else .move, }; try self.commands.init(self); @@ -49,19 +46,14 @@ pub fn create(allocator: Allocator, ctx: command.Context) !struct { tui.Mode, tu pub fn deinit(self: *Self) void { self.commands.deinit(); + self.allocator.free(self.operation_command); self.allocator.destroy(self); } fn name(self: *Self) []const u8 { return switch (self.operation) { - .move => switch (self.direction) { - .left => "↶ move", - .right => "↷ move", - }, - .select => switch (self.direction) { - .left => "󰒅 ↶ select", - .right => "󰒅 ↷ select", - }, + .move => "move", + .select => "select", }; } @@ -70,17 +62,7 @@ pub fn receive(_: *Self, _: tp.pid_ref, _: tp.message) error{Exit}!bool { } fn execute_operation(self: *Self, ctx: command.Context) command.Result { - const cmd = switch (self.direction) { - .left => switch (self.operation) { - .move => "move_to_char_left", - .select => "select_to_char_left", - }, - .right => switch (self.operation) { - .move => "move_to_char_right", - .select => "select_to_char_right", - }, - }; - try command.executeName(cmd, ctx); + try command.executeName(self.operation_command, ctx); try command.executeName("exit_mini_mode", .{}); }