Commit: patch 9.2.0640: the "%" command jumps to parens and braces inside comments

0 views
Skip to first unread message

Christian Brabandt

unread,
Jun 13, 2026, 4:00:13 PM (11 hours ago) Jun 13
to vim...@googlegroups.com
patch 9.2.0640: the "%" command jumps to parens and braces inside comments

Commit: https://github.com/vim/vim/commit/b8a109dcfb96f213328a966a7e38eb28da697229
Author: Hirohito Higashi <h.eas...@gmail.com>
Date: Sat Jun 13 19:39:54 2026 +0000

patch 9.2.0640: the "%" command jumps to parens and braces inside comments

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: #20329
related: #20111
closes: #20491

Co-Authored-By: Claude Opus 4.8 (1M context) <nor...@anthropic.com>
Signed-off-by: Hirohito Higashi <h.eas...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt
index 600ce647c..a78ee204e 100644
--- a/runtime/doc/motion.txt
+++ b/runtime/doc/motion.txt
@@ -1,4 +1,4 @@
-*motion.txt* For Vim version 9.2. Last change: 2026 Feb 14
+*motion.txt* For Vim version 9.2. Last change: 2026 Jun 13


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -1294,9 +1294,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 |matchit-install| 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
+ |matchit-install| or put quotes around matches.

No count is allowed, {count}% jumps to a line {count}
percentage down the file |N%|. Using '%' on
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index c7fb72cdd..a7778b5fe 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -52649,6 +52649,8 @@ Other ~
- Channel can handle |Blob| messages |channel-open-options|.
- Added the "u" flag to 'shortmess' to silence undo/redo messages: |shm-u|
- |C-indenting| detects comments better.
+- The |%| command skips parens inside comments when 'comments' defines
+ C-style "//" or "/*" comments.
- The |package-hlyank| can now optionally highlight the last put region as
well.
- Support %0{} in 'statusline' to insert the expression result verbatim and
diff --git a/src/normal.c b/src/normal.c
index b74c937c7..b803b88f6 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -4620,6 +4620,30 @@ nv_brackets(cmdarg_T *cap)
clearopbeep(cap->oap);
}

+/*
+ * 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_u *list;
+ char_u part_buf[COM_MAX_LEN]; // buffer for one 'comments' part
+
+ for (list = curbuf->b_p_com; *list; )
+ {
+ char_u *string;
+
+ (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ",");
+ 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.
*/
@@ -4659,9 +4683,23 @@ nv_percent(cmdarg_T *cap)
}
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(ml_get_curline());
+
+ if (comment_col == MAXCOL
+ || curwin->w_cursor.col < (colnr_T)comment_col)
+ flags = FM_SKIPCOMM;
+ }
+
cap->oap->motion_type = MCHAR;
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
{
diff --git a/src/testdir/test_normal.vim b/src/testdir/test_normal.vim
index eea789123..b4298e304 100644
--- a/src/testdir/test_normal.vim
+++ b/src/testdir/test_normal.vim
@@ -3896,6 +3896,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
diff --git a/src/version.c b/src/version.c
index 1fdf8d1b0..2e42bb357 100644
--- a/src/version.c
+++ b/src/version.c
@@ -759,6 +759,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 640,
/**/
639,
/**/
Reply all
Reply to author
Forward
0 new messages