mirror of
https://github.com/neovim/neovim.git
synced 2025-09-29 22:48:34 +00:00
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:
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "nvim/api/vim.h"
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/log.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/buffer.h"
|
||||
@@ -27,6 +28,7 @@
|
||||
#include "nvim/eval.h"
|
||||
#include "nvim/eval/typval.h"
|
||||
#include "nvim/option.h"
|
||||
#include "nvim/state.h"
|
||||
#include "nvim/syntax.h"
|
||||
#include "nvim/getchar.h"
|
||||
#include "nvim/os/input.h"
|
||||
@@ -701,6 +703,25 @@ Dictionary nvim_get_color_map(void)
|
||||
}
|
||||
|
||||
|
||||
/// Gets the current mode.
|
||||
/// mode: Mode string. |mode()|
|
||||
/// blocking: true if Nvim is waiting for input.
|
||||
///
|
||||
/// @returns Dictionary { "mode": String, "blocking": Boolean }
|
||||
Dictionary nvim_get_mode(void)
|
||||
FUNC_API_SINCE(2) FUNC_API_ASYNC
|
||||
{
|
||||
Dictionary rv = ARRAY_DICT_INIT;
|
||||
char *modestr = get_mode();
|
||||
bool blocked = input_blocking();
|
||||
ILOG("blocked=%d", blocked);
|
||||
|
||||
PUT(rv, "mode", STRING_OBJ(cstr_as_string(modestr)));
|
||||
PUT(rv, "blocking", BOOLEAN_OBJ(blocked));
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
Array nvim_get_api_info(uint64_t channel_id)
|
||||
FUNC_API_SINCE(1) FUNC_API_ASYNC FUNC_API_NOEVAL
|
||||
{
|
||||
|
@@ -12575,59 +12575,18 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* "mode()" function
|
||||
*/
|
||||
/// "mode()" function
|
||||
static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
{
|
||||
char_u buf[3];
|
||||
char *mode = get_mode();
|
||||
|
||||
buf[1] = NUL;
|
||||
buf[2] = NUL;
|
||||
|
||||
if (VIsual_active) {
|
||||
if (VIsual_select)
|
||||
buf[0] = VIsual_mode + 's' - 'v';
|
||||
else
|
||||
buf[0] = VIsual_mode;
|
||||
} else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE
|
||||
|| State == CONFIRM) {
|
||||
buf[0] = 'r';
|
||||
if (State == ASKMORE)
|
||||
buf[1] = 'm';
|
||||
else if (State == CONFIRM)
|
||||
buf[1] = '?';
|
||||
} else if (State == EXTERNCMD)
|
||||
buf[0] = '!';
|
||||
else if (State & INSERT) {
|
||||
if (State & VREPLACE_FLAG) {
|
||||
buf[0] = 'R';
|
||||
buf[1] = 'v';
|
||||
} else if (State & REPLACE_FLAG)
|
||||
buf[0] = 'R';
|
||||
else
|
||||
buf[0] = 'i';
|
||||
} else if (State & CMDLINE) {
|
||||
buf[0] = 'c';
|
||||
if (exmode_active)
|
||||
buf[1] = 'v';
|
||||
} else if (exmode_active) {
|
||||
buf[0] = 'c';
|
||||
buf[1] = 'e';
|
||||
} else if (State & TERM_FOCUS) {
|
||||
buf[0] = 't';
|
||||
} else {
|
||||
buf[0] = 'n';
|
||||
if (finish_op)
|
||||
buf[1] = 'o';
|
||||
// Clear out the minor mode when the argument is not a non-zero number or
|
||||
// non-empty string.
|
||||
if (!non_zero_arg(&argvars[0])) {
|
||||
mode[1] = NUL;
|
||||
}
|
||||
|
||||
/* Clear out the minor mode when the argument is not a non-zero number or
|
||||
* non-empty string. */
|
||||
if (!non_zero_arg(&argvars[0]))
|
||||
buf[1] = NUL;
|
||||
|
||||
rettv->vval.v_string = vim_strsave(buf);
|
||||
rettv->vval.v_string = (char_u *)mode;
|
||||
rettv->v_type = VAR_STRING;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -345,10 +345,6 @@ char *xstpcpy(char *restrict dst, const char *restrict src)
|
||||
/// WARNING: xstpncpy will ALWAYS write maxlen bytes. If src is shorter than
|
||||
/// maxlen, zeroes will be written to the remaining bytes.
|
||||
///
|
||||
/// TODO(aktau): I don't see a good reason to have this last behaviour, and
|
||||
/// it is potentially wasteful. Could we perhaps deviate from the standard
|
||||
/// and not zero the rest of the buffer?
|
||||
///
|
||||
/// @param dst
|
||||
/// @param src
|
||||
/// @param maxlen
|
||||
|
@@ -28,7 +28,9 @@
|
||||
#include "nvim/map.h"
|
||||
#include "nvim/log.h"
|
||||
#include "nvim/misc1.h"
|
||||
#include "nvim/state.h"
|
||||
#include "nvim/lib/kvec.h"
|
||||
#include "nvim/os/input.h"
|
||||
|
||||
#define CHANNEL_BUFFER_SIZE 0xffff
|
||||
|
||||
@@ -433,6 +435,14 @@ static void handle_request(Channel *channel, msgpack_object *request)
|
||||
handler.async = true;
|
||||
}
|
||||
|
||||
if (handler.async) {
|
||||
char buf[256] = { 0 };
|
||||
memcpy(buf, method->via.bin.ptr, MIN(255, method->via.bin.size));
|
||||
if (strcmp("nvim_get_mode", buf) == 0) {
|
||||
handler.async = input_blocking();
|
||||
}
|
||||
}
|
||||
|
||||
RequestEvent *event_data = xmalloc(sizeof(RequestEvent));
|
||||
event_data->channel = channel;
|
||||
event_data->handler = handler;
|
||||
|
@@ -76,7 +76,7 @@ typedef struct {
|
||||
size_t idx;
|
||||
} MPToAPIObjectStackItem;
|
||||
|
||||
/// Convert type used by msgpack parser to Neovim own API type
|
||||
/// Convert type used by msgpack parser to Nvim API type.
|
||||
///
|
||||
/// @param[in] obj Msgpack value to convert.
|
||||
/// @param[out] arg Location where result of conversion will be saved.
|
||||
|
@@ -14,6 +14,7 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/log.h"
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/normal.h"
|
||||
#include "nvim/buffer.h"
|
||||
@@ -541,7 +542,7 @@ static bool normal_handle_special_visual_command(NormalState *s)
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool normal_need_aditional_char(NormalState *s)
|
||||
static bool normal_need_additional_char(NormalState *s)
|
||||
{
|
||||
int flags = nv_cmds[s->idx].cmd_flags;
|
||||
bool pending_op = s->oa.op_type != OP_NOP;
|
||||
@@ -1083,7 +1084,7 @@ static int normal_execute(VimState *state, int key)
|
||||
}
|
||||
|
||||
// Get an additional character if we need one.
|
||||
if (normal_need_aditional_char(s)) {
|
||||
if (normal_need_additional_char(s)) {
|
||||
normal_get_additional_char(s);
|
||||
}
|
||||
|
||||
|
@@ -23,6 +23,7 @@
|
||||
#include "nvim/main.h"
|
||||
#include "nvim/misc1.h"
|
||||
#include "nvim/state.h"
|
||||
#include "nvim/log.h"
|
||||
|
||||
#define READ_BUFFER_SIZE 0xfff
|
||||
#define INPUT_BUFFER_SIZE (READ_BUFFER_SIZE * 4)
|
||||
@@ -38,6 +39,7 @@ static RBuffer *input_buffer = NULL;
|
||||
static bool input_eof = false;
|
||||
static int global_fd = 0;
|
||||
static int events_enabled = 0;
|
||||
static bool blocking = false;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "os/input.c.generated.h"
|
||||
@@ -327,13 +329,27 @@ static unsigned int handle_mouse_event(char **ptr, uint8_t *buf,
|
||||
return bufsize;
|
||||
}
|
||||
|
||||
/// @return true if the main loop is blocked and waiting for input.
|
||||
bool input_blocking(void)
|
||||
{
|
||||
return blocking;
|
||||
}
|
||||
|
||||
static bool input_poll(int ms)
|
||||
{
|
||||
if (do_profiling == PROF_YES && ms) {
|
||||
prof_inchar_enter();
|
||||
}
|
||||
|
||||
if ((ms == - 1 || ms > 0)
|
||||
&& !(events_enabled || input_ready() || input_eof)
|
||||
) {
|
||||
blocking = true;
|
||||
multiqueue_process_debug(main_loop.events);
|
||||
multiqueue_process_events(main_loop.events);
|
||||
}
|
||||
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, input_ready() || input_eof);
|
||||
blocking = false;
|
||||
|
||||
if (do_profiling == PROF_YES && ms) {
|
||||
prof_inchar_exit();
|
||||
|
@@ -98,3 +98,52 @@ int get_real_state(void)
|
||||
return State;
|
||||
}
|
||||
|
||||
/// @returns[allocated] mode string
|
||||
char *get_mode(void)
|
||||
{
|
||||
char *buf = xcalloc(3, sizeof(char));
|
||||
|
||||
if (VIsual_active) {
|
||||
if (VIsual_select) {
|
||||
buf[0] = (char)(VIsual_mode + 's' - 'v');
|
||||
} else {
|
||||
buf[0] = (char)VIsual_mode;
|
||||
}
|
||||
} else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE
|
||||
|| State == CONFIRM) {
|
||||
buf[0] = 'r';
|
||||
if (State == ASKMORE) {
|
||||
buf[1] = 'm';
|
||||
} else if (State == CONFIRM) {
|
||||
buf[1] = '?';
|
||||
}
|
||||
} else if (State == EXTERNCMD) {
|
||||
buf[0] = '!';
|
||||
} else if (State & INSERT) {
|
||||
if (State & VREPLACE_FLAG) {
|
||||
buf[0] = 'R';
|
||||
buf[1] = 'v';
|
||||
} else if (State & REPLACE_FLAG) {
|
||||
buf[0] = 'R';
|
||||
} else {
|
||||
buf[0] = 'i';
|
||||
}
|
||||
} else if (State & CMDLINE) {
|
||||
buf[0] = 'c';
|
||||
if (exmode_active) {
|
||||
buf[1] = 'v';
|
||||
}
|
||||
} else if (exmode_active) {
|
||||
buf[0] = 'c';
|
||||
buf[1] = 'e';
|
||||
} else if (State & TERM_FOCUS) {
|
||||
buf[0] = 't';
|
||||
} else {
|
||||
buf[0] = 'n';
|
||||
if (finish_op) {
|
||||
buf[1] = 'o';
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
@@ -74,9 +74,6 @@ typedef struct {
|
||||
bool out_isatty;
|
||||
SignalWatcher winch_handle, cont_handle;
|
||||
bool cont_received;
|
||||
// Event scheduled by the ui bridge. Since the main thread suspends until
|
||||
// the event is handled, it is fine to use a single field instead of a queue
|
||||
Event scheduled_event;
|
||||
UGrid grid;
|
||||
kvec_t(Rect) invalid_regions;
|
||||
int out_fd;
|
||||
|
Reference in New Issue
Block a user