Skip to content

Conversation

@echasnovski
Copy link
Member

@echasnovski echasnovski commented Apr 4, 2024

This PR adds built-in support for commenting.

Design

  • Enable commenting support only through gc mappings for simplicity. No ability to configure, no Lua module, no user commands. Yet.

  • Overall implementation is a simplified version of mini.comment adapted to be a better suit for core. It basically means reducing code paths which use only specific fixed set of plugin config.

    All used options are default except pad_comment_parts = false. This means that 'commentstring' option is used as is without forcing single space inner padding. This choice is deliberate because without user configuration this is the only approach which allows users to have any comment format. The downside of this is that some (usually "older") values of 'commentstring' currently have no padding in them (like Vimscript's "%s" and C's /*%s*/). I think (more like, hope) those can be changed without breaking much.

    The best approach for users to customize 'commentstring' is to create a filetype plugin (see :h ftplugin) or FileType autocommand inside which set the option locally (with :setlocal or lua vim.bo). Changing it interactively will not always work (for example, in presence of active tree-sitter parser).

Comparison with 'tpope/vim-commentary'

As tpope/vim-commentary was considered for inclusion in #27999, here is a quick summary of how this PR differs from it:

  • User-facing features. Both implement similar user-facing mappings. This PR does not include gcu which is essentially a gcgc. There are no commands, events, or configuration in this PR.

  • Size. Both have reasonably comparable number of lines of code, while this PR has more comments in tricky areas. Here is a summary table (both are compared without code which makes mappings):

    File Lines Code Comments Blanks
    This PR 252 140 69 43
    vim-commentary 113 104 2 7
  • Maintainability. This PR has (purely subjectively) better readability, tests, and Lua types.

  • Configurability. This PR has no user configuration, while 'vim-commentary' has some (partially as a counter-measure to possibly modifying 'commentstring' option).

  • Extra features:

    • This PR supports tree-sitter by computing 'commentstring' option under cursor, which can matter in presence of tree-sitter injected languages. Note: presence of tree-sitter parser in buffer is enough for this to take effect; running vim.treesitter.start()/vim.treesitter.stop() affects only highlighting.

    • This PR comments blank lines while 'tpope/vim-commentary' does not.
      It is a carefully thought through decision for a more intuitive experience at a cost of extra rules for how blank lines should be commented and later uncommented.
      Edit: after a feedback and discussion, blank lines are not taken into account when deciding whether a text range should be commented or uncommented. This allows uncommenting commented blocks which are separated only by blank lines. See this comment for an example.

    • This PR has much better speed on larger chunks of lines (like >1000). This is thanks to using nvim_buf_set_lines() to set all new lines at once, and not with vim.fn.setline(). This comes at a price of some lockmarks hackery and affecting extmarks inside a range. See more details above single nvim_buf_set_lines() call in this PR or a full PhD thesis in 'mini.comment'.

Copy link
Member

@dundargoc dundargoc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works flawlessly for me! You knocked it out of the park with this.

Copy link
Member

@justinmk justinmk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nicely done. Testability is a strong reason for including this impl.

supports tree-sitter by computing 'commentstring' option under cursor, which can matter in presence of tree-sitter injected languages.
...
comments and uncomments blank lines while 'tpope/vim-commentary' does not. ... more intuitive experience

These are worth mentioning briefly in the help, IMO.

P.S. Please include the PR description in the commit message.

@clason
Copy link
Member

clason commented Apr 4, 2024

(Links in commit messages can get very spammy, though.)

--- Get 'commentstring' at cursor
---@param ref_position integer[]
---@return string
local function get_commentstring(ref_position)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible followup: this could be more broadly useful refactored as a vim.treesitter.get_local_option('commentstring', position).

@echasnovski
Copy link
Member Author

echasnovski commented Apr 4, 2024

One thing to note for a future reference. This PR might introduce errors after :helptags ALL if users have 'tpope/vim-commentary' installed (maybe other similar plugins), as there will be duplicating tags. As tag style used in this PR is consistent with what is used in core, I think it is worth leaving as is.

This is now resolved by using -default suffix for mappings' tags.

@echasnovski
Copy link
Member Author

echasnovski commented Apr 4, 2024

Nicely done. Testability is a strong reason for including this impl.

Thanks 🙏!

supports tree-sitter by computing 'commentstring' option under cursor, which can matter in presence of tree-sitter injected languages.
...
comments and uncomments blank lines while 'tpope/vim-commentary' does not. ... more intuitive experience

These are worth mentioning briefly in the help, IMO.

The first one is already mentioned with "... 'commentstring' at cursor". I'd leave it at that. Added about commenting blank lines. Added usage examples which include empty line, so this should make more evident how commenting is done on empty/blank lines.

P.S. Please include the PR description in the commit message.

Will add a stripped down version during later squash.

@gpanders
Copy link
Member

gpanders commented Apr 5, 2024

I'm very excited to finally see this feature included. Thanks @echasnovski for once again providing an excellent contribution.

However, I have one concern about this:

This PR comments and uncomments blank lines while 'tpope/vim-commentary' does not.
It is a carefully thought through decision for a more intuitive experience at a cost of extra rules for how blank lines should be commented/uncommented.

I disagree that this is more intuitive, and in fact I would argue it's the opposite, in particular when un-commenting. Consider this example:

// int x = foo();

// int y = bar(x);

The 2nd line is a blank line: there are no characters in it all. If I use the "toggle comment" operation on these 3 lines, what I get is the following:

// // int x = foo();
//
// // int y = bar(x);

But what I would expect to see is

int x = foo();

int y = bar(x);

This is rather annoying because when this happens I have to toggle the comments on the region again to get back to the original state, and then manually use the "toggle comment" operator on each individual line. For 2 lines that may not be a big deal, but for a less contrived, real world use case where there many such lines/individual regions, it becomes much more annoying.

I really feel strongly that this should not be the default behavior.

@echasnovski
Copy link
Member Author

This is rather annoying because when this happens I have to toggle the comments on the region again to get back to the original state, and then manually use the "toggle comment" operator on each individual line. For 2 lines that may not be a big deal, but for a less contrived, real world use case where there many such lines/individual regions, it becomes much more annoying.

This situation, assuming continuous usage of suggested commenting, can happen if user toggled comment for those lines separately. In this case it is reasonable to expect for them to be also uncommented separately.

I argue that commenting whole regions at once is more common than doing it line by line. So middle blank line would have been commented to begin with.

I really feel strongly that this should not be the default behavior.

Unfortunately, I feel strongly that this should be the default behavior.

Its only downside is that toggling twice a blank non-empty line converts it to empty, which I argue is even a good side-effect.

The benefits of commenting blank lines are:

  • It shows visible intent of which block waa originally commented. This really helps when going back to commented code after a while and not having to figure out which blocks were commented in the first place (like if it already was adjacent to other commented text).
  • It makes textobject much more intuitive to reason about and easier to implement. Otherwise for better use it should cleverly ignore blank lines when computing range.

@clason
Copy link
Member

clason commented Apr 5, 2024

For the record, I'm in @gpanders camp here, personally. Although I don't care enough to block the PR on this, it's good to have this discussion explicitly and no matter what the end result is, document it. (I found "carefully thought through decision for a more intuitive experience" too vague myself but knew from previous discussions that it was about the text object.)

@clason
Copy link
Member

clason commented Apr 5, 2024

One possible compromise would be to break symmetry:

  1. comment out blank lines
  2. ignore blank lines when uncommenting (i.e., when deciding whether to comment or uncomment a block).

One thing to keep in mind is that

I argue that commenting whole regions at once is more common than doing it line by line. So middle blank line would have been commented to begin with.

is a fallacy, since you often have to work with already commented code from other people.

Second alternative -- if we absolutely can't get consensus -- is to add an option for this, just like mini.comment already has. I'd prefer if we could do without, but if that's the price of adding this feature, it'd be worth it.

@lewis6991
Copy link
Member

lewis6991 commented Apr 5, 2024

I wasn't aware of this and I also strongly agree with @gpanders .

I thought the goal here was to include a Lua version of vim-commentary, which is what a large percentage of the vim/nvim user base has grown accustomed too.

@echasnovski
Copy link
Member Author

I wasn't aware of this and I also strongly agree with @gpanders .

I thought the goal here was to include a Lua version of vim-commentary, which is what a large percentage of the vim/nvim user base has grown accustomed too.

The goal here was to include a 'mini.comment' port, which is explicitly stated very close to top of the initial comment. With an explicit mention of this issue in the later comparison because I knew it might be a big deal.

I would also say (in the same spirit) that a large percentage of Neovim user base has grown accustomed to have blank lines commented as both 'mini.comment' and 'NumToStr/Comment.nvim' have this enabled by default.

One possible compromise would be to break symmetry:

I don't think breaking a symmetry is a good idea as it would break commutativity: "comment-uncomment" will return to initial lines (if empty lines included) while "uncomment-comment" will not.

One thing to keep in mind is that

I argue that commenting whole regions at once is more common than doing it line by line. So middle blank line would have been commented to begin with.

is a fallacy, since you often have to work with already commented code from other people.

It is not as big of a statement to be called fallacy. Mostly because many more issues can arise if previous editing by other people are taken into account. For example, different comment structure could have been used and uncommenting will not work as expected (if at all).


Second alternative -- if we absolutely can't get consensus -- is to add an option for this, just like mini.comment already has. I'd prefer if we could do without, but if that's the price of adding this feature, it'd be worth it.

From my side I'll say the following. I would like to not be associated with a contribution which does not include possibility of commenting blank lines. Having it configurable is fine by me, but it will open another can of worms about how configuration is done.

One thing to add here is that forcing ignoring blank lines by default (with alternative behind an option) will disrupt synchronization of tests in this PR and 'mini.comment'. Which is a bit worth for long term maintainability.

@clason
Copy link
Member

clason commented Apr 5, 2024

I don't think breaking a symmetry is a good idea as it would break commutativity: "comment-uncomment" will return to initial lines (if empty lines included) while "uncomment-comment" will not.

I'd be fine with that. Again, the use case in mind here (which needs to be kept in mind) is working with pre-commented code; converting it to your format (blank line commented) is a feature, not a problem.

@neovim neovim deleted a comment from dhruvasagar Apr 7, 2024
@neovim neovim deleted a comment from dhruvasagar Apr 7, 2024
Gasol added a commit to Gasol/dotfiles that referenced this pull request Apr 7, 2024
@aktau
Copy link
Contributor

aktau commented Apr 8, 2024

Experience report.

Thanks for the feature, the injected languages recognition is pretty great (despite what @barrett-ruth observed, which I haven't run into yet when editing lua-in-viml).

I noticed the existence of this feature due to the default of pad_comment_parts = false as mentioned in the description. It doesn't match what tpope/vim-commentary does by default (which I was using). It's mildly jarring, I'll see if I can get used to it. The "problem" is that for real comments, I always pad with a space. For temporary comment block, I'd be OK with the lack of a space. I'm not sure how often I use gc for real comment blocks, time will tell.

Also I was surprised that the following kept on working (in the sense that it maintains the space between commentstring and first text), I was happy to find out about that:

" Rewrap the current comment block. Necessary because paragraphs don't work
" properly inside of comment blocks, so we re-use tpope's comment block text
" object. nnoremap doesn't work. I have _no_ idea why, maybe because gc isn't
" builtin?. It appears to set a cursor to the end of the block, which I fix with
" marks. Overrides mark `q`, which I hope I wasn't using. Normally I'd use the
" `'` mark but that gets overriden during the `gc` movement, probably because of
" a jump.
nmap <leader>e mjgqgc`j

(The comment about vim-commentary is now a bit outdated, of course.)

Also, this seems to override tpope/vim-commentary. That may be undesirable if people really want that space (I, too, may consider moving back). Thing is, I don't know how to move back.

@zeertzjq
Copy link
Member

zeertzjq commented Apr 8, 2024

That seems to be a bug in vim-commentary. The following change will fix it:

diff --git a/plugin/commentary.vim b/plugin/commentary.vim
index 5370106..ed056a4 100644
--- a/plugin/commentary.vim
+++ b/plugin/commentary.vim
@@ -108,7 +108,6 @@ nnoremap <expr>   <Plug>Commentary     <SID>go()
 nnoremap <expr>   <Plug>CommentaryLine <SID>go() . '_'
 onoremap <silent> <Plug>Commentary        :<C-U>call <SID>textobject(get(v:, 'operator', '') ==# 'c')<CR>
 nnoremap <silent> <Plug>ChangeCommentary c:<C-U>call <SID>textobject(1)<CR>
-nmap <silent> <Plug>CommentaryUndo :echoerr "Change your <Plug>CommentaryUndo map to <Plug>Commentary<Plug>Commentary"<CR>
 
 if !hasmapto('<Plug>Commentary') || maparg('gc','n') ==# ''
   xmap gc  <Plug>Commentary
@@ -118,4 +117,6 @@ if !hasmapto('<Plug>Commentary') || maparg('gc','n') ==# ''
   nmap gcu <Plug>Commentary<Plug>Commentary
 endif
 
+nmap <silent> <Plug>CommentaryUndo :echoerr "Change your <Plug>CommentaryUndo map to <Plug>Commentary<Plug>Commentary"<CR>
+
 " vim:set et sw=2:

@aktau
Copy link
Contributor

aktau commented Apr 8, 2024

That seems to be a bug in vim-commentary. The following change will fix it:

What specifically are you referring to? The fact that vim-commentary does not override builtin commenting?

@zeertzjq
Copy link
Member

zeertzjq commented Apr 8, 2024

That seems to be a bug in vim-commentary. The following change will fix it:

What specifically are you referring to? The fact that vim-commentary does not override builtin commenting?

Yes. I've created tpope/vim-commentary#173 to fix it.

@echasnovski
Copy link
Member Author

I noticed the existence of this feature due to the default of pad_comment_parts = false as mentioned in the description. It doesn't match what tpope/vim-commentary does by default (which I was using). It's mildly jarring, I'll see if I can get used to it. The "problem" is that for real comments, I always pad with a space. For temporary comment block, I'd be OK with the lack of a space. I'm not sure how often I use gc for real comment blocks, time will tell.

The reason for this is to allow any 'commentstring' value (including no padding) in the presence of no other way to override it (opposed to b:commentary_format from 'tpope/vim-commentary'). At the moment to counter that users can set custom 'commentstring' to have formatting they want. I hope that after the release there will be work to modify default 'commentstring' values to have padding where it is reasonable.

@clason
Copy link
Member

clason commented Apr 8, 2024

. I hope that after the release there will be work to modify default 'commentstring' values to have padding where it is reasonable.

As these are set by the bundled ftplugins, that change will have to come from vim/vim. Someone will have to open a PR there to add spaces to those commentstrings lacking it (and the maintainers not insisting that no space is in fact correct).

@aktau
Copy link
Contributor

aktau commented Apr 8, 2024

You're right, changing the commentstring to add a space seems to work in some sense, but it does mean that //comments are no longer recognized as comments which is annoying (though it's minor, such comments are not really allowed in the codebases I work on).

@aktau
Copy link
Contributor

aktau commented Apr 8, 2024

Also, perhaps help 'commentstring' should be updated referencing this new feature.

@clason
Copy link
Member

clason commented Apr 8, 2024

Already in the works.

@echasnovski
Copy link
Member Author

I've edited the initial comment with some details, but will duplicate here for more visibility.

  • The best approach for users to customize 'commentstring' is to create a filetype plugin (see :h ftplugin) or FileType autocommand inside which set the option locally (with :setlocal or lua vim.bo). Changing it interactively will not always work (for example, in presence of active tree-sitter parser).
  • Presence of a tree-sitter parser in a buffer is enough for using tree-sitter based 'commentstring' (and thus ignoring buffer-local one). Running vim.treesitter.start()/vim.treesitter.stop() affects only highlighting and will not affect commenting.

@calebdw
Copy link
Contributor

calebdw commented Apr 10, 2024

Awesome! Thanks for this!

I have nvim-ts-context-commentstring installed, is this still needed / helpful?

@echasnovski
Copy link
Member Author

echasnovski commented Apr 10, 2024

I have nvim-ts-context-commentstring installed, is this still needed / helpful?

For most use cases - probably no. For some complicated ones (like JSX) - probably yes. The best way to know for sure is to try without it and see if anything does not work as you'd expect.

askfiy added a commit to askfiy/SimpleNvim that referenced this pull request Apr 10, 2024
askfiy added a commit to askfiy/SimpleNvim that referenced this pull request Apr 10, 2024
stefanvanburen added a commit to stefanvanburen/dotfiles that referenced this pull request Apr 10, 2024
This is [built-in to neovim now][1] on HEAD, so use it instead. Behaves
differently (doesn't ignore blank lines), but would rather use the
built-in thing than quibble with that.

[1]: neovim/neovim#28176
@echasnovski
Copy link
Member Author

Not sure if this is desired behavior, but it seems rather unintuitive:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body></body>
  <script>

    console.log("hi");
    console.log("hi");

  </script>
</html>

I think this now should behave as expected after #28262.

@calebdw
Copy link
Contributor

calebdw commented Apr 12, 2024

As these are set by the bundled ftplugins, that change will have to come from vim/vim. Someone will have to open a PR there to add spaces to those commentstrings lacking it (and the maintainers not insisting that no space is in fact correct).

Until then I just created a simple autocommand to enforce the space(s):

vim.api.nvim_create_autocmd({ 'FileType' }, {
  desc = 'Force commentstring to include spaces',
  -- group = ...,
  callback = function()
    local cs = vim.bo.commentstring

    if not cs or not cs:match('%%s') then
      return
    end

    vim.bo.commentstring = cs:gsub('%s*%%s%s*', ' %%s '):gsub('%s*$', '')
  end,
})

@echasnovski
Copy link
Member Author

As these are set by the bundled ftplugins, that change will have to come from vim/vim. Someone will have to open a PR there to add spaces to those commentstrings lacking it (and the maintainers not insisting that no space is in fact correct).

Until then I just created a simple autocommand to enforce the space(s):

Here is a slightly more concise and precise version:

vim.api.nvim_create_autocmd({ 'FileType' }, {
  desc = 'Force commentstring to include spaces',
  -- group = ...,
  callback = function(event)
    local cs = vim.bo[event.buf].commentstring
    vim.bo[event.buf].commentstring = cs:gsub('(%S)%%s', '%1 %%s'):gsub('%%s(%S)', '%%s %1')
  end,
})

@T-727
Copy link
Contributor

T-727 commented Apr 12, 2024

I have my own little comment function but I wanted to try out the builtin one, but there's one behaviour I'm missing which is that the builtin comment doesn't always toggle comments, instead it's on a per-block basis. Is there any solution for that? would it be something that could be added or are most people against it?

So currently this:

// commented code
uncommented code

becomes this:

// // commented code
// uncommented code

While I would it like to become this:

commented code
// uncommented code

@gpanders
Copy link
Member

gpanders commented Apr 12, 2024

would it be something that could be added or are most people against it?

I don't think this is intuitive or expected behavior at all, because the // commented code in your example could just as well be an actual comment, and this behavior would uncomment it.

You can replicate the behavior you want using :normal and a visual mode mapping though:

xnoremap gc :normal gcc<CR>

@T-727

This comment was marked as resolved.

@neovim neovim locked as resolved and limited conversation to collaborators Apr 14, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.