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, diff --git a/src/buffer/Selection.zig b/src/buffer/Selection.zig index 78ec58aa..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); } @@ -19,6 +17,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 = .{ 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/keybind/builtin/helix.json b/src/keybind/builtin/helix.json index a7847173..9b64d3c7 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"], @@ -22,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"], @@ -58,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"], @@ -93,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"], @@ -110,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"], @@ -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"], @@ -281,8 +281,7 @@ "name": "SEL", "line_numbers": "relative", "cursor": "block", - "selection": "inclusive", - "init_command": ["enable_selection"], + "init_command": ["init_helix_select_mode"], "press": [ ["ctrl+b", "select_page_up"], @@ -431,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"], @@ -441,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"], @@ -460,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"], @@ -472,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"], @@ -507,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], diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index 54291a0c..73898455 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"], @@ -76,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"], @@ -121,7 +120,6 @@ "name": "VISUAL", "line_numbers": "relative", "cursor": "block", - "selection": "normal", "init_command": ["enable_selection"], "press": [ ["", ["cancel"], ["enter_mode", "normal"]], @@ -151,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"], @@ -186,7 +184,6 @@ "name": "VISUAL LINE", "line_numbers": "relative", "cursor": "block", - "selection": "normal", "press": [ ["", ["cancel"], ["enter_mode", "normal"]], ["k", "select_up"], @@ -199,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"], @@ -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..67641ee2 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); @@ -971,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')); diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 5e34ca39..eaea46e9 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -115,63 +115,38 @@ 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(); + 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; } - 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), - }; + pub fn to_selection(self: *const Self) Selection { + return self.selection orelse Selection.from_cursor(&self.cursor); } - fn to_selection_normal(self: *const Self) Selection { - return if (self.selection) |sel| sel else Selection.from_cursor(&self.cursor); + 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); } - 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; - }; - } - - 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) 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) 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)) { @@ -1658,7 +1633,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, @@ -1771,7 +1746,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()); } @@ -1901,7 +1876,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| { @@ -1955,7 +1930,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(); @@ -1966,7 +1941,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(); @@ -1979,7 +1954,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(); @@ -1989,7 +1964,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 { @@ -2004,10 +1979,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 { @@ -2036,10 +2011,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 { @@ -2053,12 +2028,12 @@ 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; } - 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 { @@ -2214,48 +2189,60 @@ 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; 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 +2266,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 { @@ -2358,7 +2351,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; @@ -2371,22 +2364,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); } @@ -2396,7 +2373,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; } @@ -2419,7 +2396,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; } @@ -2428,32 +2405,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(); } @@ -2470,24 +2433,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) { @@ -2500,7 +2445,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 +2459,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 +2471,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 +2485,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 +2513,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 +2703,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 +2856,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); @@ -3202,14 +3147,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); @@ -3237,7 +3174,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, @@ -3260,28 +3197,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); @@ -3568,13 +3483,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; @@ -3595,13 +3503,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; @@ -3657,7 +3558,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 +3652,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 +3687,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 +3741,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 +3845,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; @@ -4039,54 +3940,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); @@ -4169,8 +4022,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" }; @@ -4376,30 +4228,12 @@ 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(); 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 +4243,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 +4253,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 +4317,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 +4336,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 +4347,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 +4356,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 +4370,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 +4404,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 +4438,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 +4514,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 +4700,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 +5303,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; @@ -5634,18 +5465,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(); @@ -6161,7 +5980,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 +6009,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 +6038,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 +6067,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 4ecaca11..69daf70b 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -7,17 +7,25 @@ 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"); 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 { @@ -25,59 +33,60 @@ 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/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", .{}); @@ -85,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", .{}); @@ -93,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", .{}); @@ -108,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); @@ -163,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; @@ -178,318 +187,425 @@ 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: *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(_: *void, _: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = try ed.buf_root(); - 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(_: *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; - 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(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = try ed.buf_root(); - + 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| { - const sel = cursel.enable_selection_normal(); + 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" }; - pub fn move_next_word_start(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, Editor.move_cursor_word_right_vim); + 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.enable_selection_inclusive(root, self.metrics); + } + } + pub const init_helix_select_mode_meta: Meta = .{}; + + 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(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, Editor.move_cursor_word_right_vim); + 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(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_long_word_right); + 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(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, move_cursor_long_word_right); + 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(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_word_left_helix); + pub fn move_prev_word_start(self: *Self, ctx: Context) Result { + 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(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, move_cursor_word_left_helix); + pub fn extend_prev_word_start(self: *Self, ctx: Context) Result { + 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} }; - pub fn move_prev_long_word_start(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_long_word_left); + 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(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, move_cursor_long_word_left); + 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(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_word_right_end_helix); + 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(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, move_cursor_word_right_end_helix); + 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(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_long_word_right_end); + 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(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, move_cursor_long_word_right_end); + 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(_: *void, _: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const b = try ed.buf_for_update(); + 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(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = try ed.buf_root(); - + 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(root, ed.metrics); + 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(root, ed.metrics); + cursel.check_selection(); }; } - ed.clamp(); + self.clamp(); } 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(); - + 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; } } - cursel.check_selection(root, ed.metrics); + 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(_: *void, ctx: Ctx) 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(_: *void, ctx: Ctx) 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(_: *void, ctx: Ctx) 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(_: *void, ctx: Ctx) 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(_: *void, ctx: Ctx) 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(_: *void, ctx: Ctx) 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(_: *void, ctx: Ctx) 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(_: *void, ctx: Ctx) 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(_: *void, ctx: Ctx) Result { - var action: []const u8 = ""; + 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; - 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); + 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(_: *void, ctx: Ctx) Result { - var action: []const u8 = ""; + 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; - 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); + 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(_: *void, _: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch 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(_: *void, ctx: Ctx) 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(_: *void, ctx: Ctx) 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(_: *void, ctx: Ctx) 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(_: *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; - 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(); + pub fn replace_with_character_helix(self: *Self, ctx: Context) Result { + const b = try self.buf_for_update(); + var root = b.root; + 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" }; + + 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 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, + }; +} + +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 = cursel.to_selection_inclusive(root, metrics); + sel.reverse(); + 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| { + if (cursel.selection) |*sel| sel.begin = pre_sel.begin; + }; + try move_cursor_prev_word_start(root, cursel, metrics); +} + 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 @@ -526,38 +642,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) command.Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = try ed.buf_root(); +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| { - cursel.selection = null; + 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, self.metrics); + if (direction == .backwards) { + 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, self.metrics) catch continue; + } else { + 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, 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) command.Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = try ed.buf_root(); +fn extend_to_word(self: *Self, ctx: command.Context, move: Editor.cursor_operator_const, _: Direction) command.Result { + const root = try self.buf_root(); - ed.with_selections_const_repeat(root, move, ctx) catch {}; - ed.clamp(); + var repeat: usize = 1; + _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; + 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, self.metrics); + } + sel.begin.col = if (sel.is_reversed()) pivot +| 1 else pivot; + cursel.cursor = sel.end; + }; + + self.clamp(); } -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; - 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 { @@ -567,7 +706,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; @@ -580,7 +719,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; @@ -601,7 +740,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)) { @@ -616,7 +755,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; @@ -657,7 +796,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; @@ -670,7 +809,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; @@ -687,7 +826,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; @@ -700,7 +839,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; @@ -714,7 +853,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; @@ -727,7 +866,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; @@ -744,7 +883,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; @@ -760,7 +899,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; @@ -798,7 +937,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| { @@ -818,7 +957,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 }; } @@ -830,7 +969,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 { @@ -910,9 +1048,9 @@ 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(root, ed.metrics); + const sel = cursel.enable_selection(); sel.normalize(); var row = sel.begin.row; const is_multiline = sel.begin.row != sel.end.row; @@ -967,7 +1105,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(); @@ -993,7 +1131,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 @@ -1013,7 +1151,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 { @@ -1130,9 +1268,10 @@ 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 mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() 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; const clipboard: []const tui.ClipboardEntry = if (ctx.args.buf.len > 0 and try ctx.args.match(.{tp.extract(&text_)})) @@ -1141,32 +1280,29 @@ 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; } - 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 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 { @@ -1180,7 +1316,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()) { @@ -1189,7 +1325,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; } @@ -1204,6 +1340,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/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(); diff --git a/src/tui/mode/vim.zig b/src/tui/mode/vim.zig index 38eacbee..22361345 100644 --- a/src/tui/mode/vim.zig +++ b/src/tui/mode/vim.zig @@ -1,12 +1,21 @@ const std = @import("std"); const command = @import("command"); +const tp = @import("thespian"); 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 +23,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 +99,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 +108,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 +117,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 +125,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 +134,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 +142,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 +150,124 @@ 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} }; + + 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 { + 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); +} diff --git a/src/tui/tui.zig b/src/tui/tui.zig index b9f1f2d4..f3e5d821 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 { @@ -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 {};