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)
3ff33114d7
This commit is contained in:
Shane Smith 2019-09-13 22:16:12 -04:00
parent cf7c34dea1
commit a4d48d37c1
5 changed files with 457 additions and 4 deletions

View File

@ -1163,11 +1163,13 @@ tag command action ~
|:cNfile| :cNf[ile] go to last error in previous file |:cNfile| :cNf[ile] go to last error in previous file
|:cabbrev| :ca[bbrev] like ":abbreviate" but for Command-line mode |:cabbrev| :ca[bbrev] like ":abbreviate" but for Command-line mode
|:cabclear| :cabc[lear] clear all abbreviations 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 |:caddbuffer| :cad[dbuffer] add errors from buffer
|:caddexpr| :cadde[xpr] add errors from expr |:caddexpr| :cadde[xpr] add errors from expr
|:caddfile| :caddf[ile] add error message to current quickfix list |:caddfile| :caddf[ile] add error message to current quickfix list
|:call| :cal[l] call a function |:call| :cal[l] call a function
|:catch| :cat[ch] part of a :try command |: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 |:cbottom| :cbo[ttom] scroll to the bottom of the quickfix window
|:cbuffer| :cb[uffer] parse error messages and jump to first error |:cbuffer| :cb[uffer] parse error messages and jump to first error
|:cc| :cc go to specific error |:cc| :cc go to specific error
@ -1324,12 +1326,14 @@ tag command action ~
|:lNext| :lN[ext] go to previous entry in location list |:lNext| :lN[ext] go to previous entry in location list
|:lNfile| :lNf[ile] go to last entry in previous file |:lNfile| :lNf[ile] go to last entry in previous file
|:list| :l[ist] print lines |:list| :l[ist] print lines
|:labove| :lab[ove] go to location above current line
|:laddexpr| :lad[dexpr] add locations from expr |:laddexpr| :lad[dexpr] add locations from expr
|:laddbuffer| :laddb[uffer] add locations from buffer |:laddbuffer| :laddb[uffer] add locations from buffer
|:laddfile| :laddf[ile] add locations to current location list |:laddfile| :laddf[ile] add locations to current location list
|:last| :la[st] go to the last file in the argument list |:last| :la[st] go to the last file in the argument list
|:language| :lan[guage] set the language (locale) |:language| :lan[guage] set the language (locale)
|:later| :lat[er] go to newer change, redo |: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 |:lbottom| :lbo[ttom] scroll to the bottom of the location window
|:lbuffer| :lb[uffer] parse locations and jump to first location |:lbuffer| :lb[uffer] parse locations and jump to first location
|:lcd| :lc[d] change directory locally |:lcd| :lc[d] change directory locally

View File

@ -109,6 +109,36 @@ processing a quickfix or location list command, it will be aborted.
list for the current window is used instead of the list for the current window is used instead of the
quickfix list. 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* *:cnf* *:cnfile*
:[count]cnf[ile][!] Display the first error in the [count] next file in :[count]cnf[ile][!] Display the first error in the [count] next file in
the list that includes a file name. If there are no the list that includes a file name. If there are no

View File

@ -322,6 +322,12 @@ return {
addr_type=ADDR_LINES, addr_type=ADDR_LINES,
func='ex_abclear', func='ex_abclear',
}, },
{
command='cabove',
flags=bit.bor(RANGE, TRLBAR),
addr_type=ADDR_OTHER ,
func='ex_cbelow',
},
{ {
command='caddbuffer', command='caddbuffer',
flags=bit.bor(RANGE, NOTADR, WORD1, TRLBAR), flags=bit.bor(RANGE, NOTADR, WORD1, TRLBAR),
@ -358,6 +364,12 @@ return {
addr_type=ADDR_LINES, addr_type=ADDR_LINES,
func='ex_cbuffer', func='ex_cbuffer',
}, },
{
command='cbelow',
flags=bit.bor(RANGE, TRLBAR),
addr_type=ADDR_OTHER ,
func='ex_cbelow',
},
{ {
command='cbottom', command='cbottom',
flags=bit.bor(TRLBAR), flags=bit.bor(TRLBAR),
@ -1272,6 +1284,12 @@ return {
addr_type=ADDR_LINES, addr_type=ADDR_LINES,
func='ex_last', func='ex_last',
}, },
{
command='labove',
flags=bit.bor(RANGE, TRLBAR),
addr_type=ADDR_OTHER ,
func='ex_cbelow',
},
{ {
command='language', command='language',
flags=bit.bor(EXTRA, TRLBAR, CMDWIN), flags=bit.bor(EXTRA, TRLBAR, CMDWIN),
@ -1308,6 +1326,12 @@ return {
addr_type=ADDR_LINES, addr_type=ADDR_LINES,
func='ex_cbuffer', func='ex_cbuffer',
}, },
{
command='lbelow',
flags=bit.bor(RANGE, TRLBAR),
addr_type=ADDR_OTHER ,
func='ex_cbelow',
},
{ {
command='lbottom', command='lbottom',
flags=bit.bor(TRLBAR), flags=bit.bor(TRLBAR),

View File

@ -215,7 +215,10 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "quickfix.c.generated.h" # include "quickfix.c.generated.h"
#endif #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) #define IS_QF_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref == NULL)
/* Location list window check helper macro */ /* Location list window check helper macro */
#define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL) #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; 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 // Return a pointer to a list in the specified quickfix stack
static qf_list_T * qf_get_list(qf_info_T *qi, int idx) 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; qfline_T *prev_qf_ptr;
int prev_index; int prev_index;
static char_u *e_no_more_items = (char_u *)N_("E553: No more items");
char_u *err = e_no_more_items; char_u *err = e_no_more_items;
while (errornr--) { while (errornr--) {
@ -4240,7 +4249,7 @@ int qf_get_cur_valid_idx(exarg_T *eap)
qf_list_T *qfl = qf_get_curlist(qi); qf_list_T *qfl = qf_get_curlist(qi);
// Check if the list has valid errors. // Check if the list has valid errors.
if (qfl->qf_count <= 0 || qfl->qf_nonevalid) { if (!qf_list_has_valid_entries(qfl)) {
return 1; 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 FUNC_ATTR_NONNULL_ALL
{ {
// Check if the list has valid errors. // Check if the list has valid errors.
if (qfl->qf_count <= 0 || qfl->qf_nonevalid) { if (!qf_list_has_valid_entries(qfl)) {
return 1; return 1;
} }
@ -4419,6 +4428,282 @@ void ex_cnext(exarg_T *eap)
qf_jump(qi, dir, errornr, eap->forceit); 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 // Return the autocmd name for the :cfile Ex commands
static char_u * cfile_get_auname(cmdidx_T cmdidx) static char_u * cfile_get_auname(cmdidx_T cmdidx)
{ {

View File

@ -37,6 +37,8 @@ func s:setup_commands(cchar)
command! -nargs=* Xgrepadd <mods> grepadd <args> command! -nargs=* Xgrepadd <mods> grepadd <args>
command! -nargs=* Xhelpgrep helpgrep <args> command! -nargs=* Xhelpgrep helpgrep <args>
command! -nargs=0 -count Xcc <count>cc 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:Xgetlist = function('getqflist')
let g:Xsetlist = function('setqflist') let g:Xsetlist = function('setqflist')
call setqflist([], 'f') call setqflist([], 'f')
@ -70,6 +72,8 @@ func s:setup_commands(cchar)
command! -nargs=* Xgrepadd <mods> lgrepadd <args> command! -nargs=* Xgrepadd <mods> lgrepadd <args>
command! -nargs=* Xhelpgrep lhelpgrep <args> command! -nargs=* Xhelpgrep lhelpgrep <args>
command! -nargs=0 -count Xcc <count>ll 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:Xgetlist = function('getloclist', [0])
let g:Xsetlist = function('setloclist', [0]) let g:Xsetlist = function('setloclist', [0])
call setloclist(0, [], 'f') call setloclist(0, [], 'f')
@ -3817,4 +3821,110 @@ func Test_viscol()
call delete('Xfile1') call delete('Xfile1')
endfunc 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 " vim: shiftwidth=2 sts=2 expandtab