From 21540af8efe299caf071e1f0fac1bc91d3bdc129 Mon Sep 17 00:00:00 2001 From: lulvz Date: Sun, 2 Feb 2025 02:04:44 +0000 Subject: [PATCH 1/9] feat(editor): start implementing is_word_boundary_left_vim for vim-like word detection --- src/tui/editor.zig | 64 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index b537f55..a2a47c2 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -1939,6 +1939,68 @@ pub const Editor = struct { return false; } + fn is_not_word_char_vim(c: []const u8) bool { + if (c.len == 0) return true; + return switch (c[0]) { + '=' => 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, + else => false, + }; + } + + fn is_non_word_char_at_cursor_vim(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { + return cursor.test_at(root, is_not_word_char_vim, metrics); + } + + fn is_white_space(c: []const u8) bool { + if (c.len == 0) return true; + return switch (c[0]) { + ' ' => true, + else => false + }; + } + + fn is_white_space_at_cursor(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { + return cursor.test_at(root, is_white_space, metrics); + } + + fn is_word_boundary_left_vim(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { + if(is_white_space_at_cursor(root, cursor, metrics)) return false; + var next = cursor.*; + next.move_left(root, metrics) catch return true; + + // right now this doesn't work + // ["enter_vim_mode"], abc + // jumping from the last " forward once using this command goes to the end of the line + // it should go to the start of abc + const curr_is_non_word = is_non_word_char_at_cursor_vim(root, cursor, metrics); + const prev_is_non_word = is_non_word_char_at_cursor_vim(root, &next, metrics); + return curr_is_non_word != prev_is_non_word; + } + fn is_non_word_boundary_left(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { if (cursor.col == 0) return true; @@ -2575,7 +2637,7 @@ pub const Editor = struct { pub fn move_cursor_word_right_vim(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { try move_cursor_right(root, cursor, metrics); - move_cursor_right_until(root, cursor, is_word_boundary_left, metrics); + move_cursor_right_until(root, cursor, is_word_boundary_left_vim, metrics); } pub fn move_cursor_word_right_space(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { From 4b8ba080d7ff6a5251415e0dc3bba9f09f7add05 Mon Sep 17 00:00:00 2001 From: lulvz Date: Sun, 2 Feb 2025 02:05:43 +0000 Subject: [PATCH 2/9] feat(vim): added visual mode movement keybindings to json --- src/keybind/builtin/vim.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index 4ef1158..a293330 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -14,12 +14,13 @@ ["w", "move_word_right_vim"], ["e", "move_word_right"], ["x", "delete_forward"], + ["s", ["delete_forward"], ["enter_mode", "insert"]], ["u", "undo"], ["j", "move_down"], ["k", "move_up"], ["l", "move_right_vim"], - ["h", "move_left"], + ["h", "move_left_vim"], ["", "move_right_vim"], ["i", "enter_mode", "insert"], @@ -73,7 +74,11 @@ "line_numbers": "relative", "cursor": "underline", "press": [ - ["", "enter_mode", "normal"] + ["", "enter_mode", "normal"], + ["k", "select_up"], + ["j", "select_down"], + ["h", "select_left"], + ["l", "select_right"] ] }, "insert": { From a3864224dd05f06763957693f2774647f2d04576 Mon Sep 17 00:00:00 2001 From: lulvz Date: Sun, 2 Feb 2025 13:08:15 +0000 Subject: [PATCH 3/9] feat(editor): implement vim-style word movement for cursor navigation --- src/keybind/builtin/vim.json | 2 +- src/tui/editor.zig | 32 +++++++++++++++++++------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index a293330..1b42dea 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -10,7 +10,7 @@ "line_numbers": "relative", "cursor": "block", "press": [ - ["b", "move_word_left"], + ["b", "move_word_left_vim"], ["w", "move_word_right_vim"], ["e", "move_word_right"], ["x", "delete_forward"], diff --git a/src/tui/editor.zig b/src/tui/editor.zig index a2a47c2..4d7b5e5 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -1945,8 +1945,6 @@ pub const Editor = struct { '=' => true, '"' => true, '\'' => true, - '\t' => true, - '\n' => true, '/' => true, '\\' => true, '*' => true, @@ -1976,11 +1974,7 @@ pub const Editor = struct { } fn is_white_space(c: []const u8) bool { - if (c.len == 0) return true; - return switch (c[0]) { - ' ' => true, - else => false - }; + return (c.len == 0) or (c[0] == ' '); } fn is_white_space_at_cursor(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { @@ -1992,13 +1986,12 @@ pub const Editor = struct { var next = cursor.*; next.move_left(root, metrics) catch return true; - // right now this doesn't work - // ["enter_vim_mode"], abc - // jumping from the last " forward once using this command goes to the end of the line - // it should go to the start of abc + const next_is_white_space = is_white_space_at_cursor(root, &next, metrics); + if(next_is_white_space) return true; + const curr_is_non_word = is_non_word_char_at_cursor_vim(root, cursor, metrics); - const prev_is_non_word = is_non_word_char_at_cursor_vim(root, &next, metrics); - return curr_is_non_word != prev_is_non_word; + const next_is_non_word = is_non_word_char_at_cursor_vim(root, &next, metrics); + return curr_is_non_word != next_is_non_word; } fn is_non_word_boundary_left(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { @@ -2619,6 +2612,11 @@ pub const Editor = struct { move_cursor_left_until(root, cursor, is_word_boundary_left, metrics); } + fn move_cursor_word_left_vim(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { + try move_cursor_left(root, cursor, metrics); + move_cursor_left_until(root, cursor, is_word_boundary_left_vim, metrics); + } + fn move_cursor_word_left_space(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { try move_cursor_left(root, cursor, metrics); var next = cursor.*; @@ -2661,6 +2659,14 @@ pub const Editor = struct { } pub const move_word_left_meta = .{ .description = "Move cursor left by word" }; + pub fn move_word_left_vim(self: *Self, _: Context) Result { + const root = try self.buf_root(); + self.with_cursors_const(root, move_cursor_word_left_vim) catch {}; + self.clamp(); + } + pub const move_word_left_vim_meta = .{ .description = "Move cursor left by word (vim)" }; + + pub fn move_word_right(self: *Self, _: Context) Result { const root = try self.buf_root(); self.with_cursors_const(root, move_cursor_word_right) catch {}; From 5d21fb07dc40f708265324d719f27a639ef48da7 Mon Sep 17 00:00:00 2001 From: lulvz Date: Sun, 2 Feb 2025 13:16:15 +0000 Subject: [PATCH 4/9] feat(vim): add move_word_right_end_vim function (correspondent to e in NORMAL mode) and update keybindings --- src/keybind/builtin/vim.json | 2 +- src/tui/editor.zig | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index 1b42dea..b069ed3 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -12,7 +12,7 @@ "press": [ ["b", "move_word_left_vim"], ["w", "move_word_right_vim"], - ["e", "move_word_right"], + ["e", "move_word_right_end_vim"], ["x", "delete_forward"], ["s", ["delete_forward"], ["enter_mode", "insert"]], ["u", "undo"], diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 4d7b5e5..6992ad6 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -2019,6 +2019,19 @@ pub const Editor = struct { return false; } + fn is_word_boundary_right_vim(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { + if(is_white_space_at_cursor(root, cursor, metrics)) return false; + var next = cursor.*; + next.move_right(root, metrics) catch return true; + + const next_is_white_space = is_white_space_at_cursor(root, &next, metrics); + if(next_is_white_space) return true; + + const curr_is_non_word = is_non_word_char_at_cursor_vim(root, cursor, metrics); + const next_is_non_word = is_non_word_char_at_cursor_vim(root, &next, metrics); + return curr_is_non_word != next_is_non_word; + } + fn is_non_word_boundary_right(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { const line_width = root.line_width(cursor.row, metrics) catch return true; if (cursor.col >= line_width) @@ -2638,6 +2651,11 @@ pub const Editor = struct { move_cursor_right_until(root, cursor, is_word_boundary_left_vim, metrics); } + pub fn move_cursor_word_right_end_vim(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { + try move_cursor_right(root, cursor, metrics); + move_cursor_right_until(root, cursor, is_word_boundary_right_vim, metrics); + } + pub fn move_cursor_word_right_space(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { var next = cursor.*; next.move_right(root, metrics) catch { @@ -2681,6 +2699,13 @@ pub const Editor = struct { } pub const move_word_right_vim_meta = .{ .description = "Move cursor right by word (vim)" }; + pub fn move_word_right_end_vim(self: *Self, _: Context) Result { + const root = try self.buf_root(); + self.with_cursors_const(root, move_cursor_word_right_end_vim) catch {}; + self.clamp(); + } + pub const move_word_right_end_vim_meta = .{ .description = "Move cursor right by end of word (vim)" }; + fn move_cursor_to_char_left(root: Buffer.Root, cursor: *Cursor, ctx: Context, metrics: Buffer.Metrics) error{Stop}!void { var egc: []const u8 = undefined; if (!(ctx.args.match(.{tp.extract(&egc)}) catch return error.Stop)) From 5269f2eff669d8f99bc21aa99f54d88cceb5eec3 Mon Sep 17 00:00:00 2001 From: lulvz Date: Sun, 2 Feb 2025 16:45:18 +0000 Subject: [PATCH 5/9] feat(editor): implement cut operations for internal clipboard buffer for vim --- src/keybind/builtin/vim.json | 12 ++++-- src/tui/editor.zig | 80 ++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index b069ed3..049a85e 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -13,8 +13,8 @@ ["b", "move_word_left_vim"], ["w", "move_word_right_vim"], ["e", "move_word_right_end_vim"], - ["x", "delete_forward"], - ["s", ["delete_forward"], ["enter_mode", "insert"]], + ["x", "cut_forward_internal"], + ["s", ["cut_forward_internal"], ["enter_mode", "insert"]], ["u", "undo"], ["j", "move_down"], @@ -48,7 +48,7 @@ ["G", "move_buffer_end"], ["d$", "delete_to_end"], - ["dd", "cut"], + ["dd", "cut_internal"], ["\"_dd", "delete_line"], ["yy", "copy_line"], @@ -78,7 +78,11 @@ ["k", "select_up"], ["j", "select_down"], ["h", "select_left"], - ["l", "select_right"] + ["l", "select_right"], + + ["x", ["cut_forward_internal"], ["enter_mode", "normal"]], + ["d", ["cut_forward_internal"], ["enter_mode", "normal"]], + ["s", ["cut_forward_internal"], ["enter_mode", "insert"]] ] }, "insert": { diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 6992ad6..b3426f0 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -2364,6 +2364,12 @@ pub const Editor = struct { } } + fn set_clipboard_internal(self: *Self, text: []const u8) void { + if (self.clipboard) |old| + self.allocator.free(old); + self.clipboard = text; + } + fn copy_selection(root: Buffer.Root, sel: Selection, text_allocator: Allocator, metrics: Buffer.Metrics) ![]u8 { var size: usize = 0; _ = try root.get_range(sel, null, &size, null, metrics); @@ -2411,6 +2417,71 @@ pub const Editor = struct { return root_; } + fn cut_to(self: *Self, move: cursor_operator_const, root_: Buffer.Root) !struct { []const u8, Buffer.Root } { + var all_stop = true; + var root = root_; + + // For each cursor, collect what would be deleted + var text = std.ArrayList(u8).init(self.allocator); + var first = true; + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + if (cursel.selection) |_| { + const cut_text, root = self.cut_selection(root, cursel) catch continue; + all_stop = false; + if (first) { + first = false; + } else { + try text.appendSlice("\n"); + } + try text.appendSlice(cut_text); + continue; + } + + with_selection_const(root, move, cursel, self.metrics) catch continue; + const cut_text, root = self.cut_selection(root, cursel) catch continue; + + if (first) { + first = false; + } else { + try text.appendSlice("\n"); + } + try text.appendSlice(cut_text); + all_stop = false; + }; + + if (all_stop) + return error.Stop; + return .{text.items, root}; + } + + pub fn cut_internal(self: *Self, _: Context) Result { + const primary = self.get_primary(); + const b = self.buf_for_update() catch return; + var root = b.root; + if (self.cursels.items.len == 1) + if (primary.selection) |_| {} else { + const sel = primary.enable_selection(root, self.metrics) catch return; + 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); + }; + var first = true; + var text = std.ArrayList(u8).init(self.allocator); + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + const cut_text, root = try self.cut_selection(root, cursel); + if (first) { + first = false; + } else { + try text.appendSlice("\n"); + } + try text.appendSlice(cut_text); + }; + try self.update_buf(root); + self.set_clipboard_internal(text.items); + self.clamp(); + } + pub const cut_internal_meta = .{ .description = "Cut selection or current line to internal clipboard" }; + pub fn cut(self: *Self, _: Context) Result { const primary = self.get_primary(); const b = self.buf_for_update() catch return; @@ -2524,6 +2595,15 @@ pub const Editor = struct { } pub const delete_backward_meta = .{ .description = "Delete previous character" }; + pub fn cut_forward_internal(self: *Self, _: Context) Result { + const b = try self.buf_for_update(); + const text, const root= try self.cut_to(move_cursor_right, b.root); + self.set_clipboard_internal(text); + try self.update_buf(root); + self.clamp(); + } + pub const cut_forward_internal_meta = .{ .description = "Cut next character" }; + pub fn delete_word_left(self: *Self, _: Context) Result { const b = try self.buf_for_update(); const root = try self.delete_to(move_cursor_word_left_space, b.root, b.allocator); From f9f57a66165a41cdea9ad7431b1c9783120b6c76 Mon Sep 17 00:00:00 2001 From: lulvz Date: Sun, 2 Feb 2025 17:09:11 +0000 Subject: [PATCH 6/9] feat(vim): update selection mode to inclusive for NORMAL and VISUAL keybindings --- src/keybind/builtin/vim.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index 049a85e..4abf580 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -9,6 +9,7 @@ "name": "NORMAL", "line_numbers": "relative", "cursor": "block", + "selection": "inclusive", "press": [ ["b", "move_word_left_vim"], ["w", "move_word_right_vim"], @@ -73,6 +74,7 @@ "name": "VISUAL", "line_numbers": "relative", "cursor": "underline", + "selection": "inclusive", "press": [ ["", "enter_mode", "normal"], ["k", "select_up"], From 87fb11eaa1afcb3d81543bddd2cfc5b98e19abbc Mon Sep 17 00:00:00 2001 From: lulvz Date: Sun, 2 Feb 2025 21:59:30 +0000 Subject: [PATCH 7/9] feat(vim): update selection mode to normal (while inclusive doesn't work) and add cut operations for word navigation --- src/keybind/builtin/vim.json | 23 ++++++++++++++-------- src/tui/editor.zig | 37 ++++++++++++++++++++++++++---------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index 4abf580..eeba29f 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -9,7 +9,7 @@ "name": "NORMAL", "line_numbers": "relative", "cursor": "block", - "selection": "inclusive", + "selection": "normal", "press": [ ["b", "move_word_left_vim"], ["w", "move_word_right_vim"], @@ -32,15 +32,15 @@ ["O", ["smart_insert_line_before"], ["enter_mode", "insert"]], ["v", "enter_mode", "visual"], + ["V", ["move_begin"], ["enter_mode", "visual"], ["select_end"]], - ["/", "find"], ["n", "goto_next_match"], ["0", "move_begin"], + ["^", "smart_move_begin"], ["$", "move_end"], [":", "open_command_palette"], ["p", "paste"], - ["gd", "goto_definition"], ["gi", "goto_implementation"], ["gy", "goto_type_definition"], ["gg", "move_buffer_begin"], @@ -49,6 +49,8 @@ ["G", "move_buffer_end"], ["d$", "delete_to_end"], + ["dw", "cut_word_right_vim"], + ["db", "cut_word_left_vim"], ["dd", "cut_internal"], ["\"_dd", "delete_line"], @@ -62,10 +64,12 @@ ["", "jump_forward"], ["", "redo"], + ["/", "find"], + ["", "TODO"], - ["", "smart_insert_line_after"], - ["", "smart_insert_line"] + ["", ["move_down"], ["move_begin"]], + ["", ["move_down"], ["move_begin"]] ] }, "visual": { @@ -73,8 +77,8 @@ "on_match_failure": "ignore", "name": "VISUAL", "line_numbers": "relative", - "cursor": "underline", - "selection": "inclusive", + "cursor": "block", + "selection": "normal", "press": [ ["", "enter_mode", "normal"], ["k", "select_up"], @@ -97,7 +101,10 @@ ["", "enter_mode", "normal"], ["", "delete_forward"], ["", "delete_backward"], - ["", "insert_line_after"] + ["", "smart_insert_line"], + + ["", "delete_word_left"], + [" Date: Mon, 3 Feb 2025 20:09:12 +0000 Subject: [PATCH 8/9] feat(vim): add vim-style cut, copy, and paste operations that use internal clipboard only --- src/keybind/builtin/vim.json | 16 +++-- src/tui/editor.zig | 122 ++++++++++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 9 deletions(-) diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index eeba29f..761744b 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -13,6 +13,8 @@ "press": [ ["b", "move_word_left_vim"], ["w", "move_word_right_vim"], + ["W", "move_word_right"], + ["B", "move_word_left"], ["e", "move_word_right_end_vim"], ["x", "cut_forward_internal"], ["s", ["cut_forward_internal"], ["enter_mode", "insert"]], @@ -39,7 +41,7 @@ ["^", "smart_move_begin"], ["$", "move_end"], [":", "open_command_palette"], - ["p", "paste"], + ["p", "paste_internal_vim"], ["gi", "goto_implementation"], ["gy", "goto_type_definition"], @@ -51,10 +53,10 @@ ["d$", "delete_to_end"], ["dw", "cut_word_right_vim"], ["db", "cut_word_left_vim"], - ["dd", "cut_internal"], + ["dd", "cut_internal_vim"], ["\"_dd", "delete_line"], - ["yy", "copy_line"], + ["yy", ["copy_line_internal_vim"], ["cancel"]], ["", "move_scroll_page_up"], ["", "move_scroll_page_down"], @@ -86,9 +88,11 @@ ["h", "select_left"], ["l", "select_right"], - ["x", ["cut_forward_internal"], ["enter_mode", "normal"]], - ["d", ["cut_forward_internal"], ["enter_mode", "normal"]], - ["s", ["cut_forward_internal"], ["enter_mode", "insert"]] + ["y", ["copy_internal_vim"], ["cancel"], ["enter_mode", "normal"]], + + ["x", ["cut_forward_internal"], ["cancel"], ["enter_mode", "normal"]], + ["d", ["cut_forward_internal"], ["cancel"], ["enter_mode", "normal"]], + ["s", ["cut_forward_internal"], ["cancel"], ["enter_mode", "insert"]] ] }, "insert": { diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 1ff5e2b..abe1e4e 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -2417,6 +2417,16 @@ pub const Editor = struct { return root_; } + fn insert_line_vim(self: *Self, root: Buffer.Root, cursel: *CurSel, s: []const u8, allocator: Allocator) !Buffer.Root { + var root_ = if (cursel.selection) |_| try self.delete_selection(root, cursel, allocator) else root; + const cursor = &cursel.cursor; + const begin = cursel.cursor; + _, _, root_ = try root_.insert_chars(cursor.row, cursor.col, s, allocator, self.metrics); + cursor.target = cursor.col; + self.nudge_insert(.{ .begin = begin, .end = cursor.* }, cursel, s.len); + return root_; + } + fn cut_to(self: *Self, move: cursor_operator_const, root_: Buffer.Root) !struct { []const u8, Buffer.Root } { var all_stop = true; var root = root_; @@ -2453,19 +2463,20 @@ pub const Editor = struct { return .{text.items, root}; } - pub fn cut_internal(self: *Self, _: Context) Result { + pub fn cut_internal_vim(self: *Self, _: Context) Result { const primary = self.get_primary(); const b = self.buf_for_update() catch return; var root = b.root; + var text = std.ArrayList(u8).init(self.allocator); if (self.cursels.items.len == 1) if (primary.selection) |_| {} else { + try text.appendSlice("\n"); const sel = primary.enable_selection(root, self.metrics) catch return; 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); }; var first = true; - var text = std.ArrayList(u8).init(self.allocator); for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { const cut_text, root = try self.cut_selection(root, cursel); if (first) { @@ -2479,7 +2490,7 @@ pub const Editor = struct { self.set_clipboard_internal(text.items); self.clamp(); } - pub const cut_internal_meta = .{ .description = "Cut selection or current line to internal clipboard" }; + pub const cut_internal_vim_meta = .{ .description = "Cut selection or current line to internal clipboard (vim)" }; pub fn cut(self: *Self, _: Context) Result { const primary = self.get_primary(); @@ -2543,6 +2554,66 @@ pub const Editor = struct { } pub const copy_meta = .{ .description = "Copy selection to clipboard" }; + pub fn copy_internal_vim(self: *Self, _: Context) Result { + const root = self.buf_root() catch return; + var first = true; + var text = std.ArrayList(u8).init(self.allocator); + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + if (cursel.selection) |sel| { + const copy_text = try copy_selection(root, sel, self.allocator, self.metrics); + if (first) { + first = false; + } else { + try text.appendSlice("\n"); + } + try text.appendSlice(copy_text); + } + }; + if (text.items.len > 0) { + if (text.items.len > 100) { + self.logger.print("copy:{s}...", .{std.fmt.fmtSliceEscapeLower(text.items[0..100])}); + } else { + self.logger.print("copy:{s}", .{std.fmt.fmtSliceEscapeLower(text.items)}); + } + self.set_clipboard_internal(text.items); + } + } + pub const copy_internal_vim_meta = .{ .description = "Copy selection to internal clipboard (vim)" }; + + pub fn copy_line_internal_vim(self: *Self, _: Context) Result { + const primary = self.get_primary(); + const root = self.buf_root() catch return; + var first = true; + var text = std.ArrayList(u8).init(self.allocator); + try text.appendSlice("\n"); + if (primary.selection) |_| {} else { + const sel = primary.enable_selection(root, self.metrics) catch return; + 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); + } + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + if (cursel.selection) |sel| { + const copy_text = try copy_selection(root, sel, self.allocator, self.metrics); + if (first) { + first = false; + } else { + try text.appendSlice("\n"); + } + try text.appendSlice(copy_text); + } + }; + if (text.items.len > 0) { + if (text.items.len > 100) { + self.logger.print("copy:{s}...", .{std.fmt.fmtSliceEscapeLower(text.items[0..100])}); + } else { + self.logger.print("copy:{s}", .{std.fmt.fmtSliceEscapeLower(text.items)}); + } + self.set_clipboard_internal(text.items); + } + } + pub const copy_line_internal_vim_meta = .{ .description = "Copy line to internal clipboard (vim)" }; + pub fn paste(self: *Self, ctx: Context) Result { var text: []const u8 = undefined; if (!(ctx.args.buf.len > 0 and try ctx.args.match(.{tp.extract(&text)}))) { @@ -2578,6 +2649,51 @@ pub const Editor = struct { } pub const paste_meta = .{ .description = "Paste from internal clipboard" }; + pub fn paste_internal_vim(self: *Self, ctx: Context) Result { + var text: []const u8 = undefined; + if (!(ctx.args.buf.len > 0 and try ctx.args.match(.{tp.extract(&text)}))) { + if (self.clipboard) |text_| text = text_ else return; + } + + self.logger.print("paste: {d} bytes", .{text.len}); + const b = try self.buf_for_update(); + var root = b.root; + + if(std.mem.eql(u8, text[text.len-1..], "\n")) text = text[0..text.len-1]; + + if (std.mem.indexOfScalar(u8, text, '\n')) |idx| { + if(idx == 0) { + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + try move_cursor_end(root, &cursel.cursor, self.metrics); + root = try self.insert(root, cursel, "\n", b.allocator); + }; + text = text[1..]; + } + if (self.cursels.items.len == 1) { + const primary = self.get_primary(); + root = try self.insert_line_vim(root, primary, text, b.allocator); + } else { + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + root = try self.insert_line_vim(root, cursel, text, b.allocator); + }; + } + } else { + if (self.cursels.items.len == 1) { + const primary = self.get_primary(); + root = try self.insert(root, primary, text, b.allocator); + } else { + for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + root = try self.insert(root, cursel, text, b.allocator); + }; + } + } + + try self.update_buf(root); + self.clamp(); + self.need_render(); + } + pub const paste_internal_vim_meta = .{ .description = "Paste from internal clipboard (vim)" }; + pub fn delete_forward(self: *Self, _: Context) Result { const b = try self.buf_for_update(); const root = try self.delete_to(move_cursor_right, b.root, b.allocator); From 5c352be7d05775b1ca9f128ccadb158bd089a2fe Mon Sep 17 00:00:00 2001 From: lulvz Date: Tue, 4 Feb 2025 20:40:18 +0000 Subject: [PATCH 9/9] feat(vim): add goto definition keybind back, fix cut word descriptions, removed unnecessary is_not_word_char_vim --- src/keybind/builtin/vim.json | 1 + src/tui/editor.zig | 48 ++++++------------------------------ 2 files changed, 8 insertions(+), 41 deletions(-) diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index 761744b..3291471 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -43,6 +43,7 @@ [":", "open_command_palette"], ["p", "paste_internal_vim"], + ["gd", "goto_definition"], ["gi", "goto_implementation"], ["gy", "goto_type_definition"], ["gg", "move_buffer_begin"], diff --git a/src/tui/editor.zig b/src/tui/editor.zig index abe1e4e..23f2442 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -1939,42 +1939,8 @@ pub const Editor = struct { return false; } - fn is_not_word_char_vim(c: []const u8) bool { - if (c.len == 0) return true; - return switch (c[0]) { - '=' => true, - '"' => true, - '\'' => true, - '/' => true, - '\\' => true, - '*' => true, - ':' => true, - '.' => true, - ',' => true, - '(' => true, - ')' => true, - '{' => true, - '}' => true, - '[' => true, - ']' => true, - ';' => true, - '|' => true, - '!' => true, - '?' => true, - '&' => true, - '-' => true, - '<' => true, - '>' => true, - else => false, - }; - } - - fn is_non_word_char_at_cursor_vim(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { - return cursor.test_at(root, is_not_word_char_vim, metrics); - } - fn is_white_space(c: []const u8) bool { - return (c.len == 0) or (c[0] == ' '); + return (c.len == 0) or (c[0] == ' ') or (c[0] == '\t'); } fn is_white_space_at_cursor(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { @@ -1989,8 +1955,8 @@ pub const Editor = struct { const next_is_white_space = is_white_space_at_cursor(root, &next, metrics); if(next_is_white_space) return true; - const curr_is_non_word = is_non_word_char_at_cursor_vim(root, cursor, metrics); - const next_is_non_word = is_non_word_char_at_cursor_vim(root, &next, metrics); + const curr_is_non_word = is_non_word_char_at_cursor(root, cursor, metrics); + const next_is_non_word = is_non_word_char_at_cursor(root, &next, metrics); return curr_is_non_word != next_is_non_word; } @@ -2027,8 +1993,8 @@ pub const Editor = struct { const next_is_white_space = is_white_space_at_cursor(root, &next, metrics); if(next_is_white_space) return true; - const curr_is_non_word = is_non_word_char_at_cursor_vim(root, cursor, metrics); - const next_is_non_word = is_non_word_char_at_cursor_vim(root, &next, metrics); + const curr_is_non_word = is_non_word_char_at_cursor(root, cursor, metrics); + const next_is_non_word = is_non_word_char_at_cursor(root, &next, metrics); return curr_is_non_word != next_is_non_word; } @@ -2734,7 +2700,7 @@ pub const Editor = struct { try self.update_buf(root); self.clamp(); } - pub const cut_word_left_vim_meta = .{ .description = "Cut next character to internal clipboard" }; + pub const cut_word_left_vim_meta = .{ .description = "Cut previous word to internal clipboard (vim)" }; pub fn delete_word_right(self: *Self, _: Context) Result { const b = try self.buf_for_update(); @@ -2751,7 +2717,7 @@ pub const Editor = struct { try self.update_buf(root); self.clamp(); } - pub const cut_word_right_vim_meta = .{ .description = "Cut next character to internal clipboard" }; + pub const cut_word_right_vim_meta = .{ .description = "Cut next word to internal clipboard (vim)" }; pub fn delete_to_begin(self: *Self, _: Context) Result { const b = try self.buf_for_update();