aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2017-03-13 15:02:37 +0100
committerJustin M. Keyes <justinkz@gmail.com>2017-04-28 19:14:34 +0200
commit3ea10077534cb1dcb1597ffcf85e601fa0c0e27b (patch)
treea043bcdda63a9f3e4c3475dd6371ebb2c2324ddb
parent7044aa6e8256844bc1bd23eb61d4a41ca6d418d0 (diff)
downloadrneovim-3ea10077534cb1dcb1597ffcf85e601fa0c0e27b.tar.gz
rneovim-3ea10077534cb1dcb1597ffcf85e601fa0c0e27b.tar.bz2
rneovim-3ea10077534cb1dcb1597ffcf85e601fa0c0e27b.zip
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
-rw-r--r--runtime/doc/help.txt5
-rw-r--r--runtime/doc/intro.txt5
-rw-r--r--runtime/doc/message.txt4
-rw-r--r--src/nvim/api/vim.c21
-rw-r--r--src/nvim/eval.c55
-rw-r--r--src/nvim/event/loop.c3
-rw-r--r--src/nvim/event/multiqueue.c35
-rw-r--r--src/nvim/memory.c4
-rw-r--r--src/nvim/msgpack_rpc/channel.c10
-rw-r--r--src/nvim/msgpack_rpc/helpers.c2
-rw-r--r--src/nvim/normal.c5
-rw-r--r--src/nvim/os/input.c16
-rw-r--r--src/nvim/state.c49
-rw-r--r--src/nvim/tui/tui.c3
-rw-r--r--test/functional/api/vim_spec.lua106
-rw-r--r--test/functional/helpers.lua4
16 files changed, 250 insertions, 77 deletions
diff --git a/runtime/doc/help.txt b/runtime/doc/help.txt
index f71f46bad3..3837cf3e26 100644
--- a/runtime/doc/help.txt
+++ b/runtime/doc/help.txt
@@ -154,22 +154,17 @@ Interfaces ~
|if_cscop.txt| using Cscope with Vim
|if_pyth.txt| Python interface
|if_ruby.txt| Ruby interface
-|debugger.txt| Interface with a debugger
|sign.txt| debugging signs
Versions ~
|vim_diff.txt| Main differences between Nvim and Vim
|vi_diff.txt| Main differences between Vim and Vi
- *sys-file-list*
-Remarks about specific systems ~
-|os_win32.txt| MS-Windows
*standard-plugin-list*
Standard plugins ~
|pi_gzip.txt| Reading and writing compressed files
|pi_netrw.txt| Reading and writing files over a network
|pi_paren.txt| Highlight matching parens
|pi_tar.txt| Tar file explorer
-|pi_vimball.txt| Create a self-installing Vim script
|pi_zip.txt| Zip archive explorer
LOCAL ADDITIONS: *local-additions*
diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt
index a5f9106bb0..bc34b69508 100644
--- a/runtime/doc/intro.txt
+++ b/runtime/doc/intro.txt
@@ -34,11 +34,6 @@ It can be accessed from within Vim with the <Help> or <F1> key and with the
is not located in the default place. You can jump to subjects like with tags:
Use CTRL-] to jump to a subject under the cursor, use CTRL-T to jump back.
-This manual refers to Vim on various machines. There may be small differences
-between different computers and terminals. Besides the remarks given in this
-document, there is a separate document for each supported system, see
-|sys-file-list|.
-
*pronounce*
Vim is pronounced as one word, like Jim, not vi-ai-em. It's written with a
capital, since it's a name, again like Jim.
diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt
index d0bdba41ab..5c2dddc8b3 100644
--- a/runtime/doc/message.txt
+++ b/runtime/doc/message.txt
@@ -40,10 +40,6 @@ Note: If the output has been stopped with "q" at the more prompt, it will only
be displayed up to this point.
The previous command output is cleared when another command produces output.
-If you are using translated messages, the first printed line tells who
-maintains the messages or the translations. You can use this to contact the
-maintainer when you spot a mistake.
-
If you want to find help on a specific (error) message, use the ID at the
start of the message. For example, to get help on the message: >
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index da00fbc6e3..7c57a5b456 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -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
{
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index c02d172458..b0f47d8e45 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -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;
}
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index 6963978581..c709ce9a1c 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -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;
}
diff --git a/src/nvim/event/multiqueue.c b/src/nvim/event/multiqueue.c
index a17bae31e3..b144347fdb 100644
--- a/src/nvim/event/multiqueue.c
+++ b/src/nvim/event/multiqueue.c
@@ -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)
{
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 0ee4776581..74c58fb203 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -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
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 59594357de..799e6eadef 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -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;
diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c
index 0228582d37..91ef5524ea 100644
--- a/src/nvim/msgpack_rpc/helpers.c
+++ b/src/nvim/msgpack_rpc/helpers.c
@@ -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.
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 51da9429b6..09ad7beb4b 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -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);
}
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 7b5e14dd19..26f2be6c02 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -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();
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 210708c3f4..be6aa21664 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -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;
+}
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 21abc19c47..356221f7ce 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -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;
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 5b173f3196..d06dd4c487 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -9,6 +9,7 @@ local funcs = helpers.funcs
local request = helpers.request
local meth_pcall = helpers.meth_pcall
local command = helpers.command
+local wait = helpers.wait
describe('api', function()
before_each(clear)
@@ -221,6 +222,109 @@ describe('api', function()
end)
end)
+ local function appendfile(fname, text)
+ local file = io.open(fname, 'a')
+ file:write(text)
+ file:flush()
+ file:close()
+ end
+
+ describe('nvim_get_mode', function()
+ it("during normal-mode `g` returns blocking=true", function()
+ nvim("input", "o") -- add a line
+ eq({mode='i', blocking=false}, nvim("get_mode"))
+ nvim("input", [[<C-\><C-N>]])
+ eq(2, nvim("eval", "line('.')"))
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+
+ nvim("input", "g")
+ eq({mode='n', blocking=true}, nvim("get_mode"))
+
+ nvim("input", "k") -- complete the operator
+ eq(1, nvim("eval", "line('.')")) -- verify the completed operator
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ end)
+
+ it("returns the correct result multiple consecutive times", function()
+ for _ = 1,5 do
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ end
+ nvim("input", "g")
+ for _ = 1,4 do
+ eq({mode='n', blocking=true}, nvim("get_mode"))
+ end
+ nvim("input", "g")
+ for _ = 1,7 do
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ end
+ end)
+
+ it("during normal-mode CTRL-W, returns blocking=true", function()
+ nvim("input", "<C-W>")
+ eq({mode='n', blocking=true}, nvim("get_mode"))
+
+ nvim("input", "s") -- complete the operator
+ eq(2, nvim("eval", "winnr('$')")) -- verify the completed operator
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ end)
+
+ it("during press-enter prompt returns blocking=true", function()
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ command("echom 'msg1'")
+ command("echom 'msg2'")
+ command("echom 'msg3'")
+ command("echom 'msg4'")
+ command("echom 'msg5'")
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ nvim("input", ":messages<CR>")
+ eq({mode='r', blocking=true}, nvim("get_mode"))
+ end)
+
+ it("during getchar() returns blocking=false", function()
+ nvim("input", ":let g:test_input = nr2char(getchar())<CR>")
+ -- Events are enabled during getchar(), RPC calls are *not* blocked. #5384
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ eq(0, nvim("eval", "exists('g:test_input')"))
+ nvim("input", "J")
+ eq("J", nvim("eval", "g:test_input"))
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ end)
+
+ -- TODO: bug #6247#issuecomment-286403810
+ it("batched with input", function()
+ eq({mode='n', blocking=false}, nvim("get_mode"))
+ command("echom 'msg1'")
+ command("echom 'msg2'")
+ command("echom 'msg3'")
+ command("echom 'msg4'")
+ command("echom 'msg5'")
+
+ local req = {
+ {'nvim_get_mode', {}},
+ {'nvim_input', {':messages<CR>'}},
+ {'nvim_get_mode', {}},
+ {'nvim_eval', {'1'}},
+ }
+ eq({{{mode='n', blocking=false},
+ 13,
+ {mode='n', blocking=false}, -- TODO: should be blocked=true
+ 1},
+ NIL}, meths.call_atomic(req))
+ eq({mode='r', blocking=true}, nvim("get_mode"))
+ end)
+ -- TODO: bug #6166
+ it("during insert-mode map-pending, returns blocking=true #6166", function()
+ command("inoremap xx foo")
+ nvim("input", "ix")
+ eq({mode='i', blocking=true}, nvim("get_mode"))
+ end)
+ -- TODO: bug #6166
+ it("during normal-mode gU, returns blocking=false #6166", function()
+ nvim("input", "gu")
+ eq({mode='no', blocking=false}, nvim("get_mode"))
+ end)
+ end)
+
describe('nvim_replace_termcodes', function()
it('escapes K_SPECIAL as K_SPECIAL KS_SPECIAL KE_FILLER', function()
eq('\128\254X', helpers.nvim('replace_termcodes', '\128', true, true, true))
@@ -459,7 +563,7 @@ describe('api', function()
eq(very_long_name, err:match('Ax+Z?'))
end)
- it("doesn't leak memory on incorrect argument types", function()
+ it("does not leak memory on incorrect argument types", function()
local status, err = pcall(nvim, 'set_current_dir',{'not', 'a', 'dir'})
eq(false, status)
ok(err:match(': Wrong type for argument 1, expecting String') ~= nil)
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua
index 0f30910450..2919165280 100644
--- a/test/functional/helpers.lua
+++ b/test/functional/helpers.lua
@@ -385,9 +385,9 @@ local function curbuf(method, ...)
end
local function wait()
- -- Execute 'vim_eval' (a deferred function) to block
+ -- Execute 'nvim_eval' (a deferred function) to block
-- until all pending input is processed.
- session:request('vim_eval', '1')
+ session:request('nvim_eval', '1')
end
-- sleeps the test runner (_not_ the nvim instance)