# HG changeset patch # User Bram Moolenaar # Date 1555839907 -7200 # Node ID 1dcbaa780b8e7211135a415f60e1736df7424d9f # Parent 5ad204d8a2f237ac5e6d4905b4270de514a3220a patch 8.1.1195: Vim script debugger functionality needs cleanup commit https://github.com/vim/vim/commit/eead75c5e8e1f965548c55ee3a9388b2cb3afc36 Author: Bram Moolenaar Date: Sun Apr 21 11:35:00 2019 +0200 patch 8.1.1195: Vim script debugger functionality needs cleanup Problem: Vim script debugger functionality needs cleanup. Solution: Move debugger code to a separate file. Add more tests. (Yegappan Lakshmanan, closes #4285) diff --git a/Filelist b/Filelist --- a/Filelist +++ b/Filelist @@ -23,6 +23,7 @@ SRC_ALL = \ src/charset.c \ src/crypt.c \ src/crypt_zip.c \ + src/debugger.c \ src/dict.c \ src/diff.c \ src/digraph.c \ @@ -156,6 +157,7 @@ SRC_ALL = \ src/proto/charset.pro \ src/proto/crypt.pro \ src/proto/crypt_zip.pro \ + src/proto/debugger.pro \ src/proto/dict.pro \ src/proto/diff.pro \ src/proto/digraph.pro \ diff --git a/src/Make_bc5.mak b/src/Make_bc5.mak --- a/src/Make_bc5.mak +++ b/src/Make_bc5.mak @@ -516,6 +516,7 @@ vimobj = \ $(OBJDIR)\charset.obj \ $(OBJDIR)\crypt.obj \ $(OBJDIR)\crypt_zip.obj \ + $(OBJDIR)\debugger.obj \ $(OBJDIR)\dict.obj \ $(OBJDIR)\diff.obj \ $(OBJDIR)\digraph.obj \ diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak --- a/src/Make_cyg_ming.mak +++ b/src/Make_cyg_ming.mak @@ -703,6 +703,7 @@ OBJ = \ $(OUTDIR)/charset.o \ $(OUTDIR)/crypt.o \ $(OUTDIR)/crypt_zip.o \ + $(OUTDIR)/debugger.o \ $(OUTDIR)/dict.o \ $(OUTDIR)/diff.o \ $(OUTDIR)/digraph.o \ diff --git a/src/Make_dice.mak b/src/Make_dice.mak --- a/src/Make_dice.mak +++ b/src/Make_dice.mak @@ -33,6 +33,7 @@ SRC = \ charset.c \ crypt.c \ crypt_zip.c \ + debugger.c \ dict.c \ diff.c \ digraph.c \ @@ -93,6 +94,7 @@ OBJ = o/arabic.o \ o/charset.o \ o/crypt.o \ o/crypt_zip.o \ + o/debugger.o \ o/dict.o \ o/diff.o \ o/digraph.o \ @@ -179,6 +181,8 @@ o/crypt.o: crypt.c $(SYMS) o/crypt_zip.o: crypt_zip.c $(SYMS) +o/debugger.o: debugger.c $(SYMS) + o/dict.o: dict.c $(SYMS) o/diff.o: diff.c $(SYMS) diff --git a/src/Make_ivc.mak b/src/Make_ivc.mak --- a/src/Make_ivc.mak +++ b/src/Make_ivc.mak @@ -217,6 +217,7 @@ LINK32_OBJS= \ "$(INTDIR)/charset.obj" \ "$(INTDIR)/crypt.obj" \ "$(INTDIR)/crypt_zip.obj" \ + "$(INTDIR)/debugger.obj" \ "$(INTDIR)/dict.obj" \ "$(INTDIR)/diff.obj" \ "$(INTDIR)/digraph.obj" \ @@ -369,6 +370,10 @@ SOURCE=.\crypt_zip.c # End Source File # Begin Source File +SOURCE=.\debugger.c +# End Source File +# Begin Source File + SOURCE=.\dict.c # End Source File # Begin Source File diff --git a/src/Make_manx.mak b/src/Make_manx.mak --- a/src/Make_manx.mak +++ b/src/Make_manx.mak @@ -43,6 +43,7 @@ SRC = arabic.c \ charset.c \ crypt.c \ crypt_zip.c \ + debugger.c \ dict.c \ diff.c \ digraph.c \ @@ -105,6 +106,7 @@ OBJ = obj/arabic.o \ obj/charset.o \ obj/crypt.o \ obj/crypt_zip.o \ + obj/debugger.o \ obj/dict.o \ obj/diff.o \ obj/digraph.o \ @@ -165,6 +167,7 @@ PRO = proto/arabic.pro \ proto/charset.pro \ proto/crypt.pro \ proto/crypt_zip.pro \ + proto/debugger.pro \ proto/dict.pro \ proto/diff.pro \ proto/digraph.pro \ @@ -283,6 +286,9 @@ obj/crypt.o: crypt.c obj/crypt_zip.o: crypt_zip.c $(CCSYM) $@ crypt_zip.c +obj/debugger.o: debugger.c + $(CCSYM) $@ debugger.c + obj/dict.o: dict.c $(CCSYM) $@ dict.c diff --git a/src/Make_morph.mak b/src/Make_morph.mak --- a/src/Make_morph.mak +++ b/src/Make_morph.mak @@ -31,6 +31,7 @@ SRC = arabic.c \ charset.c \ crypt.c \ crypt_zip.c \ + debugger.c \ dict.c \ diff.c \ digraph.c \ diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -711,6 +711,7 @@ OBJ = \ $(OUTDIR)\charset.obj \ $(OUTDIR)\crypt.obj \ $(OUTDIR)\crypt_zip.obj \ + $(OUTDIR)\debugger.obj \ $(OUTDIR)\dict.obj \ $(OUTDIR)\diff.obj \ $(OUTDIR)\digraph.obj \ @@ -1367,6 +1368,8 @@ testclean: $(OUTDIR)/crypt_zip.obj: $(OUTDIR) crypt_zip.c $(INCL) +$(OUTDIR)/debugger.obj: $(OUTDIR) debugger.c $(INCL) + $(OUTDIR)/dict.obj: $(OUTDIR) dict.c $(INCL) $(OUTDIR)/diff.obj: $(OUTDIR) diff.c $(INCL) @@ -1638,6 +1641,7 @@ proto.h: \ proto/charset.pro \ proto/crypt.pro \ proto/crypt_zip.pro \ + proto/debugger.pro \ proto/dict.pro \ proto/diff.pro \ proto/digraph.pro \ diff --git a/src/Make_sas.mak b/src/Make_sas.mak --- a/src/Make_sas.mak +++ b/src/Make_sas.mak @@ -96,6 +96,7 @@ SRC = \ charset.c \ crypt.c \ crypt_zip.c \ + debugger.c \ dict.c \ diff.c \ digraph.c \ @@ -157,6 +158,7 @@ OBJ = \ charset.o \ crypt.o \ crypt_zip.o \ + debugger.o \ dict.o \ diff.o \ digraph.o \ @@ -218,6 +220,7 @@ PRO = \ proto/charset.pro \ proto/crypt.pro \ proto/crypt_zip.pro \ + proto/debugger.pro \ proto/dict.pro \ proto/diff.pro \ proto/digraph.pro \ @@ -340,6 +343,8 @@ crypt.o: crypt.c proto/crypt.pro: crypt.c crypt_zip.o: crypt_zip.c proto/crypt_zip.pro: crypt_zip.c +debugger.o: debugger.c +proto/debugger.pro: debugger.c dict.o: dict.c proto/dict.pro: dict.c diff.o: diff.c diff --git a/src/Make_vms.mms b/src/Make_vms.mms --- a/src/Make_vms.mms +++ b/src/Make_vms.mms @@ -308,30 +308,30 @@ ALL_LIBS = $(LIBS) $(GUI_LIB_DIR) $(GUI_ $(PERL_LIB) $(PYTHON_LIB) $(TCL_LIB) $(RUBY_LIB) SRC = arabic.c autocmd.c beval.c blob.c blowfish.c buffer.c charset.c \ - crypt.c crypt_zip.c dict.c diff.c digraph.c edit.c eval.c evalfunc.c \ - ex_cmds.c ex_cmds2.c ex_docmd.c ex_eval.c ex_getln.c if_cscope.c \ - if_xcmdsrv.c fileio.c findfile.c fold.c getchar.c hardcopy.c \ - hashtab.c indent.c insexpand.c json.c list.c main.c mark.c menu.c \ - mbyte.c memfile.c memline.c message.c misc1.c misc2.c move.c normal.c \ - ops.c option.c popupmnu.c quickfix.c regexp.c search.c sha256.c sign.c \ - spell.c spellfile.c syntax.c tag.c term.c termlib.c textprop.c ui.c \ - undo.c userfunc.c version.c screen.c window.c os_unix.c os_vms.c \ - pathdef.c + crypt.c crypt_zip.c debugger.c dict.c diff.c digraph.c edit.c eval.c \ + evalfunc.c ex_cmds.c ex_cmds2.c ex_docmd.c ex_eval.c ex_getln.c \ + if_cscope.c if_xcmdsrv.c fileio.c findfile.c fold.c getchar.c \ + hardcopy.c hashtab.c indent.c insexpand.c json.c list.c main.c mark.c \ + menu.c mbyte.c memfile.c memline.c message.c misc1.c misc2.c move.c \ + normal.c ops.c option.c popupmnu.c quickfix.c regexp.c search.c \ + sha256.c sign.c spell.c spellfile.c syntax.c tag.c term.c termlib.c \ + textprop.c ui.c undo.c userfunc.c version.c screen.c window.c \ + os_unix.c os_vms.c pathdef.c \ $(GUI_SRC) $(PERL_SRC) $(PYTHON_SRC) $(TCL_SRC) \ $(RUBY_SRC) $(HANGULIN_SRC) $(MZSCH_SRC) $(XDIFF_SRC) OBJ = arabic.obj autocmd.obj beval.obj blob.obj blowfish.obj buffer.obj \ - charset.obj crypt.obj crypt_zip.obj dict.obj diff.obj digraph.obj \ - edit.obj eval.obj evalfunc.obj ex_cmds.obj ex_cmds2.obj ex_docmd.obj \ - ex_eval.obj ex_getln.obj if_cscope.obj if_xcmdsrv.obj \ + charset.obj crypt.obj crypt_zip.obj debugger.obj dict.obj diff.obj \ + digraph.obj edit.obj eval.obj evalfunc.obj ex_cmds.obj ex_cmds2.obj \ + ex_docmd.obj ex_eval.obj ex_getln.obj if_cscope.obj if_xcmdsrv.obj \ fileio.obj findfile.obj fold.obj getchar.obj hardcopy.obj hashtab.obj \ indent.obj insexpand.obj json.obj list.obj main.obj mark.obj \ menu.obj memfile.obj memline.obj message.obj misc1.obj misc2.obj \ move.obj mbyte.obj normal.obj ops.obj option.obj popupmnu.obj \ quickfix.obj regexp.obj search.obj sha256.obj sign.obj spell.obj \ spellfile.obj syntax.obj tag.obj term.obj termlib.obj textprop.obj \ - ui.obj undo.obj userfunc.obj screen.obj version.obj window.obj \ - os_unix.obj os_vms.obj pathdef.obj if_mzsch.obj \ + ui.obj undo.obj userfunc.obj screen.obj version.obj \ + window.obj os_unix.obj os_vms.obj pathdef.obj if_mzsch.obj \ $(GUI_OBJ) $(PERL_OBJ) $(PYTHON_OBJ) $(TCL_OBJ) \ $(RUBY_OBJ) $(HANGULIN_OBJ) $(MZSCH_OBJ) $(XDIFF_OBJ) @@ -522,6 +522,10 @@ crypt_zip.obj : crypt_zip.c vim.h [.auto ascii.h keymap.h term.h macros.h option.h structs.h \ regexp.h gui.h beval.h [.proto]gui_beval.pro alloc.h ex_cmds.h spell.h \ proto.h globals.h +debugger.obj : debugger.c vim.h [.auto]config.h feature.h os_unix.h \ + ascii.h keymap.h term.h macros.h structs.h regexp.h \ + gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ + globals.h dict.obj : dict.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \ beval.h [.proto]gui_beval.pro alloc.h ex_cmds.h spell.h proto.h \ diff --git a/src/Makefile b/src/Makefile --- a/src/Makefile +++ b/src/Makefile @@ -1579,6 +1579,7 @@ BASIC_SRC = \ charset.c \ crypt.c \ crypt_zip.c \ + debugger.c \ dict.c \ diff.c \ digraph.c \ @@ -1693,6 +1694,7 @@ OBJ_COMMON = \ objects/blowfish.o \ objects/crypt.o \ objects/crypt_zip.o \ + objects/debugger.o \ objects/dict.o \ objects/diff.o \ objects/digraph.o \ @@ -1820,6 +1822,7 @@ PRO_AUTO = \ charset.pro \ crypt.pro \ crypt_zip.pro \ + debugger.pro \ dict.pro \ diff.pro \ digraph.pro \ @@ -2963,6 +2966,9 @@ objects/crypt.o: crypt.c objects/crypt_zip.o: crypt_zip.c $(CCC) -o $@ crypt_zip.c +objects/debugger.o: debugger.c + $(CCC) -o $@ debugger.c + objects/dict.o: dict.c $(CCC) -o $@ dict.c @@ -3425,6 +3431,9 @@ objects/crypt_zip.o: crypt_zip.c vim.h p os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ proto.h globals.h +objects/debugger.o: debugger.c vim.h protodef.h auto/config.h feature.h os_unix.h \ + os_mac.h ascii.h keymap.h term.h macros.h option.h beval.h structs.h \ + regexp.h gui.h alloc.h ex_cmds.h spell.h proto.h globals.h objects/dict.o: dict.c vim.h protodef.h auto/config.h feature.h os_unix.h \ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ diff --git a/src/debugger.c b/src/debugger.c new file mode 100644 --- /dev/null +++ b/src/debugger.c @@ -0,0 +1,990 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * debugger.c: Vim script debugger functions + */ + +#include "vim.h" + +#if defined(FEAT_EVAL) || defined(PROTO) +static int debug_greedy = FALSE; /* batch mode debugging: don't save + and restore typeahead. */ +static void do_setdebugtracelevel(char_u *arg); +static void do_checkbacktracelevel(void); +static void do_showbacktrace(char_u *cmd); + +static char_u *debug_oldval = NULL; /* old and newval for debug expressions */ +static char_u *debug_newval = NULL; +static int debug_expr = 0; /* use debug_expr */ + + int +has_watchexpr(void) +{ + return debug_expr; +} + +/* + * do_debug(): Debug mode. + * Repeatedly get Ex commands, until told to continue normal execution. + */ + void +do_debug(char_u *cmd) +{ + int save_msg_scroll = msg_scroll; + int save_State = State; + int save_did_emsg = did_emsg; + int save_cmd_silent = cmd_silent; + int save_msg_silent = msg_silent; + int save_emsg_silent = emsg_silent; + int save_redir_off = redir_off; + tasave_T typeaheadbuf; + int typeahead_saved = FALSE; + int save_ignore_script = 0; + int save_ex_normal_busy; + int n; + char_u *cmdline = NULL; + char_u *p; + char *tail = NULL; + static int last_cmd = 0; +#define CMD_CONT 1 +#define CMD_NEXT 2 +#define CMD_STEP 3 +#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 + +#ifdef ALWAYS_USE_GUI + /* Can't do this when there is no terminal for input/output. */ + if (!gui.in_use) + { + /* Break as soon as possible. */ + debug_break_level = 9999; + return; + } +#endif + + /* Make sure we are in raw mode and start termcap mode. Might have side + * effects... */ + settmode(TMODE_RAW); + starttermcap(); + + ++RedrawingDisabled; /* don't redisplay the window */ + ++no_wait_return; /* don't wait for return */ + did_emsg = FALSE; /* don't use error from debugged stuff */ + cmd_silent = FALSE; /* display commands */ + msg_silent = FALSE; /* display messages */ + emsg_silent = FALSE; /* display error messages */ + redir_off = TRUE; /* don't redirect debug commands */ + + State = NORMAL; + debug_mode = TRUE; + + if (!debug_did_msg) + msg(_("Entering Debug mode. Type \"cont\" to continue.")); + if (debug_oldval != NULL) + { + smsg(_("Oldval = \"%s\""), debug_oldval); + vim_free(debug_oldval); + debug_oldval = NULL; + } + if (debug_newval != NULL) + { + smsg(_("Newval = \"%s\""), debug_newval); + vim_free(debug_newval); + debug_newval = NULL; + } + if (sourcing_name != NULL) + msg((char *)sourcing_name); + if (sourcing_lnum != 0) + smsg(_("line %ld: %s"), (long)sourcing_lnum, cmd); + else + smsg(_("cmd: %s"), cmd); + /* + * Repeat getting a command and executing it. + */ + for (;;) + { + msg_scroll = TRUE; + need_wait_return = FALSE; + + /* Save the current typeahead buffer and replace it with an empty one. + * This makes sure we get input from the user here and don't interfere + * with the commands being executed. Reset "ex_normal_busy" to avoid + * the side effects of using ":normal". Save the stuff buffer and make + * it empty. Set ignore_script to avoid reading from script input. */ + save_ex_normal_busy = ex_normal_busy; + ex_normal_busy = 0; + if (!debug_greedy) + { + save_typeahead(&typeaheadbuf); + typeahead_saved = TRUE; + save_ignore_script = ignore_script; + ignore_script = TRUE; + } + + vim_free(cmdline); + cmdline = getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL); + + if (typeahead_saved) + { + restore_typeahead(&typeaheadbuf); + ignore_script = save_ignore_script; + } + 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". + * For a blank line use previous command. */ + p = skipwhite(cmdline); + if (*p != NUL) + { + switch (*p) + { + case 'c': last_cmd = CMD_CONT; + tail = "ont"; + break; + case 'n': last_cmd = CMD_NEXT; + tail = "ext"; + break; + case 's': last_cmd = CMD_STEP; + tail = "tep"; + break; + 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"; + break; + 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) + { + /* Check that the tail matches. */ + ++p; + while (*p != NUL && *p == *tail) + { + ++p; + ++tail; + } + if (ASCII_ISALPHA(*p) && last_cmd != CMD_FRAME) + last_cmd = 0; + } + } + + if (last_cmd != 0) + { + /* Execute debug command: decided where to break next and + * return. */ + switch (last_cmd) + { + case CMD_CONT: + debug_break_level = -1; + break; + case CMD_NEXT: + debug_break_level = ex_nesting_level; + break; + case CMD_STEP: + debug_break_level = 9999; + break; + case CMD_FINISH: + debug_break_level = ex_nesting_level - 1; + break; + case CMD_QUIT: + got_int = TRUE; + debug_break_level = -1; + break; + case CMD_INTERRUPT: + got_int = TRUE; + debug_break_level = 9999; + /* 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; + } + + /* don't debug this command */ + n = debug_break_level; + debug_break_level = -1; + (void)do_cmdline(cmdline, getexline, NULL, + DOCMD_VERBOSE|DOCMD_EXCRESET); + debug_break_level = n; + } + lines_left = Rows - 1; + } + vim_free(cmdline); + + --RedrawingDisabled; + --no_wait_return; + redraw_all_later(NOT_VALID); + need_wait_return = FALSE; + msg_scroll = save_msg_scroll; + lines_left = Rows - 1; + State = save_State; + debug_mode = FALSE; + did_emsg = save_did_emsg; + cmd_silent = save_cmd_silent; + msg_silent = save_msg_silent; + emsg_silent = save_emsg_silent; + redir_off = save_redir_off; + + /* Only print the message again when typing a command before coming back + * here. */ + debug_did_msg = TRUE; +} + + static int +get_maxbacktrace_level(void) +{ + char *p, *q; + int maxbacktrace = 0; + + if (sourcing_name != NULL) + { + p = (char *)sourcing_name; + while ((q = strstr(p, "..")) != NULL) + { + p = q + 2; + maxbacktrace++; + } + } + return maxbacktrace; +} + + static void +do_setdebugtracelevel(char_u *arg) +{ + int level; + + 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) +{ + char *cur; + char *next; + int i = 0; + int max = get_maxbacktrace_level(); + + if (sourcing_name != NULL) + { + cur = (char *)sourcing_name; + while (!got_int) + { + 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 %ld: %s"), (long)sourcing_lnum, cmd); + else + smsg(_("cmd: %s"), cmd); +} + +/* + * ":debug". + */ + void +ex_debug(exarg_T *eap) +{ + int debug_break_level_save = debug_break_level; + + debug_break_level = 9999; + do_cmdline_cmd(eap->arg); + debug_break_level = debug_break_level_save; +} + +static char_u *debug_breakpoint_name = NULL; +static linenr_T debug_breakpoint_lnum; + +/* + * When debugging or a breakpoint is set on a skipped command, no debug prompt + * is shown by do_one_cmd(). This situation is indicated by debug_skipped, and + * debug_skipped_name is then set to the source name in the breakpoint case. If + * a skipped command decides itself that a debug prompt should be displayed, it + * can do so by calling dbg_check_skipped(). + */ +static int debug_skipped; +static char_u *debug_skipped_name; + +/* + * Go to debug mode when a breakpoint was encountered or "ex_nesting_level" is + * at or below the break level. But only when the line is actually + * executed. Return TRUE and set breakpoint_name for skipped commands that + * decide to execute something themselves. + * Called from do_one_cmd() before executing a command. + */ + void +dbg_check_breakpoint(exarg_T *eap) +{ + char_u *p; + + debug_skipped = FALSE; + if (debug_breakpoint_name != NULL) + { + if (!eap->skip) + { + /* replace K_SNR with "" */ + if (debug_breakpoint_name[0] == K_SPECIAL + && debug_breakpoint_name[1] == KS_EXTRA + && debug_breakpoint_name[2] == (int)KE_SNR) + p = (char_u *)""; + else + p = (char_u *)""; + smsg(_("Breakpoint in \"%s%s\" line %ld"), + p, + debug_breakpoint_name + (*p == NUL ? 0 : 3), + (long)debug_breakpoint_lnum); + debug_breakpoint_name = NULL; + do_debug(eap->cmd); + } + else + { + debug_skipped = TRUE; + debug_skipped_name = debug_breakpoint_name; + debug_breakpoint_name = NULL; + } + } + else if (ex_nesting_level <= debug_break_level) + { + if (!eap->skip) + do_debug(eap->cmd); + else + { + debug_skipped = TRUE; + debug_skipped_name = NULL; + } + } +} + +/* + * Go to debug mode if skipped by dbg_check_breakpoint() because eap->skip was + * set. Return TRUE when the debug mode is entered this time. + */ + int +dbg_check_skipped(exarg_T *eap) +{ + int prev_got_int; + + if (debug_skipped) + { + /* + * Save the value of got_int and reset it. We don't want a previous + * interruption cause flushing the input buffer. + */ + prev_got_int = got_int; + got_int = FALSE; + debug_breakpoint_name = debug_skipped_name; + /* eap->skip is TRUE */ + eap->skip = FALSE; + (void)dbg_check_breakpoint(eap); + eap->skip = TRUE; + got_int |= prev_got_int; + return TRUE; + } + return FALSE; +} + +/* + * The list of breakpoints: dbg_breakp. + * This is a grow-array of structs. + */ +struct debuggy +{ + int dbg_nr; /* breakpoint number */ + int dbg_type; /* DBG_FUNC, 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 */ +#ifdef FEAT_EVAL + typval_T *dbg_val; /* last result of watchexpression */ +#endif + int dbg_level; /* stored nested level for expr */ +}; + +static garray_T dbg_breakp = {0, 0, sizeof(struct debuggy), 4, NULL}; +#define BREAKP(idx) (((struct debuggy *)dbg_breakp.ga_data)[idx]) +#define DEBUGGY(gap, idx) (((struct debuggy *)gap->ga_data)[idx]) +static int last_breakp = 0; /* nr of last defined breakpoint */ + +#ifdef FEAT_PROFILE +/* Profiling uses file and func names similar to breakpoints. */ +static garray_T prof_ga = {0, 0, sizeof(struct debuggy), 4, NULL}; +#endif +#define DBG_FUNC 1 +#define DBG_FILE 2 +#define DBG_EXPR 3 + +static linenr_T debuggy_find(int file,char_u *fname, linenr_T after, garray_T *gap, int *fp); + +/* + * Parse the arguments of ":profile", ":breakadd" or ":breakdel" and put them + * in the entry just after the last one in dbg_breakp. Note that "dbg_name" + * is allocated. + * Returns FAIL for failure. + */ + static int +dbg_parsearg( + char_u *arg, + garray_T *gap) /* either &dbg_breakp or &prof_ga */ +{ + char_u *p = arg; + char_u *q; + struct debuggy *bp; + int here = FALSE; + + if (ga_grow(gap, 1) == FAIL) + return FAIL; + bp = &DEBUGGY(gap, gap->ga_len); + + /* Find "func" or "file". */ + if (STRNCMP(p, "func", 4) == 0) + bp->dbg_type = DBG_FUNC; + else if (STRNCMP(p, "file", 4) == 0) + bp->dbg_type = DBG_FILE; + else if ( +#ifdef FEAT_PROFILE + gap != &prof_ga && +#endif + STRNCMP(p, "here", 4) == 0) + { + if (curbuf->b_ffname == NULL) + { + emsg(_(e_noname)); + return FAIL; + } + bp->dbg_type = DBG_FILE; + here = TRUE; + } + else if ( +#ifdef FEAT_PROFILE + gap != &prof_ga && +#endif + STRNCMP(p, "expr", 4) == 0) + bp->dbg_type = DBG_EXPR; + else + { + semsg(_(e_invarg2), p); + return FAIL; + } + p = skipwhite(p + 4); + + /* Find optional line number. */ + if (here) + bp->dbg_lnum = curwin->w_cursor.lnum; + else if ( +#ifdef FEAT_PROFILE + gap != &prof_ga && +#endif + VIM_ISDIGIT(*p)) + { + bp->dbg_lnum = getdigits(&p); + p = skipwhite(p); + } + else + bp->dbg_lnum = 0; + + /* Find the function or file name. Don't accept a function name with (). */ + if ((!here && *p == NUL) + || (here && *p != NUL) + || (bp->dbg_type == DBG_FUNC && strstr((char *)p, "()") != NULL)) + { + semsg(_(e_invarg2), arg); + return FAIL; + } + + if (bp->dbg_type == DBG_FUNC) + 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); + if (bp->dbg_name != NULL) + bp->dbg_val = eval_expr(bp->dbg_name, NULL); + } + 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 + * "~/dir". */ + q = expand_env_save(p); + if (q == NULL) + return FAIL; + p = expand_env_save(q); + vim_free(q); + if (p == NULL) + return FAIL; + if (*p != '*') + { + bp->dbg_name = fix_fname(p); + vim_free(p); + } + else + bp->dbg_name = p; + } + + if (bp->dbg_name == NULL) + return FAIL; + return OK; +} + +/* + * ":breakadd". Also used for ":profile". + */ + void +ex_breakadd(exarg_T *eap) +{ + struct debuggy *bp; + char_u *pat; + garray_T *gap; + + gap = &dbg_breakp; +#ifdef FEAT_PROFILE + if (eap->cmdidx == CMD_profile) + gap = &prof_ga; +#endif + + if (dbg_parsearg(eap->arg, gap) == OK) + { + bp = &DEBUGGY(gap, gap->ga_len); + bp->dbg_forceit = eap->forceit; + + if (bp->dbg_type != DBG_EXPR) + { + 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); + vim_free(pat); + } + if (pat == NULL || bp->dbg_prog == NULL) + vim_free(bp->dbg_name); + else + { + if (bp->dbg_lnum == 0) /* default line number is 1 */ + bp->dbg_lnum = 1; +#ifdef FEAT_PROFILE + if (eap->cmdidx != CMD_profile) +#endif + { + DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp; + ++debug_tick; + } + ++gap->ga_len; + } + } + else + { + /* DBG_EXPR */ + DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp; + ++debug_tick; + } + } +} + +/* + * ":debuggreedy". + */ + void +ex_debuggreedy(exarg_T *eap) +{ + if (eap->addr_count == 0 || eap->line2 != 0) + debug_greedy = TRUE; + else + debug_greedy = FALSE; +} + +/* + * ":breakdel" and ":profdel". + */ + void +ex_breakdel(exarg_T *eap) +{ + struct debuggy *bp, *bpi; + int nr; + int todel = -1; + int del_all = FALSE; + int i; + linenr_T best_lnum = 0; + garray_T *gap; + + gap = &dbg_breakp; + if (eap->cmdidx == CMD_profdel) + { +#ifdef FEAT_PROFILE + gap = &prof_ga; +#else + ex_ni(eap); + return; +#endif + } + + if (vim_isdigit(*eap->arg)) + { + /* ":breakdel {nr}" */ + nr = atol((char *)eap->arg); + for (i = 0; i < gap->ga_len; ++i) + if (DEBUGGY(gap, i).dbg_nr == nr) + { + todel = i; + break; + } + } + else if (*eap->arg == '*') + { + todel = 0; + del_all = TRUE; + } + else + { + /* ":breakdel {func|file|expr} [lnum] {name}" */ + if (dbg_parsearg(eap->arg, gap) == FAIL) + return; + bp = &DEBUGGY(gap, gap->ga_len); + for (i = 0; i < gap->ga_len; ++i) + { + bpi = &DEBUGGY(gap, i); + if (bp->dbg_type == bpi->dbg_type + && STRCMP(bp->dbg_name, bpi->dbg_name) == 0 + && (bp->dbg_lnum == bpi->dbg_lnum + || (bp->dbg_lnum == 0 + && (best_lnum == 0 + || bpi->dbg_lnum < best_lnum)))) + { + todel = i; + best_lnum = bpi->dbg_lnum; + } + } + vim_free(bp->dbg_name); + } + + if (todel < 0) + semsg(_("E161: Breakpoint not found: %s"), eap->arg); + else + { + while (gap->ga_len > 0) + { + vim_free(DEBUGGY(gap, todel).dbg_name); +#ifdef FEAT_EVAL + if (DEBUGGY(gap, todel).dbg_type == DBG_EXPR + && DEBUGGY(gap, todel).dbg_val != NULL) + free_tv(DEBUGGY(gap, todel).dbg_val); +#endif + vim_regfree(DEBUGGY(gap, todel).dbg_prog); + --gap->ga_len; + if (todel < gap->ga_len) + mch_memmove(&DEBUGGY(gap, todel), &DEBUGGY(gap, todel + 1), + (gap->ga_len - todel) * sizeof(struct debuggy)); +#ifdef FEAT_PROFILE + if (eap->cmdidx == CMD_breakdel) +#endif + ++debug_tick; + if (!del_all) + break; + } + + /* If all breakpoints were removed clear the array. */ + if (gap->ga_len == 0) + ga_clear(gap); + } +} + +/* + * ":breaklist". + */ + void +ex_breaklist(exarg_T *eap UNUSED) +{ + struct debuggy *bp; + int i; + + if (dbg_breakp.ga_len == 0) + msg(_("No breakpoints defined")); + else + for (i = 0; i < dbg_breakp.ga_len; ++i) + { + bp = &BREAKP(i); + 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 %ld"), + bp->dbg_nr, + bp->dbg_type == DBG_FUNC ? "func" : "file", + bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff, + (long)bp->dbg_lnum); + else + smsg(_("%3d expr %s"), + bp->dbg_nr, bp->dbg_name); + } +} + +/* + * Find a breakpoint for a function or sourced file. + * Returns line number at which to break; zero when no matching breakpoint. + */ + linenr_T +dbg_find_breakpoint( + int file, /* TRUE for a file, FALSE for a function */ + char_u *fname, /* file or function name */ + linenr_T after) /* after this line number */ +{ + return debuggy_find(file, fname, after, &dbg_breakp, NULL); +} + +#if defined(FEAT_PROFILE) || defined(PROTO) +/* + * Return TRUE if profiling is on for a function or sourced file. + */ + int +has_profiling( + int file, /* TRUE for a file, FALSE for a function */ + char_u *fname, /* file or function name */ + int *fp) /* return: forceit */ +{ + return (debuggy_find(file, fname, (linenr_T)0, &prof_ga, fp) + != (linenr_T)0); +} +#endif + +/* + * Common code for dbg_find_breakpoint() and has_profiling(). + */ + static linenr_T +debuggy_find( + int file, /* TRUE for a file, FALSE for a function */ + char_u *fname, /* file or function name */ + linenr_T after, /* after this line number */ + garray_T *gap, /* either &dbg_breakp or &prof_ga */ + int *fp) /* if not NULL: return forceit */ +{ + struct debuggy *bp; + int i; + linenr_T lnum = 0; + char_u *name = fname; + int prev_got_int; + + /* Return quickly when there are no breakpoints. */ + if (gap->ga_len == 0) + return (linenr_T)0; + + /* Replace K_SNR in function name with "". */ + if (!file && fname[0] == K_SPECIAL) + { + name = alloc((unsigned)STRLEN(fname) + 3); + if (name == NULL) + name = fname; + else + { + STRCPY(name, ""); + STRCPY(name + 5, fname + 3); + } + } + + for (i = 0; i < gap->ga_len; ++i) + { + /* Skip entries that are not useful or are for a line that is beyond + * an already found breakpoint. */ + bp = &DEBUGGY(gap, i); + if (((bp->dbg_type == DBG_FILE) == file && + bp->dbg_type != DBG_EXPR && ( +#ifdef FEAT_PROFILE + gap == &prof_ga || +#endif + (bp->dbg_lnum > after && (lnum == 0 || bp->dbg_lnum < lnum))))) + { + /* + * Save the value of got_int and reset it. We don't want a + * previous interruption cancel matching, only hitting CTRL-C + * while matching should abort it. + */ + prev_got_int = got_int; + got_int = FALSE; + if (vim_regexec_prog(&bp->dbg_prog, FALSE, name, (colnr_T)0)) + { + lnum = bp->dbg_lnum; + if (fp != NULL) + *fp = bp->dbg_forceit; + } + got_int |= prev_got_int; + } +#ifdef FEAT_EVAL + else if (bp->dbg_type == DBG_EXPR) + { + typval_T *tv; + int line = FALSE; + + prev_got_int = got_int; + got_int = FALSE; + + tv = eval_expr(bp->dbg_name, NULL); + 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) + { + typval_T *v; + + line = TRUE; + debug_oldval = typval_tostring(bp->dbg_val); + /* Need to evaluate again, typval_compare() overwrites + * "tv". */ + v = eval_expr(bp->dbg_name, NULL); + debug_newval = typval_tostring(v); + free_tv(bp->dbg_val); + bp->dbg_val = v; + } + free_tv(tv); + } + } + else if (bp->dbg_val != NULL) + { + debug_oldval = typval_tostring(bp->dbg_val); + debug_newval = typval_tostring(NULL); + free_tv(bp->dbg_val); + bp->dbg_val = NULL; + line = TRUE; + } + + if (line) + { + lnum = after > 0 ? after : 1; + break; + } + + got_int |= prev_got_int; + } +#endif + } + if (name != fname) + vim_free(name); + + return lnum; +} + +/* + * Called when a breakpoint was encountered. + */ + void +dbg_breakpoint(char_u *name, linenr_T lnum) +{ + /* We need to check if this line is actually executed in do_one_cmd() */ + debug_breakpoint_name = name; + debug_breakpoint_lnum = lnum; +} +#endif diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c --- a/src/ex_cmds2.c +++ b/src/ex_cmds2.c @@ -66,981 +66,6 @@ typedef struct sn_prl_S #endif #if defined(FEAT_EVAL) || defined(PROTO) -static int debug_greedy = FALSE; /* batch mode debugging: don't save - and restore typeahead. */ -static void do_setdebugtracelevel(char_u *arg); -static void do_checkbacktracelevel(void); -static void do_showbacktrace(char_u *cmd); - -static char_u *debug_oldval = NULL; /* old and newval for debug expressions */ -static char_u *debug_newval = NULL; -static int debug_expr = 0; /* use debug_expr */ - - int -has_watchexpr(void) -{ - return debug_expr; -} - -/* - * do_debug(): Debug mode. - * Repeatedly get Ex commands, until told to continue normal execution. - */ - void -do_debug(char_u *cmd) -{ - int save_msg_scroll = msg_scroll; - int save_State = State; - int save_did_emsg = did_emsg; - int save_cmd_silent = cmd_silent; - int save_msg_silent = msg_silent; - int save_emsg_silent = emsg_silent; - int save_redir_off = redir_off; - tasave_T typeaheadbuf; - int typeahead_saved = FALSE; - int save_ignore_script = 0; - int save_ex_normal_busy; - int n; - char_u *cmdline = NULL; - char_u *p; - char *tail = NULL; - static int last_cmd = 0; -#define CMD_CONT 1 -#define CMD_NEXT 2 -#define CMD_STEP 3 -#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 - -#ifdef ALWAYS_USE_GUI - /* Can't do this when there is no terminal for input/output. */ - if (!gui.in_use) - { - /* Break as soon as possible. */ - debug_break_level = 9999; - return; - } -#endif - - /* Make sure we are in raw mode and start termcap mode. Might have side - * effects... */ - settmode(TMODE_RAW); - starttermcap(); - - ++RedrawingDisabled; /* don't redisplay the window */ - ++no_wait_return; /* don't wait for return */ - did_emsg = FALSE; /* don't use error from debugged stuff */ - cmd_silent = FALSE; /* display commands */ - msg_silent = FALSE; /* display messages */ - emsg_silent = FALSE; /* display error messages */ - redir_off = TRUE; /* don't redirect debug commands */ - - State = NORMAL; - debug_mode = TRUE; - - if (!debug_did_msg) - msg(_("Entering Debug mode. Type \"cont\" to continue.")); - if (debug_oldval != NULL) - { - smsg(_("Oldval = \"%s\""), debug_oldval); - vim_free(debug_oldval); - debug_oldval = NULL; - } - if (debug_newval != NULL) - { - smsg(_("Newval = \"%s\""), debug_newval); - vim_free(debug_newval); - debug_newval = NULL; - } - if (sourcing_name != NULL) - msg((char *)sourcing_name); - if (sourcing_lnum != 0) - smsg(_("line %ld: %s"), (long)sourcing_lnum, cmd); - else - smsg(_("cmd: %s"), cmd); - /* - * Repeat getting a command and executing it. - */ - for (;;) - { - msg_scroll = TRUE; - need_wait_return = FALSE; - - /* Save the current typeahead buffer and replace it with an empty one. - * This makes sure we get input from the user here and don't interfere - * with the commands being executed. Reset "ex_normal_busy" to avoid - * the side effects of using ":normal". Save the stuff buffer and make - * it empty. Set ignore_script to avoid reading from script input. */ - save_ex_normal_busy = ex_normal_busy; - ex_normal_busy = 0; - if (!debug_greedy) - { - save_typeahead(&typeaheadbuf); - typeahead_saved = TRUE; - save_ignore_script = ignore_script; - ignore_script = TRUE; - } - - vim_free(cmdline); - cmdline = getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL); - - if (typeahead_saved) - { - restore_typeahead(&typeaheadbuf); - ignore_script = save_ignore_script; - } - 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". - * For a blank line use previous command. */ - p = skipwhite(cmdline); - if (*p != NUL) - { - switch (*p) - { - case 'c': last_cmd = CMD_CONT; - tail = "ont"; - break; - case 'n': last_cmd = CMD_NEXT; - tail = "ext"; - break; - case 's': last_cmd = CMD_STEP; - tail = "tep"; - break; - 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"; - break; - 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) - { - /* Check that the tail matches. */ - ++p; - while (*p != NUL && *p == *tail) - { - ++p; - ++tail; - } - if (ASCII_ISALPHA(*p) && last_cmd != CMD_FRAME) - last_cmd = 0; - } - } - - if (last_cmd != 0) - { - /* Execute debug command: decided where to break next and - * return. */ - switch (last_cmd) - { - case CMD_CONT: - debug_break_level = -1; - break; - case CMD_NEXT: - debug_break_level = ex_nesting_level; - break; - case CMD_STEP: - debug_break_level = 9999; - break; - case CMD_FINISH: - debug_break_level = ex_nesting_level - 1; - break; - case CMD_QUIT: - got_int = TRUE; - debug_break_level = -1; - break; - case CMD_INTERRUPT: - got_int = TRUE; - debug_break_level = 9999; - /* 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; - } - - /* don't debug this command */ - n = debug_break_level; - debug_break_level = -1; - (void)do_cmdline(cmdline, getexline, NULL, - DOCMD_VERBOSE|DOCMD_EXCRESET); - debug_break_level = n; - } - lines_left = Rows - 1; - } - vim_free(cmdline); - - --RedrawingDisabled; - --no_wait_return; - redraw_all_later(NOT_VALID); - need_wait_return = FALSE; - msg_scroll = save_msg_scroll; - lines_left = Rows - 1; - State = save_State; - debug_mode = FALSE; - did_emsg = save_did_emsg; - cmd_silent = save_cmd_silent; - msg_silent = save_msg_silent; - emsg_silent = save_emsg_silent; - redir_off = save_redir_off; - - /* Only print the message again when typing a command before coming back - * here. */ - debug_did_msg = TRUE; -} - - static int -get_maxbacktrace_level(void) -{ - char *p, *q; - int maxbacktrace = 0; - - if (sourcing_name != NULL) - { - p = (char *)sourcing_name; - while ((q = strstr(p, "..")) != NULL) - { - p = q + 2; - maxbacktrace++; - } - } - return maxbacktrace; -} - - static void -do_setdebugtracelevel(char_u *arg) -{ - int level; - - 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) -{ - char *cur; - char *next; - int i = 0; - int max = get_maxbacktrace_level(); - - if (sourcing_name != NULL) - { - cur = (char *)sourcing_name; - while (!got_int) - { - 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 %ld: %s"), (long)sourcing_lnum, cmd); - else - smsg(_("cmd: %s"), cmd); -} - -/* - * ":debug". - */ - void -ex_debug(exarg_T *eap) -{ - int debug_break_level_save = debug_break_level; - - debug_break_level = 9999; - do_cmdline_cmd(eap->arg); - debug_break_level = debug_break_level_save; -} - -static char_u *debug_breakpoint_name = NULL; -static linenr_T debug_breakpoint_lnum; - -/* - * When debugging or a breakpoint is set on a skipped command, no debug prompt - * is shown by do_one_cmd(). This situation is indicated by debug_skipped, and - * debug_skipped_name is then set to the source name in the breakpoint case. If - * a skipped command decides itself that a debug prompt should be displayed, it - * can do so by calling dbg_check_skipped(). - */ -static int debug_skipped; -static char_u *debug_skipped_name; - -/* - * Go to debug mode when a breakpoint was encountered or "ex_nesting_level" is - * at or below the break level. But only when the line is actually - * executed. Return TRUE and set breakpoint_name for skipped commands that - * decide to execute something themselves. - * Called from do_one_cmd() before executing a command. - */ - void -dbg_check_breakpoint(exarg_T *eap) -{ - char_u *p; - - debug_skipped = FALSE; - if (debug_breakpoint_name != NULL) - { - if (!eap->skip) - { - /* replace K_SNR with "" */ - if (debug_breakpoint_name[0] == K_SPECIAL - && debug_breakpoint_name[1] == KS_EXTRA - && debug_breakpoint_name[2] == (int)KE_SNR) - p = (char_u *)""; - else - p = (char_u *)""; - smsg(_("Breakpoint in \"%s%s\" line %ld"), - p, - debug_breakpoint_name + (*p == NUL ? 0 : 3), - (long)debug_breakpoint_lnum); - debug_breakpoint_name = NULL; - do_debug(eap->cmd); - } - else - { - debug_skipped = TRUE; - debug_skipped_name = debug_breakpoint_name; - debug_breakpoint_name = NULL; - } - } - else if (ex_nesting_level <= debug_break_level) - { - if (!eap->skip) - do_debug(eap->cmd); - else - { - debug_skipped = TRUE; - debug_skipped_name = NULL; - } - } -} - -/* - * Go to debug mode if skipped by dbg_check_breakpoint() because eap->skip was - * set. Return TRUE when the debug mode is entered this time. - */ - int -dbg_check_skipped(exarg_T *eap) -{ - int prev_got_int; - - if (debug_skipped) - { - /* - * Save the value of got_int and reset it. We don't want a previous - * interruption cause flushing the input buffer. - */ - prev_got_int = got_int; - got_int = FALSE; - debug_breakpoint_name = debug_skipped_name; - /* eap->skip is TRUE */ - eap->skip = FALSE; - (void)dbg_check_breakpoint(eap); - eap->skip = TRUE; - got_int |= prev_got_int; - return TRUE; - } - return FALSE; -} - -/* - * The list of breakpoints: dbg_breakp. - * This is a grow-array of structs. - */ -struct debuggy -{ - int dbg_nr; /* breakpoint number */ - int dbg_type; /* DBG_FUNC, 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 */ -#ifdef FEAT_EVAL - typval_T *dbg_val; /* last result of watchexpression */ -#endif - int dbg_level; /* stored nested level for expr */ -}; - -static garray_T dbg_breakp = {0, 0, sizeof(struct debuggy), 4, NULL}; -#define BREAKP(idx) (((struct debuggy *)dbg_breakp.ga_data)[idx]) -#define DEBUGGY(gap, idx) (((struct debuggy *)gap->ga_data)[idx]) -static int last_breakp = 0; /* nr of last defined breakpoint */ - -#ifdef FEAT_PROFILE -/* Profiling uses file and func names similar to breakpoints. */ -static garray_T prof_ga = {0, 0, sizeof(struct debuggy), 4, NULL}; -#endif -#define DBG_FUNC 1 -#define DBG_FILE 2 -#define DBG_EXPR 3 - -static linenr_T debuggy_find(int file,char_u *fname, linenr_T after, garray_T *gap, int *fp); - -/* - * Parse the arguments of ":profile", ":breakadd" or ":breakdel" and put them - * in the entry just after the last one in dbg_breakp. Note that "dbg_name" - * is allocated. - * Returns FAIL for failure. - */ - static int -dbg_parsearg( - char_u *arg, - garray_T *gap) /* either &dbg_breakp or &prof_ga */ -{ - char_u *p = arg; - char_u *q; - struct debuggy *bp; - int here = FALSE; - - if (ga_grow(gap, 1) == FAIL) - return FAIL; - bp = &DEBUGGY(gap, gap->ga_len); - - /* Find "func" or "file". */ - if (STRNCMP(p, "func", 4) == 0) - bp->dbg_type = DBG_FUNC; - else if (STRNCMP(p, "file", 4) == 0) - bp->dbg_type = DBG_FILE; - else if ( -#ifdef FEAT_PROFILE - gap != &prof_ga && -#endif - STRNCMP(p, "here", 4) == 0) - { - if (curbuf->b_ffname == NULL) - { - emsg(_(e_noname)); - return FAIL; - } - bp->dbg_type = DBG_FILE; - here = TRUE; - } - else if ( -#ifdef FEAT_PROFILE - gap != &prof_ga && -#endif - STRNCMP(p, "expr", 4) == 0) - bp->dbg_type = DBG_EXPR; - else - { - semsg(_(e_invarg2), p); - return FAIL; - } - p = skipwhite(p + 4); - - /* Find optional line number. */ - if (here) - bp->dbg_lnum = curwin->w_cursor.lnum; - else if ( -#ifdef FEAT_PROFILE - gap != &prof_ga && -#endif - VIM_ISDIGIT(*p)) - { - bp->dbg_lnum = getdigits(&p); - p = skipwhite(p); - } - else - bp->dbg_lnum = 0; - - /* Find the function or file name. Don't accept a function name with (). */ - if ((!here && *p == NUL) - || (here && *p != NUL) - || (bp->dbg_type == DBG_FUNC && strstr((char *)p, "()") != NULL)) - { - semsg(_(e_invarg2), arg); - return FAIL; - } - - if (bp->dbg_type == DBG_FUNC) - 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); - if (bp->dbg_name != NULL) - bp->dbg_val = eval_expr(bp->dbg_name, NULL); - } - 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 - * "~/dir". */ - q = expand_env_save(p); - if (q == NULL) - return FAIL; - p = expand_env_save(q); - vim_free(q); - if (p == NULL) - return FAIL; - if (*p != '*') - { - bp->dbg_name = fix_fname(p); - vim_free(p); - } - else - bp->dbg_name = p; - } - - if (bp->dbg_name == NULL) - return FAIL; - return OK; -} - -/* - * ":breakadd". Also used for ":profile". - */ - void -ex_breakadd(exarg_T *eap) -{ - struct debuggy *bp; - char_u *pat; - garray_T *gap; - - gap = &dbg_breakp; -#ifdef FEAT_PROFILE - if (eap->cmdidx == CMD_profile) - gap = &prof_ga; -#endif - - if (dbg_parsearg(eap->arg, gap) == OK) - { - bp = &DEBUGGY(gap, gap->ga_len); - bp->dbg_forceit = eap->forceit; - - if (bp->dbg_type != DBG_EXPR) - { - 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); - vim_free(pat); - } - if (pat == NULL || bp->dbg_prog == NULL) - vim_free(bp->dbg_name); - else - { - if (bp->dbg_lnum == 0) /* default line number is 1 */ - bp->dbg_lnum = 1; -#ifdef FEAT_PROFILE - if (eap->cmdidx != CMD_profile) -#endif - { - DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp; - ++debug_tick; - } - ++gap->ga_len; - } - } - else - { - /* DBG_EXPR */ - DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp; - ++debug_tick; - } - } -} - -/* - * ":debuggreedy". - */ - void -ex_debuggreedy(exarg_T *eap) -{ - if (eap->addr_count == 0 || eap->line2 != 0) - debug_greedy = TRUE; - else - debug_greedy = FALSE; -} - -/* - * ":breakdel" and ":profdel". - */ - void -ex_breakdel(exarg_T *eap) -{ - struct debuggy *bp, *bpi; - int nr; - int todel = -1; - int del_all = FALSE; - int i; - linenr_T best_lnum = 0; - garray_T *gap; - - gap = &dbg_breakp; - if (eap->cmdidx == CMD_profdel) - { -#ifdef FEAT_PROFILE - gap = &prof_ga; -#else - ex_ni(eap); - return; -#endif - } - - if (vim_isdigit(*eap->arg)) - { - /* ":breakdel {nr}" */ - nr = atol((char *)eap->arg); - for (i = 0; i < gap->ga_len; ++i) - if (DEBUGGY(gap, i).dbg_nr == nr) - { - todel = i; - break; - } - } - else if (*eap->arg == '*') - { - todel = 0; - del_all = TRUE; - } - else - { - /* ":breakdel {func|file|expr} [lnum] {name}" */ - if (dbg_parsearg(eap->arg, gap) == FAIL) - return; - bp = &DEBUGGY(gap, gap->ga_len); - for (i = 0; i < gap->ga_len; ++i) - { - bpi = &DEBUGGY(gap, i); - if (bp->dbg_type == bpi->dbg_type - && STRCMP(bp->dbg_name, bpi->dbg_name) == 0 - && (bp->dbg_lnum == bpi->dbg_lnum - || (bp->dbg_lnum == 0 - && (best_lnum == 0 - || bpi->dbg_lnum < best_lnum)))) - { - todel = i; - best_lnum = bpi->dbg_lnum; - } - } - vim_free(bp->dbg_name); - } - - if (todel < 0) - semsg(_("E161: Breakpoint not found: %s"), eap->arg); - else - { - while (gap->ga_len > 0) - { - vim_free(DEBUGGY(gap, todel).dbg_name); -#ifdef FEAT_EVAL - if (DEBUGGY(gap, todel).dbg_type == DBG_EXPR - && DEBUGGY(gap, todel).dbg_val != NULL) - free_tv(DEBUGGY(gap, todel).dbg_val); -#endif - vim_regfree(DEBUGGY(gap, todel).dbg_prog); - --gap->ga_len; - if (todel < gap->ga_len) - mch_memmove(&DEBUGGY(gap, todel), &DEBUGGY(gap, todel + 1), - (gap->ga_len - todel) * sizeof(struct debuggy)); -#ifdef FEAT_PROFILE - if (eap->cmdidx == CMD_breakdel) -#endif - ++debug_tick; - if (!del_all) - break; - } - - /* If all breakpoints were removed clear the array. */ - if (gap->ga_len == 0) - ga_clear(gap); - } -} - -/* - * ":breaklist". - */ - void -ex_breaklist(exarg_T *eap UNUSED) -{ - struct debuggy *bp; - int i; - - if (dbg_breakp.ga_len == 0) - msg(_("No breakpoints defined")); - else - for (i = 0; i < dbg_breakp.ga_len; ++i) - { - bp = &BREAKP(i); - 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 %ld"), - bp->dbg_nr, - bp->dbg_type == DBG_FUNC ? "func" : "file", - bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff, - (long)bp->dbg_lnum); - else - smsg(_("%3d expr %s"), - bp->dbg_nr, bp->dbg_name); - } -} - -/* - * Find a breakpoint for a function or sourced file. - * Returns line number at which to break; zero when no matching breakpoint. - */ - linenr_T -dbg_find_breakpoint( - int file, /* TRUE for a file, FALSE for a function */ - char_u *fname, /* file or function name */ - linenr_T after) /* after this line number */ -{ - return debuggy_find(file, fname, after, &dbg_breakp, NULL); -} - -#if defined(FEAT_PROFILE) || defined(PROTO) -/* - * Return TRUE if profiling is on for a function or sourced file. - */ - int -has_profiling( - int file, /* TRUE for a file, FALSE for a function */ - char_u *fname, /* file or function name */ - int *fp) /* return: forceit */ -{ - return (debuggy_find(file, fname, (linenr_T)0, &prof_ga, fp) - != (linenr_T)0); -} -#endif - -/* - * Common code for dbg_find_breakpoint() and has_profiling(). - */ - static linenr_T -debuggy_find( - int file, /* TRUE for a file, FALSE for a function */ - char_u *fname, /* file or function name */ - linenr_T after, /* after this line number */ - garray_T *gap, /* either &dbg_breakp or &prof_ga */ - int *fp) /* if not NULL: return forceit */ -{ - struct debuggy *bp; - int i; - linenr_T lnum = 0; - char_u *name = fname; - int prev_got_int; - - /* Return quickly when there are no breakpoints. */ - if (gap->ga_len == 0) - return (linenr_T)0; - - /* Replace K_SNR in function name with "". */ - if (!file && fname[0] == K_SPECIAL) - { - name = alloc((unsigned)STRLEN(fname) + 3); - if (name == NULL) - name = fname; - else - { - STRCPY(name, ""); - STRCPY(name + 5, fname + 3); - } - } - - for (i = 0; i < gap->ga_len; ++i) - { - /* Skip entries that are not useful or are for a line that is beyond - * an already found breakpoint. */ - bp = &DEBUGGY(gap, i); - if (((bp->dbg_type == DBG_FILE) == file && - bp->dbg_type != DBG_EXPR && ( -#ifdef FEAT_PROFILE - gap == &prof_ga || -#endif - (bp->dbg_lnum > after && (lnum == 0 || bp->dbg_lnum < lnum))))) - { - /* - * Save the value of got_int and reset it. We don't want a - * previous interruption cancel matching, only hitting CTRL-C - * while matching should abort it. - */ - prev_got_int = got_int; - got_int = FALSE; - if (vim_regexec_prog(&bp->dbg_prog, FALSE, name, (colnr_T)0)) - { - lnum = bp->dbg_lnum; - if (fp != NULL) - *fp = bp->dbg_forceit; - } - got_int |= prev_got_int; - } -#ifdef FEAT_EVAL - else if (bp->dbg_type == DBG_EXPR) - { - typval_T *tv; - int line = FALSE; - - prev_got_int = got_int; - got_int = FALSE; - - tv = eval_expr(bp->dbg_name, NULL); - 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) - { - typval_T *v; - - line = TRUE; - debug_oldval = typval_tostring(bp->dbg_val); - /* Need to evaluate again, typval_compare() overwrites - * "tv". */ - v = eval_expr(bp->dbg_name, NULL); - debug_newval = typval_tostring(v); - free_tv(bp->dbg_val); - bp->dbg_val = v; - } - free_tv(tv); - } - } - else if (bp->dbg_val != NULL) - { - debug_oldval = typval_tostring(bp->dbg_val); - debug_newval = typval_tostring(NULL); - free_tv(bp->dbg_val); - bp->dbg_val = NULL; - line = TRUE; - } - - if (line) - { - lnum = after > 0 ? after : 1; - break; - } - - got_int |= prev_got_int; - } -#endif - } - if (name != fname) - vim_free(name); - - return lnum; -} - -/* - * Called when a breakpoint was encountered. - */ - void -dbg_breakpoint(char_u *name, linenr_T lnum) -{ - /* We need to check if this line is actually executed in do_one_cmd() */ - debug_breakpoint_name = name; - debug_breakpoint_lnum = lnum; -} - - # if defined(FEAT_PROFILE) || defined(FEAT_RELTIME) || defined(PROTO) /* * Store the current time in "tm". diff --git a/src/proto.h b/src/proto.h --- a/src/proto.h +++ b/src/proto.h @@ -68,6 +68,7 @@ extern int _stricoll(char *a, char *b); # ifdef FEAT_CSCOPE # include "if_cscope.pro" # endif +# include "debugger.pro" # include "dict.pro" # include "diff.pro" # include "digraph.pro" diff --git a/src/proto/debugger.pro b/src/proto/debugger.pro new file mode 100644 --- /dev/null +++ b/src/proto/debugger.pro @@ -0,0 +1,14 @@ +/* debugger.c */ +int has_watchexpr(void); +void do_debug(char_u *cmd); +void ex_debug(exarg_T *eap); +void dbg_check_breakpoint(exarg_T *eap); +int dbg_check_skipped(exarg_T *eap); +void ex_breakadd(exarg_T *eap); +void ex_debuggreedy(exarg_T *eap); +void ex_breakdel(exarg_T *eap); +void ex_breaklist(exarg_T *eap); +linenr_T dbg_find_breakpoint(int file, char_u *fname, linenr_T after); +int has_profiling(int file, char_u *fname, int *fp); +void dbg_breakpoint(char_u *name, linenr_T lnum); +/* vim: set ft=c : */ diff --git a/src/proto/ex_cmds2.pro b/src/proto/ex_cmds2.pro --- a/src/proto/ex_cmds2.pro +++ b/src/proto/ex_cmds2.pro @@ -1,16 +1,4 @@ /* ex_cmds2.c */ -int has_watchexpr(void); -void do_debug(char_u *cmd); -void ex_debug(exarg_T *eap); -void dbg_check_breakpoint(exarg_T *eap); -int dbg_check_skipped(exarg_T *eap); -void ex_breakadd(exarg_T *eap); -void ex_debuggreedy(exarg_T *eap); -void ex_breakdel(exarg_T *eap); -void ex_breaklist(exarg_T *eap); -linenr_T dbg_find_breakpoint(int file, char_u *fname, linenr_T after); -int has_profiling(int file, char_u *fname, int *fp); -void dbg_breakpoint(char_u *name, linenr_T lnum); void profile_start(proftime_T *tm); void profile_end(proftime_T *tm); void profile_sub(proftime_T *tm, proftime_T *tm2); diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -772,6 +772,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1195, +/**/ 1194, /**/ 1193,