Skip to content

Commit

Permalink
Add auto indentation correction on slurp/barf
Browse files Browse the repository at this point in the history
This adds support for automatic indentation correction when slurping and
barfing.

The goal of this implementation is to:

1) Provide a visual aid to the user that allows them to confirm they are
   operating on the correct node, and to know when to stop when
   performing recursive slurp/barf operations.
2) Be simple to maintain and understand while being as correct as
   possible
3) Be replaceable with other implementations.
4) Be as performant as possible. There should be no lag or visual jitter

The goal is _not_ to be 100% correct. If a more correct implementation
is needed then one can be provided through `indent_fn`. For example, an
implementation using `vim.lsp.buf.format` could be built if the user
doesn't mind sacrificing performance for correctness.
  • Loading branch information
julienvincent committed Sep 3, 2023
1 parent b03534c commit 0dc4262
Show file tree
Hide file tree
Showing 11 changed files with 501 additions and 33 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,27 @@ require("nvim-paredit").setup({
-- defaults to all supported file types including custom lang
-- extensions (see next section)
filetypes = { "clojure" },

-- This controls where the cursor is placed when performing slurp/barf operations
--
-- - "remain" - It will never change the cursor position, keeping it in the same place
-- - "follow" - It will always place the cursor on the form edge that was moved
-- - "auto" - A combination of remain and follow, it will try keep the cursor in the original position
-- unless doing so would result in the cursor no longer being within the original form. In
-- this case it will place the cursor on the moved edge
cursor_behaviour = "auto", -- remain, follow, auto

indent = {
-- This controls how nvim-paredit handles indentation when performing operations which
-- should change the indentation of the form (such as when slurping or barfing).
--
-- When set to true then it will attempt to fix the indentation of nodes operated on.
enabled = true,
-- A function that will be called after a slurp/barf if you want to provide a custom indentation
-- implementation.
indentor = require("nvim-paredit.indentation.native").indentor,
},

-- list of default keybindings
keys = {
[">)"] = { paredit.api.slurp_forwards, "Slurp forwards" },
Expand Down Expand Up @@ -270,6 +290,7 @@ The main reasons you might want to consider `nvim-paredit` instead are:
- Easier configuration and an exposed lua API
- Control over how the cursor is moved during slurp/barf. (For example if you don't want the cursor to always be moved)
- Recursive slurp/barf operations. If your cursor is in a nested form you can still slurp from the forms parent(s)
- Automatic form/element indentations on slurp/barf
- Subjectively better out-of-the-box keybindings

### [vim-sexp-mappings-for-regular-people](https://github.com/tpope/vim-sexp-mappings-for-regular-people)
Expand Down
38 changes: 33 additions & 5 deletions lua/nvim-paredit/api/barfing.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local traversal = require("nvim-paredit.utils.traversal")
local indentation = require("nvim-paredit.indentation")
local common = require("nvim-paredit.utils.common")
local ts = require("nvim-treesitter.ts_utils")
local config = require("nvim-paredit.config")
Expand Down Expand Up @@ -28,11 +29,11 @@ function M.barf_forwards(opts)
local child
if opts.reversed then
child = traversal.get_first_child_ignoring_comments(form, {
lang = lang
lang = lang,
})
else
child = traversal.get_last_child_ignoring_comments(form, {
lang = lang
lang = lang,
})
end
if not child then
Expand All @@ -42,7 +43,7 @@ function M.barf_forwards(opts)
local edges = lang.get_form_edges(form)

local sibling = traversal.get_prev_sibling_ignoring_comments(child, {
lang = lang
lang = lang,
})

local end_pos
Expand All @@ -55,6 +56,7 @@ function M.barf_forwards(opts)
local buf = vim.api.nvim_get_current_buf()

local range = edges.right.range
-- stylua: ignore
vim.api.nvim_buf_set_text(
buf,
range[1], range[2],
Expand All @@ -63,6 +65,7 @@ function M.barf_forwards(opts)
)

local text = edges.right.text
-- stylua: ignore
vim.api.nvim_buf_set_text(buf,
end_pos[1], end_pos[2],
end_pos[1], end_pos[2],
Expand All @@ -77,6 +80,19 @@ function M.barf_forwards(opts)
vim.api.nvim_win_set_cursor(0, { end_pos[1] + 1, end_pos[2] })
end
end

local event = {
type = "barf-forwards",
-- stylua: ignore
parent_range = {
edges.left.range[1], edges.left.range[2],
end_pos[1], end_pos[2],
},
reversed = false,
indent_behaviour = opts.indent_behaviour or config.config.indent_behaviour,
lang = lang,
}
indentation.handle_indentation(event, opts)
end

function M.barf_backwards(opts)
Expand All @@ -99,7 +115,7 @@ function M.barf_backwards(opts)
end

local child = traversal.get_first_child_ignoring_comments(form, {
lang = lang
lang = lang,
})
if not child then
return
Expand All @@ -108,7 +124,7 @@ function M.barf_backwards(opts)
local edges = lang.get_form_edges(lang.get_node_root(form))

local sibling = traversal.get_next_sibling_ignoring_comments(child, {
lang = lang
lang = lang,
})

local end_pos
Expand All @@ -121,13 +137,15 @@ function M.barf_backwards(opts)
local buf = vim.api.nvim_get_current_buf()

local text = edges.left.text
-- stylua: ignore
vim.api.nvim_buf_set_text(buf,
end_pos[1], end_pos[2],
end_pos[1], end_pos[2],
{ text }
)

local range = edges.left.range
-- stylua: ignore
vim.api.nvim_buf_set_text(
buf,
range[1], range[2],
Expand All @@ -143,6 +161,16 @@ function M.barf_backwards(opts)
vim.api.nvim_win_set_cursor(0, { end_pos[1] + 1, end_pos[2] })
end
end

local event = {
type = "barf-backwards",
-- stylua: ignore
parent_range = {
end_pos[1], end_pos[2],
edges.right.range[1], edges.right.range[2],
},
}
indentation.handle_indentation(event, opts)
end

return M
41 changes: 34 additions & 7 deletions lua/nvim-paredit/api/slurping.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
local traversal = require("nvim-paredit.utils.traversal")
local common = require("nvim-paredit.utils.common")
local indentation = require("nvim-paredit.indentation")
local ts = require("nvim-treesitter.ts_utils")
local config = require("nvim-paredit.config")
local langs = require("nvim-paredit.lang")
Expand Down Expand Up @@ -40,11 +40,12 @@ local function slurp(opts)
end

local buf = vim.api.nvim_get_current_buf()
local form_edges = lang.get_form_edges(form)
local left_or_right_edge
if opts.reversed then
left_or_right_edge = lang.get_form_edges(form).left
left_or_right_edge = form_edges.left
else
left_or_right_edge = lang.get_form_edges(form).right
left_or_right_edge = form_edges.right
end

local start_or_end
Expand All @@ -57,6 +58,7 @@ local function slurp(opts)
local row = start_or_end[1]
local col = start_or_end[2]

-- stylua: ignore
vim.api.nvim_buf_set_text(buf,
row, col,
row, col,
Expand All @@ -65,9 +67,10 @@ local function slurp(opts)

local offset = 0
if opts.reversed and row == left_or_right_edge.range[1] then
offset = string.len(left_or_right_edge.text)
offset = #left_or_right_edge.text
end

-- stylua: ignore
vim.api.nvim_buf_set_text(
buf,
left_or_right_edge.range[1], left_or_right_edge.range[2] + offset,
Expand All @@ -77,7 +80,7 @@ local function slurp(opts)

local cursor_behaviour = opts.cursor_behaviour or config.config.cursor_behaviour
if cursor_behaviour == "follow" then
local offset = 0
offset = 0
if not opts.reversed then
offset = string.len(left_or_right_edge.text)
end
Expand All @@ -88,15 +91,39 @@ local function slurp(opts)
vim.api.nvim_win_set_cursor(0, cursor_pos)
end
end

local operation_type
local new_range
if not opts.reversed then
operation_type = "slurp-forwards"
-- stylua: ignore
new_range = {
form_edges.left.range[1], form_edges.left.range[2],
row, col,
}
else
operation_type = "slurp-backwards"
-- stylua: ignore
new_range = {
row, col,
form_edges.right.range[1], form_edges.right.range[2],
}
end

local event = {
type = operation_type,
parent_range = new_range,
}
indentation.handle_indentation(event, opts)
end

function M.slurp_forwards(opts)
slurp(opts or {})
end

function M.slurp_backwards(opts)
slurp(common.merge(opts or {}, {
reversed = true
slurp(vim.tbl_deep_extend("force", opts or {}, {
reversed = true,
}))
end

Expand Down
4 changes: 1 addition & 3 deletions lua/nvim-paredit/config.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
local common = require("nvim-paredit.utils.common")

local M = {}

M.config = {}

function M.update_config(config)
M.config = common.merge(M.config, config)
M.config = vim.tbl_deep_extend("force", M.config, config)
end

return M
4 changes: 4 additions & 0 deletions lua/nvim-paredit/defaults.lua
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ M.default_keys = {
M.defaults = {
use_default_keys = true,
cursor_behaviour = "auto", -- remain, follow, auto
indent = {
enabled = true,
indentor = require("nvim-paredit.indentation.native").indentor,
},
keys = {},
}

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

local M = {}

function M.handle_indentation(event, opts)
local indent = opts.indent or config.config.indent or {}
if not indent.enabled or not indent.indentor then
return
end

local tree = vim.treesitter.get_parser(0)

tree:parse()
local parent = tree:named_node_for_range(event.parent_range)

indent.indentor(
vim.tbl_deep_extend("force", event, {
tree = tree,
parent = parent,
}),
vim.tbl_deep_extend("force", opts, {
indent = indent,
})
)
end

return M
Loading

0 comments on commit 0dc4262

Please sign in to comment.