Skip to content

Commit 27fbf6e

Browse files
jflychrisbra
authored andcommitted
patch 9.1.0785: cannot preserve error position when setting quickfix list
Problem: cannot preserve error position when setting quickfix lists Solution: Add the 'u' action for setqflist()/setloclist() and try to keep the closes target position (Jeremy Fleischman) fixes: #15839 closes: #15841 Signed-off-by: Jeremy Fleischman <jeremyfleischman@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
1 parent 83a0670 commit 27fbf6e

5 files changed

Lines changed: 214 additions & 17 deletions

File tree

runtime/doc/builtin.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*builtin.txt* For Vim version 9.1. Last change: 2024 Oct 12
1+
*builtin.txt* For Vim version 9.1. Last change: 2024 Oct 14
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -9766,6 +9766,8 @@ setqflist({list} [, {action} [, {what}]]) *setqflist()*
97669766
clear the list: >
97679767
:call setqflist([], 'r')
97689768
<
9769+
'u' Like 'r', but tries to preserve the current selection
9770+
in the quickfix list.
97699771
'f' All the quickfix lists in the quickfix stack are
97709772
freed.
97719773

runtime/doc/version9.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*version9.txt* For Vim version 9.1. Last change: 2024 Oct 08
1+
*version9.txt* For Vim version 9.1. Last change: 2024 Oct 14
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -41598,6 +41598,8 @@ Changed~
4159841598
- the regex engines match correctly case-insensitive multi-byte characters
4159941599
(and apply proper case folding)
4160041600
- |:keeppatterns| preserves the last substitute pattern when used with |:s|
41601+
- |setqflist()| and |setloclist()| can optionally try to preserve the current
41602+
selection in the quickfix list with the "u" action.
4160141603

4160241604
*added-9.2*
4160341605
Added ~

src/quickfix.c

Lines changed: 115 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ static buf_T *load_dummy_buffer(char_u *fname, char_u *dirname_start, char_u *re
190190
static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start);
191191
static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start);
192192
static qf_info_T *ll_get_or_alloc_list(win_T *);
193+
static int entry_is_closer_to_target(qfline_T *entry, qfline_T *other_entry, int target_fnum, int target_lnum, int target_col);
193194

194195
// Quickfix window check helper macro
195196
#define IS_QF_WINDOW(wp) (bt_quickfix((wp)->w_buffer) && (wp)->w_llist_ref == NULL)
@@ -7499,6 +7500,62 @@ qf_add_entry_from_dict(
74997500
return status;
75007501
}
75017502

7503+
/*
7504+
* Check if `entry` is closer to the target than `other_entry`.
7505+
*
7506+
* Only returns TRUE if `entry` is definitively closer. If it's further
7507+
* away, or there's not enough information to tell, return FALSE.
7508+
*/
7509+
static int
7510+
entry_is_closer_to_target(
7511+
qfline_T *entry,
7512+
qfline_T *other_entry,
7513+
int target_fnum,
7514+
int target_lnum,
7515+
int target_col)
7516+
{
7517+
// First, compare entries to target file.
7518+
if (!target_fnum)
7519+
// Without a target file, we can't know which is closer.
7520+
return FALSE;
7521+
7522+
int is_target_file = entry->qf_fnum && entry->qf_fnum == target_fnum;
7523+
int other_is_target_file = other_entry->qf_fnum && other_entry->qf_fnum == target_fnum;
7524+
if (!is_target_file && other_is_target_file)
7525+
return FALSE;
7526+
else if (is_target_file && !other_is_target_file)
7527+
return TRUE;
7528+
7529+
// Both entries are pointing at the exact same file. Now compare line
7530+
// numbers.
7531+
if (!target_lnum)
7532+
// Without a target line number, we can't know which is closer.
7533+
return FALSE;
7534+
7535+
int line_distance = entry->qf_lnum ? labs(entry->qf_lnum - target_lnum) : INT_MAX;
7536+
int other_line_distance = other_entry->qf_lnum ? labs(other_entry->qf_lnum - target_lnum) : INT_MAX;
7537+
if (line_distance > other_line_distance)
7538+
return FALSE;
7539+
else if (line_distance < other_line_distance)
7540+
return TRUE;
7541+
7542+
// Both entries are pointing at the exact same line number (or no line
7543+
// number at all). Now compare columns.
7544+
if (!target_col)
7545+
// Without a target column, we can't know which is closer.
7546+
return FALSE;
7547+
7548+
int column_distance = entry->qf_col ? abs(entry->qf_col - target_col) : INT_MAX;
7549+
int other_column_distance = other_entry->qf_col ? abs(other_entry->qf_col - target_col): INT_MAX;
7550+
if (column_distance > other_column_distance)
7551+
return FALSE;
7552+
else if (column_distance < other_column_distance)
7553+
return TRUE;
7554+
7555+
// It's a complete tie! The exact same file, line, and column.
7556+
return FALSE;
7557+
}
7558+
75027559
/*
75037560
* Add list of entries to quickfix/location list. Each list entry is
75047561
* a dictionary with item information.
@@ -7518,21 +7575,54 @@ qf_add_entries(
75187575
int retval = OK;
75197576
int valid_entry = FALSE;
75207577

7578+
// If there's an entry selected in the quickfix list, remember its location
7579+
// (file, line, column), so we can select the nearest entry in the updated
7580+
// quickfix list.
7581+
int prev_fnum = 0;
7582+
int prev_lnum = 0;
7583+
int prev_col = 0;
7584+
if (qfl->qf_ptr)
7585+
{
7586+
prev_fnum = qfl->qf_ptr->qf_fnum;
7587+
prev_lnum = qfl->qf_ptr->qf_lnum;
7588+
prev_col = qfl->qf_ptr->qf_col;
7589+
}
7590+
7591+
int select_first_entry = FALSE;
7592+
int select_nearest_entry = FALSE;
7593+
75217594
if (action == ' ' || qf_idx == qi->qf_listcount)
75227595
{
7596+
select_first_entry = TRUE;
75237597
// make place for a new list
75247598
qf_new_list(qi, title);
75257599
qf_idx = qi->qf_curlist;
75267600
qfl = qf_get_list(qi, qf_idx);
75277601
}
7528-
else if (action == 'a' && !qf_list_empty(qfl))
7529-
// Adding to existing list, use last entry.
7530-
old_last = qfl->qf_last;
7602+
else if (action == 'a')
7603+
{
7604+
if (qf_list_empty(qfl))
7605+
// Appending to empty list, select first entry.
7606+
select_first_entry = TRUE;
7607+
else
7608+
// Adding to existing list, use last entry.
7609+
old_last = qfl->qf_last;
7610+
}
75317611
else if (action == 'r')
75327612
{
7613+
select_first_entry = TRUE;
75337614
qf_free_items(qfl);
75347615
qf_store_title(qfl, title);
75357616
}
7617+
else if (action == 'u')
7618+
{
7619+
select_nearest_entry = TRUE;
7620+
qf_free_items(qfl);
7621+
qf_store_title(qfl, title);
7622+
}
7623+
7624+
qfline_T *entry_to_select = NULL;
7625+
int entry_to_select_index = 0;
75367626

75377627
FOR_ALL_LIST_ITEMS(list, li)
75387628
{
@@ -7547,6 +7637,18 @@ qf_add_entries(
75477637
&valid_entry);
75487638
if (retval == QF_FAIL)
75497639
break;
7640+
7641+
qfline_T *entry = qfl->qf_last;
7642+
if (
7643+
(select_first_entry && entry_to_select == NULL) ||
7644+
(select_nearest_entry &&
7645+
(entry_to_select == NULL ||
7646+
entry_is_closer_to_target(entry, entry_to_select, prev_fnum,
7647+
prev_lnum, prev_col))))
7648+
{
7649+
entry_to_select = entry;
7650+
entry_to_select_index = qfl->qf_count;
7651+
}
75507652
}
75517653

75527654
// Check if any valid error entries are added to the list.
@@ -7556,14 +7658,12 @@ qf_add_entries(
75567658
// no valid entry
75577659
qfl->qf_nonevalid = TRUE;
75587660

7559-
// If not appending to the list, set the current error to the first entry
7560-
if (action != 'a')
7561-
qfl->qf_ptr = qfl->qf_start;
7562-
7563-
// Update the current error index if not appending to the list or if the
7564-
// list was empty before and it is not empty now.
7565-
if ((action != 'a' || qfl->qf_index == 0) && !qf_list_empty(qfl))
7566-
qfl->qf_index = 1;
7661+
// Set the current error.
7662+
if (entry_to_select)
7663+
{
7664+
qfl->qf_ptr = entry_to_select;
7665+
qfl->qf_index = entry_to_select_index;
7666+
}
75677667

75687668
// Don't update the cursor in quickfix window when appending entries
75697669
qf_update_buffer(qi, old_last);
@@ -7700,7 +7800,7 @@ qf_setprop_items_from_lines(
77007800
if (di->di_tv.v_type != VAR_LIST || di->di_tv.vval.v_list == NULL)
77017801
return FAIL;
77027802

7703-
if (action == 'r')
7803+
if (action == 'r' || action == 'u')
77047804
qf_free_items(&qi->qf_lists[qf_idx]);
77057805
if (qf_init_ext(qi, qf_idx, NULL, NULL, &di->di_tv, errorformat,
77067806
FALSE, (linenr_T)0, (linenr_T)0, NULL, NULL) >= 0)
@@ -7897,8 +7997,8 @@ qf_free_stack(win_T *wp, qf_info_T *qi)
78977997
/*
78987998
* Populate the quickfix list with the items supplied in the list
78997999
* of dictionaries. "title" will be copied to w:quickfix_title.
7900-
* "action" is 'a' for add, 'r' for replace. Otherwise create a new list.
7901-
* When "what" is not NULL then only set some properties.
8000+
* "action" is 'a' for add, 'r' for replace, 'u' for update. Otherwise
8001+
* create a new list. When "what" is not NULL then only set some properties.
79028002
*/
79038003
int
79048004
set_errorlist(
@@ -8740,7 +8840,7 @@ set_qf_ll_list(
87408840
act = tv_get_string_chk(action_arg);
87418841
if (act == NULL)
87428842
return; // type error; errmsg already given
8743-
if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') &&
8843+
if ((*act == 'a' || *act == 'r' || *act == 'u' || *act == ' ' || *act == 'f') &&
87448844
act[1] == NUL)
87458845
action = *act;
87468846
else

src/testdir/test_quickfix.vim

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6462,6 +6462,97 @@ func Test_quickfix_buffer_contents()
64626462
call setqflist([], 'f')
64636463
endfunc
64646464

6465+
func Test_quickfix_update()
6466+
" Setup: populate a couple buffers
6467+
new
6468+
call setline(1, range(1, 5))
6469+
let b1 = bufnr()
6470+
new
6471+
call setline(1, range(1, 3))
6472+
let b2 = bufnr()
6473+
" Setup: set a quickfix list.
6474+
let items = [{'bufnr': b1, 'lnum': 1}, {'bufnr': b1, 'lnum': 2}, {'bufnr': b2, 'lnum': 1}, {'bufnr': b2, 'lnum': 2}]
6475+
call setqflist(items)
6476+
6477+
" Open the quickfix list, select the third entry.
6478+
copen
6479+
exe "normal jj\<CR>"
6480+
call assert_equal(3, getqflist({'idx' : 0}).idx)
6481+
6482+
" Update the quickfix list. Make sure the third entry is still selected.
6483+
call setqflist([], 'u', { 'items': items })
6484+
call assert_equal(3, getqflist({'idx' : 0}).idx)
6485+
6486+
" Update the quickfix list again, but this time with missing line number
6487+
" information. Confirm that we keep the current buffer selected.
6488+
call setqflist([{'bufnr': b1}, {'bufnr': b2}], 'u')
6489+
call assert_equal(2, getqflist({'idx' : 0}).idx)
6490+
6491+
" Cleanup the buffers we allocated during this test.
6492+
%bwipe!
6493+
%bwipe!
6494+
endfunc
6495+
6496+
func Test_quickfix_update_with_missing_coordinate_info()
6497+
new
6498+
call setline(1, range(1, 5))
6499+
let b1 = bufnr()
6500+
6501+
new
6502+
call setline(1, range(1, 3))
6503+
let b2 = bufnr()
6504+
6505+
new
6506+
call setline(1, range(1, 2))
6507+
let b3 = bufnr()
6508+
6509+
" Setup: set a quickfix list with no coordinate information at all.
6510+
call setqflist([{}, {}])
6511+
6512+
" Open the quickfix list, select the second entry.
6513+
copen
6514+
exe "normal j\<CR>"
6515+
call assert_equal(2, getqflist({'idx' : 0}).idx)
6516+
6517+
" Update the quickfix list. As the previously selected entry has no
6518+
" coordinate information, we expect the first entry to now be selected.
6519+
call setqflist([{'bufnr': b1}, {'bufnr': b2}, {'bufnr': b3}], 'u')
6520+
call assert_equal(1, getqflist({'idx' : 0}).idx)
6521+
6522+
" Select the second entry in the quickfix list.
6523+
copen
6524+
exe "normal j\<CR>"
6525+
call assert_equal(2, getqflist({'idx' : 0}).idx)
6526+
6527+
" Update the quickfix list again. The currently selected entry does not have
6528+
" a line number, but we should keep the file selected.
6529+
call setqflist([{'bufnr': b1}, {'bufnr': b2, 'lnum': 3}, {'bufnr': b3}], 'u')
6530+
call assert_equal(2, getqflist({'idx' : 0}).idx)
6531+
6532+
" Update the quickfix list again. The currently selected entry (bufnr=b2, lnum=3)
6533+
" is no longer present. We should pick the nearest entry.
6534+
call setqflist([{'bufnr': b1}, {'bufnr': b2, 'lnum': 1}, {'bufnr': b2, 'lnum': 4}], 'u')
6535+
call assert_equal(3, getqflist({'idx' : 0}).idx)
6536+
6537+
" Set the quickfix list again, with a specific column number. The currently selected entry doesn't have a
6538+
" column number, but they share a line number.
6539+
call setqflist([{'bufnr': b1}, {'bufnr': b2, 'lnum': 4, 'col': 5}, {'bufnr': b2, 'lnum': 4, 'col': 6}], 'u')
6540+
call assert_equal(2, getqflist({'idx' : 0}).idx)
6541+
6542+
" Set the quickfix list again. The currently selected column number (6) is
6543+
" no longer present. We should select the nearest column number.
6544+
call setqflist([{'bufnr': b1}, {'bufnr': b2, 'lnum': 4, 'col': 2}, {'bufnr': b2, 'lnum': 4, 'col': 4}], 'u')
6545+
call assert_equal(3, getqflist({'idx' : 0}).idx)
6546+
6547+
" Now set the quickfix list, but without columns. We should still pick the
6548+
" same line.
6549+
call setqflist([{'bufnr': b2, 'lnum': 3}, {'bufnr': b2, 'lnum': 4}, {'bufnr': b2, 'lnum': 4}], 'u')
6550+
call assert_equal(2, getqflist({'idx' : 0}).idx)
6551+
6552+
" Cleanup the buffers we allocated during this test.
6553+
%bwipe!
6554+
endfunc
6555+
64656556
" Test for "%b" in "errorformat"
64666557
func Test_efm_format_b()
64676558
call setqflist([], 'f')

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,8 @@ static char *(features[]) =
704704

705705
static int included_patches[] =
706706
{ /* Add new patch number below this line */
707+
/**/
708+
785,
707709
/**/
708710
784,
709711
/**/

0 commit comments

Comments
 (0)