Skip to content

Commit e023c79

Browse files
committed
Merge remote-tracking branch 'origin/master' into auto-indentation
2 parents 41e68d8 + c24a806 commit e023c79

File tree

9 files changed

+343
-28
lines changed

9 files changed

+343
-28
lines changed

README.md

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,15 @@ require("nvim-paredit").setup({
8787
["<localleader>o"] = { paredit.api.raise_form, "Raise form" },
8888
["<localleader>O"] = { paredit.api.raise_element, "Raise element" },
8989

90-
["E"] = {
90+
["E"] = {
9191
paredit.api.move_to_next_element,
9292
"Jump to next element tail",
9393
-- by default all keybindings are dot repeatable
9494
repeatable = false,
9595
mode = { "n", "x", "o", "v" },
9696
},
9797
["B"] = {
98-
paredit.api.move_to_prev_element,
98+
paredit.api.move_to_prev_element,
9999
"Jump to previous element head",
100100
repeatable = false,
101101
mode = { "n", "x", "o", "v" },
@@ -198,6 +198,73 @@ paredit.api.slurp_forwards()
198198
- **`move_to_next_element`**
199199
- **`move_to_prev_element`**
200200

201+
Form/element wrap api is in `paredit.wrap` module:
202+
203+
- **`wrap_element_under_cursor`** - accepts prefix and suffix, returns wrapped `TSNode`
204+
- **`wrap_enclosing_form_under_cursor`** - accepts prefix and suffix, returns wrapped `TSNode`
205+
206+
Cursor api `paredit.cursor`
207+
208+
- **`place_cursor`** - accepts `TSNode`, and following options:
209+
- `placement` - enumeration `left_edge`,`inner_start`,`inner_end`,`right_edge`
210+
- `mode` - currently only `insert` is supported, defaults to `normal`
211+
212+
## API usage recipes
213+
214+
### `vim-sexp` wrap form (head/tail) replication
215+
216+
Require api module:
217+
```lua
218+
local paredit = require("nvim-paredit.api")
219+
```
220+
Add following keybindings to config:
221+
```lua
222+
["<localleader>w"] = {
223+
function()
224+
-- place cursor and set mode to `insert`
225+
paredit.cursor.place_cursor(
226+
-- wrap element under cursor with `( ` and `)`
227+
paredit.wrap.wrap_element_under_cursor("( ", ")"),
228+
-- cursor placement opts
229+
{ placement = "inner_start", mode = "insert" }
230+
)
231+
end,
232+
"Wrap element insert head",
233+
},
234+
235+
["<localleader>W"] = {
236+
function()
237+
paredit.cursor.place_cursor(
238+
paredit.wrap.wrap_element_under_cursor("(", ")"),
239+
{ placement = "inner_end", mode = "insert" }
240+
)
241+
end,
242+
"Wrap element insert tail",
243+
},
244+
245+
-- same as above but for enclosing form
246+
["<localleader>i"] = {
247+
function()
248+
paredit.cursor.place_cursor(
249+
paredit.wrap.wrap_enclosing_form_under_cursor("( ", ")"),
250+
{ placement = "inner_start", mode = "insert" }
251+
)
252+
end,
253+
"Wrap form insert head",
254+
},
255+
256+
["<localleader>I"] = {
257+
function()
258+
paredit.cursor.place_cursor(
259+
paredit.wrap.wrap_enclosing_form_under_cursor("(", ")"),
260+
{ placement = "inner_end", mode = "insert" }
261+
)
262+
end,
263+
"Wrap form insert tail",
264+
}
265+
```
266+
Same approach can be used for other `vim-sexp` keybindings (e.g. `<localleader>e[`) with cursor placement or without.
267+
201268
## Prior Art
202269

203270
### [vim-sexp](https://github.com/guns/vim-sexp)
@@ -206,10 +273,10 @@ Currently the de-facto s-expression editing plugin with the most extensive set o
206273

207274
The main reasons you might want to consider `nvim-paredit` instead are:
208275

209-
+ Easier configuration and an exposed lua API
210-
+ Control over how the cursor is moved during slurp/barf. (For example if you don't want the cursor to always be moved)
211-
+ Recursive slurp/barf operations. If your cursor is in a nested form you can still slurp from the forms parent(s)
212-
+ Subjectively better out-of-the-box keybindings
276+
- Easier configuration and an exposed lua API
277+
- Control over how the cursor is moved during slurp/barf. (For example if you don't want the cursor to always be moved)
278+
- Recursive slurp/barf operations. If your cursor is in a nested form you can still slurp from the forms parent(s)
279+
- Subjectively better out-of-the-box keybindings
213280

214281
### [vim-sexp-mappings-for-regular-people](https://github.com/tpope/vim-sexp-mappings-for-regular-people)
215282

lua/nvim-paredit/api/cursor.lua

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
local M = {}
2+
3+
function M.insert_mode()
4+
vim.api.nvim_feedkeys("i", "n", true)
5+
end
6+
7+
function M.place_cursor(form, opts)
8+
if not form then
9+
return
10+
end
11+
12+
local range = { form:range() }
13+
local cursor_pos
14+
if opts.placement == "left_edge" then
15+
cursor_pos = { range[1] + 1, range[2] }
16+
elseif opts.placement == "inner_start" then
17+
cursor_pos = { range[1] + 1, range[2] + 1 }
18+
elseif opts.placement == "inned_end" then
19+
cursor_pos = { range[3] + 1, range[4] - 2 }
20+
else
21+
cursor_pos = { range[3] + 1, range[4] - 1 }
22+
end
23+
vim.api.nvim_win_set_cursor(0, cursor_pos)
24+
25+
if opts.mode == "insert" then
26+
M.insert_mode()
27+
end
28+
end
29+
30+
return M

lua/nvim-paredit/api/motions.lua

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ local langs = require("nvim-paredit.lang")
55

66
local M = {}
77

8-
local default_whitespace_chars = { " ", "," }
9-
108
-- When the cursor is placed on whitespace within a form then the node returned by
119
-- the treesitter `get_node_at_cursor` fn is the outer form and not a child within
1210
-- the form.
@@ -24,13 +22,7 @@ local function get_next_node_from_cursor(lang, reversed)
2422
local cursor = vim.api.nvim_win_get_cursor(0)
2523
cursor = { cursor[1] - 1, cursor[2] }
2624

27-
local char_under_cursor = vim.api.nvim_buf_get_text(0, cursor[1], cursor[2], cursor[1], cursor[2] + 1, {})
28-
local char_is_whitespace = common.included_in_table(
29-
lang.whitespace_chars or default_whitespace_chars,
30-
char_under_cursor[1]
31-
) or char_under_cursor[1] == ""
32-
33-
if not (lang.node_is_form(current_node) and char_is_whitespace) then
25+
if not (lang.node_is_form(current_node) and common.is_whitespace_under_cursor(lang)) then
3426
return lang.get_node_root(current_node)
3527
end
3628

lua/nvim-paredit/api/wrap.lua

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
local traversal = require("nvim-paredit.utils.traversal")
2+
local common = require("nvim-paredit.utils.common")
3+
local ts = require("nvim-treesitter.ts_utils")
4+
local langs = require("nvim-paredit.lang")
5+
6+
local M = {}
7+
8+
local function reparse(buf)
9+
local parser = vim.treesitter.get_parser(buf, vim.bo.filetype)
10+
parser:parse()
11+
end
12+
13+
function M.find_element_under_cursor(lang)
14+
local node = ts.get_node_at_cursor()
15+
return lang.get_node_root(node)
16+
end
17+
18+
function M.find_form(element, lang)
19+
return traversal.find_nearest_form(element, { lang = lang, use_source = false })
20+
end
21+
22+
function M.find_parend_form(element, lang)
23+
local nearest_form = M.find_form(element, lang)
24+
25+
if not nearest_form then
26+
return element
27+
end
28+
29+
local parent = nearest_form
30+
31+
if nearest_form:equal(element) then
32+
parent = nearest_form:parent()
33+
end
34+
35+
if parent and parent:type() ~= "source" then
36+
return M.find_form(parent, lang)
37+
end
38+
return nearest_form
39+
end
40+
41+
function M.wrap_element(buf, element, prefix, suffix)
42+
prefix = prefix or ""
43+
suffix = suffix or ""
44+
45+
local range = { element:range() }
46+
vim.api.nvim_buf_set_text(buf, range[3], range[4], range[3], range[4], { suffix })
47+
vim.api.nvim_buf_set_text(buf, range[1], range[2], range[1], range[2], { prefix })
48+
end
49+
50+
function M.wrap_element_under_cursor(prefix, suffix)
51+
local buf = vim.api.nvim_get_current_buf()
52+
local lang = langs.get_language_api()
53+
local current_element = M.find_element_under_cursor(lang)
54+
55+
if not current_element then
56+
return
57+
end
58+
if lang.node_is_comment(current_element) then
59+
return
60+
end
61+
if common.is_whitespace_under_cursor(lang) then
62+
return
63+
end
64+
65+
M.wrap_element(buf, current_element, prefix, suffix)
66+
67+
reparse(buf)
68+
69+
current_element = lang.get_node_root(ts.get_node_at_cursor())
70+
return M.find_form(current_element, lang)
71+
end
72+
73+
function M.wrap_enclosing_form_under_cursor(prefix, suffix)
74+
local buf = vim.api.nvim_get_current_buf()
75+
local lang = langs.get_language_api()
76+
local current_element = M.find_element_under_cursor(lang)
77+
78+
if not current_element then
79+
return
80+
end
81+
82+
local use_direct_parent = common.is_whitespace_under_cursor(lang) or lang.node_is_comment(ts.get_node_at_cursor())
83+
84+
local form = M.find_form(current_element, lang)
85+
if not use_direct_parent and form:type() ~= "source" then
86+
form = M.find_parend_form(current_element, lang)
87+
end
88+
89+
M.wrap_element(buf, form, prefix, suffix)
90+
91+
reparse(buf)
92+
93+
current_element = M.find_element_under_cursor(lang)
94+
if use_direct_parent then
95+
form = current_element
96+
else
97+
form = M.find_parend_form(current_element, lang)
98+
end
99+
return M.find_parend_form(form, lang)
100+
end
101+
102+
return M

lua/nvim-paredit/defaults.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ local M = {}
44

55
M.default_keys = {
66
[">)"] = { api.slurp_forwards, "Slurp forwards" },
7-
[">("] = { api.slurp_backwards, "Slurp backwards" },
7+
[">("] = { api.barf_backwards, "Barf backwards" },
88

99
["<)"] = { api.barf_forwards, "Barf forwards" },
10-
["<("] = { api.barf_backwards, "Barf backwards" },
10+
["<("] = { api.slurp_backwards, "Slurp backwards" },
1111

1212
[">e"] = { api.drag_element_forwards, "Drag element right" },
1313
["<e"] = { api.drag_element_backwards, "Drag element left" },

lua/nvim-paredit/init.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ local lang = require("nvim-paredit.lang")
66

77
local M = {
88
api = require("nvim-paredit.api"),
9+
wrap = require("nvim-paredit.api.wrap"),
10+
cursor = require("nvim-paredit.api.cursor"),
911
}
1012

1113
local function setup_keybingings(filetype, buf)

lua/nvim-paredit/lang/init.lua

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
local common = require("nvim-paredit.utils.common")
2+
13
local langs = {
24
clojure = require("nvim-paredit.lang.clojure"),
35
}
46

7+
local M = {}
8+
59
local function keys(tbl)
610
local result = {}
711
for k, _ in pairs(tbl) do
@@ -10,16 +14,16 @@ local function keys(tbl)
1014
return result
1115
end
1216

13-
return {
14-
get_language_api = function()
15-
return langs[vim.bo.filetype]
16-
end,
17+
function M.get_language_api()
18+
return langs[vim.bo.filetype]
19+
end
1720

18-
add_language_extension = function(filetype, api)
19-
langs[filetype] = api
20-
end,
21+
function M.add_language_extension(filetype, api)
22+
langs[filetype] = api
23+
end
2124

22-
filetypes = function()
23-
return keys(langs)
24-
end,
25-
}
25+
function M.filetypes()
26+
return keys(langs)
27+
end
28+
29+
return M

lua/nvim-paredit/utils/common.lua

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,18 @@ function M.ensure_visual_mode()
6565
end
6666
end
6767

68+
M.default_whitespace_chars = { " " }
69+
70+
function M.is_whitespace_under_cursor(lang)
71+
local cursor = vim.api.nvim_win_get_cursor(0)
72+
cursor = { cursor[1] - 1, cursor[2] }
73+
74+
local char_under_cursor = vim.api.nvim_buf_get_text(0, cursor[1], cursor[2], cursor[1], cursor[2] + 1, {})
75+
return M.included_in_table(
76+
lang.whitespace_chars or M.default_whitespace_chars,
77+
char_under_cursor[1]
78+
) or char_under_cursor[1] == ""
79+
end
80+
6881
return M
6982

0 commit comments

Comments
 (0)