changeset 16543:1d2b3bb35414 v8.1.1275

patch 8.1.1275: cannot navigate to errors before/after the cursor commit https://github.com/vim/vim/commit/cf6a55c4b0cbf38b0c3fbed5ffd9a3fd0d2ede0e Author: Bram Moolenaar <Bram@vim.org> Date: Sun May 5 15:02:30 2019 +0200 patch 8.1.1275: cannot navigate to errors before/after the cursor Problem: Cannot navigate to errors before/after the cursor. Solution: Add the :cbefore and :cafter commands. (Yegappan Lakshmanan, closes #4340)
author Bram Moolenaar <Bram@vim.org>
date Sun, 05 May 2019 15:15:04 +0200
parents 5545614fe8e1
children 609f0de1e64c
files runtime/doc/index.txt runtime/doc/quickfix.txt src/ex_cmdidxs.h src/ex_cmds.h src/quickfix.c src/testdir/test_quickfix.vim src/version.c
diffstat 7 files changed, 297 insertions(+), 81 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -1192,9 +1192,11 @@ tag		command		action ~
 |:caddbuffer|	:cad[dbuffer]	add errors from buffer
 |:caddexpr|	:cadde[xpr]	add errors from expr
 |:caddfile|	:caddf[ile]	add error message to current quickfix list
+|:cafter|	:caf[ter]	go to error after current cursor
 |:call|		:cal[l]		call a function
 |:catch|	:cat[ch]	part of a :try command
-|:cbelow|	:cbe[low]	got to error below current line
+|:cbefore|	:cbef[ore]	go to error before current cursor
+|:cbelow|	:cbel[ow]	go to error below current line
 |:cbottom|	:cbo[ttom]	scroll to the bottom of the quickfix window
 |:cbuffer|	:cb[uffer]	parse error messages and jump to first error
 |:cc|		:cc		go to specific error
@@ -1356,10 +1358,12 @@ tag		command		action ~
 |:laddexpr|	:lad[dexpr]	add locations from expr
 |:laddbuffer|	:laddb[uffer]	add locations from buffer
 |:laddfile|	:laddf[ile]	add locations to current location list
+|:lafter|	:laf[ter]	go to location after current cursor
 |:last|		:la[st]		go to the last file in the argument list
 |:language|	:lan[guage]	set the language (locale)
 |:later|	:lat[er]	go to newer change, redo
-|:lbelow|	:lbe[low]	go to location below current line
+|:lbefore|	:lbef[ore]	go to location before current cursor
+|:lbelow|	:lbel[ow]	go to location below current line
 |:lbottom|	:lbo[ttom]	scroll to the bottom of the location window
 |:lbuffer|	:lb[uffer]	parse locations and jump to first location
 |:lcd|		:lc[d]		change directory locally
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -152,8 +152,36 @@ processing a quickfix or location list c
 			exceeds the number of entries below the current line,
 			then the last error in the file is selected.
 
-							*:lbe* *:lbelow*
-:[count]lbe[low]	Same as ":cbelow", except the location list for the
+							*:lbel* *:lbelow*
+:[count]lbel[ow]	Same as ":cbelow", except the location list for the
+			current window is used instead of the quickfix list.
+
+							*:cbe* *:cbefore*
+:[count]cbe[fore]	Go to the [count] error before the current cursor
+			position in the current buffer.  If [count] is
+			omitted, then 1 is used.  If there are no errors, then
+			an error message is displayed.  Assumes that the
+			entries in a quickfix list are sorted by their buffer,
+			line and column numbers.  If [count] exceeds the
+			number of entries before the current position, then
+			the first error in the file is selected.
+
+							*:lbef* *:lbefore*
+:[count]lbef[ore]	Same as ":cbefore", except the location list for the
+			current window is used instead of the quickfix list.
+
+							*:caf* *:cafter*
+:[count]caf[ter]	Go to the [count] error after the current cursor
+			position in the current buffer.  If [count] is
+			omitted, then 1 is used.  If there are no errors, then
+			an error message is displayed.  Assumes that the
+			entries in a quickfix list are sorted by their buffer,
+			line and column numbers.  If [count] exceeds the
+			number of entries after the current position, then
+			the last error in the file is selected.
+
+							*:laf* *:lafter*
+:[count]laf[ter]	Same as ":cafter", except the location list for the
 			current window is used instead of the quickfix list.
 
 							*:cnf* *:cnfile*
--- a/src/ex_cmdidxs.h
+++ b/src/ex_cmdidxs.h
@@ -8,29 +8,29 @@ static const unsigned short cmdidxs1[26]
   /* a */ 0,
   /* b */ 19,
   /* c */ 42,
-  /* d */ 105,
-  /* e */ 127,
-  /* f */ 147,
-  /* g */ 163,
-  /* h */ 169,
-  /* i */ 178,
-  /* j */ 196,
-  /* k */ 198,
-  /* l */ 203,
-  /* m */ 263,
-  /* n */ 281,
-  /* o */ 301,
-  /* p */ 313,
-  /* q */ 352,
-  /* r */ 355,
-  /* s */ 375,
-  /* t */ 443,
-  /* u */ 488,
-  /* v */ 499,
-  /* w */ 517,
-  /* x */ 531,
-  /* y */ 540,
-  /* z */ 541
+  /* d */ 107,
+  /* e */ 129,
+  /* f */ 149,
+  /* g */ 165,
+  /* h */ 171,
+  /* i */ 180,
+  /* j */ 198,
+  /* k */ 200,
+  /* l */ 205,
+  /* m */ 267,
+  /* n */ 285,
+  /* o */ 305,
+  /* p */ 317,
+  /* q */ 356,
+  /* r */ 359,
+  /* s */ 379,
+  /* t */ 447,
+  /* u */ 492,
+  /* v */ 503,
+  /* w */ 521,
+  /* x */ 535,
+  /* y */ 544,
+  /* z */ 545
 };
 
 /*
@@ -43,7 +43,7 @@ static const unsigned char cmdidxs2[26][
 { /*         a   b   c   d   e   f   g   h   i   j   k   l   m   n   o   p   q   r   s   t   u   v   w   x   y   z */
   /* a */ {  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  4,  5,  6,  0,  0,  0,  7, 15,  0, 16,  0,  0,  0,  0,  0 },
   /* b */ {  2,  0,  0,  4,  5,  7,  0,  0,  0,  0,  0,  8,  9, 10, 11, 12,  0, 13,  0,  0,  0,  0, 22,  0,  0,  0 },
-  /* c */ {  3, 11, 14, 16, 18, 20, 23,  0,  0,  0,  0, 31, 35, 38, 44, 53, 55, 56, 57,  0, 59,  0, 62,  0,  0,  0 },
+  /* c */ {  3, 12, 16, 18, 20, 22, 25,  0,  0,  0,  0, 33, 37, 40, 46, 55, 57, 58, 59,  0, 61,  0, 64,  0,  0,  0 },
   /* d */ {  0,  0,  0,  0,  0,  0,  0,  0,  6, 15,  0, 16,  0,  0, 17,  0,  0, 19, 20,  0,  0,  0,  0,  0,  0,  0 },
   /* e */ {  1,  0,  2,  0,  0,  0,  0,  0,  0,  0,  0,  7,  9, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0, 16,  0,  0 },
   /* f */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  9,  0,  0,  0,  0,  0, 15,  0,  0,  0,  0,  0 },
@@ -52,7 +52,7 @@ static const unsigned char cmdidxs2[26][
   /* i */ {  1,  0,  0,  0,  0,  3,  0,  0,  0,  4,  0,  5,  6,  0,  0,  0,  0,  0, 13,  0, 15,  0,  0,  0,  0,  0 },
   /* j */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0 },
   /* k */ {  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
-  /* l */ {  3, 10, 13, 17, 18, 22, 25, 30,  0,  0,  0, 32, 35, 38, 42, 48,  0, 50, 59, 51, 52, 56, 58,  0,  0,  0 },
+  /* l */ {  3, 11, 15, 19, 20, 24, 27, 32,  0,  0,  0, 34, 37, 40, 44, 50,  0, 52, 61, 53, 54, 58, 60,  0,  0,  0 },
   /* m */ {  1,  0,  0,  0,  7,  0,  0,  0,  0,  0, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 16 },
   /* n */ {  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  5,  8, 10,  0,  0,  0,  0,  0, 17,  0,  0,  0,  0,  0 },
   /* o */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  2,  5,  0,  0,  0,  0,  0,  0,  9,  0, 11,  0,  0,  0 },
@@ -69,4 +69,4 @@ static const unsigned char cmdidxs2[26][
   /* z */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }
 };
 
-static const int command_count = 554;
+static const int command_count = 558;
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -266,6 +266,9 @@ EX(CMD_caddexpr,	"caddexpr",	ex_cexpr,
 EX(CMD_caddfile,	"caddfile",	ex_cfile,
 			TRLBAR|FILE1,
 			ADDR_NONE),
+EX(CMD_cafter,		"cafter",	ex_cbelow,
+			RANGE|COUNT|TRLBAR,
+			ADDR_UNSIGNED),
 EX(CMD_call,		"call",		ex_call,
 			RANGE|NEEDARG|EXTRA|NOTRLCOM|SBOXOK|CMDWIN,
 			ADDR_LINES),
@@ -275,6 +278,9 @@ EX(CMD_catch,		"catch",	ex_catch,
 EX(CMD_cbuffer,		"cbuffer",	ex_cbuffer,
 			BANG|RANGE|WORD1|TRLBAR,
 			ADDR_OTHER),
+EX(CMD_cbefore,		"cbefore",	ex_cbelow,
+			RANGE|COUNT|TRLBAR,
+			ADDR_UNSIGNED),
 EX(CMD_cbelow,		"cbelow",	ex_cbelow,
 			RANGE|COUNT|TRLBAR,
 			ADDR_UNSIGNED),
@@ -749,12 +755,18 @@ EX(CMD_laddbuffer,	"laddbuffer",	ex_cbuf
 EX(CMD_laddfile,	"laddfile",	ex_cfile,
 			TRLBAR|FILE1,
 			ADDR_NONE),
+EX(CMD_lafter,		"lafter",	ex_cbelow,
+			RANGE|COUNT|TRLBAR,
+			ADDR_UNSIGNED),
 EX(CMD_later,		"later",	ex_later,
 			TRLBAR|EXTRA|NOSPC|CMDWIN,
 			ADDR_NONE),
 EX(CMD_lbuffer,		"lbuffer",	ex_cbuffer,
 			BANG|RANGE|WORD1|TRLBAR,
 			ADDR_OTHER),
+EX(CMD_lbefore,		"lbefore",	ex_cbelow,
+			RANGE|COUNT|TRLBAR,
+			ADDR_UNSIGNED),
 EX(CMD_lbelow,		"lbelow",	ex_cbelow,
 			RANGE|COUNT|TRLBAR,
 			ADDR_UNSIGNED),
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -5128,36 +5128,100 @@ qf_find_last_entry_on_line(qfline_T *ent
 }
 
 /*
- * Find the first quickfix entry below line 'lnum' in buffer 'bnr'.
+ * Returns TRUE if the specified quickfix entry is
+ *   after the given line (linewise is TRUE)
+ *   or after the line and column.
+ */
+    static int
+qf_entry_after_pos(qfline_T *qfp, pos_T *pos, int linewise)
+{
+    if (linewise)
+	return qfp->qf_lnum > pos->lnum;
+    else
+	return (qfp->qf_lnum > pos->lnum ||
+		(qfp->qf_lnum == pos->lnum && qfp->qf_col > pos->col));
+}
+
+/*
+ * Returns TRUE if the specified quickfix entry is
+ *   before the given line (linewise is TRUE)
+ *   or before the line and column.
+ */
+    static int
+qf_entry_before_pos(qfline_T *qfp, pos_T *pos, int linewise)
+{
+    if (linewise)
+	return qfp->qf_lnum < pos->lnum;
+    else
+	return (qfp->qf_lnum < pos->lnum ||
+		(qfp->qf_lnum == pos->lnum && qfp->qf_col < pos->col));
+}
+
+/*
+ * Returns TRUE if the specified quickfix entry is
+ *   on or after the given line (linewise is TRUE)
+ *   or on or after the line and column.
+ */
+    static int
+qf_entry_on_or_after_pos(qfline_T *qfp, pos_T *pos, int linewise)
+{
+    if (linewise)
+	return qfp->qf_lnum >= pos->lnum;
+    else
+	return (qfp->qf_lnum > pos->lnum ||
+		(qfp->qf_lnum == pos->lnum && qfp->qf_col >= pos->col));
+}
+
+/*
+ * Returns TRUE if the specified quickfix entry is
+ *   on or before the given line (linewise is TRUE)
+ *   or on or before the line and column.
+ */
+    static int
+qf_entry_on_or_before_pos(qfline_T *qfp, pos_T *pos, int linewise)
+{
+    if (linewise)
+	return qfp->qf_lnum <= pos->lnum;
+    else
+	return (qfp->qf_lnum < pos->lnum ||
+		(qfp->qf_lnum == pos->lnum && qfp->qf_col <= pos->col));
+}
+
+/*
+ * Find the first quickfix entry after position 'pos' in buffer 'bnr'.
+ * If 'linewise' is TRUE, returns the entry after the specified line and treats
+ * multiple entries on a single line as one. Otherwise returns the entry after
+ * the specified line and column.
  * 'qfp' points to the very first entry in the buffer and 'errornr' is the
  * index of the very first entry in the quickfix list.
- * Returns NULL if an entry is not found after 'lnum'.
+ * Returns NULL if an entry is not found after 'pos'.
  */
     static qfline_T *
-qf_find_entry_on_next_line(
+qf_find_entry_after_pos(
 	int		bnr,
-	linenr_T	lnum,
+	pos_T		*pos,
+	int		linewise,
 	qfline_T	*qfp,
 	int		*errornr)
 {
-    if (qfp->qf_lnum > lnum)
-	// First entry is after line 'lnum'
+    if (qf_entry_after_pos(qfp, pos, linewise))
+	// First entry is after postion 'pos'
 	return qfp;
 
-    // Find the entry just before or at the line 'lnum'
+    // Find the entry just before or at the position 'pos'
     while (qfp->qf_next != NULL
 	    && qfp->qf_next->qf_fnum == bnr
-	    && qfp->qf_next->qf_lnum <= lnum)
+	    && qf_entry_on_or_before_pos(qfp->qf_next, pos, linewise))
     {
 	qfp = qfp->qf_next;
 	++*errornr;
     }
 
     if (qfp->qf_next == NULL || qfp->qf_next->qf_fnum != bnr)
-	// No entries found after 'lnum'
+	// No entries found after position 'pos'
 	return NULL;
 
-    // Use the entry just after line 'lnum'
+    // Use the entry just after position 'pos'
     qfp = qfp->qf_next;
     ++*errornr;
 
@@ -5165,46 +5229,52 @@ qf_find_entry_on_next_line(
 }
 
 /*
- * Find the first quickfix entry before line 'lnum' in buffer 'bnr'.
+ * Find the first quickfix entry before position 'pos' in buffer 'bnr'.
+ * If 'linewise' is TRUE, returns the entry before the specified line and
+ * treats multiple entries on a single line as one. Otherwise returns the entry
+ * before the specified line and column.
  * 'qfp' points to the very first entry in the buffer and 'errornr' is the
  * index of the very first entry in the quickfix list.
- * Returns NULL if an entry is not found before 'lnum'.
+ * Returns NULL if an entry is not found before 'pos'.
  */
     static qfline_T *
-qf_find_entry_on_prev_line(
+qf_find_entry_before_pos(
 	int		bnr,
-	linenr_T	lnum,
+	pos_T		*pos,
+	int		linewise,
 	qfline_T	*qfp,
 	int		*errornr)
 {
-    // Find the entry just before the line 'lnum'
+    // Find the entry just before the position 'pos'
     while (qfp->qf_next != NULL
 	    && qfp->qf_next->qf_fnum == bnr
-	    && qfp->qf_next->qf_lnum < lnum)
+	    && qf_entry_before_pos(qfp->qf_next, pos, linewise))
     {
 	qfp = qfp->qf_next;
 	++*errornr;
     }
 
-    if (qfp->qf_lnum >= lnum)	// entry is after 'lnum'
+    if (qf_entry_on_or_after_pos(qfp, pos, linewise))
 	return NULL;
 
-    // If multiple entries are on the same line, then use the first entry
-    qfp = qf_find_first_entry_on_line(qfp, errornr);
+    if (linewise)
+	// If multiple entries are on the same line, then use the first entry
+	qfp = qf_find_first_entry_on_line(qfp, errornr);
 
     return qfp;
 }
 
 /*
- * Find a quickfix entry in 'qfl' closest to line 'lnum' in buffer 'bnr' in
+ * Find a quickfix entry in 'qfl' closest to position 'pos' in buffer 'bnr' in
  * the direction 'dir'.
  */
     static qfline_T *
 qf_find_closest_entry(
 	qf_list_T	*qfl,
 	int		bnr,
-	linenr_T	lnum,
+	pos_T		*pos,
 	int		dir,
+	int		linewise,
 	int		*errornr)
 {
     qfline_T	*qfp;
@@ -5217,35 +5287,40 @@ qf_find_closest_entry(
 	return NULL;		// no entry in this file
 
     if (dir == FORWARD)
-	qfp = qf_find_entry_on_next_line(bnr, lnum, qfp, errornr);
+	qfp = qf_find_entry_after_pos(bnr, pos, linewise, qfp, errornr);
     else
-	qfp = qf_find_entry_on_prev_line(bnr, lnum, qfp, errornr);
+	qfp = qf_find_entry_before_pos(bnr, pos, linewise, qfp, errornr);
 
     return qfp;
 }
 
 /*
- * Get the nth quickfix entry below the specified entry treating multiple
- * entries on a single line as one. Searches forward in the list.
+ * Get the nth quickfix entry below the specified entry.  Searches forward in
+ * the list. If linewise is TRUE, then treat multiple entries on a single line
+ * as one.
  */
     static qfline_T *
-qf_get_nth_below_entry(qfline_T *entry, int *errornr, int n)
+qf_get_nth_below_entry(qfline_T *entry, int n, int linewise, int *errornr)
 {
     while (n-- > 0 && !got_int)
     {
 	qfline_T	*first_entry = entry;
 	int		first_errornr = *errornr;
 
-	// Treat all the entries on the same line in this file as one
-	entry = qf_find_last_entry_on_line(entry, errornr);
+	if (linewise)
+	    // Treat all the entries on the same line in this file as one
+	    entry = qf_find_last_entry_on_line(entry, errornr);
 
 	if (entry->qf_next == NULL
 		|| entry->qf_next->qf_fnum != entry->qf_fnum)
 	{
-	    // If multiple entries are on the same line, then use the first
-	    // entry
-	    entry = first_entry;
-	    *errornr = first_errornr;
+	    if (linewise)
+	    {
+		// If multiple entries are on the same line, then use the first
+		// entry
+		entry = first_entry;
+		*errornr = first_errornr;
+	    }
 	    break;
 	}
 
@@ -5257,11 +5332,12 @@ qf_get_nth_below_entry(qfline_T *entry, 
 }
 
 /*
- * Get the nth quickfix entry above the specified entry treating multiple
- * entries on a single line as one. Searches backwards in the list.
+ * Get the nth quickfix entry above the specified entry.  Searches backwards in
+ * the list. If linewise is TRUE, then treat multiple entries on a single line
+ * as one.
  */
     static qfline_T *
-qf_get_nth_above_entry(qfline_T *entry, int *errornr, int n)
+qf_get_nth_above_entry(qfline_T *entry, int n, int linewise, int *errornr)
 {
     while (n-- > 0 && !got_int)
     {
@@ -5273,25 +5349,32 @@ qf_get_nth_above_entry(qfline_T *entry, 
 	--*errornr;
 
 	// If multiple entries are on the same line, then use the first entry
-	entry = qf_find_first_entry_on_line(entry, errornr);
+	if (linewise)
+	    entry = qf_find_first_entry_on_line(entry, errornr);
     }
 
     return entry;
 }
 
 /*
- * Find the n'th quickfix entry adjacent to line 'lnum' in buffer 'bnr' in the
- * specified direction.
- * Returns the error number in the quickfix list or 0 if an entry is not found.
+ * Find the n'th quickfix entry adjacent to position 'pos' in buffer 'bnr' in
+ * the specified direction.  Returns the error number in the quickfix list or 0
+ * if an entry is not found.
  */
     static int
-qf_find_nth_adj_entry(qf_list_T *qfl, int bnr, linenr_T lnum, int n, int dir)
+qf_find_nth_adj_entry(
+	qf_list_T	*qfl,
+	int		bnr,
+	pos_T		*pos,
+	int		n,
+	int		dir,
+	int		linewise)
 {
     qfline_T	*adj_entry;
     int		errornr;
 
-    // Find an entry closest to the specified line
-    adj_entry = qf_find_closest_entry(qfl, bnr, lnum, dir, &errornr);
+    // Find an entry closest to the specified position
+    adj_entry = qf_find_closest_entry(qfl, bnr, pos, dir, linewise, &errornr);
     if (adj_entry == NULL)
 	return 0;
 
@@ -5299,17 +5382,21 @@ qf_find_nth_adj_entry(qf_list_T *qfl, in
     {
 	// Go to the n'th entry in the current buffer
 	if (dir == FORWARD)
-	    adj_entry = qf_get_nth_below_entry(adj_entry, &errornr, n);
+	    adj_entry = qf_get_nth_below_entry(adj_entry, n, linewise,
+		    &errornr);
 	else
-	    adj_entry = qf_get_nth_above_entry(adj_entry, &errornr, n);
+	    adj_entry = qf_get_nth_above_entry(adj_entry, n, linewise,
+		    &errornr);
     }
 
     return errornr;
 }
 
 /*
- * Jump to a quickfix entry in the current file nearest to the current line.
- * ":cabove", ":cbelow", ":labove" and ":lbelow" commands
+ * Jump to a quickfix entry in the current file nearest to the current line or
+ * current line/col.
+ * ":cabove", ":cbelow", ":labove", ":lbelow", ":cafter", ":cbefore",
+ * ":lafter" and ":lbefore" commands
  */
     void
 ex_cbelow(exarg_T *eap)
@@ -5319,6 +5406,7 @@ ex_cbelow(exarg_T *eap)
     int		dir;
     int		buf_has_flag;
     int		errornr = 0;
+    pos_T	pos;
 
     if (eap->addr_count > 0 && eap->line2 <= 0)
     {
@@ -5327,7 +5415,8 @@ ex_cbelow(exarg_T *eap)
     }
 
     // Check whether the current buffer has any quickfix entries
-    if (eap->cmdidx == CMD_cabove || eap->cmdidx == CMD_cbelow)
+    if (eap->cmdidx == CMD_cabove || eap->cmdidx == CMD_cbelow
+	    || eap->cmdidx == CMD_cbefore || eap->cmdidx == CMD_cafter)
 	buf_has_flag = BUF_HAS_QF_ENTRY;
     else
 	buf_has_flag = BUF_HAS_LL_ENTRY;
@@ -5348,13 +5437,25 @@ ex_cbelow(exarg_T *eap)
 	return;
     }
 
-    if (eap->cmdidx == CMD_cbelow || eap->cmdidx == CMD_lbelow)
+    if (eap->cmdidx == CMD_cbelow
+	    || eap->cmdidx == CMD_lbelow
+	    || eap->cmdidx == CMD_cafter
+	    || eap->cmdidx == CMD_lafter)
+	// Forward motion commands
 	dir = FORWARD;
     else
 	dir = BACKWARD;
 
-    errornr = qf_find_nth_adj_entry(qfl, curbuf->b_fnum, curwin->w_cursor.lnum,
-	    eap->addr_count > 0 ? eap->line2 : 0, dir);
+    pos = curwin->w_cursor;
+    // A quickfix entry column number is 1 based whereas cursor column
+    // number is 0 based. Adjust the column number.
+    pos.col++;
+    errornr = qf_find_nth_adj_entry(qfl, curbuf->b_fnum, &pos,
+				eap->addr_count > 0 ? eap->line2 : 0, dir,
+				eap->cmdidx == CMD_cbelow
+					|| eap->cmdidx == CMD_lbelow
+					|| eap->cmdidx == CMD_cabove
+					|| eap->cmdidx == CMD_labove);
 
     if (errornr > 0)
 	qf_jump(qi, 0, errornr, FALSE);
--- a/src/testdir/test_quickfix.vim
+++ b/src/testdir/test_quickfix.vim
@@ -39,6 +39,8 @@ func s:setup_commands(cchar)
     command! -nargs=0 -count Xcc <count>cc
     command! -count=1 -nargs=0 Xbelow <mods><count>cbelow
     command! -count=1 -nargs=0 Xabove <mods><count>cabove
+    command! -count=1 -nargs=0 Xbefore <mods><count>cbefore
+    command! -count=1 -nargs=0 Xafter <mods><count>cafter
     let g:Xgetlist = function('getqflist')
     let g:Xsetlist = function('setqflist')
     call setqflist([], 'f')
@@ -74,6 +76,8 @@ func s:setup_commands(cchar)
     command! -nargs=0 -count Xcc <count>ll
     command! -count=1 -nargs=0 Xbelow <mods><count>lbelow
     command! -count=1 -nargs=0 Xabove <mods><count>labove
+    command! -count=1 -nargs=0 Xbefore <mods><count>lbefore
+    command! -count=1 -nargs=0 Xafter <mods><count>lafter
     let g:Xgetlist = function('getloclist', [0])
     let g:Xsetlist = function('setloclist', [0])
     call setloclist(0, [], 'f')
@@ -4041,17 +4045,22 @@ func Test_empty_qfbuf()
 endfunc
 
 " Test for the :cbelow, :cabove, :lbelow and :labove commands.
+" And for the :cafter, :cbefore, :lafter and :lbefore commands.
 func Xtest_below(cchar)
   call s:setup_commands(a:cchar)
 
   " No quickfix/location list
   call assert_fails('Xbelow', 'E42:')
   call assert_fails('Xabove', 'E42:')
+  call assert_fails('Xbefore', 'E42:')
+  call assert_fails('Xafter', 'E42:')
 
   " Empty quickfix/location list
   call g:Xsetlist([])
   call assert_fails('Xbelow', 'E42:')
   call assert_fails('Xabove', 'E42:')
+  call assert_fails('Xbefore', 'E42:')
+  call assert_fails('Xafter', 'E42:')
 
   call s:create_test_file('X1')
   call s:create_test_file('X2')
@@ -4065,39 +4074,74 @@ func Xtest_below(cchar)
   call assert_fails('Xabove', 'E42:')
   call assert_fails('3Xbelow', 'E42:')
   call assert_fails('4Xabove', 'E42:')
+  call assert_fails('Xbefore', 'E42:')
+  call assert_fails('Xafter', 'E42:')
+  call assert_fails('3Xbefore', 'E42:')
+  call assert_fails('4Xafter', 'E42:')
 
   " Test the commands with various arguments
-  Xexpr ["X1:5:L5", "X2:5:L5", "X2:10:L10", "X2:15:L15", "X3:3:L3"]
+  Xexpr ["X1:5:3:L5", "X2:5:2:L5", "X2:10:3:L10", "X2:15:4:L15", "X3:3:5:L3"]
   edit +7 X2
   Xabove
   call assert_equal(['X2', 5], [bufname(''), line('.')])
   call assert_fails('Xabove', 'E553:')
+  normal 7G
+  Xbefore
+  call assert_equal(['X2', 5, 2], [bufname(''), line('.'), col('.')])
+  call assert_fails('Xbefore', 'E553:')
+
   normal 2j
   Xbelow
   call assert_equal(['X2', 10], [bufname(''), line('.')])
+  normal 7G
+  Xafter
+  call assert_equal(['X2', 10, 3], [bufname(''), line('.'), col('.')])
+
   " Last error in this file
   Xbelow 99
   call assert_equal(['X2', 15], [bufname(''), line('.')])
   call assert_fails('Xbelow', 'E553:')
+  normal gg
+  Xafter 99
+  call assert_equal(['X2', 15, 4], [bufname(''), line('.'), col('.')])
+  call assert_fails('Xafter', 'E553:')
+
   " First error in this file
   Xabove 99
   call assert_equal(['X2', 5], [bufname(''), line('.')])
   call assert_fails('Xabove', 'E553:')
+  normal G
+  Xbefore 99
+  call assert_equal(['X2', 5, 2], [bufname(''), line('.'), col('.')])
+  call assert_fails('Xbefore', 'E553:')
+
   normal gg
   Xbelow 2
   call assert_equal(['X2', 10], [bufname(''), line('.')])
+  normal gg
+  Xafter 2
+  call assert_equal(['X2', 10, 3], [bufname(''), line('.'), col('.')])
+
   normal G
   Xabove 2
   call assert_equal(['X2', 10], [bufname(''), line('.')])
+  normal G
+  Xbefore 2
+  call assert_equal(['X2', 10, 3], [bufname(''), line('.'), col('.')])
+
   edit X4
   call assert_fails('Xabove', 'E42:')
   call assert_fails('Xbelow', 'E42:')
+  call assert_fails('Xbefore', 'E42:')
+  call assert_fails('Xafter', 'E42:')
   if a:cchar == 'l'
     " If a buffer has location list entries from some other window but not
     " from the current window, then the commands should fail.
     edit X1 | split | call setloclist(0, [], 'f')
     call assert_fails('Xabove', 'E776:')
     call assert_fails('Xbelow', 'E776:')
+    call assert_fails('Xbefore', 'E776:')
+    call assert_fails('Xafter', 'E776:')
     close
   endif
 
@@ -4108,27 +4152,52 @@ func Xtest_below(cchar)
   edit +1 X2
   Xbelow 2
   call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')])
+  normal 1G
+  Xafter 2
+  call assert_equal(['X2', 5, 2], [bufname(''), line('.'), col('.')])
+
   normal gg
   Xbelow 99
   call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')])
+  normal gg
+  Xafter 99
+  call assert_equal(['X2', 15, 3], [bufname(''), line('.'), col('.')])
+
   normal G
   Xabove 2
   call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')])
   normal G
+  Xbefore 2
+  call assert_equal(['X2', 15, 2], [bufname(''), line('.'), col('.')])
+
+  normal G
   Xabove 99
   call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')])
+  normal G
+  Xbefore 99
+  call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')])
+
   normal 10G
   Xabove
   call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')])
+  normal 10G$
+  2Xbefore
+  call assert_equal(['X2', 10, 2], [bufname(''), line('.'), col('.')])
+
   normal 10G
   Xbelow
   call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')])
+  normal 9G
+  5Xafter
+  call assert_equal(['X2', 15, 2], [bufname(''), line('.'), col('.')])
 
   " Invalid range
   if a:cchar == 'c'
     call assert_fails('-2cbelow', 'E16:')
+    call assert_fails('-2cafter', 'E16:')
   else
     call assert_fails('-2lbelow', 'E16:')
+    call assert_fails('-2lafter', 'E16:')
   endif
 
   call delete('X1')
--- a/src/version.c
+++ b/src/version.c
@@ -768,6 +768,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1275,
+/**/
     1274,
 /**/
     1273,