Skip to content

Commit

Permalink
Merge #8371 'API: more reliable/descriptive VimL errors'
Browse files Browse the repository at this point in the history
  • Loading branch information
justinmk committed May 10, 2018
2 parents 1cd8517 + 966e7ab commit 8d40b36
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 113 deletions.
12 changes: 8 additions & 4 deletions runtime/autoload/msgpack.vim
Expand Up @@ -40,9 +40,10 @@ function s:msgpack_init_python() abort
return s:msgpack_python_type
endif
let s:msgpack_python_initialized = 1
for suf in ['', '3']
for suf in (has('win32') ? ['3'] : ['', '3'])
try
execute 'python' . suf
\. "\n"
\. "def shada_dict_strftime():\n"
\. " import datetime\n"
\. " import vim\n"
Expand All @@ -60,12 +61,15 @@ function s:msgpack_init_python() abort
\. " fmt = vim.eval('a:format')\n"
\. " timestr = vim.eval('a:string')\n"
\. " timestamp = datetime.datetime.strptime(timestr, fmt)\n"
\. " timestamp = int(timestamp.timestamp())\n"
\. " try:\n"
\. " timestamp = int(timestamp.timestamp())\n"
\. " except:\n"
\. " timestamp = int(timestamp.strftime('%s'))\n"
\. " if timestamp > 2 ** 31:\n"
\. " tsabs = abs(timestamp)"
\. " tsabs = abs(timestamp)\n"
\. " return ('{\"_TYPE\": v:msgpack_types.integer,'\n"
\. " + '\"_VAL\": [{sign},{v1},{v2},{v3}]}').format(\n"
\. " sign=1 if timestamp >= 0 else -1,\n"
\. " sign=(1 if timestamp >= 0 else -1),\n"
\. " v1=((tsabs >> 62) & 0x3),\n"
\. " v2=((tsabs >> 31) & (2 ** 31 - 1)),\n"
\. " v3=(tsabs & (2 ** 31 - 1)))\n"
Expand Down
4 changes: 1 addition & 3 deletions src/nvim/api/private/helpers.c
Expand Up @@ -120,9 +120,7 @@ bool try_end(Error *err)
// try_enter/try_leave.
trylevel--;

// Without this it stops processing all subsequent VimL commands and
// generates strange error messages if I e.g. try calling Test() in a
// cycle
// Set by emsg(), affects aborting(). See also enter_cleanup().
did_emsg = false;

if (got_int) {
Expand Down
104 changes: 72 additions & 32 deletions src/nvim/api/vim.c
Expand Up @@ -46,8 +46,7 @@

/// Executes an ex-command.
///
/// On parse error: forwards the Vim error; does not update v:errmsg.
/// On runtime error: forwards the Vim error; does not update v:errmsg.
/// On execution error: fails with VimL error, does not update v:errmsg.
///
/// @param command Ex-command string
/// @param[out] err Error details (Vim error), if any
Expand Down Expand Up @@ -103,7 +102,8 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err)
}

/// Passes input keys to Nvim.
/// On VimL error: Does not fail, but updates v:errmsg.
///
/// On execution error: does not fail, but updates v:errmsg.
///
/// @param keys to be typed
/// @param mode mapping options
Expand Down Expand Up @@ -169,7 +169,8 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
}

/// Passes keys to Nvim as raw user-input.
/// On VimL error: Does not fail, but updates v:errmsg.
///
/// On execution error: does not fail, but updates v:errmsg.
///
/// Unlike `nvim_feedkeys`, this uses a lower-level input buffer and the call
/// is not deferred. This is the most reliable way to send real user input.
Expand Down Expand Up @@ -213,8 +214,7 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt,
/// Executes an ex-command and returns its (non-error) output.
/// Shell |:!| output is not captured.
///
/// On parse error: forwards the Vim error; does not update v:errmsg.
/// On runtime error: forwards the Vim error; does not update v:errmsg.
/// On execution error: fails with VimL error, does not update v:errmsg.
///
/// @param command Ex-command string
/// @param[out] err Error details (Vim error), if any
Expand Down Expand Up @@ -259,30 +259,51 @@ String nvim_command_output(String command, Error *err)

/// Evaluates a VimL expression (:help expression).
/// Dictionaries and Lists are recursively expanded.
/// On VimL error: Returns a generic error; v:errmsg is not updated.
///
/// On execution error: fails with VimL error, does not update v:errmsg.
///
/// @param expr VimL expression string
/// @param[out] err Error details, if any
/// @return Evaluation result or expanded object
Object nvim_eval(String expr, Error *err)
FUNC_API_SINCE(1)
{
static int recursive = 0; // recursion depth
Object rv = OBJECT_INIT;
// Evaluate the expression

// `msg_list` controls the collection of abort-causing non-exception errors,
// which would otherwise be ignored. This pattern is from do_cmdline().
struct msglist **saved_msg_list = msg_list;
struct msglist *private_msg_list;
msg_list = &private_msg_list;
private_msg_list = NULL;

// Initialize `force_abort` and `suppress_errthrow` at the top level.
if (!recursive) {
force_abort = false;
suppress_errthrow = false;
current_exception = NULL;
// `did_emsg` is set by emsg(), which cancels execution.
did_emsg = false;
}
recursive++;
try_start();

typval_T rettv;
if (eval0((char_u *)expr.data, &rettv, NULL, true) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to evaluate expression");
}
int ok = eval0((char_u *)expr.data, &rettv, NULL, true);

if (!try_end(err)) {
// No errors, convert the result
rv = vim_to_object(&rettv);
if (ok == FAIL) {
// Should never happen, try_end() should get the error. #8371
api_set_error(err, kErrorTypeException, "Failed to evaluate expression");
} else {
rv = vim_to_object(&rettv);
}
}

// Free the Vim object
tv_clear(&rettv);
msg_list = saved_msg_list; // Restore the exception context.
recursive--;

return rv;
}
Expand Down Expand Up @@ -314,7 +335,9 @@ Object nvim_execute_lua(String code, Array args, Error *err)
/// @return Result of the function call
static Object _call_function(String fn, Array args, dict_T *self, Error *err)
{
static int recursive = 0; // recursion depth
Object rv = OBJECT_INIT;

if (args.size > MAX_FUNC_ARGS) {
api_set_error(err, kErrorTypeValidation,
"Function called with too many arguments");
Expand All @@ -330,20 +353,36 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err)
}
}

// `msg_list` controls the collection of abort-causing non-exception errors,
// which would otherwise be ignored. This pattern is from do_cmdline().
struct msglist **saved_msg_list = msg_list;
struct msglist *private_msg_list;
msg_list = &private_msg_list;
private_msg_list = NULL;

// Initialize `force_abort` and `suppress_errthrow` at the top level.
if (!recursive) {
force_abort = false;
suppress_errthrow = false;
current_exception = NULL;
// `did_emsg` is set by emsg(), which cancels execution.
did_emsg = false;
}
recursive++;
try_start();
// Call the function
typval_T rettv;
int dummy;
int r = call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size,
vim_args, NULL, curwin->w_cursor.lnum,
curwin->w_cursor.lnum, &dummy, true, NULL, self);
if (r == FAIL) {
api_set_error(err, kErrorTypeException, "Error calling function");
}
// call_func() retval is deceptive, ignore it. Instead we set `msg_list`
// (see above) to capture abort-causing non-exception errors.
(void)call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size,
vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&dummy, true, NULL, self);
if (!try_end(err)) {
rv = vim_to_object(&rettv);
}
tv_clear(&rettv);
msg_list = saved_msg_list; // Restore the exception context.
recursive--;

free_vim_args:
while (i > 0) {
Expand All @@ -355,7 +394,7 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err)

/// Calls a VimL function with the given arguments.
///
/// On VimL error: Returns a generic error; v:errmsg is not updated.
/// On execution error: fails with VimL error, does not update v:errmsg.
///
/// @param fn Function to call
/// @param args Function arguments packed in an Array
Expand All @@ -369,6 +408,8 @@ Object nvim_call_function(String fn, Array args, Error *err)

/// Calls a VimL |Dictionary-function| with the given arguments.
///
/// On execution error: fails with VimL error, does not update v:errmsg.
///
/// @param dict Dictionary, or String evaluating to a VimL |self| dict
/// @param fn Name of the function defined on the VimL dict
/// @param args Function arguments packed in an Array
Expand Down Expand Up @@ -934,27 +975,26 @@ Array nvim_get_api_info(uint64_t channel_id)
return rv;
}

/// Call many api methods atomically
/// Calls many API methods atomically.
///
/// This has two main usages: Firstly, to perform several requests from an
/// async context atomically, i.e. without processing requests from other rpc
/// clients or redrawing or allowing user interaction in between. Note that api
/// methods that could fire autocommands or do event processing still might do
/// so. For instance invoking the :sleep command might call timer callbacks.
/// Secondly, it can be used to reduce rpc overhead (roundtrips) when doing
/// many requests in sequence.
/// This has two main usages:
/// 1. To perform several requests from an async context atomically, i.e.
/// without interleaving redraws, RPC requests from other clients, or user
/// interactions (however API methods may trigger autocommands or event
/// processing which have such side-effects, e.g. |:sleep| may wake timers).
/// 2. To minimize RPC overhead (roundtrips) of a sequence of many requests.
///
/// @param calls an array of calls, where each call is described by an array
/// with two elements: the request name, and an array of arguments.
/// @param[out] err Details of a validation error of the nvim_multi_request call
/// itself, i e malformatted `calls` parameter. Errors from called methods will
/// itself, i.e. malformed `calls` parameter. Errors from called methods will
/// be indicated in the return value, see below.
///
/// @return an array with two elements. The first is an array of return
/// values. The second is NIL if all calls succeeded. If a call resulted in
/// an error, it is a three-element array with the zero-based index of the call
/// which resulted in an error, the error type and the error message. If an
/// error ocurred, the values from all preceding calls will still be returned.
/// error occurred, the values from all preceding calls will still be returned.
Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
Expand Down
29 changes: 17 additions & 12 deletions src/nvim/eval.c
Expand Up @@ -6241,20 +6241,21 @@ bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID)
/// invoked function uses them. It is called like this:
/// new_argcount = argv_func(current_argcount, argv, called_func_argcount)
///
/// Return FAIL when the function can't be called, OK otherwise.
/// Also returns OK when an error was encountered while executing the function.
/// @return FAIL if function cannot be called, else OK (even if an error
/// occurred while executing the function! Set `msg_list` to capture
/// the error, see do_cmdline()).
int
call_func(
const char_u *funcname, // name of the function
int len, // length of "name"
typval_T *rettv, // return value goes here
typval_T *rettv, // [out] value goes here
int argcount_in, // number of "argvars"
typval_T *argvars_in, // vars for arguments, must have "argcount"
// PLUS ONE elements!
ArgvFunc argv_func, // function to fill in argvars
linenr_T firstline, // first line of range
linenr_T lastline, // last line of range
int *doesrange, // return: function handled range
int *doesrange, // [out] function handled range
bool evaluate,
partial_T *partial, // optional, can be NULL
dict_T *selfdict_in // Dictionary for "self"
Expand Down Expand Up @@ -6428,21 +6429,25 @@ call_func(
return ret;
}

/*
* Give an error message with a function name. Handle <SNR> things.
* "ermsg" is to be passed without translation, use N_() instead of _().
*/
/// Give an error message with a function name. Handle <SNR> things.
///
/// @param ermsg must be passed without translation (use N_() instead of _()).
/// @param name function name
static void emsg_funcname(char *ermsg, char_u *name)
{
char_u *p;
char_u *p;

if (*name == K_SPECIAL)
if (*name == K_SPECIAL) {
p = concat_str((char_u *)"<SNR>", name + 3);
else
} else {
p = name;
}

EMSG2(_(ermsg), p);
if (p != name)

if (p != name) {
xfree(p);
}
}

/*
Expand Down
31 changes: 15 additions & 16 deletions src/nvim/ex_eval.c
Expand Up @@ -28,22 +28,21 @@
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_eval.c.generated.h"
#endif
/*
* Exception handling terms:
*
* :try ":try" command \
* ... try block |
* :catch RE ":catch" command |
* ... catch clause |- try conditional
* :finally ":finally" command |
* ... finally clause |
* :endtry ":endtry" command /
*
* The try conditional may have any number of catch clauses and at most one
* finally clause. A ":throw" command can be inside the try block, a catch
* clause, the finally clause, or in a function called or script sourced from
* there or even outside the try conditional. Try conditionals may be nested.
*/

// Exception handling terms:
//
// :try ":try" command ─┐
// ... try block │
// :catch RE ":catch" command │
// ... catch clause ├─ try conditional
// :finally ":finally" command │
// ... finally clause │
// :endtry ":endtry" command ─┘
//
// The try conditional may have any number of catch clauses and at most one
// finally clause. A ":throw" command can be inside the try block, a catch
// clause, the finally clause, or in a function called or script sourced from
// there or even outside the try conditional. Try conditionals may be nested.

// Configuration whether an exception is thrown on error or interrupt. When
// the preprocessor macros below evaluate to FALSE, an error (did_emsg) or
Expand Down

0 comments on commit 8d40b36

Please sign in to comment.