aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2024-10-15 18:53:59 +0800
committerGitHub <noreply@github.com>2024-10-15 18:53:59 +0800
commit84623dbe93777c0a8e7ddf57470ddeb2ea738069 (patch)
tree0856b7556667a3efc5c7ec6219a9a5b3f47d487d
parente0a5c3bb581752569df4490b48cb54e7c1ab0613 (diff)
downloadrneovim-84623dbe93777c0a8e7ddf57470ddeb2ea738069.tar.gz
rneovim-84623dbe93777c0a8e7ddf57470ddeb2ea738069.tar.bz2
rneovim-84623dbe93777c0a8e7ddf57470ddeb2ea738069.zip
vim-patch:9.1.0785: cannot preserve error position when setting quickfix list (#30820)
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: vim/vim#15839 closes: vim/vim#15841 https://github.com/vim/vim/commit/27fbf6e5e8bee5c6b61819a5e82a0b50b265f0b0 Co-authored-by: Jeremy Fleischman <jeremyfleischman@gmail.com>
-rw-r--r--runtime/doc/builtin.txt2
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua2
-rw-r--r--src/nvim/eval.lua2
-rw-r--r--src/nvim/quickfix.c130
-rw-r--r--test/old/testdir/test_quickfix.vim91
5 files changed, 208 insertions, 19 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index aa31c59ab2..8916c434e7 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -9039,6 +9039,8 @@ setqflist({list} [, {action} [, {what}]]) *setqflist()*
clear the list: >vim
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
freed.
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index 81e7070ac0..f588162112 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -8229,6 +8229,8 @@ function vim.fn.setpos(expr, list) end
--- clear the list: >vim
--- 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
--- freed.
---
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index a440450b9d..f0746e6159 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -9896,6 +9896,8 @@ M.funcs = {
clear the list: >vim
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
freed.
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index ddf2a7247f..5bd81ce469 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -6462,6 +6462,64 @@ static int qf_add_entry_from_dict(qf_list_T *qfl, dict_T *d, bool first_entry, b
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 bool 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;
+ }
+
+ bool is_target_file = entry->qf_fnum && entry->qf_fnum == target_fnum;
+ bool 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
+ ? abs(entry->qf_lnum - target_lnum) : INT_MAX;
+ int other_line_distance = other_entry->qf_lnum
+ ? abs(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
/// a dictionary with item information.
static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, char *title, int action)
@@ -6471,19 +6529,48 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, char *title,
int retval = OK;
bool 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;
+ }
+
+ bool select_first_entry = false;
+ bool select_nearest_entry = false;
+
if (action == ' ' || qf_idx == qi->qf_listcount) {
+ select_first_entry = true;
// make place for a new list
qf_new_list(qi, title);
qf_idx = qi->qf_curlist;
qfl = qf_get_list(qi, qf_idx);
- } else if (action == 'a' && !qf_list_empty(qfl)) {
- // Adding to existing list, use last entry.
- old_last = qfl->qf_last;
+ } 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.
+ old_last = qfl->qf_last;
+ }
} else if (action == 'r') {
+ select_first_entry = true;
+ qf_free_items(qfl);
+ 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;
+
TV_LIST_ITER_CONST(list, li, {
if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT) {
continue; // Skip non-dict items.
@@ -6498,6 +6585,16 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, char *title,
if (retval == QF_FAIL) {
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.
@@ -6507,16 +6604,10 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, char *title,
qfl->qf_nonevalid = true;
}
- // If not appending to the list, set the current error to the first entry
- if (action != 'a') {
- qfl->qf_ptr = qfl->qf_start;
- }
-
- // Update the current error index if not appending to the list or if the
- // 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;
+ // Set the current error.
+ if (entry_to_select) {
+ qfl->qf_ptr = entry_to_select;
+ qfl->qf_index = entry_to_select_index;
}
// Don't update the cursor in quickfix window when appending entries
@@ -6632,7 +6723,7 @@ static int qf_setprop_items_from_lines(qf_info_T *qi, int qf_idx, const dict_T *
return FAIL;
}
- if (action == 'r') {
+ if (action == 'r' || action == 'u') {
qf_free_items(&qi->qf_lists[qf_idx]);
}
if (qf_init_ext(qi, qf_idx, NULL, NULL, &di->di_tv, errorformat,
@@ -6789,10 +6880,11 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi)
}
}
-// Populate the quickfix list with the items supplied in the list
-// of dictionaries. "title" will be copied to w:quickfix_title
-// "action" is 'a' for add, 'r' for replace. Otherwise create a new list.
-// When "what" is not NULL then only set some properties.
+/// Populate the quickfix list with the items supplied in the list
+/// of dictionaries. "title" will be copied to w:quickfix_title
+/// "action" is 'a' for add, 'r' for replace, 'u' for update. Otherwise
+/// create a new list.
+/// When "what" is not NULL then only set some properties.
int set_errorlist(win_T *wp, list_T *list, int action, char *title, dict_T *what)
{
qf_info_T *qi = &ql_info;
@@ -7433,7 +7525,7 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
return;
}
const char *const act = tv_get_string_chk(action_arg);
- if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f')
+ if ((*act == 'a' || *act == 'r' || *act == 'u' || *act == ' ' || *act == 'f')
&& act[1] == NUL) {
action = *act;
} else {
diff --git a/test/old/testdir/test_quickfix.vim b/test/old/testdir/test_quickfix.vim
index b4f8b2626a..c50fef1e46 100644
--- a/test/old/testdir/test_quickfix.vim
+++ b/test/old/testdir/test_quickfix.vim
@@ -6340,6 +6340,97 @@ func Test_quickfix_buffer_contents()
call setqflist([], 'f')
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"
func Test_efm_format_b()
call setqflist([], 'f')