summaryrefslogtreecommitdiff
path: root/src/scriptfile.c
diff options
context:
space:
mode:
authorYegappan Lakshmanan <yegappan@yahoo.com>2022-03-19 12:56:51 +0000
committerBram Moolenaar <Bram@vim.org>2022-03-19 12:56:51 +0000
commit36a5b6867bb6c0bd69c8da7d788000ab8a0b0ab0 (patch)
treefbf0b74c593ab218dc3c66856d13edb2695128b1 /src/scriptfile.c
parent95d2e7634ccd8e0da78086002509a856999e180c (diff)
downloadvim-git-36a5b6867bb6c0bd69c8da7d788000ab8a0b0ab0.tar.gz
patch 8.2.4594: need to write script to a file to be able to source themv8.2.4594
Problem: Need to write script to a file to be able to source them. Solution: Make ":source" use lines from the current buffer. (Yegappan Lakshmanan et al., closes #9967)
Diffstat (limited to 'src/scriptfile.c')
-rw-r--r--src/scriptfile.c285
1 files changed, 279 insertions, 6 deletions
diff --git a/src/scriptfile.c b/src/scriptfile.c
index 260673754..977884342 100644
--- a/src/scriptfile.c
+++ b/src/scriptfile.c
@@ -18,6 +18,11 @@
static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL};
#endif
+// last used sequence number for sourcing scripts (current_sctx.sc_seq)
+#ifdef FEAT_EVAL
+static int last_current_SID_seq = 0;
+#endif
+
/*
* Initialize the execution stack.
*/
@@ -1074,12 +1079,270 @@ ExpandPackAddDir(
return OK;
}
+/*
+ * Cookie used to source Ex commands from a buffer.
+ */
+typedef struct
+{
+ garray_T lines_to_source;
+ int lnum;
+ linenr_T sourcing_lnum;
+} bufline_cookie_T;
+
+/*
+ * Concatenate a Vim script line if it starts with a line continuation into a
+ * growarray (excluding the continuation chars and leading whitespace).
+ * Growsize of the growarray may be changed to speed up concatenations!
+ *
+ * Returns TRUE if this line did begin with a continuation (the next line
+ * should also be considered, if it exists); FALSE otherwise.
+ */
+ static int
+concat_continued_line(
+ garray_T *ga,
+ int init_growsize,
+ char_u *nextline,
+ int options)
+{
+ int comment_char = in_vim9script() ? '#' : '"';
+ char_u *p = skipwhite(nextline);
+ int contline;
+ int do_vim9_all = in_vim9script()
+ && options == GETLINE_CONCAT_ALL;
+ int do_bar_cont = do_vim9_all
+ || options == GETLINE_CONCAT_CONTBAR;
+
+ if (*p == NUL)
+ return FALSE;
+
+ // Concatenate the next line when it starts with a backslash.
+ /* Also check for a comment in between continuation lines: "\ */
+ // Also check for a Vim9 comment, empty line, line starting with '|',
+ // but not "||".
+ if ((p[0] == comment_char && p[1] == '\\' && p[2] == ' ')
+ || (do_vim9_all && (*p == NUL
+ || vim9_comment_start(p))))
+ return TRUE;
+
+ contline = (*p == '\\' || (do_bar_cont && p[0] == '|' && p[1] != '|'));
+ if (!contline)
+ return FALSE;
+
+ // Adjust the growsize to the current length to speed up concatenating many
+ // lines.
+ if (ga->ga_len > init_growsize)
+ ga->ga_growsize = ga->ga_len > 8000 ? 8000 : ga->ga_len;
+ if (*p == '\\')
+ ga_concat(ga, (char_u *)p + 1);
+ else if (*p == '|')
+ {
+ ga_concat(ga, (char_u *)" ");
+ ga_concat(ga, p);
+ }
+
+ return TRUE;
+}
+
+/*
+ * Get one full line from a sourced string (in-memory, no file).
+ * Called by do_cmdline() when it's called from source_using_linegetter().
+ *
+ * Returns a pointer to allocated line, or NULL for end-of-file.
+ */
+ static char_u *
+source_getbufline(
+ int c UNUSED,
+ void *cookie,
+ int indent UNUSED,
+ getline_opt_T opts)
+{
+ bufline_cookie_T *p = cookie;
+ char_u *line;
+ garray_T ga;
+
+ SOURCING_LNUM = p->sourcing_lnum + 1;
+
+ if (p->lnum >= p->lines_to_source.ga_len)
+ return NULL;
+ line = ((char_u **)p->lines_to_source.ga_data)[p->lnum];
+
+ ga_init2(&ga, sizeof(char_u), 400);
+ ga_concat(&ga, (char_u *)line);
+ p->lnum++;
+
+ if ((opts != GETLINE_NONE) && vim_strchr(p_cpo, CPO_CONCAT) == NULL)
+ {
+ while (p->lnum < p->lines_to_source.ga_len)
+ {
+ line = ((char_u **)p->lines_to_source.ga_data)[p->lnum];
+ if (!concat_continued_line(&ga, 400, line, opts))
+ break;
+ p->sourcing_lnum++;
+ p->lnum++;
+ }
+ }
+ ga_append(&ga, NUL);
+ p->sourcing_lnum++;
+
+ return ga.ga_data;
+}
+
+/*
+ * Source Ex commands from the lines in 'cookie'.
+ */
+ static int
+do_sourcebuffer(
+ void *cookie,
+ char_u *scriptname)
+{
+ char_u *save_sourcing_name = SOURCING_NAME;
+ linenr_T save_sourcing_lnum = SOURCING_LNUM;
+ char_u sourcing_name_buf[256];
+ sctx_T save_current_sctx;
+#ifdef FEAT_EVAL
+ int sid;
+ funccal_entry_T funccalp_entry;
+ int save_estack_compiling = estack_compiling;
+ scriptitem_T *si = NULL;
+#endif
+ int save_sticky_cmdmod_flags = sticky_cmdmod_flags;
+ int retval = FAIL;
+ ESTACK_CHECK_DECLARATION
+
+ if (save_sourcing_name == NULL)
+ SOURCING_NAME = (char_u *)scriptname;
+ else
+ {
+ vim_snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf),
+ "%s called at %s:%ld", scriptname, save_sourcing_name,
+ save_sourcing_lnum);
+ SOURCING_NAME = sourcing_name_buf;
+ }
+ SOURCING_LNUM = 0;
+
+ // Keep the sourcing name/lnum, for recursive calls.
+ estack_push(ETYPE_SCRIPT, scriptname, 0);
+ ESTACK_CHECK_SETUP
+
+ // "legacy" does not apply to commands in the script
+ sticky_cmdmod_flags = 0;
+
+ save_current_sctx = current_sctx;
+ current_sctx.sc_version = 1; // default script version
+#ifdef FEAT_EVAL
+ estack_compiling = FALSE;
+ // Always use a new sequence number.
+ current_sctx.sc_seq = ++last_current_SID_seq;
+ current_sctx.sc_lnum = save_sourcing_lnum;
+ save_funccal(&funccalp_entry);
+
+ sid = find_script_by_name(scriptname);
+ if (sid < 0)
+ {
+ int error = OK;
+
+ // First time sourcing this buffer, create a new script item.
+
+ sid = get_new_scriptitem(&error);
+ if (error == FAIL)
+ goto theend;
+ current_sctx.sc_sid = sid;
+ si = SCRIPT_ITEM(current_sctx.sc_sid);
+ si->sn_name = vim_strsave(scriptname);
+ si->sn_state = SN_STATE_NEW;
+ }
+ else
+ {
+ // the buffer was sourced previously, reuse the script ID.
+ current_sctx.sc_sid = sid;
+ si = SCRIPT_ITEM(current_sctx.sc_sid);
+ si->sn_state = SN_STATE_RELOAD;
+ }
+#endif
+
+ retval = do_cmdline(NULL, source_getbufline, cookie,
+ DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT);
+
+ if (got_int)
+ emsg(_(e_interrupted));
+
+#ifdef FEAT_EVAL
+theend:
+#endif
+ ESTACK_CHECK_NOW
+ estack_pop();
+ current_sctx = save_current_sctx;
+ SOURCING_LNUM = save_sourcing_lnum;
+ SOURCING_NAME = save_sourcing_name;
+ sticky_cmdmod_flags = save_sticky_cmdmod_flags;
+#ifdef FEAT_EVAL
+ restore_funccal();
+ estack_compiling = save_estack_compiling;
+#endif
+
+ return retval;
+}
+
+/*
+ * :source Ex commands from the current buffer
+ */
+ static void
+cmd_source_buffer(exarg_T *eap)
+{
+ char_u *line = NULL;
+ linenr_T curr_lnum;
+ bufline_cookie_T cp;
+ char_u sname[32];
+
+ if (curbuf == NULL)
+ return;
+
+ // Use ":source buffer=<num>" as the script name
+ vim_snprintf((char *)sname, sizeof(sname), ":source buffer=%d",
+ curbuf->b_fnum);
+
+ ga_init2(&cp.lines_to_source, sizeof(char_u *), 100);
+
+ // Copy the lines from the buffer into a grow array
+ for (curr_lnum = eap->line1; curr_lnum <= eap->line2; curr_lnum++)
+ {
+ line = vim_strsave(ml_get(curr_lnum));
+ if (line == NULL)
+ goto errret;
+ if (ga_add_string(&cp.lines_to_source, line) == FAIL)
+ goto errret;
+ line = NULL;
+ }
+ cp.sourcing_lnum = 0;
+ cp.lnum = 0;
+
+ // Execute the Ex commands
+ do_sourcebuffer((void *)&cp, (char_u *)sname);
+
+errret:
+ vim_free(line);
+ ga_clear_strings(&cp.lines_to_source);
+}
+
static void
cmd_source(char_u *fname, exarg_T *eap)
{
- if (*fname == NUL)
- emsg(_(e_argument_required));
+ if (*fname != NUL && eap != NULL && eap->addr_count > 0)
+ {
+ // if a filename is specified to :source, then a range is not allowed
+ emsg(_(e_no_range_allowed));
+ return;
+ }
+ if (eap != NULL && *fname == NUL)
+ {
+ if (eap->forceit)
+ // a file name is needed to source normal mode commands
+ emsg(_(e_argument_required));
+ else
+ // source ex commands from the current buffer
+ cmd_source_buffer(eap);
+ }
else if (eap != NULL && eap->forceit)
// ":source!": read Normal mode commands
// Need to execute the commands directly. This is required at least
@@ -1240,7 +1503,6 @@ do_source(
int retval = FAIL;
sctx_T save_current_sctx;
#ifdef FEAT_EVAL
- static int last_current_SID_seq = 0;
funccal_entry_T funccalp_entry;
int save_debug_break_level = debug_break_level;
int sid;
@@ -2016,6 +2278,17 @@ getsourceline(
}
/*
+ * Returns TRUE if sourcing a script either from a file or a buffer.
+ * Otherwise returns FALSE.
+ */
+ int
+sourcing_a_script(exarg_T *eap)
+{
+ return (getline_equal(eap->getline, eap->cookie, getsourceline)
+ || getline_equal(eap->getline, eap->cookie, source_getbufline));
+}
+
+/*
* ":scriptencoding": Set encoding conversion for a sourced script.
*/
void
@@ -2024,7 +2297,7 @@ ex_scriptencoding(exarg_T *eap)
source_cookie_T *sp;
char_u *name;
- if (!getline_equal(eap->getline, eap->cookie, getsourceline))
+ if (!sourcing_a_script(eap))
{
emsg(_(e_scriptencoding_used_outside_of_sourced_file));
return;
@@ -2055,7 +2328,7 @@ ex_scriptversion(exarg_T *eap UNUSED)
{
int nr;
- if (!getline_equal(eap->getline, eap->cookie, getsourceline))
+ if (!sourcing_a_script(eap))
{
emsg(_(e_scriptversion_used_outside_of_sourced_file));
return;
@@ -2087,7 +2360,7 @@ ex_scriptversion(exarg_T *eap UNUSED)
void
ex_finish(exarg_T *eap)
{
- if (getline_equal(eap->getline, eap->cookie, getsourceline))
+ if (sourcing_a_script(eap))
do_finish(eap, FALSE);
else
emsg(_(e_finish_used_outside_of_sourced_file));