diff options
Diffstat (limited to 'src')
63 files changed, 1461 insertions, 586 deletions
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index a458762cc6..f194b6b474 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1075,8 +1075,8 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) break; case kObjectTypeBoolean: - tv->v_type = VAR_SPECIAL; - tv->vval.v_special = obj.data.boolean? kSpecialVarTrue: kSpecialVarFalse; + tv->v_type = VAR_BOOL; + tv->vval.v_bool = obj.data.boolean? kBoolVarTrue: kBoolVarFalse; break; case kObjectTypeBuffer: diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 087ab37296..40cef87cf0 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1037,7 +1037,7 @@ void nvim_set_current_win(Window window, Error *err) /// /// @param listed Sets 'buflisted' /// @param scratch Creates a "throwaway" |scratch-buffer| for temporary work -/// (always 'nomodified') +/// (always 'nomodified'). Also sets 'nomodeline' on the buffer. /// @param[out] err Error details, if any /// @return Buffer handle, or 0 on error /// @@ -1067,9 +1067,10 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) if (scratch) { aco_save_T aco; aucmd_prepbuf(&aco, buf); - set_option_value("bh", 0L, "hide", OPT_LOCAL); - set_option_value("bt", 0L, "nofile", OPT_LOCAL); - set_option_value("swf", 0L, NULL, OPT_LOCAL); + set_option_value("bufhidden", 0L, "hide", OPT_LOCAL); + set_option_value("buftype", 0L, "nofile", OPT_LOCAL); + set_option_value("swapfile", 0L, NULL, OPT_LOCAL); + set_option_value("modeline", 0L, NULL, OPT_LOCAL); // 'nomodeline' aucmd_restbuf(&aco); } return buf->b_fnum; diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 7c8f93163a..3ce39feda5 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -186,14 +186,17 @@ int open_buffer( } } - /* - * if there is no memfile at all, exit - * This is OK, since there are no changes to lose. - */ + // If there is no memfile at all, exit. + // This is OK, since there are no changes to lose. if (curbuf == NULL) { EMSG(_("E82: Cannot allocate any buffer, exiting...")); + + // Don't try to do any saving, with "curbuf" NULL almost nothing + // will work. + v_dying = 2; getout(2); } + EMSG(_("E83: Cannot allocate buffer, using other one...")); enter_buffer(curbuf); if (old_tw != curbuf->b_p_tw) { diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 5eb29a7290..37cbfb968b 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -301,7 +301,8 @@ static void close_cb(Stream *stream, void *data) /// @returns [allocated] channel Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader on_stderr, Callback on_exit, - bool pty, bool rpc, bool detach, const char *cwd, + bool pty, bool rpc, bool overlapped, bool detach, + const char *cwd, uint16_t pty_width, uint16_t pty_height, char *term_name, char **env, varnumber_T *status_out) { @@ -342,6 +343,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, proc->detach = detach; proc->cwd = cwd; proc->env = env; + proc->overlapped = overlapped; char *cmd = xstrdup(proc->argv[0]); bool has_out, has_err; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 4acee7b453..66cd0e09c6 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -213,8 +213,8 @@ static struct vimvar { VV(VV_ERRORS, "errors", VAR_LIST, 0), VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), VV(VV_EVENT, "event", VAR_DICT, VV_RO), - VV(VV_FALSE, "false", VAR_SPECIAL, VV_RO), - VV(VV_TRUE, "true", VAR_SPECIAL, VV_RO), + VV(VV_FALSE, "false", VAR_BOOL, VV_RO), + VV(VV_TRUE, "true", VAR_BOOL, VV_RO), VV(VV_NULL, "null", VAR_SPECIAL, VV_RO), VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), @@ -230,12 +230,14 @@ static struct vimvar { VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), + VV(VV_ARGV, "argv", VAR_LIST, VV_RO), }; #undef VV // shorthand #define vv_type vv_di.di_tv.v_type #define vv_nr vv_di.di_tv.vval.v_number +#define vv_bool vv_di.di_tv.vval.v_bool #define vv_special vv_di.di_tv.vval.v_special #define vv_float vv_di.di_tv.vval.v_float #define vv_str vv_di.di_tv.vval.v_string @@ -387,8 +389,8 @@ void eval_init(void) set_vim_var_nr(VV_TYPE_FLOAT, VAR_TYPE_FLOAT); set_vim_var_nr(VV_TYPE_BOOL, VAR_TYPE_BOOL); - set_vim_var_special(VV_FALSE, kSpecialVarFalse); - set_vim_var_special(VV_TRUE, kSpecialVarTrue); + set_vim_var_bool(VV_FALSE, kBoolVarFalse); + set_vim_var_bool(VV_TRUE, kBoolVarTrue); set_vim_var_special(VV_NULL, kSpecialVarNull); set_vim_var_special(VV_EXITING, kSpecialVarNull); @@ -443,7 +445,7 @@ void eval_clear(void) // unreferenced lists and dicts (void)garbage_collect(false); - // functions + // functions not garbage collected free_all_functions(); } @@ -872,17 +874,19 @@ char_u *eval_to_string(char_u *arg, char_u **nextcmd, int convert) char_u *eval_to_string_safe(char_u *arg, char_u **nextcmd, int use_sandbox) { char_u *retval; - void *save_funccalp; + funccal_entry_T funccal_entry; - save_funccalp = save_funccal(); - if (use_sandbox) - ++sandbox; - ++textlock; - retval = eval_to_string(arg, nextcmd, FALSE); - if (use_sandbox) - --sandbox; - --textlock; - restore_funccal(save_funccalp); + save_funccal(&funccal_entry); + if (use_sandbox) { + sandbox++; + } + textlock++; + retval = eval_to_string(arg, nextcmd, false); + if (use_sandbox) { + sandbox--; + } + textlock--; + restore_funccal(); return retval; } @@ -1834,12 +1838,15 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, int opt_type; long numval; char *stringval = NULL; + const char *s = NULL; const char c1 = *p; *p = NUL; varnumber_T n = tv_get_number(tv); - const char *s = tv_get_string_chk(tv); // != NULL if number or string. + if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) { + s = tv_get_string_chk(tv); // != NULL if number or string. + } if (s != NULL && op != NULL && *op != '=') { opt_type = get_option_value(arg, &numval, (char_u **)&stringval, opt_flags); @@ -1865,7 +1872,8 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, } } } - if (s != NULL) { + if (s != NULL || tv->v_type == VAR_BOOL + || tv->v_type == VAR_SPECIAL) { set_option_value((const char *)arg, n, s, opt_flags); arg_end = (char_u *)p; } @@ -4196,6 +4204,7 @@ eval_index( } return FAIL; } + case VAR_BOOL: case VAR_SPECIAL: { if (verbose) { EMSG(_("E909: Cannot index a special variable")); @@ -4417,6 +4426,7 @@ eval_index( *rettv = var1; break; } + case VAR_BOOL: case VAR_SPECIAL: case VAR_FUNC: case VAR_FLOAT: @@ -5025,7 +5035,7 @@ bool garbage_collect(bool testing) // 3. Check if any funccal can be freed now. // This may call us back recursively. - did_free = did_free || free_unref_funccal(copyID, testing); + did_free = free_unref_funccal(copyID, testing) || did_free; } else if (p_verbose > 0) { verb_msg(_( "Not enough memory to set references, garbage collection aborted!")); @@ -5270,6 +5280,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, abort = set_ref_in_func(tv->vval.v_string, NULL, copyID); break; case VAR_UNKNOWN: + case VAR_BOOL: case VAR_SPECIAL: case VAR_FLOAT: case VAR_NUMBER: @@ -5740,11 +5751,11 @@ int assert_bool(typval_T *argvars, bool is_true) if ((argvars[0].v_type != VAR_NUMBER || (tv_get_number_chk(&argvars[0], &error) == 0) == is_true || error) - && (argvars[0].v_type != VAR_SPECIAL - || (argvars[0].vval.v_special - != (SpecialVarValue) (is_true - ? kSpecialVarTrue - : kSpecialVarFalse)))) { + && (argvars[0].v_type != VAR_BOOL + || (argvars[0].vval.v_bool + != (BoolVarValue)(is_true + ? kBoolVarTrue + : kBoolVarFalse)))) { prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[1], (char_u *)(is_true ? "True" : "False"), @@ -6117,7 +6128,7 @@ void common_function(typval_T *argvars, typval_T *rettv, if (tv_list_len(list) == 0) { arg_idx = 0; } else if (tv_list_len(list) > MAX_FUNC_ARGS) { - emsg_funcname((char *)e_toomanyarg, name); + emsg_funcname((char *)e_toomanyarg, s); xfree(name); goto theend; } @@ -6751,6 +6762,7 @@ void mapblock_fill_dict(dict_T *const dict, } tv_dict_add_allocated_str(dict, S_LEN("lhs"), lhs); tv_dict_add_nr(dict, S_LEN("noremap"), noremap_value); + tv_dict_add_nr(dict, S_LEN("script"), mp->m_noremap == REMAP_SCRIPT ? 1 : 0); tv_dict_add_nr(dict, S_LEN("expr"), mp->m_expr ? 1 : 0); tv_dict_add_nr(dict, S_LEN("silent"), mp->m_silent ? 1 : 0); tv_dict_add_nr(dict, S_LEN("sid"), (varnumber_T)mp->m_script_ctx.sc_sid); @@ -8045,6 +8057,17 @@ void set_vim_var_nr(const VimVarIndex idx, const varnumber_T val) vimvars[idx].vv_nr = val; } +/// Set boolean v: {true, false} to the given value +/// +/// @param[in] idx Index of variable to set. +/// @param[in] val Value to set to. +void set_vim_var_bool(const VimVarIndex idx, const BoolVarValue val) +{ + tv_clear(&vimvars[idx].vv_tv); + vimvars[idx].vv_type = VAR_BOOL; + vimvars[idx].vv_bool = val; +} + /// Set special v: variable to the given value /// /// @param[in] idx Index of variable to set. @@ -8108,6 +8131,23 @@ void set_vim_var_dict(const VimVarIndex idx, dict_T *const val) } } +/// Set the v:argv list. +void set_argv_var(char **argv, int argc) +{ + list_T *l = tv_list_alloc(argc); + int i; + + if (l == NULL) { + getout(1); + } + tv_list_set_lock(l, VAR_FIXED); + for (i = 0; i < argc; i++) { + tv_list_append_string(l, (const char *const)argv[i], -1); + TV_LIST_ITEM_TV(tv_list_last(l))->v_lock = VAR_FIXED; + } + set_vim_var_list(VV_ARGV, l); +} + /* * Set v:register if needed. */ @@ -9122,6 +9162,7 @@ int var_item_copy(const vimconv_T *const conv, case VAR_FLOAT: case VAR_FUNC: case VAR_PARTIAL: + case VAR_BOOL: case VAR_SPECIAL: tv_copy(from, to); break; @@ -9748,9 +9789,11 @@ const void *var_shada_iter(const void *const iter, const char **const name, void var_set_global(const char *const name, typval_T vartv) { - funccall_T *const saved_funccal = (funccall_T *)save_funccal(); + funccal_entry_T funccall_entry; + + save_funccal(&funccall_entry); set_var(name, strlen(name), &vartv, false); - restore_funccal(saved_funccal); + restore_funccal(); } int store_session_globals(FILE *fd) @@ -10306,8 +10349,10 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) .autocmd_fname = autocmd_fname, .autocmd_match = autocmd_match, .autocmd_bufnr = autocmd_bufnr, - .funccalp = save_funccal() + .funccalp = (void *)get_current_funccal() }; + funccal_entry_T funccal_entry; + save_funccal(&funccal_entry); provider_call_nesting++; typval_T argvars[3] = { @@ -10334,7 +10379,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) tv_list_unref(arguments); // Restore caller scope information - restore_funccal(provider_caller_scope.funccalp); + restore_funccal(); provider_caller_scope = saved_provider_caller_scope; provider_call_nesting--; assert(provider_call_nesting >= 0); diff --git a/src/nvim/eval.h b/src/nvim/eval.h index ebc0eb0b1a..0b4cbb3b4d 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -159,6 +159,7 @@ typedef enum { VV_ECHOSPACE, VV_EXITING, VV_LUA, + VV_ARGV, } VimVarIndex; /// All recognized msgpack types diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 42999ddd62..daba304f00 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -795,9 +795,9 @@ json_decode_string_cycle_start: } p += 3; POP(((typval_T) { - .v_type = VAR_SPECIAL, + .v_type = VAR_BOOL, .v_lock = VAR_UNLOCKED, - .vval = { .v_special = kSpecialVarTrue }, + .vval = { .v_bool = kBoolVarTrue }, }), false); break; } @@ -808,9 +808,9 @@ json_decode_string_cycle_start: } p += 4; POP(((typval_T) { - .v_type = VAR_SPECIAL, + .v_type = VAR_BOOL, .v_lock = VAR_UNLOCKED, - .vval = { .v_special = kSpecialVarFalse }, + .vval = { .v_bool = kBoolVarFalse }, }), false); break; } @@ -954,10 +954,10 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) } case MSGPACK_OBJECT_BOOLEAN: { *rettv = (typval_T) { - .v_type = VAR_SPECIAL, + .v_type = VAR_BOOL, .v_lock = VAR_UNLOCKED, .vval = { - .v_special = mobj.via.boolean ? kSpecialVarTrue : kSpecialVarFalse + .v_bool = mobj.via.boolean ? kBoolVarTrue : kBoolVarFalse }, }; break; diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 138f638eb2..137f099df6 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -34,10 +34,13 @@ #define utf_ptr2len(b) ((size_t)utf_ptr2len((char_u *)b)) #define utf_char2len(b) ((size_t)utf_char2len(b)) +const char *const encode_bool_var_names[] = { + [kBoolVarTrue] = "true", + [kBoolVarFalse] = "false", +}; + const char *const encode_special_var_names[] = { [kSpecialVarNull] = "null", - [kSpecialVarTrue] = "true", - [kSpecialVarFalse] = "false", }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h index ccea245ab3..596bb49ae0 100644 --- a/src/nvim/eval/encode.h +++ b/src/nvim/eval/encode.h @@ -55,6 +55,7 @@ static inline ListReaderState encode_init_lrstate(const list_T *const list) } /// Array mapping values from SpecialVarValue enum to names +extern const char *const encode_bool_var_names[]; extern const char *const encode_special_var_names[]; /// First codepoint in high surrogates block diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index 8cd21f8d62..da05ecda43 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -28,11 +28,13 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NO_SANITIZE_UNDEFINED { // Can't do anything with a Funcref, a Dict or special value on the right. - if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT) { + if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT + && tv2->v_type != VAR_BOOL && tv2->v_type != VAR_SPECIAL) { switch (tv1->v_type) { case VAR_DICT: case VAR_FUNC: case VAR_PARTIAL: + case VAR_BOOL: case VAR_SPECIAL: { break; } diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 79a52d9779..1071e75c06 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -175,8 +175,8 @@ static int non_zero_arg(typval_T *argvars) { return ((argvars[0].v_type == VAR_NUMBER && argvars[0].vval.v_number != 0) - || (argvars[0].v_type == VAR_SPECIAL - && argvars[0].vval.v_special == kSpecialVarTrue) + || (argvars[0].v_type == VAR_BOOL + && argvars[0].vval.v_bool == kBoolVarTrue) || (argvars[0].v_type == VAR_STRING && argvars[0].vval.v_string != NULL && *argvars[0].vval.v_string != NUL)); @@ -1758,21 +1758,23 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) n = (tv_dict_len(argvars[0].vval.v_dict) == 0); break; } - case VAR_SPECIAL: { - // Using switch to get warning if SpecialVarValue receives more values. - switch (argvars[0].vval.v_special) { - case kSpecialVarTrue: { + case VAR_BOOL: { + switch (argvars[0].vval.v_bool) { + case kBoolVarTrue: { n = false; break; } - case kSpecialVarFalse: - case kSpecialVarNull: { + case kBoolVarFalse: { n = true; break; } } break; } + case VAR_SPECIAL: { + n = argvars[0].vval.v_special == kSpecialVarNull; + break; + } case VAR_UNKNOWN: { internal_error("f_empty(UNKNOWN)"); break; @@ -4860,6 +4862,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool rpc = false; bool pty = false; bool clear_env = false; + bool overlapped = false; CallbackReader on_stdout = CALLBACK_READER_INIT, on_stderr = CALLBACK_READER_INIT; Callback on_exit = CALLBACK_NONE; @@ -4871,12 +4874,23 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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; + overlapped = tv_dict_get_number(job_opts, "overlapped") != 0; + if (pty && rpc) { EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set"); shell_free_argv(argv); return; } +#ifdef WIN32 + if (pty && overlapped) { + EMSG2(_(e_invarg2), + "job cannot have both 'pty' and 'overlapped' options set"); + shell_free_argv(argv); + return; + } +#endif + char *new_cwd = tv_dict_get_string(job_opts, "cwd", false); if (new_cwd && strlen(new_cwd) > 0) { cwd = new_cwd; @@ -4943,7 +4957,7 @@ 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, + rpc, overlapped, detach, cwd, width, height, term_name, env, &rettv->vval.v_number); if (chan) { channel_create_event(chan, NULL); @@ -5189,6 +5203,7 @@ static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; } case VAR_UNKNOWN: + case VAR_BOOL: case VAR_SPECIAL: case VAR_FLOAT: case VAR_PARTIAL: @@ -7243,7 +7258,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match; linenr_T save_sourcing_lnum; int save_autocmd_bufnr; - void *save_funccalp; + funccal_entry_T funccal_entry; if (l_provider_call_nesting) { // If this is called from a provider function, restore the scope @@ -7254,7 +7269,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) save_autocmd_fname = autocmd_fname; save_autocmd_match = autocmd_match; save_autocmd_bufnr = autocmd_bufnr; - save_funccalp = save_funccal(); + save_funccal(&funccal_entry); current_sctx = provider_caller_scope.script_ctx; sourcing_name = provider_caller_scope.sourcing_name; @@ -7262,7 +7277,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) autocmd_fname = provider_caller_scope.autocmd_fname; autocmd_match = provider_caller_scope.autocmd_match; autocmd_bufnr = provider_caller_scope.autocmd_bufnr; - restore_funccal(provider_caller_scope.funccalp); + set_current_funccal((funccall_T *)(provider_caller_scope.funccalp)); } @@ -7280,7 +7295,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) autocmd_fname = save_autocmd_fname; autocmd_match = save_autocmd_match; autocmd_bufnr = save_autocmd_bufnr; - restore_funccal(save_funccalp); + restore_funccal(); } if (ERROR_SET(&err)) { @@ -7369,8 +7384,8 @@ 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, NULL, - &rettv->vval.v_number); + false, true, false, false, NULL, 0, 0, + NULL, NULL, &rettv->vval.v_number); if (chan) { channel_create_event(chan, NULL); } @@ -10458,7 +10473,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin)); Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, - true, false, false, cwd, + true, false, false, false, cwd, term_width, curwin->w_height_inner, xstrdup("xterm-256color"), NULL, &rettv->vval.v_number); @@ -10808,20 +10823,8 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) case VAR_LIST: n = VAR_TYPE_LIST; break; case VAR_DICT: n = VAR_TYPE_DICT; break; case VAR_FLOAT: n = VAR_TYPE_FLOAT; break; - case VAR_SPECIAL: { - switch (argvars[0].vval.v_special) { - case kSpecialVarTrue: - case kSpecialVarFalse: { - n = VAR_TYPE_BOOL; - break; - } - case kSpecialVarNull: { - n = 7; - break; - } - } - break; - } + case VAR_BOOL: n = VAR_TYPE_BOOL; break; + case VAR_SPECIAL:n = VAR_TYPE_SPECIAL; break; case VAR_UNKNOWN: { internal_error("f_type(UNKNOWN)"); break; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 35130f6f40..0daaf6c878 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1693,21 +1693,21 @@ int tv_dict_add_float(dict_T *const d, const char *const key, return OK; } -/// Add a special entry to dictionary +/// Add a boolean entry to dictionary /// /// @param[out] d Dictionary to add entry to. /// @param[in] key Key to add. /// @param[in] key_len Key length. -/// @param[in] val SpecialVarValue to add. +/// @param[in] val BoolVarValue to add. /// /// @return OK in case of success, FAIL when key already exists. -int tv_dict_add_special(dict_T *const d, const char *const key, - const size_t key_len, SpecialVarValue val) +int tv_dict_add_bool(dict_T *const d, const char *const key, + const size_t key_len, BoolVarValue val) { dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); - item->di_tv.v_type = VAR_SPECIAL; - item->di_tv.vval.v_special = val; + item->di_tv.v_type = VAR_BOOL; + item->di_tv.vval.v_bool = val; if (tv_dict_add(d, item) == FAIL) { tv_dict_item_free(item); return FAIL; @@ -2013,12 +2013,15 @@ void tv_dict_alloc_ret(typval_T *const ret_tv) #define TYPVAL_ENCODE_CONV_NIL(tv) \ do { \ - tv->vval.v_special = kSpecialVarFalse; \ + tv->vval.v_special = kSpecialVarNull; \ tv->v_lock = VAR_UNLOCKED; \ } while (0) #define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ - TYPVAL_ENCODE_CONV_NIL(tv) + do { \ + tv->vval.v_bool = kBoolVarFalse; \ + tv->v_lock = VAR_UNLOCKED; \ + } while (0) #define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ do { \ @@ -2293,6 +2296,7 @@ void tv_free(typval_T *tv) tv_dict_unref(tv->vval.v_dict); break; } + case VAR_BOOL: case VAR_SPECIAL: case VAR_NUMBER: case VAR_FLOAT: @@ -2324,6 +2328,7 @@ void tv_copy(const typval_T *const from, typval_T *const to) switch (from->v_type) { case VAR_NUMBER: case VAR_FLOAT: + case VAR_BOOL: case VAR_SPECIAL: { break; } @@ -2425,6 +2430,7 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) case VAR_STRING: case VAR_FUNC: case VAR_PARTIAL: + case VAR_BOOL: case VAR_SPECIAL: { break; } @@ -2588,6 +2594,9 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, const char *s2 = tv_get_string_buf(tv2, buf2); return mb_strcmp_ic((bool)ic, s1, s2) == 0; } + case VAR_BOOL: { + return tv1->vval.v_bool == tv2->vval.v_bool; + } case VAR_SPECIAL: { return tv1->vval.v_special == tv2->vval.v_special; } @@ -2638,6 +2647,10 @@ bool tv_check_str_or_nr(const typval_T *const tv) EMSG(_("E728: Expected a Number or a String, Dictionary found")); return false; } + case VAR_BOOL: { + EMSG(_("E5299: Expected a Number or a String, Boolean found")); + return false; + } case VAR_SPECIAL: { EMSG(_("E5300: Expected a Number or a String")); return false; @@ -2677,6 +2690,7 @@ bool tv_check_num(const typval_T *const tv) { switch (tv->v_type) { case VAR_NUMBER: + case VAR_BOOL: case VAR_SPECIAL: case VAR_STRING: { return true; @@ -2721,6 +2735,7 @@ bool tv_check_str(const typval_T *const tv) { switch (tv->v_type) { case VAR_NUMBER: + case VAR_BOOL: case VAR_SPECIAL: case VAR_STRING: { return true; @@ -2791,8 +2806,11 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error) } return n; } + case VAR_BOOL: { + return tv->vval.v_bool == kBoolVarTrue ? 1 : 0; + } case VAR_SPECIAL: { - return tv->vval.v_special == kSpecialVarTrue ? 1 : 0; + return 0; } case VAR_UNKNOWN: { emsgf(_(e_intern2), "tv_get_number(UNKNOWN)"); @@ -2860,6 +2878,10 @@ float_T tv_get_float(const typval_T *const tv) EMSG(_("E894: Using a Dictionary as a Float")); break; } + case VAR_BOOL: { + EMSG(_("E362: Using a boolean value as a Float")); + break; + } case VAR_SPECIAL: { EMSG(_("E907: Using a special value as a Float")); break; @@ -2897,6 +2919,10 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf) } return ""; } + case VAR_BOOL: { + STRCPY(buf, encode_bool_var_names[tv->vval.v_bool]); + return buf; + } case VAR_SPECIAL: { STRCPY(buf, encode_special_var_names[tv->vval.v_special]); return buf; diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 4390db1b71..343dd205ff 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -91,10 +91,14 @@ typedef struct dict_watcher { bool busy; // prevent recursion if the dict is changed in the callback } DictWatcher; +/// Bool variable values +typedef enum { + kBoolVarFalse, ///< v:false + kBoolVarTrue, ///< v:true +} BoolVarValue; + /// Special variable values typedef enum { - kSpecialVarFalse, ///< v:false - kSpecialVarTrue, ///< v:true kSpecialVarNull, ///< v:null } SpecialVarValue; @@ -114,6 +118,7 @@ typedef enum { VAR_LIST, ///< List, .v_list is used. VAR_DICT, ///< Dictionary, .v_dict is used. VAR_FLOAT, ///< Floating-point value, .v_float is used. + VAR_BOOL, ///< true, false VAR_SPECIAL, ///< Special value (true, false, null), .v_special ///< is used. VAR_PARTIAL, ///< Partial, .v_partial is used. @@ -125,6 +130,7 @@ typedef struct { VarLockStatus v_lock; ///< Variable lock status. union typval_vval_union { varnumber_T v_number; ///< Number, for VAR_NUMBER. + BoolVarValue v_bool; ///< Bool value, for VAR_BOOL SpecialVarValue v_special; ///< Special value, for VAR_SPECIAL. float_T v_float; ///< Floating-point number, for VAR_FLOAT. char_u *v_string; ///< String, for VAR_STRING and VAR_FUNC, can be NULL. diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index af21a6fbe3..0aa64b1d5f 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -379,17 +379,22 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, _mp_last(*mpstack)); break; } + case VAR_BOOL: { + switch (tv->vval.v_bool) { + case kBoolVarTrue: + case kBoolVarFalse: { + TYPVAL_ENCODE_CONV_BOOL(tv, tv->vval.v_bool == kBoolVarTrue); + break; + } + } + break; + } case VAR_SPECIAL: { switch (tv->vval.v_special) { case kSpecialVarNull: { TYPVAL_ENCODE_CONV_NIL(tv); // -V1037 break; } - case kSpecialVarTrue: - case kSpecialVarFalse: { - TYPVAL_ENCODE_CONV_BOOL(tv, tv->vval.v_special == kSpecialVarTrue); - break; - } } break; } @@ -607,7 +612,7 @@ _convert_one_value_regular_dict: {} kMPConvDict); TYPVAL_ENCODE_CONV_DICT_START(tv, tv->vval.v_dict, tv->vval.v_dict->dv_hashtab.ht_used); - assert(saved_copyID != copyID && saved_copyID != copyID - 1); + assert(saved_copyID != copyID); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, .type = kMPConvDict, diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index ae8557a8bc..4d658498c1 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -43,11 +43,11 @@ hashtab_T func_hashtab; static garray_T funcargs = GA_EMPTY_INIT_VALUE; // pointer to funccal for currently active function -funccall_T *current_funccal = NULL; +static funccall_T *current_funccal = NULL; // Pointer to list of previously used funccal, still around because some // item in it is still being used. -funccall_T *previous_funccal = NULL; +static funccall_T *previous_funccal = NULL; static char *e_funcexts = N_( "E122: Function %s already exists, add ! to replace it"); @@ -170,6 +170,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) garray_T newargs = GA_EMPTY_INIT_VALUE; garray_T *pnewargs; ufunc_T *fp = NULL; + partial_T *pt = NULL; int varargs; int ret; char_u *start = skipwhite(*arg + 1); @@ -219,7 +220,6 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) int len, flags = 0; char_u *p; char_u name[20]; - partial_T *pt; garray_T newlines; lambda_no++; @@ -274,6 +274,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) errret: ga_clear_strings(&newargs); xfree(fp); + xfree(pt); eval_lavars_used = old_eval_lavars; return FAIL; } @@ -541,14 +542,8 @@ static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) v->di_tv.vval.v_number = nr; } -/* - * Free "fc" and what it contains. - */ -static void -free_funccal( - funccall_T *fc, - int free_val // a: vars were allocated -) +// Free "fc" +static void free_funccal(funccall_T *fc) { for (int i = 0; i < fc->fc_funcs.ga_len; i++) { ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; @@ -563,56 +558,89 @@ free_funccal( } ga_clear(&fc->fc_funcs); - // The a: variables typevals may not have been allocated, only free the - // allocated variables. - vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); + func_ptr_unref(fc->func); + xfree(fc); +} +// Free "fc" and what it contains. +// Can be called only when "fc" is kept beyond the period of it called, +// i.e. after cleanup_function_call(fc). +static void free_funccal_contents(funccall_T *fc) +{ // Free all l: variables. vars_clear(&fc->l_vars.dv_hashtab); - // Free the a:000 variables if they were allocated. - if (free_val) { - TV_LIST_ITER(&fc->l_varlist, li, { - tv_clear(TV_LIST_ITEM_TV(li)); - }); - } + // Free all a: variables. + vars_clear(&fc->l_avars.dv_hashtab); - func_ptr_unref(fc->func); - xfree(fc); + // Free the a:000 variables. + TV_LIST_ITER(&fc->l_varlist, li, { + tv_clear(TV_LIST_ITEM_TV(li)); + }); + + free_funccal(fc); } /// Handle the last part of returning from a function: free the local hashtable. /// Unless it is still in use by a closure. static void cleanup_function_call(funccall_T *fc) { + bool may_free_fc = fc->fc_refcount <= 0; + bool free_fc = true; + current_funccal = fc->caller; - // If the a:000 list and the l: and a: dicts are not referenced and there - // is no closure using it, we can free the funccall_T and what's in it. - if (!fc_referenced(fc)) { - free_funccal(fc, false); + // Free all l: variables if not referred. + if (may_free_fc && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT) { + vars_clear(&fc->l_vars.dv_hashtab); } else { - static int made_copy = 0; + free_fc = false; + } - // "fc" is still in use. This can happen when returning "a:000", - // assigning "l:" to a global variable or defining a closure. - // Link "fc" in the list for garbage collection later. - fc->caller = previous_funccal; - previous_funccal = fc; + // If the a:000 list and the l: and a: dicts are not referenced and + // there is no closure using it, we can free the funccall_T and what's + // in it. + if (may_free_fc && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) { + vars_clear_ext(&fc->l_avars.dv_hashtab, false); + } else { + free_fc = false; // Make a copy of the a: variables, since we didn't do that above. TV_DICT_ITER(&fc->l_avars, di, { tv_copy(&di->di_tv, &di->di_tv); }); + } + + if (may_free_fc && fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) + == DO_NOT_FREE_CNT) { + fc->l_varlist.lv_first = NULL; // NOLINT(runtime/deprecated) + + } else { + free_fc = false; // Make a copy of the a:000 items, since we didn't do that above. TV_LIST_ITER(&fc->l_varlist, li, { tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); }); + } - if (++made_copy == 10000) { - // We have made a lot of copies. This can happen when - // repetitively calling a function that creates a reference to + if (free_fc) { + free_funccal(fc); + } else { + static int made_copy = 0; + + // "fc" is still in use. This can happen when returning "a:000", + // assigning "l:" to a global variable or defining a closure. + // Link "fc" in the list for garbage collection later. + fc->caller = previous_funccal; + previous_funccal = fc; + + if (want_garbage_collect) { + // If garbage collector is ready, clear count. + made_copy = 0; + } else if (++made_copy >= (int)((4096 * 1024) / sizeof(*fc))) { + // We have made a lot of copies, worth 4 Mbyte. This can happen + // when repetitively calling a function that creates a reference to // itself somehow. Call the garbage collector soon to avoid using // too much memory. made_copy = 0; @@ -639,7 +667,7 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { if (fc == *pfc) { *pfc = fc->caller; - free_funccal(fc, true); + free_funccal_contents(fc); return; } } @@ -766,7 +794,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, // check for CTRL-C hit line_breakcheck(); // prepare the funccall_T structure - fc = xmalloc(sizeof(funccall_T)); + fc = xcalloc(1, sizeof(funccall_T)); fc->caller = current_funccal; current_funccal = fc; fc->func = fp; @@ -881,9 +909,11 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, } if (ai >= 0 && ai < MAX_FUNC_ARGS) { - tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]); - *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i]; - TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED; + listitem_T *li = &fc->l_listitems[ai]; + + *TV_LIST_ITEM_TV(li) = argvars[i]; + TV_LIST_ITEM_TV(li)->v_lock = VAR_FIXED; + tv_list_append(&fc->l_varlist, li); } } @@ -1106,21 +1136,26 @@ static bool func_name_refcount(char_u *name) return isdigit(*name) || *name == '<'; } -/* - * Save the current function call pointer, and set it to NULL. - * Used when executing autocommands and for ":source". - */ -void *save_funccal(void) -{ - funccall_T *fc = current_funccal; +static funccal_entry_T *funccal_stack = NULL; +// Save the current function call pointer, and set it to NULL. +// Used when executing autocommands and for ":source". +void save_funccal(funccal_entry_T *entry) +{ + entry->top_funccal = current_funccal; + entry->next = funccal_stack; + funccal_stack = entry; current_funccal = NULL; - return (void *)fc; } -void restore_funccal(void *vfc) +void restore_funccal(void) { - current_funccal = (funccall_T *)vfc; + if (funccal_stack == NULL) { + IEMSG("INTERNAL: restore_funccal()"); + } else { + current_funccal = funccal_stack->top_funccal; + funccal_stack = funccal_stack->next; + } } funccall_T *get_current_funccal(void) @@ -1128,6 +1163,11 @@ funccall_T *get_current_funccal(void) return current_funccal; } +void set_current_funccal(funccall_T *fc) +{ + current_funccal = fc; +} + #if defined(EXITFREE) void free_all_functions(void) { @@ -1137,10 +1177,13 @@ void free_all_functions(void) uint64_t todo = 1; uint64_t used; - // Clean up the call stack. + // Clean up the current_funccal chain and the funccal stack. while (current_funccal != NULL) { tv_clear(current_funccal->rettv); cleanup_function_call(current_funccal); + if (current_funccal == NULL && funccal_stack != NULL) { + restore_funccal(); + } } // First clear what the functions contain. Since this may lower the @@ -3121,7 +3164,7 @@ bool free_unref_funccal(int copyID, int testing) if (can_free_funccal(*pfc, copyID)) { funccall_T *fc = *pfc; *pfc = fc->caller; - free_funccal(fc, true); + free_funccal_contents(fc); did_free = true; did_free_funccal = true; } else { @@ -3314,9 +3357,18 @@ bool set_ref_in_call_stack(int copyID) bool abort = false; for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) { - abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); - abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_funccal(fc, copyID); } + + // Also go through the funccal_stack. + for (funccal_entry_T *entry = funccal_stack; entry != NULL; + entry = entry->next) { + for (funccall_T *fc = entry->top_funccal; !abort && fc != NULL; + fc = fc->caller) { + abort = abort || set_ref_in_funccal(fc, copyID); + } + } + return abort; } diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h index ad8e071548..e8ad0bf1da 100644 --- a/src/nvim/eval/userfunc.h +++ b/src/nvim/eval/userfunc.h @@ -11,6 +11,12 @@ typedef struct { dictitem_T *fd_di; ///< Dictionary item used. } funcdict_T; +typedef struct funccal_entry funccal_entry_T; +struct funccal_entry { + void *top_funccal; + funccal_entry_T *next; +}; + /// errors for when calling a function typedef enum { ERROR_UNKNOWN = 0, diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index 37b9f73ba4..13517d3df1 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -52,7 +52,7 @@ int libuv_process_spawn(LibuvProcess *uvproc) if (!proc->in.closed) { uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; #ifdef WIN32 - uvproc->uvstdio[0].flags |= UV_OVERLAPPED_PIPE; + uvproc->uvstdio[0].flags |= proc->overlapped ? UV_OVERLAPPED_PIPE : 0; #endif uvproc->uvstdio[0].data.stream = STRUCT_CAST(uv_stream_t, &proc->in.uv.pipe); @@ -61,8 +61,9 @@ int libuv_process_spawn(LibuvProcess *uvproc) if (!proc->out.closed) { uvproc->uvstdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; #ifdef WIN32 - // pipe must be readable for IOCP to work. - uvproc->uvstdio[1].flags |= UV_READABLE_PIPE | UV_OVERLAPPED_PIPE; + // pipe must be readable for IOCP to work on Windows. + uvproc->uvstdio[1].flags |= proc->overlapped ? + (UV_READABLE_PIPE | UV_OVERLAPPED_PIPE) : 0; #endif uvproc->uvstdio[1].data.stream = STRUCT_CAST(uv_stream_t, &proc->out.uv.pipe); diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index b677b80bfe..84e81238e9 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -27,7 +27,7 @@ struct process { Stream in, out, err; process_exit_cb cb; internal_process_cb internal_exit_cb, internal_close_cb; - bool closed, detach; + bool closed, detach, overlapped; MultiQueue *events; }; diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c index 0e87f7c6c1..1e9e530a42 100644 --- a/src/nvim/event/stream.c +++ b/src/nvim/event/stream.c @@ -11,6 +11,9 @@ #include "nvim/rbuffer.h" #include "nvim/macros.h" #include "nvim/event/stream.h" +#ifdef WIN32 +# include "nvim/os/os_win_console.h" +#endif #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/stream.c.generated.h" @@ -62,6 +65,11 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream) if (type == UV_TTY) { uv_tty_init(&loop->uv, &stream->uv.tty, fd, 0); uv_tty_set_mode(&stream->uv.tty, UV_TTY_MODE_RAW); + DWORD dwMode; + if (GetConsoleMode(stream->uv.tty.handle, &dwMode)) { + dwMode |= ENABLE_VIRTUAL_TERMINAL_INPUT; + SetConsoleMode(stream->uv.tty.handle, dwMode); + } stream->uvstream = STRUCT_CAST(uv_stream_t, &stream->uv.tty); } else { #endif diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 9f4055af8d..7f4b01e306 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -3107,7 +3107,6 @@ int do_source(char_u *fname, int check_other, int is_vimrc) int retval = FAIL; static scid_T last_current_SID = 0; static int last_current_SID_seq = 0; - void *save_funccalp; int save_debug_break_level = debug_break_level; scriptitem_T *si = NULL; proftime_T wait_start; @@ -3228,7 +3227,8 @@ int do_source(char_u *fname, int check_other, int is_vimrc) // Don't use local function variables, if called from a function. // Also starts profiling timer for nested script. - save_funccalp = save_funccal(); + funccal_entry_T funccalp_entry; + save_funccal(&funccalp_entry); // Check if this script was sourced before to finds its SID. // If it's new, generate a new SID. @@ -3353,7 +3353,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc) } current_sctx = save_current_sctx; - restore_funccal(save_funccalp); + restore_funccal(); if (l_do_profiling == PROF_YES) { prof_child_exit(&wait_start); // leaving a child now } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index b799e86ff7..1a836b7a98 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -432,8 +432,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level); tv_dict_set_keys_readonly(dict); // not readonly: - tv_dict_add_special(dict, S_LEN("abort"), - s->gotesc ? kSpecialVarTrue : kSpecialVarFalse); + tv_dict_add_bool(dict, S_LEN("abort"), + s->gotesc ? kBoolVarTrue : kBoolVarFalse); try_enter(&tstate); apply_autocmds(EVENT_CMDLINELEAVE, (char_u *)firstcbuf, (char_u *)firstcbuf, false, curbuf); @@ -5361,12 +5361,12 @@ void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options) // Concatenate new results to previous ones. ga_grow(ga, num_p); + // take over the pointers and put them in "ga" for (int i = 0; i < num_p; i++) { - ((char_u **)ga->ga_data)[ga->ga_len] = vim_strsave(p[i]); + ((char_u **)ga->ga_data)[ga->ga_len] = p[i]; ga->ga_len++; } - - FreeWild(num_p, p); + xfree(p); } } } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 2335aba6dd..f29304867a 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -6736,7 +6736,6 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, static int nesting = 0; AutoPatCmd patcmd; AutoPat *ap; - void *save_funccalp; char_u *save_cmdarg; long save_cmdbang; static int filechangeshell_busy = FALSE; @@ -6926,8 +6925,9 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, if (do_profiling == PROF_YES) prof_child_enter(&wait_time); /* doesn't count for the caller itself */ - /* Don't use local function variables, if called from a function */ - save_funccalp = save_funccal(); + // Don't use local function variables, if called from a function. + funccal_entry_T funccal_entry; + save_funccal(&funccal_entry); /* * When starting to execute autocommands, save the search patterns. @@ -7016,9 +7016,10 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, autocmd_bufnr = save_autocmd_bufnr; autocmd_match = save_autocmd_match; current_sctx = save_current_sctx; - restore_funccal(save_funccalp); - if (do_profiling == PROF_YES) + restore_funccal(); + if (do_profiling == PROF_YES) { prof_child_exit(&wait_time); + } KeyTyped = save_KeyTyped; xfree(fname); xfree(sfname); diff --git a/src/nvim/globals.h b/src/nvim/globals.h index f102c3ddd8..d6d00d6e83 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -203,7 +203,7 @@ EXTERN int msg_nowait INIT(= false); // don't wait for this msg EXTERN int emsg_off INIT(= 0); // don't display errors for now, // unless 'debug' is set. EXTERN int info_message INIT(= false); // printing informative message -EXTERN int msg_hist_off INIT(= false); // don't add messages to history +EXTERN bool msg_hist_off INIT(= false); // don't add messages to history EXTERN int need_clr_eos INIT(= false); // need to clear text before // displaying a message. EXTERN int emsg_skip INIT(= 0); // don't display errors for @@ -478,6 +478,8 @@ EXTERN int sc_col; // column for shown command EXTERN int starting INIT(= NO_SCREEN); // true when planning to exit. Might keep running if there is a changed buffer. EXTERN bool exiting INIT(= false); +// internal value of v:dying +EXTERN int v_dying INIT(= 0); // is stdin a terminal? EXTERN int stdin_isatty INIT(= true); // is stdout a terminal? diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 4b8b9992f5..a553110552 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -517,8 +517,8 @@ char_u *get_special_key_name(int c, int modifiers) /// @param[in,out] srcp Source from which <> are translated. Is advanced to /// after the <> name if there is a match. /// @param[in] src_len Length of the srcp. -/// @param[out] dst Location where translation result will be kept. Must have -/// at least six bytes. +/// @param[out] dst Location where translation result will be kept. It must +// be at least 19 bytes per "<x>" form. /// @param[in] keycode Prefer key code, e.g. K_DEL in place of DEL. /// @param[in] in_string Inside a double quoted string /// diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index fca74b5901..69114c967d 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -286,10 +286,10 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) break; } case LUA_TBOOLEAN: { - cur.tv->v_type = VAR_SPECIAL; - cur.tv->vval.v_special = (lua_toboolean(lstate, -1) - ? kSpecialVarTrue - : kSpecialVarFalse); + cur.tv->v_type = VAR_BOOL; + cur.tv->vval.v_bool = (lua_toboolean(lstate, -1) + ? kBoolVarTrue + : kBoolVarFalse); break; } case LUA_TSTRING: { diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 144646fca2..327ed6d6b7 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -28,6 +28,8 @@ #include "nvim/ascii.h" #include "nvim/change.h" #include "nvim/eval/userfunc.h" +#include "nvim/event/time.h" +#include "nvim/event/loop.h" #ifdef WIN32 #include "nvim/os/os.h" @@ -255,6 +257,101 @@ static struct luaL_Reg regex_meta[] = { { NULL, NULL } }; +// Dummy timer callback. Used by f_wait(). +static void dummy_timer_due_cb(TimeWatcher *tw, void *data) +{ +} + +// Dummy timer close callback. Used by f_wait(). +static void dummy_timer_close_cb(TimeWatcher *tw, void *data) +{ + xfree(tw); +} + +static bool nlua_wait_condition(lua_State *lstate, int *status, + bool *callback_result) +{ + lua_pushvalue(lstate, 2); + *status = lua_pcall(lstate, 0, 1, 0); + if (*status) { + return true; // break on error, but keep error on stack + } + *callback_result = lua_toboolean(lstate, -1); + lua_pop(lstate, 1); + return *callback_result; // break if true +} + +/// "vim.wait(timeout, condition[, interval])" function +static int nlua_wait(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + intptr_t timeout = luaL_checkinteger(lstate, 1); + if (timeout < 0) { + return luaL_error(lstate, "timeout must be > 0"); + } + + // Check if condition can be called. + bool is_function = (lua_type(lstate, 2) == LUA_TFUNCTION); + + // Check if condition is callable table + if (!is_function && luaL_getmetafield(lstate, 2, "__call") != 0) { + is_function = (lua_type(lstate, -1) == LUA_TFUNCTION); + lua_pop(lstate, 1); + } + + if (!is_function) { + lua_pushliteral(lstate, "vim.wait: condition must be a function"); + return lua_error(lstate); + } + + intptr_t interval = 200; + if (lua_gettop(lstate) >= 3) { + interval = luaL_checkinteger(lstate, 3); + if (interval < 0) { + return luaL_error(lstate, "interval must be > 0"); + } + } + + TimeWatcher *tw = xmalloc(sizeof(TimeWatcher)); + + // Start dummy timer. + time_watcher_init(&main_loop, tw, NULL); + tw->events = main_loop.events; + tw->blockable = true; + time_watcher_start(tw, dummy_timer_due_cb, + (uint64_t)interval, (uint64_t)interval); + + int pcall_status = 0; + bool callback_result = false; + + LOOP_PROCESS_EVENTS_UNTIL( + &main_loop, + main_loop.events, + (int)timeout, + nlua_wait_condition(lstate, &pcall_status, &callback_result) || got_int); + + // Stop dummy timer + time_watcher_stop(tw); + time_watcher_close(tw, dummy_timer_close_cb); + + if (pcall_status) { + return lua_error(lstate); + } else if (callback_result) { + lua_pushboolean(lstate, 1); + lua_pushnil(lstate); + } else if (got_int) { + got_int = false; + vgetc(); + lua_pushboolean(lstate, 0); + lua_pushinteger(lstate, -2); + } else { + lua_pushboolean(lstate, 0); + lua_pushinteger(lstate, -1); + } + + return 2; +} + /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. @@ -305,7 +402,6 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // regex lua_pushcfunction(lstate, &nlua_regex); lua_setfield(lstate, -2, "regex"); - luaL_newmetatable(lstate, "nvim_regex"); luaL_register(lstate, NULL, regex_meta); lua_pushvalue(lstate, -1); // [meta, meta] @@ -320,6 +416,10 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, &nlua_rpcnotify); lua_setfield(lstate, -2, "rpcnotify"); + // wait + lua_pushcfunction(lstate, &nlua_wait); + lua_setfield(lstate, -2, "wait"); + // vim.loop luv_set_loop(lstate, &main_loop.uv); luv_set_callback(lstate, nlua_luv_cfpcall); diff --git a/src/nvim/main.c b/src/nvim/main.c index 4a9f2371a2..b3a903cbe3 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -269,6 +269,8 @@ int main(int argc, char **argv) early_init(); + set_argv_var(argv, argc); // set v:argv + // Check if we have an interactive window. check_and_set_isatty(¶ms); @@ -619,7 +621,7 @@ void getout(int exitval) /* Optionally print hashtable efficiency. */ hash_debug_results(); - if (get_vim_var_nr(VV_DYING) <= 1) { + if (v_dying <= 1) { const tabpage_T *next_tp; // Trigger BufWinLeave for all windows, but only once per buffer. @@ -668,8 +670,9 @@ void getout(int exitval) shada_write_file(NULL, false); } - if (get_vim_var_nr(VV_DYING) <= 1) - apply_autocmds(EVENT_VIMLEAVE, NULL, NULL, FALSE, curbuf); + if (v_dying <= 1) { + apply_autocmds(EVENT_VIMLEAVE, NULL, NULL, false, curbuf); + } profile_dump(); diff --git a/src/nvim/message.c b/src/nvim/message.c index a12e665099..8999365d32 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -306,11 +306,6 @@ bool msg_attr_keep(char_u *s, int attr, bool keep, bool multiline) add_msg_hist((const char *)s, -1, attr, multiline); } - /* When displaying keep_msg, don't let msg_start() free it, caller must do - * that. */ - if (s == keep_msg) - keep_msg = NULL; - /* Truncate the message if needed. */ msg_start(); buf = msg_strtrunc(s, FALSE); @@ -2574,10 +2569,15 @@ static int do_more_prompt(int typed_char) msgchunk_T *mp; int i; + // If headless mode is enabled and no input is required, this variable + // will be true. However If server mode is enabled, the message "--more--" + // should be displayed. + bool no_need_more = headless_mode && !embedded_mode; + // We get called recursively when a timer callback outputs a message. In // that case don't show another prompt. Also when at the hit-Enter prompt // and nothing was typed. - if (entered || (State == HITRETURN && typed_char == 0)) { + if (no_need_more || entered || (State == HITRETURN && typed_char == 0)) { return false; } entered = true; diff --git a/src/nvim/move.c b/src/nvim/move.c index d4f82bc601..8a8a639a52 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -996,7 +996,7 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, col -= wp->w_leftcol; - if (col >= 0 && col < width) { + if (col >= 0 && col < wp->w_width) { coloff = col - scol + (local ? 0 : wp->w_wincol) + 1; } else { scol = ccol = ecol = 0; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 87d687198d..968cfde388 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -621,6 +621,8 @@ static void normal_redraw_mode_message(NormalState *s) update_screen(0); // now reset it, otherwise it's put in the history again keep_msg = kmsg; + + kmsg = vim_strsave(keep_msg); msg_attr((const char *)kmsg, keep_msg_attr); xfree(kmsg); } @@ -1265,10 +1267,15 @@ static void normal_redraw(NormalState *s) // Display message after redraw. If an external message is still visible, // it contains the kept message already. if (keep_msg != NULL && !msg_ext_is_visible()) { - // msg_attr_keep() will set keep_msg to NULL, must free the string here. - // Don't reset keep_msg, msg_attr_keep() uses it to check for duplicates. - char *p = (char *)keep_msg; - msg_attr(p, keep_msg_attr); + char_u *const p = vim_strsave(keep_msg); + + // msg_start() will set keep_msg to NULL, make a copy + // first. Don't reset keep_msg, msg_attr_keep() uses it to + // check for duplicates. Never put this message in + // history. + msg_hist_off = true; + msg_attr((const char *)p, keep_msg_attr); + msg_hist_off = false; xfree(p); } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index a70224f98b..eb32a1dd9b 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2740,14 +2740,18 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) tv_dict_add_str(dict, S_LEN("regname"), buf); // Motion type: inclusive or exclusive. - tv_dict_add_special(dict, S_LEN("inclusive"), - oap->inclusive ? kSpecialVarTrue : kSpecialVarFalse); + tv_dict_add_bool(dict, S_LEN("inclusive"), + oap->inclusive ? kBoolVarTrue : kBoolVarFalse); // Kind of operation: yank, delete, change). buf[0] = (char)get_op_char(oap->op_type); buf[1] = NUL; tv_dict_add_str(dict, S_LEN("operator"), buf); + // Selection type: visual or not. + tv_dict_add_bool(dict, S_LEN("visual"), + oap->is_VIsual ? kBoolVarTrue : kBoolVarFalse); + tv_dict_set_keys_readonly(dict); textlock++; apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, false, curbuf); diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index c1580c5fc3..b7878d9da8 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -188,8 +188,13 @@ size_t input_enqueue(String keys) char *ptr = keys.data; char *end = ptr + keys.size; - while (rbuffer_space(input_buffer) >= 6 && ptr < end) { - uint8_t buf[6] = { 0 }; + while (rbuffer_space(input_buffer) >= 19 && ptr < end) { + // A "<x>" form occupies at least 1 characters, and produces up + // to 19 characters (1 + 5 * 3 for the char and 3 for a modifier). + // In the case of K_SPECIAL(0x80) or CSI(0x9B), 3 bytes are escaped and + // needed, but since the keys are UTF-8, so the first byte cannot be + // K_SPECIAL(0x80) or CSI(0x9B). + uint8_t buf[19] = { 0 }; unsigned int new_size = trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true, false); diff --git a/src/nvim/os/os_win_console.c b/src/nvim/os/os_win_console.c index 8a0aa2f5ae..18dcfeafa0 100644 --- a/src/nvim/os/os_win_console.c +++ b/src/nvim/os/os_win_console.c @@ -2,8 +2,14 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include "nvim/vim.h" +#include "nvim/os/input.h" #include "nvim/os/os_win_console.h" +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/os_win_console.c.generated.h" +#endif + + int os_get_conin_fd(void) { const HANDLE conin_handle = CreateFile("CONIN$", @@ -40,3 +46,29 @@ void os_replace_stdout_and_stderr_to_conout(void) const int conerr_fd = _open_osfhandle((intptr_t)conout_handle, 0); assert(conerr_fd == STDERR_FILENO); } + +void os_set_vtp(bool enable) +{ + static TriState is_legacy = kNone; + if (is_legacy == kNone) { + uv_tty_vtermstate_t state; + uv_tty_get_vterm_state(&state); + is_legacy = (state == UV_TTY_UNSUPPORTED) ? kTrue : kFalse; + } + if (!is_legacy && !os_has_vti()) { + uv_tty_set_vterm_state(enable ? UV_TTY_SUPPORTED : UV_TTY_UNSUPPORTED); + } +} + +static bool os_has_vti(void) +{ + static TriState has_vti = kNone; + if (has_vti == kNone) { + HANDLE handle = (HANDLE)_get_osfhandle(input_global_fd()); + DWORD dwMode; + if (handle != INVALID_HANDLE_VALUE && GetConsoleMode(handle, &dwMode)) { + has_vti = !!(dwMode & ENABLE_VIRTUAL_TERMINAL_INPUT) ? kTrue : kFalse; + } + } + return has_vti == kTrue; +} diff --git a/src/nvim/os/os_win_console.h b/src/nvim/os/os_win_console.h index 154ec83d8a..7b5800afa8 100644 --- a/src/nvim/os/os_win_console.h +++ b/src/nvim/os/os_win_console.h @@ -5,4 +5,8 @@ # include "os/os_win_console.h.generated.h" #endif +#ifndef ENABLE_VIRTUAL_TERMINAL_INPUT +# define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 +#endif + #endif // NVIM_OS_OS_WIN_CONSOLE_H diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index 112de9fed8..bfe230b521 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -157,6 +157,7 @@ static void deadly_signal(int signum) { // Set the v:dying variable. set_vim_var_nr(VV_DYING, 1); + v_dying = 1; WLOG("got signal %d (%s)", signum, signal_name(signum)); diff --git a/src/nvim/os/tty.c b/src/nvim/os/tty.c index 4f525bed9a..c80ef99084 100644 --- a/src/nvim/os/tty.c +++ b/src/nvim/os/tty.c @@ -28,7 +28,7 @@ void os_tty_guess_term(const char **term, int out_fd) if (winpty) { // Force TERM=win32con when running in winpty. *term = "win32con"; - uv_set_vterm_state(UV_UNSUPPORTED); + uv_tty_set_vterm_state(UV_TTY_UNSUPPORTED); return; } @@ -55,7 +55,7 @@ void os_tty_guess_term(const char **term, int out_fd) } if (conemu_ansi) { - uv_set_vterm_state(UV_SUPPORTED); + uv_tty_set_vterm_state(UV_TTY_SUPPORTED); } } #endif diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index e06433892d..c712762bdf 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -923,6 +923,6 @@ void pum_set_event_info(dict_T *dict) tv_dict_add_float(dict, S_LEN("row"), r); tv_dict_add_float(dict, S_LEN("col"), c); tv_dict_add_nr(dict, S_LEN("size"), pum_size); - tv_dict_add_special(dict, S_LEN("scrollbar"), - pum_scrollbar ? kSpecialVarTrue : kSpecialVarFalse); + tv_dict_add_bool(dict, S_LEN("scrollbar"), + pum_scrollbar ? kBoolVarTrue : kBoolVarFalse); } diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index f33c61d39f..2ca5f42e51 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -249,6 +249,7 @@ static char_u e_nul_found[] = N_( static char_u e_misplaced[] = N_("E866: (NFA regexp) Misplaced %c"); static char_u e_ill_char_class[] = N_( "E877: (NFA regexp) Invalid character class: %" PRId64); +static char_u e_value_too_large[] = N_("E951: \\% value too large"); /* Since the out pointers in the list are always * uninitialized, we use the pointers themselves @@ -1499,7 +1500,8 @@ static int nfa_regatom(void) c = getchr(); while (ascii_isdigit(c)) { if (n > (INT32_MAX - (c - '0')) / 10) { - EMSG(_("E951: \\% value too large")); + // overflow. + EMSG(_(e_value_too_large)); return FAIL; } n = n * 10 + (c - '0'); @@ -1526,7 +1528,7 @@ static int nfa_regatom(void) limit = INT32_MAX / MB_MAXBYTES; } if (n >= limit) { - EMSG(_("E951: \\% value too large")); + EMSG(_(e_value_too_large)); return FAIL; } EMIT((int)n); diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 9e958663aa..ba52f5b489 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2994,6 +2994,12 @@ win_line ( c_final = NUL; n_extra = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, false)); + if (row == startrow) { + n_extra -= win_col_off2(wp); + if (n_extra < 0) { + n_extra = 0; + } + } if (wp->w_skipcol > 0 && wp->w_p_wrap && wp->w_briopt_sbr) { need_showbreak = false; } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index ef4dfb3caa..f3b05c3961 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -7444,6 +7444,8 @@ static int syn_add_group(char_u *name) return 0; } + char_u *const name_up = vim_strsave_up(name); + // Append another syntax_highlight entry. struct hl_group* hlgp = GA_APPEND_VIA_PTR(struct hl_group, &highlight_ga); memset(hlgp, 0, sizeof(*hlgp)); @@ -7452,7 +7454,7 @@ static int syn_add_group(char_u *name) hlgp->sg_rgb_fg = -1; hlgp->sg_rgb_sp = -1; hlgp->sg_blend = -1; - hlgp->sg_name_u = vim_strsave_up(name); + hlgp->sg_name_u = name_up; return highlight_ga.ga_len; /* ID is index plus one */ } diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index adf7463936..e249d499c4 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -44,6 +44,10 @@ if &lines < 24 || &columns < 80 qa! endif +if has('reltime') + let s:start_time = reltime() +endif + " Common with all tests on all systems. source setup.vim @@ -100,6 +104,9 @@ endfunc func RunTheTest(test) echo 'Executing ' . a:test + if has('reltime') + let func_start = reltime() + endif " Avoid stopping at the "hit enter" prompt set nomore @@ -124,7 +131,11 @@ func RunTheTest(test) endtry endif - call add(s:messages, 'Executing ' . a:test) + let message = 'Executed ' . a:test + if has('reltime') + let message ..= ' in ' .. reltimestr(reltime(func_start)) .. ' seconds' + endif + call add(s:messages, message) let s:done += 1 if a:test =~ 'Test_nocatch_' @@ -230,6 +241,9 @@ func FinishTesting() else let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test') endif + if has('reltime') + let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds' + endif echo message call add(s:messages, message) if s:fail > 0 diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 954e5d875f..5217aa7339 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1246,23 +1246,23 @@ func Test_TextYankPost() norm "ayiw call assert_equal( - \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'a', 'operator': 'y', 'regtype': 'v'}, + \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'a', 'operator': 'y', 'visual': v:false, 'regtype': 'v'}, \g:event) norm y_ call assert_equal( - \{'regcontents': ['foo'], 'inclusive': v:false, 'regname': '', 'operator': 'y', 'regtype': 'V'}, + \{'regcontents': ['foo'], 'inclusive': v:false, 'regname': '', 'operator': 'y', 'visual': v:false, 'regtype': 'V'}, \g:event) call feedkeys("\<C-V>y", 'x') call assert_equal( - \{'regcontents': ['f'], 'inclusive': v:true, 'regname': '', 'operator': 'y', 'regtype': "\x161"}, + \{'regcontents': ['f'], 'inclusive': v:true, 'regname': '', 'operator': 'y', 'visual': v:true, 'regtype': "\x161"}, \g:event) norm "xciwbar call assert_equal( - \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'x', 'operator': 'c', 'regtype': 'v'}, + \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'x', 'operator': 'c', 'visual': v:false, 'regtype': 'v'}, \g:event) norm "bdiw call assert_equal( - \{'regcontents': ['bar'], 'inclusive': v:true, 'regname': 'b', 'operator': 'd', 'regtype': 'v'}, + \{'regcontents': ['bar'], 'inclusive': v:true, 'regname': 'b', 'operator': 'd', 'visual': v:false, 'regtype': 'v'}, \g:event) call assert_equal({}, v:event) diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index 5675bf74dd..a4c1f62a43 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -373,3 +373,52 @@ func Test_breakindent19_sbr_nextpage() call s:compare_lines(expect, lines) call s:close_windows('set breakindent& briopt& sbr&') endfunc + +func Test_breakindent20_cpo_n_nextpage() + let s:input = "" + call s:test_windows('setl breakindent briopt=min:14 cpo+=n number') + call setline(1, repeat('a', 200)) + norm! 1gg + redraw! + let lines = s:screen_lines(1, 20) + let expect = [ + \ " 1 aaaaaaaaaaaaaaaa", + \ " aaaaaaaaaaaaaaaa", + \ " aaaaaaaaaaaaaaaa", + \ ] + call s:compare_lines(expect, lines) + " Scroll down one screen line + setl scrolloff=5 + norm! 5gj + redraw! + let lines = s:screen_lines(1, 20) + let expect = [ + \ "--1 aaaaaaaaaaaaaaaa", + \ " aaaaaaaaaaaaaaaa", + \ " aaaaaaaaaaaaaaaa", + \ ] + call s:compare_lines(expect, lines) + + setl briopt+=shift:2 + norm! 1gg + let lines = s:screen_lines(1, 20) + let expect = [ + \ " 1 aaaaaaaaaaaaaaaa", + \ " aaaaaaaaaaaaaa", + \ " aaaaaaaaaaaaaa", + \ ] + call s:compare_lines(expect, lines) + " Scroll down one screen line + norm! 5gj + let lines = s:screen_lines(1, 20) + let expect = [ + \ "--1 aaaaaaaaaaaaaa", + \ " aaaaaaaaaaaaaa", + \ " aaaaaaaaaaaaaa", + \ ] + call s:compare_lines(expect, lines) + + call s:close_windows('set breakindent& briopt& cpo& number&') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 2c7d64f078..f8d84f1a49 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -692,6 +692,22 @@ func Test_getcmdwin_autocmd() augroup END endfunc +" Test error: "E135: *Filter* Autocommands must not change current buffer" +func Test_cmd_bang_E135() + new + call setline(1, ['a', 'b', 'c', 'd']) + augroup test_cmd_filter_E135 + au! + autocmd FilterReadPost * help + augroup END + call assert_fails('2,3!echo "x"', 'E135:') + + augroup test_cmd_filter_E135 + au! + augroup END + %bwipe! +endfunc + func Test_verbosefile() set verbosefile=Xlog echomsg 'foo' diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim index e8e561dfd8..2e190911b2 100644 --- a/src/nvim/testdir/test_cursor_func.vim +++ b/src/nvim/testdir/test_cursor_func.vim @@ -93,3 +93,18 @@ func Test_screenpos() close bwipe! endfunc + +func Test_screenpos_number() + rightbelow new + rightbelow 73vsplit + call setline (1, repeat('x', 66)) + setlocal number + redraw + let winid = win_getid() + let [winrow, wincol] = win_screenpos(winid) + let pos = screenpos(winid, 1, 66) + call assert_equal(winrow, pos.row) + call assert_equal(wincol + 66 + 3, pos.col) + close + bwipe! +endfunc diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index ace56a375f..ffd2cee80f 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -151,6 +151,7 @@ let s:filename_checks = { \ 'ecd': ['file.ecd'], \ 'edif': ['file.edf', 'file.edif', 'file.edo'], \ 'elinks': ['/etc/elinks.conf', '/.elinks/elinks.conf'], + \ 'elm': ['file.elm'], \ 'elmfilt': ['filter-rules'], \ 'erlang': ['file.erl', 'file.hrl', 'file.yaws'], \ 'eruby': ['file.erb', 'file.rhtml'], @@ -251,7 +252,7 @@ let s:filename_checks = { \ 'lilo': ['lilo.conf'], \ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf'], \ 'liquid': ['file.liquid'], - \ 'lisp': ['sbclrc', '.sbclrc'], + \ 'lisp': ['file.lsp', 'file.lisp', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'], \ 'lite': ['file.lite', 'file.lt'], \ 'litestep': ['/LiteStep/any/file.rc'], \ 'loginaccess': ['/etc/login.access'], diff --git a/src/nvim/testdir/test_ga.vim b/src/nvim/testdir/test_ga.vim index ea3d211aeb..87f1382342 100644 --- a/src/nvim/testdir/test_ga.vim +++ b/src/nvim/testdir/test_ga.vim @@ -24,6 +24,7 @@ func Test_ga_command() " Test a few multi-bytes characters. call assert_equal("\n<é> 233, Hex 00e9, Oct 351, Digr e'", Do_ga('é')) call assert_equal("\n<ẻ> 7867, Hex 1ebb, Oct 17273, Digr e2", Do_ga('ẻ')) + call assert_equal("\n<\U00012345> 74565, Hex 00012345, Octal 221505", Do_ga("\U00012345")) " Test with combining characters. call assert_equal("\n<e> 101, Hex 65, Octal 145 < ́> 769, Hex 0301, Octal 1401", Do_ga("e\u0301")) diff --git a/src/nvim/testdir/test_maparg.vim b/src/nvim/testdir/test_maparg.vim index 5f73bd80ad..238d2f900d 100644 --- a/src/nvim/testdir/test_maparg.vim +++ b/src/nvim/testdir/test_maparg.vim @@ -15,23 +15,23 @@ function Test_maparg() map foo<C-V> is<F4>foo vnoremap <script> <buffer> <expr> <silent> bar isbar call assert_equal("is<F4>foo", maparg('foo<C-V>')) - call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'foo<C-V>', + call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'foo<C-V>', \ 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': sid, 'lnum': lnum + 1, \ 'rhs': 'is<F4>foo', 'buffer': 0}, \ maparg('foo<C-V>', '', 0, 1)) - call assert_equal({'silent': 1, 'noremap': 1, 'lhs': 'bar', 'mode': 'v', + call assert_equal({'silent': 1, 'noremap': 1, 'script': 1, 'lhs': 'bar', 'mode': 'v', \ 'nowait': 0, 'expr': 1, 'sid': sid, 'lnum': lnum + 2, \ 'rhs': 'isbar', 'buffer': 1}, \ maparg('bar', '', 0, 1)) let lnum = expand('<sflnum>') map <buffer> <nowait> foo bar - call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'foo', 'mode': ' ', + call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'foo', 'mode': ' ', \ 'nowait': 1, 'expr': 0, 'sid': sid, 'lnum': lnum + 1, 'rhs': 'bar', \ 'buffer': 1}, \ maparg('foo', '', 0, 1)) let lnum = expand('<sflnum>') tmap baz foo - call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'baz', 'mode': 't', + call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'baz', 'mode': 't', \ 'nowait': 0, 'expr': 0, 'sid': sid, 'lnum': lnum + 1, 'rhs': 'foo', \ 'buffer': 0}, \ maparg('baz', 't', 0, 1)) diff --git a/src/nvim/testdir/test_match.vim b/src/nvim/testdir/test_match.vim index 7a1894bc16..09448ca71b 100644 --- a/src/nvim/testdir/test_match.vim +++ b/src/nvim/testdir/test_match.vim @@ -149,6 +149,21 @@ function Test_match() highlight MyGroup3 NONE endfunc +func Test_match_error() + call assert_fails('match Error', 'E475:') + call assert_fails('match Error /', 'E475:') + call assert_fails('4match Error /x/', 'E476:') + call assert_fails('match Error /x/ x', 'E488:') +endfunc + +func Test_matchadd_error() + call assert_fails("call matchadd('GroupDoesNotExist', 'X')", 'E28:') + call assert_fails("call matchadd('Search', '\\(')", 'E475:') + call assert_fails("call matchadd('Search', 'XXX', 1, 123, 1)", 'E715:') + call assert_fails("call matchadd('Error', 'XXX', 1, 3)", 'E798:') + call assert_fails("call matchadd('Error', 'XXX', 1, 0)", 'E799:') +endfunc + func Test_matchaddpos() syntax on set hlsearch @@ -263,6 +278,17 @@ func Test_matchaddpos_using_negative_priority() set hlsearch& endfunc +func Test_matchaddpos_error() + call assert_fails("call matchaddpos('Error', 1)", 'E686:') + call assert_fails("call matchaddpos('Error', [1], 1, 1)", 'E798:') + call assert_fails("call matchaddpos('Error', [1], 1, 2)", 'E798:') + call assert_fails("call matchaddpos('Error', [1], 1, 0)", 'E799:') + call assert_fails("call matchaddpos('Error', [1], 1, 123, 1)", 'E715:') + call assert_fails("call matchaddpos('Error', [1], 1, 5, {'window':12345})", 'E957:') + " Why doesn't the following error have an error code E...? + call assert_fails("call matchaddpos('Error', [{}])", 'E5031:') +endfunc + func OtherWindowCommon() let lines =<< trim END call setline(1, 'Hello Vim world') @@ -288,6 +314,11 @@ func Test_matchdelete_other_window() call delete('XscriptMatchCommon') endfunc +func Test_matchdelete_error() + call assert_fails("call matchdelete(0)", 'E802:') + call assert_fails("call matchdelete(1, -1)", 'E957:') +endfunc + func Test_matchclear_other_window() if !CanRunVimInTerminal() throw 'Skipped: cannot make screendumps' diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 41f1710faf..04a5c62f66 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -42,6 +42,13 @@ function Test_wildchar() set wildchar& endfunction +func Test_wildoptions() + set wildoptions= + set wildoptions+=tagfile + set wildoptions+=tagfile + call assert_equal('tagfile', &wildoptions) +endfunc + function! Test_options() let caught = 'ok' try @@ -554,3 +561,18 @@ func Test_visualbell() set novisualbell set belloff=all endfunc + +" Test for setting option values using v:false and v:true +func Test_opt_boolean() + set number& + set number + call assert_equal(1, &nu) + set nonu + call assert_equal(0, &nu) + let &nu = v:true + call assert_equal(1, &nu) + let &nu = v:false + call assert_equal(0, &nu) + set number& +endfunc + diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index f03c493275..9abaca5957 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -584,3 +584,12 @@ func Test_start_with_tabs() " clean up call StopVimInTerminal(buf) endfunc + +func Test_v_argv() + let out = system(GetVimCommand() . ' -es -V1 -X arg1 --cmd "echo v:argv" --cmd q') + let list = split(out, "', '") + call assert_match('vim', list[0]) + let idx = index(list, 'arg1') + call assert_true(idx > 2) + call assert_equal(['arg1', '--cmd', 'echo v:argv', '--cmd', 'q'']'], list[idx:]) +endfunc diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 2c4d02812b..b4d91a01fc 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -33,6 +33,9 @@ #include "nvim/os/os.h" #include "nvim/os/signal.h" #include "nvim/os/tty.h" +#ifdef WIN32 +# include "nvim/os/os_win_console.h" +#endif #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui_bridge.h" @@ -1015,8 +1018,22 @@ static void tui_mouse_on(UI *ui) { TUIData *data = ui->data; if (!data->mouse_enabled) { +#ifdef WIN32 + // Windows versions with vtp(ENABLE_VIRTUAL_TERMINAL_PROCESSING) and + // no vti(ENABLE_VIRTUAL_TERMINAL_INPUT) will need to use mouse traking of + // libuv. For this reason, vtp (vterm) state of libuv is temporarily + // disabled because the control sequence needs to be processed by libuv + // instead of Windows vtp. + // ref. https://docs.microsoft.com/en-us/windows/console/setconsolemode + flush_buf(ui); + os_set_vtp(false); +#endif unibi_out_ext(ui, data->unibi_ext.enable_mouse); data->mouse_enabled = true; +#ifdef WIN32 + flush_buf(ui); + os_set_vtp(true); +#endif } } @@ -1024,8 +1041,22 @@ static void tui_mouse_off(UI *ui) { TUIData *data = ui->data; if (data->mouse_enabled) { +#ifdef WIN32 + // Windows versions with vtp(ENABLE_VIRTUAL_TERMINAL_PROCESSING) and + // no vti(ENABLE_VIRTUAL_TERMINAL_INPUT) will need to use mouse traking of + // libuv. For this reason, vtp (vterm) state of libuv is temporarily + // disabled because the control sequence needs to be processed by libuv + // instead of Windows vtp. + // ref. https://docs.microsoft.com/en-us/windows/console/setconsolemode + flush_buf(ui); + os_set_vtp(false); +#endif unibi_out_ext(ui, data->unibi_ext.disable_mouse); data->mouse_enabled = false; +#ifdef WIN32 + flush_buf(ui); + os_set_vtp(true); +#endif } } diff --git a/src/nvim/vim.h b/src/nvim/vim.h index c1eea1ab90..832703e83d 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -101,6 +101,7 @@ typedef enum { #define VAR_TYPE_DICT 4 #define VAR_TYPE_FLOAT 5 #define VAR_TYPE_BOOL 6 +#define VAR_TYPE_SPECIAL 7 // values for xp_context when doing command line completion diff --git a/src/tree_sitter/README.md b/src/tree_sitter/README.md new file mode 100644 index 0000000000..20cb35e7c3 --- /dev/null +++ b/src/tree_sitter/README.md @@ -0,0 +1,16 @@ +Tree-sitter vendor runtime +========================== + +This is the vendor runtime code for treesitter. + +The original code can be found [here](https://github.com/tree-sitter/tree-sitter). + +As this code is not ours, if you find any bugs, feel free to open an issue, so that we can +investigate and determine if this should go upstream. + +# Updating + +To update the treesitter runtime, use the `update-ts-runtime.sh` script in the `scripts` directory: +```sh +./scripts/update-ts-runtime.sh <commit you want to update to> +``` diff --git a/src/tree_sitter/alloc.h b/src/tree_sitter/alloc.h index 2229995bd1..d3c6b5eca8 100644 --- a/src/tree_sitter/alloc.h +++ b/src/tree_sitter/alloc.h @@ -51,6 +51,7 @@ static inline void ts_free(void *buffer) { #include <stdlib.h> static inline bool ts_toggle_allocation_recording(bool value) { + (void)value; return false; } diff --git a/src/tree_sitter/language.c b/src/tree_sitter/language.c index a396b4b0b6..c00c49e3c0 100644 --- a/src/tree_sitter/language.c +++ b/src/tree_sitter/language.c @@ -33,8 +33,8 @@ void ts_language_table_entry( assert(symbol < self->token_count); uint32_t action_index = ts_language_lookup(self, state, symbol); const TSParseActionEntry *entry = &self->parse_actions[action_index]; - result->action_count = entry->count; - result->is_reusable = entry->reusable; + result->action_count = entry->entry.count; + result->is_reusable = entry->entry.reusable; result->actions = (const TSParseAction *)(entry + 1); } } diff --git a/src/tree_sitter/language.h b/src/tree_sitter/language.h index f908b4593a..341f0f85af 100644 --- a/src/tree_sitter/language.h +++ b/src/tree_sitter/language.h @@ -93,7 +93,7 @@ static inline TSStateId ts_language_next_state(const TSLanguage *self, if (count > 0) { TSParseAction action = actions[count - 1]; if (action.type == TSParseActionTypeShift) { - return action.params.extra ? state : action.params.state; + return action.params.shift.extra ? state : action.params.shift.state; } } return 0; diff --git a/src/tree_sitter/parser.c b/src/tree_sitter/parser.c index d4b227308b..dd222cd3c4 100644 --- a/src/tree_sitter/parser.c +++ b/src/tree_sitter/parser.c @@ -101,9 +101,10 @@ typedef struct { static const char *ts_string_input_read( void *_self, uint32_t byte, - TSPoint _, + TSPoint pt, uint32_t *length ) { + (void)pt; TSStringInput *self = (TSStringInput *)_self; if (byte >= self->length) { *length = 0; @@ -210,6 +211,7 @@ static ErrorComparison ts_parser__compare_versions( ErrorStatus a, ErrorStatus b ) { + (void)self; if (!a.is_in_error && b.is_in_error) { if (a.cost < b.cost) { return ErrorComparisonTakeLeft; @@ -951,15 +953,15 @@ static bool ts_parser__do_all_potential_reductions( switch (action.type) { case TSParseActionTypeShift: case TSParseActionTypeRecover: - if (!action.params.extra && !action.params.repetition) has_shift_action = true; + if (!action.params.shift.extra && !action.params.shift.repetition) has_shift_action = true; break; case TSParseActionTypeReduce: - if (action.params.child_count > 0) + if (action.params.reduce.child_count > 0) ts_reduce_action_set_add(&self->reduce_actions, (ReduceAction){ - .symbol = action.params.symbol, - .count = action.params.child_count, - .dynamic_precedence = action.params.dynamic_precedence, - .production_id = action.params.production_id, + .symbol = action.params.reduce.symbol, + .count = action.params.reduce.child_count, + .dynamic_precedence = action.params.reduce.dynamic_precedence, + .production_id = action.params.reduce.production_id, }); default: break; @@ -1250,7 +1252,7 @@ static void ts_parser__recover( // be counted in error cost calculations. unsigned n; const TSParseAction *actions = ts_language_actions(self->language, 1, ts_subtree_symbol(lookahead), &n); - if (n > 0 && actions[n - 1].type == TSParseActionTypeShift && actions[n - 1].params.extra) { + if (n > 0 && actions[n - 1].type == TSParseActionTypeShift && actions[n - 1].params.shift.extra) { MutableSubtree mutable_lookahead = ts_subtree_make_mut(&self->tree_pool, lookahead); ts_subtree_set_extra(&mutable_lookahead); lookahead = ts_subtree_from_mut(mutable_lookahead); @@ -1379,9 +1381,9 @@ static bool ts_parser__advance( switch (action.type) { case TSParseActionTypeShift: { - if (action.params.repetition) break; + if (action.params.shift.repetition) break; TSStateId next_state; - if (action.params.extra) { + if (action.params.shift.extra) { // TODO: remove when TREE_SITTER_LANGUAGE_VERSION 9 is out. if (state == ERROR_STATE) continue; @@ -1389,7 +1391,7 @@ static bool ts_parser__advance( next_state = state; LOG("shift_extra"); } else { - next_state = action.params.state; + next_state = action.params.shift.state; LOG("shift state:%u", next_state); } @@ -1398,7 +1400,7 @@ static bool ts_parser__advance( next_state = ts_language_next_state(self->language, state, ts_subtree_symbol(lookahead)); } - ts_parser__shift(self, version, next_state, lookahead, action.params.extra); + ts_parser__shift(self, version, next_state, lookahead, action.params.shift.extra); if (did_reuse) reusable_node_advance(&self->reusable_node); return true; } @@ -1406,10 +1408,10 @@ static bool ts_parser__advance( case TSParseActionTypeReduce: { bool is_fragile = table_entry.action_count > 1; bool is_extra = lookahead.ptr == NULL; - LOG("reduce sym:%s, child_count:%u", SYM_NAME(action.params.symbol), action.params.child_count); + LOG("reduce sym:%s, child_count:%u", SYM_NAME(action.params.reduce.symbol), action.params.reduce.child_count); StackVersion reduction_version = ts_parser__reduce( - self, version, action.params.symbol, action.params.child_count, - action.params.dynamic_precedence, action.params.production_id, + self, version, action.params.reduce.symbol, action.params.reduce.child_count, + action.params.reduce.dynamic_precedence, action.params.reduce.production_id, is_fragile, is_extra ); if (reduction_version != STACK_VERSION_NONE) { diff --git a/src/tree_sitter/parser.h b/src/tree_sitter/parser.h index 9df91f8c3c..11bf4fc42a 100644 --- a/src/tree_sitter/parser.h +++ b/src/tree_sitter/parser.h @@ -62,13 +62,13 @@ typedef struct { TSStateId state; bool extra : 1; bool repetition : 1; - }; + } shift; struct { TSSymbol symbol; int16_t dynamic_precedence; uint8_t child_count; uint8_t production_id; - }; + } reduce; } params; TSParseActionType type : 4; } TSParseAction; @@ -83,7 +83,7 @@ typedef union { struct { uint8_t count; bool reusable : 1; - }; + } entry; } TSParseActionEntry; struct TSLanguage { @@ -167,22 +167,28 @@ struct TSLanguage { #define ACTIONS(id) id -#define SHIFT(state_value) \ - { \ - { \ - .type = TSParseActionTypeShift, \ - .params = {.state = state_value}, \ - } \ +#define SHIFT(state_value) \ + { \ + { \ + .params = { \ + .shift = { \ + .state = state_value \ + } \ + }, \ + .type = TSParseActionTypeShift \ + } \ } #define SHIFT_REPEAT(state_value) \ { \ { \ - .type = TSParseActionTypeShift, \ .params = { \ - .state = state_value, \ - .repetition = true \ + .shift = { \ + .state = state_value, \ + .repetition = true \ + } \ }, \ + .type = TSParseActionTypeShift \ } \ } @@ -194,20 +200,26 @@ struct TSLanguage { #define SHIFT_EXTRA() \ { \ { \ - .type = TSParseActionTypeShift, \ - .params = {.extra = true} \ + .params = { \ + .shift = { \ + .extra = true \ + } \ + }, \ + .type = TSParseActionTypeShift \ } \ } #define REDUCE(symbol_val, child_count_val, ...) \ { \ { \ - .type = TSParseActionTypeReduce, \ .params = { \ - .symbol = symbol_val, \ - .child_count = child_count_val, \ - __VA_ARGS__ \ - } \ + .reduce = { \ + .symbol = symbol_val, \ + .child_count = child_count_val, \ + __VA_ARGS__ \ + }, \ + }, \ + .type = TSParseActionTypeReduce \ } \ } diff --git a/src/tree_sitter/query.c b/src/tree_sitter/query.c index 92a8006179..59902dee3b 100644 --- a/src/tree_sitter/query.c +++ b/src/tree_sitter/query.c @@ -8,6 +8,13 @@ #include "./unicode.h" #include <wctype.h> +// #define LOG(...) fprintf(stderr, __VA_ARGS__) +#define LOG(...) + +#define MAX_STATE_COUNT 256 +#define MAX_CAPTURE_LIST_COUNT 32 +#define MAX_STEP_CAPTURE_COUNT 3 + /* * Stream - A sequence of unicode characters derived from a UTF8 string. * This struct is used in parsing queries from S-expressions. @@ -19,37 +26,35 @@ typedef struct { uint8_t next_size; } Stream; -#define MAX_STEP_CAPTURE_COUNT 4 - /* * QueryStep - A step in the process of matching a query. Each node within * a query S-expression maps to one of these steps. An entire pattern is * represented as a sequence of these steps. Fields: * * - `symbol` - The grammar symbol to match. A zero value represents the - * wildcard symbol, '*'. + * wildcard symbol, '_'. * - `field` - The field name to match. A zero value means that a field name * was not specified. - * - `capture_id` - An integer representing the name of the capture associated - * with this node in the pattern. A `NONE` value means this node is not - * captured in this pattern. + * - `capture_ids` - An array of integers representing the names of captures + * associated with this node in the pattern, terminated by a `NONE` value. * - `depth` - The depth where this node occurs in the pattern. The root node * of the pattern has depth zero. - * - `repeat_step_index` - If this step is part of a repetition, the index of - * the beginning of the repetition. A `NONE` value means this step is not - * part of a repetition. + * - `alternative_index` - The index of a different query step that serves as + * an alternative to this step. */ typedef struct { TSSymbol symbol; TSFieldId field; uint16_t capture_ids[MAX_STEP_CAPTURE_COUNT]; - uint16_t repeat_step_index; - uint16_t depth: 11; + uint16_t alternative_index; + uint16_t depth; bool contains_captures: 1; bool is_pattern_start: 1; bool is_immediate: 1; - bool is_last: 1; - bool is_repeated: 1; + bool is_last_child: 1; + bool is_pass_through: 1; + bool is_dead_end: 1; + bool alternative_is_immediate: 1; } QueryStep; /* @@ -88,18 +93,35 @@ typedef struct { * QueryState - The state of an in-progress match of a particular pattern * in a query. While executing, a `TSQueryCursor` must keep track of a number * of possible in-progress matches. Each of those possible matches is - * represented as one of these states. + * represented as one of these states. Fields: + * - `id` - A numeric id that is exposed to the public API. This allows the + * caller to remove a given match, preventing any more of its captures + * from being returned. + * - `start_depth` - The depth in the tree where the first step of the state's + * pattern was matched. + * - `pattern_index` - The pattern that the state is matching. + * - `consumed_capture_count` - The number of captures from this match that + * have already been returned. + * - `capture_list_id` - A numeric id that can be used to retrieve the state's + * list of captures from the `CaptureListPool`. + * - `seeking_immediate_match` - A flag that indicates that the state's next + * step must be matched by the very next sibling. This is used when + * processing repetitions. + * - `has_in_progress_alternatives` - A flag that indicates that there is are + * other states that have the same captures as this state, but are at + * different steps in their pattern. This means that in order to obey the + * 'longest-match' rule, this state should not be returned as a match until + * it is clear that there can be no longer match. */ typedef struct { uint32_t id; uint16_t start_depth; - uint16_t pattern_index; uint16_t step_index; - uint16_t consumed_capture_count; - uint16_t repeat_match_count; - uint16_t step_index_on_failure; - uint8_t capture_list_id; - bool seeking_non_match; + uint16_t pattern_index; + uint16_t capture_list_id; + uint16_t consumed_capture_count: 14; + bool seeking_immediate_match: 1; + bool has_in_progress_alternatives: 1; } QueryState; typedef Array(TSQueryCapture) CaptureList; @@ -111,7 +133,8 @@ typedef Array(TSQueryCapture) CaptureList; * are currently in use. */ typedef struct { - CaptureList list[32]; + CaptureList list[MAX_CAPTURE_LIST_COUNT]; + CaptureList empty_list; uint32_t usage_map; } CaptureListPool; @@ -152,14 +175,10 @@ struct TSQueryCursor { }; static const TSQueryError PARENT_DONE = -1; -static const uint8_t PATTERN_DONE_MARKER = UINT8_MAX; +static const uint16_t PATTERN_DONE_MARKER = UINT16_MAX; static const uint16_t NONE = UINT16_MAX; static const TSSymbol WILDCARD_SYMBOL = 0; static const TSSymbol NAMED_WILDCARD_SYMBOL = UINT16_MAX - 1; -static const uint16_t MAX_STATE_COUNT = 32; - -// #define LOG(...) fprintf(stderr, __VA_ARGS__) -#define LOG(...) /********** * Stream @@ -242,24 +261,31 @@ static void stream_scan_identifier(Stream *stream) { static CaptureListPool capture_list_pool_new(void) { return (CaptureListPool) { + .empty_list = array_new(), .usage_map = UINT32_MAX, }; } static void capture_list_pool_reset(CaptureListPool *self) { self->usage_map = UINT32_MAX; - for (unsigned i = 0; i < 32; i++) { + for (unsigned i = 0; i < MAX_CAPTURE_LIST_COUNT; i++) { array_clear(&self->list[i]); } } static void capture_list_pool_delete(CaptureListPool *self) { - for (unsigned i = 0; i < 32; i++) { + for (unsigned i = 0; i < MAX_CAPTURE_LIST_COUNT; i++) { array_delete(&self->list[i]); } } -static CaptureList *capture_list_pool_get(CaptureListPool *self, uint16_t id) { +static const CaptureList *capture_list_pool_get(const CaptureListPool *self, uint16_t id) { + if (id >= MAX_CAPTURE_LIST_COUNT) return &self->empty_list; + return &self->list[id]; +} + +static CaptureList *capture_list_pool_get_mut(CaptureListPool *self, uint16_t id) { + assert(id < MAX_CAPTURE_LIST_COUNT); return &self->list[id]; } @@ -273,12 +299,14 @@ static uint16_t capture_list_pool_acquire(CaptureListPool *self) { // the leading zeros in the usage map. An id of zero corresponds to the // highest-order bit in the bitmask. uint16_t id = count_leading_zeros(self->usage_map); - if (id == 32) return NONE; + if (id >= MAX_CAPTURE_LIST_COUNT) return NONE; self->usage_map &= ~bitmask_for_index(id); + array_clear(&self->list[id]); return id; } static void capture_list_pool_release(CaptureListPool *self, uint16_t id) { + if (id >= MAX_CAPTURE_LIST_COUNT) return; array_clear(&self->list[id]); self->usage_map |= bitmask_for_index(id); } @@ -416,13 +444,15 @@ static QueryStep query_step__new( .symbol = symbol, .depth = depth, .field = 0, - .capture_ids = {NONE, NONE, NONE, NONE}, + .capture_ids = {NONE, NONE, NONE}, + .alternative_index = NONE, .contains_captures = false, - .is_repeated = false, - .is_last = false, + .is_last_child = false, .is_pattern_start = false, + .is_pass_through = false, + .is_dead_end = false, .is_immediate = is_immediate, - .repeat_step_index = NONE, + .alternative_is_immediate = false, }; } @@ -511,13 +541,14 @@ static inline bool ts_query__pattern_map_search( static inline void ts_query__pattern_map_insert( TSQuery *self, TSSymbol symbol, - uint32_t start_step_index + uint32_t start_step_index, + uint32_t pattern_index ) { uint32_t index; ts_query__pattern_map_search(self, symbol, &index); array_insert(&self->pattern_map, index, ((PatternEntry) { .step_index = start_step_index, - .pattern_index = self->pattern_map.size, + .pattern_index = pattern_index, })); } @@ -548,12 +579,22 @@ static TSQueryError ts_query__parse_predicate( TSQuery *self, Stream *stream ) { - if (stream->next == ')') return PARENT_DONE; - if (stream->next != '(') return TSQueryErrorSyntax; - stream_advance(stream); + if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax; + const char *predicate_name = stream->input; + stream_scan_identifier(stream); + uint32_t length = stream->input - predicate_name; + uint16_t id = symbol_table_insert_name( + &self->predicate_values, + predicate_name, + length + ); + array_back(&self->predicates_by_pattern)->length++; + array_push(&self->predicate_steps, ((TSQueryPredicateStep) { + .type = TSQueryPredicateStepTypeString, + .value_id = id, + })); stream_skip_whitespace(stream); - unsigned step_count = 0; for (;;) { if (stream->next == ')') { stream_advance(stream); @@ -658,7 +699,6 @@ static TSQueryError ts_query__parse_predicate( return TSQueryErrorSyntax; } - step_count++; stream_skip_whitespace(stream); } @@ -675,102 +715,195 @@ static TSQueryError ts_query__parse_pattern( uint32_t *capture_count, bool is_immediate ) { - uint16_t starting_step_index = self->steps.size; + uint32_t starting_step_index = self->steps.size; if (stream->next == 0) return TSQueryErrorSyntax; - // Finish the parent S-expression - if (stream->next == ')') { + // Finish the parent S-expression. + if (stream->next == ')' || stream->next == ']') { return PARENT_DONE; } - // Parse a parenthesized node expression - else if (stream->next == '(') { + // An open bracket is the start of an alternation. + else if (stream->next == '[') { stream_advance(stream); stream_skip_whitespace(stream); - // Parse a nested list, which represents a pattern followed by - // zero-or-more predicates. - if (stream->next == '(' && depth == 0) { - TSQueryError e = ts_query__parse_pattern(self, stream, 0, capture_count, is_immediate); - if (e) return e; + // Parse each branch, and add a placeholder step in between the branches. + Array(uint32_t) branch_step_indices = array_new(); + for (;;) { + uint32_t start_index = self->steps.size; + TSQueryError e = ts_query__parse_pattern( + self, + stream, + depth, + capture_count, + is_immediate + ); - // Parse the predicates. - stream_skip_whitespace(stream); + if (e == PARENT_DONE && stream->next == ']' && branch_step_indices.size > 0) { + stream_advance(stream); + break; + } else if (e) { + array_delete(&branch_step_indices); + return e; + } + + array_push(&branch_step_indices, start_index); + array_push(&self->steps, query_step__new(0, depth, false)); + } + (void)array_pop(&self->steps); + + // For all of the branches except for the last one, add the subsequent branch as an + // alternative, and link the end of the branch to the current end of the steps. + for (unsigned i = 0; i < branch_step_indices.size - 1; i++) { + uint32_t step_index = branch_step_indices.contents[i]; + uint32_t next_step_index = branch_step_indices.contents[i + 1]; + QueryStep *start_step = &self->steps.contents[step_index]; + QueryStep *end_step = &self->steps.contents[next_step_index - 1]; + start_step->alternative_index = next_step_index; + end_step->alternative_index = self->steps.size; + end_step->is_dead_end = true; + } + + array_delete(&branch_step_indices); + } + + // An open parenthesis can be the start of three possible constructs: + // * A grouped sequence + // * A predicate + // * A named node + else if (stream->next == '(') { + stream_advance(stream); + stream_skip_whitespace(stream); + + // If this parenthesis is followed by a node, then it represents a grouped sequence. + if (stream->next == '(' || stream->next == '"' || stream->next == '[') { + bool child_is_immediate = false; for (;;) { - TSQueryError e = ts_query__parse_predicate(self, stream); - if (e == PARENT_DONE) { + if (stream->next == '.') { + child_is_immediate = true; stream_advance(stream); stream_skip_whitespace(stream); - return 0; + } + TSQueryError e = ts_query__parse_pattern( + self, + stream, + depth, + capture_count, + child_is_immediate + ); + if (e == PARENT_DONE && stream->next == ')') { + stream_advance(stream); + break; } else if (e) { return e; } + + child_is_immediate = false; } } - TSSymbol symbol; - - // Parse the wildcard symbol - if (stream->next == '*') { - symbol = depth > 0 ? NAMED_WILDCARD_SYMBOL : WILDCARD_SYMBOL; + // A pound character indicates the start of a predicate. + else if (stream->next == '#') { stream_advance(stream); + return ts_query__parse_predicate(self, stream); } - // Parse a normal node name - else if (stream_is_ident_start(stream)) { - const char *node_name = stream->input; - stream_scan_identifier(stream); - uint32_t length = stream->input - node_name; - symbol = ts_language_symbol_for_name( - self->language, - node_name, - length, - true - ); - if (!symbol) { - stream_reset(stream, node_name); - return TSQueryErrorNodeType; - } - } else { - return TSQueryErrorSyntax; - } + // Otherwise, this parenthesis is the start of a named node. + else { + TSSymbol symbol; - // Add a step for the node. - array_push(&self->steps, query_step__new(symbol, depth, is_immediate)); + // Parse the wildcard symbol + if ( + stream->next == '_' || - // Parse the child patterns - stream_skip_whitespace(stream); - bool child_is_immediate = false; - uint16_t child_start_step_index = self->steps.size; - for (;;) { - if (stream->next == '.') { - child_is_immediate = true; + // TODO - remove. + // For temporary backward compatibility, handle '*' as a wildcard. + stream->next == '*' + ) { + symbol = depth > 0 ? NAMED_WILDCARD_SYMBOL : WILDCARD_SYMBOL; stream_advance(stream); - stream_skip_whitespace(stream); } - TSQueryError e = ts_query__parse_pattern( - self, - stream, - depth + 1, - capture_count, - child_is_immediate - ); - if (e == PARENT_DONE) { - if (child_is_immediate) { - self->steps.contents[child_start_step_index].is_last = true; + // Parse a normal node name + else if (stream_is_ident_start(stream)) { + const char *node_name = stream->input; + stream_scan_identifier(stream); + uint32_t length = stream->input - node_name; + + // TODO - remove. + // For temporary backward compatibility, handle predicates without the leading '#' sign. + if (length > 0 && (node_name[length - 1] == '!' || node_name[length - 1] == '?')) { + stream_reset(stream, node_name); + return ts_query__parse_predicate(self, stream); } - stream_advance(stream); - break; - } else if (e) { - return e; + + symbol = ts_language_symbol_for_name( + self->language, + node_name, + length, + true + ); + if (!symbol) { + stream_reset(stream, node_name); + return TSQueryErrorNodeType; + } + } else { + return TSQueryErrorSyntax; } - child_is_immediate = false; + // Add a step for the node. + array_push(&self->steps, query_step__new(symbol, depth, is_immediate)); + + // Parse the child patterns + stream_skip_whitespace(stream); + bool child_is_immediate = false; + uint16_t child_start_step_index = self->steps.size; + for (;;) { + if (stream->next == '.') { + child_is_immediate = true; + stream_advance(stream); + stream_skip_whitespace(stream); + } + + TSQueryError e = ts_query__parse_pattern( + self, + stream, + depth + 1, + capture_count, + child_is_immediate + ); + if (e == PARENT_DONE && stream->next == ')') { + if (child_is_immediate) { + self->steps.contents[child_start_step_index].is_last_child = true; + } + stream_advance(stream); + break; + } else if (e) { + return e; + } + + child_is_immediate = false; + } } } + // Parse a wildcard pattern + else if ( + stream->next == '_' || + + // TODO remove. + // For temporary backward compatibility, handle '*' as a wildcard. + stream->next == '*' + ) { + stream_advance(stream); + stream_skip_whitespace(stream); + + // Add a step that matches any kind of node + array_push(&self->steps, query_step__new(WILDCARD_SYMBOL, depth, is_immediate)); + } + // Parse a double-quoted anonymous leaf node expression else if (stream->next == '"') { stream_advance(stream); @@ -842,15 +975,6 @@ static TSQueryError ts_query__parse_pattern( self->steps.contents[step_index].field = field_id; } - // Parse a wildcard pattern - else if (stream->next == '*') { - stream_advance(stream); - stream_skip_whitespace(stream); - - // Add a step that matches any kind of node - array_push(&self->steps, query_step__new(WILDCARD_SYMBOL, depth, is_immediate)); - } - else { return TSQueryErrorSyntax; } @@ -861,22 +985,54 @@ static TSQueryError ts_query__parse_pattern( for (;;) { QueryStep *step = &self->steps.contents[starting_step_index]; + // Parse the one-or-more operator. if (stream->next == '+') { stream_advance(stream); - step->is_repeated = true; - array_back(&self->steps)->repeat_step_index = starting_step_index; stream_skip_whitespace(stream); + + QueryStep repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false); + repeat_step.alternative_index = starting_step_index; + repeat_step.is_pass_through = true; + repeat_step.alternative_is_immediate = true; + array_push(&self->steps, repeat_step); + } + + // Parse the zero-or-more repetition operator. + else if (stream->next == '*') { + stream_advance(stream); + stream_skip_whitespace(stream); + + QueryStep repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false); + repeat_step.alternative_index = starting_step_index; + repeat_step.is_pass_through = true; + repeat_step.alternative_is_immediate = true; + array_push(&self->steps, repeat_step); + + while (step->alternative_index != NONE) { + step = &self->steps.contents[step->alternative_index]; + } + step->alternative_index = self->steps.size; + } + + // Parse the optional operator. + else if (stream->next == '?') { + stream_advance(stream); + stream_skip_whitespace(stream); + + while (step->alternative_index != NONE) { + step = &self->steps.contents[step->alternative_index]; + } + step->alternative_index = self->steps.size; } // Parse an '@'-prefixed capture pattern else if (stream->next == '@') { stream_advance(stream); - - // Parse the capture name if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax; const char *capture_name = stream->input; stream_scan_identifier(stream); uint32_t length = stream->input - capture_name; + stream_skip_whitespace(stream); // Add the capture id to the first step of the pattern uint16_t capture_id = symbol_table_insert_name( @@ -884,10 +1040,22 @@ static TSQueryError ts_query__parse_pattern( capture_name, length ); - query_step__add_capture(step, capture_id); - (*capture_count)++; - stream_skip_whitespace(stream); + for (;;) { + query_step__add_capture(step, capture_id); + if ( + step->alternative_index != NONE && + step->alternative_index > starting_step_index && + step->alternative_index < self->steps.size + ) { + starting_step_index = step->alternative_index; + step = &self->steps.contents[starting_step_index]; + } else { + break; + } + } + + (*capture_count)++; } // No more suffix modifiers @@ -950,6 +1118,7 @@ TSQuery *ts_query_new( Stream stream = stream_new(source, source_len); stream_skip_whitespace(&stream); while (stream.input < stream.end) { + uint32_t pattern_index = self->predicates_by_pattern.size; uint32_t start_step_index = self->steps.size; uint32_t capture_count = 0; array_push(&self->start_bytes_by_pattern, stream.input - source); @@ -963,6 +1132,7 @@ TSQuery *ts_query_new( // If any pattern could not be parsed, then report the error information // and terminate. if (*error_type) { + if (*error_type == PARENT_DONE) *error_type = TSQueryErrorSyntax; *error_offset = stream.input - source; ts_query_delete(self); return NULL; @@ -980,14 +1150,21 @@ TSQuery *ts_query_new( } // Maintain a map that can look up patterns for a given root symbol. - self->steps.contents[start_step_index].is_pattern_start = true; - ts_query__pattern_map_insert( - self, - self->steps.contents[start_step_index].symbol, - start_step_index - ); - if (self->steps.contents[start_step_index].symbol == WILDCARD_SYMBOL) { - self->wildcard_root_pattern_count++; + for (;;) { + QueryStep *step = &self->steps.contents[start_step_index]; + step->is_pattern_start = true; + ts_query__pattern_map_insert(self, step->symbol, start_step_index, pattern_index); + if (step->symbol == WILDCARD_SYMBOL) { + self->wildcard_root_pattern_count++; + } + + // If there are alternatives or options at the root of the pattern, + // then add multiple entries to the pattern map. + if (step->alternative_index != NONE) { + start_step_index = step->alternative_index; + } else { + break; + } } } @@ -1090,7 +1267,7 @@ void ts_query_disable_pattern( * QueryCursor ***************/ -TSQueryCursor *ts_query_cursor_new() { +TSQueryCursor *ts_query_cursor_new(void) { TSQueryCursor *self = ts_malloc(sizeof(TSQueryCursor)); *self = (TSQueryCursor) { .ascending = false, @@ -1103,7 +1280,7 @@ TSQueryCursor *ts_query_cursor_new() { .end_point = POINT_MAX, }; array_reserve(&self->states, MAX_STATE_COUNT); - array_reserve(&self->finished_states, MAX_STATE_COUNT); + array_reserve(&self->finished_states, MAX_CAPTURE_LIST_COUNT); return self; } @@ -1165,6 +1342,9 @@ static bool ts_query_cursor__first_in_progress_capture( uint32_t *pattern_index ) { bool result = false; + *state_index = UINT32_MAX; + *byte_offset = UINT32_MAX; + *pattern_index = UINT32_MAX; for (unsigned i = 0; i < self->states.size; i++) { const QueryState *state = &self->states.contents[i]; const CaptureList *captures = capture_list_pool_get( @@ -1176,10 +1356,7 @@ static bool ts_query_cursor__first_in_progress_capture( if ( !result || capture_byte < *byte_offset || - ( - capture_byte == *byte_offset && - state->pattern_index < *pattern_index - ) + (capture_byte == *byte_offset && state->pattern_index < *pattern_index) ) { result = true; *state_index = i; @@ -1191,85 +1368,138 @@ static bool ts_query_cursor__first_in_progress_capture( return result; } -static bool ts_query__cursor_add_state( - TSQueryCursor *self, - const PatternEntry *pattern -) { - QueryStep *step = &self->query->steps.contents[pattern->step_index]; - - // If this pattern begins with a repetition, then avoid creating - // new states after already matching the repetition one or more times. - // The query should only one match for the repetition - the one that - // started the earliest. - if (step->is_repeated) { - for (unsigned i = 0; i < self->states.size; i++) { - QueryState *state = &self->states.contents[i]; - if (state->step_index == pattern->step_index) return true; - } +// Determine which node is first in a depth-first traversal +int ts_query_cursor__compare_nodes(TSNode left, TSNode right) { + if (left.id != right.id) { + uint32_t left_start = ts_node_start_byte(left); + uint32_t right_start = ts_node_start_byte(right); + if (left_start < right_start) return -1; + if (left_start > right_start) return 1; + uint32_t left_node_count = ts_node_end_byte(left); + uint32_t right_node_count = ts_node_end_byte(right); + if (left_node_count > right_node_count) return -1; + if (left_node_count < right_node_count) return 1; } + return 0; +} - uint32_t list_id = capture_list_pool_acquire(&self->capture_list_pool); - - // If there are no capture lists left in the pool, then terminate whichever - // state has captured the earliest node in the document, and steal its - // capture list. - if (list_id == NONE) { - uint32_t state_index, byte_offset, pattern_index; - if (ts_query_cursor__first_in_progress_capture( - self, - &state_index, - &byte_offset, - &pattern_index - )) { - LOG( - " abandon state. index:%u, pattern:%u, offset:%u.\n", - state_index, pattern_index, byte_offset - ); - list_id = self->states.contents[state_index].capture_list_id; - array_erase(&self->states, state_index); +// Determine if either state contains a superset of the other state's captures. +void ts_query_cursor__compare_captures( + TSQueryCursor *self, + QueryState *left_state, + QueryState *right_state, + bool *left_contains_right, + bool *right_contains_left +) { + const CaptureList *left_captures = capture_list_pool_get( + &self->capture_list_pool, + left_state->capture_list_id + ); + const CaptureList *right_captures = capture_list_pool_get( + &self->capture_list_pool, + right_state->capture_list_id + ); + *left_contains_right = true; + *right_contains_left = true; + unsigned i = 0, j = 0; + for (;;) { + if (i < left_captures->size) { + if (j < right_captures->size) { + TSQueryCapture *left = &left_captures->contents[i]; + TSQueryCapture *right = &right_captures->contents[j]; + if (left->node.id == right->node.id && left->index == right->index) { + i++; + j++; + } else { + switch (ts_query_cursor__compare_nodes(left->node, right->node)) { + case -1: + *right_contains_left = false; + i++; + break; + case 1: + *left_contains_right = false; + j++; + break; + default: + *right_contains_left = false; + *left_contains_right = false; + i++; + j++; + break; + } + } + } else { + *right_contains_left = false; + break; + } } else { - LOG(" too many finished states.\n"); - return false; + if (j < right_captures->size) { + *left_contains_right = false; + } + break; } } +} +static bool ts_query_cursor__add_state( + TSQueryCursor *self, + const PatternEntry *pattern +) { + if (self->states.size >= MAX_STATE_COUNT) { + LOG(" too many states"); + return false; + } LOG( " start state. pattern:%u, step:%u\n", pattern->pattern_index, pattern->step_index ); + QueryStep *step = &self->query->steps.contents[pattern->step_index]; array_push(&self->states, ((QueryState) { - .capture_list_id = list_id, + .capture_list_id = NONE, .step_index = pattern->step_index, .pattern_index = pattern->pattern_index, .start_depth = self->depth - step->depth, .consumed_capture_count = 0, - .repeat_match_count = 0, - .step_index_on_failure = NONE, - .seeking_non_match = false, + .seeking_immediate_match = false, })); return true; } +// Duplicate the given state and insert the newly-created state immediately after +// the given state in the `states` array. static QueryState *ts_query__cursor_copy_state( TSQueryCursor *self, const QueryState *state ) { - uint32_t new_list_id = capture_list_pool_acquire(&self->capture_list_pool); - if (new_list_id == NONE) return NULL; - array_push(&self->states, *state); - QueryState *new_state = array_back(&self->states); - new_state->capture_list_id = new_list_id; - CaptureList *old_captures = capture_list_pool_get( - &self->capture_list_pool, - state->capture_list_id - ); - CaptureList *new_captures = capture_list_pool_get( - &self->capture_list_pool, - new_list_id - ); - array_push_all(new_captures, old_captures); - return new_state; + if (self->states.size >= MAX_STATE_COUNT) { + LOG(" too many states"); + return NULL; + } + + // If the state has captures, copy its capture list. + QueryState copy = *state; + copy.capture_list_id = state->capture_list_id; + if (state->capture_list_id != NONE) { + copy.capture_list_id = capture_list_pool_acquire(&self->capture_list_pool); + if (copy.capture_list_id == NONE) { + LOG(" too many capture lists"); + return NULL; + } + const CaptureList *old_captures = capture_list_pool_get( + &self->capture_list_pool, + state->capture_list_id + ); + CaptureList *new_captures = capture_list_pool_get_mut( + &self->capture_list_pool, + copy.capture_list_id + ); + array_push_all(new_captures, old_captures); + } + + uint32_t index = (state - self->states.contents) + 1; + array_insert(&self->states, index, copy); + return &self->states.contents[index]; } // Walk the tree, processing patterns until at least one pattern finishes, @@ -1281,56 +1511,62 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { if (self->ascending) { LOG("leave node. type:%s\n", ts_node_type(ts_tree_cursor_current_node(&self->cursor))); - // When leaving a node, remove any unfinished states whose next step - // needed to match something within that node. + // Leave this node by stepping to its next sibling or to its parent. + bool did_move = true; + if (ts_tree_cursor_goto_next_sibling(&self->cursor)) { + self->ascending = false; + } else if (ts_tree_cursor_goto_parent(&self->cursor)) { + self->depth--; + } else { + did_move = false; + } + + // After leaving a node, remove any states that cannot make further progress. uint32_t deleted_count = 0; for (unsigned i = 0, n = self->states.size; i < n; i++) { QueryState *state = &self->states.contents[i]; QueryStep *step = &self->query->steps.contents[state->step_index]; - if ((uint32_t)state->start_depth + (uint32_t)step->depth > self->depth) { + // If a state completed its pattern inside of this node, but was deferred from finishing + // in order to search for longer matches, mark it as finished. + if (step->depth == PATTERN_DONE_MARKER) { + if (state->start_depth > self->depth || !did_move) { + LOG(" finish pattern %u\n", state->pattern_index); + state->id = self->next_state_id++; + array_push(&self->finished_states, *state); + deleted_count++; + continue; + } + } + + // If a state needed to match something within this node, then remove that state + // as it has failed to match. + else if ((uint32_t)state->start_depth + (uint32_t)step->depth > self->depth) { LOG( " failed to match. pattern:%u, step:%u\n", state->pattern_index, state->step_index ); - capture_list_pool_release( &self->capture_list_pool, state->capture_list_id ); deleted_count++; - } else if (deleted_count > 0) { + continue; + } + + if (deleted_count > 0) { self->states.contents[i - deleted_count] = *state; } } - self->states.size -= deleted_count; - if (ts_tree_cursor_goto_next_sibling(&self->cursor)) { - self->ascending = false; - } else if (ts_tree_cursor_goto_parent(&self->cursor)) { - self->depth--; - } else { + if (!did_move) { return self->finished_states.size > 0; } } else { - bool has_later_siblings; - bool can_have_later_siblings_with_this_field; - TSFieldId field_id = ts_tree_cursor_current_status( - &self->cursor, - &has_later_siblings, - &can_have_later_siblings_with_this_field - ); + // If this node is before the selected range, then avoid descending into it. TSNode node = ts_tree_cursor_current_node(&self->cursor); - TSSymbol symbol = ts_node_symbol(node); - bool is_named = ts_node_is_named(node); - if (symbol != ts_builtin_sym_error && self->query->symbol_map) { - symbol = self->query->symbol_map[symbol]; - } - - // If this node is before the selected range, then avoid descending - // into it. if ( ts_node_end_byte(node) <= self->start_byte || point_lte(ts_node_end_point(node), self->start_point) @@ -1347,18 +1583,26 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { point_lte(self->end_point, ts_node_start_point(node)) ) return false; + // Get the properties of the current node. + TSSymbol symbol = ts_node_symbol(node); + bool is_named = ts_node_is_named(node); + if (symbol != ts_builtin_sym_error && self->query->symbol_map) { + symbol = self->query->symbol_map[symbol]; + } + bool can_have_later_siblings; + bool can_have_later_siblings_with_this_field; + TSFieldId field_id = ts_tree_cursor_current_status( + &self->cursor, + &can_have_later_siblings, + &can_have_later_siblings_with_this_field + ); LOG( - "enter node. " - "type:%s, field:%s, row:%u state_count:%u, " - "finished_state_count:%u, has_later_siblings:%d, " - "can_have_later_siblings_with_this_field:%d\n", + "enter node. type:%s, field:%s, row:%u state_count:%u, finished_state_count:%u\n", ts_node_type(node), ts_language_field_name_for_id(self->query->language, field_id), ts_node_start_point(node).row, self->states.size, - self->finished_states.size, - has_later_siblings, - can_have_later_siblings_with_this_field + self->finished_states.size ); // Add new states for any patterns whose root node is a wildcard. @@ -1369,7 +1613,7 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { // If this node matches the first step of the pattern, then add a new // state at the start of this pattern. if (step->field && field_id != step->field) continue; - if (!ts_query__cursor_add_state(self, pattern)) break; + if (!ts_query_cursor__add_state(self, pattern)) break; } // Add new states for any patterns whose root node matches this node. @@ -1381,7 +1625,7 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { // If this node matches the first step of the pattern, then add a new // state at the start of this pattern. if (step->field && field_id != step->field) continue; - if (!ts_query__cursor_add_state(self, pattern)) break; + if (!ts_query_cursor__add_state(self, pattern)) break; // Advance to the next pattern whose root node matches this node. i++; @@ -1392,9 +1636,11 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { } // Update all of the in-progress states with current node. - for (unsigned i = 0, n = self->states.size; i < n; i++) { + for (unsigned i = 0, copy_count = 0; i < self->states.size; i += 1 + copy_count) { QueryState *state = &self->states.contents[i]; QueryStep *step = &self->query->steps.contents[state->step_index]; + state->has_in_progress_alternatives = false; + copy_count = 0; // Check that the node matches all of the criteria for the next // step of the pattern. @@ -1407,11 +1653,11 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { step->symbol == symbol || step->symbol == WILDCARD_SYMBOL || (step->symbol == NAMED_WILDCARD_SYMBOL && is_named); - bool later_sibling_can_match = has_later_siblings; - if (step->is_immediate && is_named) { + bool later_sibling_can_match = can_have_later_siblings; + if ((step->is_immediate && is_named) || state->seeking_immediate_match) { later_sibling_can_match = false; } - if (step->is_last && has_later_siblings) { + if (step->is_last_child && can_have_later_siblings) { node_does_match = false; } if (step->field) { @@ -1424,25 +1670,8 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { } } + // Remove states immediately if it is ever clear that they cannot match. if (!node_does_match) { - // If this QueryState has processed a repeating sequence, and that repeating - // sequence has ended, move on to the *next* step of this state's pattern. - if ( - state->step_index_on_failure != NONE && - (!later_sibling_can_match || step->is_repeated) - ) { - LOG( - " finish repetition state. pattern:%u, step:%u\n", - state->pattern_index, - state->step_index - ); - state->step_index = state->step_index_on_failure; - state->step_index_on_failure = NONE; - state->repeat_match_count = 0; - i--; - continue; - } - if (!later_sibling_can_match) { LOG( " discard state. pattern:%u, step:%u\n", @@ -1455,115 +1684,201 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) { ); array_erase(&self->states, i); i--; - n--; } - - state->seeking_non_match = false; continue; } - // The `seeking_non_match` flag indicates that a previous QueryState - // has already begun processing this repeating sequence, so that *this* - // QueryState should not begin matching until a separate repeating sequence - // is found. - if (state->seeking_non_match) continue; - - // Some patterns can match their root node in multiple ways, - // capturing different children. If this pattern step could match - // later children within the same parent, then this query state - // cannot simply be updated in place. It must be split into two - // states: one that matches this node, and one which skips over - // this node, to preserve the possibility of matching later - // siblings. - QueryState *next_state = state; + // Some patterns can match their root node in multiple ways, capturing different + // children. If this pattern step could match later children within the same + // parent, then this query state cannot simply be updated in place. It must be + // split into two states: one that matches this node, and one which skips over + // this node, to preserve the possibility of matching later siblings. if ( - !step->is_pattern_start && - step->contains_captures && later_sibling_can_match && - state->repeat_match_count == 0 + !step->is_pattern_start && + step->contains_captures ) { - QueryState *copy = ts_query__cursor_copy_state(self, state); + if (ts_query__cursor_copy_state(self, state)) { + LOG( + " split state for capture. pattern:%u, step:%u\n", + state->pattern_index, + state->step_index + ); + copy_count++; + } + } - // The QueryState that matched this node has begun matching a repeating - // sequence. The QueryState that *skipped* this node should not start - // matching later elements of the same repeating sequence. - if (step->is_repeated) { - state->seeking_non_match = true; + // If the current node is captured in this pattern, add it to the capture list. + // For the first capture in a pattern, lazily acquire a capture list. + if (step->capture_ids[0] != NONE) { + if (state->capture_list_id == NONE) { + state->capture_list_id = capture_list_pool_acquire(&self->capture_list_pool); + + // If there are no capture lists left in the pool, then terminate whichever + // state has captured the earliest node in the document, and steal its + // capture list. + if (state->capture_list_id == NONE) { + uint32_t state_index, byte_offset, pattern_index; + if (ts_query_cursor__first_in_progress_capture( + self, + &state_index, + &byte_offset, + &pattern_index + )) { + LOG( + " abandon state. index:%u, pattern:%u, offset:%u.\n", + state_index, pattern_index, byte_offset + ); + state->capture_list_id = self->states.contents[state_index].capture_list_id; + array_erase(&self->states, state_index); + if (state_index < i) { + i--; + state--; + } + } else { + LOG(" too many finished states.\n"); + array_erase(&self->states, i); + i--; + continue; + } + } } - if (copy) { + CaptureList *capture_list = capture_list_pool_get_mut( + &self->capture_list_pool, + state->capture_list_id + ); + for (unsigned j = 0; j < MAX_STEP_CAPTURE_COUNT; j++) { + uint16_t capture_id = step->capture_ids[j]; + if (step->capture_ids[j] == NONE) break; + array_push(capture_list, ((TSQueryCapture) { node, capture_id })); LOG( - " split state. pattern:%u, step:%u\n", - copy->pattern_index, - copy->step_index + " capture node. pattern:%u, capture_id:%u, capture_count:%u\n", + state->pattern_index, + capture_id, + capture_list->size ); - next_state = copy; - } else { - LOG(" cannot split state.\n"); } } - // If the current node is captured in this pattern, add it to the - // capture list. - for (unsigned j = 0; j < MAX_STEP_CAPTURE_COUNT; j++) { - uint16_t capture_id = step->capture_ids[j]; - if (step->capture_ids[j] == NONE) break; - CaptureList *capture_list = capture_list_pool_get( - &self->capture_list_pool, - next_state->capture_list_id - ); - array_push(capture_list, ((TSQueryCapture) { - node, - capture_id - })); - LOG( - " capture node. pattern:%u, capture_id:%u, capture_count:%u\n", - next_state->pattern_index, - capture_id, - capture_list->size - ); + // Advance this state to the next step of its pattern. + state->step_index++; + state->seeking_immediate_match = false; + LOG( + " advance state. pattern:%u, step:%u\n", + state->pattern_index, + state->step_index + ); + + // If this state's next step has an 'alternative' step (the step is either optional, + // or is the end of a repetition), then copy the state in order to pursue both + // alternatives. The alternative step itself may have an alternative, so this is + // an interative process. + unsigned end_index = i + 1; + for (unsigned j = i; j < end_index; j++) { + QueryState *state = &self->states.contents[j]; + QueryStep *next_step = &self->query->steps.contents[state->step_index]; + if (next_step->alternative_index != NONE) { + if (next_step->is_dead_end) { + state->step_index = next_step->alternative_index; + j--; + continue; + } + + QueryState *copy = ts_query__cursor_copy_state(self, state); + if (next_step->is_pass_through) { + state->step_index++; + j--; + } + if (copy) { + copy_count++; + end_index++; + copy->step_index = next_step->alternative_index; + if (next_step->alternative_is_immediate) { + copy->seeking_immediate_match = true; + } + LOG( + " split state for branch. pattern:%u, step:%u, step:%u, immediate:%d\n", + copy->pattern_index, + state->step_index, + copy->step_index, + copy->seeking_immediate_match + ); + } + } } + } - // If this is the end of a repetition, then jump back to the beginning - // of that repetition. - if (step->repeat_step_index != NONE) { - next_state->step_index_on_failure = next_state->step_index + 1; - next_state->step_index = step->repeat_step_index; - next_state->repeat_match_count++; - LOG( - " continue repeat. pattern:%u, match_count:%u\n", - next_state->pattern_index, - next_state->repeat_match_count - ); - } else { - next_state->step_index++; - LOG( - " advance state. pattern:%u, step:%u\n", - next_state->pattern_index, - next_state->step_index - ); + for (unsigned i = 0; i < self->states.size; i++) { + QueryState *state = &self->states.contents[i]; + bool did_remove = false; - QueryStep *next_step = step + 1; + // Enfore the longest-match criteria. When a query pattern contains optional or + // repeated nodes, this is necesssary to avoid multiple redundant states, where + // one state has a strict subset of another state's captures. + for (unsigned j = i + 1; j < self->states.size; j++) { + QueryState *other_state = &self->states.contents[j]; + if ( + state->pattern_index == other_state->pattern_index && + state->start_depth == other_state->start_depth + ) { + bool left_contains_right, right_contains_left; + ts_query_cursor__compare_captures( + self, + state, + other_state, + &left_contains_right, + &right_contains_left + ); + if (left_contains_right) { + if (state->step_index == other_state->step_index) { + LOG( + " drop shorter state. pattern: %u, step_index: %u\n", + state->pattern_index, + state->step_index + ); + capture_list_pool_release(&self->capture_list_pool, other_state->capture_list_id); + array_erase(&self->states, j); + j--; + continue; + } + other_state->has_in_progress_alternatives = true; + } + if (right_contains_left) { + if (state->step_index == other_state->step_index) { + LOG( + " drop shorter state. pattern: %u, step_index: %u\n", + state->pattern_index, + state->step_index + ); + capture_list_pool_release(&self->capture_list_pool, state->capture_list_id); + array_erase(&self->states, i); + did_remove = true; + break; + } + state->has_in_progress_alternatives = true; + } + } + } - // If the pattern is now done, then remove it from the list of - // in-progress states, and add it to the list of finished states. + // If there the state is at the end of its pattern, remove it from the list + // of in-progress states and add it to the list of finished states. + if (!did_remove) { + QueryStep *next_step = &self->query->steps.contents[state->step_index]; if (next_step->depth == PATTERN_DONE_MARKER) { - LOG(" finish pattern %u\n", next_state->pattern_index); - - next_state->id = self->next_state_id++; - array_push(&self->finished_states, *next_state); - if (next_state == state) { - array_erase(&self->states, i); - i--; - n--; + if (state->has_in_progress_alternatives) { + LOG(" defer finishing pattern %u\n", state->pattern_index); } else { - self->states.size--; + LOG(" finish pattern %u\n", state->pattern_index); + state->id = self->next_state_id++; + array_push(&self->finished_states, *state); + array_erase(&self->states, state - self->states.contents); + i--; } } } } - // Continue descending if possible. if (ts_tree_cursor_goto_first_child(&self->cursor)) { self->depth++; @@ -1589,7 +1904,7 @@ bool ts_query_cursor_next_match( QueryState *state = &self->finished_states.contents[0]; match->id = state->id; match->pattern_index = state->pattern_index; - CaptureList *captures = capture_list_pool_get( + const CaptureList *captures = capture_list_pool_get( &self->capture_list_pool, state->capture_list_id ); @@ -1631,8 +1946,8 @@ bool ts_query_cursor_next_capture( // First, identify the position of the earliest capture in an unfinished // match. For a finished capture to be returned, it must be *before* // this position. - uint32_t first_unfinished_capture_byte = UINT32_MAX; - uint32_t first_unfinished_pattern_index = UINT32_MAX; + uint32_t first_unfinished_capture_byte; + uint32_t first_unfinished_pattern_index; uint32_t first_unfinished_state_index; ts_query_cursor__first_in_progress_capture( self, @@ -1647,7 +1962,7 @@ bool ts_query_cursor_next_capture( uint32_t first_finished_pattern_index = first_unfinished_pattern_index; for (unsigned i = 0; i < self->finished_states.size; i++) { const QueryState *state = &self->finished_states.contents[i]; - CaptureList *captures = capture_list_pool_get( + const CaptureList *captures = capture_list_pool_get( &self->capture_list_pool, state->capture_list_id ); @@ -1685,7 +2000,7 @@ bool ts_query_cursor_next_capture( ]; match->id = state->id; match->pattern_index = state->pattern_index; - CaptureList *captures = capture_list_pool_get( + const CaptureList *captures = capture_list_pool_get( &self->capture_list_pool, state->capture_list_id ); diff --git a/src/tree_sitter/stack.c b/src/tree_sitter/stack.c index ade1577566..6ceee2577f 100644 --- a/src/tree_sitter/stack.c +++ b/src/tree_sitter/stack.c @@ -480,6 +480,7 @@ StackSliceArray ts_stack_pop_count(Stack *self, StackVersion version, uint32_t c } inline StackAction pop_pending_callback(void *payload, const StackIterator *iterator) { + (void)payload; if (iterator->subtree_count >= 1) { if (iterator->is_pending) { return StackActionPop | StackActionStop; @@ -532,6 +533,7 @@ SubtreeArray ts_stack_pop_error(Stack *self, StackVersion version) { } inline StackAction pop_all_callback(void *payload, const StackIterator *iterator) { + (void)payload; return iterator->node->link_count == 0 ? StackActionPop : StackActionNone; } diff --git a/src/tree_sitter/subtree.c b/src/tree_sitter/subtree.c index b98f172339..ef92a32fe4 100644 --- a/src/tree_sitter/subtree.c +++ b/src/tree_sitter/subtree.c @@ -21,7 +21,7 @@ typedef struct { #define TS_MAX_INLINE_TREE_LENGTH UINT8_MAX #define TS_MAX_TREE_POOL_SIZE 32 -static const ExternalScannerState empty_state = {.length = 0, .short_data = {0}}; +static const ExternalScannerState empty_state = {{.short_data = {0}}, .length = 0}; // ExternalScannerState @@ -208,7 +208,7 @@ Subtree ts_subtree_new_leaf( .has_external_tokens = has_external_tokens, .is_missing = false, .is_keyword = is_keyword, - .first_leaf = {.symbol = 0, .parse_state = 0}, + {{.first_leaf = {.symbol = 0, .parse_state = 0}}} }; return (Subtree) {.ptr = data}; } @@ -464,15 +464,17 @@ MutableSubtree ts_subtree_new_node(SubtreePool *pool, TSSymbol symbol, *data = (SubtreeHeapData) { .ref_count = 1, .symbol = symbol, - .production_id = production_id, .visible = metadata.visible, .named = metadata.named, .has_changes = false, .fragile_left = fragile, .fragile_right = fragile, .is_keyword = false, - .node_count = 0, - .first_leaf = {.symbol = 0, .parse_state = 0}, + {{ + .node_count = 0, + .production_id = production_id, + .first_leaf = {.symbol = 0, .parse_state = 0}, + }} }; MutableSubtree result = {.ptr = data}; ts_subtree_set_children(result, children->contents, children->size, language); diff --git a/src/tree_sitter/treesitter_commit_hash.txt b/src/tree_sitter/treesitter_commit_hash.txt new file mode 100644 index 0000000000..bd7fcfbe76 --- /dev/null +++ b/src/tree_sitter/treesitter_commit_hash.txt @@ -0,0 +1 @@ +81d533d2d1b580fdb507accabc91ceddffb5b6f0 |