]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0640: the "%" command jumps to parens and braces inside comments v9.2.0640
authorHirohito Higashi <h.east.727@gmail.com>
Sat, 13 Jun 2026 19:39:54 +0000 (19:39 +0000)
committerChristian Brabandt <cb@256bit.org>
Sat, 13 Jun 2026 19:46:28 +0000 (19:46 +0000)
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) <noreply@anthropic.com>
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/doc/motion.txt
runtime/doc/version9.txt
src/normal.c
src/testdir/test_normal.vim
src/version.c

index 600ce647c57db0dad20ac52b53eb40728fb00611..a78ee204e5457bf8b008744a7426da9735d4787c 100644 (file)
@@ -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
index c7fb72cdd69089175b7fa7f3aaf5a487fc48e5fb..a7778b5fef7d414bec59bb1ba2a265c34b7f3aa8 100644 (file)
@@ -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
index b74c937c773bf7f6e5ab232517506dba2a25127b..b803b88f6b34336ecb19c0005f7a94bb1e31174a 100644 (file)
@@ -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
        {
index eea789123632a7e62889a659bc2fd67673490e53..b4298e3046a41f5255e95ca01234c45d3cb075f8 100644 (file)
@@ -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
index 1fdf8d1b01f3de82eceec61f16bcb4a69feb7e06..2e42bb357696ee0f466eee3df38585ec7781bf48 100644 (file)
@@ -759,6 +759,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    640,
 /**/
     639,
 /**/