From 572a96f3c85196411f658f3a48881a80898f3580 Mon Sep 17 00:00:00 2001 From: Saket Patel Date: Sat, 30 Sep 2023 15:03:34 +0530 Subject: [PATCH 1/4] feat(motion): allows to move to parent forms edges using `(, )` closes #34 --- lua/nvim-paredit/api/init.lua | 2 + lua/nvim-paredit/api/motions.lua | 56 +++++++++++++++++++++++++++ lua/nvim-paredit/defaults.lua | 15 ++++++++ tests/nvim-paredit/motion_spec.lua | 61 ++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+) diff --git a/lua/nvim-paredit/api/init.lua b/lua/nvim-paredit/api/init.lua index 66c2974..0189758 100644 --- a/lua/nvim-paredit/api/init.lua +++ b/lua/nvim-paredit/api/init.lua @@ -30,6 +30,8 @@ local M = { move_to_prev_element = common.deprecate(motions.move_to_prev_element_head, "use `api.move_to_prev_element_head`"), move_to_prev_element_head = motions.move_to_prev_element_head, move_to_prev_element_tail = motions.move_to_prev_element_tail, + move_to_prev_bracket = motions.move_to_parent_form_start, + move_to_next_bracket = motions.move_to_parent_form_end, select_around_form = selections.select_around_form, select_in_form = selections.select_in_form, diff --git a/lua/nvim-paredit/api/motions.lua b/lua/nvim-paredit/api/motions.lua index f08bf29..d42afe7 100644 --- a/lua/nvim-paredit/api/motions.lua +++ b/lua/nvim-paredit/api/motions.lua @@ -3,6 +3,8 @@ local common = require("nvim-paredit.utils.common") local ts = require("nvim-treesitter.ts_utils") local langs = require("nvim-paredit.lang") +local MOTION_DIRECTIONS = { LEFT = "left", RIGHT = "right" } + local M = {} -- When the cursor is placed on whitespace within a form then the node returned by @@ -130,6 +132,52 @@ local function ensure_visual_if_operator_pending() end end +local function move_to_form_edge(form_node, direction, lang) + if not form_node then + return + end + + local form_edges = lang.get_form_edges(form_node) + local final_cursor_pos = { + form_edges[direction].range[1] + 1, + form_edges[direction].range[2] + } + + vim.api.nvim_win_set_cursor(0, final_cursor_pos) +end + +local function is_cursor_at_form_edge(form_node, direction, cur_cursor_pos, lang) + local form_edges = lang.get_form_edges(form_node) + local edge_cursor_pos = { + form_edges[direction].range[1] + 1, + form_edges[direction].range[2] + } + + return common.compare_positions(edge_cursor_pos, cur_cursor_pos) == 0 +end + +local function move_to_parent_form_edge(direction) + local lang = langs.get_language_api() + local cur_node = ts.get_node_at_cursor() + + local nearest_form_node = traversal.find_nearest_form(cur_node, { lang = lang }) + if not nearest_form_node or nearest_form_node:type() == "source" then + return + end + + local cur_cursor_pos = vim.api.nvim_win_get_cursor(0) + local form_node_to_move_to = nearest_form_node + while is_cursor_at_form_edge(form_node_to_move_to, direction, cur_cursor_pos, lang) + do + form_node_to_move_to = form_node_to_move_to:parent() + if not form_node_to_move_to or form_node_to_move_to:type() == "source" then + return + end + end + + move_to_form_edge(form_node_to_move_to, direction, lang) +end + function M.move_to_prev_element_head() local count = vim.v.count1 ensure_visual_if_operator_pending() @@ -154,4 +202,12 @@ function M.move_to_next_element_head() M._move_to_element(count, false, true) end +function M.move_to_parent_form_start() + move_to_parent_form_edge(MOTION_DIRECTIONS.LEFT) +end + +function M.move_to_parent_form_end() + move_to_parent_form_edge(MOTION_DIRECTIONS.RIGHT) +end + return M diff --git a/lua/nvim-paredit/defaults.lua b/lua/nvim-paredit/defaults.lua index 00c5b91..c9939b1 100644 --- a/lua/nvim-paredit/defaults.lua +++ b/lua/nvim-paredit/defaults.lua @@ -43,6 +43,21 @@ M.default_keys = { mode = { "n", "x", "o", "v" }, }, + ["("] = { + api.move_to_prev_bracket, + "Previous bracket in form tree", + repeatable = false, + mode = { "n", "x", "v" }, + }, + + [")"] = { + api.move_to_next_bracket, + "Next bracket in form tree", + repeatable = false, + mode = { "n", "x", "v" }, + }, + + ["af"] = { api.select_around_form, "Around form", diff --git a/tests/nvim-paredit/motion_spec.lua b/tests/nvim-paredit/motion_spec.lua index b822caf..207312b 100644 --- a/tests/nvim-paredit/motion_spec.lua +++ b/tests/nvim-paredit/motion_spec.lua @@ -255,4 +255,65 @@ describe("motions", function() cursor = { 1, 4 }, }) end) + + it("should move to parent form start", function() + -- (aa (bb) @(|cc) #{1}) + prepare_buffer({ + content = "(aa (bb) @(cc) #{1})", + cursor = { 1, 11 }, + }) + + -- (aa (bb) @|(cc) #{1}) + internal_api.move_to_parent_form_start() + expect({ + cursor = { 1, 10 }, + }) + + -- (aa (bb) |@(cc) #{1}) + internal_api.move_to_parent_form_start() + expect({ + cursor = { 1, 9 }, + }) + + -- |(aa (bb) @(cc) #{1}) + internal_api.move_to_parent_form_start() + expect({ + cursor = { 1, 0 }, + }) + + -- |(aa (bb) @(cc) #{1}) + internal_api.move_to_parent_form_start() + expect({ + cursor = { 1, 0 }, + }) + + end) + + + it("should move to parent form end", function() + -- (aa (bb) |@(cc) #{1}) + prepare_buffer({ + content = "(aa (bb) @(cc) #{1})", + cursor = { 1, 9 }, + }) + + -- (aa (bb) @(cc|) #{1}) + internal_api.move_to_parent_form_end() + expect({ + cursor = { 1, 13 }, + }) + + -- (aa (bb) @(cc) #{1}|) + internal_api.move_to_parent_form_end() + expect({ + cursor = { 1, 19 }, + }) + + -- (aa (bb) @(cc) #{1}|) + internal_api.move_to_parent_form_end() + expect({ + cursor = { 1, 19 }, + }) + + end) end) From f90a4b48ba2812bcb930d468bb78357b57222aa9 Mon Sep 17 00:00:00 2001 From: Saket Patel Date: Sat, 30 Sep 2023 15:09:42 +0530 Subject: [PATCH 2/4] function names consistent with the logic --- lua/nvim-paredit/api/init.lua | 5 +++-- lua/nvim-paredit/defaults.lua | 10 +++++++--- tests/nvim-paredit/motion_spec.lua | 2 -- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lua/nvim-paredit/api/init.lua b/lua/nvim-paredit/api/init.lua index 0189758..df573fa 100644 --- a/lua/nvim-paredit/api/init.lua +++ b/lua/nvim-paredit/api/init.lua @@ -30,8 +30,9 @@ local M = { move_to_prev_element = common.deprecate(motions.move_to_prev_element_head, "use `api.move_to_prev_element_head`"), move_to_prev_element_head = motions.move_to_prev_element_head, move_to_prev_element_tail = motions.move_to_prev_element_tail, - move_to_prev_bracket = motions.move_to_parent_form_start, - move_to_next_bracket = motions.move_to_parent_form_end, + + move_to_parent_form_start = motions.move_to_parent_form_start, + move_to_parent_form_end = motions.move_to_parent_form_end, select_around_form = selections.select_around_form, select_in_form = selections.select_in_form, diff --git a/lua/nvim-paredit/defaults.lua b/lua/nvim-paredit/defaults.lua index c9939b1..b208456 100644 --- a/lua/nvim-paredit/defaults.lua +++ b/lua/nvim-paredit/defaults.lua @@ -30,6 +30,7 @@ M.default_keys = { repeatable = false, mode = { "n", "x", "o", "v" }, }, + ["B"] = { api.move_to_prev_element_head, "Previous element head", @@ -44,38 +45,40 @@ M.default_keys = { }, ["("] = { - api.move_to_prev_bracket, + api.move_to_parent_form_start, "Previous bracket in form tree", repeatable = false, mode = { "n", "x", "v" }, }, [")"] = { - api.move_to_next_bracket, + api.move_to_parent_form_end, "Next bracket in form tree", repeatable = false, mode = { "n", "x", "v" }, }, - ["af"] = { api.select_around_form, "Around form", repeatable = false, mode = { "o", "v" }, }, + ["if"] = { api.select_in_form, "In form", repeatable = false, mode = { "o", "v" }, }, + ["aF"] = { api.select_around_top_level_form, "Around top level form", repeatable = false, mode = { "o", "v" }, }, + ["iF"] = { api.select_in_top_level_form, "In top level form", @@ -89,6 +92,7 @@ M.default_keys = { repeatable = false, mode = { "o", "v" }, }, + ["ie"] = { api.select_element, "Element", diff --git a/tests/nvim-paredit/motion_spec.lua b/tests/nvim-paredit/motion_spec.lua index 207312b..2b0aa92 100644 --- a/tests/nvim-paredit/motion_spec.lua +++ b/tests/nvim-paredit/motion_spec.lua @@ -286,7 +286,6 @@ describe("motions", function() expect({ cursor = { 1, 0 }, }) - end) @@ -314,6 +313,5 @@ describe("motions", function() expect({ cursor = { 1, 19 }, }) - end) end) From 0bab2794abd606ba2fe957419ab9f3ba9f4e3d8e Mon Sep 17 00:00:00 2001 From: Saket Patel Date: Sun, 1 Oct 2023 11:18:57 +0530 Subject: [PATCH 3/4] Updates README with default keymaps for parent form edge motions --- README.md | 13 +++++++++++++ lua/nvim-paredit/defaults.lua | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0e20da9..27c263b 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,19 @@ paredit.setup({ mode = { "n", "x", "o", "v" }, }, + ["("] = { + paredit.api.move_to_parent_form_start, + "Jump to parent form's head", + repeatable = false, + mode = { "n", "x", "v" }, + }, + [")"] = { + paredit.api.move_to_parent_form_end, + "Jump to parent form's tail", + repeatable = false, + mode = { "n", "x", "v" }, + }, + -- These are text object selection keybindings which can used with standard `d, y, c`, `v` ["af"] = { paredit.api.select_around_form, diff --git a/lua/nvim-paredit/defaults.lua b/lua/nvim-paredit/defaults.lua index b208456..972bb7c 100644 --- a/lua/nvim-paredit/defaults.lua +++ b/lua/nvim-paredit/defaults.lua @@ -46,14 +46,14 @@ M.default_keys = { ["("] = { api.move_to_parent_form_start, - "Previous bracket in form tree", + "Parent form's head", repeatable = false, mode = { "n", "x", "v" }, }, [")"] = { api.move_to_parent_form_end, - "Next bracket in form tree", + "Parent form's tail", repeatable = false, mode = { "n", "x", "v" }, }, From eb9556dd931b374aba76db885e4dc56f0fa54c87 Mon Sep 17 00:00:00 2001 From: Saket Patel Date: Mon, 2 Oct 2023 20:57:44 +0530 Subject: [PATCH 4/4] fix(motion): ignores @ and other reader macros as form's edge --- lua/nvim-paredit/api/motions.lua | 2 +- tests/nvim-paredit/motion_spec.lua | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/lua/nvim-paredit/api/motions.lua b/lua/nvim-paredit/api/motions.lua index d42afe7..e24ee1f 100644 --- a/lua/nvim-paredit/api/motions.lua +++ b/lua/nvim-paredit/api/motions.lua @@ -169,7 +169,7 @@ local function move_to_parent_form_edge(direction) local form_node_to_move_to = nearest_form_node while is_cursor_at_form_edge(form_node_to_move_to, direction, cur_cursor_pos, lang) do - form_node_to_move_to = form_node_to_move_to:parent() + form_node_to_move_to = lang.get_node_root(form_node_to_move_to):parent() if not form_node_to_move_to or form_node_to_move_to:type() == "source" then return end diff --git a/tests/nvim-paredit/motion_spec.lua b/tests/nvim-paredit/motion_spec.lua index 2b0aa92..7541684 100644 --- a/tests/nvim-paredit/motion_spec.lua +++ b/tests/nvim-paredit/motion_spec.lua @@ -269,12 +269,6 @@ describe("motions", function() cursor = { 1, 10 }, }) - -- (aa (bb) |@(cc) #{1}) - internal_api.move_to_parent_form_start() - expect({ - cursor = { 1, 9 }, - }) - -- |(aa (bb) @(cc) #{1}) internal_api.move_to_parent_form_start() expect({