From 8330cc22afec67d9dbc2ad8b4a39eaf62fdf16d1 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 8 May 2019 18:01:21 +0200 Subject: [PATCH] vim-patch:8.1.1205: BufReadPre may move the cursor #9980 Problem: A BufReadPre autocommand may cause the cursor to move. Solution: Restore the cursor position after executing the autocommand, unless the autocommand moved it. (Christian Brabandt, closes vim/vim#4302, closes vim/vim#4294) https://github.com/vim/vim/commit/a68e59590905da9b4448ff1fcac929ad1a18da9e --- src/nvim/buffer_defs.h | 33 +++++++++++++++-------- src/nvim/fileio.c | 2 ++ src/nvim/testdir/test_autocmd.vim | 44 +++++++++++++++++++++++++++++++ src/nvim/window.c | 24 +++++++++++++++++ 4 files changed, 92 insertions(+), 11 deletions(-) diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 6d99a06eb9..5e28a7b513 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -999,6 +999,16 @@ typedef struct { .relative = 0, .external = false, \ .focusable = true }) +// Structure to store last cursor position and topline. Used by check_lnums() +// and reset_lnums(). +typedef struct +{ + int w_topline_save; // original topline value + int w_topline_corr; // corrected topline value + pos_T w_cursor_save; // original cursor position + pos_T w_cursor_corr; // corrected cursor position +} pos_save_T; + /// Structure which contains all information that belongs to a window. /// /// All row numbers are relative to the start of the window, except w_winrow. @@ -1091,17 +1101,18 @@ struct window_S { colnr_T w_skipcol; /* starting column when a single line doesn't fit in the window */ - /* - * Layout of the window in the screen. - * May need to add "msg_scrolled" to "w_winrow" in rare situations. - */ - int w_winrow; /* first row of window in screen */ - int w_height; /* number of rows in window, excluding - status/command line(s) */ - int w_status_height; /* number of status lines (0 or 1) */ - int w_wincol; /* Leftmost column of window in screen. */ - int w_width; /* Width of window, excluding separation. */ - int w_vsep_width; /* Number of separator columns (0 or 1). */ + // + // Layout of the window in the screen. + // May need to add "msg_scrolled" to "w_winrow" in rare situations. + // + int w_winrow; // first row of window in screen + int w_height; // number of rows in window, excluding + // status/command line(s) + int w_status_height; // number of status lines (0 or 1) + int w_wincol; // Leftmost column of window in screen. + int w_width; // Width of window, excluding separation. + int w_vsep_width; // Number of separator columns (0 or 1). + pos_save_T w_save_cursor; // backup of cursor pos and topline // inner size of window, which can be overridden by external UI int w_height_inner; diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 416d7f5cce..717ae6e602 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -7072,6 +7072,8 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, do_cmdline(NULL, getnextac, (void *)&patcmd, DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); + reset_lnums(); // restore cursor and topline, unless they were changed + if (eap != NULL) { (void)set_cmdarg(NULL, save_cmdarg); set_vim_var_nr(VV_CMDBANG, save_cmdbang); diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index d13761e2ba..1c1da0572b 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1425,6 +1425,50 @@ func Test_autocmd_once() call assert_fails('au WinNew * ++once ++once echo bad', 'E983:') endfunc +func Test_autocmd_bufreadpre() + new + let b:bufreadpre = 1 + call append(0, range(100)) + w! XAutocmdBufReadPre.txt + autocmd BufReadPre :let b:bufreadpre += 1 + norm! 50gg + sp + norm! 100gg + wincmd p + let g:wsv1 = winsaveview() + wincmd p + let g:wsv2 = winsaveview() + " triggers BufReadPre, should not move the cursor in either window + " The topline may change one line in a large window. + edit + call assert_inrange(g:wsv2.topline - 1, g:wsv2.topline + 1, winsaveview().topline) + call assert_equal(g:wsv2.lnum, winsaveview().lnum) + call assert_equal(2, b:bufreadpre) + wincmd p + call assert_equal(g:wsv1.topline, winsaveview().topline) + call assert_equal(g:wsv1.lnum, winsaveview().lnum) + call assert_equal(2, b:bufreadpre) + " Now set the cursor position in an BufReadPre autocommand + " (even though the position will be invalid, this should make Vim reset the + " cursor position in the other window. + wincmd p + 1 + " won't do anything, but try to set the cursor on an invalid lnum + autocmd BufReadPre :norm! 70gg + " triggers BufReadPre, should not move the cursor in either window + e + call assert_equal(1, winsaveview().topline) + call assert_equal(1, winsaveview().lnum) + call assert_equal(3, b:bufreadpre) + wincmd p + call assert_equal(g:wsv1.topline, winsaveview().topline) + call assert_equal(g:wsv1.lnum, winsaveview().lnum) + call assert_equal(3, b:bufreadpre) + close + close + call delete('XAutocmdBufReadPre.txt') +endfunc + " Tests for the following autocommands: " - FileWritePre writing a compressed file " - FileReadPost reading a compressed file diff --git a/src/nvim/window.c b/src/nvim/window.c index e737bfbd05..200c651d1e 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5963,16 +5963,40 @@ void check_lnums(int do_curwin) { FOR_ALL_TAB_WINDOWS(tp, wp) { if ((do_curwin || wp != curwin) && wp->w_buffer == curbuf) { + // save the original cursor position and topline + wp->w_save_cursor.w_cursor_save = wp->w_cursor; + wp->w_save_cursor.w_topline_save = wp->w_topline; + if (wp->w_cursor.lnum > curbuf->b_ml.ml_line_count) { wp->w_cursor.lnum = curbuf->b_ml.ml_line_count; } if (wp->w_topline > curbuf->b_ml.ml_line_count) { wp->w_topline = curbuf->b_ml.ml_line_count; } + + // save the corrected cursor position and topline + wp->w_save_cursor.w_cursor_corr = wp->w_cursor; + wp->w_save_cursor.w_topline_corr = wp->w_topline; } } } +/// Reset cursor and topline to its stored values from check_lnums(). +/// check_lnums() must have been called first! +void reset_lnums(void) +{ + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == curbuf) { + // Restore the value if the autocommand didn't change it. + if (equalpos(wp->w_save_cursor.w_cursor_corr, wp->w_cursor)) { + wp->w_cursor = wp->w_save_cursor.w_cursor_save; + } + if (wp->w_save_cursor.w_topline_corr == wp->w_topline) { + wp->w_topline = wp->w_save_cursor.w_topline_save; + } + } + } +} /* * A snapshot of the window sizes, to restore them after closing the help