diff options
| -rw-r--r-- | src/nvim/api/private/helpers.c | 46 | ||||
| -rw-r--r-- | src/nvim/api/private/helpers.h | 12 | ||||
| -rw-r--r-- | src/nvim/ex_getln.c | 16 | ||||
| -rw-r--r-- | test/functional/ui/cmdline_highlight_spec.lua | 24 | 
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() | 
