summaryrefslogtreecommitdiffstats
path: root/src/ex_cmds2.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ex_cmds2.c')
-rw-r--r--src/ex_cmds2.c5711
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(&regmatch, 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(&regmatch, 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