vim-patch:9.1.0554: :bw leaves jumplist and tagstack data around (#29639)

Problem:  :bw leaves jumplist and tagstack data around
          (Paul "Joey" Clark)
Solution: Wipe jumplist and tagstack references to the wiped buffer
          (LemonBoy)

As documented the :bwipeout command brutally deletes all the references
to the buffer, so let's make it delete all the entries in the jump list
and tag stack referring to the wiped-out buffer.

fixes: vim/vim#8201
closes: vim/vim#15185

4ff3a9b1e3

Co-authored-by: LemonBoy <thatlemon@gmail.com>
This commit is contained in:
zeertzjq 2024-07-10 10:35:12 +08:00 committed by GitHub
parent 545aafbeb8
commit 158ffd646d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 90 additions and 50 deletions

View File

@ -1175,11 +1175,12 @@ list of buffers. |unlisted-buffer|
:bw[ipeout][!] N1 N2 ...
Like |:bdelete|, but really delete the buffer. Everything
related to the buffer is lost. All marks in this buffer
become invalid, option settings are lost, etc. Don't use this
become invalid, option settings are lost, the jumplist and
tagstack data will be purged, etc. Don't use this
unless you know what you are doing. Examples: >
:.+,$bwipeout " wipe out all buffers after the current
" one
:%bwipeout " wipe out all buffers
:.+,$bwipeout " wipe out all buffers after the current
" one
:%bwipeout " wipe out all buffers
<
:[N]bun[load][!] *:bun* *:bunload* *E515*
:bun[load][!] [N]

View File

@ -699,6 +699,9 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i
if (buf->b_nwindows > 0) {
return false;
}
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
mark_forget_file(wp, buf->b_fnum);
}
if (buf->b_sfname != buf->b_ffname) {
XFREE_CLEAR(buf->b_sfname);
} else {
@ -1184,32 +1187,6 @@ static int empty_curbuf(bool close_others, int forceit, int action)
return retval;
}
/// Remove every jump list entry referring to a given buffer.
/// This function will also adjust the current jump list index.
void buf_remove_from_jumplist(buf_T *deleted_buf)
{
// Remove all jump list entries that match the deleted buffer.
for (int i = curwin->w_jumplistlen - 1; i >= 0; i--) {
buf_T *buf = buflist_findnr(curwin->w_jumplist[i].fmark.fnum);
if (buf == deleted_buf) {
// Found an entry that we want to delete.
curwin->w_jumplistlen -= 1;
// If the current jump list index behind the entry we want to
// delete, move it back by one.
if (curwin->w_jumplistidx > i && curwin->w_jumplistidx > 0) {
curwin->w_jumplistidx -= 1;
}
// Actually remove the entry from the jump list.
for (int d = i; d < curwin->w_jumplistlen; d++) {
curwin->w_jumplist[d] = curwin->w_jumplist[d + 1];
}
}
}
}
/// Implementation of the commands for the buffer list.
///
/// action == DOBUF_GOTO go to specified buffer
@ -1364,6 +1341,8 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
}
}
int buf_fnum = buf->b_fnum;
// When closing the current buffer stop Visual mode.
if (buf == curbuf && VIsual_active) {
end_visual_mode();
@ -1398,7 +1377,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
if (buf != curbuf) {
if (jop_flags & JOP_UNLOAD) {
// Remove the buffer to be deleted from the jump list.
buf_remove_from_jumplist(buf);
mark_jumplist_forget_file(curwin, buf_fnum);
}
close_windows(buf, false);
@ -1423,8 +1402,8 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
buf = au_new_curbuf.br_buf;
} else if (curwin->w_jumplistlen > 0) {
if (jop_flags & JOP_UNLOAD) {
// Remove the current buffer from the jump list.
buf_remove_from_jumplist(curbuf);
// Remove the buffer from the jump list.
mark_jumplist_forget_file(curwin, buf_fnum);
}
// It's possible that we removed all jump list entries, in that case we need to try another

View File

@ -43,6 +43,7 @@
#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
#include "nvim/textobject.h"
#include "nvim/types_defs.h"
#include "nvim/vim_defs.h"
@ -165,6 +166,56 @@ int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt)
return FAIL;
}
/// Remove every jump list entry referring to a given buffer.
/// This function will also adjust the current jump list index.
void mark_jumplist_forget_file(win_T *wp, int fnum)
{
// Remove all jump list entries that match the deleted buffer.
for (int i = wp->w_jumplistlen - 1; i >= 0; i--) {
if (wp->w_jumplist[i].fmark.fnum == fnum) {
// Found an entry that we want to delete.
free_xfmark(wp->w_jumplist[i]);
// If the current jump list index is behind the entry we want to delete,
// move it back by one.
if (wp->w_jumplistidx > i) {
wp->w_jumplistidx--;
}
// Actually remove the entry from the jump list.
wp->w_jumplistlen--;
memmove(&wp->w_jumplist[i], &wp->w_jumplist[i + 1],
(size_t)(wp->w_jumplistlen - i) * sizeof(wp->w_jumplist[i]));
}
}
}
/// Delete every entry referring to file "fnum" from both the jumplist and the
/// tag stack.
void mark_forget_file(win_T *wp, int fnum)
{
mark_jumplist_forget_file(wp, fnum);
// Remove all tag stack entries that match the deleted buffer.
for (int i = wp->w_tagstacklen - 1; i >= 0; i--) {
if (wp->w_tagstack[i].fmark.fnum == fnum) {
// Found an entry that we want to delete.
tagstack_clear_entry(&wp->w_tagstack[i]);
// If the current tag stack index is behind the entry we want to delete,
// move it back by one.
if (wp->w_tagstackidx > i) {
wp->w_tagstackidx--;
}
// Actually remove the entry from the tag stack.
wp->w_tagstacklen--;
memmove(&wp->w_tagstack[i], &wp->w_tagstack[i + 1],
(size_t)(wp->w_tagstacklen - i) * sizeof(wp->w_tagstack[i]));
}
}
}
// Set the previous context mark to the current position and add it to the
// jump list.
void setpcmark(void)

View File

@ -3184,7 +3184,7 @@ static int find_extra(char **pp)
//
// Free a single entry in a tag stack
//
static void tagstack_clear_entry(taggy_T *item)
void tagstack_clear_entry(taggy_T *item)
{
XFREE_CLEAR(item->tagname);
XFREE_CLEAR(item->user_data);

View File

@ -1,5 +1,6 @@
#pragma once
#include "nvim/buffer_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#include "nvim/option_defs.h" // IWYU pragma: keep

View File

@ -68,26 +68,16 @@ endfunc
func Test_jumplist_invalid()
new
clearjumps
" put some randome text
put ='a'
let prev = bufnr('%')
" Put some random text and fill the jump list.
call setline(1, ['foo', 'bar', 'baz'])
normal G
normal gg
setl nomodified bufhidden=wipe
e XXJumpListBuffer
let bnr = bufnr('%')
" 1) empty jumplist
let expected = [[
\ {'lnum': 2, 'bufnr': prev, 'col': 0, 'coladd': 0}], 1]
call assert_equal(expected, getjumplist())
" The jump list is empty as the buffer was wiped out.
call assert_equal([[], 0], getjumplist())
let jumps = execute(':jumps')
call assert_equal('>', jumps[-1:])
" now jump back
exe ":norm! \<c-o>"
let expected = [[
\ {'lnum': 2, 'bufnr': prev, 'col': 0, 'coladd': 0},
\ {'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0}], 0]
call assert_equal(expected, getjumplist())
let jumps = execute(':jumps')
call assert_match('> 0 2 0 -invalid-', jumps)
endfunc
" Test for '' mark in an empty buffer

View File

@ -1001,6 +1001,23 @@ func Test_tag_stack()
call settagstack(1, {'items' : []})
call assert_fails('pop', 'E73:')
" References to wiped buffer are deleted.
for i in range(10, 20)
edit Xtest
exe "tag var" .. i
endfor
edit Xtest
let t = gettagstack()
call assert_equal(11, t.length)
call assert_equal(12, t.curidx)
bwipe!
let t = gettagstack()
call assert_equal(0, t.length)
call assert_equal(1, t.curidx)
set tags&
%bwipe
endfunc

View File

@ -2934,6 +2934,7 @@ func Test_tfirst()
\ "Xtags", 'D')
call writefile(["one", "two", "three"], "Xfile", 'D')
call writefile(["one"], "Xother", 'D')
tag one
edit Xother
set winfixbuf