diff options
Diffstat (limited to 'src')
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 |