]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 8.2.4050: Vim9: need to prefix every item in an autoload script v8.2.4050
authorBram Moolenaar <Bram@vim.org>
Sun, 9 Jan 2022 21:36:37 +0000 (21:36 +0000)
committerBram Moolenaar <Bram@vim.org>
Sun, 9 Jan 2022 21:36:37 +0000 (21:36 +0000)
Problem:    Vim9: need to prefix every item in an autoload script.
Solution:   First step in supporting "vim9script autoload" and "import
            autoload".

15 files changed:
runtime/doc/repeat.txt
runtime/doc/vim9.txt
src/errors.h
src/eval.c
src/evalvars.c
src/proto/scriptfile.pro
src/proto/vim9compile.pro
src/scriptfile.c
src/structs.h
src/testdir/test_vim9_script.vim
src/userfunc.c
src/version.c
src/vim9compile.c
src/vim9expr.c
src/vim9script.c

index 25f375e6fc908d670cb45b600288aa69f4b0917a..407c23c239fb20d0983aae990d06b01641af240d 100644 (file)
@@ -365,11 +365,12 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
                        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
index 0af6dff33d7b60cc3a6252859a61860078d82d28..8942466e39e522d94a6f7cf40dc9b688001969a4 100644 (file)
@@ -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(<f-args>)
+   items imported from an autoload script. >
+       import autoload 'for/search.vim'
+       command -nargs=1 SearchForStuff search.Stuff(<f-args>)
 
 <   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.
 
-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.
+<   This goes in .../autoload/for/search.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 ~
index d30b4b0f023fb975606f16a5348ff1c3f8798d66..7059b28da326380da97061cdd9ce6223743ea485 100644 (file)
@@ -3203,4 +3203,8 @@ EXTERN char e_cannot_import_dot_vim_without_using_as[]
        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
index 390bb58110e10ee11afb14229fbd18aaea333cc7..7a23876f9a155ddcf2048f73e45ad3c943e16288 100644 (file)
@@ -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;
index d93fed5b0e96b5081dcddc05ff9cb8c48c6a3edf..1d5aedf4cc1362664660586625753378c6cc1765 100644 (file)
@@ -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)
        {
index cbb9253c5228ab69625b58640c124d5314fc25cf..140d67bbbdbdc463c828e681283c576f15a92413 100644 (file)
@@ -10,6 +10,7 @@ int do_in_path(char_u *path, char_u *name, int flags, void (*callback)(char_u *f
 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 : */
index 98c40c86cbaa0a06f832cdee9d5aa17faf31b079..cb95fdfb6bc34819b0f0570d967a4a0ca7ae112f 100644 (file)
@@ -7,7 +7,7 @@ int check_defined(char_u *p, size_t len, cctx_T *cctx, int is_arg);
 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);
index c10a82f2cbf8f62b6aa66a99c5a451fc4161d0e1..c1ab41503fe877652ff52f02144e2da087f62bbb 100644 (file)
@@ -239,6 +239,102 @@ source_callback(char_u *fname, void *cookie)
     (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,9 +560,23 @@ source_in_path(char_u *path, char_u *name, int flags, int *ret_sid)
     return do_in_path_and_pp(path, name, flags, source_callback, ret_sid);
 }
 
-
 #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)
-           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;
+       int error = OK;
 
-           // 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
-       }
+       // It's new, generate a new SID and initialize the scriptitem.
+       current_sctx.sc_sid = get_new_scriptitem(&error);
+       if (error == FAIL)
+           goto almosttheend;
        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
@@ -2030,6 +2116,78 @@ source_finished(
                                                fgetline, cookie))->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.
index 9d5768175fb2e2dfc07e5852df7c9e5f886659b5..fd4be4fe95be646897ac338148bd98727f747ea9 100644 (file)
@@ -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.
index 8c7f85f167d681056e941e2ec94c87ae42798873..97a51a51c6bad2a092ac8fcddac238dd95691138 100644 (file)
@@ -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
index b772913f1591646712403eb1bd1f789d13b6a6ad..ecd2e7c1ad6b7b687171c8ae1b1ea36f1082dfd3 100644 (file)
@@ -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 *name_arg, garray_T *lines_to_free)
            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 *name_arg, garray_T *lines_to_free)
        {
            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)
index cf0665c02ce5b6de06902260255bfc408bcaf7a9..a2ce58e4b4a5b0dbe165ed0da98670dd3546f4f7 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    4050,
 /**/
     4049,
 /**/
index 11e322645107fd528786c072b5ba0a8bfd6a2553..993bb1c68d550c3409278bf90c4b999206e8bdbd 100644 (file)
@@ -268,7 +268,7 @@ variable_exists(char_u *name, size_t len, cctx_T *cctx)
                && (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, cctx_T *cctx, int is_arg)
     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, size_t len, int sid)
 /*
  * 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, cctx_T *cctx)
            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)
                {
index edaee509976af7b9b068f1c75532cda60c038a5c..f12acf65a2d7e42c76e0ae94476b7a8a8f8bdcff 100644 (file)
@@ -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
index 3ebeab366ae5ebc814710f3c848a4c93555f5d5c..a8b76bb6c470d85639a0c435ab0817cb82b1bffa 100644 (file)
@@ -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: