README.md

August 2, 2026 · View on GitHub

celeste_comment.nvim

Batteries-included commenting plugin — line/block comment, textobjects, real sticky cursor, and more!

Features

  • Line/block comment toggle -- fully dot-repeatable with count support
  • Truly accurate keep cursor -- precise row/column tracking across TextEdits, adjusted per edit
  • Truly accurate keep selection -- selection range tracks each TextEdit precisely when toggling comments in visual mode
  • VSCode-style indent algorithm -- handles mixed tabs and spaces
  • Invert/Force add/Force remove comment -- per-line comment action control
  • Textobjects -- line, block, and auto textobjects, works without Tree-sitter
  • Insert mode line comment toggle -- with cursor sticky support
  • Insert comment above / below / at end of line
  • Case insensitive comment detection -- e.g. @REM vs @rem vs @rEm
  • Context-aware comment string resolution via Tree-sitter -- comment string adapts to context via Tree-sitter, no extra plugins required. e.g. supports JSX/TSX out of the box
  • Multi-variant comment string detection — recognizes all comment prefix variants when uncommenting (e.g. Rust //, ///, //!)
  • TextEdits -- unlike Neovim's built-in or other plugins, edits are modeled as TextEdits, making it more hackable and composable

Comparison

Featureceleste_comment.nvimNeovim built-inComment.nvimmini.commentvim-commentary
Edit modelTextEdits — edits as range+text objects
• commit changes 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
Force add comment
Force remove comment
Dot-repeat
Count
Indent algorithmVSCode-style — min visible col
• handle mixed tab/space
Simple — min whitespace prefix
• does not handle mixed tab/space
Standard — shiftwidth/tabstopSimple — min whitespace prefix
• does not handle mixed tab/space
Minimal — ^\s*\zs
• optional startofline
Keep cursorPrecise tracking — cursor adjusts per TextEditImprecise restore — save/restore
• no edit adjustment
Keep selectionPrecise tracking — selection adjusts per TextEdit
Invert per line
Line textobject
Block textobject
Textobject auto
Uncomment auto

Showcase

Line/Block comment toggle, textobjects, gcu

Line/Block comment toggle, textobjects, gcu

Commenting in insert mode with keep cursor

Commenting in insert mode with keep cursor

Context-aware comment string resolution via Tree-sitter

Context-aware comment string resolution via Tree-sitter

Keep selection when toggle comments in visual mode

Keep selection when toggle comments in visual mode

Force add/remove comment and dot-repeat

Force add/remove comment and dot-repeat

Invert comment status per-line

Invert comment status per-line

Cursor sticky and Dot-repeat

Cursor sticky + Dot-repeat

With multicursor.nvim

With multicursor.nvim

Requirements

  • Neovim >= 0.12
  • Tree-sitter parsers (Optional) -- for context-aware comment string resolution

Installation

Important

  • Breaking changes may occur in MINOR version bumps (e.g. 0.1.00.2.0).
  • PATCH bumps (e.g. 0.1.00.1.1) are backward compatible.
  • Pinning to a specific version or commit is recommended.

vim.pack (Neovim 0.12+)

vim.pack.add({
  {
    src = "https://github.com/celeste3z/celeste_comment.nvim",
    name = "celeste_comment",
    version = vim.version.range("*"),
  }
})

require("celeste_comment").setup({})

lazy.nvim

{ "celeste3z/celeste_comment.nvim", lazy = false, opts = {} }

Default Configuration

{
  -- Restore cursor position after commenting.
  keep_cursor            = true,

  -- Restore selection after commenting.
  -- See `:help celeste_comment-config-keep_selection`
  -- Possible values: "never" | "accurate" | "expand_block"
  keep_selection         = "never",

  -- Insert space between comment marker and text.
  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,

  -- Trim whitespace before detecting block tokens.
  block_relaxed_detect   = true,

  -- Max lines to search for block comment pairs.
  block_textobj_nlines   = 200,

  -- How to handle empty lines during comment toggle.
  -- See `:help celeste_comment-config-ignore_empty_lines` for more details
  -- Possible values: "never" | "mixed" | "always"
  ignore_empty_lines     = "always",

  -- Fallback to block comment when line comment wraps.
  -- See `:help celeste_comment-config-fallback_to_block` for more details
  -- Possible values: "never" | "if_line_cms_wrapped"
  fallback_to_block      = "if_line_cms_wrapped",

  -- Log level (nvim-0.13+). Ignored on older versions.
  log_level              = vim.log.levels.OFF,

  -- Comment string configuration.
  cms_confs              = nil,

  mappings = {
    -- Line comment by motion (n)
    line_toggle          = "gc",
    -- Line comment current line (n)
    line_toggle_cur      = "gcc",
    -- Line comment visual selection (x)
    line_toggle_visual   = "gc",
    -- Insert mode line toggle (i), example `{"<M-/>", "<M-_>"}`
    line_toggle_insert   = "",

    -- Block comment by motion (n, x)
    block_toggle         = "gb",
    -- Block comment current line (n)
    block_toggle_cur     = "gbc",
    -- Block comment visual selection (x)
    block_toggle_visual  = "gb",

    -- Linewise textobject (o)
    line_textobject      = "gc",
    -- Blockwise textobject (o)
    block_textobject     = "gb",
    -- Auto textobject (o, x), example 'ga'
    auto_textobject      = "",
    -- Auto uncomment (n), example `gcu`
    uncomment_auto       = "",

    -- Insert comment below (n), example `gco`
    line_add_below       = "",
    -- Insert comment above (n), example `gcO`
    line_add_above       = "",
    -- Insert comment at end of line (n), example `gcA`
    line_add_eol         = "",

    -- Invert comment per line (n, x), example `gcI`
    line_invert          = "",
    -- Force add line comment (n, x), example `gCC`
    line_force_add       = "",
    -- Force remove line comment (n, x), example `gCU`
    line_force_remove    = "",

    -- Cursor sticky dot-repeat
    dot_repeat           = ".",
  },

  hooks = {
    -- Called before commit edits, receives context
    pre_commit_edits     = nil,
    -- Called after commit edits, receives context
    post_commit_edits    = nil,
    -- Custom comment string resolver function
    cms_conf_resolver    = nil,
  },
}

See :help celeste_comment-configuration for details.

Tip

If a language has just one comment style (e.g. vim, asm) and Neovim already can sets its commentstring natively, you don't have to define anything here, we can fully fall back to Neovim's built-in commentstring resolution.

This plugin already has built-in block comment support for most common languages.

If your filetype isn't included, the fastest and simplest way to add it is:

  • Put the code below in your ftplugin/<filetype>.lua (see :help filetype-plugin)
  • Use FileType autocommand (:help FileType)
-- for example
vim.b.celeste_comment_block_commentstring = "{-%s-}"

Or, you can use cms_confs:

-- for example, `xxx` should be the Tree-sitter parser name or filetype
require("celeste_comment").setup({
  cms_confs = {
    "xxx" = {"//%s", "/*%s*/"}
  }
})

Line comments are configured via vim.bo.commentstring (the standard Neovim option).

For advanced comment string resolution, see :help celeste_comment.

If you like this plugin, give it a ⭐!

What it doesn't do

  • Cover all cases — This plugin's aim is to handle the vast majority of common scenarios, not every possible case. Known textobject edge cases and unusual comment patterns are acknowledged but not planned to fix.
  • Doc comment
  • Header comment

Limitations

  • Auto-detect textobject accuracytextobject_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.

Future work

  • Integrated with Neovim's builtin multicursor.

Acknowledgments

  • 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.

  • Zed — The capture-based overrides paradigm for comment scope resolution.

  • mini.comment — Its code style and linewise textobjects 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.