A Neovim implementation of lispy-kill and lispy-comment from the Emacs lispy package. These provide structure-aware editing commands that keep parentheses balanced—essential for comfortable Lisp editing.
- Balanced killing: Never leaves unmatched parentheses, brackets, or braces
- Sexp-aware commenting: Comment entire s-expressions with a single keystroke
- Context-aware: Behaves differently in strings, comments, and code
- Treesitter support: Uses treesitter when available for accurate parsing
- Graceful fallback: Works with vim syntax highlighting when treesitter isn't available
- Kill ring integration: Killed text is stored in the default register for yanking
{
'sundbp/lispy-helpers.nvim',
ft = { 'lisp', 'scheme', 'clojure', 'fennel', 'racket', 'janet', 'yuck' },
opts = {
kill_key = '<C-k>', -- default keybinding
comment_key = ';', -- default keybinding
},
}Or with more explicit configuration:
{
'sundbp/lispy-helpers.nvim',
ft = { 'lisp', 'scheme', 'clojure', 'fennel', 'racket' },
config = function()
require('lispy-helpers').setup({
kill_key = '<C-k>',
filetypes = { 'lisp', 'scheme', 'clojure', 'fennel', 'racket', 'janet', 'yuck' },
})
end,
}use {
'sundbp/lispy-helpers.nvim',
ft = { 'lisp', 'scheme', 'clojure', 'fennel', 'racket' },
config = function()
require('lispy-helpers').setup()
end,
}-
Clone or copy to your Neovim packages directory:
mkdir -p ~/.local/share/nvim/site/pack/plugins/start cd ~/.local/share/nvim/site/pack/plugins/start git clone https://github.com/sundbp/lispy-helpers.nvim
-
Add to your
init.lua:require('lispy-helpers').setup()
By default:
<C-k>is bound forlispy-killin normal and insert mode;is bound forlispy-commentin normal and visual mode
You can also call the functions directly:
-- In a keymap
vim.keymap.set('n', '<leader>k', require('lispy-helpers').kill)
vim.keymap.set('n', '<leader>;', require('lispy-helpers').comment)
-- Or use the commands
:LispyKill
:LispyComment
:3LispyComment " Comment 3 sexpsThe kill command tries these conditions in order (matching the original Emacs behavior):
| Condition | Action |
|---|---|
| Inside a comment | Standard kill-line (kill to end of line) |
| Inside a string that extends past line | Standard kill-line |
| Inside a string that ends on this line | Delete up to (but not including) closing quote |
| On a whitespace-only line | Delete entire line, move to next, re-indent |
Inside an empty list () |
Delete the empty list |
| Parens balanced from cursor to EOL | Standard kill-line |
At an opening delimiter (, [, { |
Kill entire sexp (including closing delimiter) |
| Inside a list (can find closing paren) | Delete from cursor to end of list (before closing paren) |
| Otherwise | Delete current sexp |
;; Before (| = cursor):
(defun test ()
|(foo bar baz)
(qux))
;; After <C-k>:
(defun test ()
|
(qux));; Before (cursor ON the opening paren of (bar)):
(foo |(bar))
;; After <C-k>:
(foo |);; Before:
(foo bar| baz quux)
;; After <C-k>:
(foo bar|);; Before:
(message "hello |world")
;; After <C-k>:
(message "hello |");; Before:
(foo (|) bar)
;; After <C-k>:
(foo | bar)The comment command (; by default) intelligently comments s-expressions:
| Condition | Action |
|---|---|
At opening delimiter (, [, { |
Comment entire sexp (all lines it spans) |
With count (e.g., 3;) |
Comment that many consecutive sexps |
| Inside a comment | Uncomment the current line |
| Visual selection | Comment/uncomment selected lines |
| Otherwise | Comment current line |
If the target lines are already commented, the command will uncomment them instead (toggle behavior).
;; Before (cursor on opening paren):
(defun foo ()
|(bar)
(baz))
;; After ;:
(defun foo ()
;; (bar)
(baz));; Before (cursor on opening paren):
|(foo)
(bar)
(baz)
;; After 2;:
;; (foo)
;; (bar)
(baz);; Before (cursor inside comment):
;; |(foo)
;; After ;:
(foo)require('lispy-helpers').setup({
-- Keybinding for kill (set to false to disable automatic binding)
kill_key = '<C-k>',
-- Keybinding for comment (set to false to disable)
comment_key = ';',
-- Filetypes to enable for
filetypes = {
'lisp',
'scheme',
'clojure',
'fennel',
'janet',
'racket',
'elisp',
'commonlisp',
'hy',
'lfe',
'query',
'yuck',
},
})local lispy = require('lispy-helpers')
-- Kill function
lispy.kill()
-- Comment function (optionally pass count for multiple sexps)
lispy.comment() -- comment 1 sexp
lispy.comment(3) -- comment 3 sexps
-- Default filetypes (can be referenced in config)
lispy.default_filetypes- lispy - The original Emacs package this is based on
- nvim-paredit - Structural editing for Neovim
- vim-sexp - Precision editing for S-expressions
MIT