# HG changeset patch # User Christian Brabandt # Date 1497097803 -7200 # Node ID 8e5ec22db3d8f6d07db360f3c6bbfd595a2cde0c # Parent bc5afe585f0dabc77b373725ed834f44ea623b39 patch 8.0.0630: it is not easy to work on lines without a match commit https://github.com/vim/vim/commit/f84b122a99da75741ae686fabb6f81b8b4755998 Author: Bram Moolenaar Date: Sat Jun 10 14:29:52 2017 +0200 patch 8.0.0630: it is not easy to work on lines without a match Problem: The :global command does not work recursively, which makes it difficult to execute a command on a line where one pattern matches and another does not match. (Miles Cranmer) Solution: Allow for recursion if it is for only one line. (closes #1760) diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -1,4 +1,4 @@ -*repeat.txt* For Vim version 8.0. Last change: 2017 Feb 06 +*repeat.txt* For Vim version 8.0. Last change: 2017 Jun 10 VIM REFERENCE MANUAL by Bram Moolenaar @@ -46,7 +46,7 @@ of area is used, see |visual-repeat|. ============================================================================== 2. Multiple repeats *multi-repeat* - *:g* *:global* *E147* *E148* + *:g* *:global* *E148* :[range]g[lobal]/{pattern}/[cmd] Execute the Ex command [cmd] (default ":p") on the lines within [range] where {pattern} matches. @@ -79,8 +79,15 @@ The default for [range] is the whole buf the command. If an error message is given for a line, the command for that line is aborted and the global command continues with the next marked or unmarked line. + *E147* +When the command is used recursively, it only works on one line. Giving a +range is then not allowed. This is useful to find all lines that match a +pattern and do not match another pattern: > + :g/found/v/notfound/{cmd} +This first finds all lines containing "found", but only executes {cmd} when +there is no match for "notfound". -To repeat a non-Ex command, you can use the ":normal" command: > +To execute a non-Ex command, you can use the `:normal` command: > :g/pat/normal {commands} Make sure that {commands} ends with a whole command, otherwise Vim will wait for you to type the rest of the command for each match. The screen will not diff --git a/src/ex_cmds.c b/src/ex_cmds.c --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -5903,6 +5903,17 @@ do_sub_msg( return FALSE; } + static void +global_exe_one(char_u *cmd, linenr_T lnum) +{ + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + if (*cmd == NUL || *cmd == '\n') + do_cmdline((char_u *)"p", NULL, NULL, DOCMD_NOWAIT); + else + do_cmdline(cmd, NULL, NULL, DOCMD_NOWAIT); +} + /* * Execute a global command of the form: * @@ -5933,9 +5944,13 @@ ex_global(exarg_T *eap) int match; int which_pat; - if (global_busy) - { - EMSG(_("E147: Cannot do :global recursive")); /* will increment global_busy */ + /* When nesting the command works on one line. This allows for + * ":g/found/v/notfound/command". */ + if (global_busy && (eap->line1 != 1 + || eap->line2 != curbuf->b_ml.ml_line_count)) + { + /* will increment global_busy to break out of the loop */ + EMSG(_("E147: Cannot do :global recursive with a range")); return; } @@ -5993,46 +6008,58 @@ ex_global(exarg_T *eap) return; } - /* - * pass 1: set marks for each (not) matching line - */ - for (lnum = eap->line1; lnum <= eap->line2 && !got_int; ++lnum) - { - /* a match on this line? */ + if (global_busy) + { + lnum = curwin->w_cursor.lnum; match = vim_regexec_multi(®match, curwin, curbuf, lnum, - (colnr_T)0, NULL); + (colnr_T)0, NULL); if ((type == 'g' && match) || (type == 'v' && !match)) - { - ml_setmarked(lnum); - ndone++; - } - line_breakcheck(); - } - - /* - * pass 2: execute the command for each line that has been marked - */ - if (got_int) - MSG(_(e_interr)); - else if (ndone == 0) - { - if (type == 'v') - smsg((char_u *)_("Pattern found in every line: %s"), pat); - else - smsg((char_u *)_("Pattern not found: %s"), pat); + global_exe_one(cmd, lnum); } else { -#ifdef FEAT_CLIPBOARD - start_global_changes(); -#endif - global_exe(cmd); + /* + * pass 1: set marks for each (not) matching line + */ + for (lnum = eap->line1; lnum <= eap->line2 && !got_int; ++lnum) + { + /* a match on this line? */ + match = vim_regexec_multi(®match, curwin, curbuf, lnum, + (colnr_T)0, NULL); + if ((type == 'g' && match) || (type == 'v' && !match)) + { + ml_setmarked(lnum); + ndone++; + } + line_breakcheck(); + } + + /* + * pass 2: execute the command for each line that has been marked + */ + if (got_int) + MSG(_(e_interr)); + else if (ndone == 0) + { + if (type == 'v') + smsg((char_u *)_("Pattern found in every line: %s"), pat); + else + smsg((char_u *)_("Pattern not found: %s"), pat); + } + else + { #ifdef FEAT_CLIPBOARD - end_global_changes(); -#endif - } - - ml_clearmarked(); /* clear rest of the marks */ + start_global_changes(); +#endif + global_exe(cmd); +#ifdef FEAT_CLIPBOARD + end_global_changes(); +#endif + } + + ml_clearmarked(); /* clear rest of the marks */ + } + vim_regfree(regmatch.regprog); } @@ -6063,12 +6090,7 @@ global_exe(char_u *cmd) old_lcount = curbuf->b_ml.ml_line_count; while (!got_int && (lnum = ml_firstmarked()) != 0 && global_busy == 1) { - curwin->w_cursor.lnum = lnum; - curwin->w_cursor.col = 0; - if (*cmd == NUL || *cmd == '\n') - do_cmdline((char_u *)"p", NULL, NULL, DOCMD_NOWAIT); - else - do_cmdline(cmd, NULL, NULL, DOCMD_NOWAIT); + global_exe_one(cmd, lnum); ui_breakcheck(); } @@ -8514,4 +8536,3 @@ ex_oldfiles(exarg_T *eap UNUSED) } } #endif - diff --git a/src/testdir/test_global.vim b/src/testdir/test_global.vim --- a/src/testdir/test_global.vim +++ b/src/testdir/test_global.vim @@ -9,3 +9,12 @@ func Test_yank_put_clipboard() set clipboard& bwipe! endfunc + +func Test_nested_global() + new + call setline(1, ['nothing', 'found', 'found bad', 'bad']) + call assert_fails('g/found/3v/bad/s/^/++/', 'E147') + g/found/v/bad/s/^/++/ + call assert_equal(['nothing', '++found', 'found bad', 'bad'], getline(1, 4)) + bwipe! +endfunc diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -765,6 +765,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 630, +/**/ 629, /**/ 628,