summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2019-05-03 21:56:35 +0200
committerBram Moolenaar <Bram@vim.org>2019-05-03 21:56:35 +0200
commit3ff33114d70fc0f7e9c3187c5fec9028f6499cf3 (patch)
tree62a9dedf831bf246b93c1a8c3715669c77f6040a
parent12e91862c14a1af44b537d478e8a5021893044fe (diff)
downloadvim-git-3ff33114d70fc0f7e9c3187c5fec9028f6499cf3.tar.gz
patch 8.1.1256: cannot navigate through errors relative to the cursorv8.1.1256
Problem: Cannot navigate through errors relative to the cursor. Solution: Add :cabove, :cbelow, :labove and :lbelow. (Yegappan Lakshmanan, closes #4316)
-rw-r--r--runtime/doc/index.txt4
-rw-r--r--runtime/doc/quickfix.txt30
-rw-r--r--src/ex_cmdidxs.h52
-rw-r--r--src/ex_cmds.h12
-rw-r--r--src/ex_docmd.c1
-rw-r--r--src/proto/quickfix.pro1
-rw-r--r--src/quickfix.c311
-rw-r--r--src/testdir/test_quickfix.vim110
-rw-r--r--src/version.c2
9 files changed, 494 insertions, 29 deletions
diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index c95de5b7b..719290556 100644
--- 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
diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index 0ceb231b8..6f17b77d4 100644
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -123,6 +123,36 @@ processing a quickfix or location list command, it will be aborted.
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
diff --git a/src/ex_cmdidxs.h b/src/ex_cmdidxs.h
index 1531e68c1..c63aa46f5 100644
--- 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][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][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][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;
diff --git a/src/ex_cmds.h b/src/ex_cmds.h
index 63196afd3..73914725a 100644
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -252,6 +252,9 @@ EX(CMD_cabbrev, "cabbrev", ex_abbreviate,
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),
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index b317f53bf..db9bd0698 100644
--- 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
diff --git a/src/proto/quickfix.pro b/src/proto/quickfix.pro
index c8d5956fb..1ae339e2f 100644
--- 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);
diff --git a/src/quickfix.c b/src/quickfix.c
index f90934387..e8c782961 100644
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -177,6 +177,7 @@ static buf_T *load_dummy_buffer(char_u *fname, char_u *dirname_start, char_u *re
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, int n, int fdo)
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 *
diff --git a/src/testdir/test_quickfix.vim b/src/testdir/test_quickfix.vim
index ccd7778e4..ee98502a7 100644
--- 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
diff --git a/src/version.c b/src/version.c
index f03346c55..35ef1ae7c 100644
--- 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,