mirror of
				https://github.com/vim/vim.git
				synced 2025-10-31 09:57:14 -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:
		
				
					committed by
					
						 Christian Brabandt
						Christian Brabandt
					
				
			
			
				
	
			
			
			
						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 ~ | ||||||
|   | |||||||
							
								
								
									
										130
									
								
								src/quickfix.c
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								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') | ||||||
| 	// Adding to existing list, use last entry. |     { | ||||||
| 	old_last = qfl->qf_last; | 	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') |     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, | ||||||
| /**/ | /**/ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user