changeset 28403:2655935b5ccc v8.2.4726

patch 8.2.4726: cannot use expand() to get the script name Commit: https://github.com/vim/vim/commit/6013d0045dec7ca7c0068fbe186c42d754a7368b Author: LemonBoy <thatlemon@gmail.com> Date: Sat Apr 9 21:42:10 2022 +0100 patch 8.2.4726: cannot use expand() to get the script name Problem: Cannot use expand() to get the script name. Solution: Support expand('<script>'). (closes https://github.com/vim/vim/issues/10121)
author Bram Moolenaar <Bram@vim.org>
date Sat, 09 Apr 2022 22:45:03 +0200
parents 401c4206d38c
children 95ce25a7224a
files runtime/doc/cmdline.txt src/errors.h src/ex_docmd.c src/scriptfile.c src/testdir/test_expand.vim src/version.c src/vim.h
diffstat 7 files changed, 114 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -939,7 +939,7 @@ Note: these are typed literally, they ar
 		   file name of the sourced file.  *E498*
 		   When executing a legacy function, is replaced with the call
 		   stack, as with <stack> (this is for backwards
-		   compatibility, using <stack> is preferred).
+		   compatibility, using <stack> or <script> is preferred).
 		   In Vim9 script using <sfile> in a function gives error
 		   *E1245* .
 		   Note that filename-modifiers are useless when <sfile> is
@@ -951,6 +951,12 @@ Note: these are typed literally, they ar
 		   ".." in between items.  E.g.:
 		   "function {function-name1}[{lnum}]..{function-name2}[{lnum}]"
 		   If there is no call stack you get error *E489* .
+							*:<script>* *<script>*
+	<script>   When executing a `:source` command, is replaced with the file
+		   name of the sourced file.  When executing a function, is
+		   replaced with the file name of the script where it is
+		   defined.
+		   If the file name cannot be determined you get error *E1274* .
 							*:<slnum>* *<slnum>*
 	<slnum>	   When executing a ":source" command, is replaced with the
 		   line number.  *E842*
--- a/src/errors.h
+++ b/src/errors.h
@@ -3258,3 +3258,5 @@ EXTERN char e_using_type_not_in_script_c
 #endif
 EXTERN char e_nfa_regexp_missing_value_in_chr[]
 	INIT(= N_("E1273: (NFA regexp) missing value in '\\%%%c'"));
+EXTERN char e_no_script_file_name_to_substitute_for_script[]
+	INIT(= N_("E1274: No script file name to substitute for \"<script>\""));
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -8957,8 +8957,10 @@ find_cmdline_var(char_u *src, int *usedl
 #define SPEC_SLNUM  (SPEC_SFILE + 1)
 		    "<stack>",		// call stack
 #define SPEC_STACK  (SPEC_SLNUM + 1)
+		    "<script>",		// script file name
+#define SPEC_SCRIPT (SPEC_STACK + 1)
 		    "<afile>",		// autocommand file name
-#define SPEC_AFILE  (SPEC_STACK + 1)
+#define SPEC_AFILE  (SPEC_SCRIPT + 1)
 		    "<abuf>",		// autocommand buffer number
 #define SPEC_ABUF   (SPEC_AFILE + 1)
 		    "<amatch>",		// autocommand match name
@@ -9226,14 +9228,28 @@ eval_vars(
 		break;
 
 	case SPEC_SFILE:	// file name for ":so" command
-	case SPEC_STACK:	// call stack
-		result = estack_sfile(spec_idx == SPEC_SFILE
-						? ESTACK_SFILE : ESTACK_STACK);
+		result = estack_sfile(ESTACK_SFILE);
 		if (result == NULL)
 		{
-		    *errormsg = spec_idx == SPEC_SFILE
-			? _(e_no_source_file_name_to_substitute_for_sfile)
-			: _(e_no_call_stack_to_substitute_for_stack);
+		    *errormsg = _(e_no_source_file_name_to_substitute_for_sfile);
+		    return NULL;
+		}
+		resultbuf = result;	    // remember allocated string
+		break;
+	case SPEC_STACK:	// call stack
+		result = estack_sfile(ESTACK_STACK);
+		if (result == NULL)
+		{
+		    *errormsg = _(e_no_call_stack_to_substitute_for_stack);
+		    return NULL;
+		}
+		resultbuf = result;	    // remember allocated string
+		break;
+	case SPEC_SCRIPT:	// script file name
+		result = estack_sfile(ESTACK_SCRIPT);
+		if (result == NULL)
+		{
+		    *errormsg = _(e_no_script_file_name_to_substitute_for_script);
 		    return NULL;
 		}
 		resultbuf = result;	    // remember allocated string
--- a/src/scriptfile.c
+++ b/src/scriptfile.c
@@ -118,7 +118,8 @@ estack_pop(void)
 
 /*
  * Get the current value for <sfile> in allocated memory.
- * "which" is ESTACK_SFILE for <sfile> and ESTACK_STACK for <stack>.
+ * "which" is ESTACK_SFILE for <sfile>, ESTACK_STACK for <stack> or
+ * ESTACK_SCRIPT for <script>.
  */
     char_u *
 estack_sfile(estack_arg_T which UNUSED)
@@ -156,6 +157,32 @@ estack_sfile(estack_arg_T which UNUSED)
 	return NULL;
     }
 
+    // If evaluated in a function return the path of the script where the
+    // function is defined, at script level the current script path is returned
+    // instead.
+    if (which == ESTACK_SCRIPT)
+    {
+	if (entry->es_type == ETYPE_UFUNC)
+	{
+	    sctx_T *def_ctx = &entry->es_info.ufunc->uf_script_ctx;
+
+	    if (def_ctx->sc_sid > 0)
+		return vim_strsave(SCRIPT_ITEM(def_ctx->sc_sid)->sn_name);
+	}
+	else if (exestack.ga_len > 0)
+	{
+	    // Walk the stack backwards, starting from the current frame.
+	    for (idx = exestack.ga_len - 1; idx; --idx)
+	    {
+		entry = ((estack_T *)exestack.ga_data) + idx;
+
+		if (entry->es_type == ETYPE_SCRIPT)
+		    return vim_strsave(entry->es_name);
+	    }
+	}
+	return NULL;
+    }
+
     // Give information about each stack entry up to the root.
     // For a function we compose the call stack, as it was done in the past:
     //   "function One[123]..Two[456]..Three"
--- a/src/testdir/test_expand.vim
+++ b/src/testdir/test_expand.vim
@@ -159,4 +159,54 @@ func Test_expandcmd_shell_nonomatch()
   call assert_equal('$*', expandcmd('$*'))
 endfunc
 
+func Test_expand_script_source()
+  let lines0 =<< trim [SCRIPT]
+    let g:script_level[0] = expand('<script>:t')
+    so Xscript1
+    func F0()
+      let g:func_level[0] = expand('<script>:t')
+    endfunc
+  [SCRIPT]
+
+  let lines1 =<< trim [SCRIPT]
+    let g:script_level[1] = expand('<script>:t')
+    so Xscript2
+    func F1()
+      let g:func_level[1] = expand('<script>:t')
+    endfunc
+  [SCRIPT]
+
+  let lines2 =<< trim [SCRIPT]
+    let g:script_level[2] = expand('<script>:t')
+    func F2()
+      let g:func_level[2] = expand('<script>:t')
+    endfunc
+  [SCRIPT]
+
+  call writefile(lines0, 'Xscript0')
+  call writefile(lines1, 'Xscript1')
+  call writefile(lines2, 'Xscript2')
+
+  " Check the expansion of <script> at script and function level.
+  let g:script_level = ['', '', '']
+  let g:func_level = ['', '', '']
+
+  so Xscript0
+  call F0()
+  call F1()
+  call F2()
+
+  call assert_equal(['Xscript0', 'Xscript1', 'Xscript2'], g:script_level)
+  call assert_equal(['Xscript0', 'Xscript1', 'Xscript2'], g:func_level)
+
+  unlet g:script_level g:func_level
+  delfunc F0
+  delfunc F1
+  delfunc F2
+
+  call delete('Xscript0')
+  call delete('Xscript1')
+  call delete('Xscript2')
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    4726,
+/**/
     4725,
 /**/
     4724,
--- a/src/vim.h
+++ b/src/vim.h
@@ -2220,7 +2220,8 @@ typedef enum {
 typedef enum {
     ESTACK_NONE,
     ESTACK_SFILE,
-    ESTACK_STACK
+    ESTACK_STACK,
+    ESTACK_SCRIPT,
 } estack_arg_T;
 
 // Flags for assignment functions.