Mercurial > vim
changeset 36318:e505f4541d4b draft v9.1.0785
patch 9.1.0785: cannot preserve error position when setting quickfix list
Commit: https://github.com/vim/vim/commit/27fbf6e5e8bee5c6b61819a5e82a0b50b265f0b0
Author: Jeremy Fleischman <jeremyfleischman@gmail.com>
Date: Mon Oct 14 20:46:27 2024 +0200
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>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Mon, 14 Oct 2024 21:00:03 +0200 |
parents | 08540fed30ef |
children | 7585f3cf9a8f |
files | runtime/doc/builtin.txt runtime/doc/version9.txt src/quickfix.c src/testdir/test_quickfix.vim src/version.c |
diffstat | 5 files changed, 214 insertions(+), 17 deletions(-) [+] |
line wrap: on
line diff
--- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -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 @@ -9766,6 +9766,8 @@ setqflist({list} [, {action} [, {what}]] clear the list: > :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.
--- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -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 @@ -41598,6 +41598,8 @@ Changed~ - the regex engines match correctly case-insensitive multi-byte characters (and apply proper case folding) - |: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 ~
--- a/src/quickfix.c +++ b/src/quickfix.c @@ -190,6 +190,7 @@ static buf_T *load_dummy_buffer(char_u * 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 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 #define IS_QF_WINDOW(wp) (bt_quickfix((wp)->w_buffer) && (wp)->w_llist_ref == NULL) @@ -7500,6 +7501,62 @@ qf_add_entry_from_dict( } /* + * 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 * a dictionary with item information. */ @@ -7518,21 +7575,54 @@ qf_add_entries( int retval = OK; 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) { + 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; FOR_ALL_LIST_ITEMS(list, li) { @@ -7547,6 +7637,18 @@ qf_add_entries( &valid_entry); 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. @@ -7556,14 +7658,12 @@ qf_add_entries( // no valid entry 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 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) 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, 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 * 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. + * "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( @@ -8740,7 +8840,7 @@ set_qf_ll_list( act = tv_get_string_chk(action_arg); if (act == NULL) 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) action = *act; else
--- a/src/testdir/test_quickfix.vim +++ b/src/testdir/test_quickfix.vim @@ -6462,6 +6462,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')