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/src/scriptfile.c b/src/scriptfile.c
index c10a82f..c1ab415 100644
--- a/src/scriptfile.c
+++ b/src/scriptfile.c
@@ -239,6 +239,102 @@
(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 @@
}
/*
- * 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,10 +560,24 @@
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.
*/
static void
@@ -1127,7 +1238,6 @@
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 @@
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 @@
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 @@
// 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 @@
}
/*
+ * 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.