diff options
| author | Eliseo Martínez <eliseomarmol@gmail.com> | 2014-05-12 02:25:17 +0200 |
|---|---|---|
| committer | Eliseo Martínez <eliseomarmol@gmail.com> | 2014-05-15 20:46:01 +0200 |
| commit | da51dc9cf202772f60bd2da975dbef257bd9237c (patch) | |
| tree | 5c16b93238a153f55634e9323077f30c8133970c /src/nvim/ex_cmds2.c | |
| parent | ffe61e5ba1721340ca51d56bae3ddaca415fb5bc (diff) | |
| download | rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.tar.gz rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.tar.bz2 rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.zip | |
Introduce nvim namespace: Move files.
Move files from src/ to src/nvim/.
- src/nvim/ becomes the new root dir for nvim executable sources.
- src/libnvim/ is planned to become root dir of the neovim library.
Diffstat (limited to 'src/nvim/ex_cmds2.c')
| -rw-r--r-- | src/nvim/ex_cmds2.c | 3541 |
1 files changed, 3541 insertions, 0 deletions
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c new file mode 100644 index 0000000000..0837e5386e --- /dev/null +++ b/src/nvim/ex_cmds2.c @@ -0,0 +1,3541 @@ +/* + * 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. + */ + +/* + * ex_cmds2.c: some more functions for command line commands + */ + +#include <string.h> + +#include "vim.h" +#include "version_defs.h" +#include "ex_cmds2.h" +#include "buffer.h" +#include "charset.h" +#include "eval.h" +#include "ex_cmds.h" +#include "ex_docmd.h" +#include "ex_eval.h" +#include "ex_getln.h" +#include "fileio.h" +#include "getchar.h" +#include "mark.h" +#include "mbyte.h" +#include "message.h" +#include "misc1.h" +#include "misc2.h" +#include "garray.h" +#include "memory.h" +#include "move.h" +#include "normal.h" +#include "option.h" +#include "os_unix.h" +#include "path.h" +#include "quickfix.h" +#include "regexp.h" +#include "screen.h" +#include "term.h" +#include "undo.h" +#include "window.h" +#include "os/os.h" +#include "os/shell.h" + +static void cmd_source(char_u *fname, exarg_T *eap); + +/* Growarray to store info about already sourced scripts. + * Also store the dev/ino, so that we don't have to stat() each + * script when going through the list. */ +typedef struct scriptitem_S { + char_u *sn_name; + int sn_dev_valid; + uint64_t sn_dev; + uint64_t sn_ino; + int sn_prof_on; /* TRUE when script is/was profiled */ + int sn_pr_force; /* forceit: profile functions in this script */ + proftime_T sn_pr_child; /* time set when going into first child */ + int sn_pr_nest; /* nesting for sn_pr_child */ + /* profiling the script as a whole */ + int sn_pr_count; /* nr of times sourced */ + proftime_T sn_pr_total; /* time spent in script + children */ + proftime_T sn_pr_self; /* time spent in script itself */ + proftime_T sn_pr_start; /* time at script start */ + proftime_T sn_pr_children; /* time in children after script start */ + /* profiling the script per line */ + garray_T sn_prl_ga; /* things stored for every line */ + proftime_T sn_prl_start; /* start time for current line */ + proftime_T sn_prl_children; /* time spent in children for this line */ + proftime_T sn_prl_wait; /* wait start time for current line */ + int sn_prl_idx; /* index of line being timed; -1 if none */ + int sn_prl_execed; /* line being timed was executed */ +} scriptitem_T; + +static garray_T script_items = {0, 0, sizeof(scriptitem_T), 4, NULL}; +#define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1]) + +/* Struct used in sn_prl_ga for every line of a script. */ +typedef struct sn_prl_S { + int snp_count; /* nr of times line was executed */ + proftime_T sn_prl_total; /* time spent in a line + children */ + proftime_T sn_prl_self; /* time spent in a line itself */ +} sn_prl_T; + +# define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)]) + +static int debug_greedy = FALSE; /* batch mode debugging: don't save + and restore typeahead. */ + +/* + * 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 + + + /* 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; + + if (!debug_did_msg) + MSG(_("Entering Debug mode. Type \"cont\" to continue.")); + if (sourcing_name != NULL) + msg(sourcing_name); + if (sourcing_lnum != 0) + smsg((char_u *)_("line %" PRId64 ": %s"), (int64_t)sourcing_lnum, cmd); + else + smsg((char_u *)_("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; + } + + 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; + 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 = CMD_FINISH; + tail = "inish"; + break; + case 'q': last_cmd = CMD_QUIT; + tail = "uit"; + break; + case 'i': last_cmd = CMD_INTERRUPT; + tail = "nterrupt"; + 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 = 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; + } + 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; + + free(cmdline); + } + lines_left = Rows - 1; + } + 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; + 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; +} + +/* + * ":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 "<SNR>" */ + if (debug_breakpoint_name[0] == K_SPECIAL + && debug_breakpoint_name[1] == KS_EXTRA + && debug_breakpoint_name[2] == (int)KE_SNR) + p = (char_u *)"<SNR>"; + else + p = (char_u *)""; + smsg((char_u *)_("Breakpoint in \"%s%s\" line %" PRId64), + p, + debug_breakpoint_name + (*p == NUL ? 0 : 3), + (int64_t)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 or DBG_FILE */ + char_u *dbg_name; /* function or file name */ + regprog_T *dbg_prog; /* regexp program */ + linenr_T dbg_lnum; /* line number in function or file */ + int dbg_forceit; /* ! used */ +}; + +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 */ + +/* Profiling uses file and func names similar to breakpoints. */ +static garray_T prof_ga = {0, 0, sizeof(struct debuggy), 4, NULL}; +#define DBG_FUNC 1 +#define DBG_FILE 2 + +static int dbg_parsearg(char_u *arg, garray_T *gap); +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; + + ga_grow(gap, 1); + + 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 ( + gap != &prof_ga && + STRNCMP(p, "here", 4) == 0) { + if (curbuf->b_ffname == NULL) { + EMSG(_(e_noname)); + return FAIL; + } + bp->dbg_type = DBG_FILE; + here = TRUE; + } else { + EMSG2(_(e_invarg2), p); + return FAIL; + } + p = skipwhite(p + 4); + + /* Find optional line number. */ + if (here) + bp->dbg_lnum = curwin->w_cursor.lnum; + else if ( + gap != &prof_ga && + 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)) { + EMSG2(_(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 { + /* 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); + free(q); + if (p == NULL) + return FAIL; + if (*p != '*') { + bp->dbg_name = fix_fname(p); + free(p); + } else + bp->dbg_name = p; + } + + if (bp->dbg_name == NULL) + return FAIL; + return OK; +} + +/* + * ":breakadd". + */ +void ex_breakadd(exarg_T *eap) +{ + struct debuggy *bp; + char_u *pat; + garray_T *gap; + + gap = &dbg_breakp; + if (eap->cmdidx == CMD_profile) + gap = &prof_ga; + + if (dbg_parsearg(eap->arg, gap) == OK) { + 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); + free(pat); + } + if (pat == NULL || bp->dbg_prog == NULL) + free(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; + } + } +} + +/* + * ":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) { + gap = &prof_ga; + } + + 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} [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; + } + } + free(bp->dbg_name); + } + + if (todel < 0) + EMSG2(_("E161: Breakpoint not found: %s"), eap->arg); + else { + while (gap->ga_len > 0) { + free(DEBUGGY(gap, todel).dbg_name); + vim_regfree(DEBUGGY(gap, todel).dbg_prog); + --gap->ga_len; + if (todel < gap->ga_len) + memmove(&DEBUGGY(gap, todel), &DEBUGGY(gap, todel + 1), + (gap->ga_len - todel) * sizeof(struct debuggy)); + if (eap->cmdidx == CMD_breakdel) + ++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) +{ + 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); + smsg((char_u *)_("%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); + } +} + +/* + * 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); +} + +/* + * 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; +} + +/* + * 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; + regmatch_T regmatch; + 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 "<SNR>". */ + if (!file && fname[0] == K_SPECIAL) { + name = xmalloc(STRLEN(fname) + 3); + STRCPY(name, "<SNR>"); + 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 && ( + gap == &prof_ga || + (bp->dbg_lnum > after && (lnum == 0 || bp->dbg_lnum < lnum))))) { + regmatch.regprog = bp->dbg_prog; + regmatch.rm_ic = FALSE; + /* + * 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(®match, name, (colnr_T)0)) { + lnum = bp->dbg_lnum; + if (fp != NULL) + *fp = bp->dbg_forceit; + } + got_int |= prev_got_int; + } + } + if (name != fname) + 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; +} + + +/* + * Store the current time in "tm". + */ +void profile_start(tm) +proftime_T *tm; +{ + gettimeofday(tm, NULL); +} + +/* + * Compute the elapsed time from "tm" till now and store in "tm". + */ +void profile_end(tm) +proftime_T *tm; +{ + proftime_T now; + + gettimeofday(&now, NULL); + tm->tv_usec = now.tv_usec - tm->tv_usec; + tm->tv_sec = now.tv_sec - tm->tv_sec; + if (tm->tv_usec < 0) { + tm->tv_usec += 1000000; + --tm->tv_sec; + } +} + +/* + * Subtract the time "tm2" from "tm". + */ +void profile_sub(tm, tm2) +proftime_T *tm, *tm2; +{ + tm->tv_usec -= tm2->tv_usec; + tm->tv_sec -= tm2->tv_sec; + if (tm->tv_usec < 0) { + tm->tv_usec += 1000000; + --tm->tv_sec; + } +} + +/* + * Return a string that represents the time in "tm". + * Uses a static buffer! + */ +char * profile_msg(tm) +proftime_T *tm; +{ + static char buf[50]; + + sprintf(buf, "%3ld.%06ld", (long)tm->tv_sec, (long)tm->tv_usec); + return buf; +} + +/* + * Put the time "msec" past now in "tm". + */ +void profile_setlimit(msec, tm) +long msec; +proftime_T *tm; +{ + if (msec <= 0) /* no limit */ + profile_zero(tm); + else { + long usec; + + gettimeofday(tm, NULL); + usec = (long)tm->tv_usec + (long)msec * 1000; + tm->tv_usec = usec % 1000000L; + tm->tv_sec += usec / 1000000L; + } +} + +/* + * Return TRUE if the current time is past "tm". + */ +int profile_passed_limit(tm) +proftime_T *tm; +{ + proftime_T now; + + if (tm->tv_sec == 0) /* timer was not set */ + return FALSE; + gettimeofday(&now, NULL); + return now.tv_sec > tm->tv_sec + || (now.tv_sec == tm->tv_sec && now.tv_usec > tm->tv_usec); +} + +/* + * Set the time in "tm" to zero. + */ +void profile_zero(tm) +proftime_T *tm; +{ + tm->tv_usec = 0; + tm->tv_sec = 0; +} + + +#include <math.h> + +/* + * Divide the time "tm" by "count" and store in "tm2". + */ +void profile_divide(tm, count, tm2) +proftime_T *tm; +proftime_T *tm2; +int count; +{ + if (count == 0) + profile_zero(tm2); + else { + double usec = (tm->tv_sec * 1000000.0 + tm->tv_usec) / count; + + tm2->tv_sec = floor(usec / 1000000.0); + tm2->tv_usec = vim_round(usec - (tm2->tv_sec * 1000000.0)); + } +} + +/* + * Functions for profiling. + */ +static void script_do_profile(scriptitem_T *si); +static void script_dump_profile(FILE *fd); +static proftime_T prof_wait_time; + +/* + * Add the time "tm2" to "tm". + */ +void profile_add(tm, tm2) +proftime_T *tm, *tm2; +{ + tm->tv_usec += tm2->tv_usec; + tm->tv_sec += tm2->tv_sec; + if (tm->tv_usec >= 1000000) { + tm->tv_usec -= 1000000; + ++tm->tv_sec; + } +} + +/* + * Add the "self" time from the total time and the children's time. + */ +void profile_self(self, total, children) +proftime_T *self, *total, *children; +{ + /* Check that the result won't be negative. Can happen with recursive + * calls. */ + if (total->tv_sec < children->tv_sec + || (total->tv_sec == children->tv_sec + && total->tv_usec <= children->tv_usec)) + return; + profile_add(self, total); + profile_sub(self, children); +} + +/* + * Get the current waittime. + */ +void profile_get_wait(tm) +proftime_T *tm; +{ + *tm = prof_wait_time; +} + +/* + * Subtract the passed waittime since "tm" from "tma". + */ +void profile_sub_wait(tm, tma) +proftime_T *tm, *tma; +{ + proftime_T tm3 = prof_wait_time; + + profile_sub(&tm3, tm); + profile_sub(tma, &tm3); +} + +/* + * Return TRUE if "tm1" and "tm2" are equal. + */ +int profile_equal(tm1, tm2) +proftime_T *tm1, *tm2; +{ + return tm1->tv_usec == tm2->tv_usec && tm1->tv_sec == tm2->tv_sec; +} + +/* + * Return <0, 0 or >0 if "tm1" < "tm2", "tm1" == "tm2" or "tm1" > "tm2" + */ +int profile_cmp(tm1, tm2) +const proftime_T *tm1, *tm2; +{ + if (tm1->tv_sec == tm2->tv_sec) + return tm2->tv_usec - tm1->tv_usec; + return tm2->tv_sec - tm1->tv_sec; +} + +static char_u *profile_fname = NULL; +static proftime_T pause_time; + +/* + * ":profile cmd args" + */ +void ex_profile(exarg_T *eap) +{ + char_u *e; + int len; + + e = skiptowhite(eap->arg); + len = (int)(e - eap->arg); + e = skipwhite(e); + + if (len == 5 && STRNCMP(eap->arg, "start", 5) == 0 && *e != NUL) { + free(profile_fname); + profile_fname = vim_strsave(e); + do_profiling = PROF_YES; + profile_zero(&prof_wait_time); + set_vim_var_nr(VV_PROFILING, 1L); + } else if (do_profiling == PROF_NONE) + EMSG(_("E750: First use \":profile start {fname}\"")); + else if (STRCMP(eap->arg, "pause") == 0) { + if (do_profiling == PROF_YES) + profile_start(&pause_time); + do_profiling = PROF_PAUSED; + } else if (STRCMP(eap->arg, "continue") == 0) { + if (do_profiling == PROF_PAUSED) { + profile_end(&pause_time); + profile_add(&prof_wait_time, &pause_time); + } + do_profiling = PROF_YES; + } else { + /* The rest is similar to ":breakadd". */ + ex_breakadd(eap); + } +} + +/* Command line expansion for :profile. */ +static enum { + PEXP_SUBCMD, /* expand :profile sub-commands */ + PEXP_FUNC /* expand :profile func {funcname} */ +} pexpand_what; + +static char *pexpand_cmds[] = { + "start", +#define PROFCMD_START 0 + "pause", +#define PROFCMD_PAUSE 1 + "continue", +#define PROFCMD_CONTINUE 2 + "func", +#define PROFCMD_FUNC 3 + "file", +#define PROFCMD_FILE 4 + NULL +#define PROFCMD_LAST 5 +}; + +/* + * Function given to ExpandGeneric() to obtain the profile command + * specific expansion. + */ +char_u *get_profile_name(expand_T *xp, int idx) +{ + switch (pexpand_what) { + case PEXP_SUBCMD: + return (char_u *)pexpand_cmds[idx]; + /* case PEXP_FUNC: TODO */ + default: + return NULL; + } +} + +/* + * Handle command line completion for :profile command. + */ +void set_context_in_profile_cmd(expand_T *xp, char_u *arg) +{ + char_u *end_subcmd; + + /* Default: expand subcommands. */ + xp->xp_context = EXPAND_PROFILE; + pexpand_what = PEXP_SUBCMD; + xp->xp_pattern = arg; + + end_subcmd = skiptowhite(arg); + if (*end_subcmd == NUL) + return; + + if (end_subcmd - arg == 5 && STRNCMP(arg, "start", 5) == 0) { + xp->xp_context = EXPAND_FILES; + xp->xp_pattern = skipwhite(end_subcmd); + return; + } + + /* TODO: expand function names after "func" */ + xp->xp_context = EXPAND_NOTHING; +} + +/* + * Dump the profiling info. + */ +void profile_dump(void) +{ + FILE *fd; + + if (profile_fname != NULL) { + fd = mch_fopen((char *)profile_fname, "w"); + if (fd == NULL) + EMSG2(_(e_notopen), profile_fname); + else { + script_dump_profile(fd); + func_dump_profile(fd); + fclose(fd); + } + } +} + +/* + * Start profiling script "fp". + */ +static void script_do_profile(scriptitem_T *si) +{ + si->sn_pr_count = 0; + profile_zero(&si->sn_pr_total); + profile_zero(&si->sn_pr_self); + + ga_init(&si->sn_prl_ga, sizeof(sn_prl_T), 100); + si->sn_prl_idx = -1; + si->sn_prof_on = TRUE; + si->sn_pr_nest = 0; +} + +/* + * save time when starting to invoke another script or function. + */ +void script_prof_save(tm) +proftime_T *tm; /* place to store wait time */ +{ + scriptitem_T *si; + + if (current_SID > 0 && current_SID <= script_items.ga_len) { + si = &SCRIPT_ITEM(current_SID); + if (si->sn_prof_on && si->sn_pr_nest++ == 0) + profile_start(&si->sn_pr_child); + } + profile_get_wait(tm); +} + +/* + * Count time spent in children after invoking another script or function. + */ +void script_prof_restore(tm) +proftime_T *tm; +{ + scriptitem_T *si; + + if (current_SID > 0 && current_SID <= script_items.ga_len) { + si = &SCRIPT_ITEM(current_SID); + if (si->sn_prof_on && --si->sn_pr_nest == 0) { + profile_end(&si->sn_pr_child); + profile_sub_wait(tm, &si->sn_pr_child); /* don't count wait time */ + profile_add(&si->sn_pr_children, &si->sn_pr_child); + profile_add(&si->sn_prl_children, &si->sn_pr_child); + } + } +} + +static proftime_T inchar_time; + +/* + * Called when starting to wait for the user to type a character. + */ +void prof_inchar_enter(void) +{ + profile_start(&inchar_time); +} + +/* + * Called when finished waiting for the user to type a character. + */ +void prof_inchar_exit(void) +{ + profile_end(&inchar_time); + profile_add(&prof_wait_time, &inchar_time); +} + +/* + * Dump the profiling results for all scripts in file "fd". + */ +static void script_dump_profile(FILE *fd) +{ + int id; + scriptitem_T *si; + int i; + FILE *sfd; + sn_prl_T *pp; + + for (id = 1; id <= script_items.ga_len; ++id) { + si = &SCRIPT_ITEM(id); + if (si->sn_prof_on) { + fprintf(fd, "SCRIPT %s\n", si->sn_name); + if (si->sn_pr_count == 1) + fprintf(fd, "Sourced 1 time\n"); + else + fprintf(fd, "Sourced %d times\n", si->sn_pr_count); + fprintf(fd, "Total time: %s\n", profile_msg(&si->sn_pr_total)); + fprintf(fd, " Self time: %s\n", profile_msg(&si->sn_pr_self)); + fprintf(fd, "\n"); + fprintf(fd, "count total (s) self (s)\n"); + + sfd = mch_fopen((char *)si->sn_name, "r"); + if (sfd == NULL) + fprintf(fd, "Cannot open file!\n"); + else { + for (i = 0; i < si->sn_prl_ga.ga_len; ++i) { + if (vim_fgets(IObuff, IOSIZE, sfd)) + break; + pp = &PRL_ITEM(si, i); + if (pp->snp_count > 0) { + fprintf(fd, "%5d ", pp->snp_count); + if (profile_equal(&pp->sn_prl_total, &pp->sn_prl_self)) + fprintf(fd, " "); + else + fprintf(fd, "%s ", profile_msg(&pp->sn_prl_total)); + fprintf(fd, "%s ", profile_msg(&pp->sn_prl_self)); + } else + fprintf(fd, " "); + f |