changeset 16505:28e3ba82d8c8 v8.1.1256

patch 8.1.1256: cannot navigate through errors relative to the cursor commit https://github.com/vim/vim/commit/3ff33114d70fc0f7e9c3187c5fec9028f6499cf3 Author: Bram Moolenaar <Bram@vim.org> Date: Fri May 3 21:56:35 2019 +0200 patch 8.1.1256: cannot navigate through errors relative to the cursor Problem: Cannot navigate through errors relative to the cursor. Solution: Add :cabove, :cbelow, :labove and :lbelow. (Yegappan Lakshmanan, closes #4316)
author Bram Moolenaar <Bram@vim.org>
date Fri, 03 May 2019 22:00:06 +0200
parents d70715c33e4f
children a9fef3913baf
files runtime/doc/index.txt runtime/doc/quickfix.txt src/ex_cmdidxs.h src/ex_cmds.h src/ex_docmd.c src/proto/quickfix.pro src/quickfix.c src/testdir/test_quickfix.vim src/version.c
diffstat 9 files changed, 494 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -1188,11 +1188,13 @@ tag		command		action ~
 |:cNfile|	:cNf[ile]	go to last error in previous file
 |:cabbrev|	:ca[bbrev]	like ":abbreviate" but for Command-line mode
 |:cabclear|	:cabc[lear]	clear all abbreviations for Command-line mode
+|:cabove|	:cabo[ve]	go to error above current line
 |:caddbuffer|	:cad[dbuffer]	add errors from buffer
 |:caddexpr|	:cadde[xpr]	add errors from expr
 |:caddfile|	:caddf[ile]	add error message to current quickfix list
 |:call|		:cal[l]		call a function
 |:catch|	:cat[ch]	part of a :try command
+|:cbelow|	:cbe[low]	got 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
@@ -1350,12 +1352,14 @@ tag		command		action ~
 |:lNext|	:lN[ext]	go to previous entry in location list
 |:lNfile|	:lNf[ile]	go to last entry in previous file
 |:list|		:l[ist]		print lines
+|:labove|	:lab[ove]	go to location above current line
 |:laddexpr|	:lad[dexpr]	add locations from expr
 |:laddbuffer|	:laddb[uffer]	add locations from buffer
 |:laddfile|	:laddf[ile]	add locations to current location list
 |: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
 |: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
@@ -123,6 +123,36 @@ processing a quickfix or location list c
 			list for the current window is used instead of the
 			quickfix list.
 
+							*:cabo* *:cabove*
+:[count]cabo[ve]	Go to the [count] error above the current line 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 number and line
+			number. If there are multiple errors on the same line,
+			then only the first entry is used.  If [count] exceeds
+			the number of entries above the current line, then the
+			first error in the file is selected.
+
+							*:lab* *:labove*
+:[count]lab[ove]	Same as ":cabove", except the location list for the
+			current window is used instead of the quickfix list.
+
+							*:cbe* *:cbelow*
+:[count]cbe[low]	Go to the [count] error below the current line 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 number and line
+			number.  If there are multiple errors on the same
+			line, then only the first entry is used.  If [count]
+			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
+			current window is used instead of the quickfix list.
+
 							*:cnf* *:cnfile*
 :[count]cnf[ile][!]	Display the first error in the [count] next file in
 			the list that includes a file name.  If there are no
--- 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 */ 103,
-  /* e */ 125,
-  /* f */ 145,
-  /* g */ 161,
-  /* h */ 167,
-  /* i */ 176,
-  /* j */ 194,
-  /* k */ 196,
-  /* l */ 201,
-  /* m */ 259,
-  /* n */ 277,
-  /* o */ 297,
-  /* p */ 309,
-  /* q */ 348,
-  /* r */ 351,
-  /* s */ 371,
-  /* t */ 439,
-  /* u */ 484,
-  /* v */ 495,
-  /* w */ 513,
-  /* x */ 527,
-  /* y */ 536,
-  /* z */ 537
+  /* 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
 };
 
 /*
@@ -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, 10, 12, 14, 16, 18, 21,  0,  0,  0,  0, 29, 33, 36, 42, 51, 53, 54, 55,  0, 57,  0, 60,  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 },
   /* 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,  9, 11, 15, 16, 20, 23, 28,  0,  0,  0, 30, 33, 36, 40, 46,  0, 48, 57, 49, 50, 54, 56,  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 },
   /* 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 = 550;
+static const int command_count = 554;
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -252,6 +252,9 @@ EX(CMD_cabbrev,		"cabbrev",	ex_abbreviat
 EX(CMD_cabclear,	"cabclear",	ex_abclear,
 			EXTRA|TRLBAR|CMDWIN,
 			ADDR_NONE),
+EX(CMD_cabove,		"cabove",	ex_cbelow,
+			RANGE|TRLBAR,
+			ADDR_OTHER),
 EX(CMD_caddbuffer,	"caddbuffer",	ex_cbuffer,
 			RANGE|WORD1|TRLBAR,
 			ADDR_OTHER),
@@ -270,6 +273,9 @@ EX(CMD_catch,		"catch",	ex_catch,
 EX(CMD_cbuffer,		"cbuffer",	ex_cbuffer,
 			BANG|RANGE|WORD1|TRLBAR,
 			ADDR_OTHER),
+EX(CMD_cbelow,		"cbelow",	ex_cbelow,
+			RANGE|TRLBAR,
+			ADDR_OTHER),
 EX(CMD_cbottom,		"cbottom",	ex_cbottom,
 			TRLBAR,
 			ADDR_NONE),
@@ -726,6 +732,9 @@ EX(CMD_lNfile,		"lNfile",	ex_cnext,
 EX(CMD_last,		"last",		ex_last,
 			EXTRA|BANG|EDITCMD|ARGOPT|TRLBAR,
 			ADDR_NONE),
+EX(CMD_labove,		"labove",	ex_cbelow,
+			RANGE|TRLBAR,
+			ADDR_OTHER),
 EX(CMD_language,	"language",	ex_language,
 			EXTRA|TRLBAR|CMDWIN,
 			ADDR_NONE),
@@ -744,6 +753,9 @@ EX(CMD_later,		"later",	ex_later,
 EX(CMD_lbuffer,		"lbuffer",	ex_cbuffer,
 			BANG|RANGE|WORD1|TRLBAR,
 			ADDR_OTHER),
+EX(CMD_lbelow,		"lbelow",	ex_cbelow,
+			RANGE|TRLBAR,
+			ADDR_OTHER),
 EX(CMD_lbottom,		"lbottom",	ex_cbottom,
 			TRLBAR,
 			ADDR_NONE),
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -56,6 +56,7 @@ static int	getargopt(exarg_T *eap);
 # define ex_cbuffer		ex_ni
 # define ex_cc			ex_ni
 # define ex_cnext		ex_ni
+# define ex_cbelow		ex_ni
 # define ex_cfile		ex_ni
 # define qf_list		ex_ni
 # define qf_age			ex_ni
--- a/src/proto/quickfix.pro
+++ b/src/proto/quickfix.pro
@@ -23,6 +23,7 @@ int qf_get_cur_idx(exarg_T *eap);
 int qf_get_cur_valid_idx(exarg_T *eap);
 void ex_cc(exarg_T *eap);
 void ex_cnext(exarg_T *eap);
+void ex_cbelow(exarg_T *eap);
 void ex_cfile(exarg_T *eap);
 void ex_vimgrep(exarg_T *eap);
 int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list);
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -177,6 +177,7 @@ static buf_T	*load_dummy_buffer(char_u *
 static void	wipe_dummy_buffer(buf_T *buf, char_u *dirname_start);
 static void	unload_dummy_buffer(buf_T *buf, char_u *dirname_start);
 static qf_info_T *ll_get_or_alloc_list(win_T *);
+static char_u	*e_no_more_items = (char_u *)N_("E553: No more items");
 
 // Quickfix window check helper macro
 #define IS_QF_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref == NULL)
@@ -1494,6 +1495,16 @@ qf_list_empty(qf_list_T *qfl)
 }
 
 /*
+ * Returns TRUE if the specified quickfix/location list is not empty and
+ * has valid entries.
+ */
+    static int
+qf_list_has_valid_entries(qf_list_T *qfl)
+{
+    return !qf_list_empty(qfl) && !qfl->qf_nonevalid;
+}
+
+/*
  * Return a pointer to a list in the specified quickfix stack
  */
     static qf_list_T *
@@ -2700,7 +2711,6 @@ get_nth_valid_entry(
     int			qf_idx = qfl->qf_index;
     qfline_T		*prev_qf_ptr;
     int			prev_index;
-    static char_u	*e_no_more_items = (char_u *)N_("E553: No more items");
     char_u		*err = e_no_more_items;
 
     while (errornr--)
@@ -4886,7 +4896,7 @@ qf_get_cur_valid_idx(exarg_T *eap)
     qfp = qfl->qf_start;
 
     // check if the list has valid errors
-    if (qfl->qf_count <= 0 || qfl->qf_nonevalid)
+    if (!qf_list_has_valid_entries(qfl))
 	return 1;
 
     for (i = 1; i <= qfl->qf_index && qfp!= NULL; i++, qfp = qfp->qf_next)
@@ -4924,7 +4934,7 @@ qf_get_nth_valid_entry(qf_list_T *qfl, i
     int		prev_fnum = 0;
 
     // check if the list has valid errors
-    if (qfl->qf_count <= 0 || qfl->qf_nonevalid)
+    if (!qf_list_has_valid_entries(qfl))
 	return 1;
 
     eidx = 0;
@@ -5045,6 +5055,301 @@ ex_cnext(exarg_T *eap)
 }
 
 /*
+ * Find the first entry in the quickfix list 'qfl' from buffer 'bnr'.
+ * The index of the entry is stored in 'errornr'.
+ * Returns NULL if an entry is not found.
+ */
+    static qfline_T *
+qf_find_first_entry_in_buf(qf_list_T *qfl, int bnr, int *errornr)
+{
+    qfline_T	*qfp = NULL;
+    int		idx = 0;
+
+    // Find the first entry in this file
+    FOR_ALL_QFL_ITEMS(qfl, qfp, idx)
+	if (qfp->qf_fnum == bnr)
+	    break;
+
+    *errornr = idx;
+    return qfp;
+}
+
+/*
+ * Find the first quickfix entry on the same line as 'entry'. Updates 'errornr'
+ * with the error number for the first entry. Assumes the entries are sorted in
+ * the quickfix list by line number.
+ */
+    static qfline_T *
+qf_find_first_entry_on_line(qfline_T *entry, int *errornr)
+{
+    while (!got_int
+	    && entry->qf_prev != NULL
+	    && entry->qf_fnum == entry->qf_prev->qf_fnum
+	    && entry->qf_lnum == entry->qf_prev->qf_lnum)
+    {
+	entry = entry->qf_prev;
+	--*errornr;
+    }
+
+    return entry;
+}
+
+/*
+ * Find the last quickfix entry on the same line as 'entry'. Updates 'errornr'
+ * with the error number for the last entry. Assumes the entries are sorted in
+ * the quickfix list by line number.
+ */
+    static qfline_T *
+qf_find_last_entry_on_line(qfline_T *entry, int *errornr)
+{
+    while (!got_int &&
+	    entry->qf_next != NULL
+	    && entry->qf_fnum == entry->qf_next->qf_fnum
+	    && entry->qf_lnum == entry->qf_next->qf_lnum)
+    {
+	entry = entry->qf_next;
+	++*errornr;
+    }
+
+    return entry;
+}
+
+/*
+ * Find the first quickfix entry below line 'lnum' in buffer 'bnr'.
+ * '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'.
+ */
+    static qfline_T *
+qf_find_entry_on_next_line(
+	int		bnr,
+	linenr_T	lnum,
+	qfline_T	*qfp,
+	int		*errornr)
+{
+    if (qfp->qf_lnum > lnum)
+	// First entry is after line 'lnum'
+	return qfp;
+
+    // Find the entry just before or at the line 'lnum'
+    while (qfp->qf_next != NULL
+	    && qfp->qf_next->qf_fnum == bnr
+	    && qfp->qf_next->qf_lnum <= lnum)
+    {
+	qfp = qfp->qf_next;
+	++*errornr;
+    }
+
+    if (qfp->qf_next == NULL || qfp->qf_next->qf_fnum != bnr)
+	// No entries found after 'lnum'
+	return NULL;
+
+    // Use the entry just after line 'lnum'
+    qfp = qfp->qf_next;
+    ++*errornr;
+
+    return qfp;
+}
+
+/*
+ * Find the first quickfix entry before line 'lnum' in buffer 'bnr'.
+ * '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'.
+ */
+    static qfline_T *
+qf_find_entry_on_prev_line(
+	int		bnr,
+	linenr_T	lnum,
+	qfline_T	*qfp,
+	int		*errornr)
+{
+    // Find the entry just before the line 'lnum'
+    while (qfp->qf_next != NULL
+	    && qfp->qf_next->qf_fnum == bnr
+	    && qfp->qf_next->qf_lnum < lnum)
+    {
+	qfp = qfp->qf_next;
+	++*errornr;
+    }
+
+    if (qfp->qf_lnum >= lnum)	// entry is after 'lnum'
+	return NULL;
+
+    // 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
+ * the direction 'dir'.
+ */
+    static qfline_T *
+qf_find_closest_entry(
+	qf_list_T	*qfl,
+	int		bnr,
+	linenr_T	lnum,
+	int		dir,
+	int		*errornr)
+{
+    qfline_T	*qfp;
+
+    *errornr = 0;
+
+    // Find the first entry in this file
+    qfp = qf_find_first_entry_in_buf(qfl, bnr, errornr);
+    if (qfp == NULL)
+	return NULL;		// no entry in this file
+
+    if (dir == FORWARD)
+	qfp = qf_find_entry_on_next_line(bnr, lnum, qfp, errornr);
+    else
+	qfp = qf_find_entry_on_prev_line(bnr, lnum, 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.
+ */
+    static qfline_T *
+qf_get_nth_below_entry(qfline_T *entry, int *errornr, int n)
+{
+    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 (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;
+	    break;
+	}
+
+	entry = entry->qf_next;
+	++*errornr;
+    }
+
+    return entry;
+}
+
+/*
+ * Get the nth quickfix entry above the specified entry treating multiple
+ * entries on a single line as one. Searches backwards in the list.
+ */
+    static qfline_T *
+qf_get_nth_above_entry(qfline_T *entry, int *errornr, int n)
+{
+    while (n-- > 0 && !got_int)
+    {
+	if (entry->qf_prev == NULL
+		|| entry->qf_prev->qf_fnum != entry->qf_fnum)
+	    break;
+
+	entry = entry->qf_prev;
+	--*errornr;
+
+	// If multiple entries are on the same line, then use the first entry
+	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.
+ */
+    static int
+qf_find_nth_adj_entry(qf_list_T *qfl, int bnr, linenr_T lnum, int n, int dir)
+{
+    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);
+    if (adj_entry == NULL)
+	return 0;
+
+    if (--n > 0)
+    {
+	// Go to the n'th entry in the current buffer
+	if (dir == FORWARD)
+	    adj_entry = qf_get_nth_below_entry(adj_entry, &errornr, n);
+	else
+	    adj_entry = qf_get_nth_above_entry(adj_entry, &errornr, n);
+    }
+
+    return errornr;
+}
+
+/*
+ * Jump to a quickfix entry in the current file nearest to the current line.
+ * ":cabove", ":cbelow", ":labove" and ":lbelow" commands
+ */
+    void
+ex_cbelow(exarg_T *eap)
+{
+    qf_info_T	*qi;
+    qf_list_T	*qfl;
+    int		dir;
+    int		buf_has_flag;
+    int		errornr = 0;
+
+    if (eap->addr_count > 0 && eap->line2 <= 0)
+    {
+	emsg(_(e_invrange));
+	return;
+    }
+
+    // Check whether the current buffer has any quickfix entries
+    if (eap->cmdidx == CMD_cabove || eap->cmdidx == CMD_cbelow)
+	buf_has_flag = BUF_HAS_QF_ENTRY;
+    else
+	buf_has_flag = BUF_HAS_LL_ENTRY;
+    if (!(curbuf->b_has_qf_entry & buf_has_flag))
+    {
+	emsg(_(e_quickfix));
+	return;
+    }
+
+    if ((qi = qf_cmd_get_stack(eap, TRUE)) == NULL)
+	return;
+
+    qfl = qf_get_curlist(qi);
+    // check if the list has valid errors
+    if (!qf_list_has_valid_entries(qfl))
+    {
+	emsg(_(e_quickfix));
+	return;
+    }
+
+    if (eap->cmdidx == CMD_cbelow || eap->cmdidx == CMD_lbelow)
+	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);
+
+    if (errornr > 0)
+	qf_jump(qi, 0, errornr, FALSE);
+    else
+	emsg(_(e_no_more_items));
+}
+
+/*
  * Return the autocmd name for the :cfile Ex commands
  */
     static char_u *
--- a/src/testdir/test_quickfix.vim
+++ b/src/testdir/test_quickfix.vim
@@ -37,6 +37,8 @@ func s:setup_commands(cchar)
     command! -nargs=* Xgrepadd <mods> grepadd <args>
     command! -nargs=* Xhelpgrep helpgrep <args>
     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
     let g:Xgetlist = function('getqflist')
     let g:Xsetlist = function('setqflist')
     call setqflist([], 'f')
@@ -70,6 +72,8 @@ func s:setup_commands(cchar)
     command! -nargs=* Xgrepadd <mods> lgrepadd <args>
     command! -nargs=* Xhelpgrep lhelpgrep <args>
     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
     let g:Xgetlist = function('getloclist', [0])
     let g:Xsetlist = function('setloclist', [0])
     call setloclist(0, [], 'f')
@@ -4035,3 +4039,109 @@ func Test_empty_qfbuf()
   enew
   call delete('Xfile1')
 endfunc
+
+" Test for the :cbelow, :cabove, :lbelow and :labove 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:')
+
+  " Empty quickfix/location list
+  call g:Xsetlist([])
+  call assert_fails('Xbelow', 'E42:')
+  call assert_fails('Xabove', 'E42:')
+
+  call s:create_test_file('X1')
+  call s:create_test_file('X2')
+  call s:create_test_file('X3')
+  call s:create_test_file('X4')
+
+  " Invalid entries
+  edit X1
+  call g:Xsetlist(["E1", "E2"])
+  call assert_fails('Xbelow', 'E42:')
+  call assert_fails('Xabove', 'E42:')
+  call assert_fails('3Xbelow', 'E42:')
+  call assert_fails('4Xabove', 'E42:')
+
+  " Test the commands with various arguments
+  Xexpr ["X1:5:L5", "X2:5:L5", "X2:10:L10", "X2:15:L15", "X3:3:L3"]
+  edit +7 X2
+  Xabove
+  call assert_equal(['X2', 5], [bufname(''), line('.')])
+  call assert_fails('Xabove', 'E553:')
+  normal 2j
+  Xbelow
+  call assert_equal(['X2', 10], [bufname(''), line('.')])
+  " Last error in this file
+  Xbelow 99
+  call assert_equal(['X2', 15], [bufname(''), line('.')])
+  call assert_fails('Xbelow', 'E553:')
+  " First error in this file
+  Xabove 99
+  call assert_equal(['X2', 5], [bufname(''), line('.')])
+  call assert_fails('Xabove', 'E553:')
+  normal gg
+  Xbelow 2
+  call assert_equal(['X2', 10], [bufname(''), line('.')])
+  normal G
+  Xabove 2
+  call assert_equal(['X2', 10], [bufname(''), line('.')])
+  edit X4
+  call assert_fails('Xabove', 'E42:')
+  call assert_fails('Xbelow', '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:')
+    close
+  endif
+
+  " Test for lines with multiple quickfix entries
+  Xexpr ["X1:5:L5", "X2:5:1:L5_1", "X2:5:2:L5_2", "X2:5:3:L5_3",
+	      \ "X2:10:1:L10_1", "X2:10:2:L10_2", "X2:10:3:L10_3",
+	      \ "X2:15:1:L15_1", "X2:15:2:L15_2", "X2:15:3:L15_3", "X3:3:L3"]
+  edit +1 X2
+  Xbelow 2
+  call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')])
+  normal gg
+  Xbelow 99
+  call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')])
+  normal G
+  Xabove 2
+  call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')])
+  normal G
+  Xabove 99
+  call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')])
+  normal 10G
+  Xabove
+  call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')])
+  normal 10G
+  Xbelow
+  call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')])
+
+  " Invalid range
+  if a:cchar == 'c'
+    call assert_fails('-2cbelow', 'E553:')
+    " TODO: should go to first error in the current line?
+    0cabove
+  else
+    call assert_fails('-2lbelow', 'E553:')
+    " TODO: should go to first error in the current line?
+    0labove
+  endif
+
+  call delete('X1')
+  call delete('X2')
+  call delete('X3')
+  call delete('X4')
+endfunc
+
+func Test_cbelow()
+  call Xtest_below('c')
+  call Xtest_below('l')
+endfunc
--- 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 */
 /**/
+    1256,
+/**/
     1255,
 /**/
     1254,