# HG changeset patch # User Bram Moolenaar # Date 1641764704 -3600 # Node ID 15f40772e10ad156e0133fa4a2878d43dbd4c90c # Parent 8fc14d1206308b6fa00dd1221526f0ade94d1e92 patch 8.2.4050: Vim9: need to prefix every item in an autoload script Commit: https://github.com/vim/vim/commit/dc4451df61a6aa12a0661817b7094fb32f09e11d Author: Bram Moolenaar Date: Sun Jan 9 21:36:37 2022 +0000 patch 8.2.4050: Vim9: need to prefix every item in an autoload script Problem: Vim9: need to prefix every item in an autoload script. Solution: First step in supporting "vim9script autoload" and "import autoload". diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -365,11 +365,12 @@ For writing a Vim script, see chapter 41 Vim version, or update Vim to a newer version. See |vimscript-version| for what changed between versions. -:vim9s[cript] [noclear] *:vim9s* *:vim9script* +:vim9s[cript] [noclear] [autoload] *:vim9s* *:vim9script* Marks a script file as containing |Vim9-script| commands. Also see |vim9-namespace|. Must be the first command in the file. For [noclear] see |vim9-reload|. + For [autoload] see |vim9-autoload|. Without the |+eval| feature this changes the syntax for some commands. See |:vim9cmd| for executing one command with Vim9 diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt --- a/runtime/doc/vim9.txt +++ b/runtime/doc/vim9.txt @@ -1501,37 +1501,43 @@ result in undefined items. Import in an autoload script ~ - + *vim9-autoload* For optimal startup speed, loading scripts should be postponed until they are -actually needed. A recommended mechanism: +actually needed. Using the autoload mechanism is recommended: 1. In the plugin define user commands, functions and/or mappings that refer to - an autoload script. > - command -nargs=1 SearchForStuff searchfor#Stuff() + items imported from an autoload script. > + import autoload 'for/search.vim' + command -nargs=1 SearchForStuff search.Stuff() < This goes in .../plugin/anyname.vim. "anyname.vim" can be freely chosen. + The "SearchForStuff" command is now available to the user. -2. In the autoload script do the actual work. You can import items from - other files to split up functionality in appropriate pieces. > - vim9script - import "../import/someother.vim" as other - def searchfor#Stuff(arg: string) - var filtered = other.FilterFunc(arg) + The "autoload" argument to `:import` means that the script is not loaded + until one of the items is actually used. The script will be found under + the "autoload" directory in 'runtimepath' instead of the "import" + directory. + +2. In the autoload script put the bulk of the code. > + vim9script autoload + export def Stuff(arg: string) ... -< This goes in .../autoload/searchfor.vim. "searchfor" in the file name - must be exactly the same as the prefix for the function name, that is how - Vim finds the file. + +< This goes in .../autoload/for/search.vim. -3. Other functionality, possibly shared between plugins, contains the exported - items and any private items. > - vim9script - var localVar = 'local' - export def FilterFunc(arg: string): string - ... -< This goes in .../import/someother.vim. + Adding "autoload" to `:vim9script` has the effect that "for#search#" will + be prefixed to every exported item. The prefix is obtained from the file + name, as you would to manually in a legacy autoload script. Thus the + exported function can be found with "for#search#Stuff", but you would + normally use `import autoload` and not need to specify the prefix. + + You can split up the functionality and import other scripts from the + autoload script as you like. This way you can share code between plugins. When compiling a `:def` function and a function in an autoload script is encountered, the script is not loaded until the `:def` function is called. +This also means you get any errors only at runtime, since the argument and +return types are not known yet. Import in legacy Vim script ~ diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3203,4 +3203,8 @@ EXTERN char e_cannot_import_dot_vim_with INIT(= N_("E1261: Cannot import .vim without using \"as\"")); EXTERN char e_cannot_import_same_script_twice_str[] INIT(= N_("E1262: Cannot import the same script twice: %s")); +EXTERN char e_using_autoload_in_script_not_under_autoload_directory[] + INIT(= N_("E1263: Using autoload in a script not under an autoload directory")); +EXTERN char e_autoload_import_cannot_use_absolute_or_relative_path[] + INIT(= N_("E1264: Autoload import cannot use absolute or relative path: %s")); #endif diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -886,7 +886,9 @@ get_lval( if (*p == '.' && in_vim9script()) { - imported_T *import = find_imported(lp->ll_name, p - lp->ll_name, NULL); + imported_T *import = find_imported(lp->ll_name, p - lp->ll_name, + TRUE, NULL); + if (import != NULL) { ufunc_T *ufunc; diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -2683,7 +2683,7 @@ eval_variable( char_u *p = STRNCMP(name, "s:", 2) == 0 ? name + 2 : name; if (sid == 0) - import = find_imported(p, 0, NULL); + import = find_imported(p, 0, TRUE, NULL); // imported variable from another script if (import != NULL || sid != 0) @@ -3015,7 +3015,7 @@ lookup_scriptitem( res = HASHITEM_EMPTY(hi) ? FAIL : OK; // if not script-local, then perhaps imported - if (res == FAIL && find_imported(p, 0, NULL) != NULL) + if (res == FAIL && find_imported(p, 0, FALSE, NULL) != NULL) res = OK; if (p != buffer) vim_free(p); @@ -3388,7 +3388,7 @@ set_var_const( if (di == NULL && var_in_vim9script) { - imported_T *import = find_imported(varname, 0, NULL); + imported_T *import = find_imported(varname, 0, FALSE, NULL); if (import != NULL) { diff --git a/src/proto/scriptfile.pro b/src/proto/scriptfile.pro --- a/src/proto/scriptfile.pro +++ b/src/proto/scriptfile.pro @@ -10,6 +10,7 @@ int do_in_path(char_u *path, char_u *nam int do_in_runtimepath(char_u *name, int flags, void (*callback)(char_u *fname, void *ck), void *cookie); int source_runtime(char_u *name, int flags); int source_in_path(char_u *path, char_u *name, int flags, int *ret_sid); +int find_script_in_rtp(char_u *name); void add_pack_start_dirs(void); void load_start_packages(void); void ex_packloadall(exarg_T *eap); @@ -36,6 +37,8 @@ void ex_scriptversion(exarg_T *eap); void ex_finish(exarg_T *eap); void do_finish(exarg_T *eap, int reanimate); int source_finished(char_u *(*fgetline)(int, void *, int, getline_opt_T), void *cookie); +char_u *script_name_after_autoload(scriptitem_T *si); +char_u *may_prefix_autoload(char_u *name); char_u *autoload_name(char_u *name); int script_autoload(char_u *name, int reload); /* vim: set ft=c : */ diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro --- a/src/proto/vim9compile.pro +++ b/src/proto/vim9compile.pro @@ -7,7 +7,7 @@ int check_defined(char_u *p, size_t len, int need_type(type_T *actual, type_T *expected, int offset, int arg_idx, cctx_T *cctx, int silent, int actual_is_const); lvar_T *reserve_local(cctx_T *cctx, char_u *name, size_t len, int isConst, type_T *type); int get_script_item_idx(int sid, char_u *name, int check_writable, cctx_T *cctx); -imported_T *find_imported(char_u *name, size_t len, cctx_T *cctx); +imported_T *find_imported(char_u *name, size_t len, int load, cctx_T *cctx); char_u *may_peek_next_line(cctx_T *cctx, char_u *arg, char_u **nextp); char_u *peek_next_line_from_context(cctx_T *cctx); char_u *next_line_from_context(cctx_T *cctx, int skip_comment); diff --git a/src/scriptfile.c b/src/scriptfile.c --- a/src/scriptfile.c +++ b/src/scriptfile.c @@ -239,6 +239,102 @@ source_callback(char_u *fname, void *coo (void)do_source(fname, FALSE, DOSO_NONE, cookie); } +#ifdef FEAT_EVAL +/* + * Find an already loaded script "name". + * If found returns its script ID. If not found returns -1. + */ + static int +find_script_by_name(char_u *name) +{ + int sid; + scriptitem_T *si; + + for (sid = script_items.ga_len; sid > 0; --sid) + { + // We used to check inode here, but that doesn't work: + // - If a script is edited and written, it may get a different + // inode number, even though to the user it is the same script. + // - If a script is deleted and another script is written, with a + // different name, the inode may be re-used. + si = SCRIPT_ITEM(sid); + if (si->sn_name != NULL && fnamecmp(si->sn_name, name) == 0) + return sid; + } + return -1; +} + +/* + * Add a new scriptitem with all items initialized. + * When running out of memory "error" is set to FAIL. + * Returns the script ID. + */ + static int +get_new_scriptitem(int *error) +{ + static scid_T last_current_SID = 0; + int sid = ++last_current_SID; + scriptitem_T *si; + + if (ga_grow(&script_items, (int)(sid - script_items.ga_len)) == FAIL) + { + *error = FAIL; + return sid; + } + while (script_items.ga_len < sid) + { + si = ALLOC_CLEAR_ONE(scriptitem_T); + if (si == NULL) + { + *error = FAIL; + return sid; + } + ++script_items.ga_len; + SCRIPT_ITEM(script_items.ga_len) = si; + si->sn_name = NULL; + si->sn_version = 1; + + // Allocate the local script variables to use for this script. + new_script_vars(script_items.ga_len); + ga_init2(&si->sn_var_vals, sizeof(svar_T), 10); + hash_init(&si->sn_all_vars.dv_hashtab); + ga_init2(&si->sn_imports, sizeof(imported_T), 10); + ga_init2(&si->sn_type_list, sizeof(type_T), 10); +# ifdef FEAT_PROFILE + si->sn_prof_on = FALSE; +# endif + } + + // Used to check script variable index is still valid. + si->sn_script_seq = current_sctx.sc_seq; + + return sid; +} + + static void +find_script_callback(char_u *fname, void *cookie) +{ + int sid; + int error = OK; + int *ret_sid = cookie; + + sid = find_script_by_name(fname); + if (sid < 0) + { + // script does not exist yet, create a new scriptitem + sid = get_new_scriptitem(&error); + if (error == OK) + { + scriptitem_T *si = SCRIPT_ITEM(sid); + + si->sn_name = vim_strsave(fname); + si->sn_state = SN_STATE_NOT_LOADED; + } + } + *ret_sid = sid; +} +#endif + /* * Find the file "name" in all directories in "path" and invoke * "callback(fname, cookie)". @@ -455,7 +551,8 @@ source_runtime(char_u *name, int flags) } /* - * Just like source_runtime(), but use "path" instead of 'runtimepath'. + * Just like source_runtime(), but use "path" instead of 'runtimepath' + * and return the script ID in "ret_sid". */ int source_in_path(char_u *path, char_u *name, int flags, int *ret_sid) @@ -463,8 +560,22 @@ source_in_path(char_u *path, char_u *nam return do_in_path_and_pp(path, name, flags, source_callback, ret_sid); } +#if defined(FEAT_EVAL) || defined(PROTO) -#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Find "name" in 'runtimepath'. If found a new scriptitem is created for it + * and it's script ID is returned. + * If not found returns -1. + */ + int +find_script_in_rtp(char_u *name) +{ + int sid = -1; + + (void)do_in_path_and_pp(p_rtp, name, DIP_NOAFTER, + find_script_callback, &sid); + return sid; +} /* * Expand wildcards in "pat" and invoke do_source() for each match. @@ -1127,7 +1238,6 @@ do_source( int retval = FAIL; sctx_T save_current_sctx; #ifdef FEAT_EVAL - static scid_T last_current_SID = 0; static int last_current_SID_seq = 0; funccal_entry_T funccalp_entry; int save_debug_break_level = debug_break_level; @@ -1161,18 +1271,7 @@ do_source( estack_compiling = FALSE; // See if we loaded this script before. - for (sid = script_items.ga_len; sid > 0; --sid) - { - // We used to check inode here, but that doesn't work: - // - If a script is edited and written, it may get a different - // inode number, even though to the user it is the same script. - // - If a script is deleted and another script is written, with a - // different name, the inode may be re-used. - si = SCRIPT_ITEM(sid); - if (si->sn_name != NULL && fnamecmp(si->sn_name, fname_exp) == 0) - // Found it! - break; - } + sid = find_script_by_name(fname_exp); if (sid > 0 && ret_sid != NULL) { // Already loaded and no need to load again, return here. @@ -1318,54 +1417,44 @@ do_source( dictitem_T *di; // loading the same script again - si->sn_state = SN_STATE_RELOAD; current_sctx.sc_sid = sid; + si = SCRIPT_ITEM(sid); + if (si->sn_state == SN_STATE_NOT_LOADED) + { + // this script was found but not loaded yet + si->sn_state = SN_STATE_NEW; + } + else + { + si->sn_state = SN_STATE_RELOAD; - // Script-local variables remain but "const" can be set again. - // In Vim9 script variables will be cleared when "vim9script" is - // encountered without the "noclear" argument. - ht = &SCRIPT_VARS(sid); - todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) - if (!HASHITEM_EMPTY(hi)) - { - --todo; - di = HI2DI(hi); - di->di_flags |= DI_FLAGS_RELOAD; - } - // imports can be redefined once - mark_imports_for_reload(sid); + // Script-local variables remain but "const" can be set again. + // In Vim9 script variables will be cleared when "vim9script" is + // encountered without the "noclear" argument. + ht = &SCRIPT_VARS(sid); + todo = (int)ht->ht_used; + for (hi = ht->ht_array; todo > 0; ++hi) + if (!HASHITEM_EMPTY(hi)) + { + --todo; + di = HI2DI(hi); + di->di_flags |= DI_FLAGS_RELOAD; + } + // imports can be redefined once + mark_imports_for_reload(sid); - // reset version, "vim9script" may have been added or removed. - si->sn_version = 1; + // reset version, "vim9script" may have been added or removed. + si->sn_version = 1; + } } else { - // It's new, generate a new SID. - current_sctx.sc_sid = ++last_current_SID; - if (ga_grow(&script_items, - (int)(current_sctx.sc_sid - script_items.ga_len)) == FAIL) + int error = OK; + + // It's new, generate a new SID and initialize the scriptitem. + current_sctx.sc_sid = get_new_scriptitem(&error); + if (error == FAIL) goto almosttheend; - while (script_items.ga_len < current_sctx.sc_sid) - { - si = ALLOC_CLEAR_ONE(scriptitem_T); - if (si == NULL) - goto almosttheend; - ++script_items.ga_len; - SCRIPT_ITEM(script_items.ga_len) = si; - si->sn_name = NULL; - si->sn_version = 1; - - // Allocate the local script variables to use for this script. - new_script_vars(script_items.ga_len); - ga_init2(&si->sn_var_vals, sizeof(svar_T), 10); - hash_init(&si->sn_all_vars.dv_hashtab); - ga_init2(&si->sn_imports, sizeof(imported_T), 10); - ga_init2(&si->sn_type_list, sizeof(type_T), 10); -# ifdef FEAT_PROFILE - si->sn_prof_on = FALSE; -# endif - } si = SCRIPT_ITEM(current_sctx.sc_sid); si->sn_name = fname_exp; fname_exp = vim_strsave(si->sn_name); // used for autocmd @@ -1374,9 +1463,6 @@ do_source( // Remember the "is_vimrc" flag for when the file is sourced again. si->sn_is_vimrc = is_vimrc; - - // Used to check script variable index is still valid. - si->sn_script_seq = current_sctx.sc_seq; } # ifdef FEAT_PROFILE @@ -2031,6 +2117,78 @@ source_finished( } /* + * Find the path of a script below the "autoload" directory. + * Returns NULL if there is no "/autoload/" in the script name. + */ + char_u * +script_name_after_autoload(scriptitem_T *si) +{ + char_u *p = si->sn_name; + char_u *res = NULL; + + for (;;) + { + char_u *n = (char_u *)strstr((char *)p, "autoload"); + + if (n == NULL) + break; + if (n > p && vim_ispathsep(n[-1]) && vim_ispathsep(n[8])) + res = n + 9; + p = n + 8; + } + return res; +} + +/* + * If in a Vim9 autoload script return "name" with the autoload prefix for the + * script. If successful "name" is freed, the returned name is allocated. + * Otherwise it returns "name" unmodified. + */ + char_u * +may_prefix_autoload(char_u *name) +{ + if (SCRIPT_ID_VALID(current_sctx.sc_sid)) + { + scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); + + if (si->sn_is_autoload) + { + char_u *p = script_name_after_autoload(si); + + if (p != NULL) + { + char_u *tail = vim_strsave(p); + + if (tail != NULL) + { + for (p = tail; *p != NUL; p += mb_ptr2len(p)) + { + if (vim_ispathsep(*p)) + *p = '#'; + else if (STRCMP(p, ".vim")) + { + size_t len = (p - tail) + STRLEN(name) + 2; + char_u *res = alloc(len); + + if (res == NULL) + break; + *p = NUL; + vim_snprintf((char *)res, len, "%s#%s", tail, name); + vim_free(name); + vim_free(tail); + return res; + } + } + } + // did not find ".vim" at the end + vim_free(tail); + } + } + } + return name; +} + +/* * Return the autoload script name for a function or variable name. * Returns NULL when out of memory. * Caller must make sure that "name" contains AUTOLOAD_CHAR. diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1827,6 +1827,7 @@ typedef struct { } imported_T; #define IMP_FLAGS_RELOAD 2 // script reloaded, OK to redefine +#define IMP_FLAGS_AUTOLOAD 4 // script still needs to be loaded /* * Info about an already sourced scripts. @@ -1863,6 +1864,7 @@ typedef struct int sn_state; // SN_STATE_ values char_u *sn_save_cpo; // 'cpo' value when :vim9script found char sn_is_vimrc; // .vimrc file, do not restore 'cpo' + char sn_is_autoload; // "vim9script autoload" # ifdef FEAT_PROFILE int sn_prof_on; // TRUE when script is/was profiled @@ -1886,7 +1888,8 @@ typedef struct } scriptitem_T; #define SN_STATE_NEW 0 // newly loaded script, nothing done -#define SN_STATE_RELOAD 1 // script loaded before, nothing done +#define SN_STATE_NOT_LOADED 1 // script located but not loaded +#define SN_STATE_RELOAD 2 // script loaded before, nothing done #define SN_STATE_HAD_COMMAND 9 // a command was executed // Struct passed through eval() functions. diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -3032,6 +3032,26 @@ def Test_vim9_autoload() writefile(lines, 'Xdir/autoload/Other.vim') assert_equal('other', g:Other#getOther()) + # using "vim9script autoload" prefix is not needed + lines =<< trim END + vim9script autoload + g:prefixed_loaded = 'yes' + export def Gettest(): string + return 'test' + enddef + export var name = 'name' + END + writefile(lines, 'Xdir/autoload/prefixed.vim') + + lines =<< trim END + vim9script + import autoload 'prefixed.vim' + assert_false(exists('g:prefixed_loaded')) + assert_equal('test', prefixed.Gettest()) + assert_equal('yes', g:prefixed_loaded) + END + CheckScriptSuccess(lines) + delete('Xdir', 'rf') &rtp = save_rtp enddef diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -1608,7 +1608,7 @@ deref_func_name( p = name + 2; len -= 2; } - import = find_imported(p, len, NULL); + import = find_imported(p, len, FALSE, NULL); // imported function from another script if (import != NULL) @@ -4079,6 +4079,9 @@ define_function(exarg_T *eap, char_u *na else eap->skip = TRUE; } + +// if (is_export) +// name = may_prefix_autoload(name); } // An error in a function call during evaluation of an expression in magic @@ -4363,7 +4366,7 @@ define_function(exarg_T *eap, char_u *na { char_u *uname = untrans_function_name(name); - import = find_imported(uname == NULL ? name : uname, 0, NULL); + import = find_imported(uname == NULL ? name : uname, 0, FALSE, NULL); } if (fp != NULL || import != NULL) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 4050, +/**/ 4049, /**/ 4048, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -268,7 +268,7 @@ variable_exists(char_u *name, size_t len && (lookup_local(name, len, NULL, cctx) == OK || arg_exists(name, len, NULL, NULL, NULL, cctx) == OK)) || script_var_exists(name, len, cctx) == OK - || find_imported(name, len, cctx) != NULL; + || find_imported(name, len, FALSE, cctx) != NULL; } /* @@ -331,7 +331,7 @@ check_defined(char_u *p, size_t len, cct if ((cctx != NULL && (lookup_local(p, len, NULL, cctx) == OK || arg_exists(p, len, NULL, NULL, NULL, cctx) == OK)) - || find_imported(p, len, cctx) != NULL + || find_imported(p, len, FALSE, cctx) != NULL || (ufunc = find_func_even_dead(p, FALSE, cctx)) != NULL) { // A local or script-local function can shadow a global function. @@ -581,11 +581,13 @@ find_imported_in_script(char_u *name, si /* * Find "name" in imported items of the current script or in "cctx" if not * NULL. + * If "load" is TRUE and the script was not loaded yet, load it now. */ imported_T * -find_imported(char_u *name, size_t len, cctx_T *cctx) +find_imported(char_u *name, size_t len, int load, cctx_T *cctx) { int idx; + imported_T *ret = NULL; if (!SCRIPT_ID_VALID(current_sctx.sc_sid)) return NULL; @@ -598,10 +600,23 @@ find_imported(char_u *name, size_t len, if (len == 0 ? STRCMP(name, import->imp_name) == 0 : STRLEN(import->imp_name) == len && STRNCMP(name, import->imp_name, len) == 0) - return import; + { + ret = import; + break; + } } - return find_imported_in_script(name, len, current_sctx.sc_sid); + if (ret == NULL) + ret = find_imported_in_script(name, len, current_sctx.sc_sid); + + if (ret != NULL && load && ret->imp_flags == IMP_FLAGS_AUTOLOAD) + { + // script found before but not loaded yet + ret->imp_flags = 0; + (void)do_source(SCRIPT_ITEM(ret->imp_sid)->sn_name, FALSE, + DOSO_NONE, NULL); + } + return ret; } /* @@ -1326,7 +1341,7 @@ compile_lhs( : script_var_exists(var_start, lhs->lhs_varlen, cctx)) == OK; imported_T *import = - find_imported(var_start, lhs->lhs_varlen, cctx); + find_imported(var_start, lhs->lhs_varlen, FALSE, cctx); if (script_namespace || script_var || import != NULL) { diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -266,7 +266,7 @@ compile_load_scriptvar( return OK; } - import = end == NULL ? NULL : find_imported(name, 0, cctx); + import = end == NULL ? NULL : find_imported(name, 0, FALSE, cctx); if (import != NULL) { char_u *p = skipwhite(*end); @@ -275,6 +275,7 @@ compile_load_scriptvar( ufunc_T *ufunc; type_T *type; + // TODO: if this is an autoload import do something else. // Need to lookup the member. if (*p != '.') { @@ -474,7 +475,7 @@ compile_load( // "var" can be script-local even without using "s:" if it // already exists in a Vim9 script or when it's imported. if (script_var_exists(*arg, len, cctx) == OK - || find_imported(name, 0, cctx) != NULL) + || find_imported(name, 0, FALSE, cctx) != NULL) res = compile_load_scriptvar(cctx, name, *arg, &end, FALSE); // When evaluating an expression and the name starts with an diff --git a/src/vim9script.c b/src/vim9script.c --- a/src/vim9script.c +++ b/src/vim9script.c @@ -68,6 +68,9 @@ ex_vim9script(exarg_T *eap UNUSED) #ifdef FEAT_EVAL int sid = current_sctx.sc_sid; scriptitem_T *si; + int found_noclear = FALSE; + int found_autoload = FALSE; + char_u *p; if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { @@ -81,12 +84,40 @@ ex_vim9script(exarg_T *eap UNUSED) emsg(_(e_vim9script_must_be_first_command_in_script)); return; } - if (!IS_WHITE_OR_NUL(*eap->arg) && STRCMP(eap->arg, "noclear") != 0) + + for (p = eap->arg; !IS_WHITE_OR_NUL(*p); p = skipwhite(skiptowhite(p))) { - semsg(_(e_invalid_argument_str), eap->arg); - return; + if (STRNCMP(p, "noclear", 7) == 0 && IS_WHITE_OR_NUL(p[7])) + { + if (found_noclear) + { + semsg(_(e_duplicate_argument_str), p); + return; + } + found_noclear = TRUE; + } + else if (STRNCMP(p, "autoload", 8) == 0 && IS_WHITE_OR_NUL(p[8])) + { + if (found_autoload) + { + semsg(_(e_duplicate_argument_str), p); + return; + } + found_autoload = TRUE; + if (script_name_after_autoload(si) == NULL) + { + emsg(_(e_using_autoload_in_script_not_under_autoload_directory)); + return; + } + } + else + { + semsg(_(e_invalid_argument_str), eap->arg); + return; + } } - if (si->sn_state == SN_STATE_RELOAD && IS_WHITE_OR_NUL(*eap->arg)) + + if (si->sn_state == SN_STATE_RELOAD && !found_noclear) { hashtab_T *ht = &SCRIPT_VARS(sid); @@ -101,6 +132,8 @@ ex_vim9script(exarg_T *eap UNUSED) } si->sn_state = SN_STATE_HAD_COMMAND; + si->sn_is_autoload = found_autoload; + current_sctx.sc_version = SCRIPT_VERSION_VIM9; si->sn_version = SCRIPT_VERSION_VIM9; @@ -366,6 +399,7 @@ handle_import( { char_u *arg = arg_start; char_u *nextarg; + int is_autoload = FALSE; int getnext; char_u *expr_end; int ret = FAIL; @@ -377,6 +411,12 @@ handle_import( garray_T *import_gap; int i; + if (STRNCMP(arg, "autoload", 8) == 0 && VIM_ISWHITE(arg[8])) + { + is_autoload = TRUE; + arg = skipwhite(arg + 8); + } + // The name of the file can be an expression, which must evaluate to a // string. ret = eval0_retarg(arg, &tv, NULL, evalarg, &expr_end); @@ -402,23 +442,48 @@ handle_import( char_u *tail = gettail(si->sn_name); char_u *from_name; - // Relative to current script: "./name.vim", "../../name.vim". - len = STRLEN(si->sn_name) - STRLEN(tail) + STRLEN(tv.vval.v_string) + 2; - from_name = alloc((int)len); - if (from_name == NULL) - goto erret; - vim_strncpy(from_name, si->sn_name, tail - si->sn_name); - add_pathsep(from_name); - STRCAT(from_name, tv.vval.v_string); - simplify_filename(from_name); + if (is_autoload) + res = FAIL; + else + { - res = do_source(from_name, FALSE, DOSO_NONE, &sid); - vim_free(from_name); + // Relative to current script: "./name.vim", "../../name.vim". + len = STRLEN(si->sn_name) - STRLEN(tail) + + STRLEN(tv.vval.v_string) + 2; + from_name = alloc((int)len); + if (from_name == NULL) + goto erret; + vim_strncpy(from_name, si->sn_name, tail - si->sn_name); + add_pathsep(from_name); + STRCAT(from_name, tv.vval.v_string); + simplify_filename(from_name); + + res = do_source(from_name, FALSE, DOSO_NONE, &sid); + vim_free(from_name); + } } else if (mch_isFullName(tv.vval.v_string)) { // Absolute path: "/tmp/name.vim" - res = do_source(tv.vval.v_string, FALSE, DOSO_NONE, &sid); + if (is_autoload) + res = FAIL; + else + res = do_source(tv.vval.v_string, FALSE, DOSO_NONE, &sid); + } + else if (is_autoload) + { + size_t len = 9 + STRLEN(tv.vval.v_string) + 1; + char_u *from_name; + + // Find file in "autoload" subdirs in 'runtimepath'. + from_name = alloc((int)len); + if (from_name == NULL) + goto erret; + vim_snprintf((char *)from_name, len, "autoload/%s", tv.vval.v_string); + // we need a scriptitem without loading the script + sid = find_script_in_rtp(from_name); + vim_free(from_name); + res = SCRIPT_ID_VALID(sid) ? OK : FAIL; } else { @@ -428,9 +493,7 @@ handle_import( // Find file in "import" subdirs in 'runtimepath'. from_name = alloc((int)len); if (from_name == NULL) - { goto erret; - } vim_snprintf((char *)from_name, len, "import/%s", tv.vval.v_string); res = source_in_path(p_rtp, from_name, DIP_NOAFTER, &sid); vim_free(from_name); @@ -438,7 +501,9 @@ handle_import( if (res == FAIL || sid <= 0) { - semsg(_(e_could_not_import_str), tv.vval.v_string); + semsg(_(is_autoload && sid <= 0 + ? e_autoload_import_cannot_use_absolute_or_relative_path + : e_could_not_import_str), tv.vval.v_string); goto erret; } @@ -451,7 +516,7 @@ handle_import( { if (import->imp_flags & IMP_FLAGS_RELOAD) { - // encountering same script first ime on a reload is OK + // encountering same script first time on a reload is OK import->imp_flags &= ~IMP_FLAGS_RELOAD; break; } @@ -513,7 +578,7 @@ handle_import( { imported_T *imported; - imported = find_imported(as_name, STRLEN(as_name), cctx); + imported = find_imported(as_name, FALSE, STRLEN(as_name), cctx); if (imported != NULL && imported->imp_sid != sid) { semsg(_(e_name_already_defined_str), as_name); @@ -529,6 +594,8 @@ handle_import( imported->imp_name = as_name; as_name = NULL; imported->imp_sid = sid; + if (is_autoload) + imported->imp_flags = IMP_FLAGS_AUTOLOAD; } erret: