changeset 7092:64e30831fa42 v7.4.858

commit https://github.com/vim/vim/commit/aa23b379421aa214e6543b06c974594a25799b09 Author: Bram Moolenaar <Bram@vim.org> Date: Tue Sep 8 18:46:31 2015 +0200 patch 7.4.858 Problem: It's a bit clumsy to execute a command on a list of matches. Solution: Add the ":ldo", ":lfdo", ":cdo" and ":cfdo" commands. (Yegappan Lakshmanan)
author Christian Brabandt <cb@256bit.org>
date Tue, 08 Sep 2015 19:00:05 +0200
parents 0f1600f46238
children 503f4e8f86c4
files runtime/doc/cmdline.txt runtime/doc/editing.txt runtime/doc/index.txt runtime/doc/tabpage.txt runtime/doc/windows.txt src/ex_cmds.h src/ex_cmds2.c src/ex_docmd.c src/proto/quickfix.pro src/quickfix.c src/testdir/Make_amiga.mak src/testdir/Make_dos.mak src/testdir/Make_ming.mak src/testdir/Make_os2.mak src/testdir/Make_vms.mms src/testdir/Makefile src/testdir/test_cdo.in src/testdir/test_cdo.ok src/version.c
diffstat 19 files changed, 506 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -511,6 +511,8 @@ followed by another Vim command:
     :argdo
     :autocmd
     :bufdo
+    :cdo
+    :cfdo
     :command
     :cscope
     :debug
@@ -521,6 +523,8 @@ followed by another Vim command:
     :help
     :helpfind
     :lcscope
+    :ldo
+    :lfdo
     :make
     :normal
     :perl
--- a/runtime/doc/editing.txt
+++ b/runtime/doc/editing.txt
@@ -868,7 +868,8 @@ USING THE ARGUMENT LIST
 			each file.
 			{not in Vi} {not available when compiled without the
 			|+listcmds| feature}
-			Also see |:windo|, |:tabdo| and |:bufdo|.
+			Also see |:windo|, |:tabdo|, |:bufdo|, |:cdo|, |:ldo|,
+			|:cfdo| and |:lfdo|
 
 Example: >
 	:args *.c
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -1138,6 +1138,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|		:cfd[o]		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
@@ -1296,6 +1298,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
--- a/runtime/doc/tabpage.txt
+++ b/runtime/doc/tabpage.txt
@@ -248,7 +248,8 @@ LOOPING OVER TAB PAGES:
 		{cmd} must not open or close tab pages or reorder them.
 		{not in Vi} {not available when compiled without the
 		|+listcmds| feature}
-		Also see |:windo|, |:argdo| and |:bufdo|.
+		Also see |:windo|, |:argdo|, |:bufdo|, |:cdo|, |:ldo|, |:cfdo|
+		and |:lfdo|
 
 ==============================================================================
 3. Other items						*tab-page-other*
--- a/runtime/doc/windows.txt
+++ b/runtime/doc/windows.txt
@@ -715,7 +715,8 @@ 8. Do a command in all buffers or window
 			{cmd} must not open or close windows or reorder them.
 			{not in Vi} {not available when compiled without the
 			|+listcmds| feature}
-			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
@@ -743,7 +744,8 @@ 8. Do a command in all buffers or window
 			each buffer.
 			{not in Vi} {not available when compiled without the
 			|+listcmds| feature}
-			Also see |:tabdo|, |:argdo| and |:windo|.
+			Also see |:tabdo|, |:argdo|, |:windo|, |:cdo|, |:ldo|,
+			|:cfdo| and |:lfdo|
 
 Examples: >
 
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -65,6 +65,7 @@
 #define ADDR_LOADED_BUFFERS	3
 #define ADDR_BUFFERS		4
 #define ADDR_TABS		5
+#define ADDR_QUICKFIX		6
 
 #ifndef DO_DECLARE_EXCMD
 typedef struct exarg exarg_T;
@@ -270,6 +271,9 @@ EX(CMD_cclose,		"cclose",	ex_cclose,
 EX(CMD_cd,		"cd",		ex_cd,
 			BANG|FILE1|TRLBAR|CMDWIN,
 			ADDR_LINES),
+EX(CMD_cdo,		"cdo",		ex_listdo,
+			BANG|NEEDARG|EXTRA|NOTRLCOM|RANGE|NOTADR|DFLALL,
+			ADDR_QUICKFIX),
 EX(CMD_center,		"center",	ex_align,
 			TRLBAR|RANGE|WHOLEFOLD|EXTRA|CMDWIN|MODIFY,
 			ADDR_LINES),
@@ -279,6 +283,9 @@ EX(CMD_cexpr,		"cexpr",	ex_cexpr,
 EX(CMD_cfile,		"cfile",	ex_cfile,
 			TRLBAR|FILE1|BANG,
 			ADDR_LINES),
+EX(CMD_cfdo,		"cfdo",		ex_listdo,
+			BANG|NEEDARG|EXTRA|NOTRLCOM|RANGE|NOTADR|DFLALL,
+			ADDR_QUICKFIX),
 EX(CMD_cfirst,		"cfirst",	ex_cc,
 			RANGE|NOTADR|COUNT|TRLBAR|BANG,
 			ADDR_LINES),
@@ -729,6 +736,9 @@ EX(CMD_lclose,		"lclose",	ex_cclose,
 EX(CMD_lcscope,		"lcscope",	do_cscope,
 			EXTRA|NOTRLCOM|XFILE,
 			ADDR_LINES),
+EX(CMD_ldo,		"ldo",		ex_listdo,
+			BANG|NEEDARG|EXTRA|NOTRLCOM|RANGE|NOTADR|DFLALL,
+			ADDR_QUICKFIX),
 EX(CMD_left,		"left",		ex_align,
 			TRLBAR|RANGE|WHOLEFOLD|EXTRA|CMDWIN|MODIFY,
 			ADDR_LINES),
@@ -744,6 +754,9 @@ EX(CMD_lexpr,		"lexpr",	ex_cexpr,
 EX(CMD_lfile,		"lfile",	ex_cfile,
 			TRLBAR|FILE1|BANG,
 			ADDR_LINES),
+EX(CMD_lfdo,		"lfdo",		ex_listdo,
+			BANG|NEEDARG|EXTRA|NOTRLCOM|RANGE|NOTADR|DFLALL,
+			ADDR_QUICKFIX),
 EX(CMD_lfirst,		"lfirst",	ex_cc,
 			RANGE|NOTADR|COUNT|TRLBAR|BANG,
 			ADDR_LINES),
--- a/src/ex_cmds2.c
+++ b/src/ex_cmds2.c
@@ -2429,7 +2429,7 @@ ex_argdelete(eap)
 }
 
 /*
- * ":argdo", ":windo", ":bufdo", ":tabdo"
+ * ":argdo", ":windo", ":bufdo", ":tabdo", ":cdo", ":ldo", ":cfdo" and ":lfdo"
  */
     void
 ex_listdo(eap)
@@ -2446,6 +2446,10 @@ ex_listdo(eap)
     char_u	*save_ei = NULL;
 #endif
     char_u	*p_shm_save;
+#ifdef FEAT_QUICKFIX
+    int		qf_size;
+    int		qf_idx;
+#endif
 
 #ifndef FEAT_WINDOWS
     if (eap->cmdidx == CMD_windo)
@@ -2498,18 +2502,37 @@ ex_listdo(eap)
 	}
 	/* set pcmark now */
 	if (eap->cmdidx == CMD_bufdo)
-        {
+	{
 	    /* Advance to the first listed buffer after "eap->line1". */
-            for (buf = firstbuf; buf != NULL && (buf->b_fnum < eap->line1
+	    for (buf = firstbuf; buf != NULL && (buf->b_fnum < eap->line1
 					  || !buf->b_p_bl); buf = buf->b_next)
 		if (buf->b_fnum > eap->line2)
 		{
 		    buf = NULL;
 		    break;
 		}
-            if (buf != NULL)
+	    if (buf != NULL)
 		goto_buffer(eap, DOBUF_FIRST, FORWARD, buf->b_fnum);
-        }
+	}
+#ifdef FEAT_QUICKFIX
+	else if (eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo
+		|| eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo)
+	{
+	    qf_size = qf_get_size(eap);
+	    if (qf_size <= 0 || eap->line1 > qf_size)
+		buf = NULL;
+	    else
+	    {
+		ex_cc(eap);
+
+		buf = curbuf;
+		i = eap->line1 - 1;
+		if (eap->addr_count <= 0)
+		    /* default is all the quickfix/location list entries */
+		    eap->line2 = qf_size;
+	    }
+	}
+#endif
 	else
 	    setpcmark();
 	listcmd_busy = TRUE;	    /* avoids setting pcmark below */
@@ -2595,11 +2618,28 @@ ex_listdo(eap)
 		set_option_value((char_u *)"shm", 0L, p_shm_save, 0);
 		vim_free(p_shm_save);
 
-		/* If autocommands took us elsewhere, quit here */
+		/* If autocommands took us elsewhere, quit here. */
 		if (curbuf->b_fnum != next_fnum)
 		    break;
 	    }
 
+#ifdef FEAT_QUICKFIX
+	    if (eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo
+		    || eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo)
+	    {
+		if (i >= qf_size || i >= eap->line2)
+		    break;
+
+		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;
+	    }
+#endif
+
 	    if (eap->cmdidx == CMD_windo)
 	    {
 		validate_cursor();	/* cursor may have moved */
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -135,7 +135,7 @@ static int	getargopt __ARGS((exarg_T *ea
 #endif
 
 static int	check_more __ARGS((int, int));
-static linenr_T get_address __ARGS((char_u **, int addr_type, int skip, int to_other_file));
+static linenr_T get_address __ARGS((exarg_T *, char_u **, int addr_type, int skip, int to_other_file));
 static void	get_flags __ARGS((exarg_T *eap));
 #if !defined(FEAT_PERL) \
 	|| !defined(FEAT_PYTHON) || !defined(FEAT_PYTHON3) \
@@ -2173,9 +2173,12 @@ do_one_cmd(cmdlinep, sourcing,
 		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)
@@ -2233,6 +2236,12 @@ do_one_cmd(cmdlinep, sourcing,
 			    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;
 	    }
@@ -2693,6 +2702,11 @@ do_one_cmd(cmdlinep, sourcing,
 		else
 		    ea.line2 = ARGCOUNT;
 		break;
+	    case ADDR_QUICKFIX:
+		ea.line2 = qf_get_size(&ea);
+		if (ea.line2 == 0)
+		    ea.line2 = 1;
+		break;
 	}
     }
 
@@ -3839,6 +3853,8 @@ set_one_cmd_context(xp, buff)
 	case CMD_botright:
 	case CMD_browse:
 	case CMD_bufdo:
+	case CMD_cdo:
+	case CMD_cfdo:
 	case CMD_confirm:
 	case CMD_debug:
 	case CMD_folddoclosed:
@@ -3848,7 +3864,9 @@ set_one_cmd_context(xp, buff)
 	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:
@@ -4321,7 +4339,8 @@ skip_range(cmd, ctx)
  * Return MAXLNUM when no Ex address was found.
  */
     static linenr_T
-get_address(ptr, addr_type, skip, to_other_file)
+get_address(eap, ptr, addr_type, skip, to_other_file)
+    exarg_T	*eap;
     char_u	**ptr;
     int		addr_type;  /* flag: one of ADDR_LINES, ... */
     int		skip;	    /* only skip the address, don't use it */
@@ -4362,6 +4381,9 @@ get_address(ptr, addr_type, skip, to_oth
 		    case ADDR_TABS:
 			lnum = CURRENT_TAB_NR;
 			break;
+		    case ADDR_QUICKFIX:
+			lnum = qf_get_cur_valid_idx(eap);
+			break;
 		}
 		break;
 
@@ -4394,6 +4416,11 @@ get_address(ptr, addr_type, skip, to_oth
 		    case ADDR_TABS:
 			lnum = LAST_TAB_NR;
 			break;
+		    case ADDR_QUICKFIX:
+			lnum = qf_get_size(eap);
+			if (lnum == 0)
+			    lnum = 1;
+			break;
 		}
 		break;
 
@@ -4569,6 +4596,9 @@ get_address(ptr, addr_type, skip, to_oth
 		    case ADDR_TABS:
 			lnum = CURRENT_TAB_NR;
 			break;
+		    case ADDR_QUICKFIX:
+			lnum = qf_get_cur_valid_idx(eap);
+			break;
 		}
 	    }
 
@@ -4707,6 +4737,10 @@ invalid_range(eap)
 		if (eap->line2 > LAST_TAB_NR)
 		    return (char_u *)_(e_invrange);
 		break;
+	    case ADDR_QUICKFIX:
+		if (eap->line2 != 1 && eap->line2 > qf_get_size(eap))
+		    return (char_u *)_(e_invrange);
+		break;
 	}
     }
     return NULL;
@@ -5817,6 +5851,7 @@ static struct
     {ADDR_TABS, "tabs"},
     {ADDR_BUFFERS, "buffers"},
     {ADDR_WINDOWS, "windows"},
+    {ADDR_QUICKFIX, "quickfix"},
     {-1, NULL}
 };
 #endif
@@ -9224,7 +9259,7 @@ ex_copymove(eap)
 {
     long	n;
 
-    n = get_address(&eap->arg, eap->addr_type, FALSE, FALSE);
+    n = get_address(eap, &eap->arg, eap->addr_type, FALSE, FALSE);
     if (eap->arg == NULL)	    /* error detected */
     {
 	eap->nextcmd = NULL;
--- a/src/proto/quickfix.pro
+++ b/src/proto/quickfix.pro
@@ -17,6 +17,9 @@ int bt_dontwrite_msg __ARGS((buf_T *buf)
 int buf_hide __ARGS((buf_T *buf));
 int grep_internal __ARGS((cmdidx_T cmdidx));
 void ex_make __ARGS((exarg_T *eap));
+int qf_get_size __ARGS((exarg_T *eap));
+int qf_get_cur_idx __ARGS((exarg_T *eap));
+int qf_get_cur_valid_idx __ARGS((exarg_T *eap));
 void ex_cc __ARGS((exarg_T *eap));
 void ex_cnext __ARGS((exarg_T *eap));
 void ex_cfile __ARGS((exarg_T *eap));
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -1373,7 +1373,7 @@ qf_clean_dir_stack(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?
@@ -2990,19 +2990,183 @@ get_mef_name()
 }
 
 /*
+ * Returns the number of valid entries in the current quickfix/location list.
+ */
+    int
+qf_get_size(eap)
+    exarg_T	*eap;
+{
+    qf_info_T	*qi = &ql_info;
+    qfline_T	*qfp;
+    int		i, sz = 0;
+    int		prev_fnum = 0;
+
+    if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo)
+    {
+	/* Location list */
+	qi = GET_LOC_LIST(curwin);
+	if (qi == NULL)
+	    return 0;
+    }
+
+    for (i = 0, qfp = qi->qf_lists[qi->qf_curlist].qf_start;
+	    (i < qi->qf_lists[qi->qf_curlist].qf_count) && (qfp != NULL);
+	    ++i, qfp = qfp->qf_next)
+    {
+	if (qfp->qf_valid)
+	{
+	    if (eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo)
+		sz++;	/* Count all valid entries */
+	    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.
+ */
+    int
+qf_get_cur_idx(eap)
+    exarg_T	*eap;
+{
+    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;
+    }
+
+    return qi->qf_lists[qi->qf_curlist].qf_index;
+}
+
+/*
+ * Returns the current index in the quickfix/location list (counting only valid
+ * entries). If no valid entries are in the list, then returns 1.
+ */
+    int
+qf_get_cur_valid_idx(eap)
+    exarg_T	*eap;
+{
+    qf_info_T	*qi = &ql_info;
+    qf_list_T	*qfl;
+    qfline_T	*qfp;
+    int		i, eidx = 0;
+    int		prev_fnum = 0;
+
+    if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo)
+    {
+	/* Location list */
+	qi = GET_LOC_LIST(curwin);
+	if (qi == NULL)
+	    return 1;
+    }
+
+    qfl = &qi->qf_lists[qi->qf_curlist];
+    qfp = qfl->qf_start;
+
+    /* check if the list has valid errors */
+    if (qfl->qf_count <= 0 || qfl->qf_nonevalid)
+	return 1;
+
+    for (i = 1; i <= qfl->qf_index && qfp!= NULL; i++, qfp = qfp->qf_next)
+    {
+	if (qfp->qf_valid)
+	{
+	    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 ? 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 int
+qf_get_nth_valid_entry(qi, n, fdo)
+    qf_info_T	*qi;
+    int		n;
+    int		fdo;
+{
+    qf_list_T	*qfl = &qi->qf_lists[qi->qf_curlist];
+    qfline_T	*qfp = qfl->qf_start;
+    int		i, eidx;
+    int		prev_fnum = 0;
+
+    /* check if the list has valid errors */
+    if (qfl->qf_count <= 0 || qfl->qf_nonevalid)
+	return 1;
+
+    for (i = 1, eidx = 0; i <= 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;
+    }
+
+    if (i <= qfl->qf_count)
+	return i;
+    else
+	return 1;
+}
+
+/*
  * ":cc", ":crewind", ":cfirst" and ":clast".
  * ":ll", ":lrewind", ":lfirst" and ":llast".
+ * ":cdo", ":ldo", ":cfdo" and ":lfdo"
  */
     void
 ex_cc(eap)
     exarg_T	*eap;
 {
     qf_info_T	*qi = &ql_info;
+    int		errornr;
 
     if (eap->cmdidx == CMD_ll
 	    || eap->cmdidx == CMD_lrewind
 	    || eap->cmdidx == CMD_lfirst
-	    || eap->cmdidx == CMD_llast)
+	    || eap->cmdidx == CMD_llast
+	    || eap->cmdidx == CMD_ldo
+	    || eap->cmdidx == CMD_lfdo)
     {
 	qi = GET_LOC_LIST(curwin);
 	if (qi == NULL)
@@ -3012,34 +3176,51 @@ ex_cc(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);
+    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)
+	errornr = qf_get_nth_valid_entry(qi,
+		eap->addr_count > 0 ? (int)eap->line1 : 1,
+		eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo);
+
+    qf_jump(qi, 0, errornr, eap->forceit);
 }
 
 /*
  * ":cnext", ":cnfile", ":cNext" and ":cprevious".
  * ":lnext", ":lNext", ":lprevious", ":lnfile", ":lNfile" and ":lpfile".
+ * Also, used by ":cdo", ":ldo", ":cfdo" and ":lfdo" commands.
  */
     void
 ex_cnext(eap)
     exarg_T	*eap;
 {
     qf_info_T	*qi = &ql_info;
+    int		errornr;
 
     if (eap->cmdidx == CMD_lnext
 	    || eap->cmdidx == CMD_lNext
 	    || eap->cmdidx == CMD_lprevious
 	    || eap->cmdidx == CMD_lnfile
 	    || eap->cmdidx == CMD_lNfile
-	    || eap->cmdidx == CMD_lpfile)
+	    || eap->cmdidx == CMD_lpfile
+	    || eap->cmdidx == CMD_ldo
+	    || eap->cmdidx == CMD_lfdo)
     {
 	qi = GET_LOC_LIST(curwin);
 	if (qi == NULL)
@@ -3049,15 +3230,24 @@ ex_cnext(eap)
 	}
     }
 
-    qf_jump(qi, (eap->cmdidx == CMD_cnext || eap->cmdidx == CMD_lnext)
+    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);
 }
 
 /*
--- a/src/testdir/Make_amiga.mak
+++ b/src/testdir/Make_amiga.mak
@@ -41,6 +41,7 @@ SCRIPTS = test1.out test3.out test4.out 
 		test_autocmd_option.out \
 		test_autoformat_join.out \
 		test_breakindent.out \
+		test_cdo.out \
 		test_changelist.out \
 		test_charsearch.out \
 		test_close_count.out \
@@ -195,6 +196,7 @@ test_argument_count.out: test_argument_c
 test_autocmd_option.out: test_autocmd_option.in
 test_autoformat_join.out: test_autoformat_join.in
 test_breakindent.out: test_breakindent.in
+test_cdo.out: test_cdo.in
 test_changelist.out: test_changelist.in
 test_charsearch.out: test_charsearch.in
 test_close_count.out: test_close_count.in
--- a/src/testdir/Make_dos.mak
+++ b/src/testdir/Make_dos.mak
@@ -40,6 +40,7 @@ SCRIPTS =	test3.out test4.out test5.out 
 		test_autocmd_option.out \
 		test_autoformat_join.out \
 		test_breakindent.out \
+		test_cdo.out \
 		test_changelist.out \
 		test_charsearch.out \
 		test_close_count.out \
--- a/src/testdir/Make_ming.mak
+++ b/src/testdir/Make_ming.mak
@@ -62,6 +62,7 @@ SCRIPTS =	test3.out test4.out test5.out 
 		test_autocmd_option.out \
 		test_autoformat_join.out \
 		test_breakindent.out \
+		test_cdo.out \
 		test_changelist.out \
 		test_charsearch.out \
 		test_close_count.out \
--- a/src/testdir/Make_os2.mak
+++ b/src/testdir/Make_os2.mak
@@ -42,6 +42,7 @@ SCRIPTS = test1.out test3.out test4.out 
 		test_autocmd_option.out \
 		test_autoformat_join.out \
 		test_breakindent.out \
+		test_cdo.out \
 		test_changelist.out \
 		test_charsearch.out \
 		test_close_count.out \
--- a/src/testdir/Make_vms.mms
+++ b/src/testdir/Make_vms.mms
@@ -4,7 +4,7 @@
 # Authors:	Zoltan Arpadffy, <arpadffy@polarhome.com>
 #		Sandor Kopanyi,  <sandor.kopanyi@mailbox.hu>
 #
-# Last change:  2015 Sep 01
+# Last change:  2015 Sep 08
 #
 # This has been tested on VMS 6.2 to 8.3 on DEC Alpha, VAX and IA64.
 # Edit the lines in the Configuration section below to select.
@@ -101,6 +101,7 @@ SCRIPT = test1.out  test2.out  test3.out
 	 test_autocmd_option.out \
 	 test_autoformat_join.out \
 	 test_breakindent.out \
+	 test_cdo.out \
 	 test_changelist.out \
 	 test_charsearch.out \
 	 test_close_count.out \
--- a/src/testdir/Makefile
+++ b/src/testdir/Makefile
@@ -38,6 +38,7 @@ SCRIPTS = test1.out test2.out test3.out 
 		test_autocmd_option.out \
 		test_autoformat_join.out \
 		test_breakindent.out \
+		test_cdo.out \
 		test_changelist.out \
 		test_charsearch.out \
 		test_close_count.out \
new file mode 100644
--- /dev/null
+++ b/src/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
+
new file mode 100644
--- /dev/null
+++ b/src/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
+
--- a/src/version.c
+++ b/src/version.c
@@ -742,6 +742,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    858,
+/**/
     857,
 /**/
     856,