Skip to main content

Editor Enhancement Plugins

These plugins enhance the core editing experience without adding LSP complexity. They make common operations faster and add new motion vocabulary.

1. nvim-autopairs — Smart Bracket Completion

Automatically closes brackets, quotes, and other pairs as you type:

lua/plugins/editor.lua
{
"windwp/nvim-autopairs",
event = "InsertEnter",
dependencies = { "hrsh7th/nvim-cmp" },
config = function()
local autopairs = require("nvim-autopairs")
local cmp_autopairs = require("nvim-autopairs.completion.cmp")
local cmp = require("cmp")

autopairs.setup({
check_ts = true, -- use treesitter to check pairs
ts_config = {
lua = { "string" }, -- don't add pairs inside lua strings
javascript = { "template_string" },
},
disable_filetype = { "TelescopePrompt", "spectre_panel" },
fast_wrap = {
map = "<M-e>", -- Alt+e to wrap selection in pair
chars = { "{", "[", "(", '"', "'" },
pattern = string.gsub([[ [%'%"%)%>%]%)%}%,] ]], "%s+", ""),
end_key = "$",
keys = "qwertyuiopzxcvbnmasdfghjkl",
check_comma = true,
highlight = "Search",
highlight_grey = "Comment",
},
})

-- Integrate with nvim-cmp: add ( after function completion
cmp.event:on("confirm_done", cmp_autopairs.on_confirm_done())
end,
},

Key Behaviors

TypedResultCursor
(()inside
"""inside
{<CR>{<CR>}<CR>indented inside
<BS> inside ()removes both
<M-e> on wordwraps in pair

2. nvim-surround — Add/Change/Delete Surroundings

The best surround plugin. Works on any delimiter, quotes, function calls, HTML tags, and custom pairs:

{
"kylechui/nvim-surround",
version = "*",
event = "VeryLazy",
opts = {
keymaps = {
insert = "<C-g>s", -- in insert mode
insert_line = "<C-g>S",
normal = "ys", -- ys{motion}{char}
normal_cur = "yss", -- yss{char} for current line
normal_line = "yS",
normal_cur_line = "ySS",
visual = "S", -- S{char} in visual mode
visual_line = "gS",
delete = "ds", -- ds{char}
change = "cs", -- cs{old}{new}
change_line = "cS",
},
},
},

Usage Examples

# Add surroundings:
ysw" → surround word with double quotes: word → "word"
ys3w( → surround 3 words with parens
ysiw[ → surround inner word with brackets
yss{ → surround current line with braces
ysiw<em> → surround inner word in HTML tag: <em>word</em>

# Change surroundings:
cs"' → change " to ': "hello" → 'hello'
cs({ → change ( to {: (args) → {args}
cs"<strong> → change quote to HTML tag

# Delete surroundings:
ds" → delete surrounding quotes: "hello" → hello
ds( → delete surrounding parens: (args) → args
dst → delete surrounding HTML tag: <p>text</p> → text

# Visual mode:
viw S" → select inner word, surround with "
V S<div> → select line, surround with div tag

3. Comment.nvim — Toggle Comments

{
"numToStr/Comment.nvim",
keys = {
{ "gcc", mode = "n", desc = "Comment line" },
{ "gbc", mode = "n", desc = "Block comment line" },
{ "gc", mode = "v", desc = "Comment selection" },
{ "gb", mode = "v", desc = "Block comment selection" },
},
opts = {
padding = true,
sticky = true,
ignore = "^$", -- ignore blank lines
mappings = {
basic = true,
extra = true,
},
pre_hook = function(ctx)
-- Use treesitter-context-commentstring if available
local ok, tsc = pcall(require, "ts_context_commentstring.integrations.comment_nvim")
if ok then return tsc.create_pre_hook()(ctx) end
end,
},
},

-- Context-aware comments for embedded languages (JSX, PHP+HTML, etc.)
{
"JoosepAlviste/nvim-ts-context-commentstring",
lazy = true,
opts = { enable_autocmd = false },
},

Usage

n:  gcc         → toggle comment on current line
n: gc3j → comment current + 3 lines down
n: gco → add comment on line below
n: gcO → add comment on line above
n: gcA → add comment at end of line
v: gc → toggle comment on selection
v: gb → block comment on selection

Context-Aware Comments

With ts_context_commentstring, comments use the correct syntax for mixed files:

<!-- In <template> section of Vue: uses HTML comment -->
<div>content</div>

// In <script> section: uses JS comment
const x = 1

4. Flash.nvim — Fast Cursor Jumping

Flash replaces traditional f/t/s motions with a visual label-based jump system:

{
"folke/flash.nvim",
event = "VeryLazy",
opts = {
labels = "asdfghjklqwertyuiopzxcvbnm",
search = {
mode = "fuzzy",
incremental = true,
},
jump = {
jumplist = true,
pos = "start",
history = false,
register = false,
nohlsearch = false,
autojump = false,
},
label = {
uppercase = false,
exclude = "",
current = true,
after = true,
before = false,
style = "overlay",
reuse = "lowercase",
distance = true,
min_pattern_length = 0,
rainbow = { enabled = false, shade = 5 },
},
highlight = {
backdrop = true,
matches = true,
priority = 5000,
groups = {
match = "FlashMatch",
current = "FlashCurrent",
backdrop = "FlashBackdrop",
label = "FlashLabel",
},
},
modes = {
-- enhanced f/t/F/T
char = {
enabled = true,
autohide = true,
jump_labels = true,
multi_line = true,
label = { exclude = "hjkliardc" },
char_actions = function(motion)
return {
[";"] = "next",
[","] = "prev",
[motion:lower()] = "next",
[motion:upper()] = "prev",
}
end,
search = { wrap = false },
highlight = { backdrop = false },
jump = { register = false },
},
treesitter = {
labels = "abcdefghijklmnopqrstuvwxyz",
jump = { pos = "range" },
search = { incremental = false },
label = { before = true, after = true, style = "inline" },
highlight = { backdrop = false, matches = false },
},
},
},
keys = {
{ "s", mode = { "n", "x", "o" }, function() require("flash").jump() end, desc = "Flash jump" },
{ "S", mode = { "n", "x", "o" }, function() require("flash").treesitter() end, desc = "Flash treesitter" },
{ "r", mode = "o", function() require("flash").remote() end, desc = "Remote Flash" },
{ "R", mode = { "o", "x" }, function() require("flash").treesitter_search() end, desc = "Treesitter search" },
{ "<c-s>", mode = { "c" }, function() require("flash").toggle() end, desc = "Toggle flash search" },
},
},

How Flash Works

Type: s         → activates flash jump mode
Type: fun → highlights all "fun" matches with labels (a, b, c...)
Type: c → jump to the match labelled 'c'

Type: S → treesitter jump
→ labels entire syntax nodes (functions, classes, blocks)

Operator mode:
dS → delete + treesitter select → pick a node to delete
ySiw) → surround inner word after jumping

5. mini.nvim — 40+ Micro Plugins in One

mini.nvim is a collection of small, focused plugins. Use only the modules you need:

{
"echasnovski/mini.nvim",
version = false,
config = function()
-- mini.ai: better text objects (extends i/a with more targets)
require("mini.ai").setup({
n_lines = 500,
custom_textobjects = {
-- whole buffer
B = function()
local from = { line = 1, col = 1 }
local to = {
line = vim.fn.line("$"),
col = math.max(vim.fn.getline("$"):len(), 1),
}
return { from = from, to = to }
end,
},
})

-- mini.move: move lines/selections with Alt+hjkl
require("mini.move").setup({
mappings = {
left = "<M-h>",
right = "<M-l>",
down = "<M-j>",
up = "<M-k>",
line_left = "<M-h>",
line_right = "<M-l>",
line_down = "<M-j>",
line_up = "<M-k>",
},
})

-- mini.pairs: alternative to nvim-autopairs (lighter)
-- require("mini.pairs").setup()

-- mini.splitjoin: split/join arguments (gS to split, gJ to join)
require("mini.splitjoin").setup({
mappings = { toggle = "gs" },
})

-- mini.bufremove: safer buffer deletion that keeps window open
require("mini.bufremove").setup()
vim.keymap.set("n", "<leader>bd", function()
require("mini.bufremove").delete(0, false)
end, { desc = "Delete buffer" })

-- mini.hipatterns: highlight patterns (TODO, FIXME, hex colors)
local hipatterns = require("mini.hipatterns")
hipatterns.setup({
highlighters = {
fixme = { pattern = "%f[%w]()FIXME()%f[%W]", group = "MiniHipatternsFixme" },
hack = { pattern = "%f[%w]()HACK()%f[%W]", group = "MiniHipatternsHack" },
todo = { pattern = "%f[%w]()TODO()%f[%W]", group = "MiniHipatternsTodo" },
note = { pattern = "%f[%w]()NOTE()%f[%W]", group = "MiniHipatternsNote" },
-- highlight hex colors inline
hex_color = hipatterns.gen_highlighter.hex_color(),
},
})

-- mini.indentscope: animated indent scope indicator
require("mini.indentscope").setup({
symbol = "│",
options = { try_as_border = true },
draw = { animation = require("mini.indentscope").gen_animation.none() },
})
end,
},

mini.nvim Module Reference

ModulePurposeAlternative
mini.aiExtended text objectstreesitter-textobjects
mini.moveMove lines/selections
mini.pairsAuto-pairsnvim-autopairs
mini.surroundSurroundnvim-surround
mini.commentToggle commentsComment.nvim
mini.bufremoveSafe buffer delete
mini.splitjoinSplit/join argssplitjoin.vim
mini.hipatternsPattern highlightstodo-comments
mini.indentscopeAnimated indentindent-blankline
mini.sessionsSession managementauto-session
mini.starterDashboardalpha-nvim
mini.filesFile manageroil.nvim
mini.diffInline diffgitsigns
mini.statuslineStatus linelualine
mini.tablineBuffer tabsbufferline
mini.mapCode minimap
mini.clueKey hintswhich-key

6. Todo-comments.nvim — Highlight TODOs

{
"folke/todo-comments.nvim",
dependencies = { "nvim-lua/plenary.nvim" },
event = { "BufReadPost", "BufNewFile" },
keys = {
{ "]t", function() require("todo-comments").jump_next() end, desc = "Next TODO" },
{ "[t", function() require("todo-comments").jump_prev() end, desc = "Prev TODO" },
{ "<leader>ft", "<cmd>TodoTelescope<cr>", desc = "Find TODOs" },
{ "<leader>fT", "<cmd>TodoTrouble<cr>", desc = "TODO list (Trouble)" },
},
opts = {
signs = true,
sign_priority = 8,
keywords = {
FIX = { icon = " ", color = "error", alt = { "FIXME", "BUG", "FIXIT", "ISSUE" } },
TODO = { icon = " ", color = "info" },
HACK = { icon = " ", color = "warning" },
WARN = { icon = " ", color = "warning", alt = { "WARNING", "XXX" } },
PERF = { icon = "󰅒 ", color = "hint", alt = { "OPTIM", "PERFORMANCE", "OPTIMIZE" } },
NOTE = { icon = "󰋽 ", color = "hint", alt = { "INFO" } },
TEST = { icon = "⏲ ", color = "test", alt = { "TESTING", "PASSED", "FAILED" } },
},
highlight = {
multiline = true,
multiline_pattern = "^.",
multiline_context = 10,
before = "",
keyword = "wide",
after = "fg",
pattern = [[.*<(KEYWORDS)\s*:]],
comments_only = true,
},
},
},

Usage in code:

-- TODO: implement this feature
-- FIXME: this breaks when x is nil
-- HACK: workaround for upstream issue #123
-- NOTE: this is intentional behavior
-- PERF: consider caching this result

What's Next