//, ///, //!)| Feature | celeste_comment.nvim | Neovim built-in | Comment.nvim | mini.comment | vim-commentary |
|---|---|---|---|---|---|
| Edit model | TextEdits — edits as range+text objects• changes commit via nvim_buf_set_text or nvim_buf_set_lines (lockmarks) |
Direct line replacement• nvim_buf_set_lines (lockmarks) |
Direct line replacement• nvim_buf_set_lines (lockmarks) |
Direct line replacement• nvim_buf_set_lines (lockmarks) |
Direct line replacement• Vim setline() |
| Line comment | ✅ | ✅ | ✅ | ✅ | ✅ |
| Block comment | ✅ | ❌ | ✅ | ❌ | ❌ |
| Dot-repeat | ✅ | ✅ | ✅ | ✅ | ✅ |
| Count | ✅ | ✅ | ✅ | ✅ | ✅ |
| Indent algorithm | VSCode-style — min visible col• handle mixed tab/space | Simple — min whitespace prefix• not handle mixed tab/space | Standard — shiftwidth/tabstop | Simple — min whitespace prefix• not handle mixed tab/space | Minimal — ^\s*\zs• optional startofline |
| Keep cursor | Precise tracking — adjust per TextEdit• row/col shifts• multi-line inserts | ❌ | Imprecise restore — save/restore• no edit adjustment | ❌ | ❌ |
| Invert per line | ✅ | ❌ | ❌ | ❌ | ❌ |
| Line textobject | ✅ | ✅ | ❌ | ✅ | ✅ |
| Block textobject | ✅ | ❌ | ❌ | ❌ | ❌ |
| Textobject auto | ✅ | ❌ | ❌ | ❌ | ❌ |
| gcu | ✅ | ❌ | ❌ | ❌ | ✅ |
[!IMPORTANT] Breaking changes may occur, pinning to a specific version or commit is recommended.
vim.pack.add({ src = "https://github.com/celeste3z/celeste_comment.nvim", name = "celeste_comment" })
require("celeste_comment").setup({})
{ "celeste3z/celeste_comment.nvim", opts = {} }
{
-- Restore cursor position after commenting. Saves the cursor position
-- before the operator runs, then computes the new position based on
-- the edits (adjusting row/col for insertions and deletions).
keep_cursor = true,
-- Insert space between comment marker and text. When `true`, a single
-- space is appended after the trimmed comment marker. When `false`, the
-- commentstring's original spacing is preserved as-is.
-- Example with `commentstring = "// %s"`:
-- `true` → tlcs="//", olcs="// " → `// hello`
-- `false` → lcs="// ", olcs="// " → `// hello`
insert_space = true,
-- Place comment at start of line, skip indent alignment
line_comment_no_indent = false,
-- Match comment markers case-insensitively (e.g. `@REM` vs `@rem` vs `@rEm`)
case_insensitive = false,
-- Relaxed block detection: when true, leading and trailing whitespace
-- is trimmed from the range before looking for block comment tokens.
-- This helps find block comments inside selections that have extra
-- whitespace padding. When false, block detection uses the selection
-- range as-is.
block_relaxed_detect = true,
-- Maximum number of lines (forward and backward) to search for block
-- comment pairs when using the block textobject (`gb` in operator-pending).
-- Only relevant when no treesitter query is available for the buffer.
block_textobj_nlines = 200,
-- Controls how empty lines are handled during comment toggle:
-- - "never": toggle and align empty lines normally.
-- - "indent": toggle empty lines but exclude them from indent alignment.
-- Also trim to blank when uncommenting leaves only whitespace.
-- - "always": skip empty lines entirely — not toggled, not aligned.
ignore_empty_lines = "indent",
-- Controls when to fallback from line comment to block comment:
-- - "never": never fallback. A wrapping commentstring
-- (e.g. HTML's `<!-- %s -->`) is treated as
-- a line comment. Each line gets wrapped
-- individually on `gc`.
-- - "if_line_cms_wrapped": fallback when the line comment string wraps
-- the content (`<!-- %s -->`), or when no
-- line comment is available at all.
--
-- Example: buffer with `commentstring = "<!-- %s -->"`, press `gc2j`
-- on lines `aaa` / `bbb`:
--
-- "never": "if_line_cms_wrapped":
-- <!-- aaa --> <!-- aaa
-- <!-- bbb --> bbb -->
--
-- With `"if_line_cms_wrapped"`, the plugin detects the wrapping nature
-- and uses block toggle to wrap the whole region once.
fallback_to_block = "if_line_cms_wrapped",
-- Log level
log_level = vim.log.levels.OFF,
mappings = {
-- Line comment by motion (n)
comment = "gc",
-- Line comment current line (n)
comment_line = "gcc",
-- Line comment visual selection (x)
comment_visual = "gc",
-- Block comment by motion (n, x)
block = "gb",
-- Block comment current line (n)
block_line = "gbc",
-- Block comment visual selection (x)
block_visual = "gb",
-- Linewise textobject (o)
textobject_line = "gc",
-- Blockwise textobject (o)
textobject_block = "gb",
-- Auto textobject (o, x)
textobject_auto = "",
-- Auto uncomment (n)
uncomment_auto = "",
-- Insert comment below (n)
comment_below = "",
-- Insert comment above (n)
comment_above = "",
-- Insert comment at end of line (n)
comment_eol = "",
-- Invert comment per line (n, x)
invert = "",
},
hooks = {
-- Called before commit edits, receives context
pre_commit_edits = nil,
-- Custom comment string resolver function
cms_conf_resolver = nil,
},
}
Set a mapping to "" to disable it.
Buffer-local configuration can be set via vim.b.celeste_comment_config.
This allows overriding non-mapping options per buffer:
-- In an ftplugin or autocmd:
vim.b.celeste_comment_config = {
keep_cursor = false,
insert_space = false,
}
The buffer config is merged with the global config, so you only need to
specify the fields you want to override. Note: mappings cannot be
overridden per buffer — they are fixed at setup() time.
The plugin resolves comment strings through a chain of resolvers (high to low priority):
1. hooks.cms_conf_resolver — Full control, highest priority:
-- Global
require("celeste_comment").setup({
hooks = {
cms_conf_resolver = function(ctx)
-- ctx.cursor, ctx.cfg, ctx.range
ctx.o_cms_conf = { "//%s", "/*%s*/" } -- { line, block }
end,
},
})
-- Per-buffer
vim.b.celeste_comment_config = {
hooks = {
cms_conf_resolver = function(ctx)
ctx.o_cms_conf = { "#%s" }
end,
},
}
2. cms_confs table — Override per filetype:
-- Global
require("celeste_comment").setup({
cms_confs = {
toml = { "#%s" }, -- line only
html = { nil, "<!--%s-->" }, -- block only
python = { "#%s", '"""%s"""' }, -- both
rust = { { "//%s", "///%s", "//!%s" }, "/*%s*/" }, -- multi-token line
mylang = function(ctx) -- dynamic resolver per filetype
return { "//%s", "/*%s*/" }
end,
},
})
-- Per-buffer
vim.b.celeste_comment_config = {
cms_confs = {
python = { "#%s" },
},
}
3. cms_confs = false — Disable the built-in table entirely:
-- Global
require("celeste_comment").setup({ cms_confs = false })
-- Per-buffer
vim.b.celeste_comment_config = { cms_confs = false }
4. Built-in defaults — 40+ languages supported out of the box.
5. Buffer fallback — Uses vim.bo.commentstring and
vim.b.celeste_comment_block_commentstring as the last resort:
vim.bo.commentstring = "// %s"
vim.b.celeste_comment_block_commentstring = "/* %s */"
hooks.pre_commit_editsCalled before edits are applied to the buffer:
---@param ctx Celeste.Comment.Hooks.PreCommitEdits.Ctx
function(ctx)
end
hooks.cms_conf_resolverCalled to resolve comment string configuration for a given language/buffer.
Receives a context table and should set o_cms_conf:
---@param ctx Celeste.Comment.Hooks.CmsConfResolver.Ctx
function(ctx)
ctx.o_cms_conf = { "//%s", "/*%s*/" }
end
Set vim.g.celeste_comment_disable globally or vim.b.celeste_comment_disable per buffer to true to disable all functionality.
vim.g.celeste_comment_disable = true
Auto-detect textobject accuracy — textobject_auto() first checks
whether the current line contains a line comment. In languages like Lua
where -- is used for both line comments (--) and block comments
(--[[ ]]), a line starting with -- may be misidentified as a line
comment, leading to incorrect textobject selection.
Regex-based textobject range — Pattern matching can produce false
positives in certain scenarios. For example, comment-like tokens inside
strings may be mistakenly treated as actual comments. Additionally, the
scan range is capped by block_textobj_nlines (default 200), so
textobject detection may not work beyond that limit.
Visual block mode (<C-v>) — Selection is treated as linewise; the
entire selected lines are block-commented rather than inserting comment
markers per column. For column-wise comment operations, consider using
a plugin like multicursor.nvim.
VSCode — The indent algorithm is ported from VSCode's comment implementation. Most of its test cases have also been ported to this plugin's test suite. This plugin is highly inspired by it.
mini.comment — Its code style and linewise textobject implementation served as a reference for this plugin's development.
Comment.nvim — Part of the built-in language comment string table was adapted from Comment.nvim.