diff options
Diffstat (limited to 'src')
41 files changed, 1138 insertions, 405 deletions
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 69cefe2247..4b0e680c25 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -133,14 +133,14 @@ typedef struct buffheader buffheader_T; */ struct buffblock { buffblock_T *b_next; // pointer to next buffblock - char_u b_str[1]; // contents (actually longer) + char_u b_str[]; // contents (flexible array) }; /* * header used for the stuff buffer and the redo buffer */ struct buffheader { - buffblock_T bh_first; // first (dummy) block of list + buffblock_T *bh_first; // first block of the list buffblock_T *bh_curr; // buffblock for appending size_t bh_index; // index for reading size_t bh_space; // space in bh_curr for appending diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 8b8d27affd..3a45a8aec7 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -22,19 +22,10 @@ PMap(uint64_t) *channels = NULL; /// 2 is reserved for stderr channel static uint64_t next_chan_id = CHAN_STDERR+1; - -typedef struct { - Channel *chan; - Callback *callback; - const char *type; - // if reader is set, status is ignored. - CallbackReader *reader; - int status; -} ChannelEvent; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "channel.c.generated.h" #endif + /// Teardown the module void channel_teardown(void) { @@ -179,6 +170,7 @@ static Channel *channel_alloc(ChannelStreamType type) } chan->events = multiqueue_new_child(main_loop.events); chan->refcount = 1; + chan->exit_status = -1; chan->streamtype = type; pmap_put(uint64_t)(channels, chan->id, chan); return chan; @@ -234,9 +226,10 @@ void callback_reader_free(CallbackReader *reader) ga_clear(&reader->buffer); } -void callback_reader_start(CallbackReader *reader) +void callback_reader_start(CallbackReader *reader, const char *type) { ga_init(&reader->buffer, sizeof(char *), 32); + reader->type = type; } static void free_channel_event(void **argv) @@ -246,7 +239,7 @@ static void free_channel_event(void **argv) rpc_free(chan); } - callback_reader_free(&chan->on_stdout); + callback_reader_free(&chan->on_data); callback_reader_free(&chan->on_stderr); callback_free(&chan->on_exit); @@ -286,7 +279,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, assert(cwd == NULL || os_isdir_executable(cwd)); Channel *chan = channel_alloc(kChannelStreamProc); - chan->on_stdout = on_stdout; + chan->on_data = on_stdout; chan->on_stderr = on_stderr; chan->on_exit = on_exit; @@ -326,7 +319,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, has_out = true; has_err = false; } else { - has_out = rpc || callback_reader_set(chan->on_stdout); + has_out = rpc || callback_reader_set(chan->on_data); has_err = callback_reader_set(chan->on_stderr); } int status = process_spawn(proc, true, has_out, has_err); @@ -352,13 +345,13 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, rpc_start(chan); } else { if (has_out) { - callback_reader_start(&chan->on_stdout); - rstream_start(&proc->out, on_job_stdout, chan); + callback_reader_start(&chan->on_data, "stdout"); + rstream_start(&proc->out, on_channel_data, chan); } } if (has_err) { - callback_reader_start(&chan->on_stderr); + callback_reader_start(&chan->on_stderr, "stderr"); rstream_init(&proc->err, 0); rstream_start(&proc->err, on_job_stderr, chan); } @@ -402,9 +395,9 @@ uint64_t channel_connect(bool tcp, const char *address, if (rpc) { rpc_start(channel); } else { - channel->on_stdout = on_output; - callback_reader_start(&channel->on_stdout); - rstream_start(&channel->stream.socket, on_socket_output, channel); + channel->on_data = on_output; + callback_reader_start(&channel->on_data, "data"); + rstream_start(&channel->stream.socket, on_channel_data, channel); } end: @@ -452,9 +445,9 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, if (rpc) { rpc_start(channel); } else { - channel->on_stdout = on_output; - callback_reader_start(&channel->on_stdout); - rstream_start(&channel->stream.stdio.in, on_stdio_input, channel); + channel->on_data = on_output; + callback_reader_start(&channel->on_data, "stdin"); + rstream_start(&channel->stream.stdio.in, on_channel_data, channel); } return channel->id; @@ -519,55 +512,22 @@ static inline list_T *buffer_to_tv_list(const char *const buf, const size_t len) return l; } -// vimscript job callbacks must be executed on Nvim main loop -static inline void process_channel_event(Channel *chan, Callback *callback, - const char *type, - CallbackReader *reader, int status) -{ - assert(callback); - ChannelEvent *event_data = xmalloc(sizeof(*event_data)); - event_data->reader = reader; - event_data->status = status; - channel_incref(chan); // Hold on ref to callback - event_data->chan = chan; - event_data->callback = callback; - event_data->type = type; - - multiqueue_put(chan->events, on_channel_event, 1, event_data); -} - -void on_job_stdout(Stream *stream, RBuffer *buf, size_t count, - void *data, bool eof) +void on_channel_data(Stream *stream, RBuffer *buf, size_t count, + void *data, bool eof) { Channel *chan = data; - on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "stdout"); + on_channel_output(stream, chan, buf, count, eof, &chan->on_data); } void on_job_stderr(Stream *stream, RBuffer *buf, size_t count, void *data, bool eof) { Channel *chan = data; - on_channel_output(stream, chan, buf, count, eof, &chan->on_stderr, "stderr"); -} - -static void on_socket_output(Stream *stream, RBuffer *buf, size_t count, - void *data, bool eof) -{ - Channel *chan = data; - on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "data"); -} - -static void on_stdio_input(Stream *stream, RBuffer *buf, size_t count, - void *data, bool eof) -{ - Channel *chan = data; - on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "stdin"); + on_channel_output(stream, chan, buf, count, eof, &chan->on_stderr); } -/// @param type must have static lifetime static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, - size_t count, bool eof, CallbackReader *reader, - const char *type) + size_t count, bool eof, CallbackReader *reader) { // stub variable, to keep reading consistent with the order of events, only // consider the count parameter. @@ -575,57 +535,93 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, char *ptr = rbuffer_read_ptr(buf, &r); if (eof) { - if (reader->buffered) { - if (reader->cb.type != kCallbackNone) { - process_channel_event(chan, &reader->cb, type, reader, 0); - } else if (reader->self) { - if (tv_dict_find(reader->self, type, -1) == NULL) { - list_T *data = buffer_to_tv_list(reader->buffer.ga_data, - (size_t)reader->buffer.ga_len); - tv_dict_add_list(reader->self, type, strlen(type), data); - } else { - // can't display error message now, defer it. - channel_incref(chan); - multiqueue_put(chan->events, on_buffered_error, 2, chan, type); - } - ga_clear(&reader->buffer); - } else { - abort(); - } - } else if (reader->cb.type != kCallbackNone) { - process_channel_event(chan, &reader->cb, type, reader, 0); + reader->eof = true; + } else { + if (chan->term) { + terminal_receive(chan->term, ptr, count); + terminal_flush_output(chan->term); } - return; - } - // The order here matters, the terminal must receive the data first because - // process_channel_event will modify the read buffer(convert NULs into NLs) - if (chan->term) { - terminal_receive(chan->term, ptr, count); - terminal_flush_output(chan->term); + rbuffer_consumed(buf, count); + + if (callback_reader_set(*reader)) { + ga_concat_len(&reader->buffer, ptr, count); + } } - rbuffer_consumed(buf, count); + if (callback_reader_set(*reader)) { + schedule_channel_event(chan); + } +} - if (callback_reader_set(*reader) || reader->buffered) { - // if buffer wasn't consumed, a pending callback is stalled. Aggregate the - // received data and avoid a "burst" of multiple callbacks. - bool buffer_set = reader->buffer.ga_len > 0; - ga_concat_len(&reader->buffer, ptr, count); - if (callback_reader_set(*reader) && !reader->buffered && !buffer_set) { - process_channel_event(chan, &reader->cb, type, reader, 0); +/// schedule the necessary callbacks to be invoked as a deferred event +static void schedule_channel_event(Channel *chan) +{ + if (!chan->callback_scheduled) { + if (!chan->callback_busy) { + multiqueue_put(chan->events, on_channel_event, 1, chan); + channel_incref(chan); } + chan->callback_scheduled = true; } } -static void on_buffered_error(void **args) +static void on_channel_event(void **args) { Channel *chan = (Channel *)args[0]; - const char *stream = (const char *)args[1]; - EMSG3(_(e_streamkey), stream, chan->id); + + chan->callback_busy = true; + chan->callback_scheduled = false; + + int exit_status = chan->exit_status; + channel_reader_callbacks(chan, &chan->on_data); + channel_reader_callbacks(chan, &chan->on_stderr); + if (exit_status > -1) { + channel_callback_call(chan, NULL); + chan->exit_status = -1; + } + + chan->callback_busy = false; + if (chan->callback_scheduled) { + // further callback was deferred to avoid recursion. + multiqueue_put(chan->events, on_channel_event, 1, chan); + channel_incref(chan); + } + channel_decref(chan); } +void channel_reader_callbacks(Channel *chan, CallbackReader *reader) +{ + if (reader->buffered) { + if (reader->eof) { + if (reader->self) { + if (tv_dict_find(reader->self, reader->type, -1) == NULL) { + list_T *data = buffer_to_tv_list(reader->buffer.ga_data, + (size_t)reader->buffer.ga_len); + tv_dict_add_list(reader->self, reader->type, strlen(reader->type), + data); + } else { + EMSG3(_(e_streamkey), reader->type, chan->id); + } + } else { + channel_callback_call(chan, reader); + } + reader->eof = false; + } + } else { + bool is_eof = reader->eof; + if (reader->buffer.ga_len > 0) { + channel_callback_call(chan, reader); + } + // if the stream reached eof, invoke extra callback with no data + if (is_eof) { + channel_callback_call(chan, reader); + reader->eof = false; + } + } +} + static void channel_process_exit_cb(Process *proc, int status, void *data) { Channel *chan = data; @@ -637,45 +633,46 @@ static void channel_process_exit_cb(Process *proc, int status, void *data) // If process did not exit, we only closed the handle of a detached process. bool exited = (status >= 0); - if (exited) { - process_channel_event(chan, &chan->on_exit, "exit", NULL, status); + if (exited && chan->on_exit.type != kCallbackNone) { + schedule_channel_event(chan); + chan->exit_status = status; } channel_decref(chan); } -static void on_channel_event(void **args) +static void channel_callback_call(Channel *chan, CallbackReader *reader) { - ChannelEvent *ev = (ChannelEvent *)args[0]; - + Callback *cb; typval_T argv[4]; argv[0].v_type = VAR_NUMBER; argv[0].v_lock = VAR_UNLOCKED; - argv[0].vval.v_number = (varnumber_T)ev->chan->id; + argv[0].vval.v_number = (varnumber_T)chan->id; - if (ev->reader) { + if (reader) { argv[1].v_type = VAR_LIST; argv[1].v_lock = VAR_UNLOCKED; - argv[1].vval.v_list = buffer_to_tv_list(ev->reader->buffer.ga_data, - (size_t)ev->reader->buffer.ga_len); + argv[1].vval.v_list = buffer_to_tv_list(reader->buffer.ga_data, + (size_t)reader->buffer.ga_len); tv_list_ref(argv[1].vval.v_list); - ga_clear(&ev->reader->buffer); + ga_clear(&reader->buffer); + cb = &reader->cb; + argv[2].vval.v_string = (char_u *)reader->type; } else { argv[1].v_type = VAR_NUMBER; argv[1].v_lock = VAR_UNLOCKED; - argv[1].vval.v_number = ev->status; + argv[1].vval.v_number = chan->exit_status; + cb = &chan->on_exit; + argv[2].vval.v_string = (char_u *)"exit"; } argv[2].v_type = VAR_STRING; argv[2].v_lock = VAR_UNLOCKED; - argv[2].vval.v_string = (uint8_t *)ev->type; typval_T rettv = TV_INITIAL_VALUE; - callback_call(ev->callback, 3, argv, &rettv); + callback_call(cb, 3, argv, &rettv); tv_clear(&rettv); - channel_decref(ev->chan); - xfree(ev); } diff --git a/src/nvim/channel.h b/src/nvim/channel.h index b856d197f1..c733e276be 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -42,13 +42,16 @@ typedef struct { Callback cb; dict_T *self; garray_T buffer; + bool eof; bool buffered; + const char *type; } CallbackReader; #define CALLBACK_READER_INIT ((CallbackReader){ .cb = CALLBACK_NONE, \ .self = NULL, \ .buffer = GA_EMPTY_INIT_VALUE, \ - .buffered = false }) + .buffered = false, \ + .type = NULL }) static inline bool callback_reader_set(CallbackReader reader) { return reader.cb.type != kCallbackNone || reader.self; @@ -73,9 +76,13 @@ struct Channel { RpcState rpc; Terminal *term; - CallbackReader on_stdout; + CallbackReader on_data; CallbackReader on_stderr; Callback on_exit; + int exit_status; + + bool callback_busy; + bool callback_scheduled; }; EXTERN PMap(uint64_t) *channels; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 9af003f140..8040109685 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3838,11 +3838,14 @@ static int ins_compl_get_exp(pos_T *ini) e_cpt = (compl_cont_status & CONT_LOCAL) ? (char_u *)"." : curbuf->b_p_cpt; last_match_pos = first_match_pos = *ini; + } else if (ins_buf != curbuf && !buf_valid(ins_buf)) { + ins_buf = curbuf; // In case the buffer was wiped out. } compl_old_match = compl_curr_match; // remember the last current match pos = (compl_direction == FORWARD) ? &last_match_pos : &first_match_pos; - /* For ^N/^P loop over all the flags/windows/buffers in 'complete' */ + + // For ^N/^P loop over all the flags/windows/buffers in 'complete' for (;; ) { found_new_match = FAIL; set_match_pos = FALSE; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1d34af2230..cd2888883b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -176,6 +176,7 @@ static char *e_funcref = N_("E718: Funcref required"); static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); static char *e_nofunc = N_("E130: Unknown function: %s"); static char *e_illvar = N_("E461: Illegal variable name: %s"); +static char *e_cannot_mod = N_("E995: Cannot modify existing variable"); static const char *e_readonlyvar = N_( "E46: Cannot change read-only variable \"%.*s\""); @@ -776,10 +777,11 @@ var_redir_start( did_emsg = FALSE; tv.v_type = VAR_STRING; tv.vval.v_string = (char_u *)""; - if (append) - set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"."); - else - set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"="); + if (append) { + set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"."); + } else { + set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"="); + } clear_lval(redir_lval); err = did_emsg; did_emsg |= save_emsg; @@ -837,7 +839,7 @@ void var_redir_stop(void) redir_endp = (char_u *)get_lval(redir_varname, NULL, redir_lval, false, false, 0, FNE_CHECK_START); if (redir_endp != NULL && redir_lval->ll_name != NULL) { - set_var_lval(redir_lval, redir_endp, &tv, false, (char_u *)"."); + set_var_lval(redir_lval, redir_endp, &tv, false, false, (char_u *)"."); } clear_lval(redir_lval); } @@ -955,6 +957,88 @@ eval_to_bool( return retval; } +// Call eval1() and give an error message if not done at a lower level. +static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + const int did_emsg_before = did_emsg; + const int called_emsg_before = called_emsg; + + const int ret = eval1(arg, rettv, evaluate); + if (ret == FAIL) { + // Report the invalid expression unless the expression evaluation has + // been cancelled due to an aborting error, an interrupt, or an + // exception, or we already gave a more specific error. + // Also check called_emsg for when using assert_fails(). + if (!aborting() + && did_emsg == did_emsg_before + && called_emsg == called_emsg_before) { + emsgf(_(e_invexpr2), arg); + } + } + return ret; +} + +static int eval_expr_typval(const typval_T *expr, typval_T *argv, + int argc, typval_T *rettv) + FUNC_ATTR_NONNULL_ARG(1, 2, 4) +{ + int dummy; + + if (expr->v_type == VAR_FUNC) { + const char_u *const s = expr->vval.v_string; + if (s == NULL || *s == NUL) { + return FAIL; + } + if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL, + 0L, 0L, &dummy, true, NULL, NULL) == FAIL) { + return FAIL; + } + } else if (expr->v_type == VAR_PARTIAL) { + partial_T *const partial = expr->vval.v_partial; + const char_u *const s = partial_name(partial); + if (s == NULL || *s == NUL) { + return FAIL; + } + if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL, + 0L, 0L, &dummy, true, partial, NULL) == FAIL) { + return FAIL; + } + } else { + char buf[NUMBUFLEN]; + char_u *s = (char_u *)tv_get_string_buf_chk(expr, buf); + if (s == NULL) { + return FAIL; + } + s = skipwhite(s); + if (eval1_emsg(&s, rettv, true) == FAIL) { + return FAIL; + } + if (*s != NUL) { // check for trailing chars after expr + tv_clear(rettv); + emsgf(_(e_invexpr2), s); + return FAIL; + } + } + return OK; +} + +/// Like eval_to_bool() but using a typval_T instead of a string. +/// Works for string, funcref and partial. +static bool eval_expr_to_bool(const typval_T *expr, bool *error) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + typval_T argv, rettv; + + if (eval_expr_typval(expr, &argv, 0, &rettv) == FAIL) { + *error = true; + return false; + } + const bool res = (tv_get_number_chk(&rettv, error) != 0); + tv_clear(&rettv); + return res; +} + /// Top level evaluation function, returning a string /// /// @param[in] arg String to evaluate. @@ -1436,21 +1520,33 @@ int eval_foldexpr(char_u *arg, int *cp) return (int)retval; } -/* - * ":let" list all variable values - * ":let var1 var2" list variable values - * ":let var = expr" assignment command. - * ":let var += expr" assignment command. - * ":let var -= expr" assignment command. - * ":let var *= expr" assignment command. - * ":let var /= expr" assignment command. - * ":let var %= expr" assignment command. - * ":let var .= expr" assignment command. - * ":let var ..= expr" assignment command. - * ":let [var1, var2] = expr" unpack list. - */ +// ":cons[t] var = expr1" define constant +// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list +// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list +void ex_const(exarg_T *eap) +{ + ex_let_const(eap, true); +} + +// ":let" list all variable values +// ":let var1 var2" list variable values +// ":let var = expr" assignment command. +// ":let var += expr" assignment command. +// ":let var -= expr" assignment command. +// ":let var *= expr" assignment command. +// ":let var /= expr" assignment command. +// ":let var %= expr" assignment command. +// ":let var .= expr" assignment command. +// ":let var ..= expr" assignment command. +// ":let [var1, var2] = expr" unpack list. +// ":let [name, ..., ; lastname] = expr" unpack list. void ex_let(exarg_T *eap) { + ex_let_const(eap, false); +} + +static void ex_let_const(exarg_T *eap, const bool is_const) +{ char_u *arg = eap->arg; char_u *expr = NULL; typval_T rettv; @@ -1512,7 +1608,8 @@ void ex_let(exarg_T *eap) } emsg_skip--; } else if (i != FAIL) { - (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, op); + (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, + is_const, op); tv_clear(&rettv); } } @@ -1530,9 +1627,10 @@ static int ex_let_vars( char_u *arg_start, typval_T *tv, - int copy, /* copy values from "tv", don't move */ - int semicolon, /* from skip_var_list() */ - int var_count, /* from skip_var_list() */ + int copy, // copy values from "tv", don't move + int semicolon, // from skip_var_list() + int var_count, // from skip_var_list() + int is_const, // lock variables for :const char_u *nextchars ) { @@ -1543,8 +1641,9 @@ ex_let_vars( /* * ":let var = expr" or ":for var in list" */ - if (ex_let_one(arg, tv, copy, nextchars, nextchars) == NULL) + if (ex_let_one(arg, tv, copy, is_const, nextchars, nextchars) == NULL) { return FAIL; + } return OK; } @@ -1572,8 +1671,8 @@ ex_let_vars( size_t rest_len = tv_list_len(l); while (*arg != ']') { arg = skipwhite(arg + 1); - arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", - nextchars); + arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, is_const, + (const char_u *)",;]", nextchars); if (arg == NULL) { return FAIL; } @@ -1595,7 +1694,7 @@ ex_let_vars( ltv.vval.v_list = rest_list; tv_list_ref(rest_list); - arg = ex_let_one(skipwhite(arg + 1), <v, false, + arg = ex_let_one(skipwhite(arg + 1), <v, false, is_const, (char_u *)"]", nextchars); tv_clear(<v); if (arg == NULL) { @@ -1851,8 +1950,8 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) /// @return a pointer to the char just after the var name or NULL in case of /// error. static char_u *ex_let_one(char_u *arg, typval_T *const tv, - const bool copy, const char_u *const endchars, - const char_u *const op) + const bool copy, const bool is_const, + const char_u *const endchars, const char_u *const op) FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT { char_u *arg_end = NULL; @@ -1864,6 +1963,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, * ":let $VAR = expr": Set environment variable. */ if (*arg == '$') { + if (is_const) { + EMSG(_("E996: Cannot lock an environment variable")); + return NULL; + } // Find the end of the name. arg++; char *name = (char *)arg; @@ -1909,6 +2012,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, // ":let &l:option = expr": Set local option value. // ":let &g:option = expr": Set global option value. } else if (*arg == '&') { + if (is_const) { + EMSG(_("E996: Cannot lock an option")); + return NULL; + } // Find the end of the name. char *const p = (char *)find_option_end((const char **)&arg, &opt_flags); if (p == NULL @@ -1959,6 +2066,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, } // ":let @r = expr": Set register contents. } else if (*arg == '@') { + if (is_const) { + EMSG(_("E996: Cannot lock a register")); + return NULL; + } arg++; if (op != NULL && vim_strchr((char_u *)"+-*/%", *op) != NULL) { emsgf(_(e_letwrong), op); @@ -1998,7 +2109,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) { EMSG(_(e_letunexp)); } else { - set_var_lval(&lv, p, tv, copy, op); + set_var_lval(&lv, p, tv, copy, is_const, op); arg_end = p; } } @@ -2363,7 +2474,7 @@ static void clear_lval(lval_T *lp) * "%" for "%=", "." for ".=" or "=" for "=". */ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, - int copy, const char_u *op) + int copy, const bool is_const, const char_u *op) { int cc; listitem_T *ri; @@ -2375,6 +2486,12 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, if (op != NULL && *op != '=') { typval_T tv; + if (is_const) { + EMSG(_(e_cannot_mod)); + *endp = cc; + return; + } + // handle +=, -=, *=, /=, %= and .= di = NULL; if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name), @@ -2390,7 +2507,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, tv_clear(&tv); } } else { - set_var(lp->ll_name, lp->ll_name_len, rettv, copy); + set_var_const(lp->ll_name, lp->ll_name_len, rettv, copy, is_const); } *endp = cc; } else if (tv_check_lock(lp->ll_newkey == NULL @@ -2401,6 +2518,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, listitem_T *ll_li = lp->ll_li; int ll_n1 = lp->ll_n1; + if (is_const) { + EMSG(_("E996: Cannot lock a range")); + return; + } + // Check whether any of the list items is locked for (ri = tv_list_first(rettv->vval.v_list); ri != NULL && ll_li != NULL; ) { @@ -2456,6 +2578,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, dict_T *dict = lp->ll_dict; bool watched = tv_dict_is_watched(dict); + if (is_const) { + EMSG(_("E996: Cannot lock a list or dict")); + return; + } + // Assign to a List or Dictionary item. if (lp->ll_newkey != NULL) { if (op != NULL && *op != '=') { @@ -2579,7 +2706,7 @@ bool next_for_item(void *fi_void, char_u *arg) } else { fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item); return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true, - fi->fi_semicolon, fi->fi_varcount, NULL) == OK); + fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK); } } @@ -5165,7 +5292,7 @@ bool garbage_collect(bool testing) { Channel *data; map_foreach_value(channels, data, { - set_ref_in_callback_reader(&data->on_stdout, copyID, NULL, NULL); + set_ref_in_callback_reader(&data->on_data, copyID, NULL, NULL); set_ref_in_callback_reader(&data->on_stderr, copyID, NULL, NULL); set_ref_in_callback(&data->on_exit, copyID, NULL, NULL); }) @@ -6308,6 +6435,7 @@ call_func( partial_T *partial, // optional, can be NULL dict_T *selfdict_in // Dictionary for "self" ) + FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9) { int ret = FAIL; int error = ERROR_NONE; @@ -8830,6 +8958,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) } hash_unlock(ht); } else { + assert(argvars[0].v_type == VAR_LIST); vimvars[VV_KEY].vv_type = VAR_NUMBER; for (listitem_T *li = tv_list_first(l); li != NULL;) { @@ -8860,44 +8989,17 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) } static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) + FUNC_ATTR_NONNULL_ARG(1, 2) { typval_T rettv; typval_T argv[3]; int retval = FAIL; - int dummy; tv_copy(tv, &vimvars[VV_VAL].vv_tv); argv[0] = vimvars[VV_KEY].vv_tv; argv[1] = vimvars[VV_VAL].vv_tv; - if (expr->v_type == VAR_FUNC) { - const char_u *const s = expr->vval.v_string; - if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, - 0L, 0L, &dummy, true, NULL, NULL) == FAIL) { - goto theend; - } - } else if (expr->v_type == VAR_PARTIAL) { - partial_T *partial = expr->vval.v_partial; - - const char_u *const s = partial_name(partial); - if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, - 0L, 0L, &dummy, true, partial, NULL) == FAIL) { - goto theend; - } - } else { - char buf[NUMBUFLEN]; - const char *s = tv_get_string_buf_chk(expr, buf); - if (s == NULL) { - goto theend; - } - s = (const char *)skipwhite((const char_u *)s); - if (eval1((char_u **)&s, &rettv, true) == FAIL) { - goto theend; - } - - if (*s != NUL) { // check for trailing chars after expr - emsgf(_(e_invexpr2), s); - goto theend; - } + if (eval_expr_typval(expr, argv, 2, &rettv) == FAIL) { + goto theend; } if (map) { // map(): replace the list item value. @@ -14498,10 +14600,10 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) long lnum_stop = 0; long time_limit = 0; - // Get the three pattern arguments: start, middle, end. + // Get the three pattern arguments: start, middle, end. Will result in an + // error if not a valid argument. char nbuf1[NUMBUFLEN]; char nbuf2[NUMBUFLEN]; - char nbuf3[NUMBUFLEN]; const char *spat = tv_get_string_chk(&argvars[0]); const char *mpat = tv_get_string_buf_chk(&argvars[1], nbuf1); const char *epat = tv_get_string_buf_chk(&argvars[2], nbuf2); @@ -14529,23 +14631,28 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) } // Optional fifth argument: skip expression. - const char *skip; + const typval_T *skip; if (argvars[3].v_type == VAR_UNKNOWN || argvars[4].v_type == VAR_UNKNOWN) { - skip = ""; + skip = NULL; } else { - skip = tv_get_string_buf_chk(&argvars[4], nbuf3); - if (skip == NULL) { + skip = &argvars[4]; + if (skip->v_type != VAR_FUNC + && skip->v_type != VAR_PARTIAL + && skip->v_type != VAR_STRING) { + emsgf(_(e_invarg2), tv_get_string(&argvars[4])); goto theend; // Type error. } if (argvars[5].v_type != VAR_UNKNOWN) { lnum_stop = tv_get_number_chk(&argvars[5], NULL); if (lnum_stop < 0) { + emsgf(_(e_invarg2), tv_get_string(&argvars[5])); goto theend; } if (argvars[6].v_type != VAR_UNKNOWN) { time_limit = tv_get_number_chk(&argvars[6], NULL); if (time_limit < 0) { + emsgf(_(e_invarg2), tv_get_string(&argvars[6])); goto theend; } } @@ -14553,7 +14660,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) } retval = do_searchpair( - (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, (char_u *)skip, + (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip, flags, match_pos, lnum_stop, time_limit); theend: @@ -14601,7 +14708,7 @@ do_searchpair( char_u *mpat, // middle pattern char_u *epat, // end pattern int dir, // BACKWARD or FORWARD - char_u *skip, // skip expression + const typval_T *skip, // skip expression int flags, // SP_SETPCMARK and other SP_ values pos_T *match_pos, linenr_T lnum_stop, // stop at this line if not zero @@ -14617,8 +14724,8 @@ do_searchpair( pos_T save_cursor; pos_T save_pos; int n; - int r; int nest = 1; + bool use_skip = false; int options = SEARCH_KEEP; proftime_T tm; size_t pat2_len; @@ -14648,6 +14755,13 @@ do_searchpair( options |= SEARCH_START; } + if (skip != NULL) { + // Empty string means to not use the skip expression. + if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) { + use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL; + } + } + save_cursor = curwin->w_cursor; pos = curwin->w_cursor; clearpos(&firstpos); @@ -14677,12 +14791,12 @@ do_searchpair( /* clear the start flag to avoid getting stuck here */ options &= ~SEARCH_START; - /* If the skip pattern matches, ignore this match. */ - if (*skip != NUL) { + // If the skip pattern matches, ignore this match. + if (use_skip) { save_pos = curwin->w_cursor; curwin->w_cursor = pos; - bool err; - r = eval_to_bool(skip, &err, NULL, false); + bool err = false; + const bool r = eval_expr_to_bool(skip, &err); curwin->w_cursor = save_pos; if (err) { /* Evaluating {skip} caused an error, break here. */ @@ -17831,7 +17945,6 @@ static void add_timer_info(typval_T *rettv, timer_T *timer) di->di_tv.v_type = VAR_FUNC; di->di_tv.vval.v_string = vim_strsave(timer->callback.data.funcref); } - di->di_tv.v_lock = 0; } static void add_timer_info_all(typval_T *rettv) @@ -20102,6 +20215,24 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, const bool copy) FUNC_ATTR_NONNULL_ALL { + set_var_const(name, name_len, tv, copy, false); +} + +/// Set variable to the given value +/// +/// If the variable already exists, the value is updated. Otherwise the variable +/// is created. +/// +/// @param[in] name Variable name to set. +/// @param[in] name_len Length of the variable name. +/// @param tv Variable value. +/// @param[in] copy True if value in tv is to be copied. +/// @param[in] is_const True if value in tv is to be locked. +static void set_var_const(const char *name, const size_t name_len, + typval_T *const tv, const bool copy, + const bool is_const) + FUNC_ATTR_NONNULL_ALL +{ dictitem_T *v; hashtab_T *ht; dict_T *dict; @@ -20127,6 +20258,11 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, typval_T oldtv = TV_INITIAL_VALUE; if (v != NULL) { + if (is_const) { + EMSG(_(e_cannot_mod)); + return; + } + // existing variable, need to clear the value if (var_check_ro(v->di_flags, name, name_len) || tv_check_lock(v->di_tv.v_lock, name, name_len)) { @@ -20193,6 +20329,9 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, return; } v->di_flags = DI_FLAGS_ALLOC; + if (is_const) { + v->di_flags |= DI_FLAGS_LOCK; + } } if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) { @@ -20211,6 +20350,10 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, tv_clear(&oldtv); } } + + if (is_const) { + v->di_tv.v_lock |= VAR_LOCKED; + } } /// Check whether variable is read-only (DI_FLAGS_RO, DI_FLAGS_RO_SBX) @@ -20538,7 +20681,6 @@ void ex_execute(exarg_T *eap) char_u *arg = eap->arg; typval_T rettv; int ret = OK; - char_u *p; garray_T ga; int save_did_emsg = did_emsg; @@ -20547,17 +20689,8 @@ void ex_execute(exarg_T *eap) if (eap->skip) ++emsg_skip; while (*arg != NUL && *arg != '|' && *arg != '\n') { - p = arg; - if (eval1(&arg, &rettv, !eap->skip) == FAIL) { - /* - * Report the invalid expression unless the expression evaluation - * has been cancelled due to an aborting error, an interrupt, or an - * exception. - */ - if (!aborting() && did_emsg == save_did_emsg) { - EMSG2(_(e_invexpr2), p); - } - ret = FAIL; + ret = eval1_emsg(&arg, &rettv, !eap->skip); + if (ret == FAIL) { break; } @@ -21192,7 +21325,6 @@ void ex_function(exarg_T *eap) tv_clear(&fudi.fd_di->di_tv); } fudi.fd_di->di_tv.v_type = VAR_FUNC; - fudi.fd_di->di_tv.v_lock = 0; fudi.fd_di->di_tv.vval.v_string = vim_strsave(name); /* behave like "dict" was used */ diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 7cab0e7f80..9deee69f32 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -20,10 +20,10 @@ return { ['and']={args=2}, api_info={}, append={args=2}, - argc={}, + argc={args={0, 1}}, argidx={}, arglistid={args={0, 2}}, - argv={args={0, 1}}, + argv={args={0, 2}}, asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc assert_beeps={args={1, 2}}, assert_equal={args={2, 3}}, diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ffb46abfea..91a1d083c7 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1221,7 +1221,8 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key, /// Allocate a dictionary item /// -/// @note that the value of the item (->di_tv) still needs to be initialized. +/// @note that the type and value of the item (->di_tv) still needs to +/// be initialized. /// /// @param[in] key Key, is copied to the new item. /// @param[in] key_len Key length. @@ -1235,12 +1236,14 @@ dictitem_T *tv_dict_item_alloc_len(const char *const key, const size_t key_len) memcpy(di->di_key, key, key_len); di->di_key[key_len] = NUL; di->di_flags = DI_FLAGS_ALLOC; + di->di_tv.v_lock = VAR_UNLOCKED; return di; } /// Allocate a dictionary item /// -/// @note that the value of the item (->di_tv) still needs to be initialized. +/// @note that the type and value of the item (->di_tv) still needs to +/// be initialized. /// /// @param[in] key Key, is copied to the new item. /// @@ -1572,7 +1575,6 @@ int tv_dict_add_list(dict_T *const d, const char *const key, { dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); - item->di_tv.v_lock = VAR_UNLOCKED; item->di_tv.v_type = VAR_LIST; item->di_tv.vval.v_list = list; tv_list_ref(list); @@ -1597,7 +1599,6 @@ int tv_dict_add_dict(dict_T *const d, const char *const key, { dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); - item->di_tv.v_lock = VAR_UNLOCKED; item->di_tv.v_type = VAR_DICT; item->di_tv.vval.v_dict = dict; dict->dv_refcount++; @@ -1621,7 +1622,6 @@ int tv_dict_add_nr(dict_T *const d, const char *const key, { dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); - item->di_tv.v_lock = VAR_UNLOCKED; item->di_tv.v_type = VAR_NUMBER; item->di_tv.vval.v_number = nr; if (tv_dict_add(d, item) == FAIL) { @@ -1644,7 +1644,6 @@ int tv_dict_add_special(dict_T *const d, const char *const key, { dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); - item->di_tv.v_lock = VAR_UNLOCKED; item->di_tv.v_type = VAR_SPECIAL; item->di_tv.vval.v_special = val; if (tv_dict_add(d, item) == FAIL) { @@ -1706,7 +1705,6 @@ int tv_dict_add_allocated_str(dict_T *const d, { dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); - item->di_tv.v_lock = VAR_UNLOCKED; item->di_tv.v_type = VAR_STRING; item->di_tv.vval.v_string = (char_u *)val; if (tv_dict_add(d, item) == FAIL) { diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 7059a41def..7a8a39dbcf 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -22,11 +22,6 @@ # include "event/process.c.generated.h" #endif -/// Externally defined with gcov. -#ifdef USE_GCOV -void __gcov_dump(void); -#endif - // Time for a process to exit cleanly before we send KILL. // For PTY processes SIGTERM is sent first (in case SIGHUP was not enough). #define KILL_TIMEOUT_MS 2000 @@ -55,11 +50,6 @@ int process_spawn(Process *proc, bool in, bool out, bool err) proc->err.closed = true; } -#ifdef USE_GCOV - // Dump coverage data before forking, to avoid "Merge mismatch" errors. - __gcov_dump(); -#endif - int status; switch (proc->type) { case kProcessTypeUv: diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 30667b64e5..17b66fd32c 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -335,7 +335,7 @@ static int linelen(int *has_tab) len = linetabsize(line); // Check for embedded TAB. if (has_tab != NULL) { - *has_tab = STRRCHR(first, TAB) != NULL; + *has_tab = vim_strchr(first, TAB) != NULL; } *last = save; @@ -1934,8 +1934,9 @@ void do_wqall(exarg_T *eap) int error = 0; int save_forceit = eap->forceit; - if (eap->cmdidx == CMD_xall || eap->cmdidx == CMD_wqall) - exiting = TRUE; + if (eap->cmdidx == CMD_xall || eap->cmdidx == CMD_wqall) { + exiting = true; + } FOR_ALL_BUFFERS(buf) { if (!bufIsChanged(buf)) { diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 0f69d476f9..58dc62e953 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -601,6 +601,12 @@ return { func='ex_wrongmodifier', }, { + command='const', + flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, CMDWIN), + addr_type=ADDR_LINES, + func='ex_const', + }, + { command='copen', flags=bit.bor(RANGE, NOTADR, COUNT, TRLBAR), addr_type=ADDR_LINES, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 3b83c59675..2fb818760a 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -611,7 +611,7 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) return OK; } -/// ":breakadd". +/// ":breakadd". Also used for ":profile". void ex_breakadd(exarg_T *eap) { struct debuggy *bp; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 0e8455bb07..a494463f89 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6018,7 +6018,7 @@ static void ex_highlight(exarg_T *eap) */ void not_exiting(void) { - exiting = FALSE; + exiting = false; } static bool before_quit_autocmds(win_T *wp, bool quit_all, int forceit) diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index fdc00d5dc0..d9c65e17c5 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -79,6 +79,7 @@ local get_flags = function(o) {'pri_mkrc'}, {'deny_in_modelines', 'P_NO_ML'}, {'deny_duplicates', 'P_NODUP'}, + {'modelineexpr', 'P_MLE'}, }) do local key_name = flag_desc[1] local def_name = flag_desc[2] or ('P_' .. key_name:upper()) diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 13700328fd..10937879a1 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -77,15 +77,15 @@ FileDescriptor *scriptin[NSCRIPT] = { NULL }; #define MINIMAL_SIZE 20 /* minimal size for b_str */ -static buffheader_T redobuff = { { NULL, { NUL } }, NULL, 0, 0 }; -static buffheader_T old_redobuff = { { NULL, { NUL } }, NULL, 0, 0 }; -static buffheader_T recordbuff = { { NULL, { NUL } }, NULL, 0, 0 }; +static buffheader_T redobuff = { NULL, NULL, 0, 0 }; +static buffheader_T old_redobuff = { NULL, NULL, 0, 0 }; +static buffheader_T recordbuff = { NULL, NULL, 0, 0 }; // First read ahead buffer. Used for translated commands. -static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0}; +static buffheader_T readbuf1 = { NULL, NULL, 0, 0 }; // Second read ahead buffer. Used for redo. -static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0}; +static buffheader_T readbuf2 = { NULL, NULL, 0, 0 }; static int typeahead_char = 0; /* typeahead char that's not flushed */ @@ -163,11 +163,12 @@ void free_buff(buffheader_T *buf) { buffblock_T *p, *np; - for (p = buf->bh_first.b_next; p != NULL; p = np) { + for (p = buf->bh_first; p != NULL; p = np) { np = p->b_next; xfree(p); } - buf->bh_first.b_next = NULL; + buf->bh_first = NULL; + buf->bh_curr = NULL; } /* @@ -181,18 +182,21 @@ static char_u *get_buffcont(buffheader_T *buffer, size_t count = 0; char_u *p = NULL; char_u *p2; - char_u *str; - /* compute the total length of the string */ - for (buffblock_T *bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) + // compute the total length of the string + for (const buffblock_T *bp = buffer->bh_first; bp != NULL; bp = bp->b_next) { count += STRLEN(bp->b_str); + } if (count || dozero) { p = xmalloc(count + 1); p2 = p; - for (buffblock_T *bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) - for (str = bp->b_str; *str; ) + for (const buffblock_T *bp = buffer->bh_first; + bp != NULL; bp = bp->b_next) { + for (const char_u *str = bp->b_str; *str;) { *p2++ = *str++; + } + } *p2 = NUL; } return p; @@ -257,16 +261,16 @@ static void add_buff(buffheader_T *const buf, const char *const s, return; } - if (buf->bh_first.b_next == NULL) { // first add to list + if (buf->bh_first == NULL) { // first add to list buf->bh_space = 0; - buf->bh_curr = &(buf->bh_first); + buf->bh_curr = NULL; } else if (buf->bh_curr == NULL) { // buffer has already been read IEMSG(_("E222: Add to read buffer")); return; } else if (buf->bh_index != 0) { - memmove(buf->bh_first.b_next->b_str, - buf->bh_first.b_next->b_str + buf->bh_index, - STRLEN(buf->bh_first.b_next->b_str + buf->bh_index) + 1); + memmove(buf->bh_first->b_str, + buf->bh_first->b_str + buf->bh_index, + STRLEN(buf->bh_first->b_str + buf->bh_index) + 1); } buf->bh_index = 0; @@ -281,13 +285,19 @@ static void add_buff(buffheader_T *const buf, const char *const s, } else { len = (size_t)slen; } - buffblock_T *p = xmalloc(sizeof(buffblock_T) + len); + buffblock_T *p = xmalloc(sizeof(buffblock_T) + len + 1); buf->bh_space = len - (size_t)slen; STRLCPY(p->b_str, s, slen + 1); - p->b_next = buf->bh_curr->b_next; - buf->bh_curr->b_next = p; - buf->bh_curr = p; + if (buf->bh_curr == NULL) { + p->b_next = NULL; + buf->bh_first = p; + buf->bh_curr = p; + } else { + p->b_next = buf->bh_curr->b_next; + buf->bh_curr->b_next = p; + buf->bh_curr = p; + } } return; } @@ -356,17 +366,17 @@ static int read_readbuffers(int advance) static int read_readbuf(buffheader_T *buf, int advance) { char_u c; - buffblock_T *curr; - if (buf->bh_first.b_next == NULL) /* buffer is empty */ + if (buf->bh_first == NULL) { // buffer is empty return NUL; + } - curr = buf->bh_first.b_next; + buffblock_T *const curr = buf->bh_first; c = curr->b_str[buf->bh_index]; if (advance) { if (curr->b_str[++buf->bh_index] == NUL) { - buf->bh_first.b_next = curr->b_next; + buf->bh_first = curr->b_next; xfree(curr); buf->bh_index = 0; } @@ -379,12 +389,12 @@ static int read_readbuf(buffheader_T *buf, int advance) */ static void start_stuff(void) { - if (readbuf1.bh_first.b_next != NULL) { - readbuf1.bh_curr = &(readbuf1.bh_first); + if (readbuf1.bh_first != NULL) { + readbuf1.bh_curr = readbuf1.bh_first; readbuf1.bh_space = 0; } - if (readbuf2.bh_first.b_next != NULL) { - readbuf2.bh_curr = &(readbuf2.bh_first); + if (readbuf2.bh_first != NULL) { + readbuf2.bh_curr = readbuf2.bh_first; readbuf2.bh_space = 0; } } @@ -394,7 +404,8 @@ static void start_stuff(void) */ int stuff_empty(void) { - return (readbuf1.bh_first.b_next == NULL && readbuf2.bh_first.b_next == NULL); + return (readbuf1.bh_first == NULL + && readbuf2.bh_first == NULL); } /* @@ -403,7 +414,7 @@ int stuff_empty(void) */ int readbuf1_empty(void) { - return (readbuf1.bh_first.b_next == NULL); + return (readbuf1.bh_first == NULL); } /* @@ -461,7 +472,7 @@ void ResetRedobuff(void) if (!block_redo) { free_buff(&old_redobuff); old_redobuff = redobuff; - redobuff.bh_first.b_next = NULL; + redobuff.bh_first = NULL; } } @@ -474,7 +485,7 @@ void CancelRedo(void) if (!block_redo) { free_buff(&redobuff); redobuff = old_redobuff; - old_redobuff.bh_first.b_next = NULL; + old_redobuff.bh_first = NULL; start_stuff(); while (read_readbuffers(TRUE) != NUL) { } @@ -486,9 +497,9 @@ void CancelRedo(void) void saveRedobuff(save_redo_T *save_redo) { save_redo->sr_redobuff = redobuff; - redobuff.bh_first.b_next = NULL; + redobuff.bh_first = NULL; save_redo->sr_old_redobuff = old_redobuff; - old_redobuff.bh_first.b_next = NULL; + old_redobuff.bh_first = NULL; // Make a copy, so that ":normal ." in a function works. char *const s = (char *)get_buffcont(&save_redo->sr_redobuff, false); @@ -668,12 +679,10 @@ static int read_redo(bool init, bool old_redo) int i; if (init) { - if (old_redo) - bp = old_redobuff.bh_first.b_next; - else - bp = redobuff.bh_first.b_next; - if (bp == NULL) + bp = old_redo ? old_redobuff.bh_first : redobuff.bh_first; + if (bp == NULL) { return FAIL; + } p = bp->b_str; return OK; } @@ -1206,9 +1215,9 @@ void save_typeahead(tasave_T *tp) old_char = -1; tp->save_readbuf1 = readbuf1; - readbuf1.bh_first.b_next = NULL; + readbuf1.bh_first = NULL; tp->save_readbuf2 = readbuf2; - readbuf2.bh_first.b_next = NULL; + readbuf2.bh_first = NULL; } /* @@ -1244,9 +1253,17 @@ openscript ( EMSG(_(e_nesting)); return; } - if (ignore_script) - /* Not reading from script, also don't open one. Warning message? */ + + // Disallow sourcing a file in the sandbox, the commands would be executed + // later, possibly outside of the sandbox. + if (check_secure()) { + return; + } + + if (ignore_script) { + // Not reading from script, also don't open one. Warning message? return; + } if (scriptin[curscript] != NULL) /* already reading script */ ++curscript; @@ -1927,7 +1944,8 @@ static int vgetorpeek(int advance) } if ((mp == NULL || max_mlen >= mp_match_len) - && keylen != KEYLEN_PART_MAP) { + && keylen != KEYLEN_PART_MAP + && !(keylen == KEYLEN_PART_KEY && c1 == ui_toggle[0])) { // No matching mapping found or found a non-matching mapping that // matches at least what the matching mapping matched keylen = 0; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 4fce02613c..dbbc7cdafd 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -510,7 +510,7 @@ EXTERN int sc_col; /* column for shown command */ // First NO_SCREEN, then NO_BUFFERS, then 0 when startup finished. EXTERN int starting INIT(= NO_SCREEN); // true when planning to exit. Might keep running if there is a changed buffer. -EXTERN int exiting INIT(= false); +EXTERN bool exiting INIT(= false); // is stdin a terminal? EXTERN int stdin_isatty INIT(= true); // is stdout a terminal? diff --git a/src/nvim/main.c b/src/nvim/main.c index 2c182abb96..55be97d3f4 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -584,9 +584,7 @@ int main(int argc, char **argv) void getout(int exitval) FUNC_ATTR_NORETURN { - tabpage_T *tp, *next_tp; - - exiting = TRUE; + exiting = true; /* When running in Ex mode an error causes us to exit with a non-zero exit * code. POSIX requires this, although it's not 100% clear from the @@ -603,8 +601,10 @@ void getout(int exitval) hash_debug_results(); if (get_vim_var_nr(VV_DYING) <= 1) { - /* Trigger BufWinLeave for all windows, but only once per buffer. */ - for (tp = first_tabpage; tp != NULL; tp = next_tp) { + const tabpage_T *next_tp; + + // Trigger BufWinLeave for all windows, but only once per buffer. + for (const tabpage_T *tp = first_tabpage; tp != NULL; tp = next_tp) { next_tp = tp->tp_next; FOR_ALL_WINDOWS_IN_TAB(wp, tp) { if (wp->w_buffer == NULL) { diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 4e47311dda..6738e59bb2 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -1765,6 +1765,7 @@ del_lines ( } int gchar_pos(pos_T *pos) + FUNC_ATTR_NONNULL_ARG(1) { // When searching columns is sometimes put at the end of a line. if (pos->col == MAXCOL) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 2a3b7beb8e..b5408fab9a 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1638,12 +1638,25 @@ static void mb_adjust_opend(oparg_T *oap) /* * Put character 'c' at position 'lp' */ -static inline void pchar(pos_T lp, int c) +static inline void pbyte(pos_T lp, int c) { assert(c <= UCHAR_MAX); *(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char_u)c; } +// Replace the character under the cursor with "c". +// This takes care of multi-byte characters. +static void replace_character(int c) +{ + const int n = State; + + State = REPLACE; + ins_char(c); + State = n; + // Backup to the replaced character. + dec_cursor(); +} + /* * Replace a whole area with one character. */ @@ -1795,12 +1808,7 @@ int op_replace(oparg_T *oap, int c) * with a multi-byte and the other way around. */ if (curwin->w_cursor.lnum == oap->end.lnum) oap->end.col += (*mb_char2len)(c) - (*mb_char2len)(n); - n = State; - State = REPLACE; - ins_char(c); - State = n; - /* Backup to the replaced character. */ - dec_cursor(); + replace_character(c); } else { if (n == TAB) { int end_vcol = 0; @@ -1815,7 +1823,7 @@ int op_replace(oparg_T *oap, int c) if (curwin->w_cursor.lnum == oap->end.lnum) getvpos(&oap->end, end_vcol); } - pchar(curwin->w_cursor, c); + pbyte(curwin->w_cursor, c); } } else if (virtual_op && curwin->w_cursor.lnum == oap->end.lnum) { int virtcols = oap->end.coladd; @@ -1830,9 +1838,14 @@ int op_replace(oparg_T *oap, int c) coladvance_force(getviscol2(oap->end.col, oap->end.coladd) + 1); curwin->w_cursor.col -= (virtcols + 1); for (; virtcols >= 0; virtcols--) { - pchar(curwin->w_cursor, c); - if (inc(&curwin->w_cursor) == -1) + if (utf_char2len(c) > 1) { + replace_character(c); + } else { + pbyte(curwin->w_cursor, c); + } + if (inc(&curwin->w_cursor) == -1) { break; + } } } @@ -1953,23 +1966,20 @@ static int swapchars(int op_type, pos_T *pos, int length) return did_change; } -/* - * If op_type == OP_UPPER: make uppercase, - * if op_type == OP_LOWER: make lowercase, - * if op_type == OP_ROT13: do rot13 encoding, - * else swap case of character at 'pos' - * returns TRUE when something actually changed. - */ -int swapchar(int op_type, pos_T *pos) +// If op_type == OP_UPPER: make uppercase, +// if op_type == OP_LOWER: make lowercase, +// if op_type == OP_ROT13: do rot13 encoding, +// else swap case of character at 'pos' +// returns true when something actually changed. +bool swapchar(int op_type, pos_T *pos) + FUNC_ATTR_NONNULL_ARG(2) { - int c; - int nc; + const int c = gchar_pos(pos); - c = gchar_pos(pos); - - /* Only do rot13 encoding for ASCII characters. */ - if (c >= 0x80 && op_type == OP_ROT13) - return FALSE; + // Only do rot13 encoding for ASCII characters. + if (c >= 0x80 && op_type == OP_ROT13) { + return false; + } if (op_type == OP_UPPER && c == 0xdf) { pos_T sp = curwin->w_cursor; @@ -1983,7 +1993,7 @@ int swapchar(int op_type, pos_T *pos) inc(pos); } - nc = c; + int nc = c; if (mb_islower(c)) { if (op_type == OP_ROT13) { nc = ROT13(c, 'a'); @@ -1998,7 +2008,7 @@ int swapchar(int op_type, pos_T *pos) } } if (nc != c) { - if (enc_utf8 && (c >= 0x80 || nc >= 0x80)) { + if (c >= 0x80 || nc >= 0x80) { pos_T sp = curwin->w_cursor; curwin->w_cursor = *pos; @@ -2006,11 +2016,12 @@ int swapchar(int op_type, pos_T *pos) del_bytes(utf_ptr2len(get_cursor_pos_ptr()), FALSE, FALSE); ins_char(nc); curwin->w_cursor = sp; - } else - pchar(*pos, nc); - return TRUE; + } else { + pbyte(*pos, nc); + } + return true; } - return FALSE; + return false; } /* diff --git a/src/nvim/option.c b/src/nvim/option.c index a39be0fe96..8dadf926b9 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -253,6 +253,7 @@ typedef struct vimoption { #define P_RWINONLY 0x10000000U ///< only redraw current window #define P_NDNAME 0x20000000U ///< only normal dir name chars allowed #define P_UI_OPTION 0x40000000U ///< send option to remote ui +#define P_MLE 0x80000000U ///< under control of 'modelineexpr' #define HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \ @@ -1327,6 +1328,11 @@ int do_set( errmsg = (char_u *)_("E520: Not allowed in a modeline"); goto skip; } + if ((flags & P_MLE) && !p_mle) { + errmsg = (char_u *)_( + "E992: Not allowed in a modeline when 'modelineexpr' is off"); + goto skip; + } // In diff mode some options are overruled. This avoids that // 'foldmethod' becomes "marker" instead of "diff" and that // "wrap" gets set. diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index f9f2a7d5dc..8df5039037 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -496,6 +496,7 @@ EXTERN long p_mmd; // 'maxmapdepth' EXTERN long p_mmp; // 'maxmempattern' EXTERN long p_mis; // 'menuitems' EXTERN char_u *p_msm; // 'mkspellmem' +EXTERN long p_mle; // 'modelineexpr' EXTERN long p_mls; // 'modelines' EXTERN char_u *p_mouse; // 'mouse' EXTERN char_u *p_mousem; // 'mousemodel' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 96e098778c..e892e59ba6 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -8,6 +8,7 @@ -- defaults={condition=nil, if_true={vi=224, vim=0}, if_false=nil}, -- secure=nil, gettext=nil, noglob=nil, normal_fname_chars=nil, -- pri_mkrc=nil, deny_in_modelines=nil, normal_dname_chars=nil, +-- modelineexpr=nil, -- expand=nil, nodefault=nil, no_mkrc=nil, vi_def=true, vim=true, -- alloced=nil, -- save_pv_indir=nil, @@ -283,6 +284,7 @@ return { deny_duplicates=true, vi_def=true, expand=true, + secure=true, varname='p_cdpath', defaults={if_true={vi=",,"}} }, @@ -847,6 +849,7 @@ return { type='string', scope={'window'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, redraw={'current_window'}, defaults={if_true={vi="0"}} @@ -922,6 +925,7 @@ return { type='string', scope={'window'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, redraw={'current_window'}, defaults={if_true={vi="foldtext()"}} @@ -931,6 +935,7 @@ return { type='string', scope={'buffer'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, varname='p_fex', defaults={if_true={vi=""}} @@ -1045,6 +1050,7 @@ return { full_name='guitablabel', abbreviation='gtl', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, redraw={'current_window'}, enable_if=false, }, @@ -1136,6 +1142,7 @@ return { full_name='iconstring', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, varname='p_iconstring', defaults={if_true={vi=""}} }, @@ -1198,6 +1205,7 @@ return { full_name='includeexpr', abbreviation='inex', type='string', scope={'buffer'}, vi_def=true, + modelineexpr=true, alloced=true, varname='p_inex', defaults={if_true={vi=""}} @@ -1214,6 +1222,7 @@ return { type='string', scope={'buffer'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, varname='p_inde', defaults={if_true={vi=""}} @@ -1528,6 +1537,14 @@ return { defaults={if_true={vi=false, vim=true}} }, { + full_name='modelineexpr', abbreviation='mle', + type='bool', scope={'global'}, + vi_def=true, + secure=true, + varname='p_mle', + defaults={if_true={vi=false}} + }, + { full_name='modelines', abbreviation='mls', type='number', scope={'global'}, vi_def=true, @@ -1903,6 +1920,7 @@ return { type='string', scope={'global'}, vi_def=true, alloced=true, + modelineexpr=true, redraw={'statuslines'}, varname='p_ruf', defaults={if_true={vi=""}} @@ -2310,6 +2328,7 @@ return { type='string', scope={'global', 'window'}, vi_def=true, alloced=true, + modelineexpr=true, redraw={'statuslines'}, varname='p_stl', defaults={if_true={vi=""}} @@ -2369,6 +2388,7 @@ return { full_name='tabline', abbreviation='tal', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, redraw={'all_windows'}, varname='p_tal', defaults={if_true={vi=""}} @@ -2528,6 +2548,7 @@ return { full_name='titlestring', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, varname='p_titlestring', defaults={if_true={vi=""}} }, diff --git a/src/nvim/os/dl.c b/src/nvim/os/dl.c index bbd0424a82..f0fadb16f2 100644 --- a/src/nvim/os/dl.c +++ b/src/nvim/os/dl.c @@ -54,6 +54,7 @@ bool os_libcall(const char *libname, // open the dynamic loadable library if (uv_dlopen(libname, &lib)) { EMSG2(_("dlerror = \"%s\""), uv_dlerror(&lib)); + uv_dlclose(&lib); return false; } diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 5fdf0e6181..97545a6cb1 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -36,6 +36,11 @@ # include "os/pty_process_unix.c.generated.h" #endif +/// Externally defined with gcov. +#ifdef USE_GCOV +void __gcov_flush(void); +#endif + /// termios saved at startup (for TUI) or initialized by pty_process_spawn(). static struct termios termios_default; @@ -59,6 +64,11 @@ int pty_process_spawn(PtyProcess *ptyproc) init_termios(&termios_default); } +#ifdef USE_GCOV + // Flush coverage data before forking, to avoid "Merge mismatch" errors. + __gcov_flush(); +#endif + int status = 0; // zero or negative error code (libuv convention) Process *proc = (Process *)ptyproc; assert(proc->err.closed); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 0e4fa0afc6..6779f4e05d 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2528,9 +2528,9 @@ void qf_list(exarg_T *eap) qfp = qi->qf_lists[qi->qf_curlist].qf_start; for (i = 1; !got_int && i <= qi->qf_lists[qi->qf_curlist].qf_count; ) { if ((qfp->qf_valid || all) && idx1 <= i && i <= idx2) { - msg_putchar('\n'); - if (got_int) + if (got_int) { break; + } fname = NULL; if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { @@ -2549,6 +2549,27 @@ void qf_list(exarg_T *eap) vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, (char *)fname); } } + + // Support for filtering entries using :filter /pat/ clist + // Match against the module name, file name, search pattern and + // text of the entry. + bool filter_entry = true; + if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { + filter_entry &= message_filtered(qfp->qf_module); + } + if (filter_entry && fname != NULL) { + filter_entry &= message_filtered(fname); + } + if (filter_entry && qfp->qf_pattern != NULL) { + filter_entry &= message_filtered(qfp->qf_pattern); + } + if (filter_entry) { + filter_entry &= message_filtered(qfp->qf_text); + } + if (filter_entry) { + goto next_entry; + } + msg_putchar('\n'); msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index ? HL_ATTR(HLF_QFL) : HL_ATTR(HLF_D)); if (qfp->qf_lnum == 0) { @@ -2579,6 +2600,7 @@ void qf_list(exarg_T *eap) ui_flush(); /* show one line at a time */ } +next_entry: qfp = qfp->qf_next; if (qfp == NULL) { break; @@ -4721,11 +4743,8 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict) /// Return the quickfix list title as 'title' in retdict static int qf_getprop_title(qf_info_T *qi, int qf_idx, dict_T *retdict) { - char_u *t = qi->qf_lists[qf_idx].qf_title; - if (t == NULL) { - t = (char_u *)""; - } - return tv_dict_add_str(retdict, S_LEN("title"), (const char *)t); + return tv_dict_add_str(retdict, S_LEN("title"), + (const char *)qi->qf_lists[qf_idx].qf_title); } /// Return the quickfix list items/entries as 'items' in retdict @@ -5368,8 +5387,11 @@ void ex_cexpr(exarg_T *eap) apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, curbuf->b_fname, true, curbuf); } - if (res > 0 && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr)) { - qf_jump(qi, 0, 0, eap->forceit); // display first error + if (res > 0 + && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr) + && qi == GET_LOC_LIST(curwin)) { + // Jump to the first error if autocmds didn't free the list. + qf_jump(qi, 0, 0, eap->forceit); } } else { EMSG(_("E777: String or List expected")); diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 45c82d70d1..f349304468 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -1775,7 +1775,9 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T if (len > len_max) { len = len_max; } - copy_text_attr(off + col, (char_u *)" ", len, + char_u space_buf[18] = " "; + assert((size_t)len_max <= sizeof(space_buf)); + copy_text_attr(off + col, space_buf, len, win_hl_attr(wp, HLF_FL)); col += len; } @@ -2061,7 +2063,8 @@ win_line ( int row; // row in the window, excl w_winrow ScreenGrid *grid = &wp->w_grid; // grid specfic to the window - char_u extra[18]; // line number and 'fdc' must fit in here + char_u extra[57]; // sign, line number and 'fdc' must + // fit in here int n_extra = 0; // number of extra chars char_u *p_extra = NULL; // string of extra chars, plus NUL char_u *p_extra_free = NULL; // p_extra needs to be freed @@ -2712,15 +2715,24 @@ win_line ( sign_idx, count); if (text_sign != 0) { p_extra = sign_get_text(text_sign); - int symbol_blen = (int)STRLEN(p_extra); if (p_extra != NULL) { + int symbol_blen = (int)STRLEN(p_extra); + c_extra = NUL; c_final = NUL; + + // TODO(oni-link): Is sign text already extended to + // full cell width? + assert((size_t)win_signcol_width(wp) + >= mb_string2cells(p_extra)); // symbol(s) bytes + (filling spaces) (one byte each) n_extra = symbol_blen + (win_signcol_width(wp) - mb_string2cells(p_extra)); + + assert(sizeof(extra) > (size_t)symbol_blen); memset(extra, ' ', sizeof(extra)); - STRNCPY(extra, p_extra, STRLEN(p_extra)); + memcpy(extra, p_extra, symbol_blen); + p_extra = extra; p_extra[n_extra] = NUL; } diff --git a/src/nvim/search.c b/src/nvim/search.c index 9cbe21bdc8..b6d666cbe8 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3370,7 +3370,6 @@ current_tagblock( ) { long count = count_arg; - long n; pos_T old_pos; pos_T start_pos; pos_T end_pos; @@ -3379,7 +3378,6 @@ current_tagblock( char_u *p; char_u *cp; int len; - int r; bool do_include = include; bool save_p_ws = p_ws; int retval = FAIL; @@ -3428,12 +3426,12 @@ again: * Search backwards for unclosed "<aaa>". * Put this position in start_pos. */ - for (n = 0; n < count; ++n) { - if (do_searchpair((char_u *) - "<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", - (char_u *)"", - (char_u *)"</[^>]*>", BACKWARD, (char_u *)"", 0, - NULL, (linenr_T)0, 0L) <= 0) { + for (long n = 0; n < count; n++) { + if (do_searchpair( + (char_u *)"<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", + (char_u *)"", + (char_u *)"</[^>]*>", BACKWARD, NULL, 0, + NULL, (linenr_T)0, 0L) <= 0) { curwin->w_cursor = old_pos; goto theend; } @@ -3459,8 +3457,8 @@ again: sprintf((char *)spat, "<%.*s\\>\\%%(\\s\\_[^>]\\{-}[^/]>\\|>\\)\\c", len, p); sprintf((char *)epat, "</%.*s>\\c", len, p); - r = do_searchpair(spat, (char_u *)"", epat, FORWARD, (char_u *)"", - 0, NULL, (linenr_T)0, 0L); + const int r = do_searchpair(spat, (char_u *)"", epat, FORWARD, NULL, + 0, NULL, (linenr_T)0, 0L); xfree(spat); xfree(epat); diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 7b1f0f59cc..fe9e0bc9c8 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -32,14 +32,12 @@ NEW_TESTS_ALOT := test_alot_utf8 test_alot NEW_TESTS_IN_ALOT := $(shell sed '/^source/ s/^source //;s/\.vim$$//' test_alot*.vim) # Ignored tests. # test_alot_latin: Nvim does not allow setting encoding. -# test_arglist: ported to Lua, but kept for easier merging. # test_autochdir: ported to Lua, but kept for easier merging. # test_eval_func: used as include in old-style test (test_eval.in). # test_listlbr: Nvim does not allow setting encoding. # test_largefile: uses too much resources to run on CI. NEW_TESTS_IGNORE := $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_ALOT) \ test_alot_latin \ - test_arglist \ test_autochdir \ test_eval_func \ test_listlbr \ diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 6f039509f3..605e7c73eb 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -264,6 +264,7 @@ endif " Names of flaky tests. let s:flaky = [ + \ 'Test_cursorhold_insert()', \ 'Test_exit_callback_interval()', \ 'Test_oneshot()', \ 'Test_out_cb()', diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim index 467abcd9b9..837e55ebca 100644 --- a/src/nvim/testdir/test49.vim +++ b/src/nvim/testdir/test49.vim @@ -1,6 +1,6 @@ " Vim script language tests " Author: Servatius Brandt <Servatius.Brandt@fujitsu-siemens.com> -" Last Change: 2016 Feb 07 +" Last Change: 2019 May 24 "------------------------------------------------------------------------------- " Test environment {{{1 @@ -9005,5 +9005,4 @@ Xcheck 50443995 "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=4 tw=80 fdm=marker -" vim: fdt=substitute(substitute(foldtext(),\ '\\%(^+--\\)\\@<=\\(\\s*\\)\\(.\\{-}\\)\:\ \\%(\"\ \\)\\=\\(Test\ \\d*\\)\:\\s*',\ '\\3\ (\\2)\:\ \\1',\ \"\"),\ '\\(Test\\s*\\)\\(\\d\\)\\D\\@=',\ '\\1\ \\2',\ "") "------------------------------------------------------------------------------- diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim new file mode 100644 index 0000000000..06062c5e58 --- /dev/null +++ b/src/nvim/testdir/test_const.vim @@ -0,0 +1,237 @@ + +" Test for :const + +func s:noop() +endfunc + +func Test_define_var_with_lock() + const i = 1 + const f = 1.1 + const s = 'vim' + const F = funcref('s:noop') + const l = [1, 2, 3] + const d = {'foo': 10} + if has('channel') + const j = test_null_job() + const c = test_null_channel() + endif + const b = v:true + const n = v:null + + call assert_true(exists('i')) + call assert_true(exists('f')) + call assert_true(exists('s')) + call assert_true(exists('F')) + call assert_true(exists('l')) + call assert_true(exists('d')) + if has('channel') + call assert_true(exists('j')) + call assert_true(exists('c')) + endif + call assert_true(exists('b')) + call assert_true(exists('n')) + + call assert_fails('let i = 1', 'E741:') + call assert_fails('let f = 1.1', 'E741:') + call assert_fails('let s = "vim"', 'E741:') + call assert_fails('let F = funcref("s:noop")', 'E741:') + call assert_fails('let l = [1, 2, 3]', 'E741:') + call assert_fails('let d = {"foo": 10}', 'E741:') + if has('channel') + call assert_fails('let j = test_null_job()', 'E741:') + call assert_fails('let c = test_null_channel()', 'E741:') + endif + call assert_fails('let b = v:true', 'E741:') + call assert_fails('let n = v:null', 'E741:') + + " Unlet + unlet i + unlet f + unlet s + unlet F + unlet l + unlet d + if has('channel') + unlet j + unlet c + endif + unlet b + unlet n +endfunc + +func Test_define_l_var_with_lock() + " With l: prefix + const l:i = 1 + const l:f = 1.1 + const l:s = 'vim' + const l:F = funcref('s:noop') + const l:l = [1, 2, 3] + const l:d = {'foo': 10} + if has('channel') + const l:j = test_null_job() + const l:c = test_null_channel() + endif + const l:b = v:true + const l:n = v:null + + call assert_fails('let l:i = 1', 'E741:') + call assert_fails('let l:f = 1.1', 'E741:') + call assert_fails('let l:s = "vim"', 'E741:') + call assert_fails('let l:F = funcref("s:noop")', 'E741:') + call assert_fails('let l:l = [1, 2, 3]', 'E741:') + call assert_fails('let l:d = {"foo": 10}', 'E741:') + if has('channel') + call assert_fails('let l:j = test_null_job()', 'E741:') + call assert_fails('let l:c = test_null_channel()', 'E741:') + endif + call assert_fails('let l:b = v:true', 'E741:') + call assert_fails('let l:n = v:null', 'E741:') + + " Unlet + unlet l:i + unlet l:f + unlet l:s + unlet l:F + unlet l:l + unlet l:d + if has('channel') + unlet l:j + unlet l:c + endif + unlet l:b + unlet l:n +endfunc + +func Test_define_script_var_with_lock() + const s:x = 0 + call assert_fails('let s:x = 1', 'E741:') + unlet s:x +endfunc + +func Test_descructuring_with_lock() + const [a, b, c] = [1, 1.1, 'vim'] + + call assert_fails('let a = 1', 'E741:') + call assert_fails('let b = 1.1', 'E741:') + call assert_fails('let c = "vim"', 'E741:') + + const [d; e] = [1, 1.1, 'vim'] + call assert_fails('let d = 1', 'E741:') + call assert_fails('let e = [2.2, "a"]', 'E741:') +endfunc + +func Test_cannot_modify_existing_variable() + let i = 1 + let f = 1.1 + let s = 'vim' + let F = funcref('s:noop') + let l = [1, 2, 3] + let d = {'foo': 10} + if has('channel') + let j = test_null_job() + let c = test_null_channel() + endif + let b = v:true + let n = v:null + + call assert_fails('const i = 1', 'E995:') + call assert_fails('const f = 1.1', 'E995:') + call assert_fails('const s = "vim"', 'E995:') + call assert_fails('const F = funcref("s:noop")', 'E995:') + call assert_fails('const l = [1, 2, 3]', 'E995:') + call assert_fails('const d = {"foo": 10}', 'E995:') + if has('channel') + call assert_fails('const j = test_null_job()', 'E995:') + call assert_fails('const c = test_null_channel()', 'E995:') + endif + call assert_fails('const b = v:true', 'E995:') + call assert_fails('const n = v:null', 'E995:') + call assert_fails('const [i, f, s] = [1, 1.1, "vim"]', 'E995:') + + const i2 = 1 + const f2 = 1.1 + const s2 = 'vim' + const F2 = funcref('s:noop') + const l2 = [1, 2, 3] + const d2 = {'foo': 10} + if has('channel') + const j2 = test_null_job() + const c2 = test_null_channel() + endif + const b2 = v:true + const n2 = v:null + + call assert_fails('const i2 = 1', 'E995:') + call assert_fails('const f2 = 1.1', 'E995:') + call assert_fails('const s2 = "vim"', 'E995:') + call assert_fails('const F2 = funcref("s:noop")', 'E995:') + call assert_fails('const l2 = [1, 2, 3]', 'E995:') + call assert_fails('const d2 = {"foo": 10}', 'E995:') + if has('channel') + call assert_fails('const j2 = test_null_job()', 'E995:') + call assert_fails('const c2 = test_null_channel()', 'E995:') + endif + call assert_fails('const b2 = v:true', 'E995:') + call assert_fails('const n2 = v:null', 'E995:') + call assert_fails('const [i2, f2, s2] = [1, 1.1, "vim"]', 'E995:') +endfunc + +func Test_const_with_index_access() + let l = [1, 2, 3] + call assert_fails('const l[0] = 4', 'E996:') + call assert_fails('const l[0:1] = [1, 2]', 'E996:') + + let d = {'aaa': 0} + call assert_fails("const d['aaa'] = 4", 'E996:') + call assert_fails("const d.aaa = 4", 'E996:') +endfunc + +func Test_const_with_compound_assign() + let i = 0 + call assert_fails('const i += 4', 'E995:') + call assert_fails('const i -= 4', 'E995:') + call assert_fails('const i *= 4', 'E995:') + call assert_fails('const i /= 4', 'E995:') + call assert_fails('const i %= 4', 'E995:') + + let s = 'a' + call assert_fails('const s .= "b"', 'E995:') + + let [a, b, c] = [1, 2, 3] + call assert_fails('const [a, b, c] += [4, 5, 6]', 'E995:') + + let [d; e] = [1, 2, 3] + call assert_fails('const [d; e] += [4, 5, 6]', 'E995:') +endfunc + +func Test_const_with_special_variables() + call assert_fails('const $FOO = "hello"', 'E996:') + call assert_fails('const @a = "hello"', 'E996:') + call assert_fails('const &filetype = "vim"', 'E996:') + call assert_fails('const &l:filetype = "vim"', 'E996:') + call assert_fails('const &g:encoding = "utf-8"', 'E996:') +endfunc + +func Test_const_with_eval_name() + let s = 'foo' + + " eval name with :const should work + const abc_{s} = 1 + const {s}{s} = 1 + + let s2 = 'abc_foo' + call assert_fails('const {s2} = "bar"', 'E995:') +endfunc + +func Test_lock_depth_is_1() + const l = [1, 2, 3] + const d = {'foo': 10} + + " Modify list + call add(l, 4) + let l[0] = 42 + + " Modify dict + let d['bar'] = 'hello' + let d.foo = 44 +endfunc diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim index c8d64ce0a4..1dd3a5b29f 100644 --- a/src/nvim/testdir/test_filter_map.vim +++ b/src/nvim/testdir/test_filter_map.vim @@ -79,3 +79,8 @@ func Test_filter_map_dict_expr_funcref() endfunc call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4'))) endfunc + +func Test_map_fails() + call assert_fails('call map([1], "42 +")', 'E15:') + call assert_fails('call filter([1], "42 +")', 'E15:') +endfunc diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index ed9c70403e..0c3c356622 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1,4 +1,5 @@ " Tests for various functions. +source shared.vim " Must be done first, since the alternate buffer must be unset. func Test_00_bufexists() @@ -1140,3 +1141,50 @@ func Test_reg_executing_and_recording() delfunc s:save_reg_stat unlet s:reg_stat endfunc + +func Test_libcall_libcallnr() + if !has('libcall') + return + endif + + if has('win32') + let libc = 'msvcrt.dll' + elseif has('mac') + let libc = 'libSystem.B.dylib' + elseif system('uname -s') =~ 'SunOS' + " Set the path to libc.so according to the architecture. + let test_bits = system('file ' . GetVimProg()) + let test_arch = system('uname -p') + if test_bits =~ '64-bit' && test_arch =~ 'sparc' + let libc = '/usr/lib/sparcv9/libc.so' + elseif test_bits =~ '64-bit' && test_arch =~ 'i386' + let libc = '/usr/lib/amd64/libc.so' + else + let libc = '/usr/lib/libc.so' + endif + else + " On Unix, libc.so can be in various places. + " Interestingly, using an empty string for the 1st argument of libcall + " allows to call functions from libc which is not documented. + let libc = '' + endif + + if has('win32') + call assert_equal($USERPROFILE, libcall(libc, 'getenv', 'USERPROFILE')) + else + call assert_equal($HOME, libcall(libc, 'getenv', 'HOME')) + endif + + " If function returns NULL, libcall() should return an empty string. + call assert_equal('', libcall(libc, 'getenv', 'X_ENV_DOES_NOT_EXIT')) + + " Test libcallnr() with string and integer argument. + call assert_equal(4, libcallnr(libc, 'strlen', 'abcd')) + call assert_equal(char2nr('A'), libcallnr(libc, 'toupper', char2nr('a'))) + + call assert_fails("call libcall(libc, 'Xdoesnotexist_', '')", 'E364:') + call assert_fails("call libcallnr(libc, 'Xdoesnotexist_', '')", 'E364:') + + call assert_fails("call libcall('Xdoesnotexist_', 'getenv', 'HOME')", 'E364:') + call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", 'E364:') +endfunc diff --git a/src/nvim/testdir/test_modeline.vim b/src/nvim/testdir/test_modeline.vim index 091a833774..1e196e07f0 100644 --- a/src/nvim/testdir/test_modeline.vim +++ b/src/nvim/testdir/test_modeline.vim @@ -60,14 +60,17 @@ func Test_modeline_keymap() set keymap= iminsert=0 imsearch=-1 endfunc -func s:modeline_fails(what, text) +func s:modeline_fails(what, text, error) + if !exists('+' . a:what) + return + endif let fname = "Xmodeline_fails_" . a:what call writefile(['vim: set ' . a:text . ' :', 'nothing'], fname) let modeline = &modeline set modeline filetype plugin on syntax enable - call assert_fails('split ' . fname, 'E474:') + call assert_fails('split ' . fname, a:error) call assert_equal("", &filetype) call assert_equal("", &syntax) @@ -79,16 +82,92 @@ func s:modeline_fails(what, text) endfunc func Test_modeline_filetype_fails() - call s:modeline_fails('filetype', 'ft=evil$CMD') + call s:modeline_fails('filetype', 'ft=evil$CMD', 'E474:') endfunc func Test_modeline_syntax_fails() - call s:modeline_fails('syntax', 'syn=evil$CMD') + call s:modeline_fails('syntax', 'syn=evil$CMD', 'E474:') endfunc func Test_modeline_keymap_fails() - if !has('keymap') - return - endif - call s:modeline_fails('keymap', 'keymap=evil$CMD') + call s:modeline_fails('keymap', 'keymap=evil$CMD', 'E474:') +endfunc + +func Test_modeline_fails_always() + call s:modeline_fails('backupdir', 'backupdir=Something()', 'E520:') + call s:modeline_fails('cdpath', 'cdpath=Something()', 'E520:') + call s:modeline_fails('charconvert', 'charconvert=Something()', 'E520:') + call s:modeline_fails('completefunc', 'completefunc=Something()', 'E520:') + call s:modeline_fails('cscopeprg', 'cscopeprg=Something()', 'E520:') + call s:modeline_fails('diffexpr', 'diffexpr=Something()', 'E520:') + call s:modeline_fails('directory', 'directory=Something()', 'E520:') + call s:modeline_fails('equalprg', 'equalprg=Something()', 'E520:') + call s:modeline_fails('errorfile', 'errorfile=Something()', 'E520:') + call s:modeline_fails('exrc', 'exrc=Something()', 'E520:') + call s:modeline_fails('formatprg', 'formatprg=Something()', 'E520:') + call s:modeline_fails('fsync', 'fsync=Something()', 'E520:') + call s:modeline_fails('grepprg', 'grepprg=Something()', 'E520:') + call s:modeline_fails('helpfile', 'helpfile=Something()', 'E520:') + call s:modeline_fails('imactivatefunc', 'imactivatefunc=Something()', 'E520:') + call s:modeline_fails('imstatusfunc', 'imstatusfunc=Something()', 'E520:') + call s:modeline_fails('imstyle', 'imstyle=Something()', 'E520:') + call s:modeline_fails('keywordprg', 'keywordprg=Something()', 'E520:') + call s:modeline_fails('langmap', 'langmap=Something()', 'E520:') + call s:modeline_fails('luadll', 'luadll=Something()', 'E520:') + call s:modeline_fails('makeef', 'makeef=Something()', 'E520:') + call s:modeline_fails('makeprg', 'makeprg=Something()', 'E520:') + call s:modeline_fails('mkspellmem', 'mkspellmem=Something()', 'E520:') + call s:modeline_fails('mzschemedll', 'mzschemedll=Something()', 'E520:') + call s:modeline_fails('mzschemegcdll', 'mzschemegcdll=Something()', 'E520:') + call s:modeline_fails('modelineexpr', 'modelineexpr=Something()', 'E520:') + call s:modeline_fails('omnifunc', 'omnifunc=Something()', 'E520:') + call s:modeline_fails('operatorfunc', 'operatorfunc=Something()', 'E520:') + call s:modeline_fails('perldll', 'perldll=Something()', 'E520:') + call s:modeline_fails('printdevice', 'printdevice=Something()', 'E520:') + call s:modeline_fails('patchexpr', 'patchexpr=Something()', 'E520:') + call s:modeline_fails('printexpr', 'printexpr=Something()', 'E520:') + call s:modeline_fails('pythondll', 'pythondll=Something()', 'E520:') + call s:modeline_fails('pythonhome', 'pythonhome=Something()', 'E520:') + call s:modeline_fails('pythonthreedll', 'pythonthreedll=Something()', 'E520:') + call s:modeline_fails('pythonthreehome', 'pythonthreehome=Something()', 'E520:') + call s:modeline_fails('pyxversion', 'pyxversion=Something()', 'E520:') + call s:modeline_fails('rubydll', 'rubydll=Something()', 'E520:') + call s:modeline_fails('runtimepath', 'runtimepath=Something()', 'E520:') + call s:modeline_fails('secure', 'secure=Something()', 'E520:') + call s:modeline_fails('shell', 'shell=Something()', 'E520:') + call s:modeline_fails('shellcmdflag', 'shellcmdflag=Something()', 'E520:') + call s:modeline_fails('shellpipe', 'shellpipe=Something()', 'E520:') + call s:modeline_fails('shellquote', 'shellquote=Something()', 'E520:') + call s:modeline_fails('shellredir', 'shellredir=Something()', 'E520:') + call s:modeline_fails('shellxquote', 'shellxquote=Something()', 'E520:') + call s:modeline_fails('spellfile', 'spellfile=Something()', 'E520:') + call s:modeline_fails('spellsuggest', 'spellsuggest=Something()', 'E520:') + call s:modeline_fails('tcldll', 'tcldll=Something()', 'E520:') + call s:modeline_fails('titleold', 'titleold=Something()', 'E520:') + call s:modeline_fails('viewdir', 'viewdir=Something()', 'E520:') + call s:modeline_fails('viminfo', 'viminfo=Something()', 'E520:') + call s:modeline_fails('viminfofile', 'viminfofile=Something()', 'E520:') + call s:modeline_fails('winptydll', 'winptydll=Something()', 'E520:') + call s:modeline_fails('undodir', 'undodir=Something()', 'E520:') + " only check a few terminal options + " Skip these since nvim doesn't support termcodes as options + "call s:modeline_fails('t_AB', 't_AB=Something()', 'E520:') + "call s:modeline_fails('t_ce', 't_ce=Something()', 'E520:') + "call s:modeline_fails('t_sr', 't_sr=Something()', 'E520:') + "call s:modeline_fails('t_8b', 't_8b=Something()', 'E520:') +endfunc + +func Test_modeline_fails_modelineexpr() + call s:modeline_fails('balloonexpr', 'balloonexpr=Something()', 'E992:') + call s:modeline_fails('foldexpr', 'foldexpr=Something()', 'E992:') + call s:modeline_fails('foldtext', 'foldtext=Something()', 'E992:') + call s:modeline_fails('formatexpr', 'formatexpr=Something()', 'E992:') + call s:modeline_fails('guitablabel', 'guitablabel=Something()', 'E992:') + call s:modeline_fails('iconstring', 'iconstring=Something()', 'E992:') + call s:modeline_fails('includeexpr', 'includeexpr=Something()', 'E992:') + call s:modeline_fails('indentexpr', 'indentexpr=Something()', 'E992:') + call s:modeline_fails('rulerformat', 'rulerformat=Something()', 'E992:') + call s:modeline_fails('statusline', 'statusline=Something()', 'E992:') + call s:modeline_fails('tabline', 'tabline=Something()', 'E992:') + call s:modeline_fails('titlestring', 'titlestring=Something()', 'E992:') endfunc diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index da87a22f1e..28576709a3 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -92,9 +92,6 @@ function! Test_path_keep_commas() endfunction func Test_filetype_valid() - if !has('autocmd') - return - endif set ft=valid_name call assert_equal("valid_name", &filetype) set ft=valid-name @@ -347,19 +344,62 @@ func Test_set_indentexpr() endfunc func Test_backupskip() + " Option 'backupskip' may contain several comma-separated path + " specifications if one or more of the environment variables TMPDIR, TMP, + " or TEMP is defined. To simplify testing, convert the string value into a + " list. + let bsklist = split(&bsk, ',') + if has("mac") - call assert_match('/private/tmp/\*', &bsk) + let found = (index(bsklist, '/private/tmp/*') >= 0) + call assert_true(found, '/private/tmp not in option bsk: ' . &bsk) elseif has("unix") - call assert_match('/tmp/\*', &bsk) + let found = (index(bsklist, '/tmp/*') >= 0) + call assert_true(found, '/tmp not in option bsk: ' . &bsk) endif - let bskvalue = substitute(&bsk, '\\', '/', 'g') - for var in ['$TEMPDIR', '$TMP', '$TEMP'] + " If our test platform is Windows, the path(s) in option bsk will use + " backslash for the path separator and the components could be in short + " (8.3) format. As such, we need to replace the backslashes with forward + " slashes and convert the path components to long format. The expand() + " function will do this but it cannot handle comma-separated paths. This is + " why bsk was converted from a string into a list of strings above. + " + " One final complication is that the wildcard "/*" is at the end of each + " path and so expand() might return a list of matching files. To prevent + " this, we need to remove the wildcard before calling expand() and then + " append it afterwards. + if has('win32') + let item_nbr = 0 + while item_nbr < len(bsklist) + let path_spec = bsklist[item_nbr] + let path_spec = strcharpart(path_spec, 0, strlen(path_spec)-2) + let path_spec = substitute(expand(path_spec), '\\', '/', 'g') + let bsklist[item_nbr] = path_spec . '/*' + let item_nbr += 1 + endwhile + endif + + " Option bsk will also include these environment variables if defined. + " If they're defined, verify they appear in the option value. + for var in ['$TMPDIR', '$TMP', '$TEMP'] if exists(var) let varvalue = substitute(expand(var), '\\', '/', 'g') - call assert_match(varvalue . '.\*', bskvalue) + let varvalue = substitute(varvalue, '/$', '', '') + let varvalue .= '/*' + let found = (index(bsklist, varvalue) >= 0) + call assert_true(found, var . ' (' . varvalue . ') not in option bsk: ' . &bsk) endif endfor + + " Duplicates should be filtered out (option has P_NODUP) + let backupskip = &backupskip + set backupskip= + set backupskip+=/test/dir + set backupskip+=/other/dir + set backupskip+=/test/dir + call assert_equal('/test/dir,/other/dir', &backupskip) + let &backupskip = backupskip endfunc func Test_copy_winopt() diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index fcb02d3437..ce0b8f1be8 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -3373,6 +3373,34 @@ func Test_lbuffer_with_bwipe() augroup END endfunc +" Tests for the ':filter /pat/ clist' command +func Test_filter_clist() + cexpr ['Xfile1:10:10:Line 10', 'Xfile2:15:15:Line 15'] + call assert_equal([' 2 Xfile2:15 col 15: Line 15'], + \ split(execute('filter /Line 15/ clist'), "\n")) + call assert_equal([' 1 Xfile1:10 col 10: Line 10'], + \ split(execute('filter /Xfile1/ clist'), "\n")) + call assert_equal([], split(execute('filter /abc/ clist'), "\n")) + + call setqflist([{'module' : 'abc', 'pattern' : 'pat1'}, + \ {'module' : 'pqr', 'pattern' : 'pat2'}], ' ') + call assert_equal([' 2 pqr:pat2: '], + \ split(execute('filter /pqr/ clist'), "\n")) + call assert_equal([' 1 abc:pat1: '], + \ split(execute('filter /pat1/ clist'), "\n")) +endfunc + +func Test_setloclist_in_aucmd() + " This was using freed memory. + augroup nasty + au * * call setloclist(0, [], 'f') + augroup END + lexpr "x" + augroup nasty + au! + augroup END +endfunc + " Tests for the "CTRL-W <CR>" command. func Xview_result_split_tests(cchar) call s:setup_commands(a:cchar) diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 87cad241e2..5e5ec96fd1 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -288,16 +288,53 @@ func Test_searchpair() new call setline(1, ['other code here', '', '[', '" cursor here', ']']) 4 - let a=searchpair('\[','',']','bW') + let a = searchpair('\[','',']','bW') call assert_equal(3, a) set nomagic 4 - let a=searchpair('\[','',']','bW') + let a = searchpair('\[','',']','bW') call assert_equal(3, a) set magic q! endfunc +func Test_searchpair_errors() + call assert_fails("call searchpair([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: using List as a String') + call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String') + call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String') + call assert_fails("call searchpair('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags') + call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 0, 99, 100)", 'E475: Invalid argument: 0') + call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99') + call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100') +endfunc + +func Test_searchpair_skip() + func Zero() + return 0 + endfunc + func Partial(x) + return a:x + endfunc + new + call setline(1, ['{', 'foo', 'foo', 'foo', '}']) + 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', '')) + 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', '0')) + 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', {-> 0})) + 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', function('Zero'))) + 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', function('Partial', [0]))) + bw! +endfunc + +func Test_searchpair_leak() + new + call setline(1, 'if one else another endif') + + " The error in the skip expression caused memory to leak. + call assert_fails("call searchpair('\\<if\\>', '\\<else\\>', '\\<endif\\>', '', '\"foo\" 2')", 'E15:') + + bwipe! +endfunc + func Test_searchc() " These commands used to cause memory overflow in searchc(). new diff --git a/src/nvim/testdir/test_source.vim b/src/nvim/testdir/test_source.vim new file mode 100644 index 0000000000..42ac0c4d0f --- /dev/null +++ b/src/nvim/testdir/test_source.vim @@ -0,0 +1,10 @@ +" Tests for the :source command. + +func Test_source_sandbox() + new + call writefile(["Ohello\<Esc>"], 'Xsourcehello') + source! Xsourcehello | echo + call assert_equal('hello', getline(1)) + call assert_fails('sandbox source! Xsourcehello', 'E48:') + bwipe! +endfunc diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index d0ecbef7e1..f3e40e1210 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1375,5 +1375,4 @@ endfunc "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=4 tw=80 fdm=marker -" vim: fdt=substitute(substitute(foldtext(),\ '\\%(^+--\\)\\@<=\\(\\s*\\)\\(.\\{-}\\)\:\ \\%(\"\ \\)\\=\\(Test\ \\d*\\)\:\\s*',\ '\\3\ (\\2)\:\ \\1',\ \"\"),\ '\\(Test\\s*\\)\\(\\d\\)\\D\\@=',\ '\\1\ \\2',\ "") "------------------------------------------------------------------------------- diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index d49025237b..abe79f6a4a 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -42,6 +42,22 @@ func Test_paste_end_of_line() set virtualedit= endfunc +func Test_replace_end_of_line() + new + set virtualedit=all + call setline(1, range(20)) + exe "normal! gg2jv10lr-" + call assert_equal(["1", "-----------", "3"], getline(2,4)) + if has('multi_byte') + call setline(1, range(20)) + exe "normal! gg2jv10lr\<c-k>hh" + call assert_equal(["1", "───────────", "3"], getline(2,4)) + endif + + bwipe! + set virtualedit= +endfunc + func Test_edit_CTRL_G() new set virtualedit=insert diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index 9da9df2150..6d88c0d8cd 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -33,7 +33,7 @@ func Test_writefile_fails_gently() endfunc func Test_writefile_fails_conversion() - if !has('multi_byte') || !has('iconv') + if !has('multi_byte') || !has('iconv') || system('uname -s') =~ 'SunOS' return endif " Without a backup file the write won't happen if there is a conversion diff --git a/src/nvim/version.c b/src/nvim/version.c index be7a2ffcad..571bd3cbcd 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -76,7 +76,7 @@ static const int included_patches[] = { // 1848, 1847, // 1846, - // 1845, + 1845, // 1844, 1843, // 1842, @@ -137,7 +137,7 @@ static const int included_patches[] = { 1787, // 1786, 1785, - // 1784, + 1784, // 1783, 1782, 1781, @@ -165,7 +165,7 @@ static const int included_patches[] = { // 1759, 1758, 1757, - // 1756, + 1756, 1755, // 1754, // 1753, @@ -186,7 +186,7 @@ static const int included_patches[] = { // 1738, 1737, 1736, - // 1735, + 1735, 1734, // 1733, // 1732, @@ -195,10 +195,10 @@ static const int included_patches[] = { // 1729, // 1728, 1727, - // 1726, + 1726, // 1725, // 1724, - // 1723, + 1723, // 1722, // 1721, // 1720, @@ -217,9 +217,9 @@ static const int included_patches[] = { 1707, // 1706, 1705, - // 1704, + 1704, // 1703, - // 1702, + 1702, 1701, 1700, 1699, @@ -232,7 +232,7 @@ static const int included_patches[] = { 1692, // 1691, // 1690, - // 1689, + 1689, // 1688, 1687, 1686, @@ -252,7 +252,7 @@ static const int included_patches[] = { 1672, // 1671, // 1670, - // 1669, + 1669, // 1668, // 1667, // 1666, @@ -372,7 +372,7 @@ static const int included_patches[] = { // 1552, // 1551, // 1550, - // 1549, + 1549, 1548, 1547, // 1546, @@ -403,9 +403,9 @@ static const int included_patches[] = { // 1521, // 1520, 1519, - // 1518, + 1518, 1517, - // 1516, + 1516, // 1515, 1514, 1513, @@ -507,9 +507,9 @@ static const int included_patches[] = { 1417, 1416, 1415, - // 1414, + 1414, 1413, - // 1412, + 1412, 1411, 1410, 1409, @@ -537,7 +537,7 @@ static const int included_patches[] = { 1387, // 1386, 1385, - // 1384, + 1384, 1383, // 1382, // 1381, @@ -616,7 +616,7 @@ static const int included_patches[] = { 1308, // 1307, 1306, - // 1305, + 1305, 1304, 1303, 1302, @@ -643,7 +643,7 @@ static const int included_patches[] = { 1281, 1280, 1279, - // 1278, + 1278, // 1277, // 1276, 1275, @@ -676,7 +676,7 @@ static const int included_patches[] = { 1248, 1247, // 1246, - // 1245, + 1245, // 1244, 1243, 1242, @@ -968,7 +968,7 @@ static const int included_patches[] = { // 956, 955, 954, - // 953, + 953, 952, 951, 950, @@ -988,9 +988,9 @@ static const int included_patches[] = { // 936, // 935, // 934, - // 933, + 933, // 932, - // 931, + 931, // 930, // 929, 928, @@ -1008,7 +1008,7 @@ static const int included_patches[] = { // 916, 915, // 914, - // 913, + 913, // 912, 911, // 910, @@ -1022,43 +1022,43 @@ static const int included_patches[] = { // 902, 901, 900, - // 899, - // 898, + 899, + 898, 897, - // 896, + 896, 895, 894, - // 893, - // 892, + 893, + 892, 891, 890, 889, 888, - // 887, + 887, 886, - // 885, - // 884, + 885, + 884, 883, - // 882, + 882, 881, 880, 879, 878, - // 877, + 877, 876, 875, - // 874, + 874, 873, 872, 871, - // 870, - // 869, + 870, + 869, 868, - // 867, + 867, 866, 865, - // 864, - // 863, + 864, + 863, 862, 861, 860, @@ -1100,12 +1100,12 @@ static const int included_patches[] = { 824, 823, 822, - // 821, + 821, 820, 819, - // 818, - // 817, - // 816, + 818, + 817, + 816, 815, 814, 813, @@ -1118,7 +1118,7 @@ static const int included_patches[] = { 806, 805, 804, - // 803, + 803, 802, 801, 800, |