A collection of editing helpers:
- toggle the case style of a word/selection,
- expand or collapse a multiline expression,
- highlight TODO/FIXME-style tags in comments,
- auto-close and auto-rename matching HTML/JSX-style tags,
- auto-close bracket/quote pairs, with wrapping, endwise, and cmp support.
-- lazy.nvim
{ "alexsobolenko/code-toolkit.nvim" }-- packer.nvim
use "alexsobolenko/code-toolkit.nvim"" vim-plug
Plug 'alexsobolenko/code-toolkit.nvim'# native packages (Neovim 0.5+)
git clone https://github.com/alexsobolenko/code-toolkit.nvim \
~/.local/share/nvim/site/pack/plugins/start/code-toolkit.nvimNo setup() call is required — the plugin registers its commands automatically.
Converts the word under the cursor (Normal mode) or the current selection (Visual mode) to one of:
| id | example |
|---|---|
camel |
fooBarBaz |
pascal |
FooBarBaz |
snake |
foo_bar_baz |
constant |
FOO_BAR_BAZ |
kebab |
foo-bar-baz |
dot |
foo.bar.baz |
path |
foo/bar/baz |
title |
Foo Bar Baz |
sentence |
Foo bar baz |
lower |
foobarbaz |
upper |
FOOBARBAZ |
lowerFirst |
fooBarBaz |
upperFirst |
FooBarBaz |
swap |
fOObARbAZ |
A single-line selection is converted as one merged string; a multi-line selection converts each line independently.
Usage:
:CodeToolkitToggleCase snake " apply directly
:CodeToolkitToggleCase " open a picker (shows a live preview per option)
This plugin sets no keymaps of its own — bind whatever you like in your config, in both Normal and Visual mode:
local code_toolkit = require("code-toolkit")
vim.keymap.set({ "n", "x" }, "<leader>cc", function()
code_toolkit.toggle_case()
end)
vim.keymap.set({ "n", "x" }, "<leader>cs", function()
code_toolkit.toggle_case("snake")
end)
-- OR --
vim.keymap.set({ "n", "x" }, "<leader>cc", "<cmd>CodeToolkitToggleCase<cr>")
vim.keymap.set({ "n", "x" }, "<leader>cs", "<cmd>CodeToolkitToggleCase snake<cr>")Puts the cursor inside a () / [] / {} expression — function call/params, array literal, object literal — and expands a single-line expression into one item per line (with a trailing comma where the language supports it), or collapses a multi-line one back onto a single line.
It tells apart:
- a call/array/parameter list from control-flow parens (
if (...),for (...),switch (...), etc.) — those are left alone foo[0]index access from an array literal- an object literal
{a: 1}from a code block{ ... } - PHP's
array(...)as an array
For a function declaration, toggling also moves the opening { between the same line as the closing ) and its own next line, matching whichever direction the params are going (K&R when exploded, Allman when collapsed).
Collapsing refuses (with a warning) if any item spans multiple lines or contains a line comment.
Usage:
:CodeToolkitToggleMultilineExpression
vim.keymap.set("n", "<leader>cm", function()
code_toolkit.toggle_multiline_expression()
end)
-- OR --
vim.keymap.set("n", "<leader>cm", "<cmd>CodeToolkitToggleMultilineExpression<cr>")Highlights TODO, FIXME, BUG, HACK, WARN, WARNING, NOTE, PERF and XXX when they appear inside a comment — never inside a string or in ordinary code. Enabled automatically, no command or keymap needed.
The whole comment on the tag's line is highlighted, not just the bare word — a // TODO: fix this trailing comment is highlighted from // to end of line, and a tag inside a /* ... */ block is highlighted from the tag through to wherever the comment actually ends on that line (stopping at */ if it closes there, otherwise running to end of line).
It uses treesitter to tell comments apart from strings/code when a buffer's treesitter highlighter is active, and falls back to legacy :syntax otherwise.
To customize the tag list or colors:
require("code-toolkit").setup({
comment_highlight = {
tags = {
{ tag = "TODO", fg = "#1e90ff", bold = true },
{ tag = "FIXME", fg = "#ff5555", bold = true },
-- fg / bg / bold / italic / underline / strikethrough per tag
},
},
})Closes and renames matching tags in html, xml, vue, php, javascript, javascriptreact, typescript and typescriptreact buffers. Enabled automatically, no command or keymap needed.
- Typing the closing
>of an opening tag inserts the matching closing tag and leaves the cursor between them. Void HTML elements (br,img,input, ...) and self-closing tags (<Foo />) are left alone. - Renaming either side of a tag pair (in Insert or Normal mode) updates the other side to match, once the edit is applied.
To customize the filetype list, tag skip list, or disable either half of the feature:
require("code-toolkit").setup({
autotag = {
filetypes = { "html", "javascriptreact", "typescriptreact", "vue", "php", "javascript", "typescript", "xml" },
enable_close = true,
enable_rename = true,
skip_tags = { "br", "img", "input", "hr", "meta", "link" },
},
})Auto-closes (), [], {}, "", '' and `` as you type, in every filetype unless excluded. Enabled automatically, no command or keymap needed for the typing behavior itself.
- Typing an opening character inserts its matching close and puts the cursor between them; typing the close character again just moves past it instead of inserting a duplicate. A pair isn't added when the next character is a word character, when a stray unmatched closer already follows on the line, or when the cursor is inside an existing string or comment (via treesitter, or legacy
:syntaxas a fallback). <CR>between a freshly-typed empty pair splits it onto three lines with the middle line indented one level;<BS>between an empty pair deletes both characters at once.- In
lua,sh/bashandrubybuffers,<CR>after an opening construct (function (),if ... then,for ... do,def,class,case ... in, etc.) also inserts the matchingend/fi/done/esacon the next line, unless the line is already a complete one-liner or the closer is already present below. - Pairing is skipped while recording or executing a macro, and never triggers in Visual block mode or Replace mode.
Two actions aren't automatic and have no default keymap — bind them yourself:
require("code-toolkit").setup({
autopairs = {
disable_filetypes = { "TelescopePrompt" },
disable_in_macro = true,
endwise = {
enabled = true,
filetypes = { "lua", "sh", "bash", "ruby" },
},
},
})
vim.keymap.set({ "n", "x" }, "<M-e>", "<cmd>CodeToolkitFastWrap<cr>")
vim.keymap.set("n", "<leader>cp", "<cmd>CodeToolkitToggleAutopairs<cr>"):CodeToolkitFastWrap wraps the word under the cursor (Normal mode) or the current selection (Visual mode) in a pair you pick from a picker. :CodeToolkitToggleAutopairs turns the whole feature on/off.
For nvim-cmp to add () after confirming a function/method completion, wire the plugin's hook into your own cmp config:
local cmp = require("cmp")
cmp.event:on("confirm_done", require("code-toolkit.autopairs").cmp_on_confirm_done())