Merge pull request #28486 from zeertzjq/vim-8.2.4603

vim-patch:8.2.{4594,4603,4607,4647,4974}
This commit is contained in:
zeertzjq
2025-02-28 18:40:24 +08:00
committed by GitHub
16 changed files with 848 additions and 228 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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',
},

View File

@@ -198,13 +198,14 @@ 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) {
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.
///

View File

@@ -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

View File

@@ -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

View File

@@ -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) {
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;
} 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;
@@ -2101,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;
@@ -2114,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;
@@ -2124,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,
@@ -2138,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 == '_') ? '.' : '_';
@@ -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;
}
}
// 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
}
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);
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 (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
// 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"));

View File

@@ -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(

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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(

View File

@@ -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)

View File

@@ -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')
)

View File

@@ -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(

View File

@@ -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