Browse Source

Merge pull request #14314 from janlazo/vim-8.2.2720

vim-patch:8.0.{1505,1511},8.2.{1304,1305,1393,1508,1791,2720}
pull/11523/merge
Jan Edmund Lazo 7 days ago
committed by GitHub
parent
commit
ed3c0a27c7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1276 additions and 244 deletions
  1. +13
    -0
      runtime/doc/repeat.txt
  2. +216
    -187
      src/nvim/eval.c
  3. +13
    -0
      src/nvim/eval.h
  4. +1
    -24
      src/nvim/eval/funcs.c
  5. +98
    -24
      src/nvim/ex_cmds2.c
  6. +5
    -0
      src/nvim/testdir/runtest.vim
  7. +810
    -9
      src/nvim/testdir/test_debugger.vim
  8. +120
    -0
      src/nvim/testdir/test_writefile.vim

+ 13
- 0
runtime/doc/repeat.txt View File

@ -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.


+ 216
- 187
src/nvim/eval.c View File

@ -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;
}

+ 13
- 0
src/nvim/eval.h View File

@ -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.


+ 1
- 24
src/nvim/eval/funcs.c View File

@ -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;


+ 98
- 24
src/nvim/ex_cmds2.c View File

@ -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,22 +652,28 @@ 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 (pat != NULL) {
bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
xfree(pat);
}
if (pat == NULL || bp->dbg_prog == NULL) {
xfree(bp->dbg_name);
} else {
if (bp->dbg_lnum == 0) { // default line number is 1
bp->dbg_lnum = 1;
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);
}
if (eap->cmdidx != CMD_profile) {
DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp;
debug_tick++;
if (pat == NULL || bp->dbg_prog == NULL) {
xfree(bp->dbg_name);
} else {
if (bp->dbg_lnum == 0) { // default line number is 1
bp->dbg_lnum = 1;
}
if (eap->cmdidx != CMD_profile) {
DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp;
debug_tick++;
}
gap->ga_len++;
}
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);
}
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);
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;
}
}


+ 5
- 0
src/nvim/testdir/runtest.vim View File

@ -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


+ 810
- 9
src/nvim/testdir/test_debugger.vim View File

@ -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' ] )