# HG changeset patch # User Bram Moolenaar # Date 1661355903 -7200 # Node ID 6b7020f3d8565ed8ee1fa0cf4eba6d7951594e25 # Parent db296237ca1d8fe8f566b844bb9d71db23bbb9fa patch 9.0.0253: a symlink to an autoload script results in two entries Commit: https://github.com/vim/vim/commit/753885b6c5b9021184daa94d32fd8bf025f1b488 Author: Bram Moolenaar Date: Wed Aug 24 16:30:36 2022 +0100 patch 9.0.0253: a symlink to an autoload script results in two entries Problem: A symlink to an autoload script results in two entries in the list of scripts, items expected in one are actually in the other. Solution: Have one script item refer to the actually sourced one. (closes #10960) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -253,7 +253,7 @@ getreg([{regname} [, 1 [, {list}]]]) String or List contents of a register getreginfo([{regname}]) Dict information about a register getregtype([{regname}]) String type of a register -getscriptinfo() List list of sourced scripts +getscriptinfo() List list of sourced scripts gettabinfo([{expr}]) List list of tab pages gettabvar({nr}, {varname} [, {def}]) any variable {varname} in tab {nr} or {def} @@ -4089,17 +4089,20 @@ getregtype([{regname}]) *getregtype( Can also be used as a |method|: > GetRegname()->getregtype() -getscriptinfo() *getscriptinfo()* +getscriptinfo() *getscriptinfo()* Returns a |List| with information about all the sourced Vim - scripts in the order they were sourced. (|:scriptinfo|) + scripts in the order they were sourced, like what + `:scriptnames` shows. Each item in the returned List is a |Dict| with the following items: autoload set to TRUE for a script that was used with - |import autoload| but was not actually sourced - yet. + `import autoload` but was not actually sourced + yet (see |import-autoload|). name vim script file name. sid script ID ||. + sourced if this script is an alias this is the script + ID of the actually sourced script, otherwise zero gettabinfo([{tabnr}]) *gettabinfo()* If {tabnr} is not specified, then information about all the diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -417,6 +417,10 @@ For writing a Vim script, see chapter 41 For a script that was used with `import autoload` but was not actually sourced yet an "A" is shown after the script ID. + For a script that was referred to by one name but + after resolving symbolic links got sourced with + another name the other script is after "->". E.g. + "20->22" means script 20 was sourced as script 22. {not available when compiled without the |+eval| feature} diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -1039,6 +1039,7 @@ get_lval( ufunc_T *ufunc; type_T *type; + import_check_sourced_sid(&import->imp_sid); lp->ll_sid = import->imp_sid; lp->ll_name = skipwhite(p + 1); p = find_name_end(lp->ll_name, NULL, NULL, fne_flags); diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -2953,6 +2953,7 @@ eval_variable( { if (rettv != NULL) { + // special value that is used in handle_subscript() rettv->v_type = VAR_ANY; rettv->vval.v_number = sid != 0 ? sid : import->imp_sid; } diff --git a/src/proto/vim9script.pro b/src/proto/vim9script.pro --- a/src/proto/vim9script.pro +++ b/src/proto/vim9script.pro @@ -12,6 +12,7 @@ void ex_export(exarg_T *eap); void free_imports_and_script_vars(int sid); void mark_imports_for_reload(int sid); void ex_import(exarg_T *eap); +void import_check_sourced_sid(int *sid); int find_exported(int sid, char_u *name, ufunc_T **ufunc, type_T **type, cctx_T *cctx, cstack_T *cstack, int verbose); char_u *vim9_declare_scriptvar(exarg_T *eap, char_u *arg); void update_vim9_script_var(int create, dictitem_T *di, char_u *name, int flags, typval_T *tv, type_T **type, int do_member); diff --git a/src/scriptfile.c b/src/scriptfile.c --- a/src/scriptfile.c +++ b/src/scriptfile.c @@ -1357,6 +1357,7 @@ do_source_ext( { source_cookie_T cookie; char_u *p; + char_u *fname_not_fixed = NULL; char_u *fname_exp; char_u *firstline = NULL; int retval = FAIL; @@ -1389,13 +1390,12 @@ do_source_ext( } else { - p = expand_env_save(fname); - if (p == NULL) - return retval; - fname_exp = fix_fname(p); - vim_free(p); + fname_not_fixed = expand_env_save(fname); + if (fname_not_fixed == NULL) + goto theend; + fname_exp = fix_fname(fname_not_fixed); if (fname_exp == NULL) - return retval; + goto theend; if (mch_isdir(fname_exp)) { smsg(_("Cannot source a directory: \"%s\""), fname); @@ -1602,14 +1602,15 @@ do_source_ext( int error = OK; // It's new, generate a new SID and initialize the scriptitem. - current_sctx.sc_sid = get_new_scriptitem(&error); + sid = get_new_scriptitem(&error); + current_sctx.sc_sid = sid; if (error == FAIL) goto almosttheend; - si = SCRIPT_ITEM(current_sctx.sc_sid); + si = SCRIPT_ITEM(sid); si->sn_name = fname_exp; fname_exp = vim_strsave(si->sn_name); // used for autocmd if (ret_sid != NULL) - *ret_sid = current_sctx.sc_sid; + *ret_sid = sid; // Remember the "is_vimrc" flag for when the file is sourced again. si->sn_is_vimrc = is_vimrc; @@ -1668,7 +1669,7 @@ do_source_ext( if (do_profiling == PROF_YES) { // Get "si" again, "script_items" may have been reallocated. - si = SCRIPT_ITEM(current_sctx.sc_sid); + si = SCRIPT_ITEM(sid); if (si->sn_prof_on) { profile_end(&si->sn_pr_start); @@ -1719,7 +1720,7 @@ almosttheend: // If "sn_save_cpo" is set that means we encountered "vim9script": restore // 'cpoptions', unless in the main .vimrc file. // Get "si" again, "script_items" may have been reallocated. - si = SCRIPT_ITEM(current_sctx.sc_sid); + si = SCRIPT_ITEM(sid); if (si->sn_save_cpo != NULL && si->sn_is_vimrc == DOSO_NONE) { if (STRCMP(p_cpo, CPO_VIM) != 0) @@ -1774,6 +1775,19 @@ almosttheend: apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, FALSE, curbuf); theend: + if (sid > 0 && ret_sid != NULL + && fname_not_fixed != NULL && fname_exp != NULL) + { + int not_fixed_sid = find_script_by_name(fname_not_fixed); + + // If "fname_not_fixed" is a symlink then we source the linked file. + // If the original name is in the script list we add the ID of the + // script that was actually sourced. + if (SCRIPT_ID_VALID(not_fixed_sid) && not_fixed_sid != sid) + SCRIPT_ITEM(not_fixed_sid)->sn_sourced_sid = sid; + } + + vim_free(fname_not_fixed); vim_free(fname_exp); sticky_cmdmod_flags = save_sticky_cmdmod_flags; #ifdef FEAT_EVAL @@ -1787,7 +1801,7 @@ do_source( char_u *fname, int check_other, // check for .vimrc and _vimrc int is_vimrc, // DOSO_ value - int *ret_sid UNUSED) + int *ret_sid) { return do_source_ext(fname, check_other, is_vimrc, ret_sid, NULL, FALSE); } @@ -1828,9 +1842,16 @@ ex_scriptnames(exarg_T *eap) if (si->sn_name != NULL) { + char sourced_buf[20]; + home_replace(NULL, si->sn_name, NameBuff, MAXPATHL, TRUE); - vim_snprintf((char *)IObuff, IOSIZE, "%3d%s: %s", + if (si->sn_sourced_sid > 0) + vim_snprintf(sourced_buf, 20, "->%d", si->sn_sourced_sid); + else + sourced_buf[0] = NUL; + vim_snprintf((char *)IObuff, IOSIZE, "%3d%s%s: %s", i, + sourced_buf, si->sn_state == SN_STATE_NOT_LOADED ? " A" : "", NameBuff); if (!message_filtered(IObuff)) @@ -1946,6 +1967,7 @@ f_getscriptinfo(typval_T *argvars UNUSED || list_append_dict(l, d) == FAIL || dict_add_string(d, "name", si->sn_name) == FAIL || dict_add_number(d, "sid", i) == FAIL + || dict_add_number(d, "sourced", si->sn_sourced_sid) == FAIL || dict_add_bool(d, "autoload", si->sn_state == SN_STATE_NOT_LOADED) == FAIL) return; diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1850,13 +1850,19 @@ typedef struct { #define IMP_FLAGS_AUTOLOAD 4 // script still needs to be loaded /* - * Info about an already sourced scripts. + * Info about an encoutered script. + * When sn_state has the SN_STATE_NOT_LOADED is has not been sourced yet. */ typedef struct { char_u *sn_name; // full path of script file int sn_script_seq; // latest sctx_T sc_seq value + // When non-zero the script ID of the actually sourced script. Used if a + // script is used by a name which has a symlink, we list both names, but + // only the linked-to script is actually sourced. + int sn_sourced_sid; + // "sn_vars" stores the s: variables currently valid. When leaving a block // variables local to that block are removed. scriptvar_T *sn_vars; diff --git a/src/testdir/test_vim9_import.vim b/src/testdir/test_vim9_import.vim --- a/src/testdir/test_vim9_import.vim +++ b/src/testdir/test_vim9_import.vim @@ -2906,5 +2906,50 @@ def Test_vim9_autoload_error() v9.CheckScriptFailure(lines, 'E461: Illegal variable name: foo#bar', 2) enddef +def Test_vim9_import_symlink() + if !has('unix') + CheckUnix + else + mkdir('Xto/plugin', 'p') + var lines =<< trim END + vim9script + import autoload 'bar.vim' + g:resultFunc = bar.Func() + g:resultValue = bar.value + END + writefile(lines, 'Xto/plugin/foo.vim') + + mkdir('Xto/autoload', 'p') + lines =<< trim END + vim9script + export def Func(): string + return 'func' + enddef + export var value = 'val' + END + writefile(lines, 'Xto/autoload/bar.vim') + + var save_rtp = &rtp + &rtp = getcwd() .. '/Xfrom' + system('ln -s ' .. getcwd() .. '/Xto Xfrom') + + source Xfrom/plugin/foo.vim + assert_equal('func', g:resultFunc) + assert_equal('val', g:resultValue) + + var infoTo = getscriptinfo()->filter((_, v) => v.name =~ 'Xto/autoload/bar') + var infoFrom = getscriptinfo()->filter((_, v) => v.name =~ 'Xfrom/autoload/bar') + assert_equal(1, len(infoTo)) + assert_equal(1, len(infoFrom)) + assert_equal(infoTo[0].sid, infoFrom[0].sourced) + + unlet g:resultFunc + unlet g:resultValue + &rtp = save_rtp + delete('Xto', 'rf') + delete('Xfrom', 'rf') + endif +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -732,6 +732,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 253, +/**/ 252, /**/ 251, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -610,7 +610,7 @@ find_imported(char_u *name, size_t len, ret = find_imported_in_script(name, len, current_sctx.sc_sid); if (ret != NULL && load && (ret->imp_flags & IMP_FLAGS_AUTOLOAD)) { - scid_T dummy; + scid_T actual_sid = 0; int save_emsg_off = emsg_off; // "emsg_off" will be set when evaluating an expression silently, but @@ -621,7 +621,11 @@ find_imported(char_u *name, size_t len, // script found before but not loaded yet ret->imp_flags &= ~IMP_FLAGS_AUTOLOAD; (void)do_source(SCRIPT_ITEM(ret->imp_sid)->sn_name, FALSE, - DOSO_NONE, &dummy); + DOSO_NONE, &actual_sid); + // If the script is a symlink it may be sourced with another name, may + // need to adjust the script ID for that. + if (actual_sid != 0) + ret->imp_sid = actual_sid; emsg_off = save_emsg_off; } diff --git a/src/vim9script.c b/src/vim9script.c --- a/src/vim9script.c +++ b/src/vim9script.c @@ -697,6 +697,20 @@ ex_import(exarg_T *eap) } /* + * When a script is a symlink it may be imported with one name and sourced + * under another name. Adjust the import script ID if needed. + * "*sid" must be a valid script ID. + */ + void +import_check_sourced_sid(int *sid) +{ + scriptitem_T *script = SCRIPT_ITEM(*sid); + + if (script->sn_sourced_sid > 0) + *sid = script->sn_sourced_sid; +} + +/* * Find an exported item in "sid" matching "name". * Either "cctx" or "cstack" is NULL. * When it is a variable return the index.