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:
parent
83a06705dc
commit
27fbf6e5e8
@ -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.
|
||||||
|
|
||||||
|
@ -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 ~
|
||||||
|
126
src/quickfix.c
126
src/quickfix.c
@ -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
|
||||||
|
@ -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')
|
||||||
|
@ -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,
|
||||||
/**/
|
/**/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user