Skip to content

Commit

Permalink
Merge branch 'master' into feat/form-update-api
Browse files Browse the repository at this point in the history
  • Loading branch information
armed authored Aug 13, 2023
2 parents b38d43e + 3237777 commit c37d391
Show file tree
Hide file tree
Showing 14 changed files with 650 additions and 37 deletions.
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

The goal of `nvim-paredit` is to provide a comparable s-expression editing experience in Neovim to that provided by Emacs. This is what is provided:

- Treesitter based lisp structural editing and cursor motions
- Treesitter based lisp structural editing, cursor motions and text object selections
- Dot-repeatable keybindings
- Language extensibility
- Programmable API
Expand Down Expand Up @@ -73,12 +73,28 @@ require("nvim-paredit").setup({
paredit.api.move_to_next_element,
"Jump to next element tail",
-- by default all keybindings are dot repeatable
repeatable = false
repeatable = false,
mode = { "n", "x", "o", "v" },
},
["B"] = {
paredit.api.move_to_prev_element,
"Jump to previous element head",
repeatable = false
repeatable = false,
mode = { "n", "x", "o", "v" },
},

-- These are text object selection keybindings which can used with standard `d, y, c`, `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" }
},
}
})
Expand Down Expand Up @@ -158,6 +174,9 @@ paredit.api.slurp_forwards()
- **`drag_form_backwards`**
- **`raise_element`**
- **`raise_form`**
- **`delete_form`**
- **`delete_in_form`**
- **`delete_element`**
- **`move_to_next_element`**
- **`move_to_prev_element`**

Expand Down
55 changes: 55 additions & 0 deletions lua/nvim-paredit/api/deletions.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
local selections = require("nvim-paredit.api.selections")

local M = {}

function M.delete_form()
local range = selections.get_range_around_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
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
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

return M
10 changes: 10 additions & 0 deletions lua/nvim-paredit/api/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ local barfing = require("nvim-paredit.api.barfing")
local dragging = require("nvim-paredit.api.dragging")
local raising = require("nvim-paredit.api.raising")
local motions = require("nvim-paredit.api.motions")
local selections = require("nvim-paredit.api.selections")
local deletions = require("nvim-paredit.api.deletions")

local M = {
slurp_forwards = slurping.slurp_forwards,
Expand All @@ -20,6 +22,14 @@ local M = {

move_to_next_element = motions.move_to_next_element,
move_to_prev_element = motions.move_to_prev_element,

select_around_form = selections.select_around_form,
select_in_form = selections.select_in_form,
select_element = selections.select_element,

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

return M
20 changes: 18 additions & 2 deletions lua/nvim-paredit/api/motions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ function M._move_to_element(count, reversed)
end
end

if lang.node_is_comment(current_node) then
count = count + 1
end
local next_pos
if is_in_middle and count == 1 then
next_pos = node_edge
Expand Down Expand Up @@ -113,12 +116,25 @@ function M._move_to_element(count, reversed)
vim.api.nvim_win_set_cursor(0, cursor_pos)
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.
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()
end
end

function M.move_to_prev_element()
M._move_to_element(vim.v.count1, true)
local count = vim.v.count1
ensure_visual_if_operator_pending()
M._move_to_element(count, true)
end

function M.move_to_next_element()
M._move_to_element(vim.v.count1, false)
local count = vim.v.count1
ensure_visual_if_operator_pending()
M._move_to_element(count, false)
end

return M
105 changes: 105 additions & 0 deletions lua/nvim-paredit/api/selections.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
local traversal = require("nvim-paredit.utils.traversal")
local ts = require("nvim-treesitter.ts_utils")
local langs = require("nvim-paredit.lang")

local M = {}

function M.ensure_visual_mode()
if vim.api.nvim_get_mode().mode ~= "v" then
vim.api.nvim_command("normal! v")
end
end

function M.get_range_around_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 root = lang.get_node_root(current_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
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(), {
lang = lang,
use_source = false,
})
if not current_form then
return
end

local edges = lang.get_form_edges(current_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
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()
if not node then
return
end

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

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

function M.select_element()
local range = M.get_element_range()
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] })
end

return M
17 changes: 15 additions & 2 deletions lua/nvim-paredit/defaults.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,26 @@ M.default_keys = {
api.move_to_next_element,
"Next element tail",
repeatable = false,
operator = true,
mode = { "n", "x", "o", "v" },
},
["B"] = {
api.move_to_prev_element,
"Previous element head",
repeatable = false,
operator = true,
mode = { "n", "x", "o", "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" },
},
}

Expand Down
2 changes: 2 additions & 0 deletions lua/nvim-paredit/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ local function setup_keybingings(filetype, buf)
end

function M.setup(opts)
opts = opts or {}

for filetype, api in pairs(opts.extensions or {}) do
lang.add_language_extension(filetype, api)
end
Expand Down
6 changes: 6 additions & 0 deletions lua/nvim-paredit/utils/common.lua
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,11 @@ function M.intersection(tbl, original)
return result
end

function M.ensure_visual_mode()
if vim.api.nvim_get_mode().mode ~= "v" then
vim.api.nvim_command("normal! v")
end
end

return M

25 changes: 3 additions & 22 deletions lua/nvim-paredit/utils/keybindings.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,44 +17,25 @@ function M.with_repeat(fn)
end
end

-- we wrap motion keys with visual mode for operator mode
-- such that dE/cE becomes dvE/cvE
function M.visualize(fn)
return function()
vim.api.nvim_command("normal! v")
fn()
end
end

function M.setup_keybindings(opts)
for keymap, action in pairs(opts.keys) do
local repeatable = true
local operator = false
if type(action.repeatable) == "boolean" then
repeatable = action.repeatable
end
if type(action.operator) == "boolean" then
operator = action.operator
end

local fn = action[1]
if repeatable then
fn = M.with_repeat(fn)
end

vim.keymap.set({ "n", "x" }, keymap, fn, {
vim.keymap.set(action.mode or { "n", "x" }, keymap, fn, {
desc = action[2],
buffer = opts.buf or 0,
expr = repeatable,
remap = false,
silent = true,
})

if operator then
vim.keymap.set("o", keymap, M.visualize(fn), {
desc = action[2],
buffer = opts.buf or 0,
expr = repeatable,
})
end
end
end

Expand Down
Loading

0 comments on commit c37d391

Please sign in to comment.