diff options
Diffstat (limited to 'src/ex_cmds2.c')
-rw-r--r-- | src/ex_cmds2.c | 5711 |
1 files changed, 5711 insertions, 0 deletions
diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c new file mode 100644 index 0000000000..8ec8be71a4 --- /dev/null +++ b/src/ex_cmds2.c @@ -0,0 +1,5711 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * 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 + */ + +#if defined(WIN32) && defined(FEAT_CSCOPE) +# include <io.h> +#endif + +#include "vim.h" + +#if defined(WIN32) && defined(FEAT_CSCOPE) +# include <fcntl.h> +#endif + +#include "version.h" + +static void cmd_source __ARGS((char_u *fname, exarg_T *eap)); + +#if defined(FEAT_EVAL) || defined(PROTO) +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(cmd) + 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; +# ifdef FEAT_EX_EXTRA + int save_ex_normal_busy; +# endif + 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 + +#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; +#ifdef FEAT_SNIFF + want_sniff_request = 0; /* No K_SNIFF wanted */ +#endif + + 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 %ld: %s"), (long)sourcing_lnum, cmd); + else + msg_str((char_u *)_("cmd: %s"), cmd); + + /* + * Repeat getting a command and executing it. + */ + for (;;) + { + msg_scroll = TRUE; + need_wait_return = FALSE; +#ifdef FEAT_SNIFF + ProcessSniffRequests(); +#endif + /* 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. */ +# ifdef FEAT_EX_EXTRA + save_ex_normal_busy = ex_normal_busy; + ex_normal_busy = 0; +# endif + if (!debug_greedy) + save_typeahead(&typeaheadbuf); + + cmdline = getcmdline_prompt('>', NULL, 0); + + if (!debug_greedy) + restore_typeahead(&typeaheadbuf); +# ifdef FEAT_EX_EXTRA + ex_normal_busy = save_ex_normal_busy; +# endif + + 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; + + vim_free(cmdline); + } + 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; + 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(eap) + 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(eap) + 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 %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(eap) + 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 */ +}; + +static garray_T dbg_breakp = {0, 0, sizeof(struct debuggy), 4, NULL}; +#define BREAKP(idx) (((struct debuggy *)dbg_breakp.ga_data)[idx]) +static int last_breakp = 0; /* nr of last defined breakpoint */ + +#define DBG_FUNC 1 +#define DBG_FILE 2 + +static int dbg_parsearg __ARGS((char_u *arg)); + +/* + * Parse the arguments of ":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(arg) + char_u *arg; +{ + char_u *p = arg; + char_u *q; + struct debuggy *bp; + + if (ga_grow(&dbg_breakp, 1) == FAIL) + return FAIL; + bp = &BREAKP(dbg_breakp.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 + { + EMSG2(_(e_invarg2), p); + return FAIL; + } + p = skipwhite(p + 4); + + /* Find optional line number. */ + if (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 (*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 + { + /* 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". */ +#ifdef RISCOS + q = mch_munge_fname(p); +#else + q = expand_env_save(p); +#endif + if (q == NULL) + return FAIL; +#ifdef RISCOS + p = mch_munge_fname(q); +#else + p = expand_env_save(q); +#endif + vim_free(q); + if (p == NULL) + return FAIL; + bp->dbg_name = fix_fname(p); + vim_free(p); +#ifdef MACOS_CLASSIC + if (bp->dbg_name != NULL) + slash_n_colon_adjust(bp->dbg_name); +#endif + } + + if (bp->dbg_name == NULL) + return FAIL; + return OK; +} + +/* + * ":breakadd". + */ + void +ex_breakadd(eap) + exarg_T *eap; +{ + struct debuggy *bp; + char_u *pat; + + if (dbg_parsearg(eap->arg) == OK) + { + bp = &BREAKP(dbg_breakp.ga_len); + 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; + BREAKP(dbg_breakp.ga_len++).dbg_nr = ++last_breakp; + --dbg_breakp.ga_room; + ++debug_tick; + } + } +} + +/* + * ":debuggreedy". + */ + void +ex_debuggreedy(eap) + exarg_T *eap; +{ + if (eap->addr_count == 0 || eap->line2 != 0) + debug_greedy = TRUE; + else + debug_greedy = FALSE; +} + +/* + * ":breakdel". + */ + void +ex_breakdel(eap) + exarg_T *eap; +{ + struct debuggy *bp, *bpi; + int nr; + int todel = -1; + int i; + linenr_T best_lnum = 0; + + if (vim_isdigit(*eap->arg)) + { + /* ":breakdel {nr}" */ + nr = atol((char *)eap->arg); + for (i = 0; i < dbg_breakp.ga_len; ++i) + if (BREAKP(i).dbg_nr == nr) + { + todel = i; + break; + } + } + else + { + /* ":breakdel {func|file} [lnum] {name}" */ + if (dbg_parsearg(eap->arg) == FAIL) + return; + bp = &BREAKP(dbg_breakp.ga_len); + for (i = 0; i < dbg_breakp.ga_len; ++i) + { + bpi = &BREAKP(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) + EMSG2(_("E161: Breakpoint not found: %s"), eap->arg); + else + { + vim_free(BREAKP(todel).dbg_name); + vim_free(BREAKP(todel).dbg_prog); + --dbg_breakp.ga_len; + ++dbg_breakp.ga_room; + if (todel < dbg_breakp.ga_len) + mch_memmove(&BREAKP(todel), &BREAKP(todel + 1), + (dbg_breakp.ga_len - todel) * sizeof(struct debuggy)); + ++debug_tick; + } +} + +/* + * ":breaklist". + */ +/*ARGSUSED*/ + void +ex_breaklist(eap) + 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); + smsg((char_u *)_("%3d %s %s line %ld"), + bp->dbg_nr, + bp->dbg_type == DBG_FUNC ? "func" : "file", + bp->dbg_name, + (long)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(file, fname, after) + int file; /* TRUE for a file, FALSE for a function */ + char_u *fname; /* file or function name */ + linenr_T after; /* after this line number */ +{ + struct debuggy *bp; + int i; + linenr_T lnum = 0; + regmatch_T regmatch; + char_u *name = fname; + int prev_got_int; + + /* Replace K_SNR in function name with "<SNR>". */ + if (!file && fname[0] == K_SPECIAL) + { + name = alloc((unsigned)STRLEN(fname) + 3); + if (name == NULL) + name = fname; + else + { + STRCPY(name, "<SNR>"); + STRCPY(name + 5, fname + 3); + } + } + + for (i = 0; i < dbg_breakp.ga_len; ++i) + { + /* skip entries that are not useful or are for a line that is beyond + * an already found breakpoint */ + bp = &BREAKP(i); + if ((bp->dbg_type == DBG_FILE) == file + && 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; + got_int |= prev_got_int; + } + } + if (name != fname) + vim_free(name); + + return lnum; +} + +/* + * Called when a breakpoint was encountered. + */ + void +dbg_breakpoint(name, lnum) + 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 + +/* + * If 'autowrite' option set, try to write the file. + * Careful: autocommands may make "buf" invalid! + * + * return FAIL for failure, OK otherwise + */ + int +autowrite(buf, forceit) + buf_T *buf; + int forceit; +{ + if (!(p_aw || p_awa) || !p_write +#ifdef FEAT_QUICKFIX + /* never autowrite a "nofile" or "nowrite" buffer */ + || bt_dontwrite(buf) +#endif + || (!forceit && buf->b_p_ro) || buf->b_ffname == NULL) + return FAIL; + return buf_write_all(buf, forceit); +} + +/* + * flush all buffers, except the ones that are readonly + */ + void +autowrite_all() +{ + buf_T *buf; + + if (!(p_aw || p_awa) || !p_write) + return; + for (buf = firstbuf; buf; buf = buf->b_next) + if (bufIsChanged(buf) && !buf->b_p_ro) + { + (void)buf_write_all(buf, FALSE); +#ifdef FEAT_AUTOCMD + /* an autocommand may have deleted the buffer */ + if (!buf_valid(buf)) + buf = firstbuf; +#endif + } +} + +/* + * return TRUE if buffer was changed and cannot be abandoned. + */ +/*ARGSUSED*/ + int +check_changed(buf, checkaw, mult_win, forceit, allbuf) + buf_T *buf; + int checkaw; /* do autowrite if buffer was changed */ + int mult_win; /* check also when several wins for the buf */ + int forceit; + int allbuf; /* may write all buffers */ +{ + if ( !forceit + && bufIsChanged(buf) + && (mult_win || buf->b_nwindows <= 1) + && (!checkaw || autowrite(buf, forceit) == FAIL)) + { +#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) + if ((p_confirm || cmdmod.confirm) && p_write) + { + buf_T *buf2; + int count = 0; + + if (allbuf) + for (buf2 = firstbuf; buf2 != NULL; buf2 = buf2->b_next) + if (bufIsChanged(buf2) + && (buf2->b_ffname != NULL +# ifdef FEAT_BROWSE + || cmdmod.browse +# endif + )) + ++count; +# ifdef FEAT_AUTOCMD + if (!buf_valid(buf)) + /* Autocommand deleted buffer, oops! It's not changed now. */ + return FALSE; +# endif + dialog_changed(buf, count > 1); +# ifdef FEAT_AUTOCMD + if (!buf_valid(buf)) + /* Autocommand deleted buffer, oops! It's not changed now. */ + return FALSE; +# endif + return bufIsChanged(buf); + } +#endif + EMSG(_(e_nowrtmsg)); + return TRUE; + } + return FALSE; +} + +#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) || defined(PROTO) + +#if defined(FEAT_BROWSE) || defined(PROTO) +/* + * When wanting to write a file without a file name, ask the user for a name. + */ + void +browse_save_fname(buf) + buf_T *buf; +{ + if (buf->b_fname == NULL) + { + char_u *fname; + + fname = do_browse(TRUE, (char_u *)_("Save As"), NULL, NULL, NULL, + NULL, buf); + if (fname != NULL) + { + if (setfname(buf, fname, NULL, TRUE) == OK) + buf->b_flags |= BF_NOTEDITED; + vim_free(fname); + } + } +} +#endif + +/* + * Ask the user what to do when abondoning a changed buffer. + * Must check 'write' option first! + */ + void +dialog_changed(buf, checkall) + buf_T *buf; + int checkall; /* may abandon all changed buffers */ +{ + char_u buff[IOSIZE]; + int ret; + buf_T *buf2; + + dialog_msg(buff, _("Save changes to \"%.*s\"?"), + (buf->b_fname != NULL) ? + buf->b_fname : (char_u *)_("Untitled")); + if (checkall) + ret = vim_dialog_yesnoallcancel(VIM_QUESTION, NULL, buff, 1); + else + ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1); + + if (ret == VIM_YES) + { +#ifdef FEAT_BROWSE + /* May get file name, when there is none */ + browse_save_fname(buf); +#endif + if (buf->b_fname != NULL) /* didn't hit Cancel */ + (void)buf_write_all(buf, FALSE); + } + else if (ret == VIM_NO) + { + unchanged(buf, TRUE); + } + else if (ret == VIM_ALL) + { + /* + * Write all modified files that can be written. + * Skip readonly buffers, these need to be confirmed + * individually. + */ + for (buf2 = firstbuf; buf2 != NULL; buf2 = buf2->b_next) + { + if (bufIsChanged(buf2) + && (buf2->b_ffname != NULL +#ifdef FEAT_BROWSE + || cmdmod.browse +#endif + ) + && !buf2->b_p_ro) + { +#ifdef FEAT_BROWSE + /* May get file name, when there is none */ + browse_save_fname(buf2); +#endif + if (buf2->b_fname != NULL) /* didn't hit Cancel */ + (void)buf_write_all(buf2, FALSE); +#ifdef FEAT_AUTOCMD + /* an autocommand may have deleted the buffer */ + if (!buf_valid(buf2)) + buf2 = firstbuf; +#endif + } + } + } + else if (ret == VIM_DISCARDALL) + { + /* + * mark all buffers as unchanged + */ + for (buf2 = firstbuf; buf2 != NULL; buf2 = buf2->b_next) + unchanged(buf2, TRUE); + } +} +#endif + +/* + * Return TRUE if the buffer "buf" can be abandoned, either by making it + * hidden, autowriting it or unloading it. + */ + int +can_abandon(buf, forceit) + buf_T *buf; + int forceit; +{ + return ( P_HID(buf) + || !bufIsChanged(buf) + || buf->b_nwindows > 1 + || autowrite(buf, forceit) == OK + || forceit); +} + +/* + * Return TRUE if any buffer was changed and cannot be abandoned. + * That changed buffer becomes the current buffer. + */ + int +check_changed_any(hidden) + int hidden; /* Only check hidden buffers */ +{ + buf_T *buf; + int save; +#ifdef FEAT_WINDOWS + win_T *wp; +#endif + + for (;;) + { + /* check curbuf first: if it was changed we can't abandon it */ + if (!hidden && curbufIsChanged()) + buf = curbuf; + else + { + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if ((!hidden || buf->b_nwindows == 0) && bufIsChanged(buf)) + break; + } + if (buf == NULL) /* No buffers changed */ + return FALSE; + + if (check_changed(buf, p_awa, TRUE, FALSE, TRUE) && buf_valid(buf)) + break; /* didn't save - still changes */ + } + + exiting = FALSE; +#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) + /* + * When ":confirm" used, don't give an error message. + */ + if (!(p_confirm || cmdmod.confirm)) +#endif + { + /* There must be a wait_return for this message, do_buffer() + * may cause a redraw. But wait_return() is a no-op when vgetc() + * is busy (Quit used from window menu), then make sure we don't + * cause a scroll up. */ + if (vgetc_busy) + { + msg_row = cmdline_row; + msg_col = 0; + msg_didout = FALSE; + } + if (EMSG2(_("E162: No write since last change for buffer \"%s\""), + buf_spname(buf) != NULL ? (char_u *)buf_spname(buf) : + buf->b_fname)) + { + save = no_wait_return; + no_wait_return = FALSE; + wait_return(FALSE); + no_wait_return = save; + } + } + +#ifdef FEAT_WINDOWS + /* Try to find a window that contains the buffer. */ + if (buf != curbuf) + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_buffer == buf) + { + win_goto(wp); +# ifdef FEAT_AUTOCMD + /* Paranoia: did autocms wipe out the buffer with changes? */ + if (!buf_valid(buf)) + return TRUE; +# endif + break; + } +#endif + + /* Open the changed buffer in the current window. */ + if (buf != curbuf) + set_curbuf(buf, DOBUF_GOTO); + + return TRUE; +} + +/* + * return FAIL if there is no file name, OK if there is one + * give error message for FAIL + */ + int +check_fname() +{ + if (curbuf->b_ffname == NULL) + { + EMSG(_(e_noname)); + return FAIL; + } + return OK; +} + +/* + * flush the contents of a buffer, unless it has no file name + * + * return FAIL for failure, OK otherwise + */ + int +buf_write_all(buf, forceit) + buf_T *buf; + int forceit; +{ + int retval; +#ifdef FEAT_AUTOCMD + buf_T *old_curbuf = curbuf; +#endif + + retval = (buf_write(buf, buf->b_ffname, buf->b_fname, + (linenr_T)1, buf->b_ml.ml_line_count, NULL, + FALSE, forceit, TRUE, FALSE)); +#ifdef FEAT_AUTOCMD + if (curbuf != old_curbuf) + MSG(_("Warning: Entered other buffer unexpectedly (check autocommands)")); +#endif + return retval; +} + +/* + * Code to handle the argument list. + */ + +/* + * Isolate one argument, taking quotes and backticks. + * Changes the argument in-place, puts a NUL after it. + * Quotes are removed, backticks remain. + * Return a pointer to the start of the next argument. + */ + char_u * +do_one_arg(str) + char_u *str; +{ + char_u *p; + int inquote; + int inbacktick; + + inquote = FALSE; + inbacktick = FALSE; + for (p = str; *str; ++str) + { + /* + * for MSDOS et.al. a backslash is part of a file name. + * Only skip ", space and tab. + */ + if (rem_backslash(str)) + { + *p++ = *str++; + *p++ = *str; + } + else + { + /* An item ends at a space not in quotes or backticks */ + if (!inquote && !inbacktick && vim_isspace(*str)) + break; + if (!inquote && *str == '`') + inbacktick ^= TRUE; + if (!inbacktick && *str == '"') + inquote ^= TRUE; + else + *p++ = *str; + } + } + str = skipwhite(str); + *p = NUL; + + return str; +} + +static int do_arglist __ARGS((char_u *str, int what, int after)); +static void alist_check_arg_idx __ARGS((void)); +#ifdef FEAT_LISTCMDS +static int alist_add_list __ARGS((int count, char_u **files, int after)); +#endif +#define AL_SET 1 +#define AL_ADD 2 +#define AL_DEL 3 + +#if defined(FEAT_GUI) || defined(FEAT_CLIENTSERVER) || defined(PROTO) +/* + * Redefine the argument list. + */ + void +set_arglist(str) + char_u *str; +{ + do_arglist(str, AL_SET, 0); +} +#endif + +/* + * "what" == AL_SET: Redefine the argument list to 'str'. + * "what" == AL_ADD: add files in 'str' to the argument list after "after". + * "what" == AL_DEL: remove files in 'str' from the argument list. + * + * Return FAIL for failure, OK otherwise. + */ +/*ARGSUSED*/ + static int +do_arglist(str, what, after) + char_u *str; + int what; + int after; /* 0 means before first one */ +{ + garray_T new_ga; + int exp_count; + char_u **exp_files; + int i; +#ifdef FEAT_LISTCMDS + char_u *p; + int match; +#endif + + /* + * Collect all file name arguments in "new_ga". + */ + ga_init2(&new_ga, (int)sizeof(char_u *), 20); + while (*str) + { + if (ga_grow(&new_ga, 1) == FAIL) + { + ga_clear(&new_ga); + return FAIL; + } + ((char_u **)new_ga.ga_data)[new_ga.ga_len++] = str; + --new_ga.ga_room; + + /* Isolate one argument, change it in-place, put a NUL after it. */ + str = do_one_arg(str); + } + +#ifdef FEAT_LISTCMDS + if (what == AL_DEL) + { + regmatch_T regmatch; + int didone; + + /* + * Delete the items: use each item as a regexp and find a match in the + * argument list. + */ +#ifdef CASE_INSENSITIVE_FILENAME + regmatch.rm_ic = TRUE; /* Always ignore case */ +#else + regmatch.rm_ic = FALSE; /* Never ignore case */ +#endif + for (i = 0; i < new_ga.ga_len && !got_int; ++i) + { + p = ((char_u **)new_ga.ga_data)[i]; + p = file_pat_to_reg_pat(p, NULL, NULL, FALSE); + if (p == NULL) + break; + regmatch.regprog = vim_regcomp(p, p_magic ? RE_MAGIC : 0); + if (regmatch.regprog == NULL) + { + vim_free(p); + break; + } + + didone = FALSE; + for (match = 0; match < ARGCOUNT; ++match) + if (vim_regexec(®match, alist_name(&ARGLIST[match]), + (colnr_T)0)) + { + didone = TRUE; + vim_free(ARGLIST[match].ae_fname); + mch_memmove(ARGLIST + match, ARGLIST + match + 1, + (ARGCOUNT - match - 1) * sizeof(aentry_T)); + --ALIST(curwin)->al_ga.ga_len; + ++ALIST(curwin)->al_ga.ga_room; + if (curwin->w_arg_idx > match) + --curwin->w_arg_idx; + --match; + } + + vim_free(regmatch.regprog); + vim_free(p); + if (!didone) + EMSG2(_(e_nomatch2), ((char_u **)new_ga.ga_data)[i]); + } + ga_clear(&new_ga); + } + else +#endif + { + i = expand_wildcards(new_ga.ga_len, (char_u **)new_ga.ga_data, + &exp_count, &exp_files, EW_DIR|EW_FILE|EW_ADDSLASH|EW_NOTFOUND); + ga_clear(&new_ga); + if (i == FAIL) + return FAIL; + if (exp_count == 0) + { + EMSG(_(e_nomatch)); + return FAIL; + } + +#ifdef FEAT_LISTCMDS + if (what == AL_ADD) + { + (void)alist_add_list(exp_count, exp_files, after); + vim_free(exp_files); + } + else /* what == AL_SET */ +#endif + alist_set(ALIST(curwin), exp_count, exp_files, FALSE); + } + + alist_check_arg_idx(); + + return OK; +} + +/* + * Check the validity of the arg_idx for each other window. + */ + static void +alist_check_arg_idx() +{ +#ifdef FEAT_WINDOWS + win_T *win; + + for (win = firstwin; win != NULL; win = win->w_next) + if (win->w_alist == curwin->w_alist) + check_arg_idx(win); +#else + check_arg_idx(curwin); +#endif +} + +/* + * Check if window "win" is editing the w_arg_idx file in its argument list. + */ + void +check_arg_idx(win) + win_T *win; +{ + if (WARGCOUNT(win) > 1 + && (win->w_arg_idx >= WARGCOUNT(win) + || (win->w_buffer->b_fnum + != WARGLIST(win)[win->w_arg_idx].ae_fnum + && (win->w_buffer->b_ffname == NULL + || !(fullpathcmp( + alist_name(&WARGLIST(win)[win->w_arg_idx]), + win->w_buffer->b_ffname, TRUE) & FPC_SAME))))) + { + /* We are not editing the current entry in the argument list. + * Set "arg_had_last" if we are editing the last one. */ + win->w_arg_idx_invalid = TRUE; + if (win->w_arg_idx != WARGCOUNT(win) - 1 + && arg_had_last == FALSE +#ifdef FEAT_WINDOWS + && ALIST(win) == &global_alist +#endif + && GARGCOUNT > 0 + && win->w_arg_idx < GARGCOUNT + && (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum + || (win->w_buffer->b_ffname != NULL + && (fullpathcmp(alist_name(&GARGLIST[GARGCOUNT - 1]), + win->w_buffer->b_ffname, TRUE) & FPC_SAME)))) + arg_had_last = TRUE; + } + else + { + /* We are editing the current entry in the argument list. + * Set "arg_had_last" if it's also the last one */ + win->w_arg_idx_invalid = FALSE; + if (win->w_arg_idx == WARGCOUNT(win) - 1 +#ifdef FEAT_WINDOWS + && win->w_alist == &global_alist +#endif + ) + arg_had_last = TRUE; + } +} + +/* + * ":args", ":argslocal" and ":argsglobal". + */ + void +ex_args(eap) + exarg_T *eap; +{ + int i; + + if (eap->cmdidx != CMD_args) + { +#if defined(FEAT_WINDOWS) && defined(FEAT_LISTCMDS) + alist_unlink(ALIST(curwin)); + if (eap->cmdidx == CMD_argglobal) + ALIST(curwin) = &global_alist; + else /* eap->cmdidx == CMD_arglocal */ + alist_new(); +#else + ex_ni(eap); + return; +#endif + } + + if (!ends_excmd(*eap->arg)) + { + /* + * ":args file ..": define new argument list, handle like ":next" + * Also for ":argslocal file .." and ":argsglobal file ..". + */ + ex_next(eap); + } + else +#if defined(FEAT_WINDOWS) && defined(FEAT_LISTCMDS) + if (eap->cmdidx == CMD_args) +#endif + { + /* + * ":args": list arguments. + */ + if (ARGCOUNT > 0) + { + /* Overwrite the command, for a short list there is no scrolling + * required and no wait_return(). */ + gotocmdline(TRUE); + for (i = 0; i < ARGCOUNT; ++i) + { + if (i == curwin->w_arg_idx) + msg_putcha |