Skip to content

Commit

Permalink
Merge branch 'master' into jv/add-fennel-extension
Browse files Browse the repository at this point in the history
  • Loading branch information
julienvincent authored Sep 11, 2023
2 parents a6edbda + e63ad68 commit 36404a8
Show file tree
Hide file tree
Showing 12 changed files with 703 additions and 56 deletions.
82 changes: 81 additions & 1 deletion 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 = false,
-- 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 @@ -112,6 +132,65 @@ require("nvim-paredit").setup({
})
```

## Auto Indentation

Nvim-paredit comes with built-in support for fixing form indentation when performing slurp and barf operations. By default this behaviour is disabled and can be enabled by setting `indent.enabled = true` in the [configuration](#configuration)

The main goal of this implementation is to provide a visual aid to the user, allowing them to confirm they are operating on the correct node and to know when to stop when performing recursive slurp/barf operations. This implementation is fast and does not result in any UI lag or jitter.

The goal is _not_ to be 100% correct. The implementation follows a simple set of rules which account for most scenarios but not all. If a more correct implementation is needed then the native implementation can be replaced by setting the configuration property `intent.indentor`. For example an implementation using `vim.lsp.buf.format` could be built if the user doesn't mind sacrificing performance for correctness.

### Recipes

<details>
<summary><code>vim.lsp.buf.format</code></summary>

Below is a reference implementation for using `vim.lsp.buf.format` to replace the native implementation. This implementation won't be nearly as performant but it will be more correct.

```lua
local function lsp_indent(event, opts)
local traversal = require("nvim-paredit.utils.traversal")
local utils = require("nvim-paredit.indentation.utils")
local langs = require("nvim-paredit.lang")

local lang = langs.get_language_api()

local parent = event.parent

local child
if event.type == "slurp-forwards" then
child = parent:named_child(parent:named_child_count() - 1)
elseif event.type == "slurp-backwards" then
child = parent:named_child(1)
elseif event.type == "barf-forwards" then
child = traversal.get_next_sibling_ignoring_comments(event.parent, { lang = lang })
elseif event.type == "barf-backwards" then
child = event.parent
else
return
end

local child_range = { child:range() }
local lines = utils.find_affected_lines(child, utils.get_node_line_range(child_range))

vim.lsp.buf.format({
bufnr = opts.buf or 0,
range = {
["start"] = { lines[1] + 1, 0 },
["end"] = { lines[#lines] + 1, 0 },
},
})
end

require("nvim-paredit").setup({
indent = {
enabled = true,
indentor = lsp_indent
}
})
```
</details>

## Language Support

As this is built using Treesitter it requires that you have the relevant Treesitter grammar installed for your language of choice. Additionally `nvim-paredit` will need explicit support for the treesitter grammar as the node names and metadata of nodes vary between languages.
Expand Down Expand Up @@ -160,7 +239,7 @@ require("nvim-paredit").setup({
Or by calling the `add_language_extension` API directly before the setup. This would be the recommended approach for extension plugin authors.

```lua
require("nvim-paredit.lang").add_language_extension("commonlisp", { ... }).
require("nvim-paredit").extension.add_language_extension("commonlisp", { ... }).
```

### Existing Language Extensions
Expand Down Expand Up @@ -274,6 +353,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
35 changes: 30 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,16 @@ 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],
},
}
indentation.handle_indentation(event, opts)
end

function M.barf_backwards(opts)
Expand All @@ -99,7 +112,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 +121,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 +134,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 +158,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 = false,
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 36404a8

Please sign in to comment.