Skip to content

Commit

Permalink
Initial pass with top level form selection and deletion
Browse files Browse the repository at this point in the history
Could maybe do with more methods and tests, but this is already working
and the tests are passing.
  • Loading branch information
Olical committed Sep 23, 2023
1 parent 57b352e commit a4b224a
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 3 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 form",
repeatable = false,
mode = { "o", "v" }
},
["iF"] = {
paredit.api.select_in_top_level_form,
"In 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
34 changes: 34 additions & 0 deletions lua/nvim-paredit/api/deletions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ function M.delete_form()
)
end

function M.delete_top_level_form()
local range = selections.get_range_around_top_level_form()
if not range then
return
end

local buf = vim.api.nvim_get_current_buf()
-- stylua: ignore
vim.api.nvim_buf_set_text(
buf,
range[1], range[2],
range[3], range[4],
{}
)
end

function M.delete_in_form()
local range = selections.get_range_in_form()
if not range then
Expand All @@ -36,6 +52,24 @@ function M.delete_in_form()
vim.api.nvim_win_set_cursor(0, { range[1] + 1, range[2] })
end

function M.delete_in_top_level_form()
local range = selections.get_range_in_top_level_form()
if not range then
return
end

local buf = vim.api.nvim_get_current_buf()
-- stylua: ignore
vim.api.nvim_buf_set_text(
buf,
range[1], range[2],
range[3], range[4],
{}
)

vim.api.nvim_win_set_cursor(0, { range[1] + 1, range[2] })
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
65 changes: 65 additions & 0 deletions lua/nvim-paredit/api/selections.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,27 @@ function M.get_range_around_form()
}
end

function M.get_range_around_top_level_form()
local lang = langs.get_language_api()
local current_form = traversal.find_nearest_form(ts.get_node_at_cursor(), {
lang = lang,
use_source = false,
})
if not current_form then
return
end

local top_level_form = traversal.get_top_level_node_below_document(current_form)
local root = lang.get_node_root(top_level_form)
local range = { root:range() }

-- stylua: ignore
return {
range[1], range[2],
range[3], range[4],
}
end

function M.select_around_form()
local range = M.get_range_around_form()
if not range then
Expand All @@ -42,6 +63,18 @@ function M.select_around_form()
vim.api.nvim_win_set_cursor(0, { range[3] + 1, range[4] - 1 })
end

function M.select_around_top_level_form()
local range = M.get_range_around_top_level_form()
if not range then
return
end

M.ensure_visual_mode()
vim.api.nvim_win_set_cursor(0, { range[1] + 1, range[2] })
vim.api.nvim_command("normal! o")
vim.api.nvim_win_set_cursor(0, { range[3] + 1, range[4] - 1 })
end

function M.get_range_in_form()
local lang = langs.get_language_api()
local current_form = traversal.find_nearest_form(ts.get_node_at_cursor(), {
Expand All @@ -61,6 +94,26 @@ function M.get_range_in_form()
}
end

function M.get_range_in_top_level_form()
local lang = langs.get_language_api()
local current_form = traversal.find_nearest_form(ts.get_node_at_cursor(), {
lang = lang,
use_source = false,
})
if not current_form then
return
end

local top_level_form = traversal.get_top_level_node_below_document(current_form)
local edges = lang.get_form_edges(top_level_form)

-- stylua: ignore
return {
edges.left.range[3], edges.left.range[4],
edges.right.range[1], edges.right.range[2],
}
end

function M.select_in_form()
local range = M.get_range_in_form()
if not range then
Expand All @@ -73,6 +126,18 @@ function M.select_in_form()
vim.api.nvim_win_set_cursor(0, { range[3] + 1, range[4] - 1 })
end

function M.select_in_top_level_form()
local range = M.get_range_in_top_level_form()
if not range then
return
end

M.ensure_visual_mode()
vim.api.nvim_win_set_cursor(0, { range[1] + 1, range[2] })
vim.api.nvim_command("normal! o")
vim.api.nvim_win_set_cursor(0, { range[3] + 1, range[4] - 1 })
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 form",
repeatable = false,
mode = { "o", "v" },
},
["iF"] = {
api.select_in_top_level_form,
"In 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
34 changes: 34 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

0 comments on commit a4b224a

Please sign in to comment.