diff options
Diffstat (limited to 'src/nvim/eval.c')
-rw-r--r-- | src/nvim/eval.c | 656 |
1 files changed, 556 insertions, 100 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e39f36957c..a556f0bbbe 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -422,6 +422,7 @@ static struct vimvar { VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO), VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), + VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), }; #undef VV @@ -433,11 +434,14 @@ static struct vimvar { #define vv_str vv_di.di_tv.vval.v_string #define vv_list vv_di.di_tv.vval.v_list #define vv_dict vv_di.di_tv.vval.v_dict +#define vv_partial vv_di.di_tv.vval.v_partial #define vv_tv vv_di.di_tv /// Variable used for v: static ScopeDictDictItem vimvars_var; +static partial_T *vvlua_partial; + /// v: hashtab #define vimvarht vimvardict.dv_hashtab @@ -639,6 +643,13 @@ void eval_init(void) set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); + vimvars[VV_LUA].vv_type = VAR_PARTIAL; + vvlua_partial = xcalloc(1, sizeof(partial_T)); + vimvars[VV_LUA].vv_partial = vvlua_partial; + // this value shouldn't be printed, but if it is, do not crash + vvlua_partial->pt_name = xmallocz(0); + vvlua_partial->pt_refcount++; + set_reg_var(0); // default for v:register is not 0 but '"' } @@ -1313,12 +1324,25 @@ int call_vim_function( { int doesrange; int ret; + int len = (int)STRLEN(func); + partial_T *pt = NULL; + + if (len >= 6 && !memcmp(func, "v:lua.", 6)) { + func += 6; + len = check_luafunc_name((const char *)func, false); + if (len == 0) { + ret = FAIL; + goto fail; + } + pt = vvlua_partial; + } rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this. - ret = call_func(func, (int)STRLEN(func), rettv, argc, argv, NULL, + ret = call_func(func, len, rettv, argc, argv, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &doesrange, true, NULL, NULL); + &doesrange, true, pt, NULL); +fail: if (ret == FAIL) { tv_clear(rettv); } @@ -1504,6 +1528,116 @@ void ex_const(exarg_T *eap) ex_let_const(eap, true); } +// Get a list of lines from a HERE document. The here document is a list of +// lines surrounded by a marker. +// cmd << {marker} +// {line1} +// {line2} +// .... +// {marker} +// +// The {marker} is a string. If the optional 'trim' word is supplied before the +// marker, then the leading indentation before the lines (matching the +// indentation in the 'cmd' line) is stripped. +// Returns a List with {lines} or NULL. +static list_T * +heredoc_get(exarg_T *eap, char_u *cmd) +{ + char_u *marker; + char_u *p; + int marker_indent_len = 0; + int text_indent_len = 0; + char_u *text_indent = NULL; + + if (eap->getline == NULL) { + EMSG(_("E991: cannot use =<< here")); + return NULL; + } + + // Check for the optional 'trim' word before the marker + cmd = skipwhite(cmd); + if (STRNCMP(cmd, "trim", 4) == 0 + && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) { + cmd = skipwhite(cmd + 4); + + // Trim the indentation from all the lines in the here document. + // The amount of indentation trimmed is the same as the indentation of + // the first line after the :let command line. To find the end marker + // the indent of the :let command line is trimmed. + p = *eap->cmdlinep; + while (ascii_iswhite(*p)) { + p++; + marker_indent_len++; + } + text_indent_len = -1; + } + + // The marker is the next word. + if (*cmd != NUL && *cmd != '"') { + marker = skipwhite(cmd); + p = skiptowhite(marker); + if (*skipwhite(p) != NUL && *skipwhite(p) != '"') { + EMSG(_(e_trailing)); + return NULL; + } + *p = NUL; + if (islower(*marker)) { + EMSG(_("E221: Marker cannot start with lower case letter")); + return NULL; + } + } else { + EMSG(_("E172: Missing marker")); + return NULL; + } + + list_T *l = tv_list_alloc(0); + for (;;) { + int mi = 0; + int ti = 0; + + char_u *theline = eap->getline(NUL, eap->cookie, 0, false); + if (theline == NULL) { + EMSG2(_("E990: Missing end marker '%s'"), marker); + break; + } + + // with "trim": skip the indent matching the :let line to find the + // marker + if (marker_indent_len > 0 + && STRNCMP(theline, *eap->cmdlinep, marker_indent_len) == 0) { + mi = marker_indent_len; + } + if (STRCMP(marker, theline + mi) == 0) { + xfree(theline); + break; + } + if (text_indent_len == -1 && *theline != NUL) { + // set the text indent from the first line. + p = theline; + text_indent_len = 0; + while (ascii_iswhite(*p)) { + p++; + text_indent_len++; + } + text_indent = vim_strnsave(theline, text_indent_len); + } + // with "trim": skip the indent matching the first line + if (text_indent != NULL) { + for (ti = 0; ti < text_indent_len; ti++) { + if (theline[ti] != text_indent[ti]) { + break; + } + } + } + + tv_list_append_string(l, (char *)(theline + ti), -1); + xfree(theline); + } + xfree(text_indent); + + return l; +} + // ":let" list all variable values // ":let var1 var2" list variable values // ":let var = expr" assignment command. @@ -1560,6 +1694,19 @@ static void ex_let_const(exarg_T *eap, const bool is_const) list_vim_vars(&first); } eap->nextcmd = check_nextcmd(arg); + } else if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') { + // HERE document + list_T *l = heredoc_get(eap, expr + 3); + if (l != NULL) { + tv_list_set_ret(&rettv, l); + if (!eap->skip) { + op[0] = '='; + op[1] = NUL; + (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, + is_const, op); + } + tv_clear(&rettv); + } } else { op[0] = '='; op[1] = NUL; @@ -2339,6 +2486,13 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } + if (lp->ll_di != NULL && tv_is_luafunc(&lp->ll_di->di_tv) + && len == -1 && rettv == NULL) { + tv_clear(&var1); + EMSG2(e_illvar, "v:['lua']"); + return NULL; + } + if (lp->ll_di == NULL) { // Can't add "v:" or "a:" variable. if (lp->ll_dict == &vimvardict @@ -2718,7 +2872,7 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx) int c; char_u *p; - if (cmdidx == CMD_let) { + if (cmdidx == CMD_let || cmdidx == CMD_const) { xp->xp_context = EXPAND_USER_VARS; if (vim_strpbrk(arg, (char_u *)"\"'+-*/%.=!?~|&$([<>,#") == NULL) { /* ":let var1 var2 ...": find last space. */ @@ -4576,7 +4730,7 @@ eval_index( if (evaluate) { n1 = 0; - if (!empty1 && rettv->v_type != VAR_DICT) { + if (!empty1 && rettv->v_type != VAR_DICT && !tv_is_luafunc(rettv)) { n1 = tv_get_number(&var1); tv_clear(&var1); } @@ -4700,7 +4854,7 @@ eval_index( if (len == -1) { tv_clear(&var1); } - if (item == NULL) { + if (item == NULL || tv_is_luafunc(&item->di_tv)) { return FAIL; } @@ -5746,13 +5900,13 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate) goto failret; } item = tv_dict_item_alloc((const char *)key); - tv_clear(&tvkey); item->di_tv = tv; item->di_tv.v_lock = 0; if (tv_dict_add(d, item) == FAIL) { tv_dict_item_free(item); } } + tv_clear(&tvkey); if (**arg == '}') break; @@ -6211,7 +6365,7 @@ static char_u *deref_func_name(const char *name, int *lenp, */ static int get_func_tv( - char_u *name, // name of the function + const char_u *name, // name of the function int len, // length of "name" typval_T *rettv, char_u **arg, // argument, pointing to the '(' @@ -6467,7 +6621,15 @@ call_func( rettv->vval.v_number = 0; error = ERROR_UNKNOWN; - if (!builtin_function((const char *)rfname, -1)) { + if (partial == vvlua_partial) { + if (len > 0) { + error = ERROR_NONE; + executor_call_lua((const char *)funcname, len, + argvars, argcount, rettv); + } else { + error = ERROR_UNKNOWN; + } + } else if (!builtin_function((const char *)rfname, -1)) { // User defined function. if (partial != NULL && partial->pt_func != NULL) { fp = partial->pt_func; @@ -6584,14 +6746,14 @@ call_func( /// /// @param ermsg must be passed without translation (use N_() instead of _()). /// @param name function name -static void emsg_funcname(char *ermsg, char_u *name) +static void emsg_funcname(char *ermsg, const char_u *name) { char_u *p; if (*name == K_SPECIAL) { p = concat_str((char_u *)"<SNR>", name + 3); } else { - p = name; + p = (char_u *)name; } EMSG2(_(ermsg), p); @@ -6947,7 +7109,8 @@ static void assert_error(garray_T *gap) (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); } -static void assert_equal_common(typval_T *argvars, assert_type_T atype) +static int assert_equal_common(typval_T *argvars, assert_type_T atype) + FUNC_ATTR_NONNULL_ALL { garray_T ga; @@ -6958,13 +7121,70 @@ static void assert_equal_common(typval_T *argvars, assert_type_T atype) &argvars[0], &argvars[1], atype); assert_error(&ga); ga_clear(&ga); + return 1; + } + return 0; +} + +static int assert_equalfile(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL +{ + char buf1[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1); + const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2); + garray_T ga; + + if (fname1 == NULL || fname2 == NULL) { + return 0; + } + + IObuff[0] = NUL; + FILE *const fd1 = os_fopen(fname1, READBIN); + if (fd1 == NULL) { + snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1); + } else { + FILE *const fd2 = os_fopen(fname2, READBIN); + if (fd2 == NULL) { + fclose(fd1); + snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2); + } else { + for (int64_t count = 0; ; count++) { + const int c1 = fgetc(fd1); + const int c2 = fgetc(fd2); + if (c1 == EOF) { + if (c2 != EOF) { + STRCPY(IObuff, "first file is shorter"); + } + break; + } else if (c2 == EOF) { + STRCPY(IObuff, "second file is shorter"); + break; + } else if (c1 != c2) { + snprintf((char *)IObuff, IOSIZE, + "difference at byte %" PRId64, count); + break; + } + } + fclose(fd1); + fclose(fd2); + } + } + if (IObuff[0] != NUL) { + prepare_assert_error(&ga); + ga_concat(&ga, IObuff); + assert_error(&ga); + ga_clear(&ga); + return 1; } + return 0; } static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const cmd = tv_get_string_chk(&argvars[0]); garray_T ga; + int ret = 0; called_vim_beep = false; suppress_errthrow = true; @@ -6976,22 +7196,30 @@ static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_concat(&ga, (const char_u *)cmd); assert_error(&ga); ga_clear(&ga); + ret = 1; } suppress_errthrow = false; emsg_on_display = false; + rettv->vval.v_number = ret; } // "assert_equal(expected, actual[, msg])" function static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_equal_common(argvars, ASSERT_EQUAL); + rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); +} + +// "assert_equalfile(fname-one, fname-two)" function +static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_equalfile(argvars); } // "assert_notequal(expected, actual[, msg])" function static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_equal_common(argvars, ASSERT_NOTEQUAL); + rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); } /// "assert_report(msg) @@ -7003,34 +7231,21 @@ static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_concat(&ga, (const char_u *)tv_get_string(&argvars[0])); assert_error(&ga); ga_clear(&ga); + rettv->vval.v_number = 1; } /// "assert_exception(string[, msg])" function static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - garray_T ga; - - const char *const error = tv_get_string_chk(&argvars[0]); - if (vimvars[VV_EXCEPTION].vv_str == NULL) { - prepare_assert_error(&ga); - ga_concat(&ga, (char_u *)"v:exception is not set"); - assert_error(&ga); - ga_clear(&ga); - } else if (error != NULL - && strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], - &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER); - assert_error(&ga); - ga_clear(&ga); - } + rettv->vval.v_number = assert_exception(argvars); } -/// "assert_fails(cmd [, error])" function +/// "assert_fails(cmd [, error [, msg]])" function static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const cmd = tv_get_string_chk(&argvars[0]); garray_T ga; + int ret = 0; int save_trylevel = trylevel; // trylevel must be zero for a ":throw" command to be considered failed @@ -7043,9 +7258,17 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (!called_emsg) { prepare_assert_error(&ga); ga_concat(&ga, (const char_u *)"command did not fail: "); - ga_concat(&ga, (const char_u *)cmd); + if (argvars[1].v_type != VAR_UNKNOWN + && argvars[2].v_type != VAR_UNKNOWN) { + char *const tofree = encode_tv2echo(&argvars[2], NULL); + ga_concat(&ga, (char_u *)tofree); + xfree(tofree); + } else { + ga_concat(&ga, (const char_u *)cmd); + } assert_error(&ga); ga_clear(&ga); + ret = 1; } else if (argvars[1].v_type != VAR_UNKNOWN) { char buf[NUMBUFLEN]; const char *const error = tv_get_string_buf_chk(&argvars[1], buf); @@ -7057,6 +7280,7 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) &vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER); assert_error(&ga); ga_clear(&ga); + ret = 1; } } @@ -7066,9 +7290,11 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg_silent = false; emsg_on_display = false; set_vim_var_string(VV_ERRMSG, NULL, 0); + rettv->vval.v_number = ret; } -void assert_inrange(typval_T *argvars) +static int assert_inrange(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL { bool error = false; const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); @@ -7076,7 +7302,7 @@ void assert_inrange(typval_T *argvars) const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); if (error) { - return; + return 0; } if (actual < lower || actual > upper) { garray_T ga; @@ -7090,11 +7316,14 @@ void assert_inrange(typval_T *argvars) ASSERT_INRANGE); assert_error(&ga); ga_clear(&ga); + return 1; } + return 0; } // Common for assert_true() and assert_false(). -static void assert_bool(typval_T *argvars, bool is_true) +static int assert_bool(typval_T *argvars, bool is_true) + FUNC_ATTR_NONNULL_ALL { bool error = false; garray_T ga; @@ -7113,16 +7342,43 @@ static void assert_bool(typval_T *argvars, bool is_true) NULL, &argvars[0], ASSERT_OTHER); assert_error(&ga); ga_clear(&ga); + return 1; + } + return 0; +} + +static int assert_exception(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL +{ + garray_T ga; + + const char *const error = tv_get_string_chk(&argvars[0]); + if (vimvars[VV_EXCEPTION].vv_str == NULL) { + prepare_assert_error(&ga); + ga_concat(&ga, (char_u *)"v:exception is not set"); + assert_error(&ga); + ga_clear(&ga); + return 1; + } else if (error != NULL + && strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], + &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER); + assert_error(&ga); + ga_clear(&ga); + return 1; } + return 0; } // "assert_false(actual[, msg])" function static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_bool(argvars, false); + rettv->vval.v_number = assert_bool(argvars, false); } -static void assert_match_common(typval_T *argvars, assert_type_T atype) +static int assert_match_common(typval_T *argvars, assert_type_T atype) + FUNC_ATTR_NONNULL_ALL { char buf1[NUMBUFLEN]; char buf2[NUMBUFLEN]; @@ -7138,31 +7394,33 @@ static void assert_match_common(typval_T *argvars, assert_type_T atype) fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype); assert_error(&ga); ga_clear(&ga); + return 1; } + return 0; } /// "assert_inrange(lower, upper[, msg])" function static void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_inrange(argvars); + rettv->vval.v_number = assert_inrange(argvars); } /// "assert_match(pattern, actual[, msg])" function static void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_match_common(argvars, ASSERT_MATCH); + rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); } /// "assert_notmatch(pattern, actual[, msg])" function static void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_match_common(argvars, ASSERT_NOTMATCH); + rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); } // "assert_true(actual[, msg])" function static void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_bool(argvars, true); + rettv->vval.v_number = assert_bool(argvars, true); } /* @@ -8458,18 +8716,25 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_dict_alloc_ret(rettv); - for (int i = 0; ; i++) { - // TODO(justinmk): use os_copyfullenv from #7202 ? - char *envname = os_getenvname_at_index((size_t)i); - if (envname == NULL) { - break; - } - const char *value = os_getenv(envname); + size_t env_size = os_get_fullenv_size(); + char **env = xmalloc(sizeof(*env) * (env_size + 1)); + env[env_size] = NULL; + + os_copy_fullenv(env, env_size); + + for (size_t i = 0; i < env_size; i++) { + const char * str = env[i]; + const char * const end = strchr(str + (str[0] == '=' ? 1 : 0), + '='); + assert(end != NULL); + ptrdiff_t len = end - str; + assert(len > 0); + const char * value = str + len + 1; tv_dict_add_str(rettv->vval.v_dict, - (char *)envname, STRLEN((char *)envname), - value == NULL ? "" : value); - xfree(envname); + str, len, + value); } + os_free_fullenv(env); } /* @@ -8492,7 +8757,7 @@ static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (p == NULL) { rettv->v_type = VAR_SPECIAL; - rettv->vval.v_number = kSpecialVarNull; + rettv->vval.v_special = kSpecialVarNull; return; } rettv->vval.v_string = p; @@ -8546,7 +8811,7 @@ typedef struct { const listitem_T *li; } GetListLineCookie; -static char_u *get_list_line(int c, void *cookie, int indent) +static char_u *get_list_line(int c, void *cookie, int indent, bool do_concat) { GetListLineCookie *const p = (GetListLineCookie *)cookie; @@ -11245,6 +11510,9 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) { static const char *const has_list[] = { +#if defined(BSD) && !defined(__APPLE__) + "bsd", +#endif #ifdef UNIX "unix", #endif @@ -12316,6 +12584,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool executable = true; char **argv = tv_to_argv(&argvars[0], NULL, &executable); + char **env = NULL; if (!argv) { rettv->vval.v_number = executable ? 0 : -1; return; // Did error message in tv_to_argv. @@ -12333,6 +12602,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool detach = false; bool rpc = false; bool pty = false; + bool clear_env = false; CallbackReader on_stdout = CALLBACK_READER_INIT, on_stderr = CALLBACK_READER_INIT; Callback on_exit = CALLBACK_NONE; @@ -12343,6 +12613,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) detach = tv_dict_get_number(job_opts, "detach") != 0; rpc = tv_dict_get_number(job_opts, "rpc") != 0; pty = tv_dict_get_number(job_opts, "pty") != 0; + clear_env = tv_dict_get_number(job_opts, "clear_env") != 0; if (pty && rpc) { EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set"); shell_free_argv(argv); @@ -12359,6 +12630,45 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } } + dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env")); + if (job_env) { + if (job_env->di_tv.v_type != VAR_DICT) { + EMSG2(_(e_invarg2), "env"); + shell_free_argv(argv); + return; + } + + size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict); + size_t i = 0; + size_t env_size = 0; + + if (clear_env) { + // + 1 for last null entry + env = xmalloc((custom_env_size + 1) * sizeof(*env)); + env_size = 0; + } else { + env_size = os_get_fullenv_size(); + + env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env)); + + os_copy_fullenv(env, env_size); + i = env_size; + } + assert(env); // env must be allocated at this point + + TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, { + const char *str = tv_get_string(&var->di_tv); + assert(str); + size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1; + env[i] = xmalloc(len); + snprintf(env[i], len, "%s=%s", (char *)var->di_key, str); + i++; + }); + + // must be null terminated + env[env_size + custom_env_size] = NULL; + } + if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { shell_free_argv(argv); @@ -12376,8 +12686,8 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, - rpc, detach, cwd, width, height, term_name, - &rettv->vval.v_number); + rpc, detach, cwd, width, height, + term_name, env, &rettv->vval.v_number); if (chan) { channel_create_event(chan, NULL); } @@ -14398,6 +14708,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) long time_limit = 0; int options = SEARCH_KEEP; int subpatnum; + searchit_arg_T sia; const char *const pat = tv_get_string(&argvars[0]); dir = get_search_arg(&argvars[1], flagsp); // May set p_ws. @@ -14445,8 +14756,11 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) } pos = save_cursor = curwin->w_cursor; + memset(&sia, 0, sizeof(sia)); + sia.sa_stop_lnum = (linenr_T)lnum_stop; + sia.sa_tm = &tm; subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, - options, RE_SEARCH, (linenr_T)lnum_stop, &tm, NULL); + options, RE_SEARCH, &sia); if (subpatnum != FAIL) { if (flags & SP_SUBPAT) retval = subpatnum; @@ -14668,7 +14982,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT, CALLBACK_READER_INIT, CALLBACK_NONE, - false, true, false, NULL, 0, 0, NULL, + false, true, false, NULL, 0, 0, NULL, NULL, &rettv->vval.v_number); if (chan) { channel_create_event(chan, NULL); @@ -15018,8 +15332,13 @@ do_searchpair( clearpos(&foundpos); pat = pat3; for (;; ) { + searchit_arg_T sia; + memset(&sia, 0, sizeof(sia)); + sia.sa_stop_lnum = lnum_stop; + sia.sa_tm = &tm; + n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, - options, RE_SEARCH, lnum_stop, &tm, NULL); + options, RE_SEARCH, &sia); if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) { // didn't find it or found the first match again: FAIL break; @@ -15406,7 +15725,7 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else set_last_csearch(PTR2CHAR(csearch), - csearch, MB_PTR2LEN(csearch)); + csearch, utfc_ptr2len(csearch)); } di = tv_dict_find(d, S_LEN("forward")); @@ -15441,7 +15760,7 @@ static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *name = tv_get_string_buf(&argvars[0], namebuf); if (argvars[1].v_type == VAR_SPECIAL - && argvars[1].vval.v_number == kSpecialVarNull) { + && argvars[1].vval.v_special == kSpecialVarNull) { os_unsetenv(name); } else { os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1); @@ -18050,7 +18369,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, true, false, false, cwd, term_width, curwin->w_height_inner, - xstrdup("xterm-256color"), + xstrdup("xterm-256color"), NULL, &rettv->vval.v_number); if (rettv->vval.v_number <= 0) { return; @@ -18350,6 +18669,7 @@ static void timer_due_cb(TimeWatcher *tw, void *data) timer_T *timer = (timer_T *)data; int save_did_emsg = did_emsg; int save_called_emsg = called_emsg; + const bool save_ex_pressedreturn = get_pressedreturn(); if (timer->stopped || timer->paused) { return; @@ -18378,6 +18698,7 @@ static void timer_due_cb(TimeWatcher *tw, void *data) } did_emsg = save_did_emsg; called_emsg = save_called_emsg; + set_pressedreturn(save_ex_pressedreturn); if (timer->emsg_count >= 3) { timer_stop(timer); @@ -18747,8 +19068,9 @@ static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (wild_menu_showing) + if (wild_menu_showing || ((State & CMDLINE) && pum_visible())) { rettv->vval.v_number = 1; + } } /// "win_findbuf()" function @@ -19937,6 +20259,26 @@ static void check_vars(const char *name, size_t len) } } +/// check if special v:lua value for calling lua functions +static bool tv_is_luafunc(typval_T *tv) +{ + return tv->v_type == VAR_PARTIAL && tv->vval.v_partial == vvlua_partial; +} + +/// check the function name after "v:lua." +static int check_luafunc_name(const char *str, bool paren) +{ + const char *p = str; + while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') { + p++; + } + if (*p != (paren ? '(' : NUL)) { + return 0; + } else { + return (int)(p-str); + } +} + /// Handle expr[expr], expr[expr:expr] subscript and .name lookup. /// Also handle function call with Funcref variable: func(expr) /// Can all be combined: dict.func(expr)[idx]['func'](expr) @@ -19950,9 +20292,30 @@ handle_subscript( { int ret = OK; dict_T *selfdict = NULL; - char_u *s; + const char_u *s; int len; typval_T functv; + int slen = 0; + bool lua = false; + + if (tv_is_luafunc(rettv)) { + if (**arg != '.') { + tv_clear(rettv); + ret = FAIL; + } else { + (*arg)++; + + lua = true; + s = (char_u *)(*arg); + slen = check_luafunc_name(*arg, true); + if (slen == 0) { + tv_clear(rettv); + ret = FAIL; + } + (*arg) += slen; + } + } + while (ret == OK && (**arg == '[' @@ -19969,14 +20332,16 @@ handle_subscript( // Invoke the function. Recursive! if (functv.v_type == VAR_PARTIAL) { pt = functv.vval.v_partial; - s = partial_name(pt); + if (!lua) { + s = partial_name(pt); + } } else { s = functv.vval.v_string; } } else { s = (char_u *)""; } - ret = get_func_tv(s, (int)STRLEN(s), rettv, (char_u **)arg, + ret = get_func_tv(s, lua ? slen : (int)STRLEN(s), rettv, (char_u **)arg, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &len, evaluate, pt, selfdict); @@ -20211,7 +20576,7 @@ static hashtab_T *get_funccal_local_ht(void) return &get_funccal()->l_vars.dv_hashtab; } -/// Find the dict and hashtable used for a variable +/// Finds the dict (g:, l:, s:, …) and hashtable used for a variable. /// /// @param[in] name Variable name, possibly with scope prefix. /// @param[in] name_len Variable name length. @@ -21077,7 +21442,6 @@ void ex_function(exarg_T *eap) bool overwrite = false; int indent; int nesting; - char_u *skip_until = NULL; dictitem_T *v; funcdict_T fudi; static int func_nr = 0; /* number for nameless function */ @@ -21085,8 +21449,13 @@ void ex_function(exarg_T *eap) hashtab_T *ht; int todo; hashitem_T *hi; - int sourcing_lnum_off; + linenr_T sourcing_lnum_off; + linenr_T sourcing_lnum_top; + bool is_heredoc = false; + char_u *skip_until = NULL; + char_u *heredoc_trimmed = NULL; bool show_block = false; + bool do_concat = true; /* * ":function" without argument: list functions. @@ -21333,15 +21702,17 @@ void ex_function(exarg_T *eap) cmdline_row = msg_row; } + // Save the starting line number. + sourcing_lnum_top = sourcing_lnum; + indent = 2; nesting = 0; for (;; ) { if (KeyTyped) { - msg_scroll = TRUE; - saved_wait_return = FALSE; + msg_scroll = true; + saved_wait_return = false; } - need_wait_return = FALSE; - sourcing_lnum_off = sourcing_lnum; + need_wait_return = false; if (line_arg != NULL) { /* Use eap->arg, split up in parts by line breaks. */ @@ -21356,9 +21727,9 @@ void ex_function(exarg_T *eap) } else { xfree(line_to_free); if (eap->getline == NULL) { - theline = getcmdline(':', 0L, indent); + theline = getcmdline(':', 0L, indent, do_concat); } else { - theline = eap->getline(':', eap->cookie, indent); + theline = eap->getline(':', eap->cookie, indent, do_concat); } line_to_free = theline; } @@ -21374,17 +21745,37 @@ void ex_function(exarg_T *eap) ui_ext_cmdline_block_append((size_t)indent, (const char *)theline); } - /* Detect line continuation: sourcing_lnum increased more than one. */ - if (sourcing_lnum > sourcing_lnum_off + 1) - sourcing_lnum_off = sourcing_lnum - sourcing_lnum_off - 1; - else + // Detect line continuation: sourcing_lnum increased more than one. + sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie); + if (sourcing_lnum < sourcing_lnum_off) { + sourcing_lnum_off -= sourcing_lnum; + } else { sourcing_lnum_off = 0; + } if (skip_until != NULL) { - /* between ":append" and "." and between ":python <<EOF" and "EOF" - * don't check for ":endfunc". */ - if (STRCMP(theline, skip_until) == 0) { - XFREE_CLEAR(skip_until); + // Don't check for ":endfunc" between + // * ":append" and "." + // * ":python <<EOF" and "EOF" + // * ":let {var-name} =<< [trim] {marker}" and "{marker}" + if (heredoc_trimmed == NULL + || (is_heredoc && skipwhite(theline) == theline) + || STRNCMP(theline, heredoc_trimmed, + STRLEN(heredoc_trimmed)) == 0) { + if (heredoc_trimmed == NULL) { + p = theline; + } else if (is_heredoc) { + p = skipwhite(theline) == theline + ? theline : theline + STRLEN(heredoc_trimmed); + } else { + p = theline + STRLEN(heredoc_trimmed); + } + if (STRCMP(p, skip_until) == 0) { + XFREE_CLEAR(skip_until); + XFREE_CLEAR(heredoc_trimmed); + do_concat = true; + is_heredoc = false; + } } } else { /* skip ':' and blanks*/ @@ -21483,6 +21874,34 @@ void ex_function(exarg_T *eap) else skip_until = vim_strsave(p); } + + // Check for ":let v =<< [trim] EOF" + // and ":let [a, b] =<< [trim] EOF" + arg = skipwhite(skiptowhite(p)); + if (*arg == '[') { + arg = vim_strchr(arg, ']'); + } + if (arg != NULL) { + arg = skipwhite(skiptowhite(arg)); + if (arg[0] == '=' + && arg[1] == '<' + && arg[2] =='<' + && (p[0] == 'l' + && p[1] == 'e' + && (!ASCII_ISALNUM(p[2]) + || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) { + p = skipwhite(arg + 3); + if (STRNCMP(p, "trim", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + heredoc_trimmed = + vim_strnsave(theline, (int)(skipwhite(theline) - theline)); + } + skip_until = vim_strnsave(p, (int)(skiptowhite(p) - p)); + do_concat = false; + is_heredoc = true; + } + } } /* Add the line to the function. */ @@ -21652,7 +22071,8 @@ void ex_function(exarg_T *eap) fp->uf_flags = flags; fp->uf_calls = 0; fp->uf_script_ctx = current_sctx; - fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len - 1; + fp->uf_script_ctx.sc_lnum += sourcing_lnum_top; + goto ret_free; erret: @@ -21669,7 +22089,7 @@ ret_free: if (show_block) { ui_ext_cmdline_block_leave(); } -} +} // NOLINT(readability/fn_size) /// Get a function name, translating "<SID>" and "<SNR>". /// Also handles a Funcref in a List or Dictionary. @@ -21753,8 +22173,19 @@ trans_function_name( *pp = (char_u *)end; } else if (lv.ll_tv->v_type == VAR_PARTIAL && lv.ll_tv->vval.v_partial != NULL) { - name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); - *pp = (char_u *)end; + if (lv.ll_tv->vval.v_partial == vvlua_partial && *end == '.') { + len = check_luafunc_name((const char *)end+1, true); + if (len == 0) { + EMSG2(e_invexpr2, "v:lua"); + goto theend; + } + name = xmallocz(len); + memcpy(name, end+1, len); + *pp = (char_u *)end+1+len; + } else { + name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); + *pp = (char_u *)end; + } if (partial != NULL) { *partial = lv.ll_tv->vval.v_partial; } @@ -22172,7 +22603,7 @@ void func_dump_profile(FILE *fd) .channel_id = 0, }; char_u *p = get_scriptname(last_set, &should_free); - fprintf(fd, " Defined: %s line %" PRIdLINENR "\n", + fprintf(fd, " Defined: %s:%" PRIdLINENR "\n", p, fp->uf_script_ctx.sc_lnum); if (should_free) { xfree(p); @@ -23305,7 +23736,7 @@ char_u *get_return_cmd(void *rettv) * Called by do_cmdline() to get the next line. * Returns allocated string, or NULL for end of function. */ -char_u *get_func_line(int c, void *cookie, int indent) +char_u *get_func_line(int c, void *cookie, int indent, bool do_concat) { funccall_T *fcp = (funccall_T *)cookie; ufunc_T *fp = fcp->func; @@ -23831,22 +24262,47 @@ repeat: * - for second :e: before the current fname * - otherwise: The last '.' */ - if (src[*usedlen + 1] == 'e' && *fnamep > tail) + const bool is_second_e = *fnamep > tail; + if (src[*usedlen + 1] == 'e' && is_second_e) { s = *fnamep - 2; - else + } else { s = *fnamep + *fnamelen - 1; - for (; s > tail; --s) - if (s[0] == '.') + } + + for (; s > tail; s--) { + if (s[0] == '.') { break; - if (src[*usedlen + 1] == 'e') { /* :e */ - if (s > tail) { - *fnamelen += (size_t)(*fnamep - (s + 1)); - *fnamep = s + 1; - } else if (*fnamep <= tail) + } + } + if (src[*usedlen + 1] == 'e') { + if (s > tail || (0 && is_second_e && s == tail)) { + // we stopped at a '.' (so anchor to &'.' + 1) + char_u *newstart = s + 1; + size_t distance_stepped_back = *fnamep - newstart; + *fnamelen += distance_stepped_back; + *fnamep = newstart; + } else if (*fnamep <= tail) { *fnamelen = 0; - } else { /* :r */ - if (s > tail) /* remove one extension */ + } + } else { + // :r - Remove one extension + // + // Ensure that `s` doesn't go before `*fnamep`, + // since then we're taking too many roots: + // + // "path/to/this.file.ext" :e:e:r:r + // ^ ^-------- *fnamep + // +------------- tail + // + // Also ensure `s` doesn't go before `tail`, + // since then we're taking too many roots again: + // + // "path/to/this.file.ext" :r:r:r + // ^ ^------------- tail + // +--------------------- *fnamep + if (s > MAX(tail, *fnamep)) { *fnamelen = (size_t)(s - *fnamep); + } } *usedlen += 2; } @@ -23952,8 +24408,8 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, /* Skip empty match except for first match. */ if (regmatch.startp[0] == regmatch.endp[0]) { if (zero_width == regmatch.startp[0]) { - /* avoid getting stuck on a match with an empty string */ - int i = MB_PTR2LEN(tail); + // avoid getting stuck on a match with an empty string + int i = utfc_ptr2len(tail); memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i); ga.ga_len += i; tail += i; |