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

Commit: 
https://github.com/vim/vim/commit/b8a109dcfb96f213328a966a7e38eb28da697229
Author: Hirohito Higashi <[email protected]>
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) <[email protected]>
    Signed-off-by: Hirohito Higashi <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

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,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1wYUWS-00DW2v-8d%40256bit.org.

Raspunde prin e-mail lui