diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 2b89c72742..b014a569a1 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -13,6 +13,7 @@ #include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/globals.h" +#include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/memory_defs.h" #include "nvim/option.h" @@ -99,8 +100,10 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * } /// Create a dummy buffer and run the FileType autocmd on it. -static buf_T *do_ft_buf(char *filetype, aco_save_T *aco, Error *err) +static buf_T *do_ft_buf(const char *filetype, aco_save_T *aco, bool *aco_used, Error *err) + FUNC_ATTR_NONNULL_ARG(2, 3, 4) { + *aco_used = false; if (filetype == NULL) { return NULL; } @@ -112,19 +115,37 @@ static buf_T *do_ft_buf(char *filetype, aco_save_T *aco, Error *err) return NULL; } + // Open a memline for use by autocommands. + if (ml_open(ftbuf) == FAIL) { + api_set_error(err, kErrorTypeException, "Could not load internal buffer"); + return ftbuf; + } + + bufref_T bufref; + set_bufref(&bufref, ftbuf); + // Set curwin/curbuf to buf and save a few things. aucmd_prepbuf(aco, ftbuf); + *aco_used = true; + + set_option_direct(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, SID_NONE); + set_option_direct(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, SID_NONE); + assert(ftbuf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already + ftbuf->b_p_swf = false; + ftbuf->b_p_ml = false; + ftbuf->b_p_ft = xstrdup(filetype); TRY_WRAP(err, { - set_option_value(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL); - set_option_value(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL); - set_option_value(kOptSwapfile, BOOLEAN_OPTVAL(false), OPT_LOCAL); - set_option_value(kOptModeline, BOOLEAN_OPTVAL(false), OPT_LOCAL); // 'nomodeline' - - ftbuf->b_p_ft = xstrdup(filetype); do_filetype_autocmd(ftbuf, false); }); + if (!bufref_valid(&bufref)) { + if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, "Internal buffer was deleted"); + } + return NULL; + } + return ftbuf; } @@ -161,13 +182,15 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) } aco_save_T aco; + bool aco_used; - buf_T *ftbuf = do_ft_buf(filetype, &aco, err); + buf_T *ftbuf = do_ft_buf(filetype, &aco, &aco_used, err); if (ERROR_SET(err)) { - if (ftbuf != NULL) { + if (aco_used) { // restore curwin/curbuf and a few other things aucmd_restbuf(&aco); - + } + if (ftbuf != NULL) { assert(curbuf != ftbuf); // safety check wipe_buffer(ftbuf, false); } @@ -183,9 +206,10 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) OptVal value = get_option_value_for(opt_idx, opt_flags, scope, from, err); if (ftbuf != NULL) { - // restore curwin/curbuf and a few other things - aucmd_restbuf(&aco); - + if (aco_used) { + // restore curwin/curbuf and a few other things + aucmd_restbuf(&aco); + } assert(curbuf != ftbuf); // safety check wipe_buffer(ftbuf, false); } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 6039d7196e..323b66ecbc 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1977,6 +1977,42 @@ describe('API', function() ]]) eq(false, api.nvim_get_option_value('modified', {})) end) + + it('errors if autocmds wipe the dummy buffer', function() + -- Wipe the dummy buffer. This will throw E813, but the buffer will still be wiped; check that + -- such errors from setting the filetype have priority. + command 'autocmd FileType * ++once bwipeout!' + eq( + 'FileType Autocommands for "*": Vim(bwipeout):E813: Cannot close autocmd window', + pcall_err(api.nvim_get_option_value, 'formatexpr', { filetype = 'lua' }) + ) + + -- Silence E813 to check that the error for wiping the dummy buffer is set. + command 'autocmd FileType * ++once silent! bwipeout!' + eq( + 'Internal buffer was deleted', + pcall_err(api.nvim_get_option_value, 'formatexpr', { filetype = 'lua' }) + ) + end) + + it('sets dummy buffer options without side-effects', function() + exec [[ + let g:events = [] + autocmd OptionSet * let g:events += [expand("")] + autocmd FileType * ++once let g:bufhidden = &l:bufhidden + \| let g:buftype = &l:buftype + \| let g:swapfile = &l:swapfile + \| let g:modeline = &l:modeline + \| let g:bufloaded = bufloaded(bufnr()) + ]] + api.nvim_get_option_value('formatexpr', { filetype = 'lua' }) + eq({}, eval('g:events')) + eq('hide', eval('g:bufhidden')) + eq('nofile', eval('g:buftype')) + eq(0, eval('g:swapfile')) + eq(0, eval('g:modeline')) + eq(1, eval('g:bufloaded')) + end) end) describe('nvim_{get,set}_current_buf, nvim_list_bufs', function()