aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2016-05-18 00:08:47 -0400
committerJustin M. Keyes <justinkz@gmail.com>2016-05-18 00:08:47 -0400
commita69c3fbc8abe492955d9e92e39e0d0dc421b1ec4 (patch)
tree5bf2389beccb715d6768767ce36e8df1dba2a22d
parentc9b1ad3a578ccc42c1b5a6ff58166590c349eed6 (diff)
parent91796f70edf7d6f89742c114d1825dae65eca7e1 (diff)
downloadrneovim-a69c3fbc8abe492955d9e92e39e0d0dc421b1ec4.tar.gz
rneovim-a69c3fbc8abe492955d9e92e39e0d0dc421b1ec4.tar.bz2
rneovim-a69c3fbc8abe492955d9e92e39e0d0dc421b1ec4.zip
Merge pull request #4734 from jbradaric/vim-7.4.1102
vim-patch:7.4.1102, 7.4.1110, 7.4.1832
-rw-r--r--runtime/doc/repeat.txt32
-rw-r--r--src/nvim/eval.c29
-rw-r--r--src/nvim/ex_cmds2.c135
-rw-r--r--src/nvim/globals.h9
-rw-r--r--src/nvim/version.c5
-rw-r--r--test/functional/legacy/108_backtrace_debug_commands_spec.lua177
6 files changed, 371 insertions, 16 deletions
diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt
index b2e935eb3f..343d3e62cf 100644
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -1,4 +1,4 @@
-*repeat.txt* For Vim version 7.4. Last change: 2015 Apr 13
+*repeat.txt* For Vim version 7.4. Last change: 2016 Jan 16
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -467,16 +467,44 @@ Additionally, these commands can be used:
finish Finish the current script or user function and come
back to debug mode for the command after the one that
sourced or called it.
+ *>bt*
+ *>backtrace*
+ *>where*
+ backtrace Show the call stacktrace for current debugging session.
+ bt
+ where
+ *>frame*
+ frame N Goes to N backtrace level. + and - signs make movement
+ relative. E.g., ":frame +3" goes three frames up.
+ *>up*
+ up Goes one level up from call stacktrace.
+ *>down*
+ down Goes one level down from call stacktrace.
About the additional commands in debug mode:
- There is no command-line completion for them, you get the completion for the
normal Ex commands only.
-- You can shorten them, up to a single character: "c", "n", "s" and "f".
+- You can shorten them, up to a single character, unless more then one command
+ starts with the same letter. "f" stands for "finish", use "fr" for "frame".
- Hitting <CR> will repeat the previous one. When doing another command, this
is reset (because it's not clear what you want to repeat).
- When you want to use the Ex command with the same name, prepend a colon:
":cont", ":next", ":finish" (or shorter).
+The backtrace shows the hierarchy of function calls, e.g.:
+ >bt ~
+ 3 function One[3] ~
+ 2 Two[3] ~
+ ->1 Three[3] ~
+ 0 Four ~
+ line 1: let four = 4 ~
+
+The "->" points to the current frame. Use "up", "down" and "frame N" to
+select another frame.
+
+In the current frame you can evaluate the local function variables. There is
+no way to see the command at the current line yet.
+
DEFINING BREAKPOINTS
*:breaka* *:breakadd*
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 712ee06b85..201a71facb 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -18123,6 +18123,25 @@ static dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, char_u *varname, in
return HI2DI(hi);
}
+// Get function call environment based on backtrace debug level
+static funccall_T *get_funccal(void)
+{
+ funccall_T *funccal = current_funccal;
+ if (debug_backtrace_level > 0) {
+ for (int i = 0; i < debug_backtrace_level; i++) {
+ funccall_T *temp_funccal = funccal->caller;
+ if (temp_funccal) {
+ funccal = temp_funccal;
+ } else {
+ // backtrace level overflow. reset to max
+ debug_backtrace_level = i;
+ }
+ }
+ }
+
+ return funccal;
+}
+
// Find the dict and hashtable used for a variable name. Set "varname" to the
// start of name without ':'.
static hashtab_T *find_var_ht_dict(char_u *name, uint8_t **varname, dict_T **d)
@@ -18147,7 +18166,11 @@ static hashtab_T *find_var_ht_dict(char_u *name, uint8_t **varname, dict_T **d)
return &compat_hashtab;
}
- *d = current_funccal ? &current_funccal->l_vars : &globvardict;
+ if (current_funccal == NULL) {
+ *d = &globvardict;
+ } else {
+ *d = &get_funccal()->l_vars; // l: variable
+ }
goto end;
}
@@ -18169,9 +18192,9 @@ static hashtab_T *find_var_ht_dict(char_u *name, uint8_t **varname, dict_T **d)
} else if (*name == 'v') { // v: variable
*d = &vimvardict;
} else if (*name == 'a' && current_funccal != NULL) { // function argument
- *d = &current_funccal->l_avars;
+ *d = &get_funccal()->l_avars;
} else if (*name == 'l' && current_funccal != NULL) { // local variable
- *d = &current_funccal->l_vars;
+ *d = &get_funccal()->l_vars;
} else if (*name == 's' // script variable
&& current_SID > 0 && current_SID <= ga_scripts.ga_len) {
*d = &SCRIPT_SV(current_SID)->sv_dict;
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index df387f9a60..5fe6209a0a 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -144,6 +144,10 @@ void do_debug(char_u *cmd)
#define CMD_FINISH 4
#define CMD_QUIT 5
#define CMD_INTERRUPT 6
+#define CMD_BACKTRACE 7
+#define CMD_FRAME 8
+#define CMD_UP 9
+#define CMD_DOWN 10
++RedrawingDisabled; /* don't redisplay the window */
@@ -185,6 +189,7 @@ void do_debug(char_u *cmd)
ignore_script = TRUE;
}
+ xfree(cmdline);
cmdline = getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL);
if (typeahead_saved) {
@@ -194,6 +199,7 @@ void do_debug(char_u *cmd)
ex_normal_busy = save_ex_normal_busy;
cmdline_row = msg_row;
+ msg_starthere();
if (cmdline != NULL) {
/* If this is a debug command, set "last_cmd".
* If not, reset "last_cmd".
@@ -210,8 +216,15 @@ void do_debug(char_u *cmd)
case 's': last_cmd = CMD_STEP;
tail = "tep";
break;
- case 'f': last_cmd = CMD_FINISH;
- tail = "inish";
+ case 'f':
+ last_cmd = 0;
+ if (p[1] == 'r') {
+ last_cmd = CMD_FRAME;
+ tail = "rame";
+ } else {
+ last_cmd = CMD_FINISH;
+ tail = "inish";
+ }
break;
case 'q': last_cmd = CMD_QUIT;
tail = "uit";
@@ -219,6 +232,26 @@ void do_debug(char_u *cmd)
case 'i': last_cmd = CMD_INTERRUPT;
tail = "nterrupt";
break;
+ case 'b':
+ last_cmd = CMD_BACKTRACE;
+ if (p[1] == 't') {
+ tail = "t";
+ } else {
+ tail = "acktrace";
+ }
+ break;
+ case 'w':
+ last_cmd = CMD_BACKTRACE;
+ tail = "here";
+ break;
+ case 'u':
+ last_cmd = CMD_UP;
+ tail = "p";
+ break;
+ case 'd':
+ last_cmd = CMD_DOWN;
+ tail = "own";
+ break;
default: last_cmd = 0;
}
if (last_cmd != 0) {
@@ -228,8 +261,9 @@ void do_debug(char_u *cmd)
++p;
++tail;
}
- if (ASCII_ISALPHA(*p))
+ if (ASCII_ISALPHA(*p) && last_cmd != CMD_FRAME) {
last_cmd = 0;
+ }
}
}
@@ -259,7 +293,28 @@ void do_debug(char_u *cmd)
/* Do not repeat ">interrupt" cmd, continue stepping. */
last_cmd = CMD_STEP;
break;
+ case CMD_BACKTRACE:
+ do_showbacktrace(cmd);
+ continue;
+ case CMD_FRAME:
+ if (*p == NUL) {
+ do_showbacktrace(cmd);
+ } else {
+ p = skipwhite(p);
+ do_setdebugtracelevel(p);
+ }
+ continue;
+ case CMD_UP:
+ debug_backtrace_level++;
+ do_checkbacktracelevel();
+ continue;
+ case CMD_DOWN:
+ debug_backtrace_level--;
+ do_checkbacktracelevel();
+ continue;
}
+ // Going out reset backtrace_level
+ debug_backtrace_level = 0;
break;
}
@@ -269,8 +324,6 @@ void do_debug(char_u *cmd)
(void)do_cmdline(cmdline, getexline, NULL,
DOCMD_VERBOSE|DOCMD_EXCRESET);
debug_break_level = n;
-
- xfree(cmdline);
}
lines_left = (int)(Rows - 1);
}
@@ -294,6 +347,78 @@ void do_debug(char_u *cmd)
debug_did_msg = TRUE;
}
+static int get_maxbacktrace_level(void)
+{
+ int maxbacktrace = 0;
+
+ if (sourcing_name != NULL) {
+ char *p = (char *)sourcing_name;
+ char *q;
+ while ((q = strstr(p, "..")) != NULL) {
+ p = q + 2;
+ maxbacktrace++;
+ }
+ }
+ return maxbacktrace;
+}
+
+static void do_setdebugtracelevel(char_u *arg)
+{
+ int level = atoi((char *)arg);
+ if (*arg == '+' || level < 0) {
+ debug_backtrace_level += level;
+ } else {
+ debug_backtrace_level = level;
+ }
+
+ do_checkbacktracelevel();
+}
+
+static void do_checkbacktracelevel(void)
+{
+ if (debug_backtrace_level < 0) {
+ debug_backtrace_level = 0;
+ MSG(_("frame is zero"));
+ } else {
+ int max = get_maxbacktrace_level();
+ if (debug_backtrace_level > max) {
+ debug_backtrace_level = max;
+ smsg(_("frame at highest level: %d"), max);
+ }
+ }
+}
+
+static void do_showbacktrace(char_u *cmd)
+{
+ if (sourcing_name != NULL) {
+ int i = 0;
+ int max = get_maxbacktrace_level();
+ char *cur = (char *)sourcing_name;
+ while (!got_int) {
+ char *next = strstr(cur, "..");
+ if (next != NULL) {
+ *next = NUL;
+ }
+ if (i == max - debug_backtrace_level) {
+ smsg("->%d %s", max - i, cur);
+ } else {
+ smsg(" %d %s", max - i, cur);
+ }
+ i++;
+ if (next == NULL) {
+ break;
+ }
+ *next = '.';
+ cur = next + 2;
+ }
+ }
+ if (sourcing_lnum != 0) {
+ smsg(_("line %" PRId64 ": %s"), (int64_t)sourcing_lnum, cmd);
+ } else {
+ smsg(_("cmd: %s"), cmd);
+ }
+}
+
/*
* ":debug".
*/
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 49d1de21d9..30fe97c8db 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -293,10 +293,11 @@ EXTERN int msg_no_more INIT(= FALSE); /* don't use more prompt, truncate
EXTERN char_u *sourcing_name INIT( = NULL); /* name of error message source */
EXTERN linenr_T sourcing_lnum INIT(= 0); /* line number of the source file */
-EXTERN int ex_nesting_level INIT(= 0); /* nesting level */
-EXTERN int debug_break_level INIT(= -1); /* break below this level */
-EXTERN int debug_did_msg INIT(= FALSE); /* did "debug mode" message */
-EXTERN int debug_tick INIT(= 0); /* breakpoint change count */
+EXTERN int ex_nesting_level INIT(= 0); // nesting level
+EXTERN int debug_break_level INIT(= -1); // break below this level
+EXTERN int debug_did_msg INIT(= false); // did "debug mode" message
+EXTERN int debug_tick INIT(= 0); // breakpoint change count
+EXTERN int debug_backtrace_level INIT(= 0); // breakpoint backtrace level
/* Values for "do_profiling". */
#define PROF_NONE 0 /* profiling not started */
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 3746f53e1f..48db7d86ee 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -69,6 +69,7 @@ static char *features[] = {
// clang-format off
static int included_patches[] = {
+ 1832,
1809,
1808,
1806,
@@ -573,7 +574,7 @@ static int included_patches[] = {
1113,
1112,
// 1111,
- // 1110,
+ 1110,
// 1109 NA
// 1108,
1107,
@@ -581,7 +582,7 @@ static int included_patches[] = {
1105,
// 1104 NA
// 1103 NA
- // 1102,
+ 1102,
1101,
// 1100 NA
// 1099 NA
diff --git a/test/functional/legacy/108_backtrace_debug_commands_spec.lua b/test/functional/legacy/108_backtrace_debug_commands_spec.lua
new file mode 100644
index 0000000000..6df645d255
--- /dev/null
+++ b/test/functional/legacy/108_backtrace_debug_commands_spec.lua
@@ -0,0 +1,177 @@
+-- Tests for backtrace debug commands.
+
+local helpers = require('test.functional.helpers')
+local feed, clear = helpers.feed, helpers.clear
+local execute, expect = helpers.execute, helpers.expect
+
+describe('108', function()
+ before_each(clear)
+
+ it('is working', function()
+ execute('lang mess C')
+ execute('function! Foo()')
+ execute(' let var1 = 1')
+ execute(' let var2 = Bar(var1) + 9')
+ execute(' return var2')
+ execute('endfunction')
+ execute('function! Bar(var)')
+ execute(' let var1 = 2 + a:var')
+ execute(' let var2 = Bazz(var1) + 4')
+ execute(' return var2')
+ execute('endfunction')
+ execute('function! Bazz(var)')
+ execute(' let var1 = 3 + a:var')
+ execute(' let var3 = "another var"')
+ execute(' return var1')
+ execute('endfunction')
+ execute('new')
+ execute('debuggreedy')
+ execute('redir => out')
+ execute('debug echo Foo()')
+ feed('step<cr>')
+ feed('step<cr>')
+ feed('step<cr>')
+ feed('step<cr>')
+ feed('step<cr>')
+ feed('step<cr>')
+ feed([[echo "- show backtrace:\n"<cr>]])
+ feed('backtrace<cr>')
+ feed([[echo "\nshow variables on different levels:\n"<cr>]])
+ feed('echo var1<cr>')
+ feed('up<cr>')
+ feed('back<cr>')
+ feed('echo var1<cr>')
+ feed('u<cr>')
+ feed('bt<cr>')
+ feed('echo var1<cr>')
+ feed([[echo "\n- undefined vars:\n"<cr>]])
+ feed('step<cr>')
+ feed('frame 2<cr>')
+ feed('echo "undefined var3 on former level:"<cr>')
+ feed('echo var3<cr>')
+ feed('fr 0<cr>')
+ feed([[echo "here var3 is defined with \"another var\":"<cr>]])
+ feed('echo var3<cr>')
+ feed('step<cr>')
+ feed('step<cr>')
+ feed('step<cr>')
+ feed('up<cr>')
+ feed([[echo "\nundefined var2 on former level"<cr>]])
+ feed('echo var2<cr>')
+ feed('down<cr>')
+ feed('echo "here var2 is defined with 10:"<cr>')
+ feed('echo var2<cr>')
+ feed([[echo "\n- backtrace movements:\n"<cr>]])
+ feed('b<cr>')
+ feed([[echo "\nnext command cannot go down, we are on bottom\n"<cr>]])
+ feed('down<cr>')
+ feed('up<cr>')
+ feed([[echo "\nnext command cannot go up, we are on top\n"<cr>]])
+ feed('up<cr>')
+ feed('b<cr>')
+ feed('echo "fil is not frame or finish, it is file"<cr>')
+ feed('fil<cr>')
+ feed([[echo "\n- relative backtrace movement\n"<cr>]])
+ feed('fr -1<cr>')
+ feed('frame<cr>')
+ feed('fra +1<cr>')
+ feed('fram<cr>')
+ feed([[echo "\n- go beyond limits does not crash\n"<cr>]])
+ feed('fr 100<cr>')
+ feed('fra<cr>')
+ feed('frame -40<cr>')
+ feed('fram<cr>')
+ feed([[echo "\n- final result 19:"<cr>]])
+ feed('cont<cr>')
+ execute('0debuggreedy')
+ execute('redir END')
+ execute('$put =out')
+
+ -- Assert buffer contents.
+ expect([=[
+
+
+
+ - show backtrace:
+
+ 2 function Foo[2]
+ 1 Bar[2]
+ ->0 Bazz
+ line 2: let var3 = "another var"
+
+ show variables on different levels:
+
+ 6
+ 2 function Foo[2]
+ ->1 Bar[2]
+ 0 Bazz
+ line 2: let var3 = "another var"
+ 3
+ ->2 function Foo[2]
+ 1 Bar[2]
+ 0 Bazz
+ line 2: let var3 = "another var"
+ 1
+
+ - undefined vars:
+
+ undefined var3 on former level:
+ Error detected while processing function Foo[2]..Bar[2]..Bazz:
+ line 3:
+ E121: Undefined variable: var3
+ E15: Invalid expression: var3
+ here var3 is defined with "another var":
+ another var
+
+ undefined var2 on former level
+ Error detected while processing function Foo[2]..Bar:
+ line 3:
+ E121: Undefined variable: var2
+ E15: Invalid expression: var2
+ here var2 is defined with 10:
+ 10
+
+ - backtrace movements:
+
+ 1 function Foo[2]
+ ->0 Bar
+ line 3: End of function
+
+ next command cannot go down, we are on bottom
+
+ frame is zero
+
+ next command cannot go up, we are on top
+
+ frame at highest level: 1
+ ->1 function Foo[2]
+ 0 Bar
+ line 3: End of function
+ fil is not frame or finish, it is file
+ "[No Name]" --No lines in buffer--
+
+ - relative backtrace movement
+
+ 1 function Foo[2]
+ ->0 Bar
+ line 3: End of function
+ ->1 function Foo[2]
+ 0 Bar
+ line 3: End of function
+
+ - go beyond limits does not crash
+
+ frame at highest level: 1
+ ->1 function Foo[2]
+ 0 Bar
+ line 3: End of function
+ frame is zero
+ 1 function Foo[2]
+ ->0 Bar
+ line 3: End of function
+
+ - final result 19:
+ 19
+ ]=])
+ end)
+end)