aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/cmdline.txt4
-rw-r--r--runtime/doc/editing.txt3
-rw-r--r--runtime/doc/index.txt4
-rw-r--r--runtime/doc/quickfix.txt94
-rw-r--r--runtime/doc/tabpage.txt3
-rw-r--r--runtime/doc/windows.txt6
-rw-r--r--src/nvim/ex_cmds.lua29
-rw-r--r--src/nvim/ex_cmds2.c44
-rw-r--r--src/nvim/ex_cmds_defs.h1
-rw-r--r--src/nvim/ex_docmd.c48
-rw-r--r--src/nvim/quickfix.c220
-rw-r--r--src/nvim/testdir/test_cdo.in107
-rw-r--r--src/nvim/testdir/test_cdo.ok66
-rw-r--r--src/nvim/version.c2
14 files changed, 601 insertions, 30 deletions
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt
index d85d41a295..ddf5b110cd 100644
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -482,6 +482,8 @@ followed by another Vim command:
:argdo
:autocmd
:bufdo
+ :cdo
+ :cfdo
:command
:cscope
:debug
@@ -492,6 +494,8 @@ followed by another Vim command:
:help
:helpfind
:lcscope
+ :ldo
+ :lfdo
:make
:normal
:promptfind
diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt
index b0d92ca678..4fdc963f9c 100644
--- a/runtime/doc/editing.txt
+++ b/runtime/doc/editing.txt
@@ -795,7 +795,8 @@ USING THE ARGUMENT LIST
autocommand event is disabled by adding it to
'eventignore'. This considerably speeds up editing
each file.
- Also see |:windo|, |:tabdo| and |:bufdo|.
+ Also see |:windo|, |:tabdo|, |:bufdo|, |:cdo|, |:ldo|,
+ |:cfdo| and |:lfdo|.
Example: >
:args *.c
diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index 75c986efcd..c98f43f324 100644
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -1128,6 +1128,8 @@ tag command action ~
|:cc| :cc go to specific error
|:cclose| :ccl[ose] close quickfix window
|:cd| :cd change directory
+|:cdo| :cdo execute command in each valid error list entry
+|:cfdo| :cfdo execute command in each file in error list
|:center| :ce[nter] format lines at the center
|:cexpr| :cex[pr] read errors from expr and jump to first
|:cfile| :cf[ile] read file with error messages and jump to first
@@ -1285,6 +1287,8 @@ tag command action ~
|:lchdir| :lch[dir] change directory locally
|:lclose| :lcl[ose] close location window
|:lcscope| :lcs[cope] like ":cscope" but uses location list
+|:ldo| :ld[o] execute command in valid location list entries
+|:lfdo| :lfd[o] execute command in each file in location list
|:left| :le[ft] left align lines
|:leftabove| :lefta[bove] make split window appear left or above
|:let| :let assign a value to a variable or option
diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index 8c428e44ef..399933b512 100644
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -291,6 +291,100 @@ use this code: >
au QuickfixCmdPost make call QfMakeConv()
+EXECUTE A COMMAND IN ALL THE BUFFERS IN QUICKFIX OR LOCATION LIST:
+ *:cdo*
+:cdo[!] {cmd} Execute {cmd} in each valid entry in the quickfix list.
+ It works like doing this: >
+ :cfirst
+ :{cmd}
+ :cnext
+ :{cmd}
+ etc.
+< When the current file can't be |abandon|ed and the [!]
+ is not present, the command fails.
+ When an error is detected on one buffer, further
+ buffers will not be visited.
+ The last buffer (or where an error occurred) becomes
+ the current buffer.
+ {cmd} can contain '|' to concatenate several commands.
+ Only valid entries in the quickfix list are used.
+ Note: While this command is executing, the Syntax
+ autocommand event is disabled by adding it to
+ 'eventignore'. This considerably speeds up editing
+ each buffer.
+ Also see |:bufdo|, |:tabdo|, |:argdo|, |:windo|,
+ |:ldo|, |:cfdo| and |:lfdo|.
+
+ *:cfdo*
+:cfdo[!] {cmd} Execute {cmd} in each file in the quickfix list.
+ It works like doing this: >
+ :cfirst
+ :{cmd}
+ :cnfile
+ :{cmd}
+ etc.
+< When the current file can't be |abandon|ed and the [!]
+ is not present, the command fails.
+ When an error is detected on one buffer, further
+ buffers will not be visited.
+ The last buffer (or where an error occurred) becomes
+ the current buffer.
+ {cmd} can contain '|' to concatenate several commands.
+ Only valid entries in the quickfix list are used.
+ Note: While this command is executing, the Syntax
+ autocommand event is disabled by adding it to
+ 'eventignore'. This considerably speeds up editing
+ each buffer.
+ Also see |:bufdo|, |:tabdo|, |:argdo|, |:windo|,
+ |:cdo|, |:ldo| and |:lfdo|.
+
+ *:ldo*
+:ld[o][!] {cmd} Execute {cmd} in each valid entry in the location list
+ for the current window.
+ It works like doing this: >
+ :lfirst
+ :{cmd}
+ :lnext
+ :{cmd}
+ etc.
+< When the current file can't be |abandon|ed and the [!]
+ is not present, the command fails.
+ When an error is detected on one buffer, further
+ buffers will not be visited.
+ The last buffer (or where an error occurred) becomes
+ the current buffer.
+ {cmd} can contain '|' to concatenate several commands.
+ Only valid entries in the location list are used.
+ Note: While this command is executing, the Syntax
+ autocommand event is disabled by adding it to
+ 'eventignore'. This considerably speeds up editing
+ each buffer.
+ Also see |:bufdo|, |:tabdo|, |:argdo|, |:windo|,
+ |:cdo|, |:cfdo| and |:lfdo|.
+
+ *:lfdo*
+:lfdo[!] {cmd} Execute {cmd} in each file in the location list for
+ the current window.
+ It works like doing this: >
+ :lfirst
+ :{cmd}
+ :lnfile
+ :{cmd}
+ etc.
+< When the current file can't be |abandon|ed and the [!]
+ is not present, the command fails.
+ When an error is detected on one buffer, further
+ buffers will not be visited.
+ The last buffer (or where an error occurred) becomes
+ the current buffer.
+ {cmd} can contain '|' to concatenate several commands.
+ Only valid entries in the location list are used.
+ Note: While this command is executing, the Syntax
+ autocommand event is disabled by adding it to
+ 'eventignore'. This considerably speeds up editing
+ each buffer.
+ Also see |:bufdo|, |:tabdo|, |:argdo|, |:windo|,
+ |:cdo|, |:ldo| and |:cfdo|.
=============================================================================
2. The error window *quickfix-window*
diff --git a/runtime/doc/tabpage.txt b/runtime/doc/tabpage.txt
index 44e770dc12..84eceee34f 100644
--- a/runtime/doc/tabpage.txt
+++ b/runtime/doc/tabpage.txt
@@ -234,7 +234,8 @@ LOOPING OVER TAB PAGES:
current tab page.
{cmd} can contain '|' to concatenate several commands.
{cmd} must not open or close tab pages or reorder them.
- Also see |:windo|, |:argdo| and |:bufdo|.
+ Also see |:windo|, |:argdo|, |:bufdo|, |:cdo|, |:ldo|, |:cfdo|
+ and |:lfdo|.
==============================================================================
3. Other items *tab-page-other*
diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt
index 1b902c908c..56f57c21c5 100644
--- a/runtime/doc/windows.txt
+++ b/runtime/doc/windows.txt
@@ -702,7 +702,8 @@ can also get to them with the buffer list commands, like ":bnext".
the current window.
{cmd} can contain '|' to concatenate several commands.
{cmd} must not open or close windows or reorder them.
- Also see |:tabdo|, |:argdo| and |:bufdo|.
+ Also see |:tabdo|, |:argdo|, |:bufdo|, |:cdo|, |:ldo|,
+ |:cfdo| and |:lfdo|.
*:bufdo*
:[range]bufdo[!] {cmd} Execute {cmd} in each buffer in the buffer list or if
@@ -728,7 +729,8 @@ can also get to them with the buffer list commands, like ":bnext".
autocommand event is disabled by adding it to
'eventignore'. This considerably speeds up editing
each buffer.
- Also see |:tabdo|, |:argdo| and |:windo|.
+ Also see |:tabdo|, |:argdo|, |:windo|, |:cdo|, |:ldo|,
+ |:cfdo| and |:lfdo|.
Examples: >
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index b7a3505c99..702abf3cf0 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -34,6 +34,7 @@ local ADDR_ARGUMENTS = 2
local ADDR_LOADED_BUFFERS = 3
local ADDR_BUFFERS = 4
local ADDR_TABS = 5
+local ADDR_QUICKFIX = 6
-- The following table is described in ex_cmds_defs.h file.
return {
@@ -374,6 +375,12 @@ return {
func='ex_cd',
},
{
+ command='cdo',
+ flags=bit.bor(BANG, NEEDARG, EXTRA, NOTRLCOM, RANGE, NOTADR, DFLALL),
+ addr_type=ADDR_QUICKFIX,
+ func='ex_listdo',
+ },
+ {
command='center',
flags=bit.bor(TRLBAR, RANGE, WHOLEFOLD, EXTRA, CMDWIN, MODIFY),
addr_type=ADDR_LINES,
@@ -391,6 +398,14 @@ return {
addr_type=ADDR_LINES,
func='ex_cfile',
},
+ -- Even though 'cfdo' is alphabetically lower than 'cfile', it is after
+ -- 'cfile' in this cmd list to support the existing ":cf" abbreviation.
+ {
+ command='cfdo',
+ flags=bit.bor(BANG, NEEDARG, EXTRA, NOTRLCOM, RANGE, NOTADR, DFLALL),
+ addr_type=ADDR_QUICKFIX,
+ func='ex_listdo',
+ },
{
command='cfirst',
flags=bit.bor(RANGE, NOTADR, COUNT, TRLBAR, BANG),
@@ -1286,6 +1301,12 @@ return {
func='do_cscope',
},
{
+ command='ldo',
+ flags=bit.bor(BANG, NEEDARG, EXTRA, NOTRLCOM, RANGE, NOTADR, DFLALL),
+ addr_type=ADDR_QUICKFIX,
+ func='ex_listdo',
+ },
+ {
command='left',
flags=bit.bor(TRLBAR, RANGE, WHOLEFOLD, EXTRA, CMDWIN, MODIFY),
addr_type=ADDR_LINES,
@@ -1315,6 +1336,14 @@ return {
addr_type=ADDR_LINES,
func='ex_cfile',
},
+ -- Even though 'lfdo' is alphabetically lower than 'lfile', it is after
+ -- 'lfile' in this cmd list to support the existing ":lf" abbreviation.
+ {
+ command='lfdo',
+ flags=bit.bor(BANG, NEEDARG, EXTRA, NOTRLCOM, RANGE, NOTADR, DFLALL),
+ addr_type=ADDR_QUICKFIX,
+ func='ex_listdo',
+ },
{
command='lfirst',
flags=bit.bor(RANGE, NOTADR, COUNT, TRLBAR, BANG),
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 87a6283310..57153cf5a1 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -1868,7 +1868,7 @@ void ex_argdelete(exarg_T *eap)
}
/*
- * ":argdo", ":windo", ":bufdo", ":tabdo"
+ * ":argdo", ":windo", ":bufdo", ":tabdo", ":cdo", ":ldo", ":cfdo" and ":lfdo"
*/
void ex_listdo(exarg_T *eap)
{
@@ -1879,7 +1879,6 @@ void ex_listdo(exarg_T *eap)
char_u *save_ei = NULL;
char_u *p_shm_save;
-
if (eap->cmdidx != CMD_windo && eap->cmdidx != CMD_tabdo)
/* Don't do syntax HL autocommands. Skipping the syntax file is a
* great speed improvement. */
@@ -1914,7 +1913,10 @@ void ex_listdo(exarg_T *eap)
default:
break;
}
+
buf_T *buf = curbuf;
+ size_t qf_size = 0;
+
/* set pcmark now */
if (eap->cmdidx == CMD_bufdo) {
/* Advance to the first listed buffer after "eap->line1". */
@@ -1929,6 +1931,22 @@ void ex_listdo(exarg_T *eap)
if (buf != NULL) {
goto_buffer(eap, DOBUF_FIRST, FORWARD, buf->b_fnum);
}
+ } else if (eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo ||
+ eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo) {
+ qf_size = qf_get_size(eap);
+ assert(eap->line1 >= 0);
+ if (qf_size == 0 || (size_t)eap->line1 > qf_size) {
+ buf = NULL;
+ } else {
+ ex_cc(eap);
+
+ buf = curbuf;
+ i = eap->line1 - 1;
+ if (eap->addr_count <= 0) {
+ // Default to all quickfix/location list entries.
+ eap->line2 = qf_size;
+ }
+ }
} else {
setpcmark();
}
@@ -2009,9 +2027,27 @@ void ex_listdo(exarg_T *eap)
set_option_value((char_u *)"shm", 0L, p_shm_save, 0);
xfree(p_shm_save);
- /* If autocommands took us elsewhere, quit here */
- if (curbuf->b_fnum != next_fnum)
+ // If autocommands took us elsewhere, quit here.
+ if (curbuf->b_fnum != next_fnum) {
+ break;
+ }
+ }
+
+ if (eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo ||
+ eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo) {
+ assert(i >= 0);
+ if ((size_t)i >= qf_size || i >= eap->line2) {
break;
+ }
+
+ size_t qf_idx = qf_get_cur_idx(eap);
+
+ ex_cnext(eap);
+
+ // If jumping to the next quickfix entry fails, quit here.
+ if (qf_get_cur_idx(eap) == qf_idx) {
+ break;
+ }
}
if (eap->cmdidx == CMD_windo) {
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index 10d2eb688e..f46d1e6d47 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -72,6 +72,7 @@
#define ADDR_LOADED_BUFFERS 3
#define ADDR_BUFFERS 4
#define ADDR_TABS 5
+#define ADDR_QUICKFIX 6
typedef struct exarg exarg_T;
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 59bda9345e..fad497928c 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -1531,9 +1531,12 @@ static char_u * do_one_cmd(char_u **cmdlinep,
lnum = CURRENT_TAB_NR;
ea.line2 = lnum;
break;
+ case ADDR_QUICKFIX:
+ ea.line2 = qf_get_cur_valid_idx(&ea);
+ break;
}
ea.cmd = skipwhite(ea.cmd);
- lnum = get_address(&ea.cmd, ea.addr_type, ea.skip, ea.addr_count == 0);
+ lnum = get_address(&ea, &ea.cmd, ea.addr_type, ea.skip, ea.addr_count == 0);
if (ea.cmd == NULL) /* error detected */
goto doend;
if (lnum == MAXLNUM) {
@@ -1582,6 +1585,13 @@ static char_u * do_one_cmd(char_u **cmdlinep,
ea.line2 = ARGCOUNT;
}
break;
+ case ADDR_QUICKFIX:
+ ea.line1 = 1;
+ ea.line2 = qf_get_size(&ea);
+ if (ea.line2 == 0) {
+ ea.line2 = 1;
+ }
+ break;
}
++ea.addr_count;
}
@@ -1962,6 +1972,12 @@ static char_u * do_one_cmd(char_u **cmdlinep,
ea.line2 = ARGCOUNT;
}
break;
+ case ADDR_QUICKFIX:
+ ea.line2 = qf_get_size(&ea);
+ if (ea.line2 == 0) {
+ ea.line2 = 1;
+ }
+ break;
}
}
@@ -2945,6 +2961,8 @@ set_one_cmd_context (
case CMD_botright:
case CMD_browse:
case CMD_bufdo:
+ case CMD_cdo:
+ case CMD_cfdo:
case CMD_confirm:
case CMD_debug:
case CMD_folddoclosed:
@@ -2954,7 +2972,9 @@ set_one_cmd_context (
case CMD_keepjumps:
case CMD_keepmarks:
case CMD_keeppatterns:
+ case CMD_ldo:
case CMD_leftabove:
+ case CMD_lfdo:
case CMD_lockmarks:
case CMD_noautocmd:
case CMD_noswapfile:
@@ -3367,7 +3387,8 @@ skip_range (
*
* Return MAXLNUM when no Ex address was found.
*/
-static linenr_T get_address(char_u **ptr,
+static linenr_T get_address(exarg_T *eap,
+ char_u **ptr,
int addr_type, // flag: one of ADDR_LINES, ...
int skip, // only skip the address, don't use it
int to_other_file // flag: may jump to other file
@@ -3405,6 +3426,9 @@ static linenr_T get_address(char_u **ptr,
case ADDR_TABS:
lnum = CURRENT_TAB_NR;
break;
+ case ADDR_QUICKFIX:
+ lnum = qf_get_cur_valid_idx(eap);
+ break;
}
break;
@@ -3436,6 +3460,12 @@ static linenr_T get_address(char_u **ptr,
case ADDR_TABS:
lnum = LAST_TAB_NR;
break;
+ case ADDR_QUICKFIX:
+ lnum = qf_get_size(eap);
+ if (lnum == 0) {
+ lnum = 1;
+ }
+ break;
}
break;
@@ -3578,6 +3608,9 @@ static linenr_T get_address(char_u **ptr,
case ADDR_TABS:
lnum = CURRENT_TAB_NR;
break;
+ case ADDR_QUICKFIX:
+ lnum = qf_get_cur_valid_idx(eap);
+ break;
}
}
@@ -3702,6 +3735,12 @@ static char_u *invalid_range(exarg_T *eap)
return (char_u *)_(e_invrange);
}
break;
+ case ADDR_QUICKFIX:
+ assert(eap->line2 >= 0);
+ if (eap->line2 != 1 && (size_t)eap->line2 > qf_get_size(eap)) {
+ return (char_u *)_(e_invrange);
+ }
+ break;
}
}
return NULL;
@@ -4589,6 +4628,7 @@ static struct {
{ADDR_TABS, "tabs"},
{ADDR_BUFFERS, "buffers"},
{ADDR_WINDOWS, "windows"},
+ {ADDR_QUICKFIX, "quickfix"},
{-1, NULL}
};
@@ -7013,9 +7053,7 @@ static void ex_put(exarg_T *eap)
*/
static void ex_copymove(exarg_T *eap)
{
- long n;
-
- n = get_address(&eap->arg, eap->addr_type, FALSE, FALSE);
+ long n = get_address(eap, &eap->arg, eap->addr_type, false, false);
if (eap->arg == NULL) { /* error detected */
eap->nextcmd = NULL;
return;
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 8e6ae46a3b..3abf43cb8c 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -1202,7 +1202,7 @@ static void qf_clean_dir_stack(struct dir_stack_T **stackptr)
/*
* Check in which directory of the directory stack the given file can be
* found.
- * Returns a pointer to the directory name or NULL if not found
+ * Returns a pointer to the directory name or NULL if not found.
* Cleans up intermediate directory entries.
*
* TODO: How to solve the following problem?
@@ -2571,9 +2571,159 @@ static char_u *get_mef_name(void)
return name;
}
+/// Returns the number of valid entries in the current quickfix/location list.
+size_t qf_get_size(exarg_T *eap)
+ FUNC_ATTR_NONNULL_ALL
+{
+ qf_info_T *qi = &ql_info;
+ if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) {
+ // Location list.
+ qi = GET_LOC_LIST(curwin);
+ if (qi == NULL) {
+ return 0;
+ }
+ }
+
+ int prev_fnum = 0;
+ size_t sz = 0;
+ qfline_T *qfp;
+ size_t i;
+ assert(qi->qf_lists[qi->qf_curlist].qf_count >= 0);
+ for (i = 0, qfp = qi->qf_lists[qi->qf_curlist].qf_start;
+ i < (size_t)qi->qf_lists[qi->qf_curlist].qf_count && qfp != NULL;
+ i++, qfp = qfp->qf_next) {
+ if (!qfp->qf_valid) {
+ continue;
+ }
+
+ if (eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo) {
+ // Count all valid entries.
+ sz++;
+ } else if (qfp->qf_fnum > 0 && qfp->qf_fnum != prev_fnum) {
+ // Count the number of files.
+ sz++;
+ prev_fnum = qfp->qf_fnum;
+ }
+ }
+
+ return sz;
+}
+
+/// Returns the current index of the quickfix/location list.
+/// Returns 0 if there is an error.
+size_t qf_get_cur_idx(exarg_T *eap)
+ FUNC_ATTR_NONNULL_ALL
+{
+ qf_info_T *qi = &ql_info;
+
+ if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) {
+ // Location list.
+ qi = GET_LOC_LIST(curwin);
+ if (qi == NULL) {
+ return 0;
+ }
+ }
+
+ assert(qi->qf_lists[qi->qf_curlist].qf_index >= 0);
+ return (size_t)qi->qf_lists[qi->qf_curlist].qf_index;
+}
+
+/// Returns the current index in the quickfix/location list,
+/// counting only valid entries.
+/// Returns 1 if there are no valid entries.
+int qf_get_cur_valid_idx(exarg_T *eap)
+ FUNC_ATTR_NONNULL_ALL
+{
+ qf_info_T *qi = &ql_info;
+
+ if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) {
+ // Location list.
+ qi = GET_LOC_LIST(curwin);
+ if (qi == NULL) {
+ return 1;
+ }
+ }
+
+ qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist];
+
+ // Check if the list has valid errors.
+ if (qfl->qf_count <= 0 || qfl->qf_nonevalid) {
+ return 1;
+ }
+
+ int prev_fnum = 0;
+ int eidx = 0;
+ qfline_T *qfp;
+ size_t i;
+ assert(qfl->qf_index >= 0);
+ for (i = 1, qfp = qfl->qf_start;
+ i <= (size_t)qfl->qf_index && qfp != NULL;
+ i++, qfp = qfp->qf_next) {
+ if (!qfp->qf_valid) {
+ continue;
+ }
+
+ if (eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo) {
+ if (qfp->qf_fnum > 0 && qfp->qf_fnum != prev_fnum) {
+ // Count the number of files.
+ eidx++;
+ prev_fnum = qfp->qf_fnum;
+ }
+ } else {
+ eidx++;
+ }
+ }
+
+ return eidx != 0 ? eidx : 1;
+}
+
+/// Get the 'n'th valid error entry in the quickfix or location list.
+///
+/// Used by :cdo, :ldo, :cfdo and :lfdo commands.
+/// For :cdo and :ldo, returns the 'n'th valid error entry.
+/// For :cfdo and :lfdo, returns the 'n'th valid file entry.
+static size_t qf_get_nth_valid_entry(qf_info_T *qi, size_t n, bool fdo)
+ FUNC_ATTR_NONNULL_ALL
+{
+ qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist];
+
+ // Check if the list has valid errors.
+ if (qfl->qf_count <= 0 || qfl->qf_nonevalid) {
+ return 1;
+ }
+
+ int prev_fnum = 0;
+ size_t eidx = 0;
+ size_t i;
+ qfline_T *qfp;
+ assert(qfl->qf_count >= 0);
+ for (i = 1, qfp = qfl->qf_start;
+ i <= (size_t)qfl->qf_count && qfp != NULL;
+ i++, qfp = qfp->qf_next) {
+ if (qfp->qf_valid) {
+ if (fdo) {
+ if (qfp->qf_fnum > 0 && qfp->qf_fnum != prev_fnum) {
+ // Count the number of files.
+ eidx++;
+ prev_fnum = qfp->qf_fnum;
+ }
+ } else {
+ eidx++;
+ }
+ }
+
+ if (eidx == n) {
+ break;
+ }
+ }
+
+ return i <= (size_t)qfl->qf_count ? i : 1;
+}
+
/*
* ":cc", ":crewind", ":cfirst" and ":clast".
* ":ll", ":lrewind", ":lfirst" and ":llast".
+ * ":cdo", ":ldo", ":cfdo" and ":lfdo".
*/
void ex_cc(exarg_T *eap)
{
@@ -2582,7 +2732,10 @@ void ex_cc(exarg_T *eap)
if (eap->cmdidx == CMD_ll
|| eap->cmdidx == CMD_lrewind
|| eap->cmdidx == CMD_lfirst
- || eap->cmdidx == CMD_llast) {
+ || eap->cmdidx == CMD_llast
+ || eap->cmdidx == CMD_llast
+ || eap->cmdidx == CMD_ldo
+ || eap->cmdidx == CMD_lfdo) {
qi = GET_LOC_LIST(curwin);
if (qi == NULL) {
EMSG(_(e_loclist));
@@ -2590,21 +2743,42 @@ void ex_cc(exarg_T *eap)
}
}
- qf_jump(qi, 0,
- eap->addr_count > 0
- ? (int)eap->line2
- : (eap->cmdidx == CMD_cc || eap->cmdidx == CMD_ll)
- ? 0
- : (eap->cmdidx == CMD_crewind || eap->cmdidx == CMD_lrewind
- || eap->cmdidx == CMD_cfirst || eap->cmdidx == CMD_lfirst)
- ? 1
- : 32767,
- eap->forceit);
+ int errornr;
+ if (eap->addr_count > 0) {
+ errornr = (int)eap->line2;
+ } else if (eap->cmdidx == CMD_cc || eap->cmdidx == CMD_ll) {
+ errornr = 0;
+ } else if (eap->cmdidx == CMD_crewind || eap->cmdidx == CMD_lrewind
+ || eap->cmdidx == CMD_cfirst || eap->cmdidx == CMD_lfirst) {
+ errornr = 1;
+ } else {
+ errornr = 32767;
+ }
+
+ // For cdo and ldo commands, jump to the nth valid error.
+ // For cfdo and lfdo commands, jump to the nth valid file entry.
+ if (eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo ||
+ eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo) {
+ size_t n;
+ if (eap->addr_count > 0) {
+ assert(eap->line1 >= 0);
+ n = (size_t)eap->line1;
+ } else {
+ n = 1;
+ }
+ size_t valid_entry = qf_get_nth_valid_entry(qi, n,
+ eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo);
+ assert(valid_entry <= INT_MAX);
+ errornr = (int)valid_entry;
+ }
+
+ qf_jump(qi, 0, errornr, eap->forceit);
}
/*
* ":cnext", ":cnfile", ":cNext" and ":cprevious".
* ":lnext", ":lNext", ":lprevious", ":lnfile", ":lNfile" and ":lpfile".
+ * ":cdo", ":ldo", ":cfdo" and ":lfdo".
*/
void ex_cnext(exarg_T *eap)
{
@@ -2615,7 +2789,10 @@ void ex_cnext(exarg_T *eap)
|| eap->cmdidx == CMD_lprevious
|| eap->cmdidx == CMD_lnfile
|| eap->cmdidx == CMD_lNfile
- || eap->cmdidx == CMD_lpfile) {
+ || eap->cmdidx == CMD_lpfile
+ || eap->cmdidx == CMD_lpfile
+ || eap->cmdidx == CMD_ldo
+ || eap->cmdidx == CMD_lfdo) {
qi = GET_LOC_LIST(curwin);
if (qi == NULL) {
EMSG(_(e_loclist));
@@ -2623,15 +2800,26 @@ void ex_cnext(exarg_T *eap)
}
}
- qf_jump(qi, (eap->cmdidx == CMD_cnext || eap->cmdidx == CMD_lnext)
+ int errornr;
+ if (eap->addr_count > 0 &&
+ (eap->cmdidx != CMD_cdo && eap->cmdidx != CMD_ldo &&
+ eap->cmdidx != CMD_cfdo && eap->cmdidx != CMD_lfdo)) {
+ errornr = (int)eap->line2;
+ } else {
+ errornr = 1;
+ }
+
+ qf_jump(qi, (eap->cmdidx == CMD_cnext || eap->cmdidx == CMD_lnext
+ || eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo)
? FORWARD
- : (eap->cmdidx == CMD_cnfile || eap->cmdidx == CMD_lnfile)
+ : (eap->cmdidx == CMD_cnfile || eap->cmdidx == CMD_lnfile
+ || eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo)
? FORWARD_FILE
: (eap->cmdidx == CMD_cpfile || eap->cmdidx == CMD_lpfile
|| eap->cmdidx == CMD_cNfile || eap->cmdidx == CMD_lNfile)
? BACKWARD_FILE
: BACKWARD,
- eap->addr_count > 0 ? (int)eap->line2 : 1, eap->forceit);
+ errornr, eap->forceit);
}
/*
diff --git a/src/nvim/testdir/test_cdo.in b/src/nvim/testdir/test_cdo.in
new file mode 100644
index 0000000000..fb80ea1164
--- /dev/null
+++ b/src/nvim/testdir/test_cdo.in
@@ -0,0 +1,107 @@
+Tests for the :cdo, :cfdo, :ldo and :lfdo commands
+
+STARTTEST
+:so small.vim
+:if !has('quickfix') | e! test.ok | wq! test.out | endif
+
+:call writefile(["Line1", "Line2", "Line3"], 'Xtestfile1')
+:call writefile(["Line1", "Line2", "Line3"], 'Xtestfile2')
+:call writefile(["Line1", "Line2", "Line3"], 'Xtestfile3')
+
+:function RunTests(cchar)
+: let nl="\n"
+
+: enew
+: " Try with an empty list
+: exe a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+
+: " Populate the list and then try
+: exe a:cchar . "getexpr ['non-error 1', 'Xtestfile1:1:3:Line1', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:3:1:Line3']"
+: exe a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+
+: " Run command only on selected error lines
+: enew
+: exe "2,3" . a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: " Boundary condition tests
+: enew
+: exe "1,1" . a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: enew
+: exe "3" . a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: " Range test commands
+: enew
+: exe "%" . a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: enew
+: exe "1,$" . a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: enew
+: exe a:cchar . 'prev'
+: exe "." . a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: " Invalid error lines test
+: enew
+: exe "27" . a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: exe "4,5" . a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+
+: " Run commands from an unsaved buffer
+: let v:errmsg=''
+: enew
+: setlocal modified
+: exe "2,2" . a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: if v:errmsg =~# 'No write since last change'
+: let g:result .= 'Unsaved file change test passed' . nl
+: else
+: let g:result .= 'Unsaved file change test failed' . nl
+: endif
+
+: " If the executed command fails, then the operation should be aborted
+: enew!
+: let subst_count = 0
+: exe a:cchar . "do s/Line/xLine/ | let subst_count += 1"
+: if subst_count == 1 && getline('.') == 'xLine1'
+: let g:result .= 'Abort command on error test passed' . nl
+: else
+: let g:result .= 'Abort command on error test failed' . nl
+: endif
+
+: exe "2,2" . a:cchar . "do! let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+
+: " List with no valid error entries
+: edit! +2 Xtestfile1
+: exe a:cchar . "getexpr ['non-error 1', 'non-error 2', 'non-error 3']"
+: exe a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: exe "2" . a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: let v:errmsg=''
+: exe "%" . a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: exe "1,$" . a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: exe "." . a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: let g:result .= v:errmsg
+
+: " List with only one valid entry
+: exe a:cchar . "getexpr ['Xtestfile3:3:1:Line3']"
+: exe a:cchar . "do let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+
+: " Tests for :cfdo and :lfdo commands
+: exe a:cchar . "getexpr ['non-error 1', 'Xtestfile1:1:3:Line1', 'Xtestfile1:2:1:Line2', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:2:3:Line2', 'Xtestfile3:3:1:Line3']"
+: exe a:cchar . "fdo let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: exe "3" . a:cchar . "fdo let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: exe "2,3" . a:cchar . "fdo let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: exe "%" . a:cchar . "fdo let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: exe "1,$" . a:cchar . "fdo let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+: exe a:cchar . 'pfile'
+: exe "." . a:cchar . "fdo let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+
+: " List with only one valid entry
+: exe a:cchar . "getexpr ['Xtestfile2:2:5:Line2']"
+: exe a:cchar . "fdo let g:result .= expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' . nl"
+:endfunction
+
+:let result=''
+:" Tests for the :cdo quickfix list command
+:call RunTests('c')
+:let result .= "\n"
+:" Tests for the :ldo location list command
+:call RunTests('l')
+
+:edit! test.out
+:0put =result
+:wq!
+ENDTEST
+
diff --git a/src/nvim/testdir/test_cdo.ok b/src/nvim/testdir/test_cdo.ok
new file mode 100644
index 0000000000..ddcff4bbb8
--- /dev/null
+++ b/src/nvim/testdir/test_cdo.ok
@@ -0,0 +1,66 @@
+Xtestfile1 1L 3C
+Xtestfile2 2L 2C
+Xtestfile3 3L 1C
+Xtestfile2 2L 2C
+Xtestfile3 3L 1C
+Xtestfile1 1L 3C
+Xtestfile3 3L 1C
+Xtestfile1 1L 3C
+Xtestfile2 2L 2C
+Xtestfile3 3L 1C
+Xtestfile1 1L 3C
+Xtestfile2 2L 2C
+Xtestfile3 3L 1C
+Xtestfile2 2L 2C
+Unsaved file change test passed
+Abort command on error test passed
+Xtestfile2 2L 2C
+Xtestfile3 3L 1C
+Xtestfile1 1L 3C
+Xtestfile2 2L 2C
+Xtestfile3 2L 3C
+Xtestfile3 2L 3C
+Xtestfile2 2L 2C
+Xtestfile3 2L 3C
+Xtestfile1 1L 3C
+Xtestfile2 2L 2C
+Xtestfile3 2L 3C
+Xtestfile1 1L 3C
+Xtestfile2 2L 2C
+Xtestfile3 2L 3C
+Xtestfile2 2L 2C
+Xtestfile2 2L 5C
+
+Xtestfile1 1L 3C
+Xtestfile2 2L 2C
+Xtestfile3 3L 1C
+Xtestfile2 2L 2C
+Xtestfile3 3L 1C
+Xtestfile1 1L 3C
+Xtestfile3 3L 1C
+Xtestfile1 1L 3C
+Xtestfile2 2L 2C
+Xtestfile3 3L 1C
+Xtestfile1 1L 3C
+Xtestfile2 2L 2C
+Xtestfile3 3L 1C
+Xtestfile2 2L 2C
+Unsaved file change test passed
+Abort command on error test passed
+Xtestfile2 2L 2C
+Xtestfile3 3L 1C
+Xtestfile1 1L 3C
+Xtestfile2 2L 2C
+Xtestfile3 2L 3C
+Xtestfile3 2L 3C
+Xtestfile2 2L 2C
+Xtestfile3 2L 3C
+Xtestfile1 1L 3C
+Xtestfile2 2L 2C
+Xtestfile3 2L 3C
+Xtestfile1 1L 3C
+Xtestfile2 2L 2C
+Xtestfile3 2L 3C
+Xtestfile2 2L 2C
+Xtestfile2 2L 5C
+
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 9a9cb509a8..018bfef9d8 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -135,7 +135,7 @@ static int included_patches[] = {
// 861 NA
// 860,
// 859,
- // 858,
+ 858,
// 857,
// 856,
// 855 NA