mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	feat(jumplist): allow opting out of removing unloaded buffers (#29347)
Problem: Cannot opt out of removing unloaded buffers from the jumplist. Solution: Only enable that with "unload" flag in 'jumpoptions'.
This commit is contained in:
		| @@ -3619,7 +3619,7 @@ A jump table for the options with a short description can be found at |Q_op|. | |||||||
| 	Otherwise only one space is inserted. | 	Otherwise only one space is inserted. | ||||||
|  |  | ||||||
| 						*'jumpoptions'* *'jop'* | 						*'jumpoptions'* *'jop'* | ||||||
| 'jumpoptions' 'jop'	string	(default "") | 'jumpoptions' 'jop'	string	(default "unload") | ||||||
| 			global | 			global | ||||||
| 	List of words that change the behavior of the |jumplist|. | 	List of words that change the behavior of the |jumplist|. | ||||||
| 	  stack         Make the jumplist behave like the tagstack. | 	  stack         Make the jumplist behave like the tagstack. | ||||||
| @@ -3632,6 +3632,9 @@ A jump table for the options with a short description can be found at |Q_op|. | |||||||
| 			|alternate-file| or using |mark-motions| try to | 			|alternate-file| or using |mark-motions| try to | ||||||
| 			restore the |mark-view| in which the action occurred. | 			restore the |mark-view| in which the action occurred. | ||||||
|  |  | ||||||
|  | 	  unload        Remove unloaded buffers from the jumplist. | ||||||
|  | 			EXPERIMENTAL: this flag may change in the future. | ||||||
|  |  | ||||||
| 						*'keymap'* *'kmp'* | 						*'keymap'* *'kmp'* | ||||||
| 'keymap' 'kmp'		string	(default "") | 'keymap' 'kmp'		string	(default "") | ||||||
| 			local to buffer | 			local to buffer | ||||||
|   | |||||||
| @@ -62,6 +62,7 @@ Defaults                                                    *nvim-defaults* | |||||||
| - 'isfname' does not include ":" (on Windows). Drive letters are handled | - 'isfname' does not include ":" (on Windows). Drive letters are handled | ||||||
|   correctly without it. (Use |gF| for filepaths suffixed with ":line:col"). |   correctly without it. (Use |gF| for filepaths suffixed with ":line:col"). | ||||||
| - 'joinspaces' is disabled | - 'joinspaces' is disabled | ||||||
|  | - 'jumpoptions' defaults to "unload" | ||||||
| - 'langnoremap' is enabled | - 'langnoremap' is enabled | ||||||
| - 'langremap' is disabled | - 'langremap' is disabled | ||||||
| - 'laststatus' defaults to 2 (statusline is always shown) | - 'laststatus' defaults to 2 (statusline is always shown) | ||||||
| @@ -341,6 +342,7 @@ string options work. | |||||||
| - 'inccommand'  shows interactive results for |:substitute|-like commands | - 'inccommand'  shows interactive results for |:substitute|-like commands | ||||||
|                 and |:command-preview| commands |                 and |:command-preview| commands | ||||||
| - 'jumpoptions' "view" tries to restore the |mark-view| when moving through | - 'jumpoptions' "view" tries to restore the |mark-view| when moving through | ||||||
|  |                 "unload" removes unloaded buffer from the jumplist | ||||||
| - the |jumplist|, |changelist|, |alternate-file| or using |mark-motions|. | - the |jumplist|, |changelist|, |alternate-file| or using |mark-motions|. | ||||||
| - 'laststatus'  global statusline support | - 'laststatus'  global statusline support | ||||||
| - 'mousescroll' amount to scroll by when scrolling with a mouse | - 'mousescroll' amount to scroll by when scrolling with a mouse | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								runtime/lua/vim/_meta/options.lua
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								runtime/lua/vim/_meta/options.lua
									
									
									
										generated
									
									
									
								
							| @@ -3551,8 +3551,11 @@ vim.go.js = vim.go.joinspaces | |||||||
| --- 		|alternate-file` or using `mark-motions` try to | --- 		|alternate-file` or using `mark-motions` try to | ||||||
| --- 		restore the `mark-view` in which the action occurred. | --- 		restore the `mark-view` in which the action occurred. | ||||||
| --- | --- | ||||||
|  | ---   unload        Remove unloaded buffers from the jumplist. | ||||||
|  | --- 		EXPERIMENTAL: this flag may change in the future. | ||||||
|  | --- | ||||||
| --- @type string | --- @type string | ||||||
| vim.o.jumpoptions = "" | vim.o.jumpoptions = "unload" | ||||||
| vim.o.jop = vim.o.jumpoptions | vim.o.jop = vim.o.jumpoptions | ||||||
| vim.go.jumpoptions = vim.o.jumpoptions | vim.go.jumpoptions = vim.o.jumpoptions | ||||||
| vim.go.jop = vim.go.jumpoptions | vim.go.jop = vim.go.jumpoptions | ||||||
|   | |||||||
| @@ -1396,8 +1396,10 @@ int do_buffer(int action, int start, int dir, int count, int forceit) | |||||||
|  |  | ||||||
|     // If the buffer to be deleted is not the current one, delete it here. |     // If the buffer to be deleted is not the current one, delete it here. | ||||||
|     if (buf != curbuf) { |     if (buf != curbuf) { | ||||||
|       // Remove the buffer to be deleted from the jump list. |       if (jop_flags & JOP_UNLOAD) { | ||||||
|       buf_remove_from_jumplist(buf); |         // Remove the buffer to be deleted from the jump list. | ||||||
|  |         buf_remove_from_jumplist(buf); | ||||||
|  |       } | ||||||
|  |  | ||||||
|       close_windows(buf, false); |       close_windows(buf, false); | ||||||
|  |  | ||||||
| @@ -1420,28 +1422,37 @@ int do_buffer(int action, int start, int dir, int count, int forceit) | |||||||
|     if (au_new_curbuf.br_buf != NULL && bufref_valid(&au_new_curbuf)) { |     if (au_new_curbuf.br_buf != NULL && bufref_valid(&au_new_curbuf)) { | ||||||
|       buf = au_new_curbuf.br_buf; |       buf = au_new_curbuf.br_buf; | ||||||
|     } else if (curwin->w_jumplistlen > 0) { |     } else if (curwin->w_jumplistlen > 0) { | ||||||
|       // Remove the current buffer from the jump list. |       if (jop_flags & JOP_UNLOAD) { | ||||||
|       buf_remove_from_jumplist(curbuf); |         // Remove the current buffer from the jump list. | ||||||
|  |         buf_remove_from_jumplist(curbuf); | ||||||
|  |       } | ||||||
|  |  | ||||||
|       // It's possible that we removed all jump list entries, in that case we need to try another |       // It's possible that we removed all jump list entries, in that case we need to try another | ||||||
|       // approach |       // approach | ||||||
|       if (curwin->w_jumplistlen > 0) { |       if (curwin->w_jumplistlen > 0) { | ||||||
|         // If the index is the same as the length, the current position was not yet added to the jump |  | ||||||
|         // list. So we can safely go back to the last entry and search from there. |  | ||||||
|         if (curwin->w_jumplistidx == curwin->w_jumplistlen) { |  | ||||||
|           curwin->w_jumplistidx = curwin->w_jumplistlen - 1; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         int jumpidx = curwin->w_jumplistidx; |         int jumpidx = curwin->w_jumplistidx; | ||||||
|  |  | ||||||
|  |         if (jop_flags & JOP_UNLOAD) { | ||||||
|  |           // If the index is the same as the length, the current position was not yet added to the | ||||||
|  |           // jump list. So we can safely go back to the last entry and search from there. | ||||||
|  |           if (jumpidx == curwin->w_jumplistlen) { | ||||||
|  |             jumpidx = curwin->w_jumplistidx = curwin->w_jumplistlen - 1; | ||||||
|  |           } | ||||||
|  |         } else { | ||||||
|  |           jumpidx--; | ||||||
|  |           if (jumpidx < 0) { | ||||||
|  |             jumpidx = curwin->w_jumplistlen - 1; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         forward = jumpidx; |         forward = jumpidx; | ||||||
|         do { |         while ((jop_flags & JOP_UNLOAD) || jumpidx != curwin->w_jumplistidx) { | ||||||
|           buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum); |           buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum); | ||||||
|  |  | ||||||
|           if (buf != NULL) { |           if (buf != NULL) { | ||||||
|             // Skip unlisted bufs.  Also skip a quickfix |             // Skip current and unlisted bufs.  Also skip a quickfix | ||||||
|             // buffer, it might be deleted soon. |             // buffer, it might be deleted soon. | ||||||
|             if (!buf->b_p_bl || bt_quickfix(buf)) { |             if (buf == curbuf || !buf->b_p_bl || bt_quickfix(buf)) { | ||||||
|               buf = NULL; |               buf = NULL; | ||||||
|             } else if (buf->b_ml.ml_mfp == NULL) { |             } else if (buf->b_ml.ml_mfp == NULL) { | ||||||
|               // skip unloaded buf, but may keep it for later |               // skip unloaded buf, but may keep it for later | ||||||
| @@ -1452,8 +1463,10 @@ int do_buffer(int action, int start, int dir, int count, int forceit) | |||||||
|             } |             } | ||||||
|           } |           } | ||||||
|           if (buf != NULL) {         // found a valid buffer: stop searching |           if (buf != NULL) {         // found a valid buffer: stop searching | ||||||
|             curwin->w_jumplistidx = jumpidx; |             if (jop_flags & JOP_UNLOAD) { | ||||||
|             update_jumplist = false; |               curwin->w_jumplistidx = jumpidx; | ||||||
|  |               update_jumplist = false; | ||||||
|  |             } | ||||||
|             break; |             break; | ||||||
|           } |           } | ||||||
|           // advance to older entry in jump list |           // advance to older entry in jump list | ||||||
| @@ -1466,7 +1479,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit) | |||||||
|           if (jumpidx == forward) {               // List exhausted for sure |           if (jumpidx == forward) {               // List exhausted for sure | ||||||
|             break; |             break; | ||||||
|           } |           } | ||||||
|         } while (jumpidx != curwin->w_jumplistidx); |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -3728,7 +3741,7 @@ void ex_buffer_all(exarg_T *eap) | |||||||
|  |  | ||||||
|       // Open the buffer in this window. |       // Open the buffer in this window. | ||||||
|       swap_exists_action = SEA_DIALOG; |       swap_exists_action = SEA_DIALOG; | ||||||
|       set_curbuf(buf, DOBUF_GOTO, false); |       set_curbuf(buf, DOBUF_GOTO, !(jop_flags & JOP_UNLOAD)); | ||||||
|       if (!bufref_valid(&bufref)) { |       if (!bufref_valid(&bufref)) { | ||||||
|         // Autocommands deleted the buffer. |         // Autocommands deleted the buffer. | ||||||
|         swap_exists_action = SEA_NONE; |         swap_exists_action = SEA_NONE; | ||||||
|   | |||||||
| @@ -540,6 +540,7 @@ EXTERN char *p_jop;             ///< 'jumpooptions' | |||||||
| EXTERN unsigned jop_flags; | EXTERN unsigned jop_flags; | ||||||
| #define JOP_STACK               0x01 | #define JOP_STACK               0x01 | ||||||
| #define JOP_VIEW                0x02 | #define JOP_VIEW                0x02 | ||||||
|  | #define JOP_UNLOAD              0x04 | ||||||
| EXTERN char *p_keymap;          ///< 'keymap' | EXTERN char *p_keymap;          ///< 'keymap' | ||||||
| EXTERN char *p_kp;              ///< 'keywordprg' | EXTERN char *p_kp;              ///< 'keywordprg' | ||||||
| EXTERN char *p_km;              ///< 'keymodel' | EXTERN char *p_km;              ///< 'keymodel' | ||||||
|   | |||||||
| @@ -4495,7 +4495,7 @@ return { | |||||||
|     { |     { | ||||||
|       abbreviation = 'jop', |       abbreviation = 'jop', | ||||||
|       cb = 'did_set_jumpoptions', |       cb = 'did_set_jumpoptions', | ||||||
|       defaults = { if_true = '' }, |       defaults = { if_true = 'unload' }, | ||||||
|       deny_duplicates = true, |       deny_duplicates = true, | ||||||
|       desc = [=[ |       desc = [=[ | ||||||
|         List of words that change the behavior of the |jumplist|. |         List of words that change the behavior of the |jumplist|. | ||||||
| @@ -4508,6 +4508,9 @@ return { | |||||||
|           view          When moving through the jumplist, |changelist|, |           view          When moving through the jumplist, |changelist|, | ||||||
|         		|alternate-file| or using |mark-motions| try to |         		|alternate-file| or using |mark-motions| try to | ||||||
|         		restore the |mark-view| in which the action occurred. |         		restore the |mark-view| in which the action occurred. | ||||||
|  |  | ||||||
|  |           unload        Remove unloaded buffers from the jumplist. | ||||||
|  |         		EXPERIMENTAL: this flag may change in the future. | ||||||
|       ]=], |       ]=], | ||||||
|       expand_cb = 'expand_set_jumpoptions', |       expand_cb = 'expand_set_jumpoptions', | ||||||
|       full_name = 'jumpoptions', |       full_name = 'jumpoptions', | ||||||
|   | |||||||
| @@ -137,7 +137,7 @@ static char *(p_fdc_values[]) = { "auto", "auto:1", "auto:2", "auto:3", "auto:4" | |||||||
|                                   "5", "6", "7", "8", "9", NULL }; |                                   "5", "6", "7", "8", "9", NULL }; | ||||||
| static char *(p_spo_values[]) = { "camel", "noplainbuffer", NULL }; | static char *(p_spo_values[]) = { "camel", "noplainbuffer", NULL }; | ||||||
| static char *(p_icm_values[]) = { "nosplit", "split", NULL }; | static char *(p_icm_values[]) = { "nosplit", "split", NULL }; | ||||||
| static char *(p_jop_values[]) = { "stack", "view", NULL }; | static char *(p_jop_values[]) = { "stack", "view", "unload", NULL }; | ||||||
| static char *(p_tpf_values[]) = { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL }; | static char *(p_tpf_values[]) = { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL }; | ||||||
| static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelta", "line", | static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelta", "line", | ||||||
|                                   "flush", NULL }; |                                   "flush", NULL }; | ||||||
|   | |||||||
| @@ -194,7 +194,7 @@ describe("jumpoptions=stack behaves like 'tagstack'", function() | |||||||
|   end) |   end) | ||||||
| end) | end) | ||||||
|  |  | ||||||
| describe('buffer deletion', function() | describe('buffer deletion with jumpoptions+=unload', function() | ||||||
|   local base_file = 'Xtest-functional-buffer-deletion' |   local base_file = 'Xtest-functional-buffer-deletion' | ||||||
|   local file1 = base_file .. '1' |   local file1 = base_file .. '1' | ||||||
|   local file2 = base_file .. '2' |   local file2 = base_file .. '2' | ||||||
| @@ -227,6 +227,12 @@ describe('buffer deletion', function() | |||||||
|     command('edit ' .. file3) |     command('edit ' .. file3) | ||||||
|   end) |   end) | ||||||
|  |  | ||||||
|  |   after_each(function() | ||||||
|  |     os.remove(file1) | ||||||
|  |     os.remove(file2) | ||||||
|  |     os.remove(file3) | ||||||
|  |   end) | ||||||
|  |  | ||||||
|   it('deletes jump list entries when the current buffer is deleted', function() |   it('deletes jump list entries when the current buffer is deleted', function() | ||||||
|     command('edit ' .. file1) |     command('edit ' .. file1) | ||||||
|  |  | ||||||
| @@ -319,6 +325,44 @@ describe('buffer deletion', function() | |||||||
|   end) |   end) | ||||||
| end) | end) | ||||||
|  |  | ||||||
|  | describe('buffer deletion with jumpoptions-=unload', function() | ||||||
|  |   local base_file = 'Xtest-functional-buffer-deletion' | ||||||
|  |   local file1 = base_file .. '1' | ||||||
|  |   local file2 = base_file .. '2' | ||||||
|  |   local base_content = 'text' | ||||||
|  |   local content1 = base_content .. '1' | ||||||
|  |   local content2 = base_content .. '2' | ||||||
|  |  | ||||||
|  |   before_each(function() | ||||||
|  |     clear() | ||||||
|  |     command('clearjumps') | ||||||
|  |     command('set jumpoptions-=unload') | ||||||
|  |  | ||||||
|  |     write_file(file1, content1, false, false) | ||||||
|  |     write_file(file2, content2, false, false) | ||||||
|  |  | ||||||
|  |     command('edit ' .. file1) | ||||||
|  |     command('edit ' .. file2) | ||||||
|  |   end) | ||||||
|  |  | ||||||
|  |   after_each(function() | ||||||
|  |     os.remove(file1) | ||||||
|  |     os.remove(file2) | ||||||
|  |   end) | ||||||
|  |  | ||||||
|  |   it('Ctrl-O reopens previous buffer with :bunload or :bdelete #28968', function() | ||||||
|  |     eq(file2, fn.bufname('')) | ||||||
|  |     command('bunload') | ||||||
|  |     eq(file1, fn.bufname('')) | ||||||
|  |     feed('<C-O>') | ||||||
|  |     eq(file2, fn.bufname('')) | ||||||
|  |     command('bdelete') | ||||||
|  |     eq(file1, fn.bufname('')) | ||||||
|  |     feed('<C-O>') | ||||||
|  |     eq(file2, fn.bufname('')) | ||||||
|  |   end) | ||||||
|  | end) | ||||||
|  |  | ||||||
| describe('jumpoptions=view', function() | describe('jumpoptions=view', function() | ||||||
|   local file1 = 'Xtestfile-functional-editor-jumps' |   local file1 = 'Xtestfile-functional-editor-jumps' | ||||||
|   local file2 = 'Xtestfile-functional-editor-jumps-2' |   local file2 = 'Xtestfile-functional-editor-jumps-2' | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ if exists('s:did_load') | |||||||
|   set laststatus=1 |   set laststatus=1 | ||||||
|   set listchars=eol:$ |   set listchars=eol:$ | ||||||
|   set joinspaces |   set joinspaces | ||||||
|  |   set jumpoptions= | ||||||
|   set mousemodel=extend |   set mousemodel=extend | ||||||
|   set nohidden nosmarttab noautoindent noautoread noruler noshowcmd |   set nohidden nosmarttab noautoindent noautoread noruler noshowcmd | ||||||
|   set nohlsearch noincsearch |   set nohlsearch noincsearch | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 zeertzjq
					zeertzjq