0
0
mirror of https://github.com/vim/vim.git synced 2025-08-28 20:23:48 -04:00

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>
This commit is contained in:
Jeremy Fleischman 2024-10-14 20:46:27 +02:00 committed by Christian Brabandt
parent 83a06705dc
commit 27fbf6e5e8
No known key found for this signature in database
GPG Key ID: F3F92DA383FDDE09
5 changed files with 214 additions and 17 deletions

View File

@ -1,4 +1,4 @@
*builtin.txt* For Vim version 9.1. Last change: 2024 Oct 12 *builtin.txt* For Vim version 9.1. Last change: 2024 Oct 14
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -9766,6 +9766,8 @@ setqflist({list} [, {action} [, {what}]]) *setqflist()*
clear the list: > clear the list: >
:call setqflist([], 'r') :call setqflist([], 'r')
< <
'u' Like 'r', but tries to preserve the current selection
in the quickfix list.
'f' All the quickfix lists in the quickfix stack are 'f' All the quickfix lists in the quickfix stack are
freed. freed.

View File

@ -1,4 +1,4 @@
*version9.txt* For Vim version 9.1. Last change: 2024 Oct 08 *version9.txt* For Vim version 9.1. Last change: 2024 Oct 14
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -41598,6 +41598,8 @@ Changed~
- the regex engines match correctly case-insensitive multi-byte characters - the regex engines match correctly case-insensitive multi-byte characters
(and apply proper case folding) (and apply proper case folding)
- |:keeppatterns| preserves the last substitute pattern when used with |:s| - |:keeppatterns| preserves the last substitute pattern when used with |:s|
- |setqflist()| and |setloclist()| can optionally try to preserve the current
selection in the quickfix list with the "u" action.
*added-9.2* *added-9.2*
Added ~ Added ~

View File

@ -190,6 +190,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 wipe_dummy_buffer(buf_T *buf, char_u *dirname_start);
static void unload_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 qf_info_T *ll_get_or_alloc_list(win_T *);
static int entry_is_closer_to_target(qfline_T *entry, qfline_T *other_entry, int target_fnum, int target_lnum, int target_col);
// Quickfix window check helper macro // 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)
@ -7499,6 +7500,62 @@ qf_add_entry_from_dict(
return status; return status;
} }
/*
* Check if `entry` is closer to the target than `other_entry`.
*
* Only returns TRUE if `entry` is definitively closer. If it's further
* away, or there's not enough information to tell, return FALSE.
*/
static int
entry_is_closer_to_target(
qfline_T *entry,
qfline_T *other_entry,
int target_fnum,
int target_lnum,
int target_col)
{
// First, compare entries to target file.
if (!target_fnum)
// Without a target file, we can't know which is closer.
return FALSE;
int is_target_file = entry->qf_fnum && entry->qf_fnum == target_fnum;
int other_is_target_file = other_entry->qf_fnum && other_entry->qf_fnum == target_fnum;
if (!is_target_file && other_is_target_file)
return FALSE;
else if (is_target_file && !other_is_target_file)
return TRUE;
// Both entries are pointing at the exact same file. Now compare line
// numbers.
if (!target_lnum)
// Without a target line number, we can't know which is closer.
return FALSE;
int line_distance = entry->qf_lnum ? labs(entry->qf_lnum - target_lnum) : INT_MAX;
int other_line_distance = other_entry->qf_lnum ? labs(other_entry->qf_lnum - target_lnum) : INT_MAX;
if (line_distance > other_line_distance)
return FALSE;
else if (line_distance < other_line_distance)
return TRUE;
// Both entries are pointing at the exact same line number (or no line
// number at all). Now compare columns.
if (!target_col)
// Without a target column, we can't know which is closer.
return FALSE;
int column_distance = entry->qf_col ? abs(entry->qf_col - target_col) : INT_MAX;
int other_column_distance = other_entry->qf_col ? abs(other_entry->qf_col - target_col): INT_MAX;
if (column_distance > other_column_distance)
return FALSE;
else if (column_distance < other_column_distance)
return TRUE;
// It's a complete tie! The exact same file, line, and column.
return FALSE;
}
/* /*
* Add list of entries to quickfix/location list. Each list entry is * Add list of entries to quickfix/location list. Each list entry is
* a dictionary with item information. * a dictionary with item information.
@ -7518,21 +7575,54 @@ qf_add_entries(
int retval = OK; int retval = OK;
int valid_entry = FALSE; int valid_entry = FALSE;
// If there's an entry selected in the quickfix list, remember its location
// (file, line, column), so we can select the nearest entry in the updated
// quickfix list.
int prev_fnum = 0;
int prev_lnum = 0;
int prev_col = 0;
if (qfl->qf_ptr)
{
prev_fnum = qfl->qf_ptr->qf_fnum;
prev_lnum = qfl->qf_ptr->qf_lnum;
prev_col = qfl->qf_ptr->qf_col;
}
int select_first_entry = FALSE;
int select_nearest_entry = FALSE;
if (action == ' ' || qf_idx == qi->qf_listcount) if (action == ' ' || qf_idx == qi->qf_listcount)
{ {
select_first_entry = TRUE;
// make place for a new list // make place for a new list
qf_new_list(qi, title); qf_new_list(qi, title);
qf_idx = qi->qf_curlist; qf_idx = qi->qf_curlist;
qfl = qf_get_list(qi, qf_idx); qfl = qf_get_list(qi, qf_idx);
} }
else if (action == 'a' && !qf_list_empty(qfl)) else if (action == 'a')
{
if (qf_list_empty(qfl))
// Appending to empty list, select first entry.
select_first_entry = TRUE;
else
// Adding to existing list, use last entry. // Adding to existing list, use last entry.
old_last = qfl->qf_last; old_last = qfl->qf_last;
}
else if (action == 'r') else if (action == 'r')
{ {
select_first_entry = TRUE;
qf_free_items(qfl); qf_free_items(qfl);
qf_store_title(qfl, title); qf_store_title(qfl, title);
} }
else if (action == 'u')
{
select_nearest_entry = TRUE;
qf_free_items(qfl);
qf_store_title(qfl, title);
}
qfline_T *entry_to_select = NULL;
int entry_to_select_index = 0;
FOR_ALL_LIST_ITEMS(list, li) FOR_ALL_LIST_ITEMS(list, li)
{ {
@ -7547,6 +7637,18 @@ qf_add_entries(
&valid_entry); &valid_entry);
if (retval == QF_FAIL) if (retval == QF_FAIL)
break; break;
qfline_T *entry = qfl->qf_last;
if (
(select_first_entry && entry_to_select == NULL) ||
(select_nearest_entry &&
(entry_to_select == NULL ||
entry_is_closer_to_target(entry, entry_to_select, prev_fnum,
prev_lnum, prev_col))))
{
entry_to_select = entry;
entry_to_select_index = qfl->qf_count;
}
} }
// Check if any valid error entries are added to the list. // Check if any valid error entries are added to the list.
@ -7556,14 +7658,12 @@ qf_add_entries(
// no valid entry // no valid entry
qfl->qf_nonevalid = TRUE; qfl->qf_nonevalid = TRUE;
// If not appending to the list, set the current error to the first entry // Set the current error.
if (action != 'a') if (entry_to_select)
qfl->qf_ptr = qfl->qf_start; {
qfl->qf_ptr = entry_to_select;
// Update the current error index if not appending to the list or if the qfl->qf_index = entry_to_select_index;
// list was empty before and it is not empty now. }
if ((action != 'a' || qfl->qf_index == 0) && !qf_list_empty(qfl))
qfl->qf_index = 1;
// Don't update the cursor in quickfix window when appending entries // Don't update the cursor in quickfix window when appending entries
qf_update_buffer(qi, old_last); qf_update_buffer(qi, old_last);
@ -7700,7 +7800,7 @@ qf_setprop_items_from_lines(
if (di->di_tv.v_type != VAR_LIST || di->di_tv.vval.v_list == NULL) if (di->di_tv.v_type != VAR_LIST || di->di_tv.vval.v_list == NULL)
return FAIL; return FAIL;
if (action == 'r') if (action == 'r' || action == 'u')
qf_free_items(&qi->qf_lists[qf_idx]); qf_free_items(&qi->qf_lists[qf_idx]);
if (qf_init_ext(qi, qf_idx, NULL, NULL, &di->di_tv, errorformat, if (qf_init_ext(qi, qf_idx, NULL, NULL, &di->di_tv, errorformat,
FALSE, (linenr_T)0, (linenr_T)0, NULL, NULL) >= 0) FALSE, (linenr_T)0, (linenr_T)0, NULL, NULL) >= 0)
@ -7897,8 +7997,8 @@ qf_free_stack(win_T *wp, qf_info_T *qi)
/* /*
* Populate the quickfix list with the items supplied in the list * Populate the quickfix list with the items supplied in the list
* of dictionaries. "title" will be copied to w:quickfix_title. * of dictionaries. "title" will be copied to w:quickfix_title.
* "action" is 'a' for add, 'r' for replace. Otherwise create a new list. * "action" is 'a' for add, 'r' for replace, 'u' for update. Otherwise
* When "what" is not NULL then only set some properties. * create a new list. When "what" is not NULL then only set some properties.
*/ */
int int
set_errorlist( set_errorlist(
@ -8740,7 +8840,7 @@ set_qf_ll_list(
act = tv_get_string_chk(action_arg); act = tv_get_string_chk(action_arg);
if (act == NULL) if (act == NULL)
return; // type error; errmsg already given return; // type error; errmsg already given
if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') && if ((*act == 'a' || *act == 'r' || *act == 'u' || *act == ' ' || *act == 'f') &&
act[1] == NUL) act[1] == NUL)
action = *act; action = *act;
else else

View File

@ -6462,6 +6462,97 @@ func Test_quickfix_buffer_contents()
call setqflist([], 'f') call setqflist([], 'f')
endfunc endfunc
func Test_quickfix_update()
" Setup: populate a couple buffers
new
call setline(1, range(1, 5))
let b1 = bufnr()
new
call setline(1, range(1, 3))
let b2 = bufnr()
" Setup: set a quickfix list.
let items = [{'bufnr': b1, 'lnum': 1}, {'bufnr': b1, 'lnum': 2}, {'bufnr': b2, 'lnum': 1}, {'bufnr': b2, 'lnum': 2}]
call setqflist(items)
" Open the quickfix list, select the third entry.
copen
exe "normal jj\<CR>"
call assert_equal(3, getqflist({'idx' : 0}).idx)
" Update the quickfix list. Make sure the third entry is still selected.
call setqflist([], 'u', { 'items': items })
call assert_equal(3, getqflist({'idx' : 0}).idx)
" Update the quickfix list again, but this time with missing line number
" information. Confirm that we keep the current buffer selected.
call setqflist([{'bufnr': b1}, {'bufnr': b2}], 'u')
call assert_equal(2, getqflist({'idx' : 0}).idx)
" Cleanup the buffers we allocated during this test.
%bwipe!
%bwipe!
endfunc
func Test_quickfix_update_with_missing_coordinate_info()
new
call setline(1, range(1, 5))
let b1 = bufnr()
new
call setline(1, range(1, 3))
let b2 = bufnr()
new
call setline(1, range(1, 2))
let b3 = bufnr()
" Setup: set a quickfix list with no coordinate information at all.
call setqflist([{}, {}])
" Open the quickfix list, select the second entry.
copen
exe "normal j\<CR>"
call assert_equal(2, getqflist({'idx' : 0}).idx)
" Update the quickfix list. As the previously selected entry has no
" coordinate information, we expect the first entry to now be selected.
call setqflist([{'bufnr': b1}, {'bufnr': b2}, {'bufnr': b3}], 'u')
call assert_equal(1, getqflist({'idx' : 0}).idx)
" Select the second entry in the quickfix list.
copen
exe "normal j\<CR>"
call assert_equal(2, getqflist({'idx' : 0}).idx)
" Update the quickfix list again. The currently selected entry does not have
" a line number, but we should keep the file selected.
call setqflist([{'bufnr': b1}, {'bufnr': b2, 'lnum': 3}, {'bufnr': b3}], 'u')
call assert_equal(2, getqflist({'idx' : 0}).idx)
" Update the quickfix list again. The currently selected entry (bufnr=b2, lnum=3)
" is no longer present. We should pick the nearest entry.
call setqflist([{'bufnr': b1}, {'bufnr': b2, 'lnum': 1}, {'bufnr': b2, 'lnum': 4}], 'u')
call assert_equal(3, getqflist({'idx' : 0}).idx)
" Set the quickfix list again, with a specific column number. The currently selected entry doesn't have a
" column number, but they share a line number.
call setqflist([{'bufnr': b1}, {'bufnr': b2, 'lnum': 4, 'col': 5}, {'bufnr': b2, 'lnum': 4, 'col': 6}], 'u')
call assert_equal(2, getqflist({'idx' : 0}).idx)
" Set the quickfix list again. The currently selected column number (6) is
" no longer present. We should select the nearest column number.
call setqflist([{'bufnr': b1}, {'bufnr': b2, 'lnum': 4, 'col': 2}, {'bufnr': b2, 'lnum': 4, 'col': 4}], 'u')
call assert_equal(3, getqflist({'idx' : 0}).idx)
" Now set the quickfix list, but without columns. We should still pick the
" same line.
call setqflist([{'bufnr': b2, 'lnum': 3}, {'bufnr': b2, 'lnum': 4}, {'bufnr': b2, 'lnum': 4}], 'u')
call assert_equal(2, getqflist({'idx' : 0}).idx)
" Cleanup the buffers we allocated during this test.
%bwipe!
endfunc
" Test for "%b" in "errorformat" " Test for "%b" in "errorformat"
func Test_efm_format_b() func Test_efm_format_b()
call setqflist([], 'f') call setqflist([], 'f')

View File

@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
785,
/**/ /**/
784, 784,
/**/ /**/