mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge pull request #14314 from janlazo/vim-8.2.2720
vim-patch:8.0.{1505,1511},8.2.{1304,1305,1393,1508,1791,2720}
This commit is contained in:
commit
ed3c0a27c7
@ -804,6 +804,19 @@ DEFINING BREAKPOINTS
|
||||
< Note that this only works for commands that are executed when
|
||||
sourcing the file, not for a function defined in that file.
|
||||
|
||||
:breaka[dd] expr {expression}
|
||||
Sets a breakpoint, that will break whenever the {expression}
|
||||
evaluates to a different value. Example: >
|
||||
:breakadd expr g:lnum
|
||||
|
||||
< Will break, whenever the global variable lnum changes.
|
||||
Note if you watch a |script-variable| this will break
|
||||
when switching scripts, since the script variable is only
|
||||
valid in the script where it has been defined and if that
|
||||
script is called from several other scripts, this will stop
|
||||
whenever that particular variable will become visible or
|
||||
unaccessible again.
|
||||
|
||||
The [lnum] is the line number of the breakpoint. Vim will stop at or after
|
||||
this line. When omitted line 1 is used.
|
||||
|
||||
|
403
src/nvim/eval.c
403
src/nvim/eval.c
@ -916,6 +916,17 @@ varnumber_T eval_to_number(char_u *expr)
|
||||
return retval;
|
||||
}
|
||||
|
||||
// Top level evaluation function.
|
||||
// Returns an allocated typval_T with the result.
|
||||
// Returns NULL when there is an error.
|
||||
typval_T *eval_expr(char_u *arg)
|
||||
{
|
||||
typval_T *tv = xmalloc(sizeof(*tv));
|
||||
if (eval0(arg, tv, NULL, true) == FAIL) {
|
||||
XFREE_CLEAR(tv);
|
||||
}
|
||||
return tv;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prepare v: variable "idx" to be used.
|
||||
@ -3129,21 +3140,6 @@ static int pattern_match(char_u *pat, char_u *text, bool ic)
|
||||
return matches;
|
||||
}
|
||||
|
||||
/*
|
||||
* types for expressions.
|
||||
*/
|
||||
typedef enum {
|
||||
TYPE_UNKNOWN = 0,
|
||||
TYPE_EQUAL, // ==
|
||||
TYPE_NEQUAL, // !=
|
||||
TYPE_GREATER, // >
|
||||
TYPE_GEQUAL, // >=
|
||||
TYPE_SMALLER, // <
|
||||
TYPE_SEQUAL, // <=
|
||||
TYPE_MATCH, // =~
|
||||
TYPE_NOMATCH, // !~
|
||||
} exptype_T;
|
||||
|
||||
// TODO(ZyX-I): move to eval/expressions
|
||||
|
||||
/*
|
||||
@ -3420,11 +3416,9 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
|
||||
{
|
||||
typval_T var2;
|
||||
char_u *p;
|
||||
int i;
|
||||
exptype_T type = TYPE_UNKNOWN;
|
||||
bool type_is = false; // true for "is" and "isnot"
|
||||
int len = 2;
|
||||
varnumber_T n1, n2;
|
||||
bool ic;
|
||||
|
||||
/*
|
||||
@ -3490,173 +3484,11 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
|
||||
tv_clear(rettv);
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
if (evaluate) {
|
||||
if (type_is && rettv->v_type != var2.v_type) {
|
||||
/* For "is" a different type always means FALSE, for "notis"
|
||||
* it means TRUE. */
|
||||
n1 = (type == TYPE_NEQUAL);
|
||||
} else if (rettv->v_type == VAR_LIST || var2.v_type == VAR_LIST) {
|
||||
if (type_is) {
|
||||
n1 = (rettv->v_type == var2.v_type
|
||||
&& rettv->vval.v_list == var2.vval.v_list);
|
||||
if (type == TYPE_NEQUAL)
|
||||
n1 = !n1;
|
||||
} else if (rettv->v_type != var2.v_type
|
||||
|| (type != TYPE_EQUAL && type != TYPE_NEQUAL)) {
|
||||
if (rettv->v_type != var2.v_type) {
|
||||
EMSG(_("E691: Can only compare List with List"));
|
||||
} else {
|
||||
EMSG(_("E692: Invalid operation for List"));
|
||||
}
|
||||
tv_clear(rettv);
|
||||
tv_clear(&var2);
|
||||
return FAIL;
|
||||
} else {
|
||||
// Compare two Lists for being equal or unequal.
|
||||
n1 = tv_list_equal(rettv->vval.v_list, var2.vval.v_list, ic, false);
|
||||
if (type == TYPE_NEQUAL) {
|
||||
n1 = !n1;
|
||||
}
|
||||
}
|
||||
} else if (rettv->v_type == VAR_DICT || var2.v_type == VAR_DICT) {
|
||||
if (type_is) {
|
||||
n1 = (rettv->v_type == var2.v_type
|
||||
&& rettv->vval.v_dict == var2.vval.v_dict);
|
||||
if (type == TYPE_NEQUAL)
|
||||
n1 = !n1;
|
||||
} else if (rettv->v_type != var2.v_type
|
||||
|| (type != TYPE_EQUAL && type != TYPE_NEQUAL)) {
|
||||
if (rettv->v_type != var2.v_type)
|
||||
EMSG(_("E735: Can only compare Dictionary with Dictionary"));
|
||||
else
|
||||
EMSG(_("E736: Invalid operation for Dictionary"));
|
||||
tv_clear(rettv);
|
||||
tv_clear(&var2);
|
||||
return FAIL;
|
||||
} else {
|
||||
// Compare two Dictionaries for being equal or unequal.
|
||||
n1 = tv_dict_equal(rettv->vval.v_dict, var2.vval.v_dict,
|
||||
ic, false);
|
||||
if (type == TYPE_NEQUAL) {
|
||||
n1 = !n1;
|
||||
}
|
||||
}
|
||||
} else if (tv_is_func(*rettv) || tv_is_func(var2)) {
|
||||
if (type != TYPE_EQUAL && type != TYPE_NEQUAL) {
|
||||
EMSG(_("E694: Invalid operation for Funcrefs"));
|
||||
tv_clear(rettv);
|
||||
tv_clear(&var2);
|
||||
return FAIL;
|
||||
}
|
||||
if ((rettv->v_type == VAR_PARTIAL
|
||||
&& rettv->vval.v_partial == NULL)
|
||||
|| (var2.v_type == VAR_PARTIAL
|
||||
&& var2.vval.v_partial == NULL)) {
|
||||
// when a partial is NULL assume not equal
|
||||
n1 = false;
|
||||
} else if (type_is) {
|
||||
if (rettv->v_type == VAR_FUNC && var2.v_type == VAR_FUNC) {
|
||||
// strings are considered the same if their value is
|
||||
// the same
|
||||
n1 = tv_equal(rettv, &var2, ic, false);
|
||||
} else if (rettv->v_type == VAR_PARTIAL
|
||||
&& var2.v_type == VAR_PARTIAL) {
|
||||
n1 = (rettv->vval.v_partial == var2.vval.v_partial);
|
||||
} else {
|
||||
n1 = false;
|
||||
}
|
||||
} else {
|
||||
n1 = tv_equal(rettv, &var2, ic, false);
|
||||
}
|
||||
if (type == TYPE_NEQUAL) {
|
||||
n1 = !n1;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* If one of the two variables is a float, compare as a float.
|
||||
* When using "=~" or "!~", always compare as string.
|
||||
*/
|
||||
else if ((rettv->v_type == VAR_FLOAT || var2.v_type == VAR_FLOAT)
|
||||
&& type != TYPE_MATCH && type != TYPE_NOMATCH) {
|
||||
float_T f1, f2;
|
||||
const int ret = typval_compare(rettv, &var2, type, type_is, ic);
|
||||
|
||||
if (rettv->v_type == VAR_FLOAT) {
|
||||
f1 = rettv->vval.v_float;
|
||||
} else {
|
||||
f1 = tv_get_number(rettv);
|
||||
}
|
||||
if (var2.v_type == VAR_FLOAT) {
|
||||
f2 = var2.vval.v_float;
|
||||
} else {
|
||||
f2 = tv_get_number(&var2);
|
||||
}
|
||||
n1 = false;
|
||||
switch (type) {
|
||||
case TYPE_EQUAL: n1 = (f1 == f2); break;
|
||||
case TYPE_NEQUAL: n1 = (f1 != f2); break;
|
||||
case TYPE_GREATER: n1 = (f1 > f2); break;
|
||||
case TYPE_GEQUAL: n1 = (f1 >= f2); break;
|
||||
case TYPE_SMALLER: n1 = (f1 < f2); break;
|
||||
case TYPE_SEQUAL: n1 = (f1 <= f2); break;
|
||||
case TYPE_UNKNOWN:
|
||||
case TYPE_MATCH:
|
||||
case TYPE_NOMATCH: break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* If one of the two variables is a number, compare as a number.
|
||||
* When using "=~" or "!~", always compare as string.
|
||||
*/
|
||||
else if ((rettv->v_type == VAR_NUMBER || var2.v_type == VAR_NUMBER)
|
||||
&& type != TYPE_MATCH && type != TYPE_NOMATCH) {
|
||||
n1 = tv_get_number(rettv);
|
||||
n2 = tv_get_number(&var2);
|
||||
switch (type) {
|
||||
case TYPE_EQUAL: n1 = (n1 == n2); break;
|
||||
case TYPE_NEQUAL: n1 = (n1 != n2); break;
|
||||
case TYPE_GREATER: n1 = (n1 > n2); break;
|
||||
case TYPE_GEQUAL: n1 = (n1 >= n2); break;
|
||||
case TYPE_SMALLER: n1 = (n1 < n2); break;
|
||||
case TYPE_SEQUAL: n1 = (n1 <= n2); break;
|
||||
case TYPE_UNKNOWN:
|
||||
case TYPE_MATCH:
|
||||
case TYPE_NOMATCH: break;
|
||||
}
|
||||
} else {
|
||||
char buf1[NUMBUFLEN];
|
||||
char buf2[NUMBUFLEN];
|
||||
const char *const s1 = tv_get_string_buf(rettv, buf1);
|
||||
const char *const s2 = tv_get_string_buf(&var2, buf2);
|
||||
if (type != TYPE_MATCH && type != TYPE_NOMATCH) {
|
||||
i = mb_strcmp_ic(ic, s1, s2);
|
||||
} else {
|
||||
i = 0;
|
||||
}
|
||||
n1 = false;
|
||||
switch (type) {
|
||||
case TYPE_EQUAL: n1 = (i == 0); break;
|
||||
case TYPE_NEQUAL: n1 = (i != 0); break;
|
||||
case TYPE_GREATER: n1 = (i > 0); break;
|
||||
case TYPE_GEQUAL: n1 = (i >= 0); break;
|
||||
case TYPE_SMALLER: n1 = (i < 0); break;
|
||||
case TYPE_SEQUAL: n1 = (i <= 0); break;
|
||||
|
||||
case TYPE_MATCH:
|
||||
case TYPE_NOMATCH: {
|
||||
n1 = pattern_match((char_u *)s2, (char_u *)s1, ic);
|
||||
if (type == TYPE_NOMATCH) {
|
||||
n1 = !n1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TYPE_UNKNOWN: break; // Avoid gcc warning.
|
||||
}
|
||||
}
|
||||
tv_clear(rettv);
|
||||
tv_clear(&var2);
|
||||
rettv->v_type = VAR_NUMBER;
|
||||
rettv->vval.v_number = n1;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8000,8 +7832,8 @@ int get_id_len(const char **const arg)
|
||||
*/
|
||||
int get_name_len(const char **const arg,
|
||||
char **alias,
|
||||
int evaluate,
|
||||
int verbose)
|
||||
bool evaluate,
|
||||
bool verbose)
|
||||
{
|
||||
int len;
|
||||
|
||||
@ -8486,10 +8318,8 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg)
|
||||
return oldval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the value of internal variable "name".
|
||||
* Return OK or FAIL.
|
||||
*/
|
||||
// Get the value of internal variable "name".
|
||||
// Return OK or FAIL. If OK is returned "rettv" must be cleared.
|
||||
int get_var_tv(
|
||||
const char *name,
|
||||
int len, // length of "name"
|
||||
@ -10746,3 +10576,202 @@ bool invoke_prompt_interrupt(void)
|
||||
tv_clear(&rettv);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compare "typ1" and "typ2". Put the result in "typ1".
|
||||
int typval_compare(
|
||||
typval_T *typ1, // first operand
|
||||
typval_T *typ2, // second operand
|
||||
exptype_T type, // operator
|
||||
bool type_is, // true for "is" and "isnot"
|
||||
bool ic // ignore case
|
||||
)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
varnumber_T n1, n2;
|
||||
|
||||
if (type_is && typ1->v_type != typ2->v_type) {
|
||||
// For "is" a different type always means false, for "notis"
|
||||
// it means true.
|
||||
n1 = type == TYPE_NEQUAL;
|
||||
} else if (typ1->v_type == VAR_LIST || typ2->v_type == VAR_LIST) {
|
||||
if (type_is) {
|
||||
n1 = typ1->v_type == typ2->v_type
|
||||
&& typ1->vval.v_list == typ2->vval.v_list;
|
||||
if (type == TYPE_NEQUAL) {
|
||||
n1 = !n1;
|
||||
}
|
||||
} else if (typ1->v_type != typ2->v_type
|
||||
|| (type != TYPE_EQUAL && type != TYPE_NEQUAL)) {
|
||||
if (typ1->v_type != typ2->v_type) {
|
||||
EMSG(_("E691: Can only compare List with List"));
|
||||
} else {
|
||||
EMSG(_("E692: Invalid operation for List"));
|
||||
}
|
||||
tv_clear(typ1);
|
||||
return FAIL;
|
||||
} else {
|
||||
// Compare two Lists for being equal or unequal.
|
||||
n1 = tv_list_equal(typ1->vval.v_list, typ2->vval.v_list, ic, false);
|
||||
if (type == TYPE_NEQUAL) {
|
||||
n1 = !n1;
|
||||
}
|
||||
}
|
||||
} else if (typ1->v_type == VAR_DICT || typ2->v_type == VAR_DICT) {
|
||||
if (type_is) {
|
||||
n1 = typ1->v_type == typ2->v_type
|
||||
&& typ1->vval.v_dict == typ2->vval.v_dict;
|
||||
if (type == TYPE_NEQUAL) {
|
||||
n1 = !n1;
|
||||
}
|
||||
} else if (typ1->v_type != typ2->v_type
|
||||
|| (type != TYPE_EQUAL && type != TYPE_NEQUAL)) {
|
||||
if (typ1->v_type != typ2->v_type) {
|
||||
EMSG(_("E735: Can only compare Dictionary with Dictionary"));
|
||||
} else {
|
||||
EMSG(_("E736: Invalid operation for Dictionary"));
|
||||
}
|
||||
tv_clear(typ1);
|
||||
return FAIL;
|
||||
} else {
|
||||
// Compare two Dictionaries for being equal or unequal.
|
||||
n1 = tv_dict_equal(typ1->vval.v_dict, typ2->vval.v_dict, ic, false);
|
||||
if (type == TYPE_NEQUAL) {
|
||||
n1 = !n1;
|
||||
}
|
||||
}
|
||||
} else if (tv_is_func(*typ1) || tv_is_func(*typ2)) {
|
||||
if (type != TYPE_EQUAL && type != TYPE_NEQUAL) {
|
||||
EMSG(_("E694: Invalid operation for Funcrefs"));
|
||||
tv_clear(typ1);
|
||||
return FAIL;
|
||||
}
|
||||
if ((typ1->v_type == VAR_PARTIAL && typ1->vval.v_partial == NULL)
|
||||
|| (typ2->v_type == VAR_PARTIAL && typ2->vval.v_partial == NULL)) {
|
||||
// when a partial is NULL assume not equal
|
||||
n1 = false;
|
||||
} else if (type_is) {
|
||||
if (typ1->v_type == VAR_FUNC && typ2->v_type == VAR_FUNC) {
|
||||
// strings are considered the same if their value is
|
||||
// the same
|
||||
n1 = tv_equal(typ1, typ2, ic, false);
|
||||
} else if (typ1->v_type == VAR_PARTIAL && typ2->v_type == VAR_PARTIAL) {
|
||||
n1 = typ1->vval.v_partial == typ2->vval.v_partial;
|
||||
} else {
|
||||
n1 = false;
|
||||
}
|
||||
} else {
|
||||
n1 = tv_equal(typ1, typ2, ic, false);
|
||||
}
|
||||
if (type == TYPE_NEQUAL) {
|
||||
n1 = !n1;
|
||||
}
|
||||
} else if ((typ1->v_type == VAR_FLOAT || typ2->v_type == VAR_FLOAT)
|
||||
&& type != TYPE_MATCH && type != TYPE_NOMATCH) {
|
||||
// If one of the two variables is a float, compare as a float.
|
||||
// When using "=~" or "!~", always compare as string.
|
||||
const float_T f1 = tv_get_float(typ1);
|
||||
const float_T f2 = tv_get_float(typ2);
|
||||
n1 = false;
|
||||
switch (type) {
|
||||
case TYPE_EQUAL: n1 = f1 == f2; break;
|
||||
case TYPE_NEQUAL: n1 = f1 != f2; break;
|
||||
case TYPE_GREATER: n1 = f1 > f2; break;
|
||||
case TYPE_GEQUAL: n1 = f1 >= f2; break;
|
||||
case TYPE_SMALLER: n1 = f1 < f2; break;
|
||||
case TYPE_SEQUAL: n1 = f1 <= f2; break;
|
||||
case TYPE_UNKNOWN:
|
||||
case TYPE_MATCH:
|
||||
case TYPE_NOMATCH: break;
|
||||
}
|
||||
} else if ((typ1->v_type == VAR_NUMBER || typ2->v_type == VAR_NUMBER)
|
||||
&& type != TYPE_MATCH && type != TYPE_NOMATCH) {
|
||||
// If one of the two variables is a number, compare as a number.
|
||||
// When using "=~" or "!~", always compare as string.
|
||||
n1 = tv_get_number(typ1);
|
||||
n2 = tv_get_number(typ2);
|
||||
switch (type) {
|
||||
case TYPE_EQUAL: n1 = n1 == n2; break;
|
||||
case TYPE_NEQUAL: n1 = n1 != n2; break;
|
||||
case TYPE_GREATER: n1 = n1 > n2; break;
|
||||
case TYPE_GEQUAL: n1 = n1 >= n2; break;
|
||||
case TYPE_SMALLER: n1 = n1 < n2; break;
|
||||
case TYPE_SEQUAL: n1 = n1 <= n2; break;
|
||||
case TYPE_UNKNOWN:
|
||||
case TYPE_MATCH:
|
||||
case TYPE_NOMATCH: break;
|
||||
}
|
||||
} else {
|
||||
char buf1[NUMBUFLEN];
|
||||
char buf2[NUMBUFLEN];
|
||||
const char *const s1 = tv_get_string_buf(typ1, buf1);
|
||||
const char *const s2 = tv_get_string_buf(typ2, buf2);
|
||||
int i;
|
||||
if (type != TYPE_MATCH && type != TYPE_NOMATCH) {
|
||||
i = mb_strcmp_ic(ic, s1, s2);
|
||||
} else {
|
||||
i = 0;
|
||||
}
|
||||
n1 = false;
|
||||
switch (type) {
|
||||
case TYPE_EQUAL: n1 = i == 0; break;
|
||||
case TYPE_NEQUAL: n1 = i != 0; break;
|
||||
case TYPE_GREATER: n1 = i > 0; break;
|
||||
case TYPE_GEQUAL: n1 = i >= 0; break;
|
||||
case TYPE_SMALLER: n1 = i < 0; break;
|
||||
case TYPE_SEQUAL: n1 = i <= 0; break;
|
||||
|
||||
case TYPE_MATCH:
|
||||
case TYPE_NOMATCH:
|
||||
n1 = pattern_match((char_u *)s2, (char_u *)s1, ic);
|
||||
if (type == TYPE_NOMATCH) {
|
||||
n1 = !n1;
|
||||
}
|
||||
break;
|
||||
case TYPE_UNKNOWN: break; // Avoid gcc warning.
|
||||
}
|
||||
}
|
||||
tv_clear(typ1);
|
||||
typ1->v_type = VAR_NUMBER;
|
||||
typ1->vval.v_number = n1;
|
||||
return OK;
|
||||
}
|
||||
|
||||
char *typval_tostring(typval_T *arg)
|
||||
{
|
||||
if (arg == NULL) {
|
||||
return xstrdup("(does not exist)");
|
||||
}
|
||||
return encode_tv2string(arg, NULL);
|
||||
}
|
||||
|
||||
bool var_exists(const char *var)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
char *tofree;
|
||||
bool n = false;
|
||||
|
||||
// get_name_len() takes care of expanding curly braces
|
||||
const char *name = var;
|
||||
const int len = get_name_len((const char **)&var, &tofree, true, false);
|
||||
if (len > 0) {
|
||||
typval_T tv;
|
||||
|
||||
if (tofree != NULL) {
|
||||
name = tofree;
|
||||
}
|
||||
n = get_var_tv(name, len, &tv, NULL, false, true) == OK;
|
||||
if (n) {
|
||||
// Handle d.key, l[idx], f(expr).
|
||||
n = handle_subscript(&var, &tv, true, false) == OK;
|
||||
if (n) {
|
||||
tv_clear(&tv);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (*var != NUL) {
|
||||
n = false;
|
||||
}
|
||||
|
||||
xfree(tofree);
|
||||
return n;
|
||||
}
|
||||
|
@ -227,6 +227,19 @@ typedef enum
|
||||
ASSERT_OTHER,
|
||||
} assert_type_T;
|
||||
|
||||
/// types for expressions.
|
||||
typedef enum {
|
||||
TYPE_UNKNOWN = 0,
|
||||
TYPE_EQUAL, ///< ==
|
||||
TYPE_NEQUAL, ///< !=
|
||||
TYPE_GREATER, ///< >
|
||||
TYPE_GEQUAL, ///< >=
|
||||
TYPE_SMALLER, ///< <
|
||||
TYPE_SEQUAL, ///< <=
|
||||
TYPE_MATCH, ///< =~
|
||||
TYPE_NOMATCH, ///< !~
|
||||
} exptype_T;
|
||||
|
||||
/// Type for dict_list function
|
||||
typedef enum {
|
||||
kDictListKeys, ///< List dictionary keys.
|
||||
|
@ -2051,7 +2051,6 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
{
|
||||
int n = false;
|
||||
int len = 0;
|
||||
|
||||
const char *p = tv_get_string(&argvars[0]);
|
||||
if (*p == '$') { // Environment variable.
|
||||
@ -2082,29 +2081,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
n = au_exists(p + 1);
|
||||
}
|
||||
} else { // Internal variable.
|
||||
typval_T tv;
|
||||
|
||||
// get_name_len() takes care of expanding curly braces
|
||||
const char *name = p;
|
||||
char *tofree;
|
||||
len = get_name_len((const char **)&p, &tofree, true, false);
|
||||
if (len > 0) {
|
||||
if (tofree != NULL) {
|
||||
name = tofree;
|
||||
}
|
||||
n = (get_var_tv(name, len, &tv, NULL, false, true) == OK);
|
||||
if (n) {
|
||||
// Handle d.key, l[idx], f(expr).
|
||||
n = (handle_subscript(&p, &tv, true, false) == OK);
|
||||
if (n) {
|
||||
tv_clear(&tv);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (*p != NUL)
|
||||
n = FALSE;
|
||||
|
||||
xfree(tofree);
|
||||
n = var_exists(p);
|
||||
}
|
||||
|
||||
rettv->vval.v_number = n;
|
||||
|
@ -120,6 +120,9 @@ struct source_cookie {
|
||||
/// batch mode debugging: don't save and restore typeahead.
|
||||
static bool debug_greedy = false;
|
||||
|
||||
static char *debug_oldval = NULL; // old and newval for debug expressions
|
||||
static char *debug_newval = NULL;
|
||||
|
||||
/// Debug mode. Repeatedly get Ex commands, until told to continue normal
|
||||
/// execution.
|
||||
void do_debug(char_u *cmd)
|
||||
@ -166,6 +169,16 @@ void do_debug(char_u *cmd)
|
||||
if (!debug_did_msg) {
|
||||
MSG(_("Entering Debug mode. Type \"cont\" to continue."));
|
||||
}
|
||||
if (debug_oldval != NULL) {
|
||||
smsg(_("Oldval = \"%s\""), debug_oldval);
|
||||
xfree(debug_oldval);
|
||||
debug_oldval = NULL;
|
||||
}
|
||||
if (debug_newval != NULL) {
|
||||
smsg(_("Newval = \"%s\""), debug_newval);
|
||||
xfree(debug_newval);
|
||||
debug_newval = NULL;
|
||||
}
|
||||
if (sourcing_name != NULL) {
|
||||
msg(sourcing_name);
|
||||
}
|
||||
@ -174,7 +187,6 @@ void do_debug(char_u *cmd)
|
||||
} else {
|
||||
smsg(_("cmd: %s"), cmd);
|
||||
}
|
||||
|
||||
// Repeat getting a command and executing it.
|
||||
for (;; ) {
|
||||
msg_scroll = true;
|
||||
@ -514,11 +526,13 @@ bool dbg_check_skipped(exarg_T *eap)
|
||||
/// This is a grow-array of structs.
|
||||
struct debuggy {
|
||||
int dbg_nr; ///< breakpoint number
|
||||
int dbg_type; ///< DBG_FUNC or DBG_FILE
|
||||
char_u *dbg_name; ///< function or file name
|
||||
int dbg_type; ///< DBG_FUNC or DBG_FILE or DBG_EXPR
|
||||
char_u *dbg_name; ///< function, expression or file name
|
||||
regprog_T *dbg_prog; ///< regexp program
|
||||
linenr_T dbg_lnum; ///< line number in function or file
|
||||
int dbg_forceit; ///< ! used
|
||||
typval_T *dbg_val; ///< last result of watchexpression
|
||||
int dbg_level; ///< stored nested level for expr
|
||||
};
|
||||
|
||||
static garray_T dbg_breakp = { 0, 0, sizeof(struct debuggy), 4, NULL };
|
||||
@ -530,6 +544,7 @@ static int last_breakp = 0; // nr of last defined breakpoint
|
||||
static garray_T prof_ga = { 0, 0, sizeof(struct debuggy), 4, NULL };
|
||||
#define DBG_FUNC 1
|
||||
#define DBG_FILE 2
|
||||
#define DBG_EXPR 3
|
||||
|
||||
|
||||
/// Parse the arguments of ":profile", ":breakadd" or ":breakdel" and put them
|
||||
@ -562,6 +577,8 @@ static int dbg_parsearg(char_u *arg, garray_T *gap)
|
||||
}
|
||||
bp->dbg_type = DBG_FILE;
|
||||
here = true;
|
||||
} else if (gap != &prof_ga && STRNCMP(p, "expr", 4) == 0) {
|
||||
bp->dbg_type = DBG_EXPR;
|
||||
} else {
|
||||
EMSG2(_(e_invarg2), p);
|
||||
return FAIL;
|
||||
@ -590,6 +607,9 @@ static int dbg_parsearg(char_u *arg, garray_T *gap)
|
||||
bp->dbg_name = vim_strsave(p);
|
||||
} else if (here) {
|
||||
bp->dbg_name = vim_strsave(curbuf->b_ffname);
|
||||
} else if (bp->dbg_type == DBG_EXPR) {
|
||||
bp->dbg_name = vim_strsave(p);
|
||||
bp->dbg_val = eval_expr(bp->dbg_name);
|
||||
} else {
|
||||
// Expand the file name in the same way as do_source(). This means
|
||||
// doing it twice, so that $DIR/file gets expanded when $DIR is
|
||||
@ -621,7 +641,6 @@ static int dbg_parsearg(char_u *arg, garray_T *gap)
|
||||
void ex_breakadd(exarg_T *eap)
|
||||
{
|
||||
struct debuggy *bp;
|
||||
char_u *pat;
|
||||
garray_T *gap;
|
||||
|
||||
gap = &dbg_breakp;
|
||||
@ -633,7 +652,8 @@ void ex_breakadd(exarg_T *eap)
|
||||
bp = &DEBUGGY(gap, gap->ga_len);
|
||||
bp->dbg_forceit = eap->forceit;
|
||||
|
||||
pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, false);
|
||||
if (bp->dbg_type != DBG_EXPR) {
|
||||
char_u *pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, false);
|
||||
if (pat != NULL) {
|
||||
bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
|
||||
xfree(pat);
|
||||
@ -650,6 +670,11 @@ void ex_breakadd(exarg_T *eap)
|
||||
}
|
||||
gap->ga_len++;
|
||||
}
|
||||
} else {
|
||||
// DBG_EXPR
|
||||
DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp;
|
||||
debug_tick++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -691,7 +716,7 @@ void ex_breakdel(exarg_T *eap)
|
||||
todel = 0;
|
||||
del_all = true;
|
||||
} else {
|
||||
// ":breakdel {func|file} [lnum] {name}"
|
||||
// ":breakdel {func|file|expr} [lnum] {name}"
|
||||
if (dbg_parsearg(eap->arg, gap) == FAIL) {
|
||||
return;
|
||||
}
|
||||
@ -716,6 +741,10 @@ void ex_breakdel(exarg_T *eap)
|
||||
} else {
|
||||
while (!GA_EMPTY(gap)) {
|
||||
xfree(DEBUGGY(gap, todel).dbg_name);
|
||||
if (DEBUGGY(gap, todel).dbg_type == DBG_EXPR
|
||||
&& DEBUGGY(gap, todel).dbg_val != NULL) {
|
||||
tv_free(DEBUGGY(gap, todel).dbg_val);
|
||||
}
|
||||
vim_regfree(DEBUGGY(gap, todel).dbg_prog);
|
||||
gap->ga_len--;
|
||||
if (todel < gap->ga_len) {
|
||||
@ -750,11 +779,15 @@ void ex_breaklist(exarg_T *eap)
|
||||
if (bp->dbg_type == DBG_FILE) {
|
||||
home_replace(NULL, bp->dbg_name, NameBuff, MAXPATHL, true);
|
||||
}
|
||||
if (bp->dbg_type != DBG_EXPR) {
|
||||
smsg(_("%3d %s %s line %" PRId64),
|
||||
bp->dbg_nr,
|
||||
bp->dbg_type == DBG_FUNC ? "func" : "file",
|
||||
bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff,
|
||||
(int64_t)bp->dbg_lnum);
|
||||
} else {
|
||||
smsg(_("%3d expr %s"), bp->dbg_nr, bp->dbg_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -814,6 +847,7 @@ debuggy_find(
|
||||
// an already found breakpoint.
|
||||
bp = &DEBUGGY(gap, i);
|
||||
if ((bp->dbg_type == DBG_FILE) == file
|
||||
&& bp->dbg_type != DBG_EXPR
|
||||
&& (gap == &prof_ga
|
||||
|| (bp->dbg_lnum > after && (lnum == 0 || bp->dbg_lnum < lnum)))) {
|
||||
// Save the value of got_int and reset it. We don't want a
|
||||
@ -827,6 +861,46 @@ debuggy_find(
|
||||
*fp = bp->dbg_forceit;
|
||||
}
|
||||
}
|
||||
got_int |= prev_got_int;
|
||||
} else if (bp->dbg_type == DBG_EXPR) {
|
||||
bool line = false;
|
||||
|
||||
prev_got_int = got_int;
|
||||
got_int = false;
|
||||
|
||||
typval_T *tv = eval_expr(bp->dbg_name);
|
||||
if (tv != NULL) {
|
||||
if (bp->dbg_val == NULL) {
|
||||
debug_oldval = typval_tostring(NULL);
|
||||
bp->dbg_val = tv;
|
||||
debug_newval = typval_tostring(bp->dbg_val);
|
||||
line = true;
|
||||
} else {
|
||||
if (typval_compare(tv, bp->dbg_val, TYPE_EQUAL, true, false) == OK
|
||||
&& tv->vval.v_number == false) {
|
||||
line = true;
|
||||
debug_oldval = typval_tostring(bp->dbg_val);
|
||||
// Need to evaluate again, typval_compare() overwrites "tv".
|
||||
typval_T *v = eval_expr(bp->dbg_name);
|
||||
debug_newval = typval_tostring(v);
|
||||
tv_free(bp->dbg_val);
|
||||
bp->dbg_val = v;
|
||||
}
|
||||
tv_free(tv);
|
||||
}
|
||||
} else if (bp->dbg_val != NULL) {
|
||||
debug_oldval = typval_tostring(bp->dbg_val);
|
||||
debug_newval = typval_tostring(NULL);
|
||||
tv_free(bp->dbg_val);
|
||||
bp->dbg_val = NULL;
|
||||
line = true;
|
||||
}
|
||||
|
||||
if (line) {
|
||||
lnum = after > 0 ? after : 1;
|
||||
break;
|
||||
}
|
||||
|
||||
got_int |= prev_got_int;
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,9 @@
|
||||
" For csh:
|
||||
" setenv TEST_FILTER Test_channel
|
||||
"
|
||||
" While working on a test you can make $TEST_NO_RETRY non-empty to not retry:
|
||||
" export TEST_NO_RETRY=yes
|
||||
"
|
||||
" To ignore failure for tests that are known to fail in a certain environment,
|
||||
" set $TEST_MAY_FAIL to a comma separated list of function names. E.g. for
|
||||
" sh/bash:
|
||||
@ -413,9 +416,11 @@ for s:test in sort(s:tests)
|
||||
call RunTheTest(s:test)
|
||||
|
||||
" Repeat a flaky test. Give up when:
|
||||
" - $TEST_NO_RETRY is not empty
|
||||
" - it fails again with the same message
|
||||
" - it fails five times (with a different message)
|
||||
if len(v:errors) > 0
|
||||
\ && $TEST_NO_RETRY == ''
|
||||
\ && (index(s:flaky_tests, s:test) >= 0
|
||||
\ || g:test_is_flaky)
|
||||
while 1
|
||||
|
@ -2,6 +2,31 @@
|
||||
|
||||
source shared.vim
|
||||
source screendump.vim
|
||||
source check.vim
|
||||
|
||||
func CheckCWD()
|
||||
" Check that the longer lines don't wrap due to the length of the script name
|
||||
" in cwd
|
||||
let script_len = len( getcwd() .. '/Xtest1.vim' )
|
||||
let longest_line = len( 'Breakpoint in "" line 1' )
|
||||
if script_len > ( 75 - longest_line )
|
||||
throw 'Skipped: Your CWD has too many characters'
|
||||
endif
|
||||
endfunc
|
||||
command! -nargs=0 -bar CheckCWD call CheckCWD()
|
||||
|
||||
func CheckDbgOutput(buf, lines, options = {})
|
||||
" Verify the expected output
|
||||
let lnum = 20 - len(a:lines)
|
||||
for l in a:lines
|
||||
if get(a:options, 'match', 'equal') ==# 'pattern'
|
||||
call WaitForAssert({-> assert_match(l, term_getline(a:buf, lnum))}, 200)
|
||||
else
|
||||
call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))}, 200)
|
||||
endif
|
||||
let lnum += 1
|
||||
endfor
|
||||
endfunc
|
||||
|
||||
" Run a Vim debugger command
|
||||
" If the expected output argument is supplied, then check for it.
|
||||
@ -10,20 +35,17 @@ func RunDbgCmd(buf, cmd, ...)
|
||||
call term_wait(a:buf)
|
||||
|
||||
if a:0 != 0
|
||||
" Verify the expected output
|
||||
let lnum = 20 - len(a:1)
|
||||
for l in a:1
|
||||
call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))})
|
||||
let lnum += 1
|
||||
endfor
|
||||
let options = #{match: 'equal'}
|
||||
if a:0 > 1
|
||||
call extend(options, a:2)
|
||||
endif
|
||||
call CheckDbgOutput(a:buf, a:1, options)
|
||||
endif
|
||||
endfunc
|
||||
|
||||
" Debugger tests
|
||||
func Test_Debugger()
|
||||
if !CanRunVimInTerminal()
|
||||
throw 'Skipped: cannot run Vim in a terminal window'
|
||||
endif
|
||||
CheckRunVimInTerminal
|
||||
|
||||
" Create a Vim script with some functions
|
||||
let lines =<< trim END
|
||||
@ -317,6 +339,785 @@ func Test_Debugger()
|
||||
call delete('Xtest.vim')
|
||||
endfunc
|
||||
|
||||
func Test_Backtrace_Through_Source()
|
||||
CheckRunVimInTerminal
|
||||
CheckCWD
|
||||
let file1 =<< trim END
|
||||
func SourceAnotherFile()
|
||||
source Xtest2.vim
|
||||
endfunc
|
||||
|
||||
func CallAFunction()
|
||||
call SourceAnotherFile()
|
||||
call File2Function()
|
||||
endfunc
|
||||
|
||||
func GlobalFunction()
|
||||
call CallAFunction()
|
||||
endfunc
|
||||
END
|
||||
call writefile(file1, 'Xtest1.vim')
|
||||
|
||||
let file2 =<< trim END
|
||||
func DoAThing()
|
||||
echo "DoAThing"
|
||||
endfunc
|
||||
|
||||
func File2Function()
|
||||
call DoAThing()
|
||||
endfunc
|
||||
|
||||
call File2Function()
|
||||
END
|
||||
call writefile(file2, 'Xtest2.vim')
|
||||
|
||||
let buf = RunVimInTerminal('-S Xtest1.vim', {})
|
||||
|
||||
call RunDbgCmd(buf,
|
||||
\ ':debug call GlobalFunction()',
|
||||
\ ['cmd: call GlobalFunction()'])
|
||||
call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])
|
||||
|
||||
call RunDbgCmd(buf, 'backtrace', ['>backtrace',
|
||||
\ '->0 function GlobalFunction',
|
||||
\ 'line 1: call CallAFunction()'])
|
||||
|
||||
call RunDbgCmd(buf, 'step', ['line 1: call SourceAnotherFile()'])
|
||||
call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
|
||||
|
||||
call RunDbgCmd(buf, 'backtrace', ['>backtrace',
|
||||
\ ' 2 function GlobalFunction[1]',
|
||||
\ ' 1 CallAFunction[1]',
|
||||
\ '->0 SourceAnotherFile',
|
||||
\ 'line 1: source Xtest2.vim'])
|
||||
|
||||
" Step into the 'source' command. Note that we print the full trace all the
|
||||
" way though the source command.
|
||||
call RunDbgCmd(buf, 'step', ['line 1: func DoAThing()'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ '->0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()'])
|
||||
|
||||
call RunDbgCmd( buf, 'up' )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ '->1 SourceAnotherFile[1]',
|
||||
\ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'up' )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ '->2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'up' )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ '->3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'up', [ 'frame at highest level: 3' ] )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ '->3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'down' )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ '->2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'down' )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ '->1 SourceAnotherFile[1]',
|
||||
\ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'down' )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ '->0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'down', [ 'frame is zero' ] )
|
||||
|
||||
" step until we have another meaninfgul trace
|
||||
call RunDbgCmd(buf, 'step', ['line 5: func File2Function()'])
|
||||
call RunDbgCmd(buf, 'step', ['line 9: call File2Function()'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ '->0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 9: call File2Function()'])
|
||||
|
||||
call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
|
||||
call RunDbgCmd(buf, 'step', ['line 1: echo "DoAThing"'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 5 function GlobalFunction[1]',
|
||||
\ ' 4 CallAFunction[1]',
|
||||
\ ' 3 SourceAnotherFile[1]',
|
||||
\ ' 2 script ' .. getcwd() .. '/Xtest2.vim[9]',
|
||||
\ ' 1 function File2Function[1]',
|
||||
\ '->0 DoAThing',
|
||||
\ 'line 1: echo "DoAThing"'])
|
||||
|
||||
" Now, step (back to Xfile1.vim), and call the function _in_ Xfile2.vim
|
||||
call RunDbgCmd(buf, 'step', ['line 1: End of function'])
|
||||
call RunDbgCmd(buf, 'step', ['line 1: End of function'])
|
||||
call RunDbgCmd(buf, 'step', ['line 10: End of sourced file'])
|
||||
call RunDbgCmd(buf, 'step', ['line 1: End of function'])
|
||||
call RunDbgCmd(buf, 'step', ['line 2: call File2Function()'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 1 function GlobalFunction[1]',
|
||||
\ '->0 CallAFunction',
|
||||
\ 'line 2: call File2Function()'])
|
||||
|
||||
call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 2 function GlobalFunction[1]',
|
||||
\ ' 1 CallAFunction[2]',
|
||||
\ '->0 File2Function',
|
||||
\ 'line 1: call DoAThing()'])
|
||||
|
||||
call StopVimInTerminal(buf)
|
||||
call delete('Xtest1.vim')
|
||||
call delete('Xtest2.vim')
|
||||
endfunc
|
||||
|
||||
func Test_Backtrace_Autocmd()
|
||||
CheckRunVimInTerminal
|
||||
CheckCWD
|
||||
let file1 =<< trim END
|
||||
func SourceAnotherFile()
|
||||
source Xtest2.vim
|
||||
endfunc
|
||||
|
||||
func CallAFunction()
|
||||
call SourceAnotherFile()
|
||||
call File2Function()
|
||||
endfunc
|
||||
|
||||
func GlobalFunction()
|
||||
call CallAFunction()
|
||||
endfunc
|
||||
|
||||
au User TestGlobalFunction :call GlobalFunction() | echo "Done"
|
||||
END
|
||||
call writefile(file1, 'Xtest1.vim')
|
||||
|
||||
let file2 =<< trim END
|
||||
func DoAThing()
|
||||
echo "DoAThing"
|
||||
endfunc
|
||||
|
||||
func File2Function()
|
||||
call DoAThing()
|
||||
endfunc
|
||||
|
||||
call File2Function()
|
||||
END
|
||||
call writefile(file2, 'Xtest2.vim')
|
||||
|
||||
let buf = RunVimInTerminal('-S Xtest1.vim', {})
|
||||
|
||||
call RunDbgCmd(buf,
|
||||
\ ':debug doautocmd User TestGlobalFunction',
|
||||
\ ['cmd: doautocmd User TestGlobalFunction'])
|
||||
call RunDbgCmd(buf, 'step', ['cmd: call GlobalFunction() | echo "Done"'])
|
||||
|
||||
" At this point the ontly thing in the stack is the autocommand
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ '->0 User Autocommands for "TestGlobalFunction"',
|
||||
\ 'cmd: call GlobalFunction() | echo "Done"'])
|
||||
|
||||
" And now we're back into the call stack
|
||||
call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 1 User Autocommands for "TestGlobalFunction"',
|
||||
\ '->0 function GlobalFunction',
|
||||
\ 'line 1: call CallAFunction()'])
|
||||
|
||||
call RunDbgCmd(buf, 'step', ['line 1: call SourceAnotherFile()'])
|
||||
call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
|
||||
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 3 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 2 function GlobalFunction[1]',
|
||||
\ ' 1 CallAFunction[1]',
|
||||
\ '->0 SourceAnotherFile',
|
||||
\ 'line 1: source Xtest2.vim'])
|
||||
|
||||
" Step into the 'source' command. Note that we print the full trace all the
|
||||
" way though the source command.
|
||||
call RunDbgCmd(buf, 'step', ['line 1: func DoAThing()'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 4 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ '->0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()'])
|
||||
|
||||
call RunDbgCmd( buf, 'up' )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 4 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ '->1 SourceAnotherFile[1]',
|
||||
\ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'up' )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 4 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ '->2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'up' )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 4 User Autocommands for "TestGlobalFunction"',
|
||||
\ '->3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'up' )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ '->4 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'up', [ 'frame at highest level: 4' ] )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ '->4 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'down' )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 4 User Autocommands for "TestGlobalFunction"',
|
||||
\ '->3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
|
||||
call RunDbgCmd( buf, 'down' )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 4 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ '->2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'down' )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 4 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ '->1 SourceAnotherFile[1]',
|
||||
\ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'down' )
|
||||
call RunDbgCmd( buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 4 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ '->0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 1: func DoAThing()' ] )
|
||||
|
||||
call RunDbgCmd( buf, 'down', [ 'frame is zero' ] )
|
||||
|
||||
" step until we have another meaninfgul trace
|
||||
call RunDbgCmd(buf, 'step', ['line 5: func File2Function()'])
|
||||
call RunDbgCmd(buf, 'step', ['line 9: call File2Function()'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 4 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 3 function GlobalFunction[1]',
|
||||
\ ' 2 CallAFunction[1]',
|
||||
\ ' 1 SourceAnotherFile[1]',
|
||||
\ '->0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ 'line 9: call File2Function()'])
|
||||
|
||||
call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
|
||||
call RunDbgCmd(buf, 'step', ['line 1: echo "DoAThing"'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 6 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 5 function GlobalFunction[1]',
|
||||
\ ' 4 CallAFunction[1]',
|
||||
\ ' 3 SourceAnotherFile[1]',
|
||||
\ ' 2 script ' .. getcwd() .. '/Xtest2.vim[9]',
|
||||
\ ' 1 function File2Function[1]',
|
||||
\ '->0 DoAThing',
|
||||
\ 'line 1: echo "DoAThing"'])
|
||||
|
||||
" Now, step (back to Xfile1.vim), and call the function _in_ Xfile2.vim
|
||||
call RunDbgCmd(buf, 'step', ['line 1: End of function'])
|
||||
call RunDbgCmd(buf, 'step', ['line 1: End of function'])
|
||||
call RunDbgCmd(buf, 'step', ['line 10: End of sourced file'])
|
||||
call RunDbgCmd(buf, 'step', ['line 1: End of function'])
|
||||
call RunDbgCmd(buf, 'step', ['line 2: call File2Function()'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 2 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 1 function GlobalFunction[1]',
|
||||
\ '->0 CallAFunction',
|
||||
\ 'line 2: call File2Function()'])
|
||||
|
||||
call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 3 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 2 function GlobalFunction[1]',
|
||||
\ ' 1 CallAFunction[2]',
|
||||
\ '->0 File2Function',
|
||||
\ 'line 1: call DoAThing()'])
|
||||
|
||||
|
||||
" Now unwind so that we get back to the original autocommand (and the second
|
||||
" cmd echo "Done")
|
||||
call RunDbgCmd(buf, 'finish', ['line 1: End of function'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 3 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 2 function GlobalFunction[1]',
|
||||
\ ' 1 CallAFunction[2]',
|
||||
\ '->0 File2Function',
|
||||
\ 'line 1: End of function'])
|
||||
|
||||
call RunDbgCmd(buf, 'finish', ['line 2: End of function'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 2 User Autocommands for "TestGlobalFunction"',
|
||||
\ ' 1 function GlobalFunction[1]',
|
||||
\ '->0 CallAFunction',
|
||||
\ 'line 2: End of function'])
|
||||
|
||||
call RunDbgCmd(buf, 'finish', ['line 1: End of function'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 1 User Autocommands for "TestGlobalFunction"',
|
||||
\ '->0 function GlobalFunction',
|
||||
\ 'line 1: End of function'])
|
||||
|
||||
call RunDbgCmd(buf, 'step', ['cmd: echo "Done"'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ '->0 User Autocommands for "TestGlobalFunction"',
|
||||
\ 'cmd: echo "Done"'])
|
||||
|
||||
call StopVimInTerminal(buf)
|
||||
call delete('Xtest1.vim')
|
||||
call delete('Xtest2.vim')
|
||||
endfunc
|
||||
|
||||
func Test_Backtrace_CmdLine()
|
||||
CheckRunVimInTerminal
|
||||
CheckCWD
|
||||
let file1 =<< trim END
|
||||
func SourceAnotherFile()
|
||||
source Xtest2.vim
|
||||
endfunc
|
||||
|
||||
func CallAFunction()
|
||||
call SourceAnotherFile()
|
||||
call File2Function()
|
||||
endfunc
|
||||
|
||||
func GlobalFunction()
|
||||
call CallAFunction()
|
||||
endfunc
|
||||
|
||||
au User TestGlobalFunction :call GlobalFunction() | echo "Done"
|
||||
END
|
||||
call writefile(file1, 'Xtest1.vim')
|
||||
|
||||
let file2 =<< trim END
|
||||
func DoAThing()
|
||||
echo "DoAThing"
|
||||
endfunc
|
||||
|
||||
func File2Function()
|
||||
call DoAThing()
|
||||
endfunc
|
||||
|
||||
call File2Function()
|
||||
END
|
||||
call writefile(file2, 'Xtest2.vim')
|
||||
|
||||
let buf = RunVimInTerminal(
|
||||
\ '-S Xtest1.vim -c "debug call GlobalFunction()"',
|
||||
\ {'wait_for_ruler': 0})
|
||||
|
||||
" Need to wait for the vim-in-terminal to be ready
|
||||
call CheckDbgOutput(buf, ['command line',
|
||||
\ 'cmd: call GlobalFunction()'])
|
||||
|
||||
" At this point the ontly thing in the stack is the cmdline
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ '->0 command line',
|
||||
\ 'cmd: call GlobalFunction()'])
|
||||
|
||||
" And now we're back into the call stack
|
||||
call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '>backtrace',
|
||||
\ ' 1 command line',
|
||||
\ '->0 function GlobalFunction',
|
||||
\ 'line 1: call CallAFunction()'])
|
||||
|
||||
call StopVimInTerminal(buf)
|
||||
call delete('Xtest1.vim')
|
||||
call delete('Xtest2.vim')
|
||||
endfunc
|
||||
|
||||
func Test_Backtrace_DefFunction()
|
||||
CheckRunVimInTerminal
|
||||
CheckCWD
|
||||
let file1 =<< trim END
|
||||
vim9script
|
||||
import File2Function from './Xtest2.vim'
|
||||
|
||||
def SourceAnotherFile()
|
||||
source Xtest2.vim
|
||||
enddef
|
||||
|
||||
def CallAFunction()
|
||||
SourceAnotherFile()
|
||||
File2Function()
|
||||
enddef
|
||||
|
||||
def g:GlobalFunction()
|
||||
CallAFunction()
|
||||
enddef
|
||||
|
||||
defcompile
|
||||
END
|
||||
call writefile(file1, 'Xtest1.vim')
|
||||
|
||||
let file2 =<< trim END
|
||||
vim9script
|
||||
|
||||
def DoAThing(): number
|
||||
var a = 100 * 2
|
||||
a += 3
|
||||
return a
|
||||
enddef
|
||||
|
||||
export def File2Function()
|
||||
DoAThing()
|
||||
enddef
|
||||
|
||||
defcompile
|
||||
File2Function()
|
||||
END
|
||||
call writefile(file2, 'Xtest2.vim')
|
||||
|
||||
let buf = RunVimInTerminal('-S Xtest1.vim', {})
|
||||
|
||||
call RunDbgCmd(buf,
|
||||
\ ':debug call GlobalFunction()',
|
||||
\ ['cmd: call GlobalFunction()'])
|
||||
|
||||
" FIXME: Vim9 lines are not debugged!
|
||||
call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
|
||||
|
||||
" But they do appear in the backtrace
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '\V>backtrace',
|
||||
\ '\V 2 function GlobalFunction[1]',
|
||||
\ '\V 1 <SNR>\.\*_CallAFunction[1]',
|
||||
\ '\V->0 <SNR>\.\*_SourceAnotherFile',
|
||||
\ '\Vline 1: source Xtest2.vim'],
|
||||
\ #{match: 'pattern'})
|
||||
|
||||
|
||||
call RunDbgCmd(buf, 'step', ['line 1: vim9script'])
|
||||
call RunDbgCmd(buf, 'step', ['line 3: def DoAThing(): number'])
|
||||
call RunDbgCmd(buf, 'step', ['line 9: export def File2Function()'])
|
||||
call RunDbgCmd(buf, 'step', ['line 9: def File2Function()'])
|
||||
call RunDbgCmd(buf, 'step', ['line 13: defcompile'])
|
||||
call RunDbgCmd(buf, 'step', ['line 14: File2Function()'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '\V>backtrace',
|
||||
\ '\V 3 function GlobalFunction[1]',
|
||||
\ '\V 2 <SNR>\.\*_CallAFunction[1]',
|
||||
\ '\V 1 <SNR>\.\*_SourceAnotherFile[1]',
|
||||
\ '\V->0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ '\Vline 14: File2Function()'],
|
||||
\ #{match: 'pattern'})
|
||||
|
||||
" Don't step into compiled functions...
|
||||
call RunDbgCmd(buf, 'step', ['line 15: End of sourced file'])
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '\V>backtrace',
|
||||
\ '\V 3 function GlobalFunction[1]',
|
||||
\ '\V 2 <SNR>\.\*_CallAFunction[1]',
|
||||
\ '\V 1 <SNR>\.\*_SourceAnotherFile[1]',
|
||||
\ '\V->0 script ' .. getcwd() .. '/Xtest2.vim',
|
||||
\ '\Vline 15: End of sourced file'],
|
||||
\ #{match: 'pattern'})
|
||||
|
||||
|
||||
call StopVimInTerminal(buf)
|
||||
call delete('Xtest1.vim')
|
||||
call delete('Xtest2.vim')
|
||||
endfunc
|
||||
|
||||
func Test_debug_backtrace_level()
|
||||
CheckRunVimInTerminal
|
||||
CheckCWD
|
||||
let lines =<< trim END
|
||||
let s:file1_var = 'file1'
|
||||
let g:global_var = 'global'
|
||||
|
||||
func s:File1Func( arg )
|
||||
let s:file1_var .= a:arg
|
||||
let local_var = s:file1_var .. ' test1'
|
||||
let g:global_var .= local_var
|
||||
source Xtest2.vim
|
||||
endfunc
|
||||
|
||||
call s:File1Func( 'arg1' )
|
||||
END
|
||||
call writefile(lines, 'Xtest1.vim')
|
||||
|
||||
let lines =<< trim END
|
||||
let s:file2_var = 'file2'
|
||||
|
||||
func s:File2Func( arg )
|
||||
let s:file2_var .= a:arg
|
||||
let local_var = s:file2_var .. ' test2'
|
||||
let g:global_var .= local_var
|
||||
endfunc
|
||||
|
||||
call s:File2Func( 'arg2' )
|
||||
END
|
||||
call writefile(lines, 'Xtest2.vim')
|
||||
|
||||
let file1 = getcwd() .. '/Xtest1.vim'
|
||||
let file2 = getcwd() .. '/Xtest2.vim'
|
||||
|
||||
" set a breakpoint and source file1.vim
|
||||
let buf = RunVimInTerminal(
|
||||
\ '-c "breakadd file 1 Xtest1.vim" -S Xtest1.vim',
|
||||
\ #{ wait_for_ruler: 0 } )
|
||||
|
||||
call CheckDbgOutput(buf, [
|
||||
\ 'Breakpoint in "' .. file1 .. '" line 1',
|
||||
\ 'Entering Debug mode. Type "cont" to continue.',
|
||||
\ 'command line..script ' .. file1,
|
||||
\ 'line 1: let s:file1_var = ''file1'''
|
||||
\ ])
|
||||
|
||||
" step throught the initial declarations
|
||||
call RunDbgCmd(buf, 'step', [ 'line 2: let g:global_var = ''global''' ] )
|
||||
call RunDbgCmd(buf, 'step', [ 'line 4: func s:File1Func( arg )' ] )
|
||||
call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
|
||||
call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
|
||||
call RunDbgCmd(buf, 'echo global_var', [ 'global' ] )
|
||||
|
||||
" step in to the first function
|
||||
call RunDbgCmd(buf, 'step', [ 'line 11: call s:File1Func( ''arg1'' )' ] )
|
||||
call RunDbgCmd(buf, 'step', [ 'line 1: let s:file1_var .= a:arg' ] )
|
||||
call RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] )
|
||||
call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
|
||||
call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
|
||||
call RunDbgCmd(buf,
|
||||
\'echo global_var',
|
||||
\[ 'E121: Undefined variable: global_var' ] )
|
||||
call RunDbgCmd(buf,
|
||||
\'echo local_var',
|
||||
\[ 'E121: Undefined variable: local_var' ] )
|
||||
call RunDbgCmd(buf,
|
||||
\'echo l:local_var',
|
||||
\[ 'E121: Undefined variable: l:local_var' ] )
|
||||
|
||||
" backtrace up
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '\V>backtrace',
|
||||
\ '\V 2 command line',
|
||||
\ '\V 1 script ' .. file1 .. '[11]',
|
||||
\ '\V->0 function <SNR>\.\*_File1Func',
|
||||
\ '\Vline 1: let s:file1_var .= a:arg',
|
||||
\ ],
|
||||
\ #{ match: 'pattern' } )
|
||||
call RunDbgCmd(buf, 'up', [ '>up' ] )
|
||||
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '\V>backtrace',
|
||||
\ '\V 2 command line',
|
||||
\ '\V->1 script ' .. file1 .. '[11]',
|
||||
\ '\V 0 function <SNR>\.\*_File1Func',
|
||||
\ '\Vline 1: let s:file1_var .= a:arg',
|
||||
\ ],
|
||||
\ #{ match: 'pattern' } )
|
||||
|
||||
" Expression evaluation in the script frame (not the function frame)
|
||||
" FIXME: Unexpected in this scope (a: should not be visibnle)
|
||||
call RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] )
|
||||
call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
|
||||
call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
|
||||
" FIXME: Unexpected in this scope (global should be found)
|
||||
call RunDbgCmd(buf,
|
||||
\'echo global_var',
|
||||
\[ 'E121: Undefined variable: global_var' ] )
|
||||
call RunDbgCmd(buf,
|
||||
\'echo local_var',
|
||||
\[ 'E121: Undefined variable: local_var' ] )
|
||||
call RunDbgCmd(buf,
|
||||
\'echo l:local_var',
|
||||
\[ 'E121: Undefined variable: l:local_var' ] )
|
||||
|
||||
|
||||
" step while backtraced jumps to the latest frame
|
||||
call RunDbgCmd(buf, 'step', [
|
||||
\ 'line 2: let local_var = s:file1_var .. '' test1''' ] )
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '\V>backtrace',
|
||||
\ '\V 2 command line',
|
||||
\ '\V 1 script ' .. file1 .. '[11]',
|
||||
\ '\V->0 function <SNR>\.\*_File1Func',
|
||||
\ '\Vline 2: let local_var = s:file1_var .. '' test1''',
|
||||
\ ],
|
||||
\ #{ match: 'pattern' } )
|
||||
|
||||
call RunDbgCmd(buf, 'step', [ 'line 3: let g:global_var .= local_var' ] )
|
||||
call RunDbgCmd(buf, 'echo local_var', [ 'file1arg1 test1' ] )
|
||||
call RunDbgCmd(buf, 'echo l:local_var', [ 'file1arg1 test1' ] )
|
||||
|
||||
call RunDbgCmd(buf, 'step', [ 'line 4: source Xtest2.vim' ] )
|
||||
call RunDbgCmd(buf, 'step', [ 'line 1: let s:file2_var = ''file2''' ] )
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '\V>backtrace',
|
||||
\ '\V 3 command line',
|
||||
\ '\V 2 script ' .. file1 .. '[11]',
|
||||
\ '\V 1 function <SNR>\.\*_File1Func[4]',
|
||||
\ '\V->0 script ' .. file2,
|
||||
\ '\Vline 1: let s:file2_var = ''file2''',
|
||||
\ ],
|
||||
\ #{ match: 'pattern' } )
|
||||
|
||||
" Expression evaluation in the script frame file2 (not the function frame)
|
||||
call RunDbgCmd(buf, 'echo a:arg', [ 'E121: Undefined variable: a:arg' ] )
|
||||
call RunDbgCmd(buf,
|
||||
\ 'echo s:file1_var',
|
||||
\ [ 'E121: Undefined variable: s:file1_var' ] )
|
||||
call RunDbgCmd(buf, 'echo g:global_var', [ 'globalfile1arg1 test1' ] )
|
||||
call RunDbgCmd(buf, 'echo global_var', [ 'globalfile1arg1 test1' ] )
|
||||
call RunDbgCmd(buf,
|
||||
\'echo local_var',
|
||||
\[ 'E121: Undefined variable: local_var' ] )
|
||||
call RunDbgCmd(buf,
|
||||
\'echo l:local_var',
|
||||
\[ 'E121: Undefined variable: l:local_var' ] )
|
||||
call RunDbgCmd(buf,
|
||||
\ 'echo s:file2_var',
|
||||
\ [ 'E121: Undefined variable: s:file2_var' ] )
|
||||
|
||||
call RunDbgCmd(buf, 'step', [ 'line 3: func s:File2Func( arg )' ] )
|
||||
call RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] )
|
||||
|
||||
" Up the stack to the other script context
|
||||
call RunDbgCmd(buf, 'up')
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '\V>backtrace',
|
||||
\ '\V 3 command line',
|
||||
\ '\V 2 script ' .. file1 .. '[11]',
|
||||
\ '\V->1 function <SNR>\.\*_File1Func[4]',
|
||||
\ '\V 0 script ' .. file2,
|
||||
\ '\Vline 3: func s:File2Func( arg )',
|
||||
\ ],
|
||||
\ #{ match: 'pattern' } )
|
||||
" FIXME: Unexpected. Should see the a: and l: dicts from File1Func
|
||||
call RunDbgCmd(buf, 'echo a:arg', [ 'E121: Undefined variable: a:arg' ] )
|
||||
call RunDbgCmd(buf,
|
||||
\ 'echo l:local_var',
|
||||
\ [ 'E121: Undefined variable: l:local_var' ] )
|
||||
|
||||
call RunDbgCmd(buf, 'up')
|
||||
call RunDbgCmd(buf, 'backtrace', [
|
||||
\ '\V>backtrace',
|
||||
\ '\V 3 command line',
|
||||
\ '\V->2 script ' .. file1 .. '[11]',
|
||||
\ '\V 1 function <SNR>\.\*_File1Func[4]',
|
||||
\ '\V 0 script ' .. file2,
|
||||
\ '\Vline 3: func s:File2Func( arg )',
|
||||
\ ],
|
||||
\ #{ match: 'pattern' } )
|
||||
|
||||
" FIXME: Unexpected (wrong script vars are used)
|
||||
call RunDbgCmd(buf,
|
||||
\ 'echo s:file1_var',
|
||||
\ [ 'E121: Undefined variable: s:file1_var' ] )
|
||||
call RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] )
|
||||
|
||||
call StopVimInTerminal(buf)
|
||||
call delete('Xtest1.vim')
|
||||
call delete('Xtest2.vim')
|
||||
endfunc
|
||||
|
||||
" Test for setting a breakpoint on a :endif where the :if condition is false
|
||||
" and then quit the script. This should generate an interrupt.
|
||||
func Test_breakpt_endif_intr()
|
||||
|
@ -1,5 +1,8 @@
|
||||
" Tests for the writefile() function and some :write commands.
|
||||
|
||||
source check.vim
|
||||
source term_util.vim
|
||||
|
||||
func Test_writefile()
|
||||
let f = tempname()
|
||||
call writefile(["over","written"], f, "b")
|
||||
@ -179,3 +182,120 @@ func Test_writefile_sync_arg()
|
||||
call writefile(['two'], 'Xtest', 'S')
|
||||
call delete('Xtest')
|
||||
endfunc
|
||||
|
||||
" Tests for reading and writing files with conversion for Win32.
|
||||
func Test_write_file_encoding()
|
||||
CheckMSWindows
|
||||
throw 'skipped: Nvim does not support :w ++enc=cp1251'
|
||||
let save_encoding = &encoding
|
||||
let save_fileencodings = &fileencodings
|
||||
set encoding& fileencodings&
|
||||
let text =<< trim END
|
||||
1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01
|
||||
2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
|
||||
3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01
|
||||
END
|
||||
call writefile(text, 'Xfile')
|
||||
edit Xfile
|
||||
|
||||
" write tests:
|
||||
" combine three values for 'encoding' with three values for 'fileencoding'
|
||||
" also write files for read tests
|
||||
call cursor(1, 1)
|
||||
set encoding=utf-8
|
||||
.w! ++enc=utf-8 Xtest
|
||||
.w ++enc=cp1251 >> Xtest
|
||||
.w ++enc=cp866 >> Xtest
|
||||
.w! ++enc=utf-8 Xutf8
|
||||
let expected =<< trim END
|
||||
1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01
|
||||
1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
|
||||
1 utf-8 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01
|
||||
END
|
||||
call assert_equal(expected, readfile('Xtest'))
|
||||
|
||||
call cursor(2, 1)
|
||||
set encoding=cp1251
|
||||
.w! ++enc=utf-8 Xtest
|
||||
.w ++enc=cp1251 >> Xtest
|
||||
.w ++enc=cp866 >> Xtest
|
||||
.w! ++enc=cp1251 Xcp1251
|
||||
let expected =<< trim END
|
||||
2 cp1251 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01
|
||||
2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
|
||||
2 cp1251 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01
|
||||
END
|
||||
call assert_equal(expected, readfile('Xtest'))
|
||||
|
||||
call cursor(3, 1)
|
||||
set encoding=cp866
|
||||
.w! ++enc=utf-8 Xtest
|
||||
.w ++enc=cp1251 >> Xtest
|
||||
.w ++enc=cp866 >> Xtest
|
||||
.w! ++enc=cp866 Xcp866
|
||||
let expected =<< trim END
|
||||
3 cp866 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01
|
||||
3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
|
||||
3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01
|
||||
END
|
||||
call assert_equal(expected, readfile('Xtest'))
|
||||
|
||||
" read three 'fileencoding's with utf-8 'encoding'
|
||||
set encoding=utf-8 fencs=utf-8,cp1251
|
||||
e Xutf8
|
||||
.w! ++enc=utf-8 Xtest
|
||||
e Xcp1251
|
||||
.w ++enc=utf-8 >> Xtest
|
||||
set fencs=utf-8,cp866
|
||||
e Xcp866
|
||||
.w ++enc=utf-8 >> Xtest
|
||||
let expected =<< trim END
|
||||
1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01
|
||||
2 cp1251 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01
|
||||
3 cp866 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01
|
||||
END
|
||||
call assert_equal(expected, readfile('Xtest'))
|
||||
|
||||
" read three 'fileencoding's with cp1251 'encoding'
|
||||
set encoding=utf-8 fencs=utf-8,cp1251
|
||||
e Xutf8
|
||||
.w! ++enc=cp1251 Xtest
|
||||
e Xcp1251
|
||||
.w ++enc=cp1251 >> Xtest
|
||||
set fencs=utf-8,cp866
|
||||
e Xcp866
|
||||
.w ++enc=cp1251 >> Xtest
|
||||
let expected =<< trim END
|
||||
1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
|
||||
2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
|
||||
3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
|
||||
END
|
||||
call assert_equal(expected, readfile('Xtest'))
|
||||
|
||||
" read three 'fileencoding's with cp866 'encoding'
|
||||
set encoding=cp866 fencs=utf-8,cp1251
|
||||
e Xutf8
|
||||
.w! ++enc=cp866 Xtest
|
||||
e Xcp1251
|
||||
.w ++enc=cp866 >> Xtest
|
||||
set fencs=utf-8,cp866
|
||||
e Xcp866
|
||||
.w ++enc=cp866 >> Xtest
|
||||
let expected =<< trim END
|
||||
1 utf-8 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01
|
||||
2 cp1251 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01
|
||||
3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01
|
||||
END
|
||||
call assert_equal(expected, readfile('Xtest'))
|
||||
|
||||
call delete('Xfile')
|
||||
call delete('Xtest')
|
||||
call delete('Xutf8')
|
||||
call delete('Xcp1251')
|
||||
call delete('Xcp866')
|
||||
let &encoding = save_encoding
|
||||
let &fileencodings = save_fileencodings
|
||||
%bw!
|
||||
endfunc
|
||||
|
||||
" vim: shiftwidth=2 sts=2 expandtab
|
||||
|
Loading…
Reference in New Issue
Block a user