To: vim_dev@googlegroups.com Subject: Patch 8.2.4594 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.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) Files: runtime/doc/repeat.txt, runtime/doc/todo.txt, src/alloc.c, src/digraph.c, src/eval.c, src/ex_cmds.h, src/scriptfile.c, src/proto/scriptfile.pro, src/vim9script.c, src/testdir/test_source.vim *** ../vim-8.2.4593/runtime/doc/repeat.txt 2022-01-18 16:25:58.618309943 +0000 --- runtime/doc/repeat.txt 2022-03-19 12:36:45.098120789 +0000 *************** *** 188,193 **** --- 197,208 ---- :so[urce] {file} Read Ex commands from {file}. These are commands that start with a ":". Triggers the |SourcePre| autocommand. + + :[range]so[urce] Read Ex commands from the [range] of lines in the + current buffer. When sourcing commands from the + current buffer, the same script-ID || is used + even if the buffer is sourced multiple times. + *:source!* :so[urce]! {file} Read Vim commands from {file}. These are commands that are executed from Normal mode, like you type *** ../vim-8.2.4593/runtime/doc/todo.txt 2021-02-13 17:24:19.322119004 +0000 --- runtime/doc/todo.txt 2022-03-19 12:36:45.098120789 +0000 *************** *** 4336,4347 **** restore option values. Especially useful for new options. Problem: how to avoid a performance penalty (esp. for string options)? - range for ":exec", pass it on to the executed command. (Webb) - 8 ":{range}source": source the lines from the current file. - You can already yank lines and use :@" to execute them. - Most of do_source() would not be used, need a new function. - It's easy when not doing breakpoints or profiling. - Requires copying the lines into a list and then creating a function to - execute lines from the list. Similar to getnextac(). 7 ":include" command: just like ":source" but doesn't start a new scriptID? Will be tricky for the list of script names. 8 Have a look at VSEL. Would it be useful to include? (Bigham) --- 4280,4285 ---- *** ../vim-8.2.4593/src/alloc.c 2022-01-29 15:19:19.542172491 +0000 --- src/alloc.c 2022-03-19 12:36:45.098120789 +0000 *************** *** 845,851 **** void ga_concat_len(garray_T *gap, char_u *s, size_t len) { ! if (s == NULL || *s == NUL) return; if (ga_grow(gap, (int)len) == OK) { --- 845,851 ---- void ga_concat_len(garray_T *gap, char_u *s, size_t len) { ! if (s == NULL || *s == NUL || len == 0) return; if (ga_grow(gap, (int)len) == OK) { *** ../vim-8.2.4593/src/digraph.c 2022-01-31 14:59:33.510943820 +0000 --- src/digraph.c 2022-03-19 12:36:45.098120789 +0000 *************** *** 2507,2513 **** int i; char_u *save_cpo = p_cpo; ! if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { emsg(_(e_using_loadkeymap_not_in_sourced_file)); return; --- 2507,2513 ---- int i; char_u *save_cpo = p_cpo; ! if (!sourcing_a_script(eap)) { emsg(_(e_using_loadkeymap_not_in_sourced_file)); return; *** ../vim-8.2.4593/src/eval.c 2022-03-18 19:44:44.939089425 +0000 --- src/eval.c 2022-03-19 12:36:45.098120789 +0000 *************** *** 140,146 **** if (eap != NULL) { evalarg->eval_cstack = eap->cstack; ! if (getline_equal(eap->getline, eap->cookie, getsourceline)) { evalarg->eval_getline = eap->getline; evalarg->eval_cookie = eap->cookie; --- 140,146 ---- if (eap != NULL) { evalarg->eval_cstack = eap->cstack; ! if (sourcing_a_script(eap)) { evalarg->eval_getline = eap->getline; evalarg->eval_cookie = eap->cookie; *** ../vim-8.2.4593/src/ex_cmds.h 2022-03-06 14:51:19.058997629 +0000 --- src/ex_cmds.h 2022-03-19 12:36:45.098120789 +0000 *************** *** 1428,1435 **** EX_RANGE|EX_ZEROR|EX_EXTRA|EX_TRLBAR|EX_NOTRLCOM|EX_CTRLV|EX_CMDWIN|EX_LOCK_OK, ADDR_OTHER), EXCMD(CMD_source, "source", ex_source, ! EX_BANG|EX_FILE1|EX_TRLBAR|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK, ! ADDR_NONE), EXCMD(CMD_sort, "sort", ex_sort, EX_RANGE|EX_DFLALL|EX_WHOLEFOLD|EX_BANG|EX_EXTRA|EX_NOTRLCOM|EX_MODIFY, ADDR_LINES), --- 1428,1435 ---- EX_RANGE|EX_ZEROR|EX_EXTRA|EX_TRLBAR|EX_NOTRLCOM|EX_CTRLV|EX_CMDWIN|EX_LOCK_OK, ADDR_OTHER), EXCMD(CMD_source, "source", ex_source, ! EX_RANGE|EX_DFLALL|EX_BANG|EX_FILE1|EX_TRLBAR|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK, ! ADDR_LINES), EXCMD(CMD_sort, "sort", ex_sort, EX_RANGE|EX_DFLALL|EX_WHOLEFOLD|EX_BANG|EX_EXTRA|EX_NOTRLCOM|EX_MODIFY, ADDR_LINES), *** ../vim-8.2.4593/src/scriptfile.c 2022-02-12 13:30:12.760432016 +0000 --- src/scriptfile.c 2022-03-19 12:51:07.033932219 +0000 *************** *** 18,23 **** --- 18,28 ---- 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,1085 **** return OK; } static void cmd_source(char_u *fname, exarg_T *eap) { ! if (*fname == NUL) ! emsg(_(e_argument_required)); else if (eap != NULL && eap->forceit) // ":source!": read Normal mode commands // Need to execute the commands directly. This is required at least --- 1079,1348 ---- 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=" 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 && 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,1246 **** 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; --- 1503,1508 ---- *************** *** 2016,2021 **** --- 2278,2294 ---- } /* + * 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,2030 **** source_cookie_T *sp; char_u *name; ! if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { emsg(_(e_scriptencoding_used_outside_of_sourced_file)); return; --- 2297,2303 ---- source_cookie_T *sp; char_u *name; ! if (!sourcing_a_script(eap)) { emsg(_(e_scriptencoding_used_outside_of_sourced_file)); return; *************** *** 2055,2061 **** { int nr; ! if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { emsg(_(e_scriptversion_used_outside_of_sourced_file)); return; --- 2328,2334 ---- { int nr; ! if (!sourcing_a_script(eap)) { emsg(_(e_scriptversion_used_outside_of_sourced_file)); return; *************** *** 2087,2093 **** void ex_finish(exarg_T *eap) { ! if (getline_equal(eap->getline, eap->cookie, getsourceline)) do_finish(eap, FALSE); else emsg(_(e_finish_used_outside_of_sourced_file)); --- 2360,2366 ---- void ex_finish(exarg_T *eap) { ! if (sourcing_a_script(eap)) do_finish(eap, FALSE); else emsg(_(e_finish_used_outside_of_sourced_file)); *** ../vim-8.2.4593/src/proto/scriptfile.pro 2022-01-10 18:06:58.682381797 +0000 --- src/proto/scriptfile.pro 2022-03-19 12:36:45.102120794 +0000 *************** *** 42,45 **** --- 42,46 ---- char_u *may_prefix_autoload(char_u *name); char_u *autoload_name(char_u *name); int script_autoload(char_u *name, int reload); + int sourcing_a_script(exarg_T *eap); /* vim: set ft=c : */ *** ../vim-8.2.4593/src/vim9script.c 2022-03-08 13:18:10.809020782 +0000 --- src/vim9script.c 2022-03-19 12:36:45.102120794 +0000 *************** *** 71,77 **** int found_noclear = FALSE; char_u *p; ! if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { emsg(_(e_vim9script_can_only_be_used_in_script)); return; --- 71,77 ---- int found_noclear = FALSE; char_u *p; ! if (!sourcing_a_script(eap)) { emsg(_(e_vim9script_can_only_be_used_in_script)); return; *************** *** 633,639 **** char_u *cmd_end; evalarg_T evalarg; ! if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { emsg(_(e_import_can_only_be_used_in_script)); return; --- 633,639 ---- char_u *cmd_end; evalarg_T evalarg; ! if (!sourcing_a_script(eap)) { emsg(_(e_import_can_only_be_used_in_script)); return; *** ../vim-8.2.4593/src/testdir/test_source.vim 2021-12-27 17:21:38.020449109 +0000 --- src/testdir/test_source.vim 2022-03-19 12:36:45.102120794 +0000 *************** *** 94,99 **** --- 94,105 ---- 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 *************** *** 110,113 **** --- 116,348 ---- 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) + + " 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("") + 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("") + 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("") + let i = 1 + + \ 2 + + "\ comment + \ 3 + let g:Slnum2 = expand("") + 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("") + let g:Slnum1 = expand("") + let l =<< trim END + let g:Slnum2 = expand("") + let g:ScriptID2 = expand("") + END + new + call setline(1, l) + source + bw! + let g:ScriptID3 = expand("") + let g:Slnum3 = expand("") + 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 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()) + + %bw! + endfunc + " vim: shiftwidth=2 sts=2 expandtab *** ../vim-8.2.4593/src/version.c 2022-03-19 11:42:13.449717210 +0000 --- src/version.c 2022-03-19 12:47:58.202122527 +0000 *************** *** 752,753 **** --- 752,755 ---- { /* Add new patch number below this line */ + /**/ + 4594, /**/ -- GALAHAD: No, please. Please! I can defeat them! There's only a hundred. GIRLS: He will beat us easily. We haven't a chance. "Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///