diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/ex_cmds.lua | 24 | ||||
-rw-r--r-- | src/nvim/quickfix.c | 293 | ||||
-rw-r--r-- | src/nvim/testdir/test_quickfix.vim | 110 |
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 |