aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.asan-blacklist4
-rw-r--r--src/nvim/CMakeLists.txt12
-rw-r--r--src/nvim/diff.c6
-rw-r--r--src/nvim/edit.c4
-rw-r--r--src/nvim/eval.c44
-rw-r--r--src/nvim/eval_defs.h10
-rw-r--r--src/nvim/event/loop.c30
-rw-r--r--src/nvim/event/loop.h20
-rw-r--r--src/nvim/event/multiqueue.c209
-rw-r--r--src/nvim/event/multiqueue.h19
-rw-r--r--src/nvim/event/process.c11
-rw-r--r--src/nvim/event/process.h2
-rw-r--r--src/nvim/event/queue.c208
-rw-r--r--src/nvim/event/queue.h19
-rw-r--r--src/nvim/event/signal.h2
-rw-r--r--src/nvim/event/socket.h2
-rw-r--r--src/nvim/event/stream.h2
-rw-r--r--src/nvim/event/time.c2
-rw-r--r--src/nvim/event/time.h2
-rw-r--r--src/nvim/ex_cmds.c14
-rw-r--r--src/nvim/ex_getln.c64
-rw-r--r--src/nvim/globals.h6
-rw-r--r--src/nvim/log.c12
-rw-r--r--src/nvim/main.c4
-rw-r--r--src/nvim/mbyte.c19
-rw-r--r--src/nvim/msgpack_rpc/channel.c13
-rw-r--r--src/nvim/normal.c10
-rw-r--r--src/nvim/option.c30
-rw-r--r--src/nvim/option_defs.h37
-rw-r--r--src/nvim/options.lua8
-rw-r--r--src/nvim/os/input.c6
-rw-r--r--src/nvim/os/shell.c12
-rw-r--r--src/nvim/rbuffer.h2
-rw-r--r--src/nvim/state.c6
-rw-r--r--src/nvim/terminal.c6
-rw-r--r--src/nvim/testdir/runtest.vim102
-rw-r--r--src/nvim/testdir/test_help_tagjump.vim115
-rw-r--r--src/nvim/testdir/test_popup.vim35
-rw-r--r--src/nvim/tui/input.c2
-rw-r--r--src/nvim/tui/tui.c56
-rw-r--r--src/nvim/tui/ui_bridge.c (renamed from src/nvim/ui_bridge.c)8
-rw-r--r--src/nvim/tui/ui_bridge.h (renamed from src/nvim/ui_bridge.h)10
-rw-r--r--src/nvim/ui.c29
-rw-r--r--src/nvim/version.c30
44 files changed, 736 insertions, 508 deletions
diff --git a/src/.asan-blacklist b/src/.asan-blacklist
index 63558170b3..7636f8fa82 100644
--- a/src/.asan-blacklist
+++ b/src/.asan-blacklist
@@ -1,3 +1,3 @@
-# libuv queue.h pointer arithmetic is not accepted by asan
-fun:queue_node_data
+# multiqueue.h pointer arithmetic is not accepted by asan
+fun:multiqueue_node_data
fun:dictwatcher_node_data
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 7c2c2feebc..59582d0734 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -36,9 +36,7 @@ set(EVAL_DEFS_FILE ${PROJECT_SOURCE_DIR}/src/nvim/eval.lua)
set(OPTIONS_LIST_FILE ${PROJECT_SOURCE_DIR}/src/nvim/options.lua)
set(UNICODE_TABLES_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genunicodetables.lua)
set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode)
-set(UNICODEDATA_FILE ${UNICODE_DIR}/UnicodeData.txt)
-set(CASEFOLDING_FILE ${UNICODE_DIR}/CaseFolding.txt)
-set(EASTASIANWIDTH_FILE ${UNICODE_DIR}/EastAsianWidth.txt)
+file(GLOB UNICODE_FILES ${UNICODE_DIR}/*.txt)
set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h)
include_directories(${GENERATED_DIR})
@@ -192,15 +190,11 @@ endforeach()
add_custom_command(OUTPUT ${GENERATED_UNICODE_TABLES}
COMMAND ${LUA_PRG} ${UNICODE_TABLES_GENERATOR}
- ${UNICODEDATA_FILE}
- ${CASEFOLDING_FILE}
- ${EASTASIANWIDTH_FILE}
+ ${UNICODE_DIR}
${GENERATED_UNICODE_TABLES}
DEPENDS
${UNICODE_TABLES_GENERATOR}
- ${UNICODEDATA_FILE}
- ${CASEFOLDING_FILE}
- ${EASTASIANWIDTH_FILE}
+ ${UNICODE_FILES}
)
add_custom_command(OUTPUT ${GENERATED_API_DISPATCH} ${API_METADATA}
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index ef5acf4845..f7b96ba3e1 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -906,8 +906,7 @@ void ex_diffpatch(exarg_T *eap)
eval_patch((char *) tmp_orig, (char *) eap->arg, (char *) tmp_new);
#endif // ifdef UNIX
} else {
- // Build the patch command and execute it. Ignore errors. Switch to
- // cooked mode to allow the user to respond to prompts.
+ // Build the patch command and execute it. Ignore errors.
#ifdef UNIX
vim_snprintf((char *)buf, buflen, "patch -o %s %s < \"%s\"",
tmp_new, tmp_orig, fullname != NULL ? fullname : eap->arg);
@@ -915,8 +914,7 @@ void ex_diffpatch(exarg_T *eap)
vim_snprintf((char *)buf, buflen, "patch -o %s %s < \"%s\"",
tmp_new, tmp_orig, eap->arg);
#endif // ifdef UNIX
- // Avoid ShellCmdPost stuff
- block_autocmds();
+ block_autocmds(); // Avoid ShellCmdPost stuff
(void)call_shell(buf, kShellOptFilter, NULL);
unblock_autocmds();
}
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 18d5ea533d..892748ff5c 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -502,7 +502,7 @@ static int insert_check(VimState *state)
Insstart_orig = Insstart;
}
- if (stop_insert_mode) {
+ if (stop_insert_mode && !pum_visible()) {
// ":stopinsert" used or 'insertmode' reset
s->count = 0;
return 0; // exit insert mode
@@ -962,7 +962,7 @@ static int insert_handle_key(InsertState *s)
break;
case K_EVENT: // some event
- queue_process_events(main_loop.events);
+ multiqueue_process_events(main_loop.events);
break;
case K_FOCUSGAINED: // Neovim has been given focus
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index c762ce9fff..0b1fd6670e 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -292,8 +292,8 @@ typedef enum {
.vv_di = { \
.di_tv = { .v_type = type }, \
.di_flags = 0, \
+ .di_key = { 0 }, \
}, \
- .vv_filler = { 0 }, \
.vv_flags = flags, \
}
@@ -303,8 +303,7 @@ typedef enum {
// variables with the VV_ defines.
static struct vimvar {
char *vv_name; ///< Name of the variable, without v:.
- dictitem_T vv_di; ///< Value of the variable, with name.
- char vv_filler[16]; ///< Space for longest name from below.
+ dictitem16_T vv_di; ///< Value and name for key (max 16 chars)
char vv_flags; ///< Flags: #VV_COMPAT, #VV_RO, #VV_RO_SBX.
} vimvars[] =
{
@@ -416,7 +415,7 @@ typedef struct {
dict_T *self;
int *status_ptr;
uint64_t id;
- Queue *events;
+ MultiQueue *events;
} TerminalJobData;
typedef struct dict_watcher {
@@ -514,6 +513,7 @@ void eval_init(void)
for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) {
p = &vimvars[i];
+ assert(STRLEN(p->vv_name) <= 16);
STRCPY(p->vv_di.di_key, p->vv_name);
if (p->vv_flags & VV_RO)
p->vv_di.di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
@@ -2684,6 +2684,10 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx)
} else if (c == '=') {
got_eq = TRUE;
xp->xp_context = EXPAND_EXPRESSION;
+ } else if (c == '#'
+ && xp->xp_context == EXPAND_EXPRESSION) {
+ // Autoload function/variable contains '#'
+ break;
} else if ((c == '<' || c == '#')
&& xp->xp_context == EXPAND_FUNCTIONS
&& vim_strchr(xp->xp_pattern, '(') == NULL) {
@@ -4948,7 +4952,8 @@ static list_T *rettv_list_alloc(typval_T *rettv)
list_T *l = list_alloc();
rettv->vval.v_list = l;
rettv->v_type = VAR_LIST;
- ++l->lv_refcount;
+ rettv->v_lock = VAR_UNLOCKED;
+ l->lv_refcount++;
return l;
}
@@ -6128,7 +6133,8 @@ static void rettv_dict_alloc(typval_T *rettv)
rettv->vval.v_dict = d;
rettv->v_type = VAR_DICT;
- ++d->dv_refcount;
+ rettv->v_lock = VAR_UNLOCKED;
+ d->dv_refcount++;
}
/// Clear all the keys of a Dictionary. "d" remains a valid empty Dictionary.
@@ -11686,7 +11692,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr)
list_T *rv = list_alloc();
ui_busy_start();
- Queue *waiting_jobs = queue_new_parent(loop_on_put, &main_loop);
+ MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop);
// For each item in the input list append an integer to the output list. -3
// is used to represent an invalid job id, -2 is for a interrupted job and
// -1 for jobs that were skipped or timed out.
@@ -11702,8 +11708,8 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr)
data->status_ptr = &rv->lv_last->li_tv.vval.v_number;
// Process any pending events for the job because we'll temporarily
// replace the parent queue
- queue_process_events(data->events);
- queue_replace_parent(data->events, waiting_jobs);
+ multiqueue_process_events(data->events);
+ multiqueue_replace_parent(data->events, waiting_jobs);
}
}
@@ -11763,11 +11769,11 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr)
continue;
}
// restore the parent queue for the job
- queue_process_events(data->events);
- queue_replace_parent(data->events, main_loop.events);
+ multiqueue_process_events(data->events);
+ multiqueue_replace_parent(data->events, main_loop.events);
}
- queue_free(waiting_jobs);
+ multiqueue_free(waiting_jobs);
ui_busy_stop();
rv->lv_refcount++;
rettv->v_type = VAR_LIST;
@@ -16394,7 +16400,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr)
timer->callback = func;
time_watcher_init(&main_loop, &timer->tw, timer);
- timer->tw.events = queue_new_child(main_loop.events);
+ timer->tw.events = multiqueue_new_child(main_loop.events);
// if main loop is blocked, don't queue up multiple events
timer->tw.blockable = true;
time_watcher_start(&timer->tw, timer_due_cb, timeout,
@@ -16471,7 +16477,7 @@ static void timer_stop(timer_T *timer)
static void timer_close_cb(TimeWatcher *tw, void *data)
{
timer_T *timer = (timer_T *)data;
- queue_free(timer->tw.events);
+ multiqueue_free(timer->tw.events);
user_func_unref(timer->callback);
pmap_del(uint64_t)(timers, timer->timer_id);
timer_decref(timer);
@@ -21719,7 +21725,7 @@ static inline TerminalJobData *common_job_init(char **argv,
data->on_stderr = on_stderr;
data->on_exit = on_exit;
data->self = self;
- data->events = queue_new_child(main_loop.events);
+ data->events = multiqueue_new_child(main_loop.events);
data->rpc = rpc;
if (pty) {
data->proc.pty = pty_process_init(&main_loop, data);
@@ -21828,7 +21834,7 @@ static inline void free_term_job_data_event(void **argv)
if (data->self) {
dict_unref(data->self);
}
- queue_free(data->events);
+ multiqueue_free(data->events);
pmap_del(uint64_t)(jobs, data->id);
xfree(data);
}
@@ -21837,7 +21843,7 @@ static inline void free_term_job_data(TerminalJobData *data)
{
// data->queue may still be used after this function returns(process_wait), so
// only free in the next event loop iteration
- queue_put(main_loop.fast_events, free_term_job_data_event, 1, data);
+ multiqueue_put(main_loop.fast_events, free_term_job_data_event, 1, data);
}
// vimscript job callbacks must be executed on Nvim main loop
@@ -21956,7 +21962,7 @@ static inline void term_delayed_free(void **argv)
{
TerminalJobData *j = argv[0];
if (j->in.pending_reqs || j->out.pending_reqs || j->err.pending_reqs) {
- queue_put(j->events, term_delayed_free, 1, j);
+ multiqueue_put(j->events, term_delayed_free, 1, j);
return;
}
@@ -21971,7 +21977,7 @@ static void term_close(void *d)
data->exited = true;
process_stop((Process *)&data->proc);
}
- queue_put(data->events, term_delayed_free, 1, data);
+ multiqueue_put(data->events, term_delayed_free, 1, data);
}
static void term_job_data_decref(TerminalJobData *data)
diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h
index 884c987f10..91d544074f 100644
--- a/src/nvim/eval_defs.h
+++ b/src/nvim/eval_defs.h
@@ -114,6 +114,16 @@ struct dictitem_S {
typedef struct dictitem_S dictitem_T;
+/// A dictitem with a 16 character key (plus NUL)
+struct dictitem16_S {
+ typval_T di_tv; ///< type and value of the variable
+ char_u di_flags; ///< flags (only used for variable)
+ char_u di_key[17]; ///< key
+};
+
+typedef struct dictitem16_S dictitem16_T;
+
+
#define DI_FLAGS_RO 1 // "di_flags" value: read-only variable
#define DI_FLAGS_RO_SBX 2 // "di_flags" value: read-only in the sandbox
#define DI_FLAGS_FIX 4 // "di_flags" value: fixed: no :unlet or remove()
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index 6f3e6b9253..d562ac1ed3 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -18,9 +18,9 @@ void loop_init(Loop *loop, void *data)
loop->uv.data = loop;
loop->children = kl_init(WatcherPtr);
loop->children_stop_requests = 0;
- loop->events = queue_new_parent(loop_on_put, loop);
- loop->fast_events = queue_new_child(loop->events);
- loop->thread_events = queue_new_parent(NULL, NULL);
+ loop->events = multiqueue_new_parent(loop_on_put, loop);
+ loop->fast_events = multiqueue_new_child(loop->events);
+ loop->thread_events = multiqueue_new_parent(NULL, NULL);
uv_mutex_init(&loop->mutex);
uv_async_init(&loop->uv, &loop->async, async_cb);
uv_signal_init(&loop->uv, &loop->children_watcher);
@@ -53,19 +53,19 @@ void loop_poll_events(Loop *loop, int ms)
}
loop->recursive--; // Can re-enter uv_run now
- queue_process_events(loop->fast_events);
+ multiqueue_process_events(loop->fast_events);
}
// Schedule an event from another thread
void loop_schedule(Loop *loop, Event event)
{
uv_mutex_lock(&loop->mutex);
- queue_put_event(loop->thread_events, event);
+ multiqueue_put_event(loop->thread_events, event);
uv_async_send(&loop->async);
uv_mutex_unlock(&loop->mutex);
}
-void loop_on_put(Queue *queue, void *data)
+void loop_on_put(MultiQueue *queue, void *data)
{
Loop *loop = data;
// Sometimes libuv will run pending callbacks(timer for example) before
@@ -76,7 +76,7 @@ void loop_on_put(Queue *queue, void *data)
uv_stop(&loop->uv);
}
-void loop_close(Loop *loop)
+void loop_close(Loop *loop, bool wait)
{
uv_mutex_destroy(&loop->mutex);
uv_close((uv_handle_t *)&loop->children_watcher, NULL);
@@ -84,11 +84,11 @@ void loop_close(Loop *loop)
uv_close((uv_handle_t *)&loop->poll_timer, NULL);
uv_close((uv_handle_t *)&loop->async, NULL);
do {
- uv_run(&loop->uv, UV_RUN_DEFAULT);
- } while (uv_loop_close(&loop->uv));
- queue_free(loop->fast_events);
- queue_free(loop->thread_events);
- queue_free(loop->events);
+ uv_run(&loop->uv, wait ? UV_RUN_DEFAULT : UV_RUN_NOWAIT);
+ } while (uv_loop_close(&loop->uv) && wait);
+ multiqueue_free(loop->fast_events);
+ multiqueue_free(loop->thread_events);
+ multiqueue_free(loop->events);
kl_destroy(WatcherPtr, loop->children);
}
@@ -96,9 +96,9 @@ static void async_cb(uv_async_t *handle)
{
Loop *l = handle->loop->data;
uv_mutex_lock(&l->mutex);
- while (!queue_empty(l->thread_events)) {
- Event ev = queue_get(l->thread_events);
- queue_put_event(l->fast_events, ev);
+ while (!multiqueue_empty(l->thread_events)) {
+ Event ev = multiqueue_get(l->thread_events);
+ multiqueue_put_event(l->fast_events, ev);
}
uv_mutex_unlock(&l->mutex);
}
diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h
index 407aa4245f..e7d7bdd483 100644
--- a/src/nvim/event/loop.h
+++ b/src/nvim/event/loop.h
@@ -7,7 +7,7 @@
#include "nvim/lib/klist.h"
#include "nvim/os/time.h"
-#include "nvim/event/queue.h"
+#include "nvim/event/multiqueue.h"
typedef void * WatcherPtr;
@@ -16,7 +16,7 @@ KLIST_INIT(WatcherPtr, WatcherPtr, _noop)
typedef struct loop {
uv_loop_t uv;
- Queue *events, *fast_events, *thread_events;
+ MultiQueue *events, *fast_events, *thread_events;
klist_t(WatcherPtr) *children;
uv_signal_t children_watcher;
uv_timer_t children_kill_timer, poll_timer;
@@ -26,10 +26,10 @@ typedef struct loop {
int recursive;
} Loop;
-#define CREATE_EVENT(queue, handler, argc, ...) \
+#define CREATE_EVENT(multiqueue, handler, argc, ...) \
do { \
- if (queue) { \
- queue_put((queue), (handler), argc, __VA_ARGS__); \
+ if (multiqueue) { \
+ multiqueue_put((multiqueue), (handler), argc, __VA_ARGS__); \
} else { \
void *argv[argc] = { __VA_ARGS__ }; \
(handler)(argv); \
@@ -37,12 +37,12 @@ typedef struct loop {
} while (0)
// Poll for events until a condition or timeout
-#define LOOP_PROCESS_EVENTS_UNTIL(loop, queue, timeout, condition) \
+#define LOOP_PROCESS_EVENTS_UNTIL(loop, multiqueue, timeout, condition) \
do { \
int remaining = timeout; \
uint64_t before = (remaining > 0) ? os_hrtime() : 0; \
while (!(condition)) { \
- LOOP_PROCESS_EVENTS(loop, queue, remaining); \
+ LOOP_PROCESS_EVENTS(loop, multiqueue, remaining); \
if (remaining == 0) { \
break; \
} else if (remaining > 0) { \
@@ -56,10 +56,10 @@ typedef struct loop {
} \
} while (0)
-#define LOOP_PROCESS_EVENTS(loop, queue, timeout) \
+#define LOOP_PROCESS_EVENTS(loop, multiqueue, timeout) \
do { \
- if (queue && !queue_empty(queue)) { \
- queue_process_events(queue); \
+ if (multiqueue && !multiqueue_empty(multiqueue)) { \
+ multiqueue_process_events(multiqueue); \
} else { \
loop_poll_events(loop, timeout); \
} \
diff --git a/src/nvim/event/multiqueue.c b/src/nvim/event/multiqueue.c
new file mode 100644
index 0000000000..7efdfc4cad
--- /dev/null
+++ b/src/nvim/event/multiqueue.c
@@ -0,0 +1,209 @@
+// Multi-level queue for selective async event processing. Multiqueue supports
+// a parent-child relationship with the following properties:
+//
+// - pushing a node to a child queue will push a corresponding link node to the
+// parent queue
+// - removing a link node from a parent queue will remove the next node
+// in the linked child queue
+// - removing a node from a child queue will remove the corresponding link node
+// in the parent queue
+//
+// These properties allow Nvim to organize and process events from different
+// sources with a certain degree of control. How the multiqueue is used:
+//
+// +----------------+
+// | Main loop |
+// +----------------+
+// ^
+// |
+// +----------------+
+// +-------------->| Event loop |<------------+
+// | +--+-------------+ |
+// | ^ ^ |
+// | | | |
+// +-----------+ +-----------+ +---------+ +---------+
+// | Channel 1 | | Channel 2 | | Job 1 | | Job 2 |
+// +-----------+ +-----------+ +---------+ +---------+
+//
+//
+// The lower boxes represent event emitters, each with its own private queue
+// having the event loop queue as the parent.
+//
+// When idle, the main loop spins the event loop which queues events from many
+// sources (channels, jobs, user...). Each event emitter pushes events to its
+// private queue which is propagated to the event loop queue. When the main loop
+// consumes an event, the corresponding event is removed from the emitter's
+// queue.
+//
+// The main reason for this queue hierarchy is to allow focusing on a single
+// event emitter while blocking the main loop. For example, if the `jobwait`
+// VimL function is called on job1, the main loop will temporarily stop polling
+// the event loop queue and poll job1 queue instead. Same with channels, when
+// calling `rpcrequest` we want to temporarily stop processing events from
+// other sources and focus on a specific channel.
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+
+#include <uv.h>
+
+#include "nvim/event/multiqueue.h"
+#include "nvim/memory.h"
+#include "nvim/os/time.h"
+
+typedef struct multiqueue_item MultiQueueItem;
+struct multiqueue_item {
+ union {
+ MultiQueue *queue;
+ struct {
+ Event event;
+ MultiQueueItem *parent;
+ } item;
+ } data;
+ bool link; // true: current item is just a link to a node in a child queue
+ QUEUE node;
+};
+
+struct multiqueue {
+ MultiQueue *parent;
+ QUEUE headtail;
+ put_callback put_cb;
+ void *data;
+};
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "event/multiqueue.c.generated.h"
+#endif
+
+static Event NILEVENT = { .handler = NULL, .argv = {NULL} };
+
+MultiQueue *multiqueue_new_parent(put_callback put_cb, void *data)
+{
+ return multiqueue_new(NULL, put_cb, data);
+}
+
+MultiQueue *multiqueue_new_child(MultiQueue *parent)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(!parent->parent);
+ return multiqueue_new(parent, NULL, NULL);
+}
+
+static MultiQueue *multiqueue_new(MultiQueue *parent, put_callback put_cb,
+ void *data)
+{
+ MultiQueue *rv = xmalloc(sizeof(MultiQueue));
+ QUEUE_INIT(&rv->headtail);
+ rv->parent = parent;
+ rv->put_cb = put_cb;
+ rv->data = data;
+ return rv;
+}
+
+void multiqueue_free(MultiQueue *this)
+{
+ assert(this);
+ while (!QUEUE_EMPTY(&this->headtail)) {
+ QUEUE *q = QUEUE_HEAD(&this->headtail);
+ MultiQueueItem *item = multiqueue_node_data(q);
+ if (this->parent) {
+ QUEUE_REMOVE(&item->data.item.parent->node);
+ xfree(item->data.item.parent);
+ }
+ QUEUE_REMOVE(q);
+ xfree(item);
+ }
+
+ xfree(this);
+}
+
+Event multiqueue_get(MultiQueue *this)
+{
+ return multiqueue_empty(this) ? NILEVENT : multiqueue_remove(this);
+}
+
+void multiqueue_put_event(MultiQueue *this, Event event)
+{
+ assert(this);
+ multiqueue_push(this, event);
+ if (this->parent && this->parent->put_cb) {
+ this->parent->put_cb(this->parent, this->parent->data);
+ }
+}
+
+void multiqueue_process_events(MultiQueue *this)
+{
+ assert(this);
+ while (!multiqueue_empty(this)) {
+ Event event = multiqueue_get(this);
+ if (event.handler) {
+ event.handler(event.argv);
+ }
+ }
+}
+
+bool multiqueue_empty(MultiQueue *this)
+{
+ assert(this);
+ return QUEUE_EMPTY(&this->headtail);
+}
+
+void multiqueue_replace_parent(MultiQueue *this, MultiQueue *new_parent)
+{
+ assert(multiqueue_empty(this));
+ this->parent = new_parent;
+}
+
+static Event multiqueue_remove(MultiQueue *this)
+{
+ assert(!multiqueue_empty(this));
+ QUEUE *h = QUEUE_HEAD(&this->headtail);
+ QUEUE_REMOVE(h);
+ MultiQueueItem *item = multiqueue_node_data(h);
+ Event rv;
+
+ if (item->link) {
+ assert(!this->parent);
+ // remove the next node in the linked queue
+ MultiQueue *linked = item->data.queue;
+ assert(!multiqueue_empty(linked));
+ MultiQueueItem *child =
+ multiqueue_node_data(QUEUE_HEAD(&linked->headtail));
+ QUEUE_REMOVE(&child->node);
+ rv = child->data.item.event;
+ xfree(child);
+ } else {
+ if (this->parent) {
+ // remove the corresponding link node in the parent queue
+ QUEUE_REMOVE(&item->data.item.parent->node);
+ xfree(item->data.item.parent);
+ }
+ rv = item->data.item.event;
+ }
+
+ xfree(item);
+ return rv;
+}
+
+static void multiqueue_push(MultiQueue *this, Event event)
+{
+ MultiQueueItem *item = xmalloc(sizeof(MultiQueueItem));
+ item->link = false;
+ item->data.item.event = event;
+ QUEUE_INSERT_TAIL(&this->headtail, &item->node);
+ if (this->parent) {
+ // push link node to the parent queue
+ item->data.item.parent = xmalloc(sizeof(MultiQueueItem));
+ item->data.item.parent->link = true;
+ item->data.item.parent->data.queue = this;
+ QUEUE_INSERT_TAIL(&this->parent->headtail, &item->data.item.parent->node);
+ }
+}
+
+static MultiQueueItem *multiqueue_node_data(QUEUE *q)
+{
+ return QUEUE_DATA(q, MultiQueueItem, node);
+}
diff --git a/src/nvim/event/multiqueue.h b/src/nvim/event/multiqueue.h
new file mode 100644
index 0000000000..def6b95a10
--- /dev/null
+++ b/src/nvim/event/multiqueue.h
@@ -0,0 +1,19 @@
+#ifndef NVIM_EVENT_MULTIQUEUE_H
+#define NVIM_EVENT_MULTIQUEUE_H
+
+#include <uv.h>
+
+#include "nvim/event/defs.h"
+#include "nvim/lib/queue.h"
+
+typedef struct multiqueue MultiQueue;
+typedef void (*put_callback)(MultiQueue *multiq, void *data);
+
+#define multiqueue_put(q, h, ...) \
+ multiqueue_put_event(q, event_create(1, h, __VA_ARGS__));
+
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "event/multiqueue.h.generated.h"
+#endif
+#endif // NVIM_EVENT_MULTIQUEUE_H
diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c
index 51f20b7eac..39dd5fd55a 100644
--- a/src/nvim/event/process.c
+++ b/src/nvim/event/process.c
@@ -126,7 +126,7 @@ void process_teardown(Loop *loop) FUNC_ATTR_NONNULL_ALL
// Wait until all children exit and all close events are processed.
LOOP_PROCESS_EVENTS_UNTIL(
loop, loop->events, -1,
- kl_empty(loop->children) && queue_empty(loop->events));
+ kl_empty(loop->children) && multiqueue_empty(loop->events));
pty_process_teardown(loop);
}
@@ -163,7 +163,8 @@ void process_close_err(Process *proc) FUNC_ATTR_NONNULL_ALL
/// indistinguishable from the process returning -1 by itself. Which
/// is possible on some OS. Returns -2 if an user has interruped the
/// wait.
-int process_wait(Process *proc, int ms, Queue *events) FUNC_ATTR_NONNULL_ARG(1)
+int process_wait(Process *proc, int ms, MultiQueue *events)
+ FUNC_ATTR_NONNULL_ARG(1)
{
// The default status is -1, which represents a timeout
int status = -1;
@@ -208,7 +209,7 @@ int process_wait(Process *proc, int ms, Queue *events) FUNC_ATTR_NONNULL_ARG(1)
decref(proc);
if (events) {
// the decref call created an exit event, process it now
- queue_process_events(events);
+ multiqueue_process_events(events);
}
} else {
proc->refcount--;
@@ -362,7 +363,7 @@ static void flush_stream(Process *proc, Stream *stream)
// Poll for data and process the generated events.
loop_poll_events(proc->loop, 0);
if (proc->events) {
- queue_process_events(proc->events);
+ multiqueue_process_events(proc->events);
}
// Stream can be closed if it is empty.
@@ -402,7 +403,7 @@ static void on_process_exit(Process *proc)
// OS. We are still in the libuv loop, so we cannot call code that polls for
// more data directly. Instead delay the reading after the libuv loop by
// queueing process_close_handles() as an event.
- Queue *queue = proc->events ? proc->events : loop->events;
+ MultiQueue *queue = proc->events ? proc->events : loop->events;
CREATE_EVENT(queue, process_close_handles, 1, proc);
}
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
index a4c6e7eeb2..5cbf7f9ce7 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/process.h
@@ -27,7 +27,7 @@ struct process {
process_exit_cb cb;
internal_process_cb internal_exit_cb, internal_close_cb;
bool closed, term_sent, detach;
- Queue *events;
+ MultiQueue *events;
};
static inline Process process_init(Loop *loop, ProcessType type, void *data)
diff --git a/src/nvim/event/queue.c b/src/nvim/event/queue.c
deleted file mode 100644
index c5ef22d426..0000000000
--- a/src/nvim/event/queue.c
+++ /dev/null
@@ -1,208 +0,0 @@
-// Queue for selective async event processing. Instances of this queue support a
-// parent/child relationship with the following properties:
-//
-// - pushing a node to a child queue will push a corresponding link node to the
-// parent queue
-// - removing a link node from a parent queue will remove the next node
-// in the linked child queue
-// - removing a node from a child queue will remove the corresponding link node
-// in the parent queue
-//
-// These properties allow neovim to organize and process events from different
-// sources with a certain degree of control. Here's how the queue is used:
-//
-// +----------------+
-// | Main loop |
-// +----------------+
-// ^
-// |
-// +----------------+
-// +-------------->| Event loop |<------------+
-// | +--+-------------+ |
-// | ^ ^ |
-// | | | |
-// +-----------+ +-----------+ +---------+ +---------+
-// | Channel 1 | | Channel 2 | | Job 1 | | Job 2 |
-// +-----------+ +-----------+ +---------+ +---------+
-//
-//
-// In the above diagram, the lower boxes represents event emitters, each with
-// it's own private queue that have the event loop queue as the parent.
-//
-// When idle, the main loop spins the event loop which queues events from many
-// sources(channels, jobs, user...). Each event emitter pushes events to its own
-// private queue which is propagated to the event loop queue. When the main loop
-// consumes an event, the corresponding event is removed from the emitter's
-// queue.
-//
-// The main reason for this queue hierarchy is to allow focusing on a single
-// event emitter while blocking the main loop. For example, if the `jobwait`
-// vimscript function is called on job1, the main loop will temporarily stop
-// polling the event loop queue and poll job1 queue instead. Same with channels,
-// when calling `rpcrequest`, we want to temporarily stop processing events from
-// other sources and focus on a specific channel.
-
-#include <assert.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stdint.h>
-
-
-#include <uv.h>
-
-#include "nvim/event/queue.h"
-#include "nvim/memory.h"
-#include "nvim/os/time.h"
-
-typedef struct queue_item QueueItem;
-struct queue_item {
- union {
- Queue *queue;
- struct {
- Event event;
- QueueItem *parent;
- } item;
- } data;
- bool link; // this is just a link to a node in a child queue
- QUEUE node;
-};
-
-struct queue {
- Queue *parent;
- QUEUE headtail;
- put_callback put_cb;
- void *data;
-};
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "event/queue.c.generated.h"
-#endif
-
-static Event NILEVENT = {.handler = NULL, .argv = {NULL}};
-
-Queue *queue_new_parent(put_callback put_cb, void *data)
-{
- return queue_new(NULL, put_cb, data);
-}
-
-Queue *queue_new_child(Queue *parent)
- FUNC_ATTR_NONNULL_ALL
-{
- assert(!parent->parent);
- return queue_new(parent, NULL, NULL);
-}
-
-static Queue *queue_new(Queue *parent, put_callback put_cb, void *data)
-{
- Queue *rv = xmalloc(sizeof(Queue));
- QUEUE_INIT(&rv->headtail);
- rv->parent = parent;
- rv->put_cb = put_cb;
- rv->data = data;
- return rv;
-}
-
-void queue_free(Queue *queue)
-{
- assert(queue);
- while (!QUEUE_EMPTY(&queue->headtail)) {
- QUEUE *q = QUEUE_HEAD(&queue->headtail);
- QueueItem *item = queue_node_data(q);
- if (queue->parent) {
- QUEUE_REMOVE(&item->data.item.parent->node);
- xfree(item->data.item.parent);
- }
- QUEUE_REMOVE(q);
- xfree(item);
- }
-
- xfree(queue);
-}
-
-Event queue_get(Queue *queue)
-{
- return queue_empty(queue) ? NILEVENT : queue_remove(queue);
-}
-
-void queue_put_event(Queue *queue, Event event)
-{
- assert(queue);
- queue_push(queue, event);
- if (queue->parent && queue->parent->put_cb) {
- queue->parent->put_cb(queue->parent, queue->parent->data);
- }
-}
-
-void queue_process_events(Queue *queue)
-{
- assert(queue);
- while (!queue_empty(queue)) {
- Event event = queue_get(queue);
- if (event.handler) {
- event.handler(event.argv);
- }
- }
-}
-
-bool queue_empty(Queue *queue)
-{
- assert(queue);
- return QUEUE_EMPTY(&queue->headtail);
-}
-
-void queue_replace_parent(Queue *queue, Queue *new_parent)
-{
- assert(queue_empty(queue));
- queue->parent = new_parent;
-}
-
-static Event queue_remove(Queue *queue)
-{
- assert(!queue_empty(queue));
- QUEUE *h = QUEUE_HEAD(&queue->headtail);
- QUEUE_REMOVE(h);
- QueueItem *item = queue_node_data(h);
- Event rv;
-
- if (item->link) {
- assert(!queue->parent);
- // remove the next node in the linked queue
- Queue *linked = item->data.queue;
- assert(!queue_empty(linked));
- QueueItem *child =
- queue_node_data(QUEUE_HEAD(&linked->headtail));
- QUEUE_REMOVE(&child->node);
- rv = child->data.item.event;
- xfree(child);
- } else {
- if (queue->parent) {
- // remove the corresponding link node in the parent queue
- QUEUE_REMOVE(&item->data.item.parent->node);
- xfree(item->data.item.parent);
- }
- rv = item->data.item.event;
- }
-
- xfree(item);
- return rv;
-}
-
-static void queue_push(Queue *queue, Event event)
-{
- QueueItem *item = xmalloc(sizeof(QueueItem));
- item->link = false;
- item->data.item.event = event;
- QUEUE_INSERT_TAIL(&queue->headtail, &item->node);
- if (queue->parent) {
- // push link node to the parent queue
- item->data.item.parent = xmalloc(sizeof(QueueItem));
- item->data.item.parent->link = true;
- item->data.item.parent->data.queue = queue;
- QUEUE_INSERT_TAIL(&queue->parent->headtail, &item->data.item.parent->node);
- }
-}
-
-static QueueItem *queue_node_data(QUEUE *q)
-{
- return QUEUE_DATA(q, QueueItem, node);
-}
diff --git a/src/nvim/event/queue.h b/src/nvim/event/queue.h
deleted file mode 100644
index 85fc59f8b2..0000000000
--- a/src/nvim/event/queue.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#ifndef NVIM_EVENT_QUEUE_H
-#define NVIM_EVENT_QUEUE_H
-
-#include <uv.h>
-
-#include "nvim/event/defs.h"
-#include "nvim/lib/queue.h"
-
-typedef struct queue Queue;
-typedef void (*put_callback)(Queue *queue, void *data);
-
-#define queue_put(q, h, ...) \
- queue_put_event(q, event_create(1, h, __VA_ARGS__));
-
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "event/queue.h.generated.h"
-#endif
-#endif // NVIM_EVENT_QUEUE_H
diff --git a/src/nvim/event/signal.h b/src/nvim/event/signal.h
index e32608acc0..7fe352edef 100644
--- a/src/nvim/event/signal.h
+++ b/src/nvim/event/signal.h
@@ -14,7 +14,7 @@ struct signal_watcher {
void *data;
signal_cb cb;
signal_close_cb close_cb;
- Queue *events;
+ MultiQueue *events;
};
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/event/socket.h b/src/nvim/event/socket.h
index ad59fdbe3a..eb0823c76d 100644
--- a/src/nvim/event/socket.h
+++ b/src/nvim/event/socket.h
@@ -30,7 +30,7 @@ struct socket_watcher {
void *data;
socket_cb cb;
socket_close_cb close_cb;
- Queue *events;
+ MultiQueue *events;
};
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/event/stream.h b/src/nvim/event/stream.h
index a176fac1c0..d27497e4a4 100644
--- a/src/nvim/event/stream.h
+++ b/src/nvim/event/stream.h
@@ -53,7 +53,7 @@ struct stream {
size_t pending_reqs;
size_t num_bytes;
bool closed;
- Queue *events;
+ MultiQueue *events;
};
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/event/time.c b/src/nvim/event/time.c
index f68a66345f..77260546db 100644
--- a/src/nvim/event/time.c
+++ b/src/nvim/event/time.c
@@ -51,7 +51,7 @@ static void time_watcher_cb(uv_timer_t *handle)
FUNC_ATTR_NONNULL_ALL
{
TimeWatcher *watcher = handle->data;
- if (watcher->blockable && !queue_empty(watcher->events)) {
+ if (watcher->blockable && !multiqueue_empty(watcher->events)) {
// the timer blocked and there already is an unprocessed event waiting
return;
}
diff --git a/src/nvim/event/time.h b/src/nvim/event/time.h
index 14df176ea3..a6de89ad6e 100644
--- a/src/nvim/event/time.h
+++ b/src/nvim/event/time.h
@@ -12,7 +12,7 @@ struct time_watcher {
uv_timer_t uv;
void *data;
time_cb cb, close_cb;
- Queue *events;
+ MultiQueue *events;
bool blockable;
};
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index c36c3a7b0e..0226499e78 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -1154,15 +1154,11 @@ static void do_filter(
}
read_linecount = curbuf->b_ml.ml_line_count;
- /*
- * When call_shell() fails wait_return() is called to give the user a
- * chance to read the error messages. Otherwise errors are ignored, so you
- * can see the error messages from the command that appear on stdout; use
- * 'u' to fix the text
- * Switch to cooked mode when not redirecting stdin, avoids that something
- * like ":r !cat" hangs.
- * Pass on the kShellDoOut flag when the output is being redirected.
- */
+ // When call_shell() fails wait_return() is called to give the user a chance
+ // to read the error messages. Otherwise errors are ignored, so you can see
+ // the error messages from the command that appear on stdout; use 'u' to fix
+ // the text.
+ // Pass on the kShellDoOut flag when the output is being redirected.
if (call_shell(
cmd_buf,
kShellOptFilter | shell_flags,
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 1a97dc3d6f..4254697241 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -357,7 +357,7 @@ static int command_line_execute(VimState *state, int key)
s->c = key;
if (s->c == K_EVENT) {
- queue_process_events(main_loop.events);
+ multiqueue_process_events(main_loop.events);
redrawcmdline();
return 1;
}
@@ -2548,10 +2548,9 @@ static void cmdline_del(int from)
ccline.cmdpos = from;
}
-/*
- * this function is called when the screen size changes and with incremental
- * search
- */
+// This function is called when the screen size changes and with incremental
+// search and in other situations where the command line may have been
+// overwritten.
void redrawcmdline(void)
{
if (cmd_silent)
@@ -3669,27 +3668,54 @@ expand_cmdline (
return EXPAND_OK;
}
-/*
- * Cleanup matches for help tags: remove "@en" if "en" is the only language.
- */
-
+// Cleanup matches for help tags:
+// Remove "@ab" if the top of 'helplang' is "ab" and the language of the first
+// tag matches it. Otherwise remove "@en" if "en" is the only language.
static void cleanup_help_tags(int num_file, char_u **file)
{
- int i, j;
- int len;
+ char_u buf[4];
+ char_u *p = buf;
+
+ if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) {
+ *p++ = '@';
+ *p++ = p_hlg[0];
+ *p++ = p_hlg[1];
+ }
+ *p = NUL;
- for (i = 0; i < num_file; ++i) {
- len = (int)STRLEN(file[i]) - 3;
- if (len > 0 && STRCMP(file[i] + len, "@en") == 0) {
- /* Sorting on priority means the same item in another language may
- * be anywhere. Search all items for a match up to the "@en". */
- for (j = 0; j < num_file; ++j)
+ for (int i = 0; i < num_file; i++) {
+ int len = (int)STRLEN(file[i]) - 3;
+ if (len <= 0) {
+ continue;
+ }
+ if (STRCMP(file[i] + len, "@en") == 0) {
+ // Sorting on priority means the same item in another language may
+ // be anywhere. Search all items for a match up to the "@en".
+ int j;
+ for (j = 0; j < num_file; j++) {
if (j != i
&& (int)STRLEN(file[j]) == len + 3
- && STRNCMP(file[i], file[j], len + 1) == 0)
+ && STRNCMP(file[i], file[j], len + 1) == 0) {
break;
- if (j == num_file)
+ }
+ }
+ if (j == num_file) {
+ // item only exists with @en, remove it
+ file[i][len] = NUL;
+ }
+ }
+ }
+
+ if (*buf != NUL) {
+ for (int i = 0; i < num_file; i++) {
+ int len = (int)STRLEN(file[i]) - 3;
+ if (len <= 0) {
+ continue;
+ }
+ if (STRCMP(file[i] + len, buf) == 0) {
+ // remove the default language
file[i][len] = NUL;
+ }
}
}
}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 57d12d396e..87fb928b30 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -927,9 +927,9 @@ EXTERN FILE *scriptin[NSCRIPT]; /* streams to read script from */
EXTERN int curscript INIT(= 0); /* index in scriptin[] */
EXTERN FILE *scriptout INIT(= NULL); /* stream to write script to */
-/* volatile because it is used in signal handler catch_sigint(). */
-EXTERN volatile int got_int INIT(= FALSE); /* set to TRUE when interrupt
- signal occurred */
+// volatile because it is used in a signal handler.
+EXTERN volatile int got_int INIT(= false); // set to true when interrupt
+ // signal occurred
EXTERN int bangredo INIT(= FALSE); /* set to TRUE with ! command */
EXTERN int searchcmdlen; /* length of previous search cmd */
EXTERN int reg_do_extmatch INIT(= 0); /* Used when compiling regexp:
diff --git a/src/nvim/log.c b/src/nvim/log.c
index c31af6b287..bbb4dfb944 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -146,10 +146,10 @@ static bool v_do_log_to_file(FILE *log_file, int log_level,
const char* fmt, va_list args)
{
static const char *log_levels[] = {
- [DEBUG_LOG_LEVEL] = "debug",
- [INFO_LOG_LEVEL] = "info",
- [WARNING_LOG_LEVEL] = "warning",
- [ERROR_LOG_LEVEL] = "error"
+ [DEBUG_LOG_LEVEL] = "DEBUG",
+ [INFO_LOG_LEVEL] = "INFO ",
+ [WARNING_LOG_LEVEL] = "WARN ",
+ [ERROR_LOG_LEVEL] = "ERROR"
};
assert(log_level >= DEBUG_LOG_LEVEL && log_level <= ERROR_LOG_LEVEL);
@@ -166,8 +166,8 @@ static bool v_do_log_to_file(FILE *log_file, int log_level,
// print the log message prefixed by the current timestamp and pid
int64_t pid = os_get_pid();
- if (fprintf(log_file, "%s [%s @ %s:%d] %" PRId64 " - ", date_time,
- log_levels[log_level], func_name, line_num, pid) < 0) {
+ if (fprintf(log_file, "%s %s %" PRId64 "/%s:%d: ", date_time,
+ log_levels[log_level], pid, func_name, line_num) < 0) {
return false;
}
if (vfprintf(log_file, fmt, args) < 0) {
diff --git a/src/nvim/main.c b/src/nvim/main.c
index a153931d8a..005f4dcc77 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -156,7 +156,7 @@ void event_teardown(void)
return;
}
- queue_process_events(main_loop.events);
+ multiqueue_process_events(main_loop.events);
input_stop();
channel_teardown();
process_teardown(&main_loop);
@@ -165,7 +165,7 @@ void event_teardown(void)
signal_teardown();
terminal_teardown();
- loop_close(&main_loop);
+ loop_close(&main_loop, true);
}
/// Performs early initialization.
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index f577fd847e..c08b9e8fcf 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -949,6 +949,9 @@ int utf_char2cells(int c)
if (intable(doublewidth, ARRAY_SIZE(doublewidth), c))
return 2;
#endif
+ if (p_emoji && intable(emoji_width, ARRAY_SIZE(emoji_width), c)) {
+ return 2;
+ }
}
/* Characters below 0x100 are influenced by 'isprint' option */
else if (c >= 0x80 && !vim_isprintc(c))
@@ -1712,16 +1715,20 @@ int utf_class(int c)
return (int)classes[mid].class;
}
+ // emoji
+ if (intable(emoji_all, ARRAY_SIZE(emoji_all), c)) {
+ return 3;
+ }
+
/* most other characters are "word" characters */
return 2;
}
-/*
- * Code for Unicode case-dependent operations. Based on notes in
- * http://www.unicode.org/Public/UNIDATA/CaseFolding.txt
- * This code uses simple case folding, not full case folding.
- * Last updated for Unicode 5.2.
- */
+int utf_ambiguous_width(int c)
+{
+ return c >= 0x80 && (intable(ambiguous, ARRAY_SIZE(ambiguous), c)
+ || intable(emoji_all, ARRAY_SIZE(emoji_all), c));
+}
/*
* Generic conversion function for case operations.
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index cd12f258b6..fef1d08db7 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -65,7 +65,7 @@ typedef struct {
uint64_t next_request_id;
kvec_t(ChannelCallFrame *) call_stack;
kvec_t(WBuffer *) delayed_notifications;
- Queue *events;
+ MultiQueue *events;
} Channel;
typedef struct {
@@ -440,7 +440,7 @@ static void handle_request(Channel *channel, msgpack_object *request)
if (handler.async) {
on_request_event((void **)&event_data);
} else {
- queue_put(channel->events, on_request_event, 1, event_data);
+ multiqueue_put(channel->events, on_request_event, 1, event_data);
}
}
@@ -620,7 +620,7 @@ static void close_channel(Channel *channel)
case kChannelTypeStdio:
stream_close(&channel->data.std.in, NULL, NULL);
stream_close(&channel->data.std.out, NULL, NULL);
- queue_put(main_loop.fast_events, exit_event, 1, channel);
+ multiqueue_put(main_loop.fast_events, exit_event, 1, channel);
return;
default:
abort();
@@ -654,7 +654,7 @@ static void free_channel(Channel *channel)
kv_destroy(channel->call_stack);
kv_destroy(channel->delayed_notifications);
if (channel->type != kChannelTypeProc) {
- queue_free(channel->events);
+ multiqueue_free(channel->events);
}
xfree(channel);
}
@@ -664,10 +664,11 @@ static void close_cb(Stream *stream, void *data)
decref(data);
}
-static Channel *register_channel(ChannelType type, uint64_t id, Queue *events)
+static Channel *register_channel(ChannelType type, uint64_t id,
+ MultiQueue *events)
{
Channel *rv = xmalloc(sizeof(Channel));
- rv->events = events ? events : queue_new_child(main_loop.events);
+ rv->events = events ? events : multiqueue_new_child(main_loop.events);
rv->type = type;
rv->refcount = 1;
rv->closed = false;
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index d0a9d3aca7..6dcbf50750 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -459,7 +459,7 @@ void normal_enter(bool cmdwin, bool noexmode)
normal_state_init(&state);
state.cmdwin = cmdwin;
state.noexmode = noexmode;
- state.toplevel = !cmdwin && !noexmode;
+ state.toplevel = (!cmdwin || cmdwin_result == 0) && !noexmode;
state_enter(&state.state);
}
@@ -1360,7 +1360,7 @@ static int normal_check(VimState *state)
// Dict internally somewhere.
// "may_garbage_collect" is reset in vgetc() which is invoked through
// do_exmode() and normal_cmd().
- may_garbage_collect = s->toplevel;
+ may_garbage_collect = !s->cmdwin && !s->noexmode;
// Update w_curswant if w_set_curswant has been set.
// Postponed until here to avoid computing w_virtcol too often.
@@ -7880,15 +7880,15 @@ static void nv_event(cmdarg_T *cap)
{
// Garbage collection should have been executed before blocking for events in
// the `os_inchar` in `state_enter`, but we also disable it here in case the
- // `os_inchar` branch was not executed(!queue_empty(loop.events), which could
- // have `may_garbage_collect` set to true in `normal_check`).
+ // `os_inchar` branch was not executed (!multiqueue_empty(loop.events), which
+ // could have `may_garbage_collect` set to true in `normal_check`).
//
// That is because here we may run code that calls `os_inchar`
// later(`f_confirm` or `get_keystroke` for example), but in these cases it is
// not safe to perform garbage collection because there could be unreferenced
// lists or dicts being used.
may_garbage_collect = false;
- queue_process_events(main_loop.events);
+ multiqueue_process_events(main_loop.events);
cap->retval |= CA_COMMAND_BUSY; // don't call edit() now
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index d8908cca90..e2a5d38bee 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -2518,15 +2518,15 @@ did_set_string_option (
else if (varp == &p_sbo) {
if (check_opt_strings(p_sbo, p_scbopt_values, TRUE) != OK)
errmsg = e_invarg;
- }
- /* 'ambiwidth' */
- else if (varp == &p_ambw) {
- if (check_opt_strings(p_ambw, p_ambw_values, FALSE) != OK)
+ } else if (varp == &p_ambw || (bool *)varp == &p_emoji) {
+ // 'ambiwidth'
+ if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) {
errmsg = e_invarg;
- else if (set_chars_option(&p_lcs) != NULL)
+ } else if (set_chars_option(&p_lcs) != NULL) {
errmsg = (char_u *)_("E834: Conflicts with value of 'listchars'");
- else if (set_chars_option(&p_fcs) != NULL)
+ } else if (set_chars_option(&p_fcs) != NULL) {
errmsg = (char_u *)_("E835: Conflicts with value of 'fillchars'");
+ }
}
/* 'background' */
else if (varp == &p_bg) {
@@ -3658,14 +3658,16 @@ set_bool_option (
/* when 'insertmode' is set from an autocommand need to do work here */
else if ((int *)varp == &p_im) {
if (p_im) {
- if ((State & INSERT) == 0)
- need_start_insertmode = TRUE;
- stop_insert_mode = FALSE;
- } else {
- need_start_insertmode = FALSE;
- stop_insert_mode = TRUE;
- if (restart_edit != 0 && mode_displayed)
- clear_cmdline = TRUE; /* remove "(insert)" */
+ if ((State & INSERT) == 0) {
+ need_start_insertmode = true;
+ }
+ stop_insert_mode = false;
+ } else if (old_value) { // only reset if it was set previously
+ need_start_insertmode = false;
+ stop_insert_mode = true;
+ if (restart_edit != 0 && mode_displayed) {
+ clear_cmdline = true; // remove "(insert)"
+ }
restart_edit = 0;
}
}
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index e36cceaaf4..e085b973ea 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -401,24 +401,25 @@ static char *(p_dy_values[]) = {"lastline", "uhex", NULL};
#endif
#define DY_LASTLINE 0x001
#define DY_UHEX 0x002
-EXTERN int p_ed; /* 'edcompatible' */
-EXTERN char_u *p_ead; /* 'eadirection' */
-EXTERN bool p_ea; /* 'equalalways' */
-EXTERN char_u *p_ep; /* 'equalprg' */
-EXTERN int p_eb; /* 'errorbells' */
-EXTERN char_u *p_ef; /* 'errorfile' */
-EXTERN char_u *p_efm; /* 'errorformat' */
-EXTERN char_u *p_gefm; /* 'grepformat' */
-EXTERN char_u *p_gp; /* 'grepprg' */
-EXTERN char_u *p_ei; /* 'eventignore' */
-EXTERN int p_ek; /* 'esckeys' */
-EXTERN int p_exrc; /* 'exrc' */
-EXTERN char_u *p_fencs; /* 'fileencodings' */
-EXTERN char_u *p_ffs; /* 'fileformats' */
-EXTERN bool p_fic; ///< 'fileignorecase'
-EXTERN char_u *p_fcl; /* 'foldclose' */
-EXTERN long p_fdls; /* 'foldlevelstart' */
-EXTERN char_u *p_fdo; /* 'foldopen' */
+EXTERN int p_ed; // 'edcompatible'
+EXTERN bool p_emoji; // 'emoji'
+EXTERN char_u *p_ead; // 'eadirection'
+EXTERN bool p_ea; // 'equalalways'
+EXTERN char_u *p_ep; // 'equalprg'
+EXTERN int p_eb; // 'errorbells'
+EXTERN char_u *p_ef; // 'errorfile'
+EXTERN char_u *p_efm; // 'errorformat'
+EXTERN char_u *p_gefm; // 'grepformat'
+EXTERN char_u *p_gp; // 'grepprg'
+EXTERN char_u *p_ei; // 'eventignore'
+EXTERN int p_ek; // 'esckeys'
+EXTERN int p_exrc; // 'exrc'
+EXTERN char_u *p_fencs; // 'fileencodings'
+EXTERN char_u *p_ffs; // 'fileformats'
+EXTERN bool p_fic; // 'fileignorecase'
+EXTERN char_u *p_fcl; // 'foldclose'
+EXTERN long p_fdls; // 'foldlevelstart'
+EXTERN char_u *p_fdo; // 'foldopen'
EXTERN unsigned fdo_flags;
# ifdef IN_OPTION_C
static char *(p_fdo_values[]) = {"all", "block", "hor", "mark", "percent",
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 060ec8c1e1..583c63614a 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -644,6 +644,14 @@ return {
defaults={if_true={vi=false}}
},
{
+ full_name='emoji', abbreviation='emo',
+ type='bool', scope={'global'},
+ vi_def=true,
+ redraw={'everything'},
+ varname='p_emoji',
+ defaults={if_true={vi=true}}
+ },
+ {
full_name='encoding', abbreviation='enc',
type='string', scope={'global'},
deny_in_modelines=true,
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 6873e32f34..1307ab5e5a 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -87,8 +87,8 @@ static void create_cursorhold_event(void)
// have been called(inbuf_poll would return kInputAvail)
// TODO(tarruda): Cursorhold should be implemented as a timer set during the
// `state_check` callback for the states where it can be triggered.
- assert(!events_enabled || queue_empty(main_loop.events));
- queue_put(main_loop.events, cursorhold_event, 0);
+ assert(!events_enabled || multiqueue_empty(main_loop.events));
+ multiqueue_put(main_loop.events, cursorhold_event, 0);
}
// Low level input function
@@ -422,5 +422,5 @@ static void read_error_exit(void)
static bool pending_events(void)
{
- return events_enabled && !queue_empty(main_loop.events);
+ return events_enabled && !multiqueue_empty(main_loop.events);
}
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index 661b12accc..e9a3dcbff8 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -162,7 +162,7 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args)
/// @param input The input to the shell (NULL for no input), passed to the
/// stdin of the resulting process.
/// @param len The length of the input buffer (not used if `input` == NULL)
-/// @param[out] output A pointer to to a location where the output will be
+/// @param[out] output Pointer to a location where the output will be
/// allocated and stored. Will point to NULL if the shell
/// command did not output anything. If NULL is passed,
/// the shell output will be ignored.
@@ -207,7 +207,7 @@ static int do_os_system(char **argv,
Stream in, out, err;
LibuvProcess uvproc = libuv_process_init(&main_loop, &buf);
Process *proc = &uvproc.process;
- Queue *events = queue_new_child(main_loop.events);
+ MultiQueue *events = multiqueue_new_child(main_loop.events);
proc->events = events;
proc->argv = argv;
proc->in = input != NULL ? &in : NULL;
@@ -221,13 +221,13 @@ static int do_os_system(char **argv,
msg_outtrans((char_u *)prog);
msg_putchar('\n');
}
- queue_free(events);
+ multiqueue_free(events);
return -1;
}
// We want to deal with stream events as fast a possible while queueing
// process events, so reset everything to NULL. It prevents closing the
- // streams while there's still data in the OS buffer(due to the process
+ // streams while there's still data in the OS buffer (due to the process
// exiting before all data is read).
if (input != NULL) {
proc->in->events = NULL;
@@ -277,8 +277,8 @@ static int do_os_system(char **argv,
}
}
- assert(queue_empty(events));
- queue_free(events);
+ assert(multiqueue_empty(events));
+ multiqueue_free(events);
return status;
}
diff --git a/src/nvim/rbuffer.h b/src/nvim/rbuffer.h
index 454972c69d..a8dfcac580 100644
--- a/src/nvim/rbuffer.h
+++ b/src/nvim/rbuffer.h
@@ -1,4 +1,4 @@
-// Ring buffer implementation. This is basically an array that wraps read/write
+// Specialized ring buffer. This is basically an array that wraps read/write
// pointers around the memory region. It should be more efficient than the old
// RBuffer which required memmove() calls to relocate read/write positions.
//
diff --git a/src/nvim/state.c b/src/nvim/state.c
index f792ec00a4..44c6441e40 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -35,8 +35,8 @@ getkey:
// processing. Characters can come from mappings, scripts and other
// sources, so this scenario is very common.
key = safe_vgetc();
- } else if (!queue_empty(main_loop.events)) {
- // Event was made available after the last queue_process_events call
+ } else if (!multiqueue_empty(main_loop.events)) {
+ // Event was made available after the last multiqueue_process_events call
key = K_EVENT;
} else {
input_enable_events();
@@ -48,7 +48,7 @@ getkey:
// directly.
(void)os_inchar(NULL, 0, -1, 0);
input_disable_events();
- key = !queue_empty(main_loop.events) ? K_EVENT : safe_vgetc();
+ key = !multiqueue_empty(main_loop.events) ? K_EVENT : safe_vgetc();
}
if (key == K_EVENT) {
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index ff98dc9f22..8401343d7a 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -166,7 +166,7 @@ void terminal_init(void)
invalidated_terminals = pmap_new(ptr_t)();
time_watcher_init(&main_loop, &refresh_timer, NULL);
// refresh_timer_cb will redraw the screen which can call vimscript
- refresh_timer.events = queue_new_child(main_loop.events);
+ refresh_timer.events = multiqueue_new_child(main_loop.events);
// initialize a rgb->color index map for cterm attributes(VTermScreenCell
// only has RGB information and we need color indexes for terminal UIs)
@@ -201,7 +201,7 @@ void terminal_init(void)
void terminal_teardown(void)
{
time_watcher_stop(&refresh_timer);
- queue_free(refresh_timer.events);
+ multiqueue_free(refresh_timer.events);
time_watcher_close(&refresh_timer, NULL);
pmap_free(ptr_t)(invalidated_terminals);
map_free(int, int)(color_indexes);
@@ -445,7 +445,7 @@ static int terminal_execute(VimState *state, int key)
case K_EVENT:
// We cannot let an event free the terminal yet. It is still needed.
s->term->refcount++;
- queue_process_events(main_loop.events);
+ multiqueue_process_events(main_loop.events);
s->term->refcount--;
if (s->term->buf_handle == 0) {
s->close = true;
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index 74bbf418fa..e2d1e67a22 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -24,6 +24,10 @@
"
" If cleanup after each Test_ function is needed, define a TearDown function.
" It will be called after each Test_ function.
+"
+" When debugging a test it can be useful to add messages to v:errors:
+" call add(v:errors, "this happened")
+
" Check that the screen size is at least 24 x 80 characters.
if &lines < 24 || &columns < 80
@@ -47,94 +51,108 @@ lang mess C
" Always use forward slashes.
set shellslash
+function RunTheTest(test)
+ echo 'Executing ' . a:test
+ if exists("*SetUp")
+ call SetUp()
+ endif
+
+ call add(s:messages, 'Executing ' . a:test)
+ let s:done += 1
+ try
+ exe 'call ' . a:test
+ catch
+ call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
+ endtry
+
+ if exists("*TearDown")
+ call TearDown()
+ endif
+endfunc
+
" Source the test script. First grab the file name, in case the script
-" navigates away.
-let testname = expand('%')
-let done = 0
-let fail = 0
-let errors = []
-let messages = []
+" navigates away. g:testname can be used by the tests.
+let g:testname = expand('%')
+let s:done = 0
+let s:fail = 0
+let s:errors = []
+let s:messages = []
if expand('%') =~ 'test_viml.vim'
- " this test has intentional errors, don't use try/catch.
+ " this test has intentional s:errors, don't use try/catch.
source %
else
try
source %
catch
- let fail += 1
- call add(errors, 'Caught exception: ' . v:exception . ' @ ' . v:throwpoint)
+ let s:fail += 1
+ call add(s:errors, 'Caught exception: ' . v:exception . ' @ ' . v:throwpoint)
endtry
endif
+" Names of flaky tests.
+let s:flaky = []
+
" Locate Test_ functions and execute them.
set nomore
redir @q
silent function /^Test_
redir END
-let tests = split(substitute(@q, 'function \(\k*()\)', '\1', 'g'))
+let s:tests = split(substitute(@q, 'function \(\k*()\)', '\1', 'g'))
" If there is an extra argument filter the function names against it.
if argc() > 1
- let tests = filter(tests, 'v:val =~ argv(1)')
+ let s:tests = filter(s:tests, 'v:val =~ argv(1)')
endif
" Execute the tests in alphabetical order.
-for test in sort(tests)
- echo 'Executing ' . test
- if exists("*SetUp")
- call SetUp()
- endif
+for s:test in sort(s:tests)
+ call RunTheTest(s:test)
- call add(messages, 'Executing ' . test)
- let done += 1
- try
- exe 'call ' . test
- catch
- let fail += 1
- call add(v:errors, 'Caught exception in ' . test . ': ' . v:exception . ' @ ' . v:throwpoint)
- endtry
+ if len(v:errors) > 0 && index(s:flaky, s:test) >= 0
+ call add(s:messages, 'Flaky test failed, running it again')
+ let v:errors = []
+ call RunTheTest(s:test)
+ endif
if len(v:errors) > 0
- let fail += 1
- call add(errors, 'Found errors in ' . test . ':')
- call extend(errors, v:errors)
+ let s:fail += 1
+ call add(s:errors, 'Found errors in ' . s:test . ':')
+ call extend(s:errors, v:errors)
let v:errors = []
endif
- if exists("*TearDown")
- call TearDown()
- endif
endfor
-if fail == 0
+if s:fail == 0
" Success, create the .res file so that make knows it's done.
- exe 'split ' . fnamemodify(testname, ':r') . '.res'
+ exe 'split ' . fnamemodify(g:testname, ':r') . '.res'
write
endif
-if len(errors) > 0
+if len(s:errors) > 0
" Append errors to test.log
split test.log
call append(line('$'), '')
- call append(line('$'), 'From ' . testname . ':')
- call append(line('$'), errors)
+ call append(line('$'), 'From ' . g:testname . ':')
+ call append(line('$'), s:errors)
write
endif
-let message = 'Executed ' . done . (done > 1 ? ' tests': ' test')
+let message = 'Executed ' . s:done . (s:done > 1 ? ' tests': ' test')
echo message
-call add(messages, message)
-if fail > 0
- let message = fail . ' FAILED'
+call add(s:messages, message)
+if s:fail > 0
+ let message = s:fail . ' FAILED'
echo message
- call add(messages, message)
+ call add(s:messages, message)
+ call extend(s:messages, s:errors)
endif
" Append messages to "messages"
split messages
call append(line('$'), '')
-call append(line('$'), 'From ' . testname . ':')
-call append(line('$'), messages)
+call append(line('$'), 'From ' . g:testname . ':')
+call append(line('$'), s:messages)
write
qall!
diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim
index 42f8391aba..cc1c81c7f6 100644
--- a/src/nvim/testdir/test_help_tagjump.vim
+++ b/src/nvim/testdir/test_help_tagjump.vim
@@ -48,3 +48,118 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*{address}\*')
helpclose
endfunc
+
+let s:langs = ['en', 'ab', 'ja']
+
+func s:doc_config_setup()
+ let s:helpfile_save = &helpfile
+ let &helpfile="Xdir1/doc-en/doc/testdoc.txt"
+ let s:rtp_save = &rtp
+ let &rtp="Xdir1/doc-en"
+ if has('multi_lang')
+ let s:helplang_save=&helplang
+ endif
+
+ call delete('Xdir1', 'rf')
+
+ for lang in s:langs
+ if lang ==# 'en'
+ let tagfname = 'tags'
+ let docfname = 'testdoc.txt'
+ else
+ let tagfname = 'tags-' . lang
+ let docfname = 'testdoc.' . lang . 'x'
+ endif
+ let docdir = "Xdir1/doc-" . lang . "/doc"
+ call mkdir(docdir, "p")
+ call writefile(["\t*test-char*", "\t*test-col*"], docdir . '/' . docfname)
+ call writefile(["test-char\t" . docfname . "\t/*test-char*",
+ \ "test-col\t" . docfname . "\t/*test-col*"],
+ \ docdir . '/' . tagfname)
+ endfor
+endfunc
+
+func s:doc_config_teardown()
+ call delete('Xdir1', 'rf')
+
+ let &helpfile = s:helpfile_save
+ let &rtp = s:rtp_save
+ if has('multi_lang')
+ let &helplang = s:helplang_save
+ endif
+endfunc
+
+func s:get_cmd_compl_list(cmd)
+ let list = []
+ let str = ''
+ for cnt in range(1, 999)
+ call feedkeys(a:cmd . repeat("\<Tab>", cnt) . "'\<C-B>let str='\<CR>", 'tx')
+ if str ==# a:cmd[1:]
+ break
+ endif
+ call add(list, str)
+ endfor
+ return list
+endfunc
+
+func Test_help_complete()
+ try
+ let list = []
+ call s:doc_config_setup()
+
+ " 'helplang=' and help file lang is 'en'
+ if has('multi_lang')
+ set helplang=
+ endif
+ let list = s:get_cmd_compl_list(":h test")
+ call assert_equal(['h test-col', 'h test-char'], list)
+
+ if has('multi_lang')
+ " 'helplang=ab' and help file lang is 'en'
+ set helplang=ab
+ let list = s:get_cmd_compl_list(":h test")
+ call assert_equal(['h test-col', 'h test-char'], list)
+
+ " 'helplang=' and help file lang is 'en' and 'ab'
+ set rtp+=Xdir1/doc-ab
+ set helplang=
+ let list = s:get_cmd_compl_list(":h test")
+ call assert_equal(sort(['h test-col@en', 'h test-col@ab',
+ \ 'h test-char@en', 'h test-char@ab']), sort(list))
+
+ " 'helplang=ab' and help file lang is 'en' and 'ab'
+ set helplang=ab
+ let list = s:get_cmd_compl_list(":h test")
+ call assert_equal(sort(['h test-col', 'h test-col@en',
+ \ 'h test-char', 'h test-char@en']), sort(list))
+
+ " 'helplang=' and help file lang is 'en', 'ab' and 'ja'
+ set rtp+=Xdir1/doc-ja
+ set helplang=
+ let list = s:get_cmd_compl_list(":h test")
+ call assert_equal(sort(['h test-col@en', 'h test-col@ab',
+ \ 'h test-col@ja', 'h test-char@en',
+ \ 'h test-char@ab', 'h test-char@ja']), sort(list))
+
+ " 'helplang=ab' and help file lang is 'en', 'ab' and 'ja'
+ set helplang=ab
+ let list = s:get_cmd_compl_list(":h test")
+ call assert_equal(sort(['h test-col', 'h test-col@en',
+ \ 'h test-col@ja', 'h test-char',
+ \ 'h test-char@en', 'h test-char@ja']), sort(list))
+
+ " 'helplang=ab,ja' and help file lang is 'en', 'ab' and 'ja'
+ set helplang=ab,ja
+ let list = s:get_cmd_compl_list(":h test")
+ call assert_equal(sort(['h test-col', 'h test-col@ja',
+ \ 'h test-col@en', 'h test-char',
+ \ 'h test-char@ja', 'h test-char@en']), sort(list))
+ endif
+ catch
+ call assert_exception('X')
+ finally
+ call s:doc_config_teardown()
+ endtry
+endfunc
+
+" vim: et sw=2:
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index 78fc81e3d2..63be8bf609 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -28,3 +28,38 @@ func Test_noinsert_complete()
set completeopt-=noinsert
iunmap <F5>
endfunc
+
+let g:months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
+let g:setting = ''
+
+func ListMonths()
+ if g:setting != ''
+ exe ":set" g:setting
+ endif
+ call complete(col('.'), g:months)
+ return ''
+endfunc
+
+func! Test_popup_completion_insertmode()
+ inoremap <F5> <C-R>=ListMonths()<CR>
+ new
+ call feedkeys("a\<F5>\<down>\<enter>\<esc>", 'tx')
+ call assert_equal('February', getline(1))
+ %d
+ let g:setting = 'noinsertmode'
+ call feedkeys("a\<F5>\<down>\<enter>\<esc>", 'tx')
+ call assert_equal('February', getline(1))
+ call assert_false(pumvisible())
+ %d
+ let g:setting = ''
+ call feedkeys("a\<F5>". repeat("\<c-n>",12)."\<enter>\<esc>", 'tx')
+ call assert_equal('', getline(1))
+ %d
+ call feedkeys("a\<F5>\<c-p>\<enter>\<esc>", 'tx')
+ call assert_equal('', getline(1))
+ %d
+ call feedkeys("a\<F5>\<c-p>\<c-p>\<enter>\<esc>", 'tx')
+ call assert_equal('December', getline(1))
+ bwipe!
+ iunmap <F5>
+endfunc
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 68ea00ee63..740716f0ef 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -340,7 +340,7 @@ static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data,
// ls *.md | xargs nvim
input->in_fd = 2;
stream_close(&input->read_stream, NULL, NULL);
- queue_put(input->loop->fast_events, restart_reading, 1, input);
+ multiqueue_put(input->loop->fast_events, restart_reading, 1, input);
} else {
loop_schedule(&main_loop, event_create(1, input_done_event, 0));
}
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index bfc03dfb81..04b5868d2c 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -1,3 +1,5 @@
+// Terminal UI functions. Invoked (by ui_bridge.c) on the TUI thread.
+
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
@@ -17,13 +19,13 @@
#include "nvim/api/private/helpers.h"
#include "nvim/event/loop.h"
#include "nvim/event/signal.h"
-#include "nvim/tui/tui.h"
-#include "nvim/tui/input.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/strings.h"
#include "nvim/ugrid.h"
-#include "nvim/ui_bridge.h"
+#include "nvim/tui/input.h"
+#include "nvim/tui/tui.h"
+#include "nvim/tui/ui_bridge.h"
// Space reserved in the output buffer to restore the cursor to normal when
// flushing. No existing terminal will require 32 bytes to do that.
@@ -65,7 +67,7 @@ typedef struct {
struct {
int enable_mouse, disable_mouse;
int enable_bracketed_paste, disable_bracketed_paste;
- int enter_insert_mode, enter_replace_mode, exit_insert_mode;
+ int set_cursor_shape_bar, set_cursor_shape_ul, set_cursor_shape_block;
int set_rgb_foreground, set_rgb_background;
int enable_focus_reporting, disable_focus_reporting;
} unibi_ext;
@@ -122,9 +124,9 @@ static void terminfo_start(UI *ui)
data->unibi_ext.disable_mouse = -1;
data->unibi_ext.enable_bracketed_paste = -1;
data->unibi_ext.disable_bracketed_paste = -1;
- data->unibi_ext.enter_insert_mode = -1;
- data->unibi_ext.enter_replace_mode = -1;
- data->unibi_ext.exit_insert_mode = -1;
+ data->unibi_ext.set_cursor_shape_bar = -1;
+ data->unibi_ext.set_cursor_shape_ul = -1;
+ data->unibi_ext.set_cursor_shape_block = -1;
data->unibi_ext.enable_focus_reporting = -1;
data->unibi_ext.disable_focus_reporting = -1;
data->out_fd = 1;
@@ -137,6 +139,8 @@ static void terminfo_start(UI *ui)
data->ut = unibi_dummy();
}
fix_terminfo(data);
+ // Initialize the cursor shape.
+ unibi_out(ui, data->unibi_ext.set_cursor_shape_block);
// Set 't_Co' from the result of unibilium & fix_terminfo.
t_colors = unibi_get_num(data->ut, unibi_max_colors);
// Enter alternate screen and clear
@@ -237,7 +241,7 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
signal_watcher_stop(&data->cont_handle);
signal_watcher_close(&data->cont_handle, NULL);
signal_watcher_close(&data->winch_handle, NULL);
- loop_close(&tui_loop);
+ loop_close(&tui_loop, false);
kv_destroy(data->invalid_regions);
xfree(data);
xfree(ui);
@@ -250,11 +254,6 @@ static void tui_scheduler(Event event, void *d)
loop_schedule(data->loop, event);
}
-static void refresh_event(void **argv)
-{
- ui_refresh();
-}
-
static void sigcont_cb(SignalWatcher *watcher, int signum, void *data)
{
((TUIData *)data)->cont_received = true;
@@ -265,8 +264,7 @@ static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data)
got_winch = true;
UI *ui = data;
update_size(ui);
- // run refresh_event in nvim main loop
- loop_schedule(&main_loop, event_create(1, refresh_event, 0));
+ ui_schedule_refresh();
}
static bool attrs_differ(HlAttrs a1, HlAttrs a2)
@@ -461,16 +459,16 @@ static void tui_mode_change(UI *ui, int mode)
if (mode == INSERT) {
if (data->showing_mode != INSERT) {
- unibi_out(ui, data->unibi_ext.enter_insert_mode);
+ unibi_out(ui, data->unibi_ext.set_cursor_shape_bar);
}
} else if (mode == REPLACE) {
if (data->showing_mode != REPLACE) {
- unibi_out(ui, data->unibi_ext.enter_replace_mode);
+ unibi_out(ui, data->unibi_ext.set_cursor_shape_ul);
}
} else {
assert(mode == NORMAL);
if (data->showing_mode != NORMAL) {
- unibi_out(ui, data->unibi_ext.exit_insert_mode);
+ unibi_out(ui, data->unibi_ext.set_cursor_shape_block);
}
}
data->showing_mode = mode;
@@ -632,8 +630,8 @@ static void tui_suspend(UI *ui)
// kill(0, SIGTSTP) won't stop the UI thread, so we must poll for SIGCONT
// before continuing. This is done in another callback to avoid
// loop_poll_events recursion
- queue_put_event(data->loop->fast_events,
- event_create(1, suspend_event, 1, ui));
+ multiqueue_put_event(data->loop->fast_events,
+ event_create(1, suspend_event, 1, ui));
}
static void tui_set_title(UI *ui, char *title)
@@ -864,23 +862,23 @@ static void fix_terminfo(TUIData *data)
|| os_getenv("KONSOLE_DBUS_SESSION") != NULL) {
// Konsole uses a proprietary escape code to set the cursor shape
// and does not support DECSCUSR.
- data->unibi_ext.enter_insert_mode = (int)unibi_add_ext_str(ut, NULL,
+ data->unibi_ext.set_cursor_shape_bar = (int)unibi_add_ext_str(ut, NULL,
TMUX_WRAP("\x1b]50;CursorShape=1;BlinkingCursorEnabled=1\x07"));
- data->unibi_ext.enter_replace_mode = (int)unibi_add_ext_str(ut, NULL,
+ data->unibi_ext.set_cursor_shape_ul = (int)unibi_add_ext_str(ut, NULL,
TMUX_WRAP("\x1b]50;CursorShape=2;BlinkingCursorEnabled=1\x07"));
- data->unibi_ext.exit_insert_mode = (int)unibi_add_ext_str(ut, NULL,
+ data->unibi_ext.set_cursor_shape_block = (int)unibi_add_ext_str(ut, NULL,
TMUX_WRAP("\x1b]50;CursorShape=0;BlinkingCursorEnabled=0\x07"));
} else if (!vte_version || atoi(vte_version) >= 3900) {
// Assume that the terminal supports DECSCUSR unless it is an
// old VTE based terminal. This should not get wrapped for tmux,
// which will handle it via its Ss/Se terminfo extension - usually
// according to its terminal-overrides.
- data->unibi_ext.enter_insert_mode = (int)unibi_add_ext_str(ut, NULL,
- "\x1b[5 q");
- data->unibi_ext.enter_replace_mode = (int)unibi_add_ext_str(ut, NULL,
- "\x1b[3 q");
- data->unibi_ext.exit_insert_mode = (int)unibi_add_ext_str(ut, NULL,
- "\x1b[2 q");
+ data->unibi_ext.set_cursor_shape_bar =
+ (int)unibi_add_ext_str(ut, NULL, "\x1b[5 q");
+ data->unibi_ext.set_cursor_shape_ul =
+ (int)unibi_add_ext_str(ut, NULL, "\x1b[3 q");
+ data->unibi_ext.set_cursor_shape_block =
+ (int)unibi_add_ext_str(ut, NULL, "\x1b[2 q");
}
end:
diff --git a/src/nvim/ui_bridge.c b/src/nvim/tui/ui_bridge.c
index 34b95baf6c..48f4b1bda6 100644
--- a/src/nvim/ui_bridge.c
+++ b/src/nvim/tui/ui_bridge.c
@@ -1,5 +1,5 @@
-// FIXME(tarruda): This module is very repetitive. It might be a good idea to
-// automatically generate it with a lua script during build
+// UI wrapper for the built-in TUI. Sends UI requests to the TUI thread.
+
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
@@ -9,11 +9,11 @@
#include "nvim/vim.h"
#include "nvim/ui.h"
#include "nvim/memory.h"
-#include "nvim/ui_bridge.h"
#include "nvim/ugrid.h"
+#include "nvim/tui/ui_bridge.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "ui_bridge.c.generated.h"
+# include "tui/ui_bridge.c.generated.h"
#endif
#define UI(b) (((UIBridgeData *)b)->ui)
diff --git a/src/nvim/ui_bridge.h b/src/nvim/tui/ui_bridge.h
index 561ddb6b24..003ed3c2c1 100644
--- a/src/nvim/ui_bridge.h
+++ b/src/nvim/tui/ui_bridge.h
@@ -1,6 +1,6 @@
// Bridge used for communication between a builtin UI thread and nvim core
-#ifndef NVIM_UI_BRIDGE_H
-#define NVIM_UI_BRIDGE_H
+#ifndef NVIM_TUI_UI_BRIDGE_H
+#define NVIM_TUI_UI_BRIDGE_H
#include <uv.h>
@@ -11,7 +11,7 @@ typedef struct ui_bridge_data UIBridgeData;
typedef void(*ui_main_fn)(UIBridgeData *bridge, UI *ui);
struct ui_bridge_data {
UI bridge; // actual UI passed to ui_attach
- UI *ui; // UI pointer that will have it's callback called in
+ UI *ui; // UI pointer that will have its callback called in
// another thread
event_scheduler scheduler;
uv_thread_t ui_thread;
@@ -39,6 +39,6 @@ struct ui_bridge_data {
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "ui_bridge.h.generated.h"
+# include "tui/ui_bridge.h.generated.h"
#endif
-#endif // NVIM_UI_BRIDGE_H
+#endif // NVIM_TUI_UI_BRIDGE_H
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index dd278893c2..648d633e07 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -53,12 +53,10 @@ static bool pending_cursor_update = false;
static int busy = 0;
static int height, width;
-// This set of macros allow us to use UI_CALL to invoke any function on
-// registered UI instances. The functions can have 0-5 arguments(configurable
-// by SELECT_NTH)
+// UI_CALL invokes a function on all registered UI instances. The functions can
+// have 0-5 arguments (configurable by SELECT_NTH).
//
-// See http://stackoverflow.com/a/11172679 for a better explanation of how it
-// works.
+// See http://stackoverflow.com/a/11172679 for how it works.
#ifdef _MSC_VER
# define UI_CALL(funname, ...) \
do { \
@@ -169,8 +167,8 @@ void ui_refresh(void)
for (size_t i = 0; i < ui_count; i++) {
UI *ui = uis[i];
- width = ui->width < width ? ui->width : width;
- height = ui->height < height ? ui->height : height;
+ width = MIN(ui->width, width);
+ height = MIN(ui->height, height);
pum_external &= ui->pum_external;
}
@@ -179,6 +177,16 @@ void ui_refresh(void)
pum_set_external(pum_external);
}
+static void ui_refresh_handler(void **argv)
+{
+ ui_refresh();
+}
+
+void ui_schedule_refresh(void)
+{
+ loop_schedule(&main_loop, event_create(1, ui_refresh_handler, 0));
+}
+
void ui_resize(int new_width, int new_height)
{
width = new_width;
@@ -252,7 +260,7 @@ void ui_detach_impl(UI *ui)
}
if (--ui_count) {
- ui_refresh();
+ ui_schedule_refresh();
}
}
@@ -389,7 +397,10 @@ static void send_output(uint8_t **ptr)
size_t clen = (size_t)mb_ptr2len(p);
UI_CALL(put, p, (size_t)clen);
col++;
- if (mb_ptr2cells(p) > 1) {
+ if (utf_ambiguous_width(*p)) {
+ pending_cursor_update = true;
+ flush_cursor_update();
+ } else if (mb_ptr2cells(p) > 1) {
// double cell character, blank the next cell
UI_CALL(put, NULL, 0);
col++;
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 2efa81af1d..9f8fcd1232 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -480,7 +480,7 @@ static int included_patches[] = {
// 1964,
// 1963 NA
// 1962,
- // 1961,
+ 1961,
1960,
// 1959 NA
// 1958 NA
@@ -620,10 +620,10 @@ static int included_patches[] = {
// 1824 NA
// 1823,
// 1822 NA
- // 1821,
- // 1820,
+ 1821,
+ 1820,
// 1819 NA
- // 1818,
+ 1818,
// 1817 NA
// 1816,
// 1815,
@@ -762,9 +762,9 @@ static int included_patches[] = {
// 1684 NA
// 1683 NA
1682,
- // 1681,
+ 1681,
// 1680 NA
- // 1679,
+ 1679,
// 1678 NA
// 1677 NA
1676,
@@ -772,8 +772,8 @@ static int included_patches[] = {
// 1674 NA
1673,
// 1672 NA
- // 1671,
- // 1670,
+ 1671,
+ 1670,
// 1669 NA
// 1668 NA
// 1667 NA
@@ -795,13 +795,13 @@ static int included_patches[] = {
// 1651 NA
// 1650,
1649,
- // 1648,
+ 1648,
// 1647,
// 1646 NA
// 1645,
// 1644,
1643,
- // 1642,
+ 1642,
1641,
// 1640,
// 1639,
@@ -813,8 +813,8 @@ static int included_patches[] = {
// 1633 NA
// 1632 NA
// 1631 NA
- // 1630,
- // 1629,
+ 1630,
+ 1629,
// 1628 NA
// 1627 NA
// 1626 NA
@@ -823,7 +823,7 @@ static int included_patches[] = {
// 1623 NA
// 1622 NA
// 1621 NA
- // 1620,
+ 1620,
// 1619,
// 1618 NA
// 1617 NA
@@ -839,7 +839,7 @@ static int included_patches[] = {
// 1607,
// 1606,
// 1605,
- // 1604,
+ 1604,
1603,
// 1602 NA
// 1601 NA
@@ -966,7 +966,7 @@ static int included_patches[] = {
1480,
1479,
1478,
- // 1477,
+ 1477,
// 1476 NA
// 1475 NA
// 1474 NA