From 715bb6bbcf7501e18fcbd3c7b9d76eac02072d56 Mon Sep 17 00:00:00 2001 From: lulvz Date: Tue, 11 Feb 2025 15:19:04 +0000 Subject: [PATCH] feat(vim): Add VISUAL line mode to vim mode, fix vertical movement behaviour --- src/keybind/builtin/vim.json | 68 ++++++++++++++++++++++++++--- src/tui/editor.zig | 84 ++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 7 deletions(-) diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index 3291471..261b240 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -20,8 +20,8 @@ ["s", ["cut_forward_internal"], ["enter_mode", "insert"]], ["u", "undo"], - ["j", "move_down"], - ["k", "move_up"], + ["j", "move_down_vim"], + ["k", "move_up_vim"], ["l", "move_right_vim"], ["h", "move_left_vim"], ["", "move_right_vim"], @@ -33,15 +33,20 @@ ["o", ["smart_insert_line_after"], ["enter_mode", "insert"]], ["O", ["smart_insert_line_before"], ["enter_mode", "insert"]], + ["", "indent"], + ["", "unindent"], + ["v", "enter_mode", "visual"], - ["V", ["move_begin"], ["enter_mode", "visual"], ["select_end"]], + ["V", ["enter_mode", "visual line"], ["select_line_vim"]], ["n", "goto_next_match"], ["0", "move_begin"], ["^", "smart_move_begin"], ["$", "move_end"], [":", "open_command_palette"], + ["p", "paste_internal_vim"], + ["P", "paste_internal_vim"], ["gd", "goto_definition"], ["gi", "goto_implementation"], @@ -59,8 +64,11 @@ ["yy", ["copy_line_internal_vim"], ["cancel"]], - ["", "move_scroll_page_up"], - ["", "move_scroll_page_down"], + ["", "move_scroll_half_page_up_vim"], + ["", "move_scroll_half_page_down_vim"], + + ["zz", "scroll_view_center"], + ["u", "undo"], ["", "redo"], ["", "jump_back"], @@ -83,12 +91,26 @@ "cursor": "block", "selection": "normal", "press": [ - ["", "enter_mode", "normal"], + ["", ["cancel"], ["enter_mode", "normal"]], ["k", "select_up"], ["j", "select_down"], ["h", "select_left"], ["l", "select_right"], + ["0", "move_begin"], + ["^", "smart_move_begin"], + ["$", "move_end"], + + ["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"], + + ["zz", "scroll_view_center"], + ["", "indent"], + ["", "unindent"], + ["y", ["copy_internal_vim"], ["cancel"], ["enter_mode", "normal"]], ["x", ["cut_forward_internal"], ["cancel"], ["enter_mode", "normal"]], @@ -96,6 +118,38 @@ ["s", ["cut_forward_internal"], ["cancel"], ["enter_mode", "insert"]] ] }, + "visual line": { + "syntax": "vim", + "on_match_failure": "ignore", + "name": "VISUAL LINE", + "line_numbers": "relative", + "cursor": "block", + "selection": "normal", + "press": [ + ["", ["cancel"], ["enter_mode", "normal"]], + ["k", "select_up"], + ["j", "select_down"], + + ["0", "move_begin"], + ["^", "smart_move_begin"], + ["$", "move_end"], + + ["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"], + + ["", "indent"], + ["", "unindent"], + + ["y", ["copy_line_internal_vim"], ["cancel"], ["enter_mode", "normal"]], + + ["x", ["cut_internal_vim"], ["cancel"], ["enter_mode", "normal"]], + ["d", ["cut_internal_vim"], ["cancel"], ["enter_mode", "normal"]], + ["s", ["cut_internal_vim"], ["cancel"], ["enter_mode", "insert"]] + ] + }, "insert": { "syntax": "vim", "name": "INSERT", @@ -103,7 +157,7 @@ "cursor": "beam", "press": [ ["jk", "enter_mode", "normal"], - ["", "enter_mode", "normal"], + ["", ["move_left_vim"], ["enter_mode", "normal"]], ["", "delete_forward"], ["", "delete_backward"], ["", "smart_insert_line"], diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 3d4780e..02db304 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -2039,6 +2039,14 @@ pub const Editor = struct { 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_left(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { try cursor.move_left(root, metrics); } @@ -2084,10 +2092,20 @@ pub const Editor = struct { try cursor.move_up(root, 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(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) !void { try cursor.move_down(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(); } @@ -2108,10 +2126,20 @@ pub const Editor = struct { 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) { @@ -2948,6 +2976,13 @@ pub const Editor = struct { } pub const move_up_meta = .{ .description = "Move cursor up" }; + pub fn move_up_vim(self: *Self, _: Context) Result { + const root = try self.buf_root(); + self.with_cursors_const(root, move_cursor_up_vim) catch {}; + self.clamp(); + } + pub const move_up_vim_meta = .{ .description = "Move cursor up (vim)" }; + pub fn add_cursor_up(self: *Self, _: Context) Result { try self.push_cursor(); const primary = self.get_primary(); @@ -2964,6 +2999,13 @@ pub const Editor = struct { } pub const move_down_meta = .{ .description = "Move cursor down" }; + pub fn move_down_vim(self: *Self, _: Context) Result { + const root = try self.buf_root(); + self.with_cursors_const(root, move_cursor_down_vim) catch {}; + self.clamp(); + } + pub const move_down_vim_meta = .{ .description = "Move cursor down (vim)" }; + pub fn add_cursor_down(self: *Self, _: Context) Result { try self.push_cursor(); const primary = self.get_primary(); @@ -3314,6 +3356,18 @@ pub const Editor = struct { } pub const move_scroll_half_page_up_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 = .{ .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(); @@ -3326,6 +3380,18 @@ pub const Editor = struct { } pub const move_scroll_half_page_down_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 = .{ .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(root, smart_move_cursor_begin); @@ -3406,6 +3472,16 @@ pub const Editor = struct { } pub const cancel_meta = .{ .description = "Cancel current action" }; + pub fn select_line_vim(self: *Self, _: Context) Result { + const primary = self.get_primary(); + const root = self.buf_root() catch return; + primary.disable_selection(root, self.metrics); + self.selection_mode = .line; + try self.select_line_around_cursor(primary); + self.clamp(); + } + pub const select_line_vim_meta = .{ .description = "Select the line around the cursor (vim)" }; + pub fn select_up(self: *Self, _: Context) Result { const root = try self.buf_root(); try self.with_selections_const(root, move_cursor_up); @@ -3600,6 +3676,14 @@ pub const Editor = struct { cursel.cursor = sel.end; } + fn select_line_around_cursor(self: *Self, cursel: *CurSel) !void { + const root = try self.buf_root(); + const sel = try cursel.enable_selection(root, self.metrics); + sel.normalize(); + try move_cursor_begin(root, &sel.begin, self.metrics); + try move_cursor_end(root, &sel.end, self.metrics); + } + fn selection_reverse(_: Buffer.Root, cursel: *CurSel) !void { if (cursel.selection) |*sel| { sel.reverse();