mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	vim-patch:8.2.4603: sourcing buffer lines is too complicated
Problem:    Sourcing buffer lines is too complicated.
Solution:   Simplify the code. Make it possible to source Vim9 script lines.
            (Yegappan Lakshmanan, closes vim/vim#9974)
85b43c6cb7
This commit changes the behavior of sourcing buffer lines to always have
a script ID, although sourcing the same buffer always produces the same
script ID.
vim-patch:9.1.0372: Calling CLEAR_FIELD() on the same struct twice
Problem:  Calling CLEAR_FIELD() on the same struct twice.
Solution: Remove the second CLEAR_FIELD().  Move the assignment of
          cookie.sourceing_lnum (zeertzjq).
closes: vim/vim#14627
f68517c167
Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
			
			
This commit is contained in:
		| @@ -201,7 +201,9 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|. | ||||
| 			code if 'filetype' is "lua" or its filename ends with | ||||
| 			".lua".  When sourcing commands or Lua code from the | ||||
| 			current buffer, the same script-ID |<SID>| is used | ||||
| 			even if the buffer is sourced multiple times. | ||||
| 			even if the buffer is sourced multiple times. If a | ||||
| 			buffer is sourced more than once, then the functions | ||||
| 			in the buffer are redefined again. | ||||
|  | ||||
| 							*:source!* | ||||
| :so[urce]! {file} | ||||
| @@ -394,10 +396,10 @@ An alternative is to put the commands in a file, and execute them with the | ||||
| ':source!' command.  Useful for long command sequences.  Can be combined with | ||||
| the ':map' command to put complicated commands under a function key. | ||||
|  | ||||
| The ':source' command reads Ex commands from a file line by line.  You will | ||||
| have to type any needed keyboard input.  The ':source!' command reads from a | ||||
| script file character by character, interpreting each character as if you | ||||
| typed it. | ||||
| The ':source' command reads Ex commands from a file or a buffer line by line. | ||||
| You will have to type any needed keyboard input.  The ':source!' command reads | ||||
| from a script file character by character, interpreting each character as if | ||||
| you typed it. | ||||
|  | ||||
| Example: When you give the ":!ls" command you get the |hit-enter| prompt.  If | ||||
| you ':source' a file with the line "!ls" in it, you will have to type the | ||||
|   | ||||
| @@ -1461,19 +1461,12 @@ static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name | ||||
|   } | ||||
| } | ||||
|  | ||||
| void nlua_source_str(const char *code, char *name) | ||||
| void nlua_exec_ga(garray_T *ga, char *name) | ||||
| { | ||||
|   const sctx_T save_current_sctx = current_sctx; | ||||
|   current_sctx.sc_sid = SID_STR; | ||||
|   current_sctx.sc_seq = 0; | ||||
|   current_sctx.sc_lnum = 0; | ||||
|   estack_push(ETYPE_SCRIPT, name, 0); | ||||
|  | ||||
|   char *code = ga_concat_strings_sep(ga, "\n"); | ||||
|   size_t len = strlen(code); | ||||
|   nlua_typval_exec(code, len, name, NULL, 0, false, NULL); | ||||
|  | ||||
|   estack_pop(); | ||||
|   current_sctx = save_current_sctx; | ||||
|   xfree(code); | ||||
| } | ||||
|  | ||||
| /// Call a LuaCallable given some typvals | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| #include "nvim/api/private/helpers.h" | ||||
| #include "nvim/cmdexpand_defs.h"  // IWYU pragma: keep | ||||
| #include "nvim/ex_cmds_defs.h"  // IWYU pragma: keep | ||||
| #include "nvim/garray_defs.h"  // IWYU pragma: keep | ||||
| #include "nvim/macros_defs.h" | ||||
| #include "nvim/types_defs.h" | ||||
| #include "nvim/usercmd.h"  // IWYU pragma: keep | ||||
|   | ||||
| @@ -75,7 +75,10 @@ typedef struct { | ||||
|   FILE *fp;                     ///< opened file for sourcing | ||||
|   char *nextline;               ///< if not NULL: line that was read ahead | ||||
|   linenr_T sourcing_lnum;       ///< line number of the source file | ||||
|   int finished;                 ///< ":finish" used | ||||
|   bool finished;                ///< ":finish" used | ||||
|   bool source_from_buf_or_str;  ///< true if sourcing from a buffer or string | ||||
|   int buf_lnum;                 ///< line number in the buffer or string | ||||
|   garray_T buflines;            ///< lines in the buffer or string | ||||
| #ifdef USE_CRNL | ||||
|   int fileformat;               ///< EOL_UNKNOWN, EOL_UNIX or EOL_DOS | ||||
|   bool error;                   ///< true if LF found after CR-LF | ||||
| @@ -1947,42 +1950,6 @@ static bool concat_continued_line(garray_T *const ga, const int init_growsize, c | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| typedef struct { | ||||
|   char *buf; | ||||
|   size_t offset; | ||||
| } GetStrLineCookie; | ||||
|  | ||||
| /// Get one full line from a sourced string (in-memory, no file). | ||||
| /// Called by do_cmdline() when it's called from do_source_str(). | ||||
| /// | ||||
| /// @return pointer to allocated line, or NULL for end-of-file or | ||||
| ///         some error. | ||||
| static char *get_str_line(int c, void *cookie, int indent, bool do_concat) | ||||
| { | ||||
|   GetStrLineCookie *p = cookie; | ||||
|   if (strlen(p->buf) <= p->offset) { | ||||
|     return NULL; | ||||
|   } | ||||
|   const char *line = p->buf + p->offset; | ||||
|   const char *eol = skip_to_newline(line); | ||||
|   garray_T ga; | ||||
|   ga_init(&ga, sizeof(char), 400); | ||||
|   ga_concat_len(&ga, line, (size_t)(eol - line)); | ||||
|   if (do_concat && vim_strchr(p_cpo, CPO_CONCAT) == NULL) { | ||||
|     while (eol[0] != NUL) { | ||||
|       line = eol + 1; | ||||
|       const char *const next_eol = skip_to_newline(line); | ||||
|       if (!concat_continued_line(&ga, 400, line, (size_t)(next_eol - line))) { | ||||
|         break; | ||||
|       } | ||||
|       eol = next_eol; | ||||
|     } | ||||
|   } | ||||
|   ga_append(&ga, NUL); | ||||
|   p->offset = (size_t)(eol - p->buf) + 1; | ||||
|   return ga.ga_data; | ||||
| } | ||||
|  | ||||
| /// Create a new script item and allocate script-local vars. @see new_script_vars | ||||
| /// | ||||
| /// @param  name  File name of the script. NULL for anonymous :source. | ||||
| @@ -1990,6 +1957,7 @@ static char *get_str_line(int c, void *cookie, int indent, bool do_concat) | ||||
| /// | ||||
| /// @return  pointer to the created script item. | ||||
| scriptitem_T *new_script_item(char *const name, scid_T *const sid_out) | ||||
|   FUNC_ATTR_NONNULL_RET | ||||
| { | ||||
|   static scid_T last_current_SID = 0; | ||||
|   const scid_T sid = ++last_current_SID; | ||||
| @@ -2012,99 +1980,97 @@ scriptitem_T *new_script_item(char *const name, scid_T *const sid_out) | ||||
|   return SCRIPT_ITEM(sid); | ||||
| } | ||||
|  | ||||
| static int source_using_linegetter(void *cookie, LineGetter fgetline, const char *traceback_name) | ||||
| /// Initialization for sourcing lines from the current buffer. Reads all the | ||||
| /// lines from the buffer and stores it in the cookie grow array. | ||||
| /// Returns a pointer to the name ":source buffer=<n>" on success and NULL on failure. | ||||
| static char *do_source_buffer_init(source_cookie_T *sp, const exarg_T *eap, bool ex_lua) | ||||
|   FUNC_ATTR_NONNULL_ALL | ||||
| { | ||||
|   char *save_sourcing_name = SOURCING_NAME; | ||||
|   linenr_T save_sourcing_lnum = SOURCING_LNUM; | ||||
|   char sourcing_name_buf[256]; | ||||
|   char *sname; | ||||
|   if (save_sourcing_name == NULL) { | ||||
|     sname = (char *)traceback_name; | ||||
|   } else { | ||||
|     snprintf(sourcing_name_buf, sizeof(sourcing_name_buf), | ||||
|              "%s called at %s:%" PRIdLINENR, traceback_name, save_sourcing_name, | ||||
|              save_sourcing_lnum); | ||||
|     sname = sourcing_name_buf; | ||||
|   if (curbuf == NULL) { | ||||
|     return NULL; | ||||
|   } | ||||
|   estack_push(ETYPE_SCRIPT, sname, 0); | ||||
|  | ||||
|   const sctx_T save_current_sctx = current_sctx; | ||||
|   if (!script_is_lua(current_sctx.sc_sid)) { | ||||
|     current_sctx.sc_sid = SID_STR; | ||||
|   if (ex_lua) { | ||||
|     // Use ":{range}lua buffer=<num>" as the script name | ||||
|     snprintf(IObuff, IOSIZE, ":{range}lua buffer=%d", curbuf->b_fnum); | ||||
|   } else { | ||||
|     // Use ":source buffer=<num>" as the script name | ||||
|     snprintf(IObuff, IOSIZE, ":source buffer=%d", curbuf->b_fnum); | ||||
|   } | ||||
|   current_sctx.sc_seq = 0; | ||||
|   current_sctx.sc_lnum = save_sourcing_lnum; | ||||
|   funccal_entry_T entry; | ||||
|   save_funccal(&entry); | ||||
|   int retval = do_cmdline(NULL, fgetline, cookie, | ||||
|                           DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT); | ||||
|   estack_pop(); | ||||
|   current_sctx = save_current_sctx; | ||||
|   restore_funccal(); | ||||
|   return retval; | ||||
|   char *fname = xstrdup(IObuff); | ||||
|  | ||||
|   ga_init(&sp->buflines, sizeof(char *), 100); | ||||
|   // Copy the lines from the buffer into a grow array | ||||
|   for (linenr_T curr_lnum = eap->line1; curr_lnum <= eap->line2; curr_lnum++) { | ||||
|     GA_APPEND(char *, &sp->buflines, xstrdup(ml_get(curr_lnum))); | ||||
|   } | ||||
|   sp->buf_lnum = 0; | ||||
|   sp->source_from_buf_or_str = true; | ||||
|   // When sourcing a range of lines from a buffer, use buffer line number. | ||||
|   sp->sourcing_lnum = eap->line1 - 1; | ||||
|  | ||||
|   return fname; | ||||
| } | ||||
|  | ||||
| /// Initialization for sourcing lines from a string. Reads all the | ||||
| /// lines from the string and stores it in the cookie grow array. | ||||
| static void do_source_str_init(source_cookie_T *sp, const char *str) | ||||
|   FUNC_ATTR_NONNULL_ALL | ||||
| { | ||||
|   ga_init(&sp->buflines, sizeof(char *), 100); | ||||
|   // Copy the lines from the string into a grow array | ||||
|   while (*str != NUL) { | ||||
|     const char *eol = skip_to_newline(str); | ||||
|     GA_APPEND(char *, &sp->buflines, xmemdupz(str, (size_t)(eol - str))); | ||||
|     str = eol + (*eol != NUL); | ||||
|   } | ||||
|   sp->buf_lnum = 0; | ||||
|   sp->source_from_buf_or_str = true; | ||||
| } | ||||
|  | ||||
| void cmd_source_buffer(const exarg_T *const eap, bool ex_lua) | ||||
|   FUNC_ATTR_NONNULL_ALL | ||||
| { | ||||
|   if (curbuf == NULL) { | ||||
|     return; | ||||
|   } | ||||
|   garray_T ga; | ||||
|   ga_init(&ga, sizeof(char), 400); | ||||
|   const linenr_T final_lnum = eap->line2; | ||||
|   // Copy the contents to be executed. | ||||
|   for (linenr_T curr_lnum = eap->line1; curr_lnum <= final_lnum; curr_lnum++) { | ||||
|     // Adjust growsize to current length to speed up concatenating many lines. | ||||
|     if (ga.ga_len > 400) { | ||||
|       ga_set_growsize(&ga, MIN(ga.ga_len, 8000)); | ||||
|     } | ||||
|     ga_concat(&ga, ml_get(curr_lnum)); | ||||
|     ga_append(&ga, NL); | ||||
|   } | ||||
|   ((char *)ga.ga_data)[ga.ga_len - 1] = NUL; | ||||
|   if (ex_lua || strequal(curbuf->b_p_ft, "lua") | ||||
|       || (curbuf->b_fname && path_with_extension(curbuf->b_fname, "lua"))) { | ||||
|     char *name = ex_lua ? ":{range}lua" : ":source (no file)"; | ||||
|     nlua_source_str(ga.ga_data, name); | ||||
|   } else { | ||||
|     const GetStrLineCookie cookie = { | ||||
|       .buf = ga.ga_data, | ||||
|       .offset = 0, | ||||
|     }; | ||||
|     source_using_linegetter((void *)&cookie, get_str_line, ":source (no file)"); | ||||
|   } | ||||
|   ga_clear(&ga); | ||||
|   do_source_ext(NULL, false, DOSO_NONE, NULL, eap, ex_lua, NULL); | ||||
| } | ||||
|  | ||||
| /// Executes lines in `src` as Ex commands. | ||||
| /// Executes lines in `str` as Ex commands. | ||||
| /// | ||||
| /// @see do_source() | ||||
| int do_source_str(const char *cmd, const char *traceback_name) | ||||
| /// @see do_source_ext() | ||||
| int do_source_str(const char *str, char *traceback_name) | ||||
|   FUNC_ATTR_NONNULL_ALL | ||||
| { | ||||
|   GetStrLineCookie cookie = { | ||||
|     .buf = (char *)cmd, | ||||
|     .offset = 0, | ||||
|   }; | ||||
|   return source_using_linegetter((void *)&cookie, get_str_line, traceback_name); | ||||
|   char *const sourcing_name = SOURCING_NAME; | ||||
|   const linenr_T sourcing_lnum = SOURCING_LNUM; | ||||
|   char sname_buf[256]; | ||||
|   if (sourcing_name != NULL) { | ||||
|     snprintf(sname_buf, sizeof(sname_buf), "%s called at %s:%" PRIdLINENR, | ||||
|              traceback_name, sourcing_name, sourcing_lnum); | ||||
|     traceback_name = sname_buf; | ||||
|   } | ||||
|   return do_source_ext(traceback_name, false, DOSO_NONE, NULL, NULL, false, str); | ||||
| } | ||||
|  | ||||
| /// When fname is a 'lua' file nlua_exec_file() is invoked to source it. | ||||
| /// When fname is a .lua file nlua_exec_file() is invoked to source it. | ||||
| /// Otherwise reads the file `fname` and executes its lines as Ex commands. | ||||
| /// | ||||
| /// This function may be called recursively! | ||||
| /// | ||||
| /// @see do_source_str | ||||
| /// | ||||
| /// @param fname | ||||
| /// @param fname        if NULL, source from the current buffer | ||||
| /// @param check_other  check for .vimrc and _vimrc | ||||
| /// @param is_vimrc     DOSO_ value | ||||
| /// @param ret_sid      if not NULL and we loaded the script before, don't load it again | ||||
| /// @param eap          used when sourcing lines from a buffer instead of a file | ||||
| /// @param str          if not NULL, source from the given string | ||||
| /// | ||||
| /// @return  FAIL if file could not be opened, OK otherwise | ||||
| /// | ||||
| /// If a scriptitem_T was found or created "*ret_sid" is set to the SID. | ||||
| int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) | ||||
| static int do_source_ext(char *const fname, const bool check_other, const int is_vimrc, | ||||
|                          int *const ret_sid, const exarg_T *const eap, const bool ex_lua, | ||||
|                          const char *const str) | ||||
| { | ||||
|   source_cookie_T cookie; | ||||
|   uint8_t *firstline = NULL; | ||||
| @@ -2114,11 +2080,24 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) | ||||
|   proftime_T wait_start; | ||||
|   bool trigger_source_post = false; | ||||
|  | ||||
|   CLEAR_FIELD(cookie); | ||||
|   char *fname_exp = NULL; | ||||
|   if (fname == NULL) { | ||||
|     assert(str == NULL); | ||||
|     // sourcing lines from a buffer | ||||
|     fname_exp = do_source_buffer_init(&cookie, eap, ex_lua); | ||||
|     if (fname_exp == NULL) { | ||||
|       return FAIL; | ||||
|     } | ||||
|   } else if (str != NULL) { | ||||
|     do_source_str_init(&cookie, str); | ||||
|     fname_exp = xstrdup(fname); | ||||
|   } else { | ||||
|     char *p = expand_env_save(fname); | ||||
|     if (p == NULL) { | ||||
|       return retval; | ||||
|     } | ||||
|   char *fname_exp = fix_fname(p); | ||||
|     fname_exp = fix_fname(p); | ||||
|     xfree(p); | ||||
|     if (fname_exp == NULL) { | ||||
|       return retval; | ||||
| @@ -2127,9 +2106,10 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) | ||||
|       smsg(0, _("Cannot source a directory: \"%s\""), fname); | ||||
|       goto theend; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // See if we loaded this script before. | ||||
|   int sid = find_script_by_name(fname_exp); | ||||
|   int sid = str != NULL ? SID_STR : find_script_by_name(fname_exp); | ||||
|   if (sid > 0 && ret_sid != NULL) { | ||||
|     // Already loaded and no need to load again, return here. | ||||
|     *ret_sid = sid; | ||||
| @@ -2137,6 +2117,7 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) | ||||
|     goto theend; | ||||
|   } | ||||
|  | ||||
|   if (str == NULL) { | ||||
|     // Apply SourceCmd autocommands, they should get the file and source it. | ||||
|     if (has_autocmd(EVENT_SOURCECMD, fname_exp, NULL) | ||||
|         && apply_autocmds(EVENT_SOURCECMD, fname_exp, fname_exp, | ||||
| @@ -2151,12 +2132,15 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) | ||||
|  | ||||
|     // Apply SourcePre autocommands, they may get the file. | ||||
|     apply_autocmds(EVENT_SOURCEPRE, fname_exp, fname_exp, false, curbuf); | ||||
|   } | ||||
|  | ||||
|   if (!cookie.source_from_buf_or_str) { | ||||
|     cookie.fp = fopen_noinh_readbin(fname_exp); | ||||
|   } | ||||
|   if (cookie.fp == NULL && check_other) { | ||||
|     // Try again, replacing file name ".nvimrc" by "_nvimrc" or vice versa, | ||||
|     // and ".exrc" by "_exrc" or vice versa. | ||||
|     p = path_tail(fname_exp); | ||||
|     char *p = path_tail(fname_exp); | ||||
|     if ((*p == '.' || *p == '_') | ||||
|         && (STRICMP(p + 1, "nvimrc") == 0 || STRICMP(p + 1, "exrc") == 0)) { | ||||
|       *p = (*p == '_') ? '.' : '_'; | ||||
| @@ -2164,7 +2148,7 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (cookie.fp == NULL) { | ||||
|   if (cookie.fp == NULL && !cookie.source_from_buf_or_str) { | ||||
|     if (p_verbose > 1) { | ||||
|       verbose_enter(); | ||||
|       if (SOURCING_NAME == NULL) { | ||||
| @@ -2201,13 +2185,8 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) | ||||
|   } else { | ||||
|     cookie.fileformat = EOL_UNKNOWN; | ||||
|   } | ||||
|   cookie.error = false; | ||||
| #endif | ||||
|  | ||||
|   cookie.nextline = NULL; | ||||
|   cookie.sourcing_lnum = 0; | ||||
|   cookie.finished = false; | ||||
|  | ||||
|   // Check if this script has a breakpoint. | ||||
|   cookie.breakpoint = dbg_find_breakpoint(true, fname_exp, 0); | ||||
|   cookie.fname = fname_exp; | ||||
| @@ -2236,15 +2215,13 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) | ||||
|  | ||||
|   const sctx_T save_current_sctx = current_sctx; | ||||
|  | ||||
|   current_sctx.sc_lnum = 0; | ||||
|  | ||||
|   // Always use a new sequence number. | ||||
|   current_sctx.sc_seq = ++last_current_SID_seq; | ||||
|  | ||||
|   if (sid > 0) { | ||||
|     // loading the same script again | ||||
|     si = SCRIPT_ITEM(sid); | ||||
|   } else { | ||||
|   } else if (str == NULL) { | ||||
|     // It's new, generate a new SID. | ||||
|     si = new_script_item(fname_exp, &sid); | ||||
|     si->sn_lua = path_with_extension(fname_exp, "lua"); | ||||
| @@ -2253,12 +2230,20 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) | ||||
|       *ret_sid = sid; | ||||
|     } | ||||
|   } | ||||
|   // Sourcing a string doesn't allocate a script item immediately. | ||||
|   assert((si != NULL) == (str == NULL)); | ||||
|  | ||||
|   // Don't change sc_sid to SID_STR when sourcing a string from a Lua script, | ||||
|   // as keeping the current sc_sid allows more useful :verbose messages. | ||||
|   if (str == NULL || !script_is_lua(current_sctx.sc_sid)) { | ||||
|     current_sctx.sc_sid = sid; | ||||
|     current_sctx.sc_lnum = 0; | ||||
|   } | ||||
|  | ||||
|   // Keep the sourcing name/lnum, for recursive calls. | ||||
|   estack_push(ETYPE_SCRIPT, si->sn_name, 0); | ||||
|   estack_push(ETYPE_SCRIPT, si != NULL ? si->sn_name : fname_exp, 0); | ||||
|  | ||||
|   if (l_do_profiling == PROF_YES) { | ||||
|   if (l_do_profiling == PROF_YES && si != NULL) { | ||||
|     bool forceit = false; | ||||
|  | ||||
|     // Check if we do profiling for this script. | ||||
| @@ -2275,7 +2260,12 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) | ||||
|  | ||||
|   cookie.conv.vc_type = CONV_NONE;              // no conversion | ||||
|  | ||||
|   if (si->sn_lua) { | ||||
|   if (fname == NULL | ||||
|       && (ex_lua || strequal(curbuf->b_p_ft, "lua") | ||||
|           || (curbuf->b_fname && path_with_extension(curbuf->b_fname, "lua")))) { | ||||
|     // Source lines from the current buffer as lua | ||||
|     nlua_exec_ga(&cookie.buflines, fname_exp); | ||||
|   } else if (si != NULL && si->sn_lua) { | ||||
|     // Source the file as lua | ||||
|     nlua_exec_file(fname_exp); | ||||
|   } else { | ||||
| @@ -2285,7 +2275,7 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) | ||||
|         && firstline[1] == 0xbb && firstline[2] == 0xbf) { | ||||
|       // Found BOM; setup conversion, skip over BOM and recode the line. | ||||
|       convert_setup(&cookie.conv, "utf-8", p_enc); | ||||
|       p = string_convert(&cookie.conv, (char *)firstline + 3, NULL); | ||||
|       char *p = string_convert(&cookie.conv, (char *)firstline + 3, NULL); | ||||
|       if (p == NULL) { | ||||
|         p = xstrdup((char *)firstline + 3); | ||||
|       } | ||||
| @@ -2298,7 +2288,7 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) | ||||
|   } | ||||
|   retval = OK; | ||||
|  | ||||
|   if (l_do_profiling == PROF_YES) { | ||||
|   if (l_do_profiling == PROF_YES && si != NULL) { | ||||
|     // Get "si" again, "script_items" may have been reallocated. | ||||
|     si = SCRIPT_ITEM(current_sctx.sc_sid); | ||||
|     if (si->sn_prof_on) { | ||||
| @@ -2345,12 +2335,17 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) | ||||
|   if (l_do_profiling == PROF_YES) { | ||||
|     prof_child_exit(&wait_start);    // leaving a child now | ||||
|   } | ||||
|   if (cookie.fp != NULL) { | ||||
|     fclose(cookie.fp); | ||||
|   } | ||||
|   if (cookie.source_from_buf_or_str) { | ||||
|     ga_clear_strings(&cookie.buflines); | ||||
|   } | ||||
|   xfree(cookie.nextline); | ||||
|   xfree(firstline); | ||||
|   convert_setup(&cookie.conv, NULL, NULL); | ||||
|  | ||||
|   if (trigger_source_post) { | ||||
|   if (str == NULL && trigger_source_post) { | ||||
|     apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf); | ||||
|   } | ||||
|  | ||||
| @@ -2359,6 +2354,13 @@ theend: | ||||
|   return retval; | ||||
| } | ||||
|  | ||||
| /// @param check_other  check for .vimrc and _vimrc | ||||
| /// @param is_vimrc     DOSO_ value | ||||
| int do_source(char *fname, bool check_other, int is_vimrc, int *ret_sid) | ||||
| { | ||||
|   return do_source_ext(fname, check_other, is_vimrc, ret_sid, NULL, false, NULL); | ||||
| } | ||||
|  | ||||
| /// Checks if the script with the given script ID is a Lua script. | ||||
| bool script_is_lua(scid_T sid) | ||||
| { | ||||
| @@ -2621,7 +2623,7 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat) | ||||
|   char *line; | ||||
|  | ||||
|   // If breakpoints have been added/deleted need to check for it. | ||||
|   if (sp->dbg_tick < debug_tick) { | ||||
|   if ((sp->dbg_tick < debug_tick) && !sp->source_from_buf_or_str) { | ||||
|     sp->breakpoint = dbg_find_breakpoint(true, sp->fname, SOURCING_LNUM); | ||||
|     sp->dbg_tick = debug_tick; | ||||
|   } | ||||
| @@ -2632,7 +2634,7 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat) | ||||
|   SOURCING_LNUM = sp->sourcing_lnum + 1; | ||||
|   // Get current line.  If there is a read-ahead line, use it, otherwise get | ||||
|   // one now.  "fp" is NULL if actually using a string. | ||||
|   if (sp->finished || sp->fp == NULL) { | ||||
|   if (sp->finished || (!sp->source_from_buf_or_str && sp->fp == NULL)) { | ||||
|     line = NULL; | ||||
|   } else if (sp->nextline == NULL) { | ||||
|     line = get_one_sourceline(sp); | ||||
| @@ -2685,7 +2687,8 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat) | ||||
|   } | ||||
|  | ||||
|   // Did we encounter a breakpoint? | ||||
|   if (sp->breakpoint != 0 && sp->breakpoint <= SOURCING_LNUM) { | ||||
|   if (!sp->source_from_buf_or_str | ||||
|       && sp->breakpoint != 0 && sp->breakpoint <= SOURCING_LNUM) { | ||||
|     dbg_breakpoint(sp->fname, SOURCING_LNUM); | ||||
|     // Find next breakpoint. | ||||
|     sp->breakpoint = dbg_find_breakpoint(true, sp->fname, SOURCING_LNUM); | ||||
| @@ -2714,18 +2717,24 @@ static char *get_one_sourceline(source_cookie_T *sp) | ||||
|   while (true) { | ||||
|     // make room to read at least 120 (more) characters | ||||
|     ga_grow(&ga, 120); | ||||
|     if (sp->source_from_buf_or_str) { | ||||
|       if (sp->buf_lnum >= sp->buflines.ga_len) { | ||||
|         break;              // all the lines are processed | ||||
|       } | ||||
|       ga_concat(&ga, ((char **)sp->buflines.ga_data)[sp->buf_lnum]); | ||||
|       sp->buf_lnum++; | ||||
|       buf = (char *)ga.ga_data; | ||||
|     } else { | ||||
|       buf = ga.ga_data; | ||||
|  | ||||
| retry: | ||||
|       errno = 0; | ||||
|     if (fgets(buf + ga.ga_len, ga.ga_maxlen - ga.ga_len, | ||||
|               sp->fp) == NULL) { | ||||
|       if (fgets(buf + ga.ga_len, ga.ga_maxlen - ga.ga_len, sp->fp) == NULL) { | ||||
|         if (errno == EINTR) { | ||||
|           goto retry; | ||||
|         } | ||||
|  | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     len = ga.ga_len + (int)strlen(buf + ga.ga_len); | ||||
| #ifdef USE_CRNL | ||||
|     // Ignore a trailing CTRL-Z, when in Dos mode. Only recognize the | ||||
| @@ -2797,7 +2806,7 @@ retry: | ||||
|   return NULL; | ||||
| } | ||||
|  | ||||
| /// Returns true if sourcing a script either from a file or a buffer. | ||||
| /// Returns true if sourcing a script either from a file or a buffer or a string. | ||||
| /// Otherwise returns false. | ||||
| int sourcing_a_script(exarg_T *eap) | ||||
| { | ||||
|   | ||||
| @@ -140,7 +140,7 @@ describe('API', function() | ||||
|     it(':verbose set {option}?', function() | ||||
|       api.nvim_exec2('set nowrap', { output = false }) | ||||
|       eq( | ||||
|         { output = 'nowrap\n\tLast set from anonymous :source' }, | ||||
|         { output = 'nowrap\n\tLast set from anonymous :source line 1' }, | ||||
|         api.nvim_exec2('verbose set wrap?', { output = true }) | ||||
|       ) | ||||
|  | ||||
| @@ -153,7 +153,7 @@ describe('API', function() | ||||
|         { output = false } | ||||
|       ) | ||||
|       eq( | ||||
|         { output = 'nowrap\n\tLast set from anonymous :source (script id 1)' }, | ||||
|         { output = 'nowrap\n\tLast set from anonymous :source (script id 1) line 2' }, | ||||
|         api.nvim_exec2('verbose set wrap?', { output = true }) | ||||
|       ) | ||||
|     end) | ||||
| @@ -296,16 +296,21 @@ describe('API', function() | ||||
|       eq('ñxx', api.nvim_get_current_line()) | ||||
|     end) | ||||
|  | ||||
|     it('can use :finish', function() | ||||
|       api.nvim_exec2('let g:var = 123\nfinish\nlet g:var = 456', {}) | ||||
|       eq(123, api.nvim_get_var('var')) | ||||
|     end) | ||||
|  | ||||
|     it('execution error', function() | ||||
|       eq( | ||||
|         'nvim_exec2(): Vim:E492: Not an editor command: bogus_command', | ||||
|         'nvim_exec2(), line 1: Vim:E492: Not an editor command: bogus_command', | ||||
|         pcall_err(request, 'nvim_exec2', 'bogus_command', {}) | ||||
|       ) | ||||
|       eq('', api.nvim_eval('v:errmsg')) -- v:errmsg was not updated. | ||||
|       eq('', eval('v:exception')) | ||||
|  | ||||
|       eq( | ||||
|         'nvim_exec2(): Vim(buffer):E86: Buffer 23487 does not exist', | ||||
|         'nvim_exec2(), line 1: Vim(buffer):E86: Buffer 23487 does not exist', | ||||
|         pcall_err(request, 'nvim_exec2', 'buffer 23487', {}) | ||||
|       ) | ||||
|       eq('', eval('v:errmsg')) -- v:errmsg was not updated. | ||||
| @@ -338,17 +343,28 @@ describe('API', function() | ||||
|       write_file(sourcing_fname, 'call nvim_exec2("source ' .. fname .. '", {"output": v:false})\n') | ||||
|       api.nvim_exec2('set verbose=2', { output = false }) | ||||
|       local traceback_output = dedent([[ | ||||
|         line 0: sourcing "%s" | ||||
|         line 0: sourcing "%s" | ||||
|         sourcing "nvim_exec2()" | ||||
|         line 1: sourcing "nvim_exec2() called at nvim_exec2():1" | ||||
|         line 1: sourcing "%s" | ||||
|         line 1: sourcing "nvim_exec2() called at %s:1" | ||||
|         line 1: sourcing "%s" | ||||
|         hello | ||||
|         finished sourcing %s | ||||
|         continuing in nvim_exec2() called at %s:1 | ||||
|         finished sourcing nvim_exec2() called at %s:1 | ||||
|         continuing in %s | ||||
|         finished sourcing %s | ||||
|         continuing in nvim_exec2() called at nvim_exec2():0]]):format( | ||||
|         continuing in nvim_exec2() called at nvim_exec2():1 | ||||
|         finished sourcing nvim_exec2() called at nvim_exec2():1 | ||||
|         continuing in nvim_exec2() | ||||
|         finished sourcing nvim_exec2()]]):format( | ||||
|         sourcing_fname, | ||||
|         sourcing_fname, | ||||
|         fname, | ||||
|         fname, | ||||
|         sourcing_fname, | ||||
|         sourcing_fname, | ||||
|         sourcing_fname, | ||||
|         sourcing_fname | ||||
|       ) | ||||
|       eq( | ||||
|   | ||||
| @@ -97,14 +97,26 @@ describe(':cquit', function() | ||||
|   end) | ||||
|  | ||||
|   it('exits with redir msg for multiple exit codes after :cquit 1 2', function() | ||||
|     test_cq('cquit 1 2', nil, 'nvim_exec2(): Vim(cquit):E488: Trailing characters: 2: cquit 1 2') | ||||
|     test_cq( | ||||
|       'cquit 1 2', | ||||
|       nil, | ||||
|       'nvim_exec2(), line 1: Vim(cquit):E488: Trailing characters: 2: cquit 1 2' | ||||
|     ) | ||||
|   end) | ||||
|  | ||||
|   it('exits with redir msg for non-number exit code after :cquit X', function() | ||||
|     test_cq('cquit X', nil, 'nvim_exec2(): Vim(cquit):E488: Trailing characters: X: cquit X') | ||||
|     test_cq( | ||||
|       'cquit X', | ||||
|       nil, | ||||
|       'nvim_exec2(), line 1: Vim(cquit):E488: Trailing characters: X: cquit X' | ||||
|     ) | ||||
|   end) | ||||
|  | ||||
|   it('exits with redir msg for negative exit code after :cquit -1', function() | ||||
|     test_cq('cquit -1', nil, 'nvim_exec2(): Vim(cquit):E488: Trailing characters: -1: cquit -1') | ||||
|     test_cq( | ||||
|       'cquit -1', | ||||
|       nil, | ||||
|       'nvim_exec2(), line 1: Vim(cquit):E488: Trailing characters: -1: cquit -1' | ||||
|     ) | ||||
|   end) | ||||
| end) | ||||
|   | ||||
| @@ -42,59 +42,62 @@ describe('named marks', function() | ||||
|   it('errors when set out of range with :mark', function() | ||||
|     command('edit ' .. file1) | ||||
|     local err = pcall_err(n.exec_capture, '1000mark x') | ||||
|     eq('nvim_exec2(): Vim(mark):E16: Invalid range: 1000mark x', err) | ||||
|     eq('nvim_exec2(), line 1: Vim(mark):E16: Invalid range: 1000mark x', err) | ||||
|   end) | ||||
|  | ||||
|   it('errors when set out of range with :k', function() | ||||
|     command('edit ' .. file1) | ||||
|     local err = pcall_err(n.exec_capture, '1000kx') | ||||
|     eq('nvim_exec2(): Vim(k):E16: Invalid range: 1000kx', err) | ||||
|     eq('nvim_exec2(), line 1: Vim(k):E16: Invalid range: 1000kx', err) | ||||
|   end) | ||||
|  | ||||
|   it('errors on unknown mark name with :mark', function() | ||||
|     command('edit ' .. file1) | ||||
|     local err = pcall_err(n.exec_capture, 'mark #') | ||||
|     eq('nvim_exec2(): Vim(mark):E191: Argument must be a letter or forward/backward quote', err) | ||||
|     eq( | ||||
|       'nvim_exec2(), line 1: Vim(mark):E191: Argument must be a letter or forward/backward quote', | ||||
|       err | ||||
|     ) | ||||
|   end) | ||||
|  | ||||
|   it("errors on unknown mark name with '", function() | ||||
|     command('edit ' .. file1) | ||||
|     local err = pcall_err(n.exec_capture, "normal! '#") | ||||
|     eq('nvim_exec2(): Vim(normal):E78: Unknown mark', err) | ||||
|     eq('nvim_exec2(), line 1: Vim(normal):E78: Unknown mark', err) | ||||
|   end) | ||||
|  | ||||
|   it('errors on unknown mark name with `', function() | ||||
|     command('edit ' .. file1) | ||||
|     local err = pcall_err(n.exec_capture, 'normal! `#') | ||||
|     eq('nvim_exec2(): Vim(normal):E78: Unknown mark', err) | ||||
|     eq('nvim_exec2(), line 1: Vim(normal):E78: Unknown mark', err) | ||||
|   end) | ||||
|  | ||||
|   it("errors when moving to a mark that is not set with '", function() | ||||
|     command('edit ' .. file1) | ||||
|     local err = pcall_err(n.exec_capture, "normal! 'z") | ||||
|     eq('nvim_exec2(): Vim(normal):E20: Mark not set', err) | ||||
|     eq('nvim_exec2(), line 1: Vim(normal):E20: Mark not set', err) | ||||
|     err = pcall_err(n.exec_capture, "normal! '.") | ||||
|     eq('nvim_exec2(): Vim(normal):E20: Mark not set', err) | ||||
|     eq('nvim_exec2(), line 1: Vim(normal):E20: Mark not set', err) | ||||
|   end) | ||||
|  | ||||
|   it('errors when moving to a mark that is not set with `', function() | ||||
|     command('edit ' .. file1) | ||||
|     local err = pcall_err(n.exec_capture, 'normal! `z') | ||||
|     eq('nvim_exec2(): Vim(normal):E20: Mark not set', err) | ||||
|     eq('nvim_exec2(), line 1: Vim(normal):E20: Mark not set', err) | ||||
|     err = pcall_err(n.exec_capture, 'normal! `>') | ||||
|     eq('nvim_exec2(): Vim(normal):E20: Mark not set', err) | ||||
|     eq('nvim_exec2(), line 1: Vim(normal):E20: Mark not set', err) | ||||
|   end) | ||||
|  | ||||
|   it("errors when moving to a global mark that is not set with '", function() | ||||
|     command('edit ' .. file1) | ||||
|     local err = pcall_err(n.exec_capture, "normal! 'Z") | ||||
|     eq('nvim_exec2(): Vim(normal):E20: Mark not set', err) | ||||
|     eq('nvim_exec2(), line 1: Vim(normal):E20: Mark not set', err) | ||||
|   end) | ||||
|  | ||||
|   it('errors when moving to a global mark that is not set with `', function() | ||||
|     command('edit ' .. file1) | ||||
|     local err = pcall_err(n.exec_capture, 'normal! `Z') | ||||
|     eq('nvim_exec2(): Vim(normal):E20: Mark not set', err) | ||||
|     eq('nvim_exec2(), line 1: Vim(normal):E20: Mark not set', err) | ||||
|   end) | ||||
|  | ||||
|   it("can move to them using '", function() | ||||
| @@ -169,7 +172,7 @@ describe('named marks', function() | ||||
|     command('next') | ||||
|     command('bw! ' .. file1) | ||||
|     local err = pcall_err(n.exec_capture, "normal! 'A") | ||||
|     eq('nvim_exec2(): Vim(normal):E92: Buffer 1 not found', err) | ||||
|     eq('nvim_exec2(), line 1: Vim(normal):E92: Buffer 1 not found', err) | ||||
|     os.remove(file1) | ||||
|   end) | ||||
|  | ||||
|   | ||||
| @@ -101,16 +101,14 @@ describe(':source', function() | ||||
|         \ k: "v" | ||||
|        "\ (o_o) | ||||
|         \ } | ||||
|       let c = expand("<SID>")->empty() | ||||
|       let c = expand("<SID>") | ||||
|       let s:s = 0zbeef.cafe | ||||
|       let d = s:s]]) | ||||
|  | ||||
|     command('source') | ||||
|     eq('2', exec_capture('echo a')) | ||||
|     eq("{'k': 'v'}", exec_capture('echo b')) | ||||
|  | ||||
|     -- Script items are created only on script var access | ||||
|     eq('1', exec_capture('echo c')) | ||||
|     eq('<SNR>1_', exec_capture('echo c')) | ||||
|     eq('0zBEEFCAFE', exec_capture('echo d')) | ||||
|  | ||||
|     exec('set cpoptions+=C') | ||||
| @@ -136,6 +134,10 @@ describe(':source', function() | ||||
|     feed_command(':source') | ||||
|     eq('3', exec_capture('echo a')) | ||||
|  | ||||
|     -- Source last line only | ||||
|     feed_command(':$source') | ||||
|     eq('Vim(echo):E117: Unknown function: s:C', exc_exec('echo D()')) | ||||
|  | ||||
|     -- Source from 2nd line to end of file | ||||
|     feed('ggjVG') | ||||
|     feed_command(':source') | ||||
| @@ -143,9 +145,9 @@ describe(':source', function() | ||||
|     eq("{'K': 'V'}", exec_capture('echo b')) | ||||
|     eq('<SNR>1_C()', exec_capture('echo D()')) | ||||
|  | ||||
|     -- Source last line only | ||||
|     -- Source last line after the lines that define s:C() have been sourced | ||||
|     feed_command(':$source') | ||||
|     eq('Vim(echo):E117: Unknown function: s:C', exc_exec('echo D()')) | ||||
|     eq('<SNR>1_C()', exec_capture('echo D()')) | ||||
|  | ||||
|     exec('set cpoptions+=C') | ||||
|     eq("Vim(let):E723: Missing end of Dictionary '}': ", exc_exec("'<,'>source")) | ||||
| @@ -248,9 +250,9 @@ describe(':source', function() | ||||
|  | ||||
|         eq(12, eval('g:c')) | ||||
|         eq('  \\ 1\n "\\ 2', exec_lua('return _G.a')) | ||||
|         eq(':source (no file)', api.nvim_get_var('sfile_value')) | ||||
|         eq(':source (no file)', api.nvim_get_var('stack_value')) | ||||
|         eq(':source (no file)', api.nvim_get_var('script_value')) | ||||
|         eq(':source buffer=1', api.nvim_get_var('sfile_value')) | ||||
|         eq(':source buffer=1', api.nvim_get_var('stack_value')) | ||||
|         eq(':source buffer=1', api.nvim_get_var('script_value')) | ||||
|       end) | ||||
|     end | ||||
|  | ||||
|   | ||||
| @@ -256,7 +256,7 @@ TestHL2        xxx guibg=Green | ||||
|     local result = exec_capture(':verbose set tw?') | ||||
|     local loc = get_last_set_location(40) | ||||
|     if loc == 'Lua (run Nvim with -V1 for more details)' then | ||||
|       loc = 'anonymous :source (script id 1)' | ||||
|       loc = 'anonymous :source (script id 1) line 5' | ||||
|     end | ||||
|     eq( | ||||
|       string.format( | ||||
|   | ||||
| @@ -148,10 +148,10 @@ describe('assert function:', function() | ||||
|         call assert_true('', 'file two') | ||||
|       ]]) | ||||
|       expected_errors({ | ||||
|         'nvim_exec2(): equal assertion failed: Expected 1 but got 100', | ||||
|         "nvim_exec2(): true  assertion failed: Expected False but got 'true'", | ||||
|         "nvim_exec2(): false assertion failed: Expected True but got 'false'", | ||||
|         "nvim_exec2(): file two: Expected True but got ''", | ||||
|         'nvim_exec2() line 1: equal assertion failed: Expected 1 but got 100', | ||||
|         "nvim_exec2() line 2: true  assertion failed: Expected False but got 'true'", | ||||
|         "nvim_exec2() line 3: false assertion failed: Expected True but got 'false'", | ||||
|         "nvim_exec2() line 1: file two: Expected True but got ''", | ||||
|       }) | ||||
|     end) | ||||
|   end) | ||||
|   | ||||
| @@ -207,7 +207,8 @@ describe(':lua', function() | ||||
|  | ||||
|     -- ":{range}lua" fails on invalid Lua code. | ||||
|     eq( | ||||
|       [[:{range}lua: Vim(lua):E5107: Error loading lua [string ":{range}lua"]:0: '=' expected near '<eof>']], | ||||
|       [[:{range}lua buffer=1: Vim(lua):E5107: Error loading lua ]] | ||||
|         .. [[[string ":{range}lua buffer=1"]:0: '=' expected near '<eof>']], | ||||
|       pcall_err(command, '1lua') | ||||
|     ) | ||||
|  | ||||
|   | ||||
| @@ -3956,7 +3956,7 @@ stack traceback: | ||||
|  | ||||
|     it('failure modes', function() | ||||
|       matches( | ||||
|         'nvim_exec2%(%): Vim:E492: Not an editor command: fooooo', | ||||
|         'nvim_exec2%(%), line 1: Vim:E492: Not an editor command: fooooo', | ||||
|         pcall_err(exec_lua, [[vim.api.nvim_win_call(0, function() vim.cmd 'fooooo' end)]]) | ||||
|       ) | ||||
|       eq( | ||||
|   | ||||
| @@ -114,4 +114,500 @@ func Test_nested_script() | ||||
|   call StopVimInTerminal(buf) | ||||
| endfunc | ||||
|  | ||||
| " Test for sourcing a script from the current buffer | ||||
| func Test_source_buffer() | ||||
|   new | ||||
|   " Source a simple script | ||||
|   let lines =<< trim END | ||||
|     let a = "Test" | ||||
|     let b = 20 | ||||
|  | ||||
|     let c = [1.1] | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal(['Test', 20, [1.1]], [g:a, g:b, g:c]) | ||||
|  | ||||
|   " Source a range of lines in the current buffer | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|     let a = 10 | ||||
|     let a += 20 | ||||
|     let a += 30 | ||||
|     let a += 40 | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   .source | ||||
|   call assert_equal(10, g:a) | ||||
|   3source | ||||
|   call assert_equal(40, g:a) | ||||
|   2,3source | ||||
|   call assert_equal(90, g:a) | ||||
|  | ||||
|   " Make sure the script line number is correct when sourcing a range of | ||||
|   " lines. | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|      Line 1 | ||||
|      Line 2 | ||||
|      func Xtestfunc() | ||||
|        return expand("<sflnum>") | ||||
|      endfunc | ||||
|      Line 3 | ||||
|      Line 4 | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   3,5source | ||||
|   call assert_equal('4', Xtestfunc()) | ||||
|   delfunc Xtestfunc | ||||
|  | ||||
|   " Source a script with line continuation lines | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|     let m = [ | ||||
|       \   1, | ||||
|       \   2, | ||||
|       \ ] | ||||
|     call add(m, 3) | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal([1, 2, 3], g:m) | ||||
|   " Source a script with line continuation lines and a comment | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|     let m = [ | ||||
|       "\ first entry | ||||
|       \   'a', | ||||
|       "\ second entry | ||||
|       \   'b', | ||||
|       \ ] | ||||
|     " third entry | ||||
|     call add(m, 'c') | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal(['a', 'b', 'c'], g:m) | ||||
|   " Source an incomplete line continuation line | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|     let k = [ | ||||
|       \ | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   call assert_fails('source', 'E697:') | ||||
|   " Source a function with a for loop | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|     let m = [] | ||||
|     " test function | ||||
|     func! Xtest() | ||||
|       for i in range(5, 7) | ||||
|         call add(g:m, i) | ||||
|       endfor | ||||
|     endfunc | ||||
|     call Xtest() | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal([5, 6, 7], g:m) | ||||
|   " Source an empty buffer | ||||
|   %d _ | ||||
|   source | ||||
|  | ||||
|   " test for script local functions and variables | ||||
|   let lines =<< trim END | ||||
|     let s:var1 = 10 | ||||
|     func s:F1() | ||||
|       let s:var1 += 1 | ||||
|       return s:var1 | ||||
|     endfunc | ||||
|     func s:F2() | ||||
|     endfunc | ||||
|     let g:ScriptID = expand("<SID>") | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_true(g:ScriptID != '') | ||||
|   call assert_true(exists('*' .. g:ScriptID .. 'F1')) | ||||
|   call assert_true(exists('*' .. g:ScriptID .. 'F2')) | ||||
|   call assert_equal(11, call(g:ScriptID .. 'F1', [])) | ||||
|  | ||||
|   " the same script ID should be used even if the buffer is sourced more than | ||||
|   " once | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|     let g:ScriptID = expand("<SID>") | ||||
|     let g:Count += 1 | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   let g:Count = 0 | ||||
|   source | ||||
|   call assert_true(g:ScriptID != '') | ||||
|   let scid = g:ScriptID | ||||
|   source | ||||
|   call assert_equal(scid, g:ScriptID) | ||||
|   call assert_equal(2, g:Count) | ||||
|   source | ||||
|   call assert_equal(scid, g:ScriptID) | ||||
|   call assert_equal(3, g:Count) | ||||
|  | ||||
|   " test for the script line number | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|     " comment | ||||
|     let g:Slnum1 = expand("<slnum>") | ||||
|     let i = 1 + | ||||
|            \ 2 + | ||||
|           "\ comment | ||||
|            \ 3 | ||||
|     let g:Slnum2 = expand("<slnum>") | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal('2', g:Slnum1) | ||||
|   call assert_equal('7', g:Slnum2) | ||||
|  | ||||
|   " test for retaining the same script number across source calls | ||||
|   let lines =<< trim END | ||||
|      let g:ScriptID1 = expand("<SID>") | ||||
|      let g:Slnum1 = expand("<slnum>") | ||||
|      let l =<< trim END | ||||
|        let g:Slnum2 = expand("<slnum>") | ||||
|        let g:ScriptID2 = expand("<SID>") | ||||
|      END | ||||
|      new | ||||
|      call setline(1, l) | ||||
|      source | ||||
|      bw! | ||||
|      let g:ScriptID3 = expand("<SID>") | ||||
|      let g:Slnum3 = expand("<slnum>") | ||||
|   END | ||||
|   call writefile(lines, 'Xscript') | ||||
|   source Xscript | ||||
|   call assert_true(g:ScriptID1 != g:ScriptID2) | ||||
|   call assert_equal(g:ScriptID1, g:ScriptID3) | ||||
|   call assert_equal('2', g:Slnum1) | ||||
|   call assert_equal('1', g:Slnum2) | ||||
|   call assert_equal('12', g:Slnum3) | ||||
|   call delete('Xscript') | ||||
|  | ||||
|   " test for sourcing a heredoc | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|      let a = 1 | ||||
|      let heredoc =<< trim DATA | ||||
|         red | ||||
|           green | ||||
|         blue | ||||
|      DATA | ||||
|      let b = 2 | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal(['red', '  green', 'blue'], g:heredoc) | ||||
|  | ||||
|   " test for a while and for statement | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|      let a = 0 | ||||
|      let b = 1 | ||||
|      while b <= 10 | ||||
|        let a += 10 | ||||
|        let b += 1 | ||||
|      endwhile | ||||
|      for i in range(5) | ||||
|        let a += 10 | ||||
|      endfor | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal(150, g:a) | ||||
|  | ||||
|   " test for sourcing the same buffer multiple times after changing a function | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|      func Xtestfunc() | ||||
|        return "one" | ||||
|      endfunc | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal("one", Xtestfunc()) | ||||
|   call setline(2, '  return "two"') | ||||
|   source | ||||
|   call assert_equal("two", Xtestfunc()) | ||||
|   call setline(2, '  return "three"') | ||||
|   source | ||||
|   call assert_equal("three", Xtestfunc()) | ||||
|   delfunc Xtestfunc | ||||
|  | ||||
|   " test for using try/catch | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|      let Trace = '1' | ||||
|      try | ||||
|        let a1 = b1 | ||||
|      catch | ||||
|        let Trace ..= '2' | ||||
|      finally | ||||
|        let Trace ..= '3' | ||||
|      endtry | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal("123", g:Trace) | ||||
|  | ||||
|   " test with the finish command | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|      let g:Color = 'blue' | ||||
|      finish | ||||
|      let g:Color = 'green' | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal('blue', g:Color) | ||||
|  | ||||
|   " Test for the SourcePre and SourcePost autocmds | ||||
|   augroup Xtest | ||||
|     au! | ||||
|     au SourcePre * let g:XsourcePre=4 | ||||
|           \ | let g:XsourcePreFile = expand("<afile>") | ||||
|     au SourcePost * let g:XsourcePost=6 | ||||
|           \ | let g:XsourcePostFile = expand("<afile>") | ||||
|   augroup END | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|      let a = 1 | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal(4, g:XsourcePre) | ||||
|   call assert_equal(6, g:XsourcePost) | ||||
|   call assert_equal(':source buffer=' .. bufnr(), g:XsourcePreFile) | ||||
|   call assert_equal(':source buffer=' .. bufnr(), g:XsourcePostFile) | ||||
|   augroup Xtest | ||||
|     au! | ||||
|   augroup END | ||||
|   augroup! Xtest | ||||
|  | ||||
|   %bw! | ||||
| endfunc | ||||
|  | ||||
| " Test for sourcing a Vim9 script from the current buffer | ||||
| func Test_source_buffer_vim9() | ||||
|   throw 'Skipped: Vim9 script is N/A' | ||||
|   new | ||||
|  | ||||
|   " test for sourcing a Vim9 script | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|      vim9script | ||||
|  | ||||
|      # check dict | ||||
|      var x: number = 10 | ||||
|      def g:Xtestfunc(): number | ||||
|        return x | ||||
|      enddef | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal(10, Xtestfunc()) | ||||
|  | ||||
|   " test for sourcing a vim9 script with line continuation | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|      vim9script | ||||
|  | ||||
|      g:Str1 = "hello " | ||||
|               .. "world" | ||||
|               .. ", how are you?" | ||||
|      g:Colors = [ | ||||
|        'red', | ||||
|        # comment | ||||
|        'blue' | ||||
|        ] | ||||
|      g:Dict = { | ||||
|        a: 22, | ||||
|        # comment | ||||
|        b: 33 | ||||
|        } | ||||
|  | ||||
|      # calling a function with line continuation | ||||
|      def Sum(...values: list<number>): number | ||||
|        var sum: number = 0 | ||||
|        for v in values | ||||
|          sum += v | ||||
|        endfor | ||||
|        return sum | ||||
|      enddef | ||||
|      g:Total1 = Sum(10, | ||||
|                    20, | ||||
|                    30) | ||||
|  | ||||
|      var i: number = 0 | ||||
|      while i < 10 | ||||
|        # while loop | ||||
|        i += | ||||
|            1 | ||||
|      endwhile | ||||
|      g:Count1 = i | ||||
|  | ||||
|      # for loop | ||||
|      g:Count2 = 0 | ||||
|      for j in range(10, 20) | ||||
|        g:Count2 += | ||||
|            i | ||||
|      endfor | ||||
|  | ||||
|      g:Total2 = 10 + | ||||
|                 20 - | ||||
|                 5 | ||||
|  | ||||
|      g:Result1 = g:Total2 > 1 | ||||
|                 ? 'red' | ||||
|                 : 'blue' | ||||
|  | ||||
|      g:Str2 = 'x' | ||||
|               ->repeat(10) | ||||
|               ->trim() | ||||
|               ->strpart(4) | ||||
|  | ||||
|      g:Result2 = g:Dict | ||||
|                     .a | ||||
|  | ||||
|      augroup Test | ||||
|        au! | ||||
|        au BufNewFile Xfile g:readFile = 1 | ||||
|              | g:readExtra = 2 | ||||
|      augroup END | ||||
|      g:readFile = 0 | ||||
|      g:readExtra = 0 | ||||
|      new Xfile | ||||
|      bwipe! | ||||
|      augroup Test | ||||
|        au! | ||||
|      augroup END | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal("hello world, how are you?", g:Str1) | ||||
|   call assert_equal(['red', 'blue'], g:Colors) | ||||
|   call assert_equal(#{a: 22, b: 33}, g:Dict) | ||||
|   call assert_equal(60, g:Total1) | ||||
|   call assert_equal(10, g:Count1) | ||||
|   call assert_equal(110, g:Count2) | ||||
|   call assert_equal(25, g:Total2) | ||||
|   call assert_equal('red', g:Result1) | ||||
|   call assert_equal('xxxxxx', g:Str2) | ||||
|   call assert_equal(22, g:Result2) | ||||
|   call assert_equal(1, g:readFile) | ||||
|   call assert_equal(2, g:readExtra) | ||||
|  | ||||
|   " test for sourcing the same buffer multiple times after changing a function | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|      vim9script | ||||
|      def g:Xtestfunc(): string | ||||
|        return "one" | ||||
|      enddef | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal("one", Xtestfunc()) | ||||
|   call setline(3, '  return "two"') | ||||
|   source | ||||
|   call assert_equal("two", Xtestfunc()) | ||||
|   call setline(3, '  return "three"') | ||||
|   source | ||||
|   call assert_equal("three", Xtestfunc()) | ||||
|   delfunc Xtestfunc | ||||
|  | ||||
|   " Test for sourcing a range of lines. Make sure the script line number is | ||||
|   " correct. | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|      Line 1 | ||||
|      Line 2 | ||||
|      vim9script | ||||
|      def g:Xtestfunc(): string | ||||
|        return expand("<sflnum>") | ||||
|      enddef | ||||
|      Line 3 | ||||
|      Line 4 | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   3,6source | ||||
|   call assert_equal('5', Xtestfunc()) | ||||
|   delfunc Xtestfunc | ||||
|  | ||||
|   " test for sourcing a heredoc | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|     vim9script | ||||
|     var a = 1 | ||||
|     g:heredoc =<< trim DATA | ||||
|        red | ||||
|          green | ||||
|        blue | ||||
|     DATA | ||||
|     var b = 2 | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal(['red', '  green', 'blue'], g:heredoc) | ||||
|  | ||||
|   " test for using the :vim9cmd modifier | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|     first line | ||||
|     g:Math = { | ||||
|          pi: 3.12, | ||||
|          e: 2.71828 | ||||
|       } | ||||
|     g:Editors = [ | ||||
|       'vim', | ||||
|       # comment | ||||
|       'nano' | ||||
|       ] | ||||
|     last line | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   vim9cmd :2,10source | ||||
|   call assert_equal(#{pi: 3.12, e: 2.71828}, g:Math) | ||||
|   call assert_equal(['vim', 'nano'], g:Editors) | ||||
|  | ||||
|   " test for using try/catch | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|      vim9script | ||||
|      g:Trace = '1' | ||||
|      try | ||||
|        a1 = b1 | ||||
|      catch | ||||
|        g:Trace ..= '2' | ||||
|      finally | ||||
|        g:Trace ..= '3' | ||||
|      endtry | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal('123', g:Trace) | ||||
|  | ||||
|   " test with the finish command | ||||
|   %d _ | ||||
|   let lines =<< trim END | ||||
|      vim9script | ||||
|      g:Color = 'red' | ||||
|      finish | ||||
|      g:Color = 'blue' | ||||
|   END | ||||
|   call setline(1, lines) | ||||
|   source | ||||
|   call assert_equal('red', g:Color) | ||||
|  | ||||
|   %bw! | ||||
| endfunc | ||||
|  | ||||
| " vim: shiftwidth=2 sts=2 expandtab | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 zeertzjq
					zeertzjq