diff options
| author | Eliseo Martínez <eliseomarmol@gmail.com> | 2014-05-12 02:25:17 +0200 |
|---|---|---|
| committer | Eliseo Martínez <eliseomarmol@gmail.com> | 2014-05-15 20:46:01 +0200 |
| commit | da51dc9cf202772f60bd2da975dbef257bd9237c (patch) | |
| tree | 5c16b93238a153f55634e9323077f30c8133970c /src/nvim/ex_cmds.c | |
| parent | ffe61e5ba1721340ca51d56bae3ddaca415fb5bc (diff) | |
| download | rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.tar.gz rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.tar.bz2 rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.zip | |
Introduce nvim namespace: Move files.
Move files from src/ to src/nvim/.
- src/nvim/ becomes the new root dir for nvim executable sources.
- src/libnvim/ is planned to become root dir of the neovim library.
Diffstat (limited to 'src/nvim/ex_cmds.c')
| -rw-r--r-- | src/nvim/ex_cmds.c | 6374 |
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(®match, 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 |