diff --git a/src/Makefile b/src/Makefile index d49c5f46ff..9a65df0499 100644 --- a/src/Makefile +++ b/src/Makefile @@ -2059,7 +2059,7 @@ test1 \ test_utf8 \ test_wordcount \ test2 test3 test4 test5 test6 test7 test8 test9 \ - test11 test12 test13 test14 test15 test17 test18 test19 \ + test11 test12 test14 test15 test17 test18 test19 \ test20 test21 test22 test23 test24 test25 test26 test27 test28 test29 \ test30 test31 test32 test33 test34 test36 test37 test38 test39 \ test40 test41 test42 test43 test44 test45 test48 test49 \ diff --git a/src/buffer.c b/src/buffer.c index 3d637c0dd5..aade1d4c52 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -450,6 +450,11 @@ close_buffer( int is_curbuf; int nwindows; bufref_T bufref; +# ifdef FEAT_WINDOWS + int is_curwin = (curwin!= NULL && curwin->w_buffer == buf); + win_T *the_curwin = curwin; + tabpage_T *the_curtab = curtab; +# endif #endif int unload_buf = (action != 0); int del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE); @@ -476,6 +481,16 @@ close_buffer( unload_buf = TRUE; #endif +#ifdef FEAT_AUTOCMD + /* Disallow deleting the buffer when it is locked (already being closed or + * halfway a command that relies on it). Unloading is allowed. */ + if (buf->b_locked > 0 && (del_buf || wipe_buf)) + { + EMSG(_("E937: Attempt to delete a buffer that is in use")); + return; + } +#endif + if (win != NULL #ifdef FEAT_WINDOWS && win_valid_any_tab(win) /* in case autocommands closed the window */ @@ -499,7 +514,7 @@ close_buffer( /* When the buffer is no longer in a window, trigger BufWinLeave */ if (buf->b_nwindows == 1) { - buf->b_closing = TRUE; + ++buf->b_locked; if (apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname, FALSE, buf) && !bufref_valid(&bufref)) @@ -509,7 +524,7 @@ aucmd_abort: EMSG(_(e_auabort)); return; } - buf->b_closing = FALSE; + --buf->b_locked; if (abort_if_last && one_window()) /* Autocommands made this the only window. */ goto aucmd_abort; @@ -518,13 +533,13 @@ aucmd_abort: * BufHidden */ if (!unload_buf) { - buf->b_closing = TRUE; + ++buf->b_locked; if (apply_autocmds(EVENT_BUFHIDDEN, buf->b_fname, buf->b_fname, FALSE, buf) && !bufref_valid(&bufref)) /* Autocommands deleted the buffer. */ goto aucmd_abort; - buf->b_closing = FALSE; + --buf->b_locked; if (abort_if_last && one_window()) /* Autocommands made this the only window. */ goto aucmd_abort; @@ -534,6 +549,19 @@ aucmd_abort: return; # endif } + +# ifdef FEAT_WINDOWS + /* If the buffer was in curwin and the window has changed, go back to that + * window, if it still exists. This avoids that ":edit x" triggering a + * "tabnext" BufUnload autocmd leaves a window behind without a buffer. */ + if (is_curwin && curwin != the_curwin && win_valid_any_tab(the_curwin)) + { + block_autocmds(); + goto_tabpage_win(the_curtab, the_curwin); + unblock_autocmds(); + } +# endif + nwindows = buf->b_nwindows; #endif @@ -683,13 +711,13 @@ buf_freeall(buf_T *buf, int flags) int is_curbuf = (buf == curbuf); bufref_T bufref; # ifdef FEAT_WINDOWS - int is_curwin = (curwin!= NULL && curwin->w_buffer == buf); + int is_curwin = (curwin != NULL && curwin->w_buffer == buf); win_T *the_curwin = curwin; tabpage_T *the_curtab = curtab; # endif /* Make sure the buffer isn't closed by autocommands. */ - buf->b_closing = TRUE; + ++buf->b_locked; set_bufref(&bufref, buf); if (buf->b_ml.ml_mfp != NULL) { @@ -715,7 +743,7 @@ buf_freeall(buf_T *buf, int flags) /* autocommands deleted the buffer */ return; } - buf->b_closing = FALSE; + --buf->b_locked; # ifdef FEAT_WINDOWS /* If the buffer was in curwin and the window has changed, go back to that @@ -748,7 +776,7 @@ buf_freeall(buf_T *buf, int flags) #endif #ifdef FEAT_SYN_HL /* Remove any ownsyntax, unless exiting. */ - if (firstwin != NULL && curwin->w_buffer == buf) + if (curwin != NULL && curwin->w_buffer == buf) reset_synblock(curwin); #endif @@ -764,7 +792,7 @@ buf_freeall(buf_T *buf, int flags) clearFolding(win); } # else - if (curwin->w_buffer == buf) + if (curwin != NULL && curwin->w_buffer == buf) clearFolding(curwin); # endif #endif @@ -1373,7 +1401,7 @@ do_buffer( */ while (buf == curbuf # ifdef FEAT_AUTOCMD - && !(curwin->w_closing || curwin->w_buffer->b_closing) + && !(curwin->w_closing || curwin->w_buffer->b_locked > 0) # endif && (firstwin != lastwin || first_tabpage->tp_next != NULL)) { @@ -5110,7 +5138,7 @@ ex_buffer_all(exarg_T *eap) #endif ) && firstwin != lastwin #ifdef FEAT_AUTOCMD - && !(wp->w_closing || wp->w_buffer->b_closing) + && !(wp->w_closing || wp->w_buffer->b_locked > 0) #endif ) { diff --git a/src/channel.c b/src/channel.c index 75359da5cb..7296e87d3b 100644 --- a/src/channel.c +++ b/src/channel.c @@ -2163,7 +2163,7 @@ channel_exe_cmd(channel_T *channel, int part, typval_T *argv) } else { - typval_T *tv; + typval_T *tv = NULL; typval_T res_tv; typval_T err_tv; char_u *json = NULL; @@ -2180,8 +2180,6 @@ channel_exe_cmd(channel_T *channel, int part, typval_T *argv) ch_logs(channel, "Calling '%s'", (char *)arg); if (func_call(arg, &argv[2], NULL, NULL, &res_tv) == OK) tv = &res_tv; - else - tv = NULL; } if (argv[id_idx].v_type == VAR_NUMBER) @@ -2195,11 +2193,9 @@ channel_exe_cmd(channel_T *channel, int part, typval_T *argv) /* If evaluation failed or the result can't be encoded * then return the string "ERROR". */ vim_free(json); - free_tv(tv); err_tv.v_type = VAR_STRING; err_tv.vval.v_string = (char_u *)"ERROR"; - tv = &err_tv; - json = json_encode_nr_expr(id, tv, options | JSON_NL); + json = json_encode_nr_expr(id, &err_tv, options | JSON_NL); } if (json != NULL) { @@ -2212,7 +2208,7 @@ channel_exe_cmd(channel_T *channel, int part, typval_T *argv) --emsg_skip; if (tv == &res_tv) clear_tv(tv); - else if (tv != &err_tv) + else free_tv(tv); } } diff --git a/src/evalfunc.c b/src/evalfunc.c index 78974e6c73..15b06dd70f 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -7648,7 +7648,7 @@ max_min(typval_T *argvars, typval_T *rettv, int domax) } } else - EMSG(_(e_listdictarg)); + EMSG2(_(e_listdictarg), domax ? "max()" : "min()"); rettv->vval.v_number = error ? 0 : n; } diff --git a/src/ex_cmds.c b/src/ex_cmds.c index 61ab2ab125..daae0dd6fd 100644 --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -3872,8 +3872,8 @@ do_ecmd( oldbuf = TRUE; set_bufref(&bufref, buf); (void)buf_check_timestamp(buf, FALSE); - /* Check if autocommands made buffer invalid or changed the current - * buffer. */ + /* Check if autocommands made the buffer invalid or changed the + * current buffer. */ if (!bufref_valid(&bufref) #ifdef FEAT_AUTOCMD || curbuf != old_curbuf.br_buf @@ -3938,8 +3938,9 @@ do_ecmd( win_T *the_curwin = curwin; /* Set the w_closing flag to avoid that autocommands close the - * window. */ + * window. And set b_locked for the same reason. */ the_curwin->w_closing = TRUE; + ++buf->b_locked; if (curbuf == old_curbuf.br_buf) #endif @@ -3953,6 +3954,7 @@ do_ecmd( #ifdef FEAT_AUTOCMD the_curwin->w_closing = FALSE; + --buf->b_locked; # ifdef FEAT_EVAL /* autocmds may abort script processing */ @@ -4139,11 +4141,6 @@ do_ecmd( /* Assume success now */ retval = OK; - /* - * Reset cursor position, could be used by autocommands. - */ - check_cursor(); - /* * Check if we are editing the w_arg_idx file in the argument list. */ diff --git a/src/ex_docmd.c b/src/ex_docmd.c index 2f5bc1b084..27482f0b86 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -7218,7 +7218,7 @@ ex_quit(exarg_T *eap) /* Refuse to quit when locked or when the buffer in the last window is * being closed (can only happen in autocommands). */ if (curbuf_locked() || (wp->w_buffer->b_nwindows == 1 - && wp->w_buffer->b_closing)) + && wp->w_buffer->b_locked > 0)) return; #endif @@ -7300,7 +7300,7 @@ ex_quit_all(exarg_T *eap) apply_autocmds(EVENT_QUITPRE, NULL, NULL, FALSE, curbuf); /* Refuse to quit when locked or when the buffer in the last window is * being closed (can only happen in autocommands). */ - if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_closing)) + if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) return; #endif @@ -7682,7 +7682,7 @@ ex_exit(exarg_T *eap) apply_autocmds(EVENT_QUITPRE, NULL, NULL, FALSE, curbuf); /* Refuse to quit when locked or when the buffer in the last window is * being closed (can only happen in autocommands). */ - if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_closing)) + if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) return; #endif diff --git a/src/misc2.c b/src/misc2.c index a5da94f650..9fbd01c4cc 100644 --- a/src/misc2.c +++ b/src/misc2.c @@ -504,6 +504,28 @@ get_cursor_rel_lnum( return retval; } +/* + * Make sure "pos.lnum" and "pos.col" are valid in "buf". + * This allows for the col to be on the NUL byte. + */ + void +check_pos(buf_T *buf, pos_T *pos) +{ + char_u *line; + colnr_T len; + + if (pos->lnum > buf->b_ml.ml_line_count) + pos->lnum = buf->b_ml.ml_line_count; + + if (pos->col > 0) + { + line = ml_get_buf(buf, pos->lnum, FALSE); + len = (colnr_T)STRLEN(line); + if (pos->col > len) + pos->col = len; + } +} + /* * Make sure curwin->w_cursor.lnum is valid. */ diff --git a/src/move.c b/src/move.c index 7f657c1c08..5d146f2227 100644 --- a/src/move.c +++ b/src/move.c @@ -2508,6 +2508,7 @@ onepage(int dir, long count) foldAdjustCursor(); #endif cursor_correct(); + check_cursor_col(); if (retval == OK) beginline(BL_SOL | BL_FIX); curwin->w_valid &= ~(VALID_WCOL|VALID_WROW|VALID_VIRTCOL); diff --git a/src/normal.c b/src/normal.c index 1f04172db8..f2e2197a1f 100644 --- a/src/normal.c +++ b/src/normal.c @@ -9485,7 +9485,10 @@ get_op_vcol( #ifdef FEAT_MBYTE /* prevent from moving onto a trail byte */ if (has_mbyte) + { + check_pos(curwin->w_buffer, &oap->end); mb_adjustpos(curwin->w_buffer, &oap->end); + } #endif getvvcol(curwin, &(oap->start), &oap->start_vcol, NULL, &oap->end_vcol); diff --git a/src/ops.c b/src/ops.c index c03c7be2e7..4bef6c5a21 100644 --- a/src/ops.c +++ b/src/ops.c @@ -4741,6 +4741,7 @@ fex_format( int use_sandbox = was_set_insecurely((char_u *)"formatexpr", OPT_LOCAL); int r; + char_u *fex; /* * Set v:lnum to the first line number and v:count to the number of lines. @@ -4750,16 +4751,22 @@ fex_format( set_vim_var_nr(VV_COUNT, count); set_vim_var_char(c); + /* Make a copy, the option could be changed while calling it. */ + fex = vim_strsave(curbuf->b_p_fex); + if (fex == NULL) + return 0; + /* * Evaluate the function. */ if (use_sandbox) ++sandbox; - r = (int)eval_to_number(curbuf->b_p_fex); + r = (int)eval_to_number(fex); if (use_sandbox) --sandbox; set_vim_var_string(VV_CHAR, NULL, -1); + vim_free(fex); return r; } diff --git a/src/proto/misc2.pro b/src/proto/misc2.pro index 70c7dae5b6..d18ae20330 100644 --- a/src/proto/misc2.pro +++ b/src/proto/misc2.pro @@ -12,6 +12,7 @@ int dec_cursor(void); int dec(pos_T *lp); int decl(pos_T *lp); linenr_T get_cursor_rel_lnum(win_T *wp, linenr_T lnum); +void check_pos(buf_T *buf, pos_T *pos); void check_cursor_lnum(void); void check_cursor_col(void); void check_cursor_col_win(win_T *win); diff --git a/src/structs.h b/src/structs.h index e58c492234..dd7a7a2925 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1848,8 +1848,8 @@ struct file_buffer int b_flags; /* various BF_ flags */ #ifdef FEAT_AUTOCMD - int b_closing; /* buffer is being closed, don't let - autocommands close it too. */ + int b_locked; /* Buffer is being closed or referenced, don't + let autocommands wipe it out. */ #endif /* diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak index b038605fbc..5a7099337d 100644 --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -111,7 +111,6 @@ SCRIPTS_MORE1 = \ SCRIPTS_MORE2 = \ test2.out \ test12.out \ - test13.out \ test25.out \ test49.out \ test97.out \ diff --git a/src/testdir/test13.in b/src/testdir/test13.in deleted file mode 100644 index cbf78c7367..0000000000 --- a/src/testdir/test13.in +++ /dev/null @@ -1,64 +0,0 @@ -Tests for autocommands on :close command - -Write three files and open them, each in a window. -Then go to next window, with autocommand that deletes the previous one. -Do this twice, writing the file. - -Also test deleting the buffer on a Unload event. If this goes wrong there -will be the ATTENTION prompt. - -Also test changing buffers in a BufDel autocommand. If this goes wrong there -are ml_line errors and/or a Crash. - -STARTTEST -:so small.vim -:/^start of testfile/,/^end of testfile/w! Xtestje1 -:/^start of testfile/,/^end of testfile/w! Xtestje2 -:/^start of testfile/,/^end of testfile/w! Xtestje3 -:e Xtestje1 -otestje1 -:w -:sp Xtestje2 -otestje2 -:w -:sp Xtestje3 -otestje3 -:w - -:au WinLeave Xtestje2 bwipe - -:w! test.out -:au WinLeave Xtestje1 bwipe Xtestje3 -:close -:w >>test.out -:e Xtestje1 -:bwipe Xtestje2 Xtestje3 test.out -:au! -:au! BufUnload Xtestje1 bwipe -:e Xtestje3 -:w >>test.out -:e Xtestje2 -:sp Xtestje1 -:e -:w >>test.out -:au! -:only -:e Xtestje1 -:bwipe Xtestje2 Xtestje3 test.out test13.in -:au BufWipeout Xtestje1 buf Xtestje1 -:bwipe -:w >>test.out -:only -:help -:wincmd w -:1quit -:$put ='Final line' -:$w >>test.out -:qa! -ENDTEST - -start of testfile - contents - contents - contents -end of testfile diff --git a/src/testdir/test13.ok b/src/testdir/test13.ok deleted file mode 100644 index 66ebce63f7..0000000000 --- a/src/testdir/test13.ok +++ /dev/null @@ -1,31 +0,0 @@ -start of testfile -testje1 - contents - contents - contents -end of testfile -start of testfile -testje1 - contents - contents - contents -end of testfile -start of testfile -testje3 - contents - contents - contents -end of testfile -start of testfile -testje2 - contents - contents - contents -end of testfile -start of testfile -testje1 - contents - contents - contents -end of testfile -Final line diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim index 8a44ab89bf..e18566b574 100644 --- a/src/testdir/test_autocmd.vim +++ b/src/testdir/test_autocmd.vim @@ -77,11 +77,59 @@ function Test_autocmd_bufunload_with_tabnext() quit call assert_equal(2, tabpagenr('$')) + autocmd! test_autocmd_bufunload_with_tabnext_group augroup! test_autocmd_bufunload_with_tabnext_group tablast quit endfunc +function Test_autocmd_bufwinleave_with_tabfirst() + tabedit + augroup sample + autocmd! + autocmd BufWinLeave tabfirst + augroup END + call setline(1, ['a', 'b', 'c']) + edit! a.txt +endfunc + +" SEGV occurs in older versions. (At least 7.4.2321 or older) +function Test_autocmd_bufunload_avoiding_SEGV_01() + split aa.txt + let lastbuf = bufnr('$') + + augroup test_autocmd_bufunload + autocmd! + exe 'autocmd BufUnload ' . (lastbuf + 1) . 'bwipeout!' + augroup END + + call assert_fails('edit bb.txt', 'E937:') + + autocmd! test_autocmd_bufunload + augroup! test_autocmd_bufunload + bwipe! aa.txt + bwipe! bb.txt +endfunc + +" SEGV occurs in older versions. (At least 7.4.2321 or older) +function Test_autocmd_bufunload_avoiding_SEGV_02() + setlocal buftype=nowrite + let lastbuf = bufnr('$') + + augroup test_autocmd_bufunload + autocmd! + exe 'autocmd BufUnload ' . (lastbuf + 1) . 'bwipeout!' + augroup END + + normal! i1 + call assert_fails('edit a.txt', 'E517:') + call feedkeys("\") + + autocmd! test_autocmd_bufunload + augroup! test_autocmd_bufunload + bwipe! a.txt +endfunc + func Test_win_tab_autocmd() let g:record = [] @@ -196,3 +244,63 @@ func Test_augroup_deleted() au! VimEnter endfunc +" Tests for autocommands on :close command. +" This used to be in test13. +func Test_three_windows() + " Write three files and open them, each in a window. + " Then go to next window, with autocommand that deletes the previous one. + " Do this twice, writing the file. + e! Xtestje1 + call setline(1, 'testje1') + w + sp Xtestje2 + call setline(1, 'testje2') + w + sp Xtestje3 + call setline(1, 'testje3') + w + wincmd w + au WinLeave Xtestje2 bwipe + wincmd w + call assert_equal('Xtestje1', expand('%')) + + au WinLeave Xtestje1 bwipe Xtestje3 + close + call assert_equal('Xtestje1', expand('%')) + + " Test deleting the buffer on a Unload event. If this goes wrong there + " will be the ATTENTION prompt. + e Xtestje1 + au! + au! BufUnload Xtestje1 bwipe + call assert_fails('e Xtestje3', 'E937:') + call assert_equal('Xtestje3', expand('%')) + + e Xtestje2 + sp Xtestje1 + call assert_fails('e', 'E937:') + call assert_equal('Xtestje2', expand('%')) + + " Test changing buffers in a BufWipeout autocommand. If this goes wrong + " there are ml_line errors and/or a Crash. + au! + only + e Xanother + e Xtestje1 + bwipe Xtestje2 + bwipe Xtestje3 + au BufWipeout Xtestje1 buf Xtestje1 + bwipe + call assert_equal('Xanother', expand('%')) + + only + help + wincmd w + 1quit + call assert_equal('Xanother', expand('%')) + + au! + call delete('Xtestje1') + call delete('Xtestje2') + call delete('Xtestje3') +endfunc diff --git a/src/testdir/test_expr.vim b/src/testdir/test_expr.vim index 3097daf443..108ee508ea 100644 --- a/src/testdir/test_expr.vim +++ b/src/testdir/test_expr.vim @@ -335,6 +335,13 @@ function Test_printf_errors() call assert_fails('echo printf("%d", 1.2)', 'E805:') endfunc +function Test_max_min_errors() + call assert_fails('call max(v:true)', 'E712:') + call assert_fails('call max(v:true)', 'max()') + call assert_fails('call min(v:true)', 'E712:') + call assert_fails('call min(v:true)', 'min()') +endfunc + function Test_printf_64bit() if has('num64') call assert_equal("123456789012345", printf('%d', 123456789012345)) diff --git a/src/testdir/test_normal.vim b/src/testdir/test_normal.vim index ff6710218d..34561ffbab 100644 --- a/src/testdir/test_normal.vim +++ b/src/testdir/test_normal.vim @@ -192,6 +192,30 @@ func! Test_normal05_formatexpr() bw! endfu +func Test_normal05_formatexpr_newbuf() + " Edit another buffer in the 'formatexpr' function + new + func! Format() + edit another + endfunc + set formatexpr=Format() + norm gqG + bw! + set formatexpr= +endfunc + +func Test_normal05_formatexpr_setopt() + " Change the 'formatexpr' value in the function + new + func! Format() + set formatexpr= + endfunc + set formatexpr=Format() + norm gqG + bw! + set formatexpr= +endfunc + func! Test_normal06_formatprg() " basic test for formatprg " only test on non windows platform @@ -398,6 +422,15 @@ func! Test_normal14_page() bw! endfu +func! Test_normal14_page_eol() + 10new + norm oxxxxxxx + exe "norm 2\" + " check with valgrind that cursor is put back in column 1 + exe "norm 2\" + bw! +endfunc + func! Test_normal15_z_scroll_vert() " basic test for z commands that scroll the window call Setup_NewWindow() diff --git a/src/version.c b/src/version.c index 13e01086ac..515714d8bb 100644 --- a/src/version.c +++ b/src/version.c @@ -778,6 +778,24 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2330, +/**/ + 2329, +/**/ + 2328, +/**/ + 2327, +/**/ + 2326, +/**/ + 2325, +/**/ + 2324, +/**/ + 2323, +/**/ + 2322, /**/ 2321, /**/ diff --git a/src/window.c b/src/window.c index 9b81072e19..033ac551cf 100644 --- a/src/window.c +++ b/src/window.c @@ -2132,7 +2132,7 @@ close_windows( { if (wp->w_buffer == buf && (!keep_curwin || wp != curwin) #ifdef FEAT_AUTOCMD - && !(wp->w_closing || wp->w_buffer->b_closing) + && !(wp->w_closing || wp->w_buffer->b_locked > 0) #endif ) { @@ -2153,7 +2153,7 @@ close_windows( for (wp = tp->tp_firstwin; wp != NULL; wp = wp->w_next) if (wp->w_buffer == buf #ifdef FEAT_AUTOCMD - && !(wp->w_closing || wp->w_buffer->b_closing) + && !(wp->w_closing || wp->w_buffer->b_locked > 0) #endif ) { @@ -2292,7 +2292,8 @@ win_close(win_T *win, int free_buf) } #ifdef FEAT_AUTOCMD - if (win->w_closing || (win->w_buffer != NULL && win->w_buffer->b_closing)) + if (win->w_closing || (win->w_buffer != NULL + && win->w_buffer->b_locked > 0)) return FAIL; /* window is already being closed */ if (win == aucmd_win) { @@ -2508,7 +2509,8 @@ win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) #ifdef FEAT_AUTOCMD /* Get here with win->w_buffer == NULL when win_close() detects the tab * page changed. */ - if (win->w_closing || (win->w_buffer != NULL && win->w_buffer->b_closing)) + if (win->w_closing || (win->w_buffer != NULL + && win->w_buffer->b_locked > 0)) return; /* window is already being closed */ #endif