aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZyX <kp-pav@yandex.ru>2017-07-01 15:34:25 +0300
committerZyX <kp-pav@yandex.ru>2017-07-01 15:34:25 +0300
commit7ab152aaa58f493e54d03a15960b8a288196e588 (patch)
tree98bdee4ca0c37d1b71b70fe0db4fedb3c4ef1f64
parentea75966e4232dc4a3693cbc4a572f2116c49b138 (diff)
downloadrneovim-7ab152aaa58f493e54d03a15960b8a288196e588.tar.gz
rneovim-7ab152aaa58f493e54d03a15960b8a288196e588.tar.bz2
rneovim-7ab152aaa58f493e54d03a15960b8a288196e588.zip
ex_getln: Save and restore try state
Problem: when processing cycle such as :for pat in [' \ze*', ' \zs*'] : try : let l = matchlist('x x', pat) : $put ='E888 NOT detected for ' . pat : catch : $put ='E888 detected for ' . pat : endtry :endfor `:let l = …` throwing an error causes this error to be caught after color_cmdline attempts to get callback for highlighting next line (the one with `$put = 'E888 NOT…`). Saving/restoring state prevents this from happening.
-rw-r--r--src/nvim/api/private/helpers.c46
-rw-r--r--src/nvim/api/private/helpers.h12
-rw-r--r--src/nvim/ex_getln.c16
-rw-r--r--test/functional/ui/cmdline_highlight_spec.lua24
4 files changed, 93 insertions, 5 deletions
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index d401ae52a0..883a5a2fd1 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -37,6 +37,52 @@ typedef struct {
# include "api/private/ui_events_metadata.generated.h"
#endif
+/// Start block that may cause VimL exceptions while evaluating another code
+///
+/// Used when caller is supposed to be operating when other VimL code is being
+/// processed and that “other VimL code” must not be affected.
+///
+/// @param[out] tstate Location where try state should be saved.
+void try_enter(TryState *const tstate)
+{
+ *tstate = (TryState) {
+ .trylevel = trylevel,
+ .got_int = got_int,
+ .did_throw = did_throw,
+ .msg_list = (const struct msglist *const *)msg_list,
+ .private_msg_list = NULL,
+ };
+ trylevel = 1;
+ got_int = false;
+ did_throw = false;
+ msg_list = &tstate->private_msg_list;
+}
+
+/// End try block, set the error message if any and restore previous state
+///
+/// @warning Return is consistent with most functions (false on error), not with
+/// try_end (true on error).
+///
+/// @param[in] tstate Previous state to restore.
+/// @param[out] err Location where error should be saved.
+///
+/// @return false if error occurred, true otherwise.
+bool try_leave(const TryState *const tstate, Error *const err)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const bool ret = !try_end(err);
+ assert(trylevel == 0);
+ assert(!got_int);
+ assert(!did_throw);
+ assert(msg_list == &tstate->private_msg_list);
+ assert(*msg_list == NULL);
+ trylevel = tstate->trylevel;
+ got_int = tstate->got_int;
+ did_throw = tstate->did_throw;
+ msg_list = (struct msglist **)tstate->msg_list;
+ return ret;
+}
+
/// Start block that may cause vimscript exceptions
void try_start(void)
{
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 159b9d5c2a..112d785bfd 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -82,6 +82,18 @@
#define api_free_window(value)
#define api_free_tabpage(value)
+/// Structure used for saving state for :try
+///
+/// Used when caller is supposed to be operating when other VimL code is being
+/// processed and that “other VimL code” must not be affected.
+typedef struct {
+ int trylevel;
+ int got_int;
+ int did_throw;
+ struct msglist *private_msg_list;
+ const struct msglist *const *msg_list;
+} TryState;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/helpers.h.generated.h"
#endif
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index c23d6089a3..e6cd7398ab 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -2222,6 +2222,11 @@ static bool color_cmdline(void)
bool ret = true;
kv_size(ccline_colors) = 0;
+ if (ccline.cmdbuff == NULL || *ccline.cmdbuff == NUL) {
+ // Nothing to do, exiting.
+ return ret;
+ }
+
const int saved_force_abort = force_abort;
force_abort = true;
bool arg_allocated = false;
@@ -2235,11 +2240,12 @@ static bool color_cmdline(void)
static int prev_prompt_errors = 0;
Callback color_cb = { .type = kCallbackNone };
bool can_free_cb = false;
+ TryState tstate;
Error err = ERROR_INIT;
const char *err_errmsg = (const char *)e_intern2;
bool dgc_ret = true;
- try_start();
+ try_enter(&tstate);
if (ccline.input_fn) {
color_cb = getln_input_callback;
} else if (ccline.cmdfirstc == ':') {
@@ -2257,7 +2263,7 @@ static bool color_cmdline(void)
} else {
goto color_cmdline_end;
}
- if (try_end(&err) || !dgc_ret) {
+ if (!try_leave(&tstate, &err) || !dgc_ret) {
goto color_cmdline_error;
}
@@ -2285,10 +2291,10 @@ static bool color_cmdline(void)
// correct, with msg_col it just misses leading `:`. Since `redraw!` in
// callback lags this is least of the user problems.
//
- // Also using try_start() because error messages may overwrite typed
+ // Also using try_enter() because error messages may overwrite typed
// command-line which is not expected.
getln_interrupted_highlight = false;
- try_start();
+ try_enter(&tstate);
err_errmsg = N_("E5407: Callback has thrown an exception: %s");
const int saved_msg_col = msg_col;
msg_silent++;
@@ -2298,7 +2304,7 @@ static bool color_cmdline(void)
if (got_int) {
getln_interrupted_highlight = true;
}
- if (try_end(&err) || !cbcall_ret) {
+ if (!try_leave(&tstate, &err) || !cbcall_ret) {
goto color_cmdline_error;
}
if (tv.v_type != VAR_LIST) {
diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua
index 671490e668..62c0694c8c 100644
--- a/test/functional/ui/cmdline_highlight_spec.lua
+++ b/test/functional/ui/cmdline_highlight_spec.lua
@@ -7,6 +7,8 @@ local clear = helpers.clear
local meths = helpers.meths
local funcs = helpers.funcs
local source = helpers.source
+local dedent = helpers.dedent
+local curbufmeths = helpers.curbufmeths
local screen
@@ -421,6 +423,8 @@ describe('Command-line coloring', function()
]])
end)
-- TODO Check for all other errors
+ -- TODO Check for colored input() called in a cycle which previously errorred
+ -- out
end)
describe('Ex commands coloring support', function()
it('still executes command-line even if errored out', function()
@@ -430,7 +434,27 @@ describe('Ex commands coloring support', function()
local msg = 'E5405: Chunk 0 start 10 splits multibyte character'
eq('\n'..msg, funcs.execute('messages'))
end)
+ it('does not error out when called from a errorred out cycle', function()
+ -- Apparently when there is a cycle in which one of the commands errors out
+ -- this error may be caught by color_cmdline before it is presented to the
+ -- user.
+ feed(dedent([[
+ :set regexpengine=2
+ :for pat in [' \ze*', ' \zs*']
+ : try
+ : let l = matchlist('x x', pat)
+ : $put ='E888 NOT detected for ' . pat
+ : catch
+ : $put ='E888 detected for ' . pat
+ : endtry
+ :endfor
+ ]]))
+ eq({'', 'E888 detected for \\ze*', 'E888 detected for \\zs*'},
+ curbufmeths.get_lines(0, -1, false))
+ eq('', funcs.execute('messages'))
+ end)
end)
-- TODO Specifically test for coloring in cmdline and expr modes
+-- TODO Check for errors from tv_dict_get_callback()
-- TODO Check using highlighted input() from inside highlighted input()