diff --git a/lua/nvim-paredit/api/motions.lua b/lua/nvim-paredit/api/motions.lua index e24ee1f..9afc4df 100644 --- a/lua/nvim-paredit/api/motions.lua +++ b/lua/nvim-paredit/api/motions.lua @@ -54,7 +54,8 @@ local function get_next_node_from_cursor(lang, reversed) end end -function M._move_to_element(count, reversed, is_head) +function M._move_to_element(count, reversed, is_head, is_exclusive) + is_exclusive = is_exclusive or false is_head = is_head or false local lang = langs.get_language_api() @@ -97,16 +98,26 @@ function M._move_to_element(count, reversed, is_head) if is_in_middle then count = count - 1 end - local sibling = traversal_fn(current_node, { + local sibling, count_left = traversal_fn(current_node, { lang = lang, count = count, }) + if sibling then - if is_head then + if is_head and count_left > 1 and not reversed then + is_head = false + is_exclusive = false + next_pos = { sibling:end_() } + elseif is_head then next_pos = { sibling:start() } else next_pos = { sibling:end_() } end + elseif is_head and not reversed then + -- convert head to tail motion + is_head = false + is_exclusive = false + next_pos = { current_node:end_() } end end @@ -114,10 +125,19 @@ function M._move_to_element(count, reversed, is_head) return end + local offset = 0 + if is_exclusive then + if reversed then + offset = 1 + else + offset = -1 + end + end + if is_head then - cursor_pos = { next_pos[1] + 1, next_pos[2] } + cursor_pos = { next_pos[1] + 1, next_pos[2] + offset } else - cursor_pos = { next_pos[1] + 1, next_pos[2] - 1 } + cursor_pos = { next_pos[1] + 1, next_pos[2] - 1 + offset } end vim.api.nvim_win_set_cursor(0, cursor_pos) @@ -125,11 +145,14 @@ end -- When in operator-pending mode (`o` or `no`) then we need to switch to -- visual mode in order for the operator to apply over a range of text. +-- returns true if operator mode is on, false otherwise local function ensure_visual_if_operator_pending() local mode = vim.api.nvim_get_mode().mode if mode == "o" or mode == "no" then common.ensure_visual_mode() + return true end + return false end local function move_to_form_edge(form_node, direction, lang) @@ -196,10 +219,12 @@ function M.move_to_next_element_tail() M._move_to_element(count, false, false) end +-- also jumps to current element tail if there is no +-- next element function M.move_to_next_element_head() local count = vim.v.count1 - ensure_visual_if_operator_pending() - M._move_to_element(count, false, true) + local is_operator = ensure_visual_if_operator_pending() + M._move_to_element(count, false, true, is_operator) end function M.move_to_parent_form_start() diff --git a/lua/nvim-paredit/utils/traversal.lua b/lua/nvim-paredit/utils/traversal.lua index 5e30356..b8857ed 100644 --- a/lua/nvim-paredit/utils/traversal.lua +++ b/lua/nvim-paredit/utils/traversal.lua @@ -81,7 +81,7 @@ end local function get_sibling_ignoring_comments(node, opts) local sibling = opts.sibling_fn(node) if not sibling then - return opts.sibling or nil + return opts.sibling or nil, opts.count + 1 end if sibling:extra() or opts.lang.node_is_comment(sibling) then @@ -94,7 +94,7 @@ local function get_sibling_ignoring_comments(node, opts) return get_sibling_ignoring_comments(sibling, new_opts) end - return sibling + return sibling, opts.count end function M.get_next_sibling_ignoring_comments(node, opts) diff --git a/tests/nvim-paredit/motion_spec.lua b/tests/nvim-paredit/motion_spec.lua index 7541684..f1c61dc 100644 --- a/tests/nvim-paredit/motion_spec.lua +++ b/tests/nvim-paredit/motion_spec.lua @@ -55,10 +55,17 @@ describe("motions", function() expect({ cursor = { 1, 15 }, }) + end) + + it("should jump to current element's tail if there is no next element", function() + prepare_buffer({ + content = "(aa (bb) @(cc) #{1})", + cursor = { 1, 15 }, + }) paredit.move_to_next_element_head() expect({ - cursor = { 1, 15 }, + cursor = { 1, 18 }, }) end) diff --git a/tests/nvim-paredit/operator_motion_spec.lua b/tests/nvim-paredit/operator_motion_spec.lua index 202e801..87c68e1 100644 --- a/tests/nvim-paredit/operator_motion_spec.lua +++ b/tests/nvim-paredit/operator_motion_spec.lua @@ -8,7 +8,7 @@ local defaults = require("nvim-paredit.defaults") describe("motions with operator pending", function() before_each(function() keybindings.setup_keybindings({ - keys = defaults.default_keys + keys = defaults.default_keys, }) end) @@ -29,6 +29,42 @@ describe("motions with operator pending", function() }) end) + it("should delete til next", function() + prepare_buffer({ + content = "(a a) (b b)", + cursor = { 1, 0 }, + }) + feedkeys("d") + expect({ + content = "(b b)", + cursor = { 1, 0 }, + }) + end) + + it("should delete form if there is no next", function() + prepare_buffer({ + content = "(b b)", + cursor = { 1, 0 }, + }) + feedkeys("d") + expect({ + content = "", + cursor = { 1, 0 }, + }) + end) + + it("should delete 2 forms within parent form and join up", function() + prepare_buffer({ + content = {"[(a a) (b b) (c c)]"}, + cursor = { 1, 1 }, + }) + feedkeys("2d") + expect({ + content = "[(c c)]", + cursor = { 1, 1 }, + }) + end) + it("should delete next form (multiline)", function() prepare_buffer({ content = { "(a a)", ";; comment", "(b b)" }, @@ -91,7 +127,7 @@ end) describe("motions with operator pending and v:count", function() before_each(function() keybindings.setup_keybindings({ - keys = defaults.default_keys + keys = defaults.default_keys, }) end)