diff options
Diffstat (limited to 'src/ex_cmds.c')
-rw-r--r-- | src/ex_cmds.c | 6198 |
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 |