fix(comment): fall back to using trimmed comment markers (#28950)

fix(comment): fall back to using trimmed comment markers (#28938)

Problem: Currently comment detection, addition, and removal are done
  by matching 'commentstring' exactly. This has the downside when users
  want to add comment markers with space (like with `-- %s`
  commentstring) but also be able to uncomment lines that do not contain
  space (like `--aaa`).

Solution: Use the following approach:
  - Line is commented if it matches 'commentstring' with trimmed parts.
  - Adding comment is 100% relying on 'commentstring' parts (as is now).
  - Removing comment is first trying exact 'commentstring' parts with
    fallback on trying its trimmed parts.
(cherry picked from commit 0a2218f965)

Co-authored-by: Evgeni Chasnovski <evgeni.chasnovski@gmail.com>
This commit is contained in:
github-actions[bot]
2024-05-23 16:02:13 -05:00
committed by GitHub
parent bdd5871dc5
commit 21b21b94e6
3 changed files with 33 additions and 25 deletions

View File

@ -579,6 +579,10 @@ Acting on multiple lines behaves as follows:
transformed to empty comments (e.g. `/**/`). Comment markers are aligned to transformed to empty comments (e.g. `/**/`). Comment markers are aligned to
the least indented line. the least indented line.
Matching 'commentstring' does not account for whitespace in comment markers.
Removing comment markers is first attempted exactly, with fallback to using
markers trimmed from whitespace.
If the filetype of the buffer is associated with a language for which a If the filetype of the buffer is associated with a language for which a
|treesitter| parser is installed, then |vim.filetype.get_option()| is called |treesitter| parser is installed, then |vim.filetype.get_option()| is called
to look up the value of 'commentstring' corresponding to the cursor position. to look up the value of 'commentstring' corresponding to the cursor position.

View File

@ -77,14 +77,11 @@ local function make_comment_check(parts)
local l_esc, r_esc = vim.pesc(parts.left), vim.pesc(parts.right) local l_esc, r_esc = vim.pesc(parts.left), vim.pesc(parts.right)
-- Commented line has the following structure: -- Commented line has the following structure:
-- <possible whitespace> <left> <anything> <right> <possible whitespace> -- <whitespace> <trimmed left> <anything> <trimmed right> <whitespace>
local nonblank_regex = '^%s-' .. l_esc .. '.*' .. r_esc .. '%s-$' local regex = '^%s-' .. vim.trim(l_esc) .. '.*' .. vim.trim(r_esc) .. '%s-$'
-- Commented blank line can have any amount of whitespace around parts
local blank_regex = '^%s-' .. vim.trim(l_esc) .. '%s*' .. vim.trim(r_esc) .. '%s-$'
return function(line) return function(line)
return line:find(nonblank_regex) ~= nil or line:find(blank_regex) ~= nil return line:find(regex) ~= nil
end end
end end
@ -153,14 +150,14 @@ end
---@return fun(line: string): string ---@return fun(line: string): string
local function make_uncomment_function(parts) local function make_uncomment_function(parts)
local l_esc, r_esc = vim.pesc(parts.left), vim.pesc(parts.right) local l_esc, r_esc = vim.pesc(parts.left), vim.pesc(parts.right)
local nonblank_regex = '^(%s*)' .. l_esc .. '(.*)' .. r_esc .. '(%s-)$' local regex = '^(%s*)' .. l_esc .. '(.*)' .. r_esc .. '(%s-)$'
local blank_regex = '^(%s*)' .. vim.trim(l_esc) .. '(%s*)' .. vim.trim(r_esc) .. '(%s-)$' local regex_trimmed = '^(%s*)' .. vim.trim(l_esc) .. '(.*)' .. vim.trim(r_esc) .. '(%s-)$'
return function(line) return function(line)
-- Try both non-blank and blank regexes -- Try regex with exact comment parts first, fall back to trimmed parts
local indent, new_line, trail = line:match(nonblank_regex) local indent, new_line, trail = line:match(regex)
if new_line == nil then if new_line == nil then
indent, new_line, trail = line:match(blank_regex) indent, new_line, trail = line:match(regex_trimmed)
end end
-- Return original if line is not commented -- Return original if line is not commented

View File

@ -301,27 +301,34 @@ describe('commenting', function()
eq(get_lines(), { 'aa', '', ' ', '\t', 'aa' }) eq(get_lines(), { 'aa', '', ' ', '\t', 'aa' })
end) end)
it('matches comment parts strictly when detecting comment/uncomment', function() it('correctly matches comment parts during checking and uncommenting', function()
local validate = function(from, to, ref_lines) local validate = function(from, to, ref_lines)
set_lines({ '#aa', '# aa', '# aa' }) set_lines({ '/*aa*/', '/* aa */', '/* aa */' })
toggle_lines(from, to) toggle_lines(from, to)
eq(get_lines(), ref_lines) eq(get_lines(), ref_lines)
end end
set_commentstring('#%s') -- Should first try to match 'commentstring' parts exactly with their
validate(1, 3, { 'aa', ' aa', ' aa' }) -- whitespace, with fallback on trimmed parts
validate(2, 3, { '#aa', ' aa', ' aa' }) set_commentstring('/*%s*/')
validate(3, 3, { '#aa', '# aa', ' aa' }) validate(1, 3, { 'aa', ' aa ', ' aa ' })
validate(2, 3, { '/*aa*/', ' aa ', ' aa ' })
validate(3, 3, { '/*aa*/', '/* aa */', ' aa ' })
set_commentstring('# %s') set_commentstring('/* %s */')
validate(1, 3, { '# #aa', '# # aa', '# # aa' }) validate(1, 3, { 'aa', 'aa', ' aa ' })
validate(2, 3, { '#aa', 'aa', ' aa' }) validate(2, 3, { '/*aa*/', 'aa', ' aa ' })
validate(3, 3, { '#aa', '# aa', ' aa' }) validate(3, 3, { '/*aa*/', '/* aa */', ' aa ' })
set_commentstring('# %s') set_commentstring('/* %s */')
validate(1, 3, { '# #aa', '# # aa', '# # aa' }) validate(1, 3, { 'aa', ' aa ', 'aa' })
validate(2, 3, { '#aa', '# # aa', '# # aa' }) validate(2, 3, { '/*aa*/', ' aa ', 'aa' })
validate(3, 3, { '#aa', '# aa', 'aa' }) validate(3, 3, { '/*aa*/', '/* aa */', 'aa' })
set_commentstring(' /*%s*/ ')
validate(1, 3, { 'aa', ' aa ', ' aa ' })
validate(2, 3, { '/*aa*/', ' aa ', ' aa ' })
validate(3, 3, { '/*aa*/', '/* aa */', ' aa ' })
end) end)
it('uncomments on inconsistent indent levels', function() it('uncomments on inconsistent indent levels', function()