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 | ||||
| @@ -9766,6 +9766,8 @@ setqflist({list} [, {action} [, {what}]])		*setqflist()* | ||||
| 			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. | ||||
|  | ||||
|   | ||||
| @@ -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 ~ | ||||
|   | ||||
							
								
								
									
										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	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) | ||||
| @@ -7499,6 +7500,62 @@ qf_add_entry_from_dict( | ||||
|     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 | ||||
|  * 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 | ||||
|   | ||||
| @@ -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') | ||||
|   | ||||
| @@ -704,6 +704,8 @@ static char *(features[]) = | ||||
|  | ||||
| static int included_patches[] = | ||||
| {   /* Add new patch number below this line */ | ||||
| /**/ | ||||
|     785, | ||||
| /**/ | ||||
|     784, | ||||
| /**/ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user