home *** CD-ROM | disk | FTP | other *** search
- /* vi:set ts=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.
- */
-
- /*
- *
- * csearch.c: do_sub() and do_glob() for :s, :g and :v
- */
-
- #include "vim.h"
- #include "globals.h"
- #include "proto.h"
- #include "option.h"
-
- /* we use modified Henry Spencer's regular expression routines */
- #include "regexp.h"
-
- static int do_sub_msg __ARGS((void));
-
- #ifdef VIMINFO
- static char_u *old_sub = NULL;
- #endif /* VIMINFO */
-
- /* do_sub(lp, up, cmd)
- *
- * Perform a substitution from line 'lp' to line 'up' using the
- * command pointed to by 'cmd' which should be of the form:
- *
- * /pattern/substitution/gc
- *
- * The trailing 'g' is optional and, if present, indicates that multiple
- * substitutions should be performed on each line, if applicable.
- * The trailing 'c' is optional and, if present, indicates that a confirmation
- * will be asked for each replacement.
- * The usual escapes are supported as described in the regexp docs.
- *
- * use_old == 0 for :substitute
- * use_old == 1 for :&
- * use_old == 2 for :~
- */
-
- static long sub_nsubs; /* total number of substitutions */
- static linenr_t sub_nlines; /* total number of lines changed */
-
- void
- do_sub(lp, up, cmd, nextcommand, use_old)
- linenr_t lp;
- linenr_t up;
- char_u *cmd;
- char_u **nextcommand;
- int use_old;
- {
- linenr_t lnum;
- long i;
- char_u *ptr;
- char_u *old_line;
- regexp *prog;
- static int do_all = FALSE; /* do multiple substitutions per line */
- static int do_ask = FALSE; /* ask for confirmation */
- int do_print = FALSE; /* print last line with subst. */
- char_u *pat, *sub;
- #ifndef VIMINFO /* otherwise it is global */
- static char_u *old_sub = NULL;
- #endif
- int delimiter;
- int sublen;
- int got_quit = FALSE;
- int got_match = FALSE;
- int temp;
- int which_pat;
-
- if (!global_busy)
- {
- sub_nsubs = 0;
- sub_nlines = 0;
- }
-
- if (use_old == 2)
- which_pat = RE_LAST; /* use last used regexp */
- else
- which_pat = RE_SUBST; /* use last substitute regexp */
-
- /* new pattern and substitution */
- if (use_old == 0 && *cmd != NUL &&
- vim_strchr((char_u *)"0123456789gcr|\"", *cmd) == NULL)
- {
- /* don't accept alphanumeric for separator */
- if (isalpha(*cmd) || isdigit(*cmd))
- {
- EMSG("Regular expressions can't be delimited by letters or digits");
- return;
- }
- /*
- * undocumented vi feature:
- * "\/sub/" and "\?sub?" use last used search pattern (almost like
- * //sub/r). "\&sub&" use last substitute pattern (like //sub/).
- */
- if (*cmd == '\\')
- {
- ++cmd;
- if (vim_strchr((char_u *)"/?&", *cmd) == NULL)
- {
- emsg(e_backslash);
- return;
- }
- if (*cmd != '&')
- which_pat = RE_SEARCH; /* use last '/' pattern */
- pat = (char_u *)""; /* empty search pattern */
- delimiter = *cmd++; /* remember delimiter character */
- }
- else /* find the end of the regexp */
- {
- which_pat = RE_LAST; /* use last used regexp */
- delimiter = *cmd++; /* remember delimiter character */
- pat = cmd; /* remember start of search pat */
- cmd = skip_regexp(cmd, delimiter);
- if (cmd[0] == delimiter) /* end delimiter found */
- *cmd++ = NUL; /* replace it with a NUL */
- }
-
- /*
- * Small incompatibility: vi sees '\n' as end of the command, but in
- * Vim we want to use '\n' to find/substitute a NUL.
- */
- sub = cmd; /* remember the start of the substitution */
-
- while (cmd[0])
- {
- if (cmd[0] == delimiter) /* end delimiter found */
- {
- *cmd++ = NUL; /* replace it with a NUL */
- break;
- }
- if (cmd[0] == '\\' && cmd[1] != 0) /* skip escaped characters */
- ++cmd;
- ++cmd;
- }
-
- vim_free(old_sub);
- old_sub = strsave(sub);
- }
- else /* use previous pattern and substitution */
- {
- if (old_sub == NULL) /* there is no previous command */
- {
- emsg(e_nopresub);
- return;
- }
- pat = NULL; /* myregcomp() will use previous pattern */
- sub = old_sub;
- }
-
- /*
- * find trailing options
- */
- if (!p_ed)
- {
- if (p_gd) /* default is global on */
- do_all = TRUE;
- else
- do_all = FALSE;
- do_ask = FALSE;
- }
- while (*cmd)
- {
- /*
- * Note that 'g' and 'c' are always inverted, also when p_ed is off
- * 'r' is never inverted.
- */
- if (*cmd == 'g')
- do_all = !do_all;
- else if (*cmd == 'c')
- do_ask = !do_ask;
- else if (*cmd == 'r') /* use last used regexp */
- which_pat = RE_LAST;
- else if (*cmd == 'p')
- do_print = TRUE;
- else
- break;
- ++cmd;
- }
-
- /*
- * check for a trailing count
- */
- cmd = skipwhite(cmd);
- if (isdigit(*cmd))
- {
- i = getdigits(&cmd);
- if (i <= 0)
- {
- emsg(e_zerocount);
- return;
- }
- lp = up;
- up += i - 1;
- }
-
- /*
- * check for trailing '|', '"' or '\n'
- */
- cmd = skipwhite(cmd);
- if (*cmd && *cmd != '\"')
- {
- if (vim_strchr((char_u *)"|\n", *cmd) == NULL)
- {
- emsg(e_trailing);
- return;
- }
- else
- *nextcommand = cmd + 1;
- }
-
- if ((prog = myregcomp(pat, RE_SUBST, which_pat, SEARCH_HIS)) == NULL)
- {
- emsg(e_invcmd);
- return;
- }
-
- /*
- * ~ in the substitute pattern is replaced with the old pattern.
- * We do it here once to avoid it to be replaced over and over again.
- */
- sub = regtilde(sub, (int)p_magic);
-
- old_line = NULL;
- for (lnum = lp; lnum <= up && !(got_int || got_quit); ++lnum)
- {
- ptr = ml_get(lnum);
- if (vim_regexec(prog, ptr, TRUE)) /* a match on this line */
- {
- char_u *new_end, *new_start = NULL;
- char_u *old_match, *old_copy;
- char_u *prev_old_match = NULL;
- char_u *p1;
- int did_sub = FALSE;
- int match, lastone;
- unsigned len, needed_len;
- unsigned new_start_len = 0;
-
- /* make a copy of the line, so it won't be taken away when updating
- the screen */
- if ((old_line = strsave(ptr)) == NULL)
- continue;
- vim_regexec(prog, old_line, TRUE); /* match again on this line to
- * update the pointers. TODO:
- * remove extra vim_regexec() */
- if (!got_match)
- {
- setpcmark();
- got_match = TRUE;
- }
-
- old_copy = old_match = old_line;
- for (;;) /* loop until nothing more to replace */
- {
- /*
- * Save the position of the last change for the final cursor
- * position (just like the real vi).
- */
- curwin->w_cursor.lnum = lnum;
- curwin->w_cursor.col = (int)(prog->startp[0] - old_line);
-
- /*
- * Match empty string does not count, except for first match.
- * This reproduces the strange vi behaviour.
- * This also catches endless loops.
- */
- if (old_match == prev_old_match && old_match == prog->endp[0])
- {
- ++old_match;
- goto skip;
- }
- old_match = prog->endp[0];
- prev_old_match = old_match;
-
- while (do_ask) /* loop until 'y', 'n', 'q', CTRL-E or CTRL-Y typed */
- {
- temp = RedrawingDisabled;
- RedrawingDisabled = FALSE;
- comp_Botline(curwin);
- search_match_len = prog->endp[0] - prog->startp[0];
- /* invert the matched string
- * remove the inversion afterwards */
- if (search_match_len == 0)
- search_match_len = 1; /* show something! */
- highlight_match = TRUE;
- updateScreen(CURSUPD);
- highlight_match = FALSE;
- redraw_later(NOT_VALID);
- /* same highlighting as for wait_return */
- (void)set_highlight('r');
- msg_highlight = TRUE;
- smsg((char_u *)"replace with %s (y/n/a/q/^E/^Y)?", sub);
- showruler(TRUE);
- RedrawingDisabled = temp;
-
- ++no_mapping; /* don't map this key */
- i = vgetc();
- --no_mapping;
-
- /* clear the question */
- msg_didout = FALSE; /* don't scroll up */
- msg_col = 0;
- gotocmdline(TRUE);
- if (i == 'q' || i == ESC || i == Ctrl('C'))
- {
- got_quit = TRUE;
- break;
- }
- else if (i == 'n')
- goto skip;
- else if (i == 'y')
- break;
- else if (i == 'a')
- {
- do_ask = FALSE;
- break;
- }
- else if (i == Ctrl('E'))
- scrollup_clamp();
- else if (i == Ctrl('Y'))
- scrolldown_clamp();
- }
- if (got_quit)
- break;
-
- /* get length of substitution part */
- sublen = vim_regsub(prog, sub, old_line, FALSE, (int)p_magic);
- if (new_start == NULL)
- {
- /*
- * Get some space for a temporary buffer to do the
- * substitution into (and some extra space to avoid
- * too many calls to alloc()/free()).
- */
- new_start_len = STRLEN(old_copy) + sublen + 25;
- if ((new_start = alloc_check(new_start_len)) == NULL)
- goto outofmem;
- *new_start = NUL;
- new_end = new_start;
- }
- else
- {
- /*
- * Extend the temporary buffer to do the substitution into.
- * Avoid an alloc()/free(), it takes a lot of time.
- */
- len = STRLEN(new_start);
- needed_len = len + STRLEN(old_copy) + sublen + 1;
- if (needed_len > new_start_len)
- {
- needed_len += 20; /* get some extra */
- if ((p1 = alloc_check(needed_len)) == NULL)
- goto outofmem;
- STRCPY(p1, new_start);
- vim_free(new_start);
- new_start = p1;
- new_start_len = needed_len;
- }
- new_end = new_start + len;
- }
-
- /*
- * copy the text up to the part that matched
- */
- i = prog->startp[0] - old_copy;
- vim_memmove(new_end, old_copy, (size_t)i);
- new_end += i;
-
- vim_regsub(prog, sub, new_end, TRUE, (int)p_magic);
- sub_nsubs++;
- did_sub = TRUE;
-
- /*
- * Now the trick is to replace CTRL-Ms with a real line break.
- * This would make it impossible to insert CTRL-Ms in the text.
- * That is the way vi works. In Vim the line break can be
- * avoided by preceding the CTRL-M with a CTRL-V. Now you can't
- * precede a line break with a CTRL-V, big deal.
- */
- while ((p1 = vim_strchr(new_end, CR)) != NULL)
- {
- if (p1 == new_end || p1[-1] != Ctrl('V'))
- {
- if (u_inssub(lnum) == OK) /* prepare for undo */
- {
- *p1 = NUL; /* truncate up to the CR */
- mark_adjust(lnum, MAXLNUM, 1L, 0L);
- ml_append(lnum - 1, new_start,
- (colnr_t)(p1 - new_start + 1), FALSE);
- ++lnum;
- ++up; /* number of lines increases */
- STRCPY(new_start, p1 + 1); /* copy the rest */
- new_end = new_start;
- }
- }
- else /* remove CTRL-V */
- {
- STRCPY(p1 - 1, p1);
- new_end = p1;
- }
- }
-
- old_copy = prog->endp[0]; /* remember next character to be copied */
- /*
- * continue searching after the match
- * prevent endless loop with patterns that match empty strings,
- * e.g. :s/$/pat/g or :s/[a-z]* /(&)/g
- */
- skip:
- match = -1;
- lastone = (*old_match == NUL || got_int || got_quit || !do_all);
- if (lastone || do_ask ||
- (match = vim_regexec(prog, old_match, (int)FALSE)) == 0)
- {
- if (new_start)
- {
- /*
- * Copy the rest of the line, that didn't match.
- * Old_match has to be adjusted, we use the end of the
- * line as reference, because the substitute may have
- * changed the number of characters.
- */
- STRCAT(new_start, old_copy);
- i = old_line + STRLEN(old_line) - old_match;
- if (u_savesub(lnum) == OK)
- ml_replace(lnum, new_start, TRUE);
-
- vim_free(old_line); /* free the temp buffer */
- old_line = new_start;
- new_start = NULL;
- old_match = old_line + STRLEN(old_line) - i;
- if (old_match < old_line) /* safety check */
- {
- EMSG("do_sub internal error: old_match < old_line");
- old_match = old_line;
- }
- old_copy = old_line;
- }
- if (match == -1 && !lastone)
- match = vim_regexec(prog, old_match, (int)FALSE);
- if (match <= 0) /* quit loop if there is no more match */
- break;
- }
- line_breakcheck();
-
- }
- if (did_sub)
- ++sub_nlines;
- vim_free(old_line); /* free the copy of the original line */
- old_line = NULL;
- }
- line_breakcheck();
- }
-
- outofmem:
- vim_free(old_line); /* may have to free an allocated copy of the line */
- if (sub_nsubs)
- {
- CHANGED;
- if (!global_busy)
- {
- updateScreen(CURSUPD); /* need this to update LineSizes */
- beginline(TRUE);
- if (!do_sub_msg() && do_ask)
- MSG("");
- }
- if (do_print)
- print_line(curwin->w_cursor.lnum, FALSE);
- }
- else if (!global_busy)
- {
- if (got_int) /* interrupted */
- emsg(e_interr);
- else if (got_match) /* did find something but nothing substituted */
- MSG("");
- else /* nothing found */
- emsg(e_nomatch);
- }
-
- vim_free(prog);
- }
-
- /*
- * Give message for number of substitutions.
- * Can also be used after a ":global" command.
- * Return TRUE if a message was given.
- */
- static int
- do_sub_msg()
- {
- if (sub_nsubs > p_report)
- {
- sprintf((char *)msg_buf, "%s%ld substitution%s on %ld line%s",
- got_int ? "(Interrupted) " : "",
- sub_nsubs, plural(sub_nsubs),
- (long)sub_nlines, plural((long)sub_nlines));
- if (msg(msg_buf))
- keep_msg = msg_buf;
- return TRUE;
- }
- if (got_int)
- {
- emsg(e_interr);
- return TRUE;
- }
- return FALSE;
- }
-
- /*
- * do_glob(cmd)
- *
- * Execute a global command of the form:
- *
- * g/pattern/X : execute X on all lines where pattern matches
- * v/pattern/X : execute X on all lines where pattern does not match
- *
- * where 'X' is an EX command
- *
- * The command character (as well as the trailing slash) is optional, and
- * is assumed to be 'p' if missing.
- *
- * This is implemented in two passes: first we scan the file for the pattern and
- * set a mark for each line that (not) matches. secondly we execute the command
- * for each line that has a mark. This is required because after deleting
- * lines we do not know where to search for the next match.
- */
-
- void
- do_glob(type, lp, up, cmd)
- int type;
- linenr_t lp, up;
- char_u *cmd;
- {
- linenr_t lnum; /* line number according to old situation */
- linenr_t old_lcount; /* curbuf->b_ml.ml_line_count before the command */
- int ndone;
-
- char_u delim; /* delimiter, normally '/' */
- char_u *pat;
- regexp *prog;
- int match;
- int which_pat;
-
- if (global_busy)
- {
- EMSG("Cannot do :global recursive"); /* will increment global_busy */
- return;
- }
-
- which_pat = RE_LAST; /* default: use last used regexp */
- sub_nsubs = 0;
- sub_nlines = 0;
-
- /*
- * undocumented vi feature:
- * "\/" and "\?": use previous search pattern.
- * "\&": use previous substitute pattern.
- */
- if (*cmd == '\\')
- {
- ++cmd;
- if (vim_strchr((char_u *)"/?&", *cmd) == NULL)
- {
- emsg(e_backslash);
- return;
- }
- if (*cmd == '&')
- which_pat = RE_SUBST; /* use previous substitute pattern */
- else
- which_pat = RE_SEARCH; /* use previous search pattern */
- ++cmd;
- pat = (char_u *)"";
- }
- else if (*cmd == NUL)
- {
- EMSG("Regular expression missing from global");
- return;
- }
- else
- {
- delim = *cmd; /* get the delimiter */
- if (delim)
- ++cmd; /* skip delimiter if there is one */
- pat = cmd; /* remember start of pattern */
- cmd = skip_regexp(cmd, delim);
- if (cmd[0] == delim) /* end delimiter found */
- *cmd++ = NUL; /* replace it with a NUL */
- }
-
- if ((prog = myregcomp(pat, RE_BOTH, which_pat, SEARCH_HIS)) == NULL)
- {
- emsg(e_invcmd);
- return;
- }
-
- /*
- * pass 1: set marks for each (not) matching line
- */
- ndone = 0;
- for (lnum = lp; lnum <= up && !got_int; ++lnum)
- {
- /* a match on this line? */
- match = vim_regexec(prog, ml_get(lnum), (int)TRUE);
- if ((type == 'g' && match) || (type == 'v' && !match))
- {
- ml_setmarked(lnum);
- ndone++;
- }
- line_breakcheck();
- }
-
- /*
- * pass 2: execute the command for each line that has been marked
- */
- if (got_int)
- MSG("Interrupted");
- else if (ndone == 0)
- msg(e_nomatch);
- else
- {
- /*
- * Set current position only once for a global command.
- * If global_busy is set, setpcmark() will not do anything.
- * If there is an error, global_busy will be incremented.
- */
- setpcmark();
-
- global_busy = 1;
- old_lcount = curbuf->b_ml.ml_line_count;
- while (!got_int && (lnum = ml_firstmarked()) != 0 && global_busy == 1)
- {
- curwin->w_cursor.lnum = lnum;
- curwin->w_cursor.col = 0;
- if (*cmd == NUL || *cmd == '\n')
- do_cmdline((char_u *)"p", FALSE, TRUE);
- else
- do_cmdline(cmd, FALSE, TRUE);
- mch_breakcheck();
- }
-
- global_busy = 0;
-
- /*
- * Redraw everything. Could use CLEAR, which is faster in some
- * situations, but when there are few changes this makes the display
- * flicker.
- */
- must_redraw = NOT_VALID;
- cursupdate();
-
- /* If subsitutes done, report number of substitues, otherwise report
- * number of extra or deleted lines. */
- if (!do_sub_msg())
- msgmore(curbuf->b_ml.ml_line_count - old_lcount);
- }
-
- ml_clearmarked(); /* clear rest of the marks */
- vim_free(prog);
- }
-
- #ifdef VIMINFO
- int
- read_viminfo_sub_string(line, fp, force)
- char_u *line;
- FILE *fp;
- int force;
- {
- if (old_sub != NULL && force)
- vim_free(old_sub);
- if (force || old_sub == NULL)
- {
- viminfo_readstring(line);
- old_sub = strsave(line + 1);
- }
- return vim_fgets(line, LSIZE, fp);
- }
-
- void
- write_viminfo_sub_string(fp)
- FILE *fp;
- {
- if (get_viminfo_parameter('/') != 0 && old_sub != NULL)
- {
- fprintf(fp, "\n# Last Substitute String:\n$");
- viminfo_writestring(fp, old_sub);
- }
- }
- #endif /* VIMINFO */
-