diff options
-rw-r--r-- | runtime/doc/repeat.txt | 29 | ||||
-rw-r--r-- | src/nvim/eval.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_cmds.lua | 2 | ||||
-rw-r--r-- | src/nvim/garray.c | 11 | ||||
-rw-r--r-- | src/nvim/lua/executor.c | 13 | ||||
-rw-r--r-- | src/nvim/lua/executor.h | 1 | ||||
-rw-r--r-- | src/nvim/runtime.c | 356 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 30 | ||||
-rw-r--r-- | test/functional/core/exit_spec.lua | 18 | ||||
-rw-r--r-- | test/functional/editor/mark_spec.lua | 27 | ||||
-rw-r--r-- | test/functional/ex_cmds/source_spec.lua | 20 | ||||
-rw-r--r-- | test/functional/ex_cmds/verbose_spec.lua | 2 | ||||
-rw-r--r-- | test/functional/legacy/assert_spec.lua | 8 | ||||
-rw-r--r-- | test/functional/lua/commands_spec.lua | 3 | ||||
-rw-r--r-- | test/functional/lua/vim_spec.lua | 2 | ||||
-rw-r--r-- | test/old/testdir/test_source.vim | 550 |
16 files changed, 847 insertions, 227 deletions
diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt index e65caa72ed..158ea6a4a1 100644 --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -192,14 +192,23 @@ Using Vim scripts *using-scripts* For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|. *:so* *:source* *load-vim-script* -:[range]so[urce] [file] Runs |Ex-commands| or Lua code (".lua" files) from - [file]. - If no [file], the current buffer is used and treated - as Lua code if 'filetype' is "lua" or its filename - ends with ".lua". +:so[urce] {file} Runs |Ex-commands| or Lua code (".lua" files) from + {file}. Triggers the |SourcePre| autocommand. + +:[range]so[urce] Read Ex commands or Lua code from the [range] of lines + in the current buffer. When [range] is omitted read + all lines. The buffer is treated as Lua code if its + '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. If a buffer is + sourced more than once, then the functions in the + buffer are defined again. + *:source!* -:[range]so[urce]! {file} +:so[urce]! {file} Runs |Normal-mode| commands from {file}. When used after |:global|, |:argdo|, |:windo|, |:bufdo|, in a loop or when another command follows the display @@ -389,10 +398,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 diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2b6fefbf82..ee5dd25e93 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -759,7 +759,7 @@ void fill_evalarg_from_eap(evalarg_T *evalarg, exarg_T *eap, bool skip) return; } - if (getline_equal(eap->ea_getline, eap->cookie, getsourceline)) { + if (sourcing_a_script(eap)) { evalarg->eval_getline = eap->ea_getline; evalarg->eval_cookie = eap->cookie; } diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index d118c808d3..d68d5a9afa 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -2533,7 +2533,7 @@ M.cmds = { }, { command = 'source', - flags = bit.bor(RANGE, DFLALL, WHOLEFOLD, BANG, FILE1, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), + flags = bit.bor(RANGE, DFLALL, BANG, FILE1, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type = 'ADDR_LINES', func = 'ex_source', }, diff --git a/src/nvim/garray.c b/src/nvim/garray.c index a8d15a1fa8..b3dce90b11 100644 --- a/src/nvim/garray.c +++ b/src/nvim/garray.c @@ -198,12 +198,13 @@ void ga_concat(garray_T *gap, const char *restrict s) void ga_concat_len(garray_T *const gap, const char *restrict s, const size_t len) FUNC_ATTR_NONNULL_ALL { - if (len) { - ga_grow(gap, (int)len); - char *data = gap->ga_data; - memcpy(data + gap->ga_len, s, len); - gap->ga_len += (int)len; + if (len == 0) { + return; } + ga_grow(gap, (int)len); + char *data = gap->ga_data; + memcpy(data + gap->ga_len, s, len); + gap->ga_len += (int)len; } /// Append one byte to a growarray which contains bytes. diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index bd00df395c..b1766db379 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -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 diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 3a0c03412f..9ff869e221 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -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 diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 861a44cc05..86ca4f08dc 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -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 @@ -106,6 +109,7 @@ garray_T script_items = { 0, 0, sizeof(scriptitem_T *), 20, NULL }; /// The names of packages that once were loaded are remembered. static garray_T ga_loaded = { 0, 0, sizeof(char *), 4, NULL }; +/// last used sequence number for sourcing scripts (current_sctx.sc_seq) static int last_current_SID_seq = 0; /// Initialize the execution stack. @@ -1825,8 +1829,20 @@ freeall: static void cmd_source(char *fname, exarg_T *eap) { + if (*fname != NUL && eap != NULL && eap->addr_count > 0) { + // if a filename is specified to :source, then a range is not allowed + emsg(_(e_norange)); + return; + } + if (eap != NULL && *fname == NUL) { - cmd_source_buffer(eap, false); + if (eap->forceit) { + // a file name is needed to source normal mode commands + emsg(_(e_argreq)); + } else { + // source ex commands from the current buffer + cmd_source_buffer(eap, false); + } } else if (eap != NULL && eap->forceit) { // ":source!": read Normal mode commands // Need to execute the commands directly. This is required at least @@ -1934,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. @@ -1977,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; @@ -1999,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; + if (curbuf == NULL) { + return NULL; + } + + if (ex_lua) { + // Use ":{range}lua buffer=<num>" as the script name + snprintf(IObuff, IOSIZE, ":{range}lua buffer=%d", curbuf->b_fnum); } 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; + // Use ":source buffer=<num>" as the script name + snprintf(IObuff, IOSIZE, ":source buffer=%d", curbuf->b_fnum); } - estack_push(ETYPE_SCRIPT, sname, 0); + char *fname = xstrdup(IObuff); - const sctx_T save_current_sctx = current_sctx; - if (!script_is_lua(current_sctx.sc_sid)) { - current_sctx.sc_sid = SID_STR; - } - 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; + 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; } -void cmd_source_buffer(const exarg_T *const eap, bool ex_lua) +/// 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 { - 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_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); } - ga_clear(&ga); + 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 +{ + 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; @@ -2101,22 +2080,36 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) proftime_T wait_start; bool trigger_source_post = false; - char *p = expand_env_save(fname); - if (p == NULL) { - return retval; - } - char *fname_exp = fix_fname(p); - xfree(p); - if (fname_exp == NULL) { - return retval; - } - if (os_isdir(fname_exp)) { - smsg(0, _("Cannot source a directory: \"%s\""), fname); - goto theend; + 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; + } + fname_exp = fix_fname(p); + xfree(p); + if (fname_exp == NULL) { + return retval; + } + if (os_isdir(fname_exp)) { + 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; @@ -2124,26 +2117,30 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) goto theend; } - // 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, - false, curbuf)) { - retval = aborting() ? FAIL : OK; - if (retval == OK) { - // Apply SourcePost autocommands. - apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf); + 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, + false, curbuf)) { + retval = aborting() ? FAIL : OK; + if (retval == OK) { + // Apply SourcePost autocommands. + apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf); + } + goto theend; } - goto theend; - } - // Apply SourcePre autocommands, they may get the file. - apply_autocmds(EVENT_SOURCEPRE, fname_exp, fname_exp, false, curbuf); + // Apply SourcePre autocommands, they may get the file. + apply_autocmds(EVENT_SOURCEPRE, fname_exp, fname_exp, false, curbuf); + } - cookie.fp = fopen_noinh_readbin(fname_exp); + 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 == '_') ? '.' : '_'; @@ -2151,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) { @@ -2188,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; @@ -2223,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"); @@ -2240,12 +2230,20 @@ int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid) *ret_sid = sid; } } - current_sctx.sc_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. @@ -2262,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 { @@ -2272,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); } @@ -2285,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) { @@ -2332,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 } - fclose(cookie.fp); + 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); } @@ -2346,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) { @@ -2608,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; } @@ -2619,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); @@ -2672,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); @@ -2701,19 +2717,28 @@ static char *get_one_sourceline(source_cookie_T *sp) while (true) { // make room to read at least 120 (more) characters ga_grow(&ga, 120); - buf = ga.ga_data; - + 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++; + ga_grow(&ga, 1); + buf = (char *)ga.ga_data; + buf[ga.ga_len++] = NUL; + len = ga.ga_len; + } else { + buf = ga.ga_data; retry: - errno = 0; - if (fgets(buf + ga.ga_len, ga.ga_maxlen - ga.ga_len, - sp->fp) == NULL) { - if (errno == EINTR) { - goto retry; + errno = 0; + if (fgets(buf + ga.ga_len, ga.ga_maxlen - ga.ga_len, sp->fp) == NULL) { + if (errno == EINTR) { + goto retry; + } + break; } - - break; + len = ga.ga_len + (int)strlen(buf + ga.ga_len); } - 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 // CTRL-Z by its own, or after a NL. @@ -2784,11 +2809,18 @@ retry: return NULL; } +/// 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) +{ + return getline_equal(eap->ea_getline, eap->cookie, getsourceline); +} + /// ":scriptencoding": Set encoding conversion for a sourced script. /// Without the multi-byte feature it's simply ignored. void ex_scriptencoding(exarg_T *eap) { - if (!getline_equal(eap->ea_getline, eap->cookie, getsourceline)) { + if (!sourcing_a_script(eap)) { emsg(_("E167: :scriptencoding used outside of a sourced file")); return; } @@ -2807,7 +2839,7 @@ void ex_scriptencoding(exarg_T *eap) /// ":finish": Mark a sourced file as finished. void ex_finish(exarg_T *eap) { - if (getline_equal(eap->ea_getline, eap->cookie, getsourceline)) { + if (sourcing_a_script(eap)) { do_finish(eap, false); } else { emsg(_("E168: :finish used outside of a sourced file")); diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 3aa9ba49d5..b5fcd88f71 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -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( diff --git a/test/functional/core/exit_spec.lua b/test/functional/core/exit_spec.lua index 65f6bc28a6..aafad4c8a5 100644 --- a/test/functional/core/exit_spec.lua +++ b/test/functional/core/exit_spec.lua @@ -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) diff --git a/test/functional/editor/mark_spec.lua b/test/functional/editor/mark_spec.lua index 08f90b088d..91bd2f4bbd 100644 --- a/test/functional/editor/mark_spec.lua +++ b/test/functional/editor/mark_spec.lua @@ -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) diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua index 8a15aa04d1..4548e51624 100644 --- a/test/functional/ex_cmds/source_spec.lua +++ b/test/functional/ex_cmds/source_spec.lua @@ -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 diff --git a/test/functional/ex_cmds/verbose_spec.lua b/test/functional/ex_cmds/verbose_spec.lua index 0187baf4ac..faddab1e42 100644 --- a/test/functional/ex_cmds/verbose_spec.lua +++ b/test/functional/ex_cmds/verbose_spec.lua @@ -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( diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua index 47eea5e7d5..9863d146b1 100644 --- a/test/functional/legacy/assert_spec.lua +++ b/test/functional/legacy/assert_spec.lua @@ -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) diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua index 1fd01cfd5a..fb55611198 100644 --- a/test/functional/lua/commands_spec.lua +++ b/test/functional/lua/commands_spec.lua @@ -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') ) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 8e22644339..c31ce3c678 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -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( diff --git a/test/old/testdir/test_source.vim b/test/old/testdir/test_source.vim index 01c08b183c..10b00d3784 100644 --- a/test/old/testdir/test_source.vim +++ b/test/old/testdir/test_source.vim @@ -92,6 +92,12 @@ func Test_source_error() call assert_fails('scriptencoding utf-8', 'E167:') call assert_fails('finish', 'E168:') " call assert_fails('scriptversion 2', 'E984:') + call assert_fails('source!', 'E471:') + new + call setline(1, ['', '', '', '']) + call assert_fails('1,3source Xscript.vim', 'E481:') + call assert_fails('1,3source! Xscript.vim', 'E481:') + bw! endfunc " Test for sourcing a script recursively @@ -108,4 +114,548 @@ 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) + + " test for ++clear argument to clear all the functions/variables + %d _ + let lines =<< trim END + g:ScriptVarFound = exists("color") + g:MyFuncFound = exists('*Myfunc') + if g:MyFuncFound + finish + endif + var color = 'blue' + def Myfunc() + enddef + END + call setline(1, lines) + vim9cmd source + call assert_false(g:MyFuncFound) + call assert_false(g:ScriptVarFound) + vim9cmd source + call assert_true(g:MyFuncFound) + call assert_true(g:ScriptVarFound) + vim9cmd source ++clear + call assert_false(g:MyFuncFound) + call assert_false(g:ScriptVarFound) + vim9cmd source ++clear + call assert_false(g:MyFuncFound) + call assert_false(g:ScriptVarFound) + call assert_fails('vim9cmd source ++clearx', 'E475:') + call assert_fails('vim9cmd source ++abcde', 'E484:') + + %bw! +endfunc + +func Test_source_buffer_long_line() + " This was reading past the end of the line. + new + norm300gr0 + so + bwipe! + + let lines =<< trim END + new + norm 10a0000000000ø00000000000 + norm i0000000000000000000 + silent! so + END + call writefile(lines, 'Xtest.vim') + source Xtest.vim + bwipe! + call delete('Xtest.vim') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab |