aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorShane Smith <shane.wm.smith@gmail.com>2019-09-13 22:16:12 -0400
committerShane Smith <shane.wm.smith@gmail.com>2019-10-27 23:43:47 -0400
commita4d48d37c15604a96be13eb4deaedbb132be6e10 (patch)
tree4314b7193fd93ebdc37e7c15af494f4b45a657d5 /src
parentcf7c34dea1080db403464f7b7fd50341597fd42c (diff)
downloadrneovim-a4d48d37c15604a96be13eb4deaedbb132be6e10.tar.gz
rneovim-a4d48d37c15604a96be13eb4deaedbb132be6e10.tar.bz2
rneovim-a4d48d37c15604a96be13eb4deaedbb132be6e10.zip
vim-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 vim/vim#4316) https://github.com/vim/vim/commit/3ff33114d70fc0f7e9c3187c5fec9028f6499cf3
Diffstat (limited to 'src')
-rw-r--r--src/nvim/ex_cmds.lua24
-rw-r--r--src/nvim/quickfix.c293
-rw-r--r--src/nvim/testdir/test_quickfix.vim110
3 files changed, 423 insertions, 4 deletions
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 6317ec77ff..f7aa8a994a 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -323,6 +323,12 @@ return {
func='ex_abclear',
},
{
+ command='cabove',
+ flags=bit.bor(RANGE, TRLBAR),
+ addr_type=ADDR_OTHER ,
+ func='ex_cbelow',
+ },
+ {
command='caddbuffer',
flags=bit.bor(RANGE, NOTADR, WORD1, TRLBAR),
addr_type=ADDR_LINES,
@@ -359,6 +365,12 @@ return {
func='ex_cbuffer',
},
{
+ command='cbelow',
+ flags=bit.bor(RANGE, TRLBAR),
+ addr_type=ADDR_OTHER ,
+ func='ex_cbelow',
+ },
+ {
command='cbottom',
flags=bit.bor(TRLBAR),
addr_type=ADDR_LINES,
@@ -1273,6 +1285,12 @@ return {
func='ex_last',
},
{
+ command='labove',
+ flags=bit.bor(RANGE, TRLBAR),
+ addr_type=ADDR_OTHER ,
+ func='ex_cbelow',
+ },
+ {
command='language',
flags=bit.bor(EXTRA, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
@@ -1309,6 +1327,12 @@ return {
func='ex_cbuffer',
},
{
+ command='lbelow',
+ flags=bit.bor(RANGE, TRLBAR),
+ addr_type=ADDR_OTHER ,
+ func='ex_cbelow',
+ },
+ {
command='lbottom',
flags=bit.bor(TRLBAR),
addr_type=ADDR_LINES,
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 731817a2b4..d6cc9d83fe 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -215,7 +215,10 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "quickfix.c.generated.h"
#endif
-/* Quickfix window check helper macro */
+
+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)
/* Location list window check helper macro */
#define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)
@@ -898,6 +901,13 @@ static bool qf_list_empty(qf_list_T *qfl)
return qfl == NULL || qfl->qf_count <= 0;
}
+// 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 * qf_get_list(qf_info_T *qi, int idx)
{
@@ -2370,7 +2380,6 @@ static qfline_T *get_nth_valid_entry(qf_list_T *qfl, int errornr,
{
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--) {
@@ -4240,7 +4249,7 @@ int qf_get_cur_valid_idx(exarg_T *eap)
qf_list_T *qfl = qf_get_curlist(qi);
// Check if the list has valid errors.
- if (qfl->qf_count <= 0 || qfl->qf_nonevalid) {
+ if (!qf_list_has_valid_entries(qfl)) {
return 1;
}
@@ -4279,7 +4288,7 @@ static size_t qf_get_nth_valid_entry(qf_list_T *qfl, size_t n, int fdo)
FUNC_ATTR_NONNULL_ALL
{
// Check if the list has valid errors.
- if (qfl->qf_count <= 0 || qfl->qf_nonevalid) {
+ if (!qf_list_has_valid_entries(qfl)) {
return 1;
}
@@ -4419,6 +4428,282 @@ void ex_cnext(exarg_T *eap)
qf_jump(qi, dir, errornr, eap->forceit);
}
+// 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,
+ linenr_T 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,
+ linenr_T 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,
+ linenr_T 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 * cfile_get_auname(cmdidx_T cmdidx)
{
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 131e9d72e3..77fba0b9af 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/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')
@@ -3817,4 +3821,110 @@ func Test_viscol()
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
+
" vim: shiftwidth=2 sts=2 expandtab