summaryrefslogtreecommitdiffstats
path: root/src/ex_cmds.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ex_cmds.c')
-rw-r--r--src/ex_cmds.c6198
1 files changed, 6198 insertions, 0 deletions
diff --git a/src/ex_cmds.c b/src/ex_cmds.c
new file mode 100644
index 0000000000..e6036ca342
--- /dev/null
+++ b/src/ex_cmds.c
@@ -0,0 +1,6198 @@
+/* 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_cmds.c: some functions for command line commands
+ */
+
+#include "vim.h"
+#include "version.h"
+
+#ifdef FEAT_EX_EXTRA
+static int linelen __ARGS((int *has_tab));
+#endif
+static void do_filter __ARGS((linenr_T line1, linenr_T line2, exarg_T *eap, char_u *cmd, int do_in, int do_out));
+#ifdef FEAT_VIMINFO
+static char_u *viminfo_filename __ARGS((char_u *));
+static void do_viminfo __ARGS((FILE *fp_in, FILE *fp_out, int want_info, int want_marks, int force_read));
+static int viminfo_encoding __ARGS((vir_T *virp));
+static int read_viminfo_up_to_marks __ARGS((vir_T *virp, int forceit, int writing));
+#endif
+
+static int check_overwrite __ARGS((exarg_T *eap, buf_T *buf, char_u *fname, char_u *ffname, int other));
+static int check_readonly __ARGS((int *forceit, buf_T *buf));
+#ifdef FEAT_AUTOCMD
+static void delbuf_msg __ARGS((char_u *name));
+#endif
+static int do_sub_msg __ARGS((void));
+static int
+#ifdef __BORLANDC__
+ _RTLENTRYF
+#endif
+ help_compare __ARGS((const void *s1, const void *s2));
+
+/*
+ * ":ascii" and "ga".
+ */
+/*ARGSUSED*/
+ void
+do_ascii(eap)
+ exarg_T *eap;
+{
+ int c;
+ char buf1[20];
+ char buf2[20];
+ char_u buf3[7];
+#ifdef FEAT_MBYTE
+ int c1 = 0;
+ int c2 = 0;
+ int len;
+
+ if (enc_utf8)
+ c = utfc_ptr2char(ml_get_cursor(), &c1, &c2);
+ else
+#endif
+ c = gchar_cursor();
+ if (c == NUL)
+ {
+ MSG("NUL");
+ return;
+ }
+
+#ifdef FEAT_MBYTE
+ IObuff[0] = NUL;
+ if (!has_mbyte || (enc_dbcs != 0 && c < 0x100) || c < 0x80)
+#endif
+ {
+ if (c == NL) /* NUL is stored as NL */
+ c = NUL;
+ if (vim_isprintc_strict(c) && (c < ' '
+#ifndef EBCDIC
+ || c > '~'
+#endif
+ ))
+ {
+ transchar_nonprint(buf3, c);
+ sprintf(buf1, " <%s>", (char *)buf3);
+ }
+ else
+ buf1[0] = NUL;
+#ifndef EBCDIC
+ if (c >= 0x80)
+ sprintf(buf2, " <M-%s>", transchar(c & 0x7f));
+ else
+#endif
+ buf2[0] = NUL;
+ sprintf((char *)IObuff, _("<%s>%s%s %d, Hex %02x, Octal %03o"),
+ transchar(c), buf1, buf2, c, c, c);
+#ifdef FEAT_MBYTE
+ c = c1;
+ c1 = c2;
+ c2 = 0;
+#endif
+ }
+
+#ifdef FEAT_MBYTE
+ /* Repeat for combining characters. */
+ while (has_mbyte && (c >= 0x100 || (enc_utf8 && c >= 0x80)))
+ {
+ len = (int)STRLEN(IObuff);
+ /* This assumes every multi-byte char is printable... */
+ if (len > 0)
+ IObuff[len++] = ' ';
+ IObuff[len++] = '<';
+ if (utf_iscomposing(c)
+#ifdef USE_GUI
+ && !gui.in_use
+#endif
+ )
+ IObuff[len++] = ' '; /* draw composing char on top of a space */
+ IObuff[len + (*mb_char2bytes)(c, IObuff + len)] = NUL;
+ sprintf((char *)IObuff + STRLEN(IObuff),
+ c < 0x10000 ? _("> %d, Hex %04x, Octal %o")
+ : _("> %d, Hex %08x, Octal %o"), c, c, c);
+ c = c1;
+ c1 = c2;
+ c2 = 0;
+ }
+#endif
+
+ msg(IObuff);
+}
+
+#if defined(FEAT_EX_EXTRA) || defined(PROTO)
+/*
+ * ":left", ":center" and ":right": align text.
+ */
+ void
+ex_align(eap)
+ exarg_T *eap;
+{
+ pos_T save_curpos;
+ int len;
+ int indent = 0;
+ int new_indent;
+ int has_tab;
+ int width;
+
+#ifdef FEAT_RIGHTLEFT
+ if (curwin->w_p_rl)
+ {
+ /* switch left and right aligning */
+ if (eap->cmdidx == CMD_right)
+ eap->cmdidx = CMD_left;
+ else if (eap->cmdidx == CMD_left)
+ eap->cmdidx = CMD_right;
+ }
+#endif
+
+ width = atoi((char *)eap->arg);
+ save_curpos = curwin->w_cursor;
+ if (eap->cmdidx == CMD_left) /* width is used for new indent */
+ {
+ if (width >= 0)
+ indent = width;
+ }
+ else
+ {
+ /*
+ * if 'textwidth' set, use it
+ * else if 'wrapmargin' set, use it
+ * if invalid value, use 80
+ */
+ if (width <= 0)
+ width = curbuf->b_p_tw;
+ if (width == 0 && curbuf->b_p_wm > 0)
+ width = W_WIDTH(curwin) - curbuf->b_p_wm;
+ if (width <= 0)
+ width = 80;
+ }
+
+ if (u_save((linenr_T)(eap->line1 - 1), (linenr_T)(eap->line2 + 1)) == FAIL)
+ return;
+
+ for (curwin->w_cursor.lnum = eap->line1;
+ curwin->w_cursor.lnum <= eap->line2; ++curwin->w_cursor.lnum)
+ {
+ if (eap->cmdidx == CMD_left) /* left align */
+ new_indent = indent;
+ else
+ {
+ len = linelen(eap->cmdidx == CMD_right ? &has_tab
+ : NULL) - get_indent();
+
+ if (len <= 0) /* skip blank lines */
+ continue;
+
+ if (eap->cmdidx == CMD_center)
+ new_indent = (width - len) / 2;
+ else
+ {
+ new_indent = width - len; /* right align */
+
+ /*
+ * Make sure that embedded TABs don't make the text go too far
+ * to the right.
+ */
+ if (has_tab)
+ while (new_indent > 0)
+ {
+ (void)set_indent(new_indent, 0);
+ if (linelen(NULL) <= width)
+ {
+ /*
+ * Now try to move the line as much as possible to
+ * the right. Stop when it moves too far.
+ */
+ do
+ (void)set_indent(++new_indent, 0);
+ while (linelen(NULL) <= width);
+ --new_indent;
+ break;
+ }
+ --new_indent;
+ }
+ }
+ }
+ if (new_indent < 0)
+ new_indent = 0;
+ (void)set_indent(new_indent, 0); /* set indent */
+ }
+ changed_lines(eap->line1, 0, eap->line2 + 1, 0L);
+ curwin->w_cursor = save_curpos;
+ beginline(BL_WHITE | BL_FIX);
+}
+
+/*
+ * Get the length of the current line, excluding trailing white space.
+ */
+ static int
+linelen(has_tab)
+ int *has_tab;
+{
+ char_u *line;
+ char_u *first;
+ char_u *last;
+ int save;
+ int len;
+
+ /* find the first non-blank character */
+ line = ml_get_curline();
+ first = skipwhite(line);
+
+ /* find the character after the last non-blank character */
+ for (last = first + STRLEN(first);
+ last > first && vim_iswhite(last[-1]); --last)
+ ;
+ save = *last;
+ *last = NUL;
+ len = linetabsize(line); /* get line length */
+ if (has_tab != NULL) /* check for embedded TAB */
+ *has_tab = (vim_strrchr(first, TAB) != NULL);
+ *last = save;
+
+ return len;
+}
+
+/*
+ * ":retab".
+ */
+ void
+ex_retab(eap)
+ exarg_T *eap;
+{
+ linenr_T lnum;
+ int got_tab = FALSE;
+ long num_spaces = 0;
+ long num_tabs;
+ long len;
+ long col;
+ long vcol;
+ long start_col = 0; /* For start of white-space string */
+ long start_vcol = 0; /* For start of white-space string */
+ int temp;
+ long old_len;
+ char_u *ptr;
+ char_u *new_line = (char_u *)1; /* init to non-NULL */
+ int did_undo; /* called u_save for current line */
+ int new_ts;
+ int save_list;
+ linenr_T first_line = 0; /* first changed line */
+ linenr_T last_line = 0; /* last changed line */
+
+ save_list = curwin->w_p_list;
+ curwin->w_p_list = 0; /* don't want list mode here */
+
+ new_ts = getdigits(&(eap->arg));
+ if (new_ts < 0)
+ {
+ EMSG(_(e_positive));
+ return;
+ }
+ if (new_ts == 0)
+ new_ts = curbuf->b_p_ts;
+ for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum)
+ {
+ ptr = ml_get(lnum);
+ col = 0;
+ vcol = 0;
+ did_undo = FALSE;
+ for (;;)
+ {
+ if (vim_iswhite(ptr[col]))
+ {
+ if (!got_tab && num_spaces == 0)
+ {
+ /* First consecutive white-space */
+ start_vcol = vcol;
+ start_col = col;
+ }
+ if (ptr[col] == ' ')
+ num_spaces++;
+ else
+ got_tab = TRUE;
+ }
+ else
+ {
+ if (got_tab || (eap->forceit && num_spaces > 1))
+ {
+ /* Retabulate this string of white-space */
+
+ /* len is virtual length of white string */
+ len = num_spaces = vcol - start_vcol;
+ num_tabs = 0;
+ if (!curbuf->b_p_et)
+ {
+ temp = new_ts - (start_vcol % new_ts);
+ if (num_spaces >= temp)
+ {
+ num_spaces -= temp;
+ num_tabs++;
+ }
+ num_tabs += num_spaces / new_ts;
+ num_spaces -= (num_spaces / new_ts) * new_ts;
+ }
+ if (curbuf->b_p_et || got_tab ||
+ (num_spaces + num_tabs < len))
+ {
+ if (did_undo == FALSE)
+ {
+ did_undo = TRUE;
+ if (u_save((linenr_T)(lnum - 1),
+ (linenr_T)(lnum + 1)) == FAIL)
+ {
+ new_line = NULL; /* flag out-of-memory */
+ break;
+ }
+ }
+
+ /* len is actual number of white characters used */
+ len = num_spaces + num_tabs;
+ old_len = (long)STRLEN(ptr);
+ new_line = lalloc(old_len - col + start_col + len + 1,
+ TRUE);
+ if (new_line == NULL)
+ break;
+ if (start_col > 0)
+ mch_memmove(new_line, ptr, (size_t)start_col);
+ mch_memmove(new_line + start_col + len,
+ ptr + col, (size_t)(old_len - col + 1));
+ ptr = new_line + start_col;
+ for (col = 0; col < len; col++)
+ ptr[col] = (col < num_tabs) ? '\t' : ' ';
+ ml_replace(lnum, new_line, FALSE);
+ if (first_line == 0)
+ first_line = lnum;
+ last_line = lnum;
+ ptr = new_line;
+ col = start_col + len;
+ }
+ }
+ got_tab = FALSE;
+ num_spaces = 0;
+ }
+ if (ptr[col] == NUL)
+ break;
+ vcol += chartabsize(ptr + col, (colnr_T)vcol);
+#ifdef FEAT_MBYTE
+ if (has_mbyte)
+ col += (*mb_ptr2len_check)(ptr + col);
+ else
+#endif
+ ++col;
+ }
+ if (new_line == NULL) /* out of memory */
+ break;
+ line_breakcheck();
+ }
+ if (got_int)
+ EMSG(_(e_interr));
+
+ if (curbuf->b_p_ts != new_ts)
+ redraw_curbuf_later(NOT_VALID);
+ if (first_line != 0)
+ changed_lines(first_line, 0, last_line + 1, 0L);
+
+ curwin->w_p_list = save_list; /* restore 'list' */
+
+ curbuf->b_p_ts = new_ts;
+ coladvance(curwin->w_curswant);
+
+ u_clearline();
+}
+#endif
+
+/*
+ * :move command - move lines line1-line2 to line dest
+ *
+ * return FAIL for failure, OK otherwise
+ */
+ int
+do_move(line1, line2, dest)
+ linenr_T line1;
+ linenr_T line2;
+ linenr_T dest;
+{
+ char_u *str;
+ linenr_T l;
+ linenr_T extra; /* Num lines added before line1 */
+ linenr_T num_lines; /* Num lines moved */
+ linenr_T last_line; /* Last line in file after adding new text */
+
+ if (dest >= line1 && dest < line2)
+ {
+ EMSG(_("E134: Move lines into themselves"));
+ return FAIL;
+ }
+
+ num_lines = line2 - line1 + 1;
+
+ /*
+ * First we copy the old text to its new location -- webb
+ * Also copy the flag that ":global" command uses.
+ */
+ if (u_save(dest, dest + 1) == FAIL)
+ return FAIL;
+ for (extra = 0, l = line1; l <= line2; l++)
+ {
+ str = vim_strsave(ml_get(l + extra));
+ if (str != NULL)
+ {
+ ml_append(dest + l - line1, str, (colnr_T)0, FALSE);
+ vim_free(str);
+ if (dest < line1)
+ extra++;
+ }
+ }
+
+ /*
+ * Now we must be careful adjusting our marks so that we don't overlap our
+ * mark_adjust() calls.
+ *
+ * We adjust the marks within the old text so that they refer to the
+ * last lines of the file (temporarily), because we know no other marks
+ * will be set there since these line numbers did not exist until we added
+ * our new lines.
+ *
+ * Then we adjust the marks on lines between the old and new text positions
+ * (either forwards or backwards).
+ *
+ * And Finally we adjust the marks we put at the end of the file back to
+ * their final destination at the new text position -- webb
+ */
+ last_line = curbuf->b_ml.ml_line_count;
+ mark_adjust(line1, line2, last_line - line2, 0L);
+ if (dest >= line2)
+ {
+ mark_adjust(line2 + 1, dest, -num_lines, 0L);
+ curbuf->b_op_start.lnum = dest - num_lines + 1;
+ curbuf->b_op_end.lnum = dest;
+ }
+ else
+ {
+ mark_adjust(dest + 1, line1 - 1, num_lines, 0L);
+ curbuf->b_op_start.lnum = dest + 1;
+ curbuf->b_op_end.lnum = dest + num_lines;
+ }
+ curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
+ mark_adjust(last_line - num_lines + 1, last_line,
+ -(last_line - dest - extra), 0L);
+
+ /*
+ * Now we delete the original text -- webb
+ */
+ if (u_save(line1 + extra - 1, line2 + extra + 1) == FAIL)
+ return FAIL;
+
+ for (l = line1; l <= line2; l++)
+ ml_delete(line1 + extra, TRUE);
+
+ if (!global_busy && num_lines > p_report)
+ {
+ if (num_lines == 1)
+ MSG(_("1 line moved"));
+ else
+ smsg((char_u *)_("%ld lines moved"), num_lines);
+ }
+
+ /*
+ * Leave the cursor on the last of the moved lines.
+ */
+ if (dest >= line1)
+ curwin->w_cursor.lnum = dest;
+ else
+ curwin->w_cursor.lnum = dest + (line2 - line1) + 1;
+
+ if (line1 < dest)
+ changed_lines(line1, 0, dest + num_lines + 1, 0L);
+ else
+ changed_lines(dest + 1, 0, line1 + num_lines, 0L);
+
+ return OK;
+}
+
+/*
+ * ":copy"
+ */
+ void
+ex_copy(line1, line2, n)
+ linenr_T line1;
+ linenr_T line2;
+ linenr_T n;
+{
+ linenr_T count;
+ char_u *p;
+
+ count = line2 - line1 + 1;
+ curbuf->b_op_start.lnum = n + 1;
+ curbuf->b_op_end.lnum = n + count;
+ curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
+
+ /*
+ * there are three situations:
+ * 1. destination is above line1
+ * 2. destination is between line1 and line2
+ * 3. destination is below line2
+ *
+ * n = destination (when starting)
+ * curwin->w_cursor.lnum = destination (while copying)
+ * line1 = start of source (while copying)
+ * line2 = end of source (while copying)
+ */
+ if (u_save(n, n + 1) == FAIL)
+ return;
+
+ curwin->w_cursor.lnum = n;
+ while (line1 <= line2)
+ {
+ /* need to use vim_strsave() because the line will be unlocked within
+ * ml_append() */
+ p = vim_strsave(ml_get(line1));
+ if (p != NULL)
+ {
+ ml_append(curwin->w_cursor.lnum, p, (colnr_T)0, FALSE);
+ vim_free(p);
+ }
+ /* situation 2: skip already copied lines */
+ if (line1 == n)
+ line1 = curwin->w_cursor.lnum;
+ ++line1;
+ if (curwin->w_cursor.lnum < line1)
+ ++line1;
+ if (curwin->w_cursor.lnum < line2)
+ ++line2;
+ ++curwin->w_cursor.lnum;
+ }
+
+ appended_lines_mark(n, count);
+
+ msgmore((long)count);
+}
+
+/*
+ * Handle the ":!cmd" command. Also for ":r !cmd" and ":w !cmd"
+ * Bangs in the argument are replaced with the previously entered command.
+ * Remember the argument.
+ *
+ * RISCOS: Bangs only replaced when followed by a space, since many
+ * pathnames contain one.
+ */
+ void
+do_bang(addr_count, eap, forceit, do_in, do_out)
+ int addr_count;
+ exarg_T *eap;
+ int forceit;
+ int do_in, do_out;
+{
+ char_u *arg = eap->arg; /* command */
+ linenr_T line1 = eap->line1; /* start of range */
+ linenr_T line2 = eap->line2; /* end of range */
+ static char_u *prevcmd = NULL; /* the previous command */
+ char_u *newcmd = NULL; /* the new command */
+ int free_newcmd = FALSE; /* need to free() newcmd */
+ int ins_prevcmd;
+ char_u *t;
+ char_u *p;
+ char_u *trailarg;
+ int len;
+ int scroll_save = msg_scroll;
+
+ /*
+ * Disallow shell commands for "rvim".
+ * Disallow shell commands from .exrc and .vimrc in current directory for
+ * security reasons.
+ */
+ if (check_restricted() || check_secure())
+ return;
+
+ if (addr_count == 0) /* :! */
+ {
+ msg_scroll = FALSE; /* don't scroll here */
+ autowrite_all();
+ msg_scroll = scroll_save;
+ }
+
+ /*
+ * Try to find an embedded bang, like in :!<cmd> ! [args]
+ * (:!! is indicated by the 'forceit' variable)
+ */
+ ins_prevcmd = forceit;
+ trailarg = arg;
+ do
+ {
+ len = (int)STRLEN(trailarg) + 1;
+ if (newcmd != NULL)
+ len += (int)STRLEN(newcmd);
+ if (ins_prevcmd)
+ {
+ if (prevcmd == NULL)
+ {
+ EMSG(_(e_noprev));
+ vim_free(newcmd);
+ return;
+ }
+ len += (int)STRLEN(prevcmd);
+ }
+ if ((t = alloc(len)) == NULL)
+ {
+ vim_free(newcmd);
+ return;
+ }
+ *t = NUL;
+ if (newcmd != NULL)
+ STRCAT(t, newcmd);
+ if (ins_prevcmd)
+ STRCAT(t, prevcmd);
+ p = t + STRLEN(t);
+ STRCAT(t, trailarg);
+ vim_free(newcmd);
+ newcmd = t;
+
+ /*
+ * Scan the rest of the argument for '!', which is replaced by the
+ * previous command. "\!" is replaced by "!" (this is vi compatible).
+ */
+ trailarg = NULL;
+ while (*p)
+ {
+ if (*p == '!'
+#ifdef RISCOS
+ && (p[1] == ' ' || p[1] == NUL)
+#endif
+ )
+ {
+ if (p > newcmd && p[-1] == '\\')
+ mch_memmove(p - 1, p, (size_t)(STRLEN(p) + 1));
+ else
+ {
+ trailarg = p;
+ *trailarg++ = NUL;
+ ins_prevcmd = TRUE;
+ break;
+ }
+ }
+ ++p;
+ }
+ } while (trailarg != NULL);
+
+ vim_free(prevcmd);
+ prevcmd = newcmd;
+
+ if (bangredo) /* put cmd in redo buffer for ! command */
+ {
+ AppendToRedobuffLit(prevcmd);
+ AppendToRedobuff((char_u *)"\n");
+ bangredo = FALSE;
+ }
+ /*
+ * Add quotes around the command, for shells that need them.
+ */
+ if (*p_shq != NUL)
+ {
+ newcmd = alloc((unsigned)(STRLEN(prevcmd) + 2 * STRLEN(p_shq) + 1));
+ if (newcmd == NULL)
+ return;
+ STRCPY(newcmd, p_shq);
+ STRCAT(newcmd, prevcmd);
+ STRCAT(newcmd, p_shq);
+ free_newcmd = TRUE;
+ }
+ if (addr_count == 0) /* :! */
+ {
+ /* echo the command */
+ msg_start();
+ msg_putchar(':');
+ msg_putchar('!');
+ msg_outtrans(newcmd);
+ msg_clr_eos();
+ windgoto(msg_row, msg_col);
+
+ do_shell(newcmd, 0);
+ }
+ else /* :range! */
+ /* Careful: This may recursively call do_bang() again! (because of
+ * autocommands) */
+ do_filter(line1, line2, eap, newcmd, do_in, do_out);
+ if (free_newcmd)
+ vim_free(newcmd);
+}
+
+/*
+ * do_filter: filter lines through a command given by the user
+ *
+ * We use temp files and the call_shell() routine here. This would normally
+ * be done using pipes on a UNIX machine, but this is more portable to
+ * non-unix machines. The call_shell() routine needs to be able
+ * to deal with redirection somehow, and should handle things like looking
+ * at the PATH env. variable, and adding reasonable extensions to the
+ * command name given by the user. All reasonable versions of call_shell()
+ * do this.
+ * We use input redirection if do_in is TRUE.
+ * We use output redirection if do_out is TRUE.
+ */
+ static void
+do_filter(line1, line2, eap, cmd, do_in, do_out)
+ linenr_T line1, line2;
+ exarg_T *eap; /* for forced 'ff' and 'fenc' */
+ char_u *cmd;
+ int do_in, do_out;
+{
+ char_u *itmp = NULL;
+ char_u *otmp = NULL;
+ linenr_T linecount;
+ linenr_T read_linecount;
+ pos_T cursor_save;
+ char_u *cmd_buf;
+#ifdef FEAT_AUTOCMD
+ buf_T *old_curbuf = curbuf;
+#endif
+
+ if (*cmd == NUL) /* no filter command */
+ return;
+
+#ifdef WIN3264
+ /*
+ * Check if external commands are allowed now.
+ */
+ if (can_end_termcap_mode(TRUE) == FALSE)
+ return;
+#endif
+
+ cursor_save = curwin->w_cursor;
+ linecount = line2 - line1 + 1;
+ curwin->w_cursor.lnum = line1;
+ curwin->w_cursor.col = 0;
+ changed_line_abv_curs();
+ invalidate_botline();
+
+ /*
+ * 1. Form temp file names
+ * 2. Write the lines to a temp file
+ * 3. Run the filter command on the temp file
+ * 4. Read the output of the command into the buffer
+ * 5. Delete the original lines to be filtered
+ * 6. Remove the temp files
+ */
+
+ if ((do_in && (itmp = vim_tempname('i')) == NULL)
+ || (do_out && (otmp = vim_tempname('o')) == NULL))
+ {
+ EMSG(_(e_notmp));
+ goto filterend;
+ }
+
+/*
+ * The writing and reading of temp files will not be shown.
+ * Vi also doesn't do this and the messages are not very informative.
+ */
+ ++no_wait_return; /* don't call wait_return() while busy */
+ if (do_in && buf_write(curbuf, itmp, NULL, line1, line2, eap,
+ FALSE, FALSE, FALSE, TRUE) == FAIL)
+ {
+ msg_putchar('\n'); /* keep message from buf_write() */
+ --no_wait_return;
+#if defined(FEAT_AUTOCMD) && defined(FEAT_EVAL)
+ if (!aborting())
+#endif
+ (void)EMSG2(_(e_notcreate), itmp); /* will call wait_return */
+ goto filterend;
+ }
+#ifdef FEAT_AUTOCMD
+ if (curbuf != old_curbuf)
+ goto filterend;
+#endif
+
+ if (!do_out)
+ msg_putchar('\n');
+
+ cmd_buf = make_filter_cmd(cmd, itmp, otmp);
+ if (cmd_buf == NULL)
+ goto filterend;
+
+ windgoto((int)Rows - 1, 0);
+ cursor_on();
+
+ /*
+ * When not redirecting the output the command can write anything to the
+ * screen. If 'shellredir' is equal to ">", screen may be messed up by
+ * stderr output of external command. Clear the screen later.
+ * If do_in is FALSE, this could be something like ":r !cat", which may
+ * also mess up the screen, clear it later.
+ */
+ if (!do_out || STRCMP(p_srr, ">") == 0 || !do_in)
+ redraw_later_clear();
+
+ /*
+ * When call_shell() fails wait_return() is called to give the user a
+ * chance to read the error messages. Otherwise errors are ignored, so you
+ * can see the error messages from the command that appear on stdout; use
+ * 'u' to fix the text
+ * Switch to cooked mode when not redirecting stdin, avoids that something
+ * like ":r !cat" hangs.
+ * Pass on the SHELL_DOOUT flag when the output is being redirected.
+ */
+ if (call_shell(cmd_buf, SHELL_FILTER | SHELL_COOKED
+ | (do_out ? SHELL_DOOUT : 0)))
+ {
+ redraw_later_clear();
+ wait_return(FALSE);
+ }
+ vim_free(cmd_buf);
+
+ did_check_timestamps = FALSE;
+ need_check_timestamps = TRUE;
+
+ /* When interrupting the shell command, it may still have produced some
+ * useful output. Reset got_int here, so that readfile() won't cancel
+ * reading. */
+ ui_breakcheck();
+ got_int = FALSE;
+
+ if (do_out)
+ {
+ if (u_save((linenr_T)(line2), (linenr_T)(line2 + 1)) == FAIL)
+ goto error;
+ redraw_curbuf_later(VALID);
+ read_linecount = curbuf->b_ml.ml_line_count;
+ if (readfile(otmp, NULL, line2, (linenr_T)0, (linenr_T)MAXLNUM, eap,
+ READ_FILTER) == FAIL)
+ {
+#if defined(FEAT_AUTOCMD) && defined(FEAT_EVAL)
+ if (!aborting())
+#endif
+ {
+ msg_putchar('\n');
+ EMSG2(_(e_notread), otmp);
+ }
+ goto error;
+ }
+#ifdef FEAT_AUTOCMD
+ if (curbuf != old_curbuf)
+ goto filterend;
+#endif
+
+ if (do_in)
+ {
+ if (cmdmod.keepmarks || vim_strchr(p_cpo, CPO_REMMARK) == NULL)
+ {
+ read_linecount = curbuf->b_ml.ml_line_count - read_linecount;
+ if (read_linecount >= linecount)
+ /* move all marks from old lines to new lines */
+ mark_adjust(line1, line2, linecount, 0L);
+ else
+ {
+ /* move marks from old lines to new lines, delete marks
+ * that are in deleted lines */
+ mark_adjust(line1, line1 + read_linecount - 1,
+ linecount, 0L);
+ mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L);
+ }
+ }
+
+ /*
+ * Put cursor on first filtered line for ":range!cmd".
+ * Adjust '[ and '] (set by buf_write()).
+ */
+ curwin->w_cursor.lnum = line1;
+ del_lines(linecount, TRUE);
+ curbuf->b_op_start.lnum -= linecount; /* adjust '[ */
+ curbuf->b_op_end.lnum -= linecount; /* adjust '] */
+ write_lnum_adjust(-linecount); /* adjust last line
+ for next write */
+ }
+ else
+ {
+ /*
+ * Put cursor on last new line for ":r !cmd".
+ */
+ curwin->w_cursor.lnum = curbuf->b_op_end.lnum;
+ linecount = curbuf->b_op_end.lnum - curbuf->b_op_start.lnum + 1;
+ }
+ beginline(BL_WHITE | BL_FIX); /* cursor on first non-blank */
+ --no_wait_return;
+
+ if (linecount > p_report)
+ {
+ if (do_in)
+ {
+ sprintf((char *)msg_buf, _("%ld lines filtered"),
+ (long)linecount);
+ if (msg(msg_buf) && !msg_scroll)
+ {
+ /* save message to display it after redraw */
+ set_keep_msg(msg_buf);
+ keep_msg_attr = 0;
+ }
+ }
+ else
+ msgmore((long)linecount);
+ }
+ }
+ else
+ {
+error:
+ /* put cursor back in same position for ":w !cmd" */
+ curwin->w_cursor = cursor_save;
+ --no_wait_return;
+ wait_return(FALSE);
+ }
+
+filterend:
+
+#ifdef FEAT_AUTOCMD
+ if (curbuf != old_curbuf)
+ {
+ --no_wait_return;
+ EMSG(_("E135: *Filter* Autocommands must not change current buffer"));
+ }
+#endif
+ if (itmp != NULL)
+ mch_remove(itmp);
+ if (otmp != NULL)
+ mch_remove(otmp);
+ vim_free(itmp);
+ vim_free(otmp);
+}
+
+/*
+ * Call a shell to execute a command.
+ * When "cmd" is NULL start an interactive shell.
+ */
+ void
+do_shell(cmd, flags)
+ char_u *cmd;
+ int flags; /* may be SHELL_DOOUT when output is redirected */
+{
+ buf_T *buf;
+#ifndef FEAT_GUI_MSWIN
+ int save_nwr;
+#endif
+#ifdef MSWIN
+ int winstart = FALSE;
+#endif
+
+ /*
+ * Disallow shell commands for "rvim".
+ * Disallow shell commands from .exrc and .vimrc in current directory for
+ * security reasons.
+ */
+ if (check_restricted() || check_secure())
+ {
+ msg_end();
+ return;
+ }
+
+#ifdef MSWIN
+ /*
+ * Check if external commands are allowed now.
+ */
+ if (can_end_termcap_mode(TRUE) == FALSE)
+ return;
+
+ /*
+ * Check if ":!start" is used.
+ */
+ if (cmd != NULL)
+ winstart = (STRNICMP(cmd, "start ", 6) == 0);
+#endif
+
+ /*
+ * For autocommands we want to get the output on the current screen, to
+ * avoid having to type return below.
+ */
+ msg_putchar('\r'); /* put cursor at start of line */
+#ifdef FEAT_AUTOCMD
+ if (!autocmd_busy)
+#endif
+ {
+#ifdef MSWIN
+ if (!winstart)
+#endif
+ stoptermcap();
+ }
+#ifdef MSWIN
+ if (!winstart)
+#endif
+ msg_putchar('\n'); /* may shift screen one line up */
+
+ /* warning message before calling the shell */
+ if (p_warn
+#ifdef FEAT_AUTOCMD
+ && !autocmd_busy
+#endif
+ && msg_silent == 0)
+ for (buf = firstbuf; buf; buf = buf->b_next)
+ if (bufIsChanged(buf))
+ {
+#ifdef FEAT_GUI_MSWIN
+ if (!winstart)
+ starttermcap(); /* don't want a message box here */
+#endif
+ MSG_PUTS(_("[No write since last change]\n"));
+#ifdef FEAT_GUI_MSWIN
+ if (!winstart)
+ stoptermcap();
+#endif
+ break;
+ }
+
+ /* This windgoto is required for when the '\n' resulted in a "delete line
+ * 1" command to the terminal. */
+ if (!swapping_screen())
+ windgoto(msg_row, msg_col);
+ cursor_on();
+ (void)call_shell(cmd, SHELL_COOKED | flags);
+ did_check_timestamps = FALSE;
+ need_check_timestamps = TRUE;
+
+ /*
+ * put the message cursor at the end of the screen, avoids wait_return()
+ * to overwrite the text that the external command showed
+ */
+ if (!swapping_screen())
+ {
+ msg_row = Rows - 1;
+ msg_col = 0;
+ }
+
+#ifdef FEAT_AUTOCMD
+ if (autocmd_busy)
+ {
+ if (msg_silent == 0)
+ redraw_later_clear();
+ }
+ else
+#endif
+ {
+ /*
+ * For ":sh" there is no need to call wait_return(), just redraw.
+ * Also for the Win32 GUI (the output is in a console window).
+ * Otherwise there is probably text on the screen that the user wants
+ * to read before redrawing, so call wait_return().
+ */
+#ifndef FEAT_GUI_MSWIN
+ if (cmd == NULL
+# ifdef WIN3264
+ || (winstart && !need_wait_return)
+# endif
+ )
+ {
+ if (msg_silent == 0)
+ redraw_later_clear();
+ need_wait_return = FALSE;
+ }
+ else
+ {
+ /*
+ * If we switch screens when starttermcap() is called, we really
+ * want to wait for "hit return to continue".
+ */
+ save_nwr = no_wait_return;
+ if (swapping_screen())
+ no_wait_return = FALSE;
+# ifdef AMIGA
+ wait_return(term_console ? -1 : msg_silent == 0); /* see below */
+# else
+ wait_return(msg_silent == 0);
+# endif
+ no_wait_return = save_nwr;
+ }
+#endif /* FEAT_GUI_W32 */
+
+#ifdef MSWIN
+ if (!winstart) /* if winstart==TRUE, never stopped termcap! */
+#endif
+ starttermcap(); /* start termcap if not done by wait_return() */
+
+ /*
+ * In an Amiga window redrawing is caused by asking the window size.
+ * If we got an interrupt this will not work. The chance that the
+ * window size is wrong is very small, but we need to redraw the
+ * screen. Don't do this if ':' hit in wait_return(). THIS IS UGLY
+ * but it saves an extra redraw.
+ */
+#ifdef AMIGA
+ if (skip_redraw) /* ':' hit in wait_return() */
+ {
+ if (msg_silent == 0)
+ redraw_later_clear();
+ }
+ else if (term_console)
+ {
+ OUT_STR(IF_EB("\033[0 q", ESC_STR "[0 q")); /* get window size */
+ if (got_int && msg_silent == 0)
+ redraw_later_clear(); /* if got_int is TRUE, redraw needed */
+ else
+ must_redraw = 0; /* no extra redraw needed */
+ }
+#endif
+ }
+
+ /* display any error messages now */
+ display_errors();
+}
+
+/*
+ * Create a shell command from a command string, input redirection file and
+ * output redirection file.
+ * Returns an allocated string with the shell command, or NULL for failure.
+ */
+ char_u *
+make_filter_cmd(cmd, itmp, otmp)
+ char_u *cmd; /* command */
+ char_u *itmp; /* NULL or name of input file */
+ char_u *otmp; /* NULL or name of output file */
+{
+ char_u *buf;
+ long_u len;
+
+ len = (long_u)STRLEN(cmd) + 3; /* "()" + NUL */
+ if (itmp != NULL)
+ len += (long_u)STRLEN(itmp) + 9; /* " { < " + " } " */
+ if (otmp != NULL)
+ len += (long_u)STRLEN(otmp) + (long_u)STRLEN(p_srr) + 2; /* " " */
+ buf = lalloc(len, TRUE);
+ if (buf == NULL)
+ return NULL;
+
+#if (defined(UNIX) && !defined(ARCHIE)) || defined(OS2)
+ /*
+ * put braces around the command (for concatenated commands)
+ */
+ sprintf((char *)buf, "(%s)", (char *)cmd);
+ if (itmp != NULL)
+ {
+ STRCAT(buf, " < ");
+ STRCAT(buf, itmp);
+ }
+#else
+ /*
+ * for shells that don't understand braces around commands, at least allow
+ * the use of commands in a pipe.
+ */
+ STRCPY(buf, cmd);
+ if (itmp != NULL)
+ {
+ char_u *p;
+
+ /*
+ * If there is a pipe, we have to put the '<' in front of it.
+ * Don't do this when 'shellquote' is not empty, otherwise the
+ * redirection would be inside the quotes.
+ */
+ if (*p_shq == NUL)
+ {
+ p = vim_strchr(buf, '|');
+ if (p != NULL)
+ *p = NUL;
+ }
+# ifdef RISCOS
+ STRCAT(buf, " { < "); /* Use RISC OS notation for input. */
+ STRCAT(buf, itmp);
+ STRCAT(buf, " } ");
+# else
+ STRCAT(buf, " <"); /* " < " causes problems on Amiga */
+ STRCAT(buf, itmp);
+# endif
+ if (*p_shq == NUL)
+ {
+ p = vim_strchr(cmd, '|');
+ if (p != NULL)
+ {
+ STRCAT(buf, " "); /* insert a space before the '|' for DOS */
+ STRCAT(buf, p);
+ }
+ }
+ }
+#endif
+ if (otmp != NULL)
+ append_redir(buf, p_srr, otmp);
+
+ return buf;
+}
+
+/*
+ * Append output redirection for file "fname" to the end of string buffer "buf"
+ * Works with the 'shellredir' and 'shellpipe' options.
+ * The caller should make sure that there is enough room:
+ * STRLEN(opt) + STRLEN(fname) + 3
+ */
+ void
+append_redir(buf, opt, fname)
+ char_u *buf;
+ char_u *opt;
+ char_u *fname;
+{
+ char_u *p;
+
+ buf += STRLEN(buf);
+ /* find "%s", skipping "%%" */
+ for (p = opt; (p = vim_strchr(p, '%')) != NULL; ++p)
+ if (p[1] == 's')
+ break;
+ if (p != NULL)
+ {
+ *buf = ' '; /* not really needed? Not with sh, ksh or bash */
+ sprintf((char *)buf + 1, (char *)opt, (char *)fname);
+ }
+ else
+ sprintf((char *)buf,
+#ifdef FEAT_QUICKFIX
+# ifndef RISCOS
+ opt != p_sp ? " %s%s" :
+# endif
+ " %s %s",
+#else
+# ifndef RISCOS
+ " %s%s", /* " > %s" causes problems on Amiga */
+# else
+ " %s %s", /* But is needed for 'shellpipe' and RISC OS */
+# endif
+#endif