Skip to content

Commit

Permalink
Merge pull request #33 from Olical/add-top-level-form-operations
Browse files Browse the repository at this point in the history
Initial pass with top level form selection and deletion
  • Loading branch information
julienvincent authored Sep 25, 2023
2 parents 57b352e + d75689a commit df1c7b8
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 15 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@ paredit.setup({
repeatable = false,
mode = { "o", "v" }
},
["aF"] = {
paredit.api.select_around_top_level_form,
"Around top level form",
repeatable = false,
mode = { "o", "v" }
},
["iF"] = {
paredit.api.select_in_top_level_form,
"In top level form",
repeatable = false,
mode = { "o", "v" }
},
["ae"] = {
paredit.api.select_element,
"Around element",
Expand Down Expand Up @@ -272,6 +284,8 @@ paredit.api.slurp_forwards()
- **`raise_form`**
- **`delete_form`**
- **`delete_in_form`**
- **`delete_top_level_form`**
- **`delete_in_top_level_form`**
- **`delete_element`**
- **`move_to_next_element`**
- **`move_to_prev_element`**
Expand Down
22 changes: 18 additions & 4 deletions lua/nvim-paredit/api/deletions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ local selections = require("nvim-paredit.api.selections")

local M = {}

function M.delete_form()
local range = selections.get_range_around_form()
local function delete_form_impl(range)
if not range then
return
end
Expand All @@ -18,8 +17,15 @@ function M.delete_form()
)
end

function M.delete_in_form()
local range = selections.get_range_in_form()
function M.delete_form()
delete_form_impl(selections.get_range_around_form())
end

function M.delete_top_level_form()
delete_form_impl(selections.get_range_around_top_level_form())
end

local function delete_in_form_impl(range)
if not range then
return
end
Expand All @@ -36,6 +42,14 @@ function M.delete_in_form()
vim.api.nvim_win_set_cursor(0, { range[1] + 1, range[2] })
end

function M.delete_in_form()
delete_in_form_impl(selections.get_range_in_form())
end

function M.delete_in_top_level_form()
delete_in_form_impl(selections.get_range_in_top_level_form())
end

function M.delete_element()
local range = selections.get_element_range()
if not range then
Expand Down
4 changes: 4 additions & 0 deletions lua/nvim-paredit/api/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@ local M = {

select_around_form = selections.select_around_form,
select_in_form = selections.select_in_form,
select_around_top_level_form = selections.select_around_top_level_form,
select_in_top_level_form = selections.select_in_top_level_form,
select_element = selections.select_element,

delete_form = deletions.delete_form,
delete_in_form = deletions.delete_in_form,
delete_top_level_form = deletions.delete_top_level_form,
delete_in_top_level_form = deletions.delete_in_top_level_form,
delete_element = deletions.delete_element,
}

Expand Down
58 changes: 50 additions & 8 deletions lua/nvim-paredit/api/selections.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function M.ensure_visual_mode()
end
end

function M.get_range_around_form()
local function get_range_around_form_impl(node_fn)
local lang = langs.get_language_api()
local current_form = traversal.find_nearest_form(ts.get_node_at_cursor(), {
lang = lang,
Expand All @@ -20,7 +20,13 @@ function M.get_range_around_form()
return
end

local root = lang.get_node_root(current_form)
local selected = current_form

if node_fn then
selected = node_fn(selected)
end

local root = lang.get_node_root(selected)
local range = { root:range() }

-- stylua: ignore
Expand All @@ -30,8 +36,15 @@ function M.get_range_around_form()
}
end

function M.select_around_form()
local range = M.get_range_around_form()
function M.get_range_around_form()
return get_range_around_form_impl()
end

function M.get_range_around_top_level_form()
return get_range_around_form_impl(traversal.get_top_level_node_below_document)
end

local function select_around_form_impl(range)
if not range then
return
end
Expand All @@ -42,7 +55,15 @@ function M.select_around_form()
vim.api.nvim_win_set_cursor(0, { range[3] + 1, range[4] - 1 })
end

function M.get_range_in_form()
function M.select_around_form()
return select_around_form_impl(M.get_range_around_form())
end

function M.select_around_top_level_form()
return select_around_form_impl(M.get_range_around_top_level_form())
end

local function get_range_in_form_impl(node_fn)
local lang = langs.get_language_api()
local current_form = traversal.find_nearest_form(ts.get_node_at_cursor(), {
lang = lang,
Expand All @@ -52,7 +73,13 @@ function M.get_range_in_form()
return
end

local edges = lang.get_form_edges(current_form)
local selected = current_form

if node_fn then
selected = node_fn(selected)
end

local edges = lang.get_form_edges(selected)

-- stylua: ignore
return {
Expand All @@ -61,8 +88,15 @@ function M.get_range_in_form()
}
end

function M.select_in_form()
local range = M.get_range_in_form()
function M.get_range_in_form()
return get_range_in_form_impl()
end

function M.get_range_in_top_level_form()
return get_range_in_form_impl(traversal.get_top_level_node_below_document)
end

local function select_in_form_impl(range)
if not range then
return
end
Expand All @@ -73,6 +107,14 @@ function M.select_in_form()
vim.api.nvim_win_set_cursor(0, { range[3] + 1, range[4] - 1 })
end

function M.select_in_form()
return select_in_form_impl(M.get_range_in_form())
end

function M.select_in_top_level_form()
return select_in_form_impl(M.get_range_in_top_level_form())
end

function M.get_element_range()
local lang = langs.get_language_api()
local node = ts.get_node_at_cursor()
Expand Down
12 changes: 12 additions & 0 deletions lua/nvim-paredit/defaults.lua
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ M.default_keys = {
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",
repeatable = false,
mode = { "o", "v" },
},

["ae"] = {
api.select_element,
Expand Down
36 changes: 33 additions & 3 deletions lua/nvim-paredit/utils/traversal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ end
function M.get_last_child_ignoring_comments(node, opts)
return get_child_ignoring_comments(node, node:named_child_count() - 1, {
direction = -1,
lang = opts.lang
lang = opts.lang,
})
end

function M.get_first_child_ignoring_comments(node, opts)
return get_child_ignoring_comments(node, 0, {
direction = 1,
lang = opts.lang
lang = opts.lang,
})
end

Expand Down Expand Up @@ -89,7 +89,7 @@ local function get_sibling_ignoring_comments(node, opts)
elseif opts.count > 1 then
local new_opts = vim.tbl_deep_extend("force", opts, {
count = opts.count - 1,
sibling = sibling
sibling = sibling,
})
return get_sibling_ignoring_comments(sibling, new_opts)
end
Expand Down Expand Up @@ -139,4 +139,34 @@ function M.find_root_element_relative_to(root, child)
return M.find_root_element_relative_to(root, parent)
end

function M.get_top_level_node_below_document(node)
-- Document
-- - Branch A
-- -- Node X
-- --- Sub-node 1
-- - Branch B
-- -- Node Y
-- --- Sub-node 2
-- --- Sub-node 3

-- If we call this function on "Sub-node 2" we expect "Branch B" to be
-- returned, the top level one below the document itself. We know which
-- node is the document because it lacks a parent, just like Batman.

local parent = node:parent()

-- Does the node have a parent? If so, we might be at the right level.
-- If not, we should just return the node right away, we're already too high.
if parent then
-- If the parent _also_ has a parent then we still need to go higher, recur.
if parent:parent() then
return M.get_top_level_node_below_document(parent)
end
end

-- As soon as we don't have a grandparent or parent, return the node
-- we're on because it means we're one step below the top level document node.
return node
end

return M
62 changes: 62 additions & 0 deletions tests/nvim-paredit/text_object_selections_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,40 @@ describe("form deletions", function()
end)
end)

describe("top level form deletions", function()
vim.api.nvim_buf_set_option(0, "filetype", "clojure")

before_each(function()
keybindings.setup_keybindings({
keys = defaults.default_keys,
})
end)

it("should delete the top level form, leaving other forms intact", function()
prepare_buffer({
content = { "(+ 1 2)", "(foo (a", "b", "c)) (comment thing)", "(x y)" },
cursor = { 2, 7 },
})
feedkeys("daF")
expect({
content = { "(+ 1 2)", " (comment thing)", "(x y)" },
cursor = { 2, 0 },
})
end)

it("should delete inside the top level form, leaving other forms and the outer parenthesis pair intact", function()
prepare_buffer({
content = { "(+ 1 2)", "(foo (a", "b", "c)) (comment thing)", "(x y)" },
cursor = { 2, 7 },
})
feedkeys("diF")
expect({
content = { "(+ 1 2)", "() (comment thing)", "(x y)" },
cursor = { 2, 1 },
})
end)
end)

describe("form selections", function()
vim.api.nvim_buf_set_option(0, "filetype", "clojure")

Expand Down Expand Up @@ -104,6 +138,34 @@ describe("form selections", function()
end)
end)

describe("top form selections", function()
vim.api.nvim_buf_set_option(0, "filetype", "clojure")

before_each(function()
keybindings.setup_keybindings({
keys = defaults.default_keys,
})
end)

it("should select the root form and not the siblings", function()
prepare_buffer({
content = {"(+ 1 2)", "(foo (a", "a)) (/ 6 2)"},
cursor = { 2, 6 },
})
feedkeys("vaF")
assert.are.same("(foo (a\na))", utils.get_selected_text())
end)

it("should select within the form", function()
prepare_buffer({
content = {"(+ 1 2)", "(foo (a", "a)) (/ 6 2)"},
cursor = { 2, 6 },
})
feedkeys("viF")
assert.are.same("foo (a\na)", utils.get_selected_text())
end)
end)

describe("element deletions", function()
vim.api.nvim_buf_set_option(0, "filetype", "clojure")

Expand Down

0 comments on commit df1c7b8

Please sign in to comment.