diff --git a/README.md b/README.md index e04282a..c8b79ec 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@

-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: +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, cursor motions and text object selections - Dot-repeatable keybindings @@ -24,9 +25,11 @@ The goal of `nvim-paredit` is to provide a comparable s-expression editing exper ## Project Status -This is currently **beta software**. It works well in the workflows of the current maintainers but has not been thoroughly tested with many users. +This is currently **beta software**. It works well in the workflows of the current maintainers but has not been +thoroughly tested with many users. -It currently only has first-class support for the `clojure` language and has a focus on supporting the fundamental paredit operations and motions. +It currently only has first-class support for the `clojure` language and has a focus on supporting the fundamental +paredit operations and motions. ## Installation @@ -136,25 +139,25 @@ paredit.setup({ paredit.api.select_around_form, "Around form", repeatable = false, - mode = { "o", "v" } + mode = { "o", "v" }, }, ["if"] = { paredit.api.select_in_form, "In form", repeatable = false, - mode = { "o", "v" } + mode = { "o", "v" }, }, ["aF"] = { paredit.api.select_around_top_level_form, "Around top level form", repeatable = false, - mode = { "o", "v" } + mode = { "o", "v" }, }, ["iF"] = { paredit.api.select_in_top_level_form, "In top level form", repeatable = false, - mode = { "o", "v" } + mode = { "o", "v" }, }, ["ae"] = { paredit.api.select_element, @@ -168,74 +171,99 @@ paredit.setup({ repeatable = false, mode = { "o", "v" }, }, - } + }, }) ``` ## 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) +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 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. +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
vim.lsp.buf.format - 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 }, - }, - }) +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 - require("nvim-paredit").setup({ - indent = { - enabled = true, - indentor = lsp_indent - } + 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 + +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, +}, +}) +``` +
## 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. +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. -Right now `nvim-paredit` only has built in support for `clojure` but exposes an extension API for adding support for other lisp dialects. This API is considered **very alpha** and may change without warning to properly account for other languages when attempts are made to add support. +Right now `nvim-paredit` only has built in support for `clojure` but exposes an extension API for adding support for +other lisp dialects. This API is considered **very alpha** and may change without warning to properly account for other +languages when attempts are made to add support. Extensions can either be added as config when calling `setup`: @@ -247,22 +275,18 @@ require("nvim-paredit").setup({ -- The node at cursor in the below example is `()` or 'list_lit': -- '(|) -- But the node root is `'()` or 'quoting_lit' - get_node_root = function(node) - end, + get_node_root = function(node) end, -- This is the inverse of `get_node_root` for forms and should find the inner node for which -- the forms elements are direct children. -- -- For example given the node `'()` or 'quoting_lit', this function should return `()` or 'list_lit'. - unwrap_form = function(node) - end, + unwrap_form = function(node) end, -- Accepts a Treesitter node and should return true or false depending on whether the given node -- can be considered a 'form' - node_is_form = function(node) - end, + node_is_form = function(node) end, -- Accepts a Treesitter node and should return true or false depending on whether the given node -- can be considered a 'comment' - node_is_comment = function(node) - end, + node_is_comment = function(node) end, -- Accepts a Treesitter node representing a form and should return the 'edges' of the node. This -- includes the node text and the range covered by the node get_form_edges = function(node) @@ -271,12 +295,13 @@ require("nvim-paredit").setup({ right = { text = "}", range = { 0, 5, 0, 6 } }, } end, - } - } + }, + }, }) ``` -Or by calling the `add_language_extension` API directly before the setup. This would be the recommended approach for extension plugin authors. +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").extension.add_language_extension("commonlisp", { ... }). @@ -284,12 +309,13 @@ require("nvim-paredit").extension.add_language_extension("commonlisp", { ... }). ### Existing Language Extensions -+ [fennel](https://github.com/julienvincent/nvim-paredit-fennel) -+ [scheme](https://github.com/ekaitz-zarraga/nvim-paredit-scheme) +- [fennel](https://github.com/julienvincent/nvim-paredit-fennel) +- [scheme](https://github.com/ekaitz-zarraga/nvim-paredit-scheme) --- -As no attempt has been made to add support for other grammars I have no idea if the language extension API's are actually sufficient for adding additional languages. They will evolve as attempts are made. +As no attempt has been made to add support for other grammars I have no idea if the language extension API's are +actually sufficient for adding additional languages. They will evolve as attempts are made. ## API @@ -334,10 +360,13 @@ Cursor api `paredit.cursor` ### `vim-sexp` wrap form (head/tail) replication Require api module: + ```lua local paredit = require("nvim-paredit") ``` + Add following keybindings to config: + ```lua ["w"] = { function() @@ -383,13 +412,16 @@ Add following keybindings to config: "Wrap form insert tail", } ``` + Same approach can be used for other `vim-sexp` keybindings (e.g. `e[`) with cursor placement or without. ## Prior Art ### [vim-sexp](https://github.com/guns/vim-sexp) -Currently the de-facto s-expression editing plugin with the most extensive set of available editing operations. If you are looking for a more complete plugin with a wider range of supported languages then you might want to look into using this instead. +Currently the de-facto s-expression editing plugin with the most extensive set of available editing operations. If you +are looking for a more complete plugin with a wider range of supported languages then you might want to look into using +this instead. The main reasons you might want to consider `nvim-paredit` instead are: @@ -401,4 +433,5 @@ The main reasons you might want to consider `nvim-paredit` instead are: ### [vim-sexp-mappings-for-regular-people](https://github.com/tpope/vim-sexp-mappings-for-regular-people) -A companion to `vim-sexp` which configures `vim-sexp` with better mappings. The default mappings for `nvim-paredit` were derived from here. +A companion to `vim-sexp` which configures `vim-sexp` with better mappings. The default mappings for `nvim-paredit` were +derived from here.