aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/ex_cmds.c
diff options
context:
space:
mode:
authorEliseo Martínez <eliseomarmol@gmail.com>2014-05-12 02:25:17 +0200
committerEliseo Martínez <eliseomarmol@gmail.com>2014-05-15 20:46:01 +0200
commitda51dc9cf202772f60bd2da975dbef257bd9237c (patch)
tree5c16b93238a153f55634e9323077f30c8133970c /src/nvim/ex_cmds.c
parentffe61e5ba1721340ca51d56bae3ddaca415fb5bc (diff)
downloadrneovim-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_cmds.c')
-rw-r--r--src/nvim/ex_cmds.c6374
1 files changed, 6374 insertions, 0 deletions
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
new file mode 100644
index 0000000000..3613a02cb3
--- /dev/null
+++ b/src/nvim/ex_cmds.c
@@ -0,0 +1,6374 @@
+/*
+ * 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 <string.h>
+#include <stdlib.h>
+
+#include "vim.h"
+#include "version_defs.h"
+#include "ex_cmds.h"
+#include "buffer.h"
+#include "charset.h"
+#include "diff.h"
+#include "digraph.h"
+#include "edit.h"
+#include "eval.h"
+#include "ex_cmds2.h"
+#include "ex_docmd.h"
+#include "ex_eval.h"
+#include "ex_getln.h"
+#include "farsi.h"
+#include "fileio.h"
+#include "fold.h"
+#include "getchar.h"
+#include "indent.h"
+#include "main.h"
+#include "mark.h"
+#include "mbyte.h"
+#include "memline.h"
+#include "message.h"
+#include "misc1.h"
+#include "misc2.h"
+#include "garray.h"
+#include "memory.h"
+#include "move.h"
+#include "normal.h"
+#include "ops.h"
+#include "option.h"
+#include "os_unix.h"
+#include "path.h"
+#include "quickfix.h"
+#include "regexp.h"
+#include "screen.h"
+#include "search.h"
+#include "spell.h"
+#include "syntax.h"
+#include "tag.h"
+#include "term.h"
+#include "ui.h"
+#include "undo.h"
+#include "window.h"
+#include "os/os.h"
+#include "os/shell.h"
+
+static int linelen(int *has_tab);
+static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap,
+ char_u *cmd, int do_in,
+ int do_out);
+static char_u *viminfo_filename(char_u *);
+static void do_viminfo(FILE *fp_in, FILE *fp_out, int flags);
+static int viminfo_encoding(vir_T *virp);
+static int read_viminfo_up_to_marks(vir_T *virp, int forceit,
+ int writing);
+
+static int check_readonly(int *forceit, buf_T *buf);
+static void delbuf_msg(char_u *name);
+static int help_compare(const void *s1, const void *s2);
+
+/*
+ * ":ascii" and "ga".
+ */
+void do_ascii(exarg_T *eap)
+{
+ int c;
+ int cval;
+ char buf1[20];
+ char buf2[20];
+ char_u buf3[7];
+ int cc[MAX_MCO];
+ int ci = 0;
+ int len;
+
+ if (enc_utf8)
+ c = utfc_ptr2char(ml_get_cursor(), cc);
+ else
+ c = gchar_cursor();
+ if (c == NUL) {
+ MSG("NUL");
+ return;
+ }
+
+ IObuff[0] = NUL;
+ if (!has_mbyte || (enc_dbcs != 0 && c < 0x100) || c < 0x80) {
+ if (c == NL) /* NUL is stored as NL */
+ c = NUL;
+ if (c == CAR && get_fileformat(curbuf) == EOL_MAC)
+ cval = NL; /* NL is stored as CR */
+ else
+ cval = c;
+ if (vim_isprintc_strict(c) && (c < ' '
+ || c > '~'
+ )) {
+ transchar_nonprint(buf3, c);
+ vim_snprintf(buf1, sizeof(buf1), " <%s>", (char *)buf3);
+ } else
+ buf1[0] = NUL;
+ if (c >= 0x80)
+ vim_snprintf(buf2, sizeof(buf2), " <M-%s>",
+ (char *)transchar(c & 0x7f));
+ else
+ buf2[0] = NUL;
+ vim_snprintf((char *)IObuff, IOSIZE,
+ _("<%s>%s%s %d, Hex %02x, Octal %03o"),
+ transchar(c), buf1, buf2, cval, cval, cval);
+ if (enc_utf8)
+ c = cc[ci++];
+ else
+ c = 0;
+ }
+
+ /* 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 (enc_utf8 && utf_iscomposing(c)
+# ifdef USE_GUI
+ && !gui.in_use
+# endif
+ )
+ IObuff[len++] = ' '; /* draw composing char on top of a space */
+ len += (*mb_char2bytes)(c, IObuff + len);
+ vim_snprintf((char *)IObuff + len, IOSIZE - len,
+ c < 0x10000 ? _("> %d, Hex %04x, Octal %o")
+ : _("> %d, Hex %08x, Octal %o"), c, c, c);
+ if (ci == MAX_MCO)
+ break;
+ if (enc_utf8)
+ c = cc[ci++];
+ else
+ c = 0;
+ }
+
+ msg(IObuff);
+}
+
+/*
+ * ":left", ":center" and ":right": align text.
+ */
+void ex_align(exarg_T *eap)
+{
+ pos_T save_curpos;
+ int len;
+ int indent = 0;
+ int new_indent;
+ int has_tab;
+ int width;
+
+ 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;
+ }
+
+ 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 {
+ has_tab = FALSE; /* avoid uninit warnings */
+ 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(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;
+}
+
+/* Buffer for two lines used during sorting. They are allocated to
+ * contain the longest line being sorted. */
+static char_u *sortbuf1;
+static char_u *sortbuf2;
+
+static int sort_ic; /* ignore case */
+static int sort_nr; /* sort on number */
+static int sort_rx; /* sort on regex instead of skipping it */
+
+static int sort_abort; /* flag to indicate if sorting has been interrupted */
+
+/* Struct to store info to be sorted. */
+typedef struct {
+ linenr_T lnum; /* line number */
+ long start_col_nr; /* starting column number or number */
+ long end_col_nr; /* ending column number */
+} sorti_T;
+
+static int
+sort_compare(const void *s1, const void *s2);
+
+static int sort_compare(const void *s1, const void *s2)
+{
+ sorti_T l1 = *(sorti_T *)s1;
+ sorti_T l2 = *(sorti_T *)s2;
+ int result = 0;
+
+ /* If the user interrupts, there's no way to stop qsort() immediately, but
+ * if we return 0 every time, qsort will assume it's done sorting and
+ * exit. */
+ if (sort_abort)
+ return 0;
+ fast_breakcheck();
+ if (got_int)
+ sort_abort = TRUE;
+
+ /* When sorting numbers "start_col_nr" is the number, not the column
+ * number. */
+ if (sort_nr)
+ result = l1.start_col_nr == l2.start_col_nr ? 0
+ : l1.start_col_nr > l2.start_col_nr ? 1 : -1;
+ else {
+ /* We need to copy one line into "sortbuf1", because there is no
+ * guarantee that the first pointer becomes invalid when obtaining the
+ * second one. */
+ STRNCPY(sortbuf1, ml_get(l1.lnum) + l1.start_col_nr,
+ l1.end_col_nr - l1.start_col_nr + 1);
+ sortbuf1[l1.end_col_nr - l1.start_col_nr] = 0;
+ STRNCPY(sortbuf2, ml_get(l2.lnum) + l2.start_col_nr,
+ l2.end_col_nr - l2.start_col_nr + 1);
+ sortbuf2[l2.end_col_nr - l2.start_col_nr] = 0;
+
+ result = sort_ic ? STRICMP(sortbuf1, sortbuf2)
+ : STRCMP(sortbuf1, sortbuf2);
+ }
+
+ /* If two lines have the same value, preserve the original line order. */
+ if (result == 0)
+ return (int)(l1.lnum - l2.lnum);
+ return result;
+}
+
+/*
+ * ":sort".
+ */
+void ex_sort(exarg_T *eap)
+{
+ regmatch_T regmatch;
+ int len;
+ linenr_T lnum;
+ long maxlen = 0;
+ size_t count = (size_t)(eap->line2 - eap->line1 + 1);
+ size_t i;
+ char_u *p;
+ char_u *s;
+ char_u *s2;
+ char_u c; /* temporary character storage */
+ int unique = FALSE;
+ long deleted;
+ colnr_T start_col;
+ colnr_T end_col;
+ int sort_oct; /* sort on octal number */
+ int sort_hex; /* sort on hex number */
+
+ /* Sorting one line is really quick! */
+ if (count <= 1)
+ return;
+
+ if (u_save((linenr_T)(eap->line1 - 1), (linenr_T)(eap->line2 + 1)) == FAIL)
+ return;
+ sortbuf1 = NULL;
+ sortbuf2 = NULL;
+ regmatch.regprog = NULL;
+ sorti_T *nrs = xmalloc(count * sizeof(sorti_T));
+
+ sort_abort = sort_ic = sort_rx = sort_nr = sort_oct = sort_hex = 0;
+
+ for (p = eap->arg; *p != NUL; ++p) {
+ if (vim_iswhite(*p))
+ ;
+ else if (*p == 'i')
+ sort_ic = TRUE;
+ else if (*p == 'r')
+ sort_rx = TRUE;
+ else if (*p == 'n')
+ sort_nr = 2;
+ else if (*p == 'o')
+ sort_oct = 2;
+ else if (*p == 'x')
+ sort_hex = 2;
+ else if (*p == 'u')
+ unique = TRUE;
+ else if (*p == '"') /* comment start */
+ break;
+ else if (check_nextcmd(p) != NULL) {
+ eap->nextcmd = check_nextcmd(p);
+ break;
+ } else if (!ASCII_ISALPHA(*p) && regmatch.regprog == NULL) {
+ s = skip_regexp(p + 1, *p, TRUE, NULL);
+ if (*s != *p) {
+ EMSG(_(e_invalpat));
+ goto sortend;
+ }
+ *s = NUL;
+ /* Use last search pattern if sort pattern is empty. */
+ if (s == p + 1) {
+ if (last_search_pat() == NULL) {
+ EMSG(_(e_noprevre));
+ goto sortend;
+ }
+ regmatch.regprog = vim_regcomp(last_search_pat(), RE_MAGIC);
+ } else
+ regmatch.regprog = vim_regcomp(p + 1, RE_MAGIC);
+ if (regmatch.regprog == NULL)
+ goto sortend;
+ p = s; /* continue after the regexp */
+ regmatch.rm_ic = p_ic;
+ } else {
+ EMSG2(_(e_invarg2), p);
+ goto sortend;
+ }
+ }
+
+ /* Can only have one of 'n', 'o' and 'x'. */
+ if (sort_nr + sort_oct + sort_hex > 2) {
+ EMSG(_(e_invarg));
+ goto sortend;
+ }
+
+ /* From here on "sort_nr" is used as a flag for any number sorting. */
+ sort_nr += sort_oct + sort_hex;
+
+ /*
+ * Make an array with all line numbers. This avoids having to copy all
+ * the lines into allocated memory.
+ * When sorting on strings "start_col_nr" is the offset in the line, for
+ * numbers sorting it's the number to sort on. This means the pattern
+ * matching and number conversion only has to be done once per line.
+ * Also get the longest line length for allocating "sortbuf".
+ */
+ for (lnum = eap->line1; lnum <= eap->line2; ++lnum) {
+ s = ml_get(lnum);
+ len = (int)STRLEN(s);
+ if (maxlen < len)
+ maxlen = len;
+
+ start_col = 0;
+ end_col = len;
+ if (regmatch.regprog != NULL && vim_regexec(&regmatch, s, 0)) {
+ if (sort_rx) {
+ start_col = (colnr_T)(regmatch.startp[0] - s);
+ end_col = (colnr_T)(regmatch.endp[0] - s);
+ } else
+ start_col = (colnr_T)(regmatch.endp[0] - s);
+ } else if (regmatch.regprog != NULL)
+ end_col = 0;
+
+ if (sort_nr) {
+ /* Make sure vim_str2nr doesn't read any digits past the end
+ * of the match, by temporarily terminating the string there */
+ s2 = s + end_col;
+ c = *s2;
+ *s2 = NUL;
+ /* Sorting on number: Store the number itself. */
+ p = s + start_col;
+ if (sort_hex)
+ s = skiptohex(p);
+ else
+ s = skiptodigit(p);
+ if (s > p && s[-1] == '-')
+ --s; /* include preceding negative sign */
+ if (*s == NUL)
+ /* empty line should sort before any number */
+ nrs[lnum - eap->line1].start_col_nr = -MAXLNUM;
+ else
+ vim_str2nr(s, NULL, NULL, sort_oct, sort_hex,
+ &nrs[lnum - eap->line1].start_col_nr, NULL);
+ *s2 = c;
+ } else {
+ /* Store the column to sort at. */
+ nrs[lnum - eap->line1].start_col_nr = start_col;
+ nrs[lnum - eap->line1].end_col_nr = end_col;
+ }
+
+ nrs[lnum - eap->line1].lnum = lnum;
+
+ if (regmatch.regprog != NULL)
+ fast_breakcheck();
+ if (got_int)
+ goto sortend;
+ }
+
+ /* Allocate a buffer that can hold the longest line. */
+ sortbuf1 = xmalloc(maxlen + 1);
+ sortbuf2 = xmalloc(maxlen + 1);
+
+ /* Sort the array of line numbers. Note: can't be interrupted! */
+ qsort((void *)nrs, count, sizeof(sorti_T), sort_compare);
+
+ if (sort_abort)
+ goto sortend;
+
+ /* Insert the lines in the sorted order below the last one. */
+ lnum = eap->line2;
+ for (i = 0; i < count; ++i) {
+ s = ml_get(nrs[eap->forceit ? count - i - 1 : i].lnum);
+ if (!unique || i == 0
+ || (sort_ic ? STRICMP(s, sortbuf1) : STRCMP(s, sortbuf1)) != 0) {
+ if (ml_append(lnum++, s, (colnr_T)0, FALSE) == FAIL)
+ break;
+ if (unique)
+ STRCPY(sortbuf1, s);
+ }
+ fast_breakcheck();
+ if (got_int)
+ goto sortend;
+ }
+
+ /* delete the original lines if appending worked */
+ if (i == count)
+ for (i = 0; i < count; ++i)
+ ml_delete(eap->line1, FALSE);
+ else
+ count = 0;
+
+ /* Adjust marks for deleted (or added) lines and prepare for displaying. */
+ deleted = (long)(count - (lnum - eap->line2));
+ if (deleted > 0)
+ mark_adjust(eap->line2 - deleted, eap->line2, (long)MAXLNUM, -deleted);
+ else if (deleted < 0)
+ mark_adjust(eap->line2, MAXLNUM, -deleted, 0L);
+ changed_lines(eap->line1, 0, eap->line2 + 1, -deleted);
+
+ curwin->w_cursor.lnum = eap->line1;
+ beginline(BL_WHITE | BL_FIX);
+
+sortend:
+ free(nrs);
+ free(sortbuf1);
+ free(sortbuf2);
+ vim_regfree(regmatch.regprog);
+ if (got_int)
+ EMSG(_(e_interr));
+}
+
+/*
+ * ":retab".
+ */
+void ex_retab(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 = xmalloc(old_len - col + start_col + len + 1);
+
+ if (start_col > 0)
+ memmove(new_line, ptr, (size_t)start_col);
+ 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);
+ if (has_mbyte)
+ col += (*mb_ptr2len)(ptr + col);
+ else
+ ++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();
+}
+
+/*
+ * :move command - move lines line1-line2 to line dest
+ *
+ * return FAIL for failure, OK otherwise
+ */
+int do_move(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);
+ 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);
+ changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines);
+ 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);
+ changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra);
+
+ /*
+ * 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 *)_("%" PRId64 " lines moved"), (int64_t)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) {
+ dest += num_lines + 1;
+ last_line = curbuf->b_ml.ml_line_count;
+ if (dest > last_line + 1)
+ dest = last_line + 1;
+ changed_lines(line1, 0, dest, 0L);
+ } else
+ changed_lines(dest + 1, 0, line1 + num_lines, 0L);
+
+ return OK;
+}
+
+/*
+ * ":copy"
+ */
+void ex_copy(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);
+ 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);
+}
+
+static char_u *prevcmd = NULL; /* the previous command */
+
+#if defined(EXITFREE) || defined(PROTO)
+void free_prev_shellcmd(void)
+{
+ free(prevcmd);
+}
+
+#endif
+
+/*
+ * 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.
+ */
+void do_bang(int addr_count, exarg_T *eap, int forceit, int do_in, int do_out)
+{
+ char_u *arg = eap->arg; /* command */
+ linenr_T line1 = eap->line1; /* start of range */
+ linenr_T line2 = eap->line2; /* end of range */
+ 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));
+ free(newcmd);
+ return;
+ }
+ len += (int)STRLEN(prevcmd);
+ }
+ t = xmalloc(len);
+ *t = NUL;
+ if (newcmd != NULL)
+ STRCAT(t, newcmd);
+ if (ins_prevcmd)
+ STRCAT(t, prevcmd);
+ p = t + STRLEN(t);
+ STRCAT(t, trailarg);
+ 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 == '!') {
+ if (p > newcmd && p[-1] == '\\')
+ STRMOVE(p - 1, p);
+ else {
+ trailarg = p;
+ *trailarg++ = NUL;
+ ins_prevcmd = TRUE;
+ break;
+ }
+ }
+ ++p;
+ }
+ } while (trailarg != NULL);
+
+ free(prevcmd);
+ prevcmd = newcmd;
+
+ if (bangredo) { /* put cmd in redo buffer for ! command */
+ /* If % or # appears in the command, it must have been escaped.
+ * Reescape them, so that redoing them does not substitute them by the
+ * buffername. */
+ char_u *cmd = vim_strsave_escaped(prevcmd, (char_u *)"%#");
+
+ AppendToRedobuffLit(cmd, -1);
+ free(cmd);
+ AppendToRedobuff((char_u *)"\n");
+ bangredo = FALSE;
+ }
+ /*
+ * Add quotes around the command, for shells that need them.
+ */
+ if (*p_shq != NUL) {
+ newcmd = xmalloc(STRLEN(prevcmd) + 2 * STRLEN(p_shq) + 1);
+ 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);
+ apply_autocmds(EVENT_SHELLFILTERPOST, NULL, NULL, FALSE, curbuf);
+ }
+ if (free_newcmd)
+ free(newcmd);
+}
+
+/*
+ * do_filter: filter lines through a command given by the user
+ *
+ * We mostly 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.
+ * Alternatively, if on Unix and redirecting input or output, but not both,
+ * and the 'shelltemp' option isn't set, use pipes.
+ * We use input redirection if do_in is TRUE.
+ * We use output redirection if do_out is TRUE.
+ */
+static void
+do_filter (
+ linenr_T line1,
+ linenr_T line2,
+ exarg_T *eap, /* for forced 'ff' and 'fenc' */
+ char_u *cmd,
+ int do_in,
+ int 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;
+ buf_T *old_curbuf = curbuf;
+ int shell_flags = 0;
+
+ if (*cmd == NUL) /* no filter command */
+ return;
+
+
+ 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();
+
+ /*
+ * When using temp files:
+ * 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
+ *
+ * When writing the input with a pipe or when catching the output with a
+ * pipe only need to do 3.
+ */
+
+ if (do_out)
+ shell_flags |= kShellOptDoOut;
+
+ if (!do_in && do_out && !p_stmp) {
+ // Use a pipe to fetch stdout of the command, do not use a temp file.
+ shell_flags |= kShellOptRead;
+ curwin->w_cursor.lnum = line2;
+ } else if (do_in && !do_out && !p_stmp) {
+ // Use a pipe to write stdin of the command, do not use a temp file.
+ shell_flags |= kShellOptWrite;
+ curbuf->b_op_start.lnum = line1;
+ curbuf->b_op_end.lnum = line2;
+ } else if (do_in && do_out && !p_stmp) {
+ // Use a pipe to write stdin and fetch stdout of the command, do not
+ // use a temp file.
+ shell_flags |= kShellOptRead | kShellOptWrite;
+ curbuf->b_op_start.lnum = line1;
+ curbuf->b_op_end.lnum = line2;
+ curwin->w_cursor.lnum = line2;
+ } else 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 (itmp != NULL && 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 (!aborting())
+ (void)EMSG2(_(e_notcreate), itmp); /* will call wait_return */
+ goto filterend;
+ }
+ if (curbuf != old_curbuf)
+ goto filterend;
+
+ if (!do_out)
+ msg_putchar('\n');
+
+ /* Create the shell command in allocated memory. */
+ cmd_buf = make_filter_cmd(cmd, itmp, otmp);
+
+ 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();
+
+ if (do_out) {
+ if (u_save((linenr_T)(line2), (linenr_T)(line2 + 1)) == FAIL) {
+ free(cmd_buf);
+ goto error;
+ }
+ redraw_curbuf_later(VALID);
+ }
+ read_linecount = curbuf->b_ml.ml_line_count;
+
+ /*
+ * 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
+ * li