aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/buffer_defs.h4
-rw-r--r--src/nvim/channel.c223
-rw-r--r--src/nvim/channel.h11
-rw-r--r--src/nvim/edit.c5
-rw-r--r--src/nvim/eval.c312
-rw-r--r--src/nvim/eval.lua4
-rw-r--r--src/nvim/eval/typval.c12
-rw-r--r--src/nvim/event/process.c10
-rw-r--r--src/nvim/ex_cmds.c7
-rw-r--r--src/nvim/ex_cmds.lua6
-rw-r--r--src/nvim/ex_cmds2.c2
-rw-r--r--src/nvim/ex_docmd.c2
-rw-r--r--src/nvim/generators/gen_options.lua1
-rw-r--r--src/nvim/getchar.c108
-rw-r--r--src/nvim/globals.h2
-rw-r--r--src/nvim/main.c10
-rw-r--r--src/nvim/misc1.c1
-rw-r--r--src/nvim/ops.c73
-rw-r--r--src/nvim/option.c6
-rw-r--r--src/nvim/option_defs.h1
-rw-r--r--src/nvim/options.lua21
-rw-r--r--src/nvim/os/dl.c1
-rw-r--r--src/nvim/os/pty_process_unix.c10
-rw-r--r--src/nvim/quickfix.c40
-rw-r--r--src/nvim/screen.c20
-rw-r--r--src/nvim/search.c18
-rw-r--r--src/nvim/testdir/Makefile2
-rw-r--r--src/nvim/testdir/runtest.vim1
-rw-r--r--src/nvim/testdir/test49.vim3
-rw-r--r--src/nvim/testdir/test_const.vim237
-rw-r--r--src/nvim/testdir/test_filter_map.vim5
-rw-r--r--src/nvim/testdir/test_functions.vim48
-rw-r--r--src/nvim/testdir/test_modeline.vim95
-rw-r--r--src/nvim/testdir/test_options.vim56
-rw-r--r--src/nvim/testdir/test_quickfix.vim28
-rw-r--r--src/nvim/testdir/test_search.vim41
-rw-r--r--src/nvim/testdir/test_source.vim10
-rw-r--r--src/nvim/testdir/test_vimscript.vim1
-rw-r--r--src/nvim/testdir/test_virtualedit.vim16
-rw-r--r--src/nvim/testdir/test_writefile.vim2
-rw-r--r--src/nvim/version.c88
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), &ltv, false,
+ arg = ex_let_one(skipwhite(arg + 1), &ltv, false, is_const,
(char_u *)"]", nextchars);
tv_clear(&ltv);
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,