diff options
author | zeertzjq <zeertzjq@outlook.com> | 2024-10-15 18:53:59 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-10-15 18:53:59 +0800 |
commit | 84623dbe93777c0a8e7ddf57470ddeb2ea738069 (patch) | |
tree | 0856b7556667a3efc5c7ec6219a9a5b3f47d487d | |
parent | e0a5c3bb581752569df4490b48cb54e7c1ab0613 (diff) | |
download | rneovim-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.txt | 2 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vimfn.lua | 2 | ||||
-rw-r--r-- | src/nvim/eval.lua | 2 | ||||
-rw-r--r-- | src/nvim/quickfix.c | 130 | ||||
-rw-r--r-- | test/old/testdir/test_quickfix.vim | 91 |
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') |