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.

LazyVim Bundled

LazyVim bundles: flash.nvim (🟢), which-key.nvim (🟢), grug-far.nvim (🟢), mini.pairs (🟢), ts-comments.nvim (🟢), mini.ai (🟢). NOT bundled: vim-surround/nvim-surround (⚪), dial.nvim (⚪), yanky.nvim (⚪).

1. nvim-autopairs — Smart Bracket Completion (⚪ Manual install)​

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 (⚪ Manual install)​

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 = "&lt;C-g&gt;s", -- in insert mode
insert_line = "&lt;C-g&gt;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&lt;em&gt; → surround inner word in HTML tag: &lt;em&gt;word</em>

# Change surroundings:
cs"' → change " to ': "hello" → 'hello'
cs({ → change ( to {: (args) → {args}
cs"&lt;strong&gt; → change quote to HTML tag

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

# Visual mode:
viw S" → select inner word, surround with "
V S&lt;div&gt; → select line, surround with div tag

3. Comment.nvim — Toggle Comments (⚪ Manual install)​

{
"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 &lt;template&gt; section of Vue: uses HTML comment -->
&lt;div&gt;content</div>

// In &lt;script&gt; section: uses JS comment
const x = 1

4. Flash.nvim — Fast Cursor Jumping (🟢 LazyVim included)​

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" },
{ "&lt;c-s&gt;", mode = { "c" }, function() require("flash").toggle() end, desc = "Toggle flash search" },
},
},

How Flash Works (🟢 LazyVim included)​

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 (🟢 LazyVim included)​

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 = "&lt;M-h&gt;",
right = "&lt;M-l&gt;",
down = "&lt;M-j&gt;",
up = "&lt;M-k&gt;",
line_left = "&lt;M-h&gt;",
line_right = "&lt;M-l&gt;",
line_down = "&lt;M-j&gt;",
line_up = "&lt;M-k&gt;",
},
})

-- 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", "&lt;leader&gt;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 (🟢 LazyVim included)​

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 (🟢 LazyVim included)​

{
"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" },
{ "&lt;leader&gt;ft", "&lt;cmd&gt;TodoTelescope&lt;cr&gt;", desc = "Find TODOs" },
{ "&lt;leader&gt;fT", "&lt;cmd&gt;TodoTrouble&lt;cr&gt;", 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

In LazyVim​

LazyVim Context

LazyVim automates the setup for most of the popular plugins discussed here. By leveraging lazy.nvim, it provides a deeply integrated out-of-the-box experience where core plugins (like Telescope, Neo-tree, and Lualine) are pre-configured with sensible defaults and keymaps, while others can be easily enabled via :LazyExtras.

AspectVanilla NeovimLazyVim
Plugin InstallationManual via lazy.nvim or packerPre-bundled or enabled via :LazyExtras
KeymapsManually definedUnified mappings under <leader>
ConfigurationManual setup() callsAuto-configured, override via opts

What's Next​