# HG changeset patch # User Bram Moolenaar # Date 1620243904 -7200 # Node ID 2818f846f09982a4992ea696be4f1431ffa5957f # Parent c770bde164eafd7183bb394341d5323b109a8922 patch 8.2.2834: Vim9: :cexpr does not work with local variables Commit: https://github.com/vim/vim/commit/5f7d4c049e934dbc8d2c3f2720797c10ee3c55c2 Author: Bram Moolenaar Date: Wed May 5 21:31:39 2021 +0200 patch 8.2.2834: Vim9: :cexpr does not work with local variables Problem: Vim9: :cexpr does not work with local variables. Solution: Compile :cexpr. diff --git a/src/proto/quickfix.pro b/src/proto/quickfix.pro --- a/src/proto/quickfix.pro +++ b/src/proto/quickfix.pro @@ -30,6 +30,9 @@ void ex_vimgrep(exarg_T *eap); int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, dict_T *what); int set_ref_in_quickfix(int copyID); void ex_cbuffer(exarg_T *eap); +char_u *cexpr_get_auname(cmdidx_T cmdidx); +int trigger_cexpr_autocmd(int cmdidx); +int cexpr_core(exarg_T *eap, typval_T *tv); void ex_cexpr(exarg_T *eap); void ex_helpgrep(exarg_T *eap); void f_getloclist(typval_T *argvars, typval_T *rettv); diff --git a/src/quickfix.c b/src/quickfix.c --- a/src/quickfix.c +++ b/src/quickfix.c @@ -7864,7 +7864,7 @@ ex_cbuffer(exarg_T *eap) /* * Return the autocmd name for the :cexpr Ex commands. */ - static char_u * + char_u * cexpr_get_auname(cmdidx_T cmdidx) { switch (cmdidx) @@ -7879,32 +7879,83 @@ cexpr_get_auname(cmdidx_T cmdidx) } } + int +trigger_cexpr_autocmd(int cmdidx) +{ + char_u *au_name = cexpr_get_auname(cmdidx); + + if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, + curbuf->b_fname, TRUE, curbuf)) + { + if (aborting()) + return FAIL; + } + return OK; +} + + int +cexpr_core(exarg_T *eap, typval_T *tv) +{ + qf_info_T *qi; + win_T *wp = NULL; + + qi = qf_cmd_get_or_alloc_stack(eap, &wp); + if (qi == NULL) + return FAIL; + + if ((tv->v_type == VAR_STRING && tv->vval.v_string != NULL) + || (tv->v_type == VAR_LIST && tv->vval.v_list != NULL)) + { + int res; + int_u save_qfid; + char_u *au_name = cexpr_get_auname(eap->cmdidx); + + incr_quickfix_busy(); + res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, tv, p_efm, + (eap->cmdidx != CMD_caddexpr + && eap->cmdidx != CMD_laddexpr), + (linenr_T)0, (linenr_T)0, + qf_cmdtitle(*eap->cmdlinep), NULL); + if (qf_stack_empty(qi)) + { + decr_quickfix_busy(); + return FAIL; + } + if (res >= 0) + qf_list_changed(qf_get_curlist(qi)); + + // Remember the current quickfix list identifier, so that we can + // check for autocommands changing the current quickfix list. + save_qfid = qf_get_curlist(qi)->qf_id; + if (au_name != NULL) + apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, + curbuf->b_fname, TRUE, curbuf); + + // Jump to the first error for a new list and if autocmds didn't + // free the list. + if (res > 0 && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr) + && qflist_valid(wp, save_qfid)) + // display the first error + qf_jump_first(qi, save_qfid, eap->forceit); + decr_quickfix_busy(); + return OK; + } + + emsg(_("E777: String or List expected")); + return FAIL; +} + /* * ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command. * ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command. + * Also: ":caddexpr", ":cgetexpr", "laddexpr" and "laddexpr". */ void ex_cexpr(exarg_T *eap) { typval_T *tv; - qf_info_T *qi; - char_u *au_name = NULL; - int res; - int_u save_qfid; - win_T *wp = NULL; - - au_name = cexpr_get_auname(eap->cmdidx); - if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, - curbuf->b_fname, TRUE, curbuf)) - { -#ifdef FEAT_EVAL - if (aborting()) - return; -#endif - } - - qi = qf_cmd_get_or_alloc_stack(eap, &wp); - if (qi == NULL) + + if (trigger_cexpr_autocmd(eap->cmdidx) == FAIL) return; // Evaluate the expression. When the result is a string or a list we can @@ -7912,42 +7963,7 @@ ex_cexpr(exarg_T *eap) tv = eval_expr(eap->arg, eap); if (tv != NULL) { - if ((tv->v_type == VAR_STRING && tv->vval.v_string != NULL) - || (tv->v_type == VAR_LIST && tv->vval.v_list != NULL)) - { - incr_quickfix_busy(); - res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, tv, p_efm, - (eap->cmdidx != CMD_caddexpr - && eap->cmdidx != CMD_laddexpr), - (linenr_T)0, (linenr_T)0, - qf_cmdtitle(*eap->cmdlinep), NULL); - if (qf_stack_empty(qi)) - { - decr_quickfix_busy(); - goto cleanup; - } - if (res >= 0) - qf_list_changed(qf_get_curlist(qi)); - - // Remember the current quickfix list identifier, so that we can - // check for autocommands changing the current quickfix list. - save_qfid = qf_get_curlist(qi)->qf_id; - if (au_name != NULL) - apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, - curbuf->b_fname, TRUE, curbuf); - - // Jump to the first error for a new list and if autocmds didn't - // free the list. - if (res > 0 && (eap->cmdidx == CMD_cexpr - || eap->cmdidx == CMD_lexpr) - && qflist_valid(wp, save_qfid)) - // display the first error - qf_jump_first(qi, save_qfid, eap->forceit); - decr_quickfix_busy(); - } - else - emsg(_("E777: String or List expected")); -cleanup: + (void)cexpr_core(eap, tv); free_tv(tv); } } diff --git a/src/testdir/test_quickfix.vim b/src/testdir/test_quickfix.vim --- a/src/testdir/test_quickfix.vim +++ b/src/testdir/test_quickfix.vim @@ -722,6 +722,22 @@ def Test_helpgrep_vim9_restore_cpo() helpclose enddef +def Test_vim9_cexpr() + var text = 'somefile:95:error' + cexpr text + var l = getqflist() + assert_equal(1, l->len()) + assert_equal(95, l[0].lnum) + assert_equal('error', l[0].text) + + text = 'somefile:77:warning' + caddexpr text + l = getqflist() + assert_equal(2, l->len()) + assert_equal(77, l[1].lnum) + assert_equal('warning', l[1].text) +enddef + func Test_errortitle() augroup QfBufWinEnter au! diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -167,6 +167,25 @@ def Test_disassemble_redir_var() res) enddef +def s:Cexpr() + var errors = "list of errors" + cexpr errors +enddef + +def Test_disassemble_cexpr() + var res = execute('disass s:Cexpr') + assert_match('\d*_Cexpr.*' .. + ' var errors = "list of errors"\_s*' .. + '\d PUSHS "list of errors"\_s*' .. + '\d STORE $0\_s*' .. + ' cexpr errors\_s*' .. + '\d CEXPR pre cexpr\_s*' .. + '\d LOAD $0\_s*' .. + '\d CEXPR core cexpr "cexpr errors"\_s*' .. + '\d RETURN 0', + res) +enddef + def s:YankRange() norm! m[jjm] :'[,']yank diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2834, +/**/ 2833, /**/ 2832, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -172,6 +172,9 @@ typedef enum { ISN_REDIRSTART, // :redir => ISN_REDIREND, // :redir END, isn_arg.number == 1 for append + ISN_CEXPR_AUCMD, // first part of :cexpr isn_arg.number is cmdidx + ISN_CEXPR_CORE, // second part of :cexpr, uses isn_arg.cexpr + ISN_FINISH // end marker in list of instructions } isntype_T; @@ -352,6 +355,18 @@ typedef struct { isn_T *subs_instr; // sequence of instructions } subs_T; +// indirect arguments to ISN_TRY +typedef struct { + int cer_cmdidx; + char_u *cer_cmdline; + int cer_forceit; +} cexprref_T; + +// arguments to ISN_CEXPR_CORE +typedef struct { + cexprref_T *cexpr_ref; +} cexpr_T; + /* * Instruction */ @@ -395,6 +410,7 @@ struct isn_S { unpack_T unpack; isn_outer_T outer; subs_T subs; + cexpr_T cexpr; } isn_arg; }; diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -8704,6 +8704,34 @@ compile_redir(char_u *line, exarg_T *eap return compile_exec(line, eap, cctx); } + static char_u * +compile_cexpr(char_u *line, exarg_T *eap, cctx_T *cctx) +{ + isn_T *isn; + char_u *p; + + isn = generate_instr(cctx, ISN_CEXPR_AUCMD); + if (isn == NULL) + return NULL; + isn->isn_arg.number = eap->cmdidx; + + p = eap->arg; + if (compile_expr0(&p, cctx) == FAIL) + return NULL; + + isn = generate_instr(cctx, ISN_CEXPR_CORE); + if (isn == NULL) + return NULL; + isn->isn_arg.cexpr.cexpr_ref = ALLOC_ONE(cexprref_T); + if (isn->isn_arg.cexpr.cexpr_ref == NULL) + return NULL; + isn->isn_arg.cexpr.cexpr_ref->cer_cmdidx = eap->cmdidx; + isn->isn_arg.cexpr.cexpr_ref->cer_forceit = eap->forceit; + isn->isn_arg.cexpr.cexpr_ref->cer_cmdline = vim_strsave(skipwhite(line)); + + return p; +} + /* * Add a function to the list of :def functions. * This sets "ufunc->uf_dfunc_idx" but the function isn't compiled yet. @@ -9262,6 +9290,16 @@ compile_def_function( line = compile_redir(line, &ea, &cctx); break; + case CMD_cexpr: + case CMD_lexpr: + case CMD_caddexpr: + case CMD_laddexpr: + case CMD_cgetexpr: + case CMD_lgetexpr: + ea.arg = p; + line = compile_cexpr(line, &ea, &cctx); + break; + // TODO: any other commands with an expression argument? case CMD_append: @@ -9602,6 +9640,10 @@ delete_instr(isn_T *isn) vim_free(isn->isn_arg.try.try_ref); break; + case ISN_CEXPR_CORE: + vim_free(isn->isn_arg.cexpr.cexpr_ref); + break; + case ISN_2BOOL: case ISN_2STRING: case ISN_2STRING_ANY: @@ -9614,6 +9656,7 @@ delete_instr(isn_T *isn) case ISN_BLOBINDEX: case ISN_BLOBSLICE: case ISN_CATCH: + case ISN_CEXPR_AUCMD: case ISN_CHECKLEN: case ISN_CHECKNR: case ISN_CMDMOD_REV: diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1442,6 +1442,29 @@ exec_instructions(ectx_T *ectx) } break; + case ISN_CEXPR_AUCMD: + if (trigger_cexpr_autocmd(iptr->isn_arg.number) == FAIL) + goto on_error; + break; + + case ISN_CEXPR_CORE: + { + exarg_T ea; + int res; + + CLEAR_FIELD(ea); + ea.cmdidx = iptr->isn_arg.cexpr.cexpr_ref->cer_cmdidx; + ea.forceit = iptr->isn_arg.cexpr.cexpr_ref->cer_forceit; + ea.cmdlinep = &iptr->isn_arg.cexpr.cexpr_ref->cer_cmdline; + --ectx->ec_stack.ga_len; + tv = STACK_TV_BOT(0); + res = cexpr_core(&ea, tv); + clear_tv(tv); + if (res == FAIL) + goto on_error; + } + break; + // execute Ex command from pieces on the stack case ISN_EXECCONCAT: { @@ -4391,6 +4414,20 @@ list_instructions(char *pfx, isn_T *inst smsg("%s%4d REDIR END%s", pfx, current, iptr->isn_arg.number ? " append" : ""); break; + case ISN_CEXPR_AUCMD: + smsg("%s%4d CEXPR pre %s", pfx, current, + cexpr_get_auname(iptr->isn_arg.number)); + break; + case ISN_CEXPR_CORE: + { + cexprref_T *cer = iptr->isn_arg.cexpr.cexpr_ref; + + smsg("%s%4d CEXPR core %s%s \"%s\"", pfx, current, + cexpr_get_auname(cer->cer_cmdidx), + cer->cer_forceit ? "!" : "", + cer->cer_cmdline); + } + break; case ISN_SUBSTITUTE: { subs_T *subs = &iptr->isn_arg.subs;