api: nvim_get_mode()

Asynchronous API functions are served immediately, which means pending
input could change the state of Nvim shortly after an async API function
result is returned.

nvim_get_mode() is different:
  - If RPCs are known to be blocked, it responds immediately (without
    flushing the input/event queue)
  - else it is handled just-in-time before waiting for input, after
    pending input was processed. This makes the result more reliable
    (but not perfect).

Internally this is handled as a special case, but _semantically_ nothing
has changed: API users never know when input flushes, so this internal
special-case doesn't violate that. As far as API users are concerned,
nvim_get_mode() is just another asynchronous API function.

In all cases nvim_get_mode() never blocks for more than the time it
takes to flush the input/event queue (~µs).

Note: This doesn't address #6166; nvim_get_mode() will provoke #6166 if
e.g. `d` is operator-pending.

Closes #6159
This commit is contained in:
Justin M. Keyes
2017-03-13 15:02:37 +01:00
parent 7044aa6e82
commit 3ea1007753
16 changed files with 250 additions and 77 deletions

View File

@@ -44,8 +44,7 @@ void loop_poll_events(Loop *loop, int ms)
// we do not block indefinitely for I/O.
uv_timer_start(&loop->poll_timer, timer_cb, (uint64_t)ms, (uint64_t)ms);
} else if (ms == 0) {
// For ms == 0, we need to do a non-blocking event poll by
// setting the run mode to UV_RUN_NOWAIT.
// For ms == 0, do a non-blocking event poll.
mode = UV_RUN_NOWAIT;
}

View File

@@ -55,6 +55,7 @@
#include "nvim/event/multiqueue.h"
#include "nvim/memory.h"
#include "nvim/log.h"
#include "nvim/os/time.h"
typedef struct multiqueue_item MultiQueueItem;
@@ -151,6 +152,40 @@ void multiqueue_process_events(MultiQueue *this)
}
}
void multiqueue_process_debug(MultiQueue *this)
{
assert(this);
QUEUE *start = QUEUE_HEAD(&this->headtail);
QUEUE *cur = start;
// MultiQueue *start = this;
// MultiQueue *cur = start;
do {
MultiQueueItem *item = multiqueue_node_data(cur);
Event ev;
if (item->link) {
assert(!this->parent);
// get 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));
ev = child->data.item.event;
} else {
ev = item->data.item.event;
}
// Event event = multiqueue_get(this);
// if (event.handler) {
// event.handler(event.argv);
// }
ILOG("ev: priority=%d, handler=%p arg1=%s", ev.priority, ev.handler,
ev.argv ? ev.argv[0] : "(null)");
cur = cur->next;
} while (cur && cur != start);
}
/// Removes all events without processing them.
void multiqueue_purge_events(MultiQueue *this)
{