vim-patch:9.2.0640: the "%" command jumps to parens and braces inside comments (#40229)

Problem:  The "%" command jumps to parens and braces inside comments,
          unlike the "=" operator (cindent), which ignores them.
Solution: When 'comments' defines C-style comments and "%" is not in
          'cpoptions', skip matching parens inside such comments, except
          when the cursor is inside a comment so a match there can still
          be found.

fixes:   vim/vim#20329
related: vim/vim#20111
closes:  vim/vim#20491

b8a109dcfb

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
zeertzjq
2026-06-14 10:52:50 +08:00
committed by GitHub
parent 710431c696
commit e997599894
3 changed files with 100 additions and 4 deletions

View File

@@ -1338,9 +1338,13 @@ remembered.
quotes). Note that this works fine for C, but not for
Perl, where single quotes are used for strings.
Nothing special is done for matches in comments. You
can either use the |matchit| plugin or put quotes around
matches.
When in addition 'comments' defines C-style "//" or
"/*" comments, parens and braces inside such comments
are skipped, like the |=| operator does. This does
not happen when the cursor is inside a comment, so a
match inside that comment can still be found.
Otherwise you can use the |matchit| plugin or put
quotes around matches.
No count is allowed, {count}% jumps to a line {count}
percentage down the file |N%|. Using '%' on

View File

@@ -4356,6 +4356,23 @@ static void nv_brackets(cmdarg_T *cap)
}
}
/// Return true when 'comments' defines a C-style line ("//") or block comment.
/// This is when "%" should skip matching parens in comments, like the "="
/// operator does.
static bool buf_has_cstyle_comments(void)
{
char part_buf[COM_MAX_LEN]; // buffer for one 'comments' part
for (char *list = curbuf->b_p_com; *list;) {
(void)copy_option_part(&list, part_buf, COM_MAX_LEN, ",");
char *string = vim_strchr(part_buf, ':'); // flags and comment leader
if (string != NULL && string[1] == '/' && (string[2] == '/' || string[2] == '*')) {
return true;
}
}
return false;
}
/// Handle Normal mode "%" command.
static void nv_percent(cmdarg_T *cap)
{
@@ -4384,10 +4401,21 @@ static void nv_percent(cmdarg_T *cap)
beginline(BL_SOL | BL_FIX);
}
} else { // "%" : go to matching paren
int flags = 0;
// Skip matching parens inside C-style comments, like the "=" operator
// does, but not when "%" is in 'cpoptions' (Vi-compatible) or the
// cursor sits in a line comment (so a match there can still be found).
if (vim_strchr(p_cpo, CPO_MATCH) == NULL && buf_has_cstyle_comments()) {
int comment_col = check_linecomment(get_cursor_line_ptr());
if (comment_col == MAXCOL || curwin->w_cursor.col < (colnr_T)comment_col) {
flags = FM_SKIPCOMM;
}
}
pos_T *pos;
cap->oap->motion_type = kMTCharWise;
cap->oap->use_reg_one = true;
if ((pos = findmatch(cap->oap, NUL)) == NULL) {
if ((pos = findmatchlimit(cap->oap, NUL, flags, 0)) == NULL) {
clearopbeep(cap->oap);
} else {
setpcmark();

View File

@@ -3908,6 +3908,70 @@ func Test_normal_percent_jump()
bwipe!
endfunc
" Test that "%" skips parens inside comments when 'comments' defines C-style
" "//" or "/*" comments.
func Test_normal_percent_skip_comment()
new
setlocal comments=s1:/*,mb:*,ex:*/,://
" Forward: skip a ")" inside a // comment, match the real one.
silent! %delete _
call setline(1, ['foo( // )', ');'])
call cursor(1, 4)
normal %
call assert_equal([2, 1], [line('.'), col('.')])
" Forward: skip a ")" inside a /* */ comment, match the real one.
silent! %delete _
call setline(1, ['bar( /* ) */ x)'])
call cursor(1, 4)
normal %
call assert_equal([1, 15], [line('.'), col('.')])
" Backward: skip a "(" inside a // comment, match the real one.
silent! %delete _
call setline(1, ['( // (', ')'])
call cursor(2, 1)
normal %
call assert_equal([1, 1], [line('.'), col('.')])
" Cursor inside a // comment: a match inside that comment is still found.
silent! %delete _
call setline(1, ['x // ( y )'])
call cursor(1, 6)
normal %
call assert_equal([1, 10], [line('.'), col('.')])
" Cursor inside a /* */ comment: a match inside that comment is still found.
silent! %delete _
call setline(1, ['/* a ( b ) c */'])
call cursor(1, 6)
normal %
call assert_equal([1, 10], [line('.'), col('.')])
" When 'comments' has no C-style comments the parens are not skipped.
setlocal comments=b:#
silent! %delete _
call setline(1, ['foo( // )', ');'])
call cursor(1, 4)
normal %
call assert_equal([1, 10], [line('.'), col('.')])
" With "%" in 'cpoptions' Vi-compatible matching is used and the parens
" inside comments are not skipped.
let save_cpo = &cpoptions
setlocal comments=s1:/*,mb:*,ex:*/,://
set cpoptions+=%
silent! %delete _
call setline(1, ['foo( // )', ');'])
call cursor(1, 4)
normal %
call assert_equal([1, 10], [line('.'), col('.')])
let &cpoptions = save_cpo
bwipe!
endfunc
" Test for << and >> commands to shift text by 'shiftwidth'
func Test_normal_shift_rightleft()
new