diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index c0a7b73438727d..a3f50fe354a542 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -6736,8 +6736,10 @@ A jump table for the options with a short description can be found at |Q_op|. global When bigger than zero, Vim will give messages about what it is doing. Currently, these messages are given: - >= 1 Lua assignments to options,keymaps etc. - >= 2 When a file is ":source"'ed and when the shada file is read or written.. + >= 1 Lua assignments to options, keymaps, etc. (applies only to + scripts sourced after this is set). + >= 2 When a file is ":source"'ed and when the shada file is read or + written. >= 3 UI info, terminal capabilities >= 4 Shell commands. >= 5 Every searched tags file and include file. diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 75ee0fdfdf3667..8047354e45bfb8 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -459,13 +459,14 @@ g8 Print the hex values of the bytes used in the 'verbosefile' option. *:verbose-cmd* -When 'verbose' is non-zero, listing the value of a Vim option or a key map or -an abbreviation or a user-defined function or a command or a highlight group -or an autocommand will also display where it was last defined. If they were -defined in Lua they will only be located if 'verbose' is set. So Start -nvim with -V1 arg to see them. If it was defined manually then there -will be no "Last set" message. When it was defined while executing a function, -user command or autocommand, the script in which it was defined is reported. +When 'verbose' is non-zero, listing the value of a Vim option, key map, +abbreviation, user-defined function or command, highlight group or an +autocommand will also display where it was last defined. If they were defined +from Lua they will be located if 'verbose' was set before the script was +sourced, so it is recommended to start Nvim with the "-V1" argument to see +them. If it was defined manually then there will be no "Last set" message. +When it was defined while executing a function, user command or autocommand, +the script in which it was defined is reported. *K* [count]K Runs the program given by 'keywordprg' to lookup the diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 88954a1aa277be..eb1297d7e32cf5 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1613,19 +1613,6 @@ void add_user_command(String name, Object command, Dict(user_command) *opts, int NLUA_CLEAR_REF(compl_luaref); } -int find_sid(uint64_t channel_id) -{ - switch (channel_id) { - case VIML_INTERNAL_CALL: - // TODO(autocmd): Figure out what this should be - // return SID_API_CLIENT; - case LUA_INTERNAL_CALL: - return SID_LUA; - default: - return SID_API_CLIENT; - } -} - /// Sets sctx for API calls. /// /// @param channel_id api clients id. Used to determine if it's a internal @@ -1636,8 +1623,7 @@ sctx_T api_set_sctx(uint64_t channel_id) { sctx_T old_current_sctx = current_sctx; if (channel_id != VIML_INTERNAL_CALL) { - current_sctx.sc_sid = - channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; + current_sctx.sc_sid = channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; current_sctx.sc_lnum = 0; } return old_current_sctx; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2571fe0c25b6c1..4775764285f66f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6638,7 +6638,7 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref, FunPtr int arg_idx = 0; list_T *list = NULL; if (STRNCMP(s, "s:", 2) == 0 || STRNCMP(s, "", 5) == 0) { - char sid_buf[25]; + char sid_buf[SIDBUFLEN]; int off = *s == 's' ? 2 : 5; // Expand s: and into nr_, so that the function can @@ -9296,31 +9296,7 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, cons *d = &funccal->l_avars; } else if (*name == 'l' && funccal != NULL) { // local variable *d = &funccal->l_vars; - } else if (*name == 's' // script variable - && (current_sctx.sc_sid > 0 || current_sctx.sc_sid == SID_LUA)) { - // Create SID if s: scope is accessed from Lua or anon Vimscript. #15994 - if (current_sctx.sc_sid == SID_LUA) { - // Try to resolve Lua file name & line no so it can be shown in LastSet messages. - nlua_set_sctx(¤t_sctx); - if (current_sctx.sc_sid != SID_LUA) { - // We have a valid SID associated with a Lua file name. - // Create a new SID with the same fname and set it as the current. - // This keeps the usual behaviour of disallowing different anonymous execs - // in the same file from accessing each others script-local stuff. - const LastSet last_set = (LastSet){ - .script_ctx = current_sctx, - .channel_id = LUA_INTERNAL_CALL, - }; - // should_free should be true, as the script has a file name. - // Because script_new_sid consumes sc_name, we don't call free. - bool should_free; - char_u *const sc_name = get_scriptname(last_set, &should_free); - assert(should_free); - current_sctx.sc_sid = script_new_sid(sc_name); - } else { - current_sctx.sc_sid = script_new_sid(NULL); - } - } + } else if (*name == 's' && current_sctx.sc_sid > 0) { // script variable // "s:" hash map is lazily allocated; ensure it's allocated now. scriptvar_T *sv = script_sv(current_sctx.sc_sid); if (sv == NULL) { diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 40dc819754bac8..cec43ecc0d00f6 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -267,9 +267,11 @@ struct blobvar_S { }; /// Type used for script ID -typedef int scid_T; +typedef int64_t scid_T; /// Format argument for scid_T -#define PRIdSCID "d" +#define PRIdSCID PRId64 +/// Buffer size required to hold "", a (not special) script ID, underscore and NUL. +#define SIDBUFLEN (26) // SCript ConteXt (SCTX): identifies a script line. // When sourcing a script "sc_lnum" is zero, "sourcing_lnum" is the current diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 78707e9f9960a6..4ac8f83cc82b9d 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -474,7 +474,7 @@ int get_func_tv(const char_u *name, int len, typval_T *rettv, char_u **arg, func return ret; } -#define FLEN_FIXED 40 +#define FLEN_FIXED (SIDBUFLEN + 28) /// Check whether function name starts with or s: /// @@ -1844,7 +1844,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, } size_t sid_buf_len = 0; - char sid_buf[20]; + char sid_buf[SIDBUFLEN]; // Copy the function name to allocated memory. // Accept name() inside a script, translate into 123_name(). diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 3d52b7b19dc411..53cf797fef7f09 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -79,7 +79,6 @@ typedef struct scriptitem_S { int sn_prl_execed; ///< line being timed was executed } scriptitem_T; -static scid_T last_current_SID = 0; static int last_current_SID_seq = 0; static PMap(scid_T) script_items = MAP_INIT; @@ -1839,18 +1838,20 @@ static scriptitem_T *new_script_item(const scid_T id, char_u *const name) return si; } -/// Create a new ID for a script so "s:" scope vars can be used. -/// @param fname allocated file name of the script. NULL for anonymous :source. -/// @return SID of the new script. -scid_T script_new_sid(char_u *const fname) +/// @param from_lua true if the script is created within a Lua context. +/// @return a new unique script ID. +scid_T sid_new(const bool from_lua) FUNC_ATTR_WARN_UNUSED_RESULT { - const scid_T sid = ++last_current_SID; - // Anonymous :sources don't require an allocated script item. - if (fname != NULL) { - (void)new_script_item(sid, fname); - } - return sid; + static scid_T last_current_SID = 0; + return ++last_current_SID + (from_lua && nlua_is_verbose() ? SID_FROM_LUA : 0); +} + +/// @return true if a SID refers to a script from a Lua context. +bool sid_is_from_lua(const scid_T sid) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST +{ + return sid == SID_LUA || sid >= SID_FROM_LUA; } static int source_using_linegetter(void *cookie, LineGetter fgetline, const char *traceback_name, @@ -1909,13 +1910,14 @@ static void cmd_source_buffer(const exarg_T *const eap) .buf = ga.ga_data, .offset = 0, }; + if (curbuf->b_fname && path_with_extension((const char *)curbuf->b_fname, "lua")) { nlua_source_using_linegetter(get_str_line, (void *)&cookie, ":source (no file)"); } else { scid_T sid = map_get(handle_T, scid_T)(&buf_sids, curbuf->handle); if (sid == 0) { // First time sourcing this buffer, use a new SID for it. - sid = ++last_current_SID; + sid = sid_new(false); map_put(handle_T, scid_T)(&buf_sids, curbuf->handle, sid); } source_using_linegetter((void *)&cookie, get_str_line, ":source (no file)", sid); @@ -1932,8 +1934,8 @@ int do_source_str(const char *cmd, const char *traceback_name) .buf = (char_u *)cmd, .offset = 0, }; - const scid_T sid = current_sctx.sc_sid == SID_LUA ? SID_LUA : ++last_current_SID; - return source_using_linegetter((void *)&cookie, get_str_line, traceback_name, sid); + const scid_T sid = sid_new(sid_is_from_lua(current_sctx.sc_sid)); + return source_using_linegetter(&cookie, get_str_line, traceback_name, sid); } /// When fname is a 'lua' file nlua_exec_file() is invoked to source it. @@ -2081,7 +2083,8 @@ int do_source(char *fname, int check_other, int is_vimrc) save_funccal(&funccalp_entry); const sctx_T save_current_sctx = current_sctx; - si = new_file_sctx(fname_exp, ¤t_sctx); + const bool is_lua = path_with_extension((const char *)fname, "lua"); + si = new_file_sctx(fname_exp, ¤t_sctx, is_lua); if (l_do_profiling == PROF_YES) { bool forceit = false; @@ -2114,10 +2117,9 @@ int do_source(char *fname, int check_other, int is_vimrc) firstline = p; } - if (path_with_extension((const char *)fname_exp, "lua")) { + if (is_lua) { const sctx_T current_sctx_backup = current_sctx; const linenr_T sourcing_lnum_backup = sourcing_lnum; - current_sctx.sc_sid = SID_LUA; current_sctx.sc_lnum = 0; sourcing_lnum = 0; // Source the file as lua @@ -2196,8 +2198,9 @@ int do_source(char *fname, int check_other, int is_vimrc) /// /// @param[in] fname file path of script. /// @param[out] ret_sctx sctx of this script. +/// @param is_lua true if a Lua script. /// @return pointer to the script item. -static scriptitem_T *new_file_sctx(char_u *fname, sctx_T *ret_sctx) +static scriptitem_T *new_file_sctx(char_u *const fname, sctx_T *const ret_sctx, const bool is_lua) FUNC_ATTR_NONNULL_ARG(1) { sctx_T script_sctx = { .sc_seq = ++last_current_SID_seq, @@ -2220,7 +2223,7 @@ static scriptitem_T *new_file_sctx(char_u *fname, sctx_T *ret_sctx) } } if (fi < 0) { - script_sctx.sc_sid = ++last_current_SID; + script_sctx.sc_sid = sid_new(is_lua); si = new_script_item(script_sctx.sc_sid, vim_strsave(fname)); GA_APPEND(scid_T, &file_sids, script_sctx.sc_sid); if (file_id_ok) { @@ -2235,15 +2238,28 @@ static scriptitem_T *new_file_sctx(char_u *fname, sctx_T *ret_sctx) return si; } -/// Create a new context referencing a script from its file name. -/// @param fname file name of script. -/// @return the new script context. -sctx_T script_new_sctx(char_u *const fname) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +/// @param sid ID of the script. +/// @return Name of the script. NULL for anonymous scripts and special SIDs. +char_u *script_name(const scid_T sid) + FUNC_ATTR_WARN_UNUSED_RESULT { - sctx_T sctx; - (void)get_script_item(fname, &sctx); - return sctx; + const scriptitem_T *const si = script_item(sid); + return si != NULL ? si->sn_name : NULL; +} + +/// Set the name of a script. +/// @note Allocates a script item for scripts without one. +/// @param sid ID of the script. +/// @param name Allocated new name of the script, or NULL to make it anonymous. +void script_set_name(const scid_T sid, char_u *const name) +{ + scriptitem_T *const si = script_item(sid); + if (si != NULL) { + xfree(si->sn_name); + si->sn_name = name; + } else { + new_script_item(sid, name); + } } /// ":scriptnames" @@ -2310,6 +2326,7 @@ char_u *get_scriptname(LastSet last_set, bool *should_free) case SID_STR: return (char_u *)_("anonymous :source"); default: { + assert(last_set.script_ctx.sc_sid > 0); const scriptitem_T *const si = script_item(last_set.script_ctx.sc_sid); if (si == NULL || si->sn_name == NULL) { snprintf((char *)IObuff, IOSIZE, _("anonymous :source (script id %" PRIdSCID ")"), diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 605a2183f358fe..adab8ff6a331fa 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -337,6 +337,9 @@ EXTERN int garbage_collect_at_exit INIT(= false); #define SID_API_CLIENT (-9) // for API clients #define SID_STR (-10) // for sourcing a string with no script item +/// Minimum SID for a script created in a Lua context when `nlua_is_verbose` is true. +#define SID_FROM_LUA ((scid_T)1 << 62) + // Script CTX being sourced or was sourced to define the current function. EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 }); // ID of the current channel making a client API call diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index c784141f3fc354..ec159da922ede3 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1241,7 +1241,7 @@ int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name) { const linenr_T save_sourcing_lnum = sourcing_lnum; const sctx_T save_current_sctx = current_sctx; - current_sctx.sc_sid = SID_STR; + current_sctx.sc_sid = SID_LUA; current_sctx.sc_seq = 0; current_sctx.sc_lnum = 0; sourcing_lnum = 0; @@ -1789,24 +1789,33 @@ void nlua_execute_on_key(int c) #endif } -// Sets the editor "script context" during Lua execution. Used by :verbose. -// @param[out] current -void nlua_set_sctx(sctx_T *current) +/// @return true if :verbose tracing is enabled for Lua scripts. ('verbose' >= 1) +bool nlua_is_verbose(void) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + return p_verbose >= 1; +} + +/// Sets script context to show the running Lua script & line no when `nlua_is_verbose` is true. +/// @note If the context refers to an anonymous script, it gets the name of the running +/// Lua script, which allocates a script item. +/// @param[in,out] sctx Script context. +void nlua_set_sctx(sctx_T *const sctx) FUNC_ATTR_NONNULL_ALL { - if (p_verbose <= 0 || current->sc_sid != SID_LUA) { + if (!nlua_is_verbose() || !sid_is_from_lua(sctx->sc_sid)) { return; } lua_State *const lstate = global_lstate; - lua_Debug *info = (lua_Debug *)xmalloc(sizeof(lua_Debug)); + lua_Debug *const info = xmalloc(sizeof(lua_Debug)); // Files where internal wrappers are defined so we can ignore them // like vim.o/opt etc are defined in _meta.lua - char *ignorelist[] = { + const char *const ignorelist[] = { "vim/_meta.lua", "vim/keymap.lua", }; - int ignorelist_size = sizeof(ignorelist) / sizeof(ignorelist[0]); + const int ignorelist_size = sizeof(ignorelist) / sizeof(ignorelist[0]); for (int level = 1; true; level++) { if (lua_getstack(lstate, level, info) != 1) { @@ -1832,11 +1841,17 @@ void nlua_set_sctx(sctx_T *current) } break; } - char *source_path = fix_fname(info->source + 1); - *current = script_new_sctx((char_u *)source_path); - xfree(source_path); - current->sc_lnum = info->currentline; - current->sc_seq = -1; + + if (script_name(sctx->sc_sid) == NULL) { + char_u *const source_path = (char_u *)fix_fname(info->source + 1); + if (sctx->sc_sid == SID_LUA) { + // Just create a script with this name. Don't use new_file_sctx! It adds to + // :scriptnames if the script wasn't explicitly :sourced. (e.g: :luafile) + sctx->sc_sid = sid_new(true); + } + script_set_name(sctx->sc_sid, source_path); + } + sctx->sc_lnum = info->currentline; cleanup: xfree(info); diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index f4a541dcfdb76a..1ac946858fe41a 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -574,6 +574,11 @@ describe('API', function() eq(NIL, meths.exec_lua('function xx(a,b)\nreturn a..b\nend',{})) eq("xy", meths.exec_lua('return xx(...)', {'x','y'})) + eq("1_", meths.exec_lua([[ + vim.api.nvim_exec("let g:a = expand('')", false) + return vim.g.a + ]], {})) + -- Deprecated name: nvim_execute_lua. eq("xy", meths.execute_lua('return xx(...)', {'x','y'})) end) diff --git a/test/functional/ex_cmds/verbose_spec.lua b/test/functional/ex_cmds/verbose_spec.lua index 0a745a3adf63c2..4a7a52ca0a2d25 100644 --- a/test/functional/ex_cmds/verbose_spec.lua +++ b/test/functional/ex_cmds/verbose_spec.lua @@ -10,12 +10,12 @@ local write_file = helpers.write_file local call_viml_function = helpers.meths.call_function describe('lua :verbose', function() - local script_location, script_file + local script_location, script_file, current_dir -- All test cases below use the same nvim instance. setup(function() clear{args={'-V1'}} script_file = 'test_verbose.lua' - local current_dir = call_viml_function('getcwd', {}) + current_dir = call_viml_function('getcwd', {}) current_dir = call_viml_function('fnamemodify', {current_dir, ':~'}) script_location = table.concat{current_dir, helpers.get_pathsep(), script_file} @@ -29,7 +29,9 @@ vim.keymap.set('n', 'key2', ':echo "test"') vim.api.nvim_exec("augroup test_group\ autocmd!\ autocmd FileType c setl cindent\ + autocmd User Foo let s:test += 1 | echom s:test\ augroup END\ + let s:test = 1336\ ", false) vim.api.nvim_command("command Bdelete :bd") @@ -107,6 +109,8 @@ test_group FileType c setl cindent Last set from %s line 7]], script_location), result) + + eq("1337", exec_capture(":doautocmd User Foo")) end) it('"Last set" for command defined by nvim_command', function() @@ -114,7 +118,7 @@ test_group FileType eq(string.format([[ Name Args Address Complete Definition Bdelete 0 :bd - Last set from %s line 13]], + Last set from %s line 15]], script_location), result) end) @@ -123,15 +127,15 @@ test_group FileType eq(string.format([[ Name Args Address Complete Definition TestCommand 0 :echo 'Hello' - Last set from %s line 14]], + Last set from %s line 16]], script_location), result) end) - it('"Last set for function', function() + it('"Last set" for function', function() local result = exec_capture(':verbose function Close_Window') eq(string.format([[ function Close_Window() abort - Last set from %s line 16 + Last set from %s line 18 1 wincmd - endfunction]], script_location), result) @@ -141,14 +145,42 @@ test_group FileType local result = exec_capture(':verbose set tw?') eq(string.format([[ textwidth=80 - Last set from %s line 23]], + Last set from %s line 25]], script_location), result) -- Different anon execs in the same file should have unique SIDs as usual. neq(eval('g:sid1'), eval('g:sid2')) -- Also shouldn't add duplicate names to :scriptnames output. - eq(string.format(" 2: %s", script_location), exec_capture(':scriptnames')) + eq(string.format("4611686018427387906: %s", script_location), exec_capture(':scriptnames')) + end) + + describe(':scriptnames', function() + local script2_file = "test.lua" + local script2_location = current_dir .. helpers.get_pathsep() .. script2_file + + setup(function() + write_file(script2_file, [[ + vim.api.nvim_exec("set cursorline", false)]]) + end) + teardown(function() os.remove(script2_location) end) + + it('does not show script sourced without :source', function() + exec('luafile ' .. script2_location) + eq(string.format("4611686018427387906: %s", script_location), exec_capture(':scriptnames')) + + -- Still check that :verbose output is correct. + eq(string.format([[ + cursorline + Last set from %s line 1]], + script2_location), exec_capture('set cursorline?')) + + exec('source ' .. script2_location) + eq(string.format([[ +4611686018427387906: %s +4611686018427387931: %s]], + script_location, script2_location), exec_capture(':scriptnames')) + end) end) end)