1 /*
2 * replace.c
3 *
4 * Functions, formerly in search.c, which handle the search and replace
5 * functionality.
6 */
7
8 #include <stdio.h>
9 #include "estruct.h"
10 #include "eproto.h"
11 #include "edef.h"
12 #include "elang.h"
13
14 static int replen; /* length of replacement string */
15 static char *oldpatmatch = NULL; /* allocated memory for un-do.*/
16
17 /*
18 * sreplace -- Search and replace.
19 */
sreplace(f,n)20 int PASCAL NEAR sreplace(f, n)
21 int f; /* default flag */
22 int n; /* # of repetitions wanted */
23 {
24 return(replaces(FALSE, f, n));
25 }
26
27 /*
28 * qreplace -- search and replace with query.
29 */
qreplace(f,n)30 int PASCAL NEAR qreplace(f, n)
31 int f; /* default flag */
32 int n; /* # of repetitions wanted */
33 {
34 return(replaces(TRUE, f, n));
35 }
36
37 /*
38 * replaces -- Search for a string and replace it with another
39 * string. Query might be enabled (according to kind).
40 */
replaces(kind,f,n)41 int PASCAL NEAR replaces(kind, f, n)
42 int kind; /* Query enabled flag */
43 int f; /* default flag */
44 int n; /* # of repetitions wanted */
45 {
46 register int status; /* success flag on pattern inputs */
47 register int nummatch; /* number of found matches */
48 long numsub; /* number of substitutions */
49 int nlflag; /* last char of search string a <NL>? */
50 int nlrepl; /* was a replace done on the last line? */
51 char c; /* input char for query */
52 LINE *origline; /* original "." position */
53 int origoff; /* and offset (for . query option) */
54 LINE *lastline; /* position of last replace and */
55 int lastoff; /* offset (for 'u' query option) */
56 int oldmatchlen; /* Closure may alter the match length.*/
57
58 /*
59 * Don't allow this command if we are
60 * in read only mode.
61 */
62 if (curbp->b_mode & MDVIEW)
63 return (rdonly());
64
65 /* Check for negative repetitions.
66 */
67 if (f && n < 0)
68 return (FALSE);
69
70 /* Ask the user for the text of a pattern.
71 */
72 if ((status = readpattern(kind ? TEXT85 : TEXT84, (char *) &pat[0], TRUE)) != TRUE)
73 /* "Replace" */
74 /* "Query replace" */
75 return (status);
76
77 /* Ask for the replacement string, and get its length.
78 */
79 if ((status = readpattern(TEXT86, (char *) &rpat[0], FALSE)) == ABORT)
80 /* "with" */
81 return (status);
82
83 /* Set up flags so we can make sure not to do a recursive
84 * replace on the last line.
85 */
86 nlflag = (pat[strlen(pat) - 1] == '\r');
87 nlrepl = FALSE;
88
89 /* Save original . position, reset the number of matches and
90 * substitutions, and scan through the file.
91 */
92 origline = curwp->w_dotp;
93 origoff = curwp->w_doto;
94 numsub = 0L;
95 nummatch = 0;
96 lastline = (LINE *) NULL;
97 mmove_flag = FALSE; /* disable mouse move events */
98
99 while ((f == FALSE || n > nummatch) &&
100 (nlflag == FALSE || nlrepl == FALSE))
101 {
102 /* let the undo checkpoint out position each q-replacement */
103 if (kind)
104 undo_insert(OP_CMND, 1, obj);
105
106 /* Search for the pattern.
107 * If we search with a regular expression,
108 * matchlen is reset to the true length of
109 * the matched string.
110 */
111 #if MAGIC
112 if (magical && (curwp->w_bufp->b_mode & MDMAGIC)) {
113 if (!mcscanner(&mcpat[0], FORWARD, PTBEG, 1))
114 break;
115 }
116 else
117 if (!mcscanner(&mcdeltapat[0], FORWARD, PTBEG, 1))
118 break; /* all done */
119 #else
120 if (!scanner(FORWARD, PTBEG, 1))
121 break; /* all done */
122 #endif
123
124 ++nummatch; /* Increment # of matches */
125
126 /* Check if we are on the last line.
127 */
128 nlrepl = (lforw(curwp->w_dotp) == curwp->w_bufp->b_linep);
129
130 /* Check for query.
131 */
132 if (kind) {
133 /* Get the query.
134 */
135 pprompt: mlrquery();
136 qprompt:
137 /* Show the proposed place to change, and
138 * update the position on the modeline if needed.
139 */
140 if (posflag)
141 upmode();
142 update(TRUE);
143 c = tgetc(); /* and input */
144 mlerase(); /* and clear it */
145
146 /* And respond appropriately.
147 */
148 switch (c) {
149 #if FRENCH
150 case 'o': /* oui, substitute */
151 case 'O':
152 #endif
153 case 'y': /* yes, substitute */
154 case 'Y':
155 case 'l': /* last substitute */
156 case 'L':
157 case ' ':
158 break;
159
160 case 'n': /* no, onward */
161 case 'N':
162 forwchar(FALSE, 1);
163 continue;
164
165 case '!': /* yes/stop asking */
166 kind = FALSE;
167 break;
168
169 case 'u': /* undo last and re-prompt */
170 case 'U':
171 /* Restore old position.
172 */
173 if (lastline == (LINE *) NULL) {
174 /* There is nothing to undo.
175 */
176 TTbeep();
177 goto pprompt;
178 }
179 curwp->w_dotp = lastline;
180 curwp->w_doto = lastoff;
181 lastline = NULL;
182 lastoff = 0;
183
184 /* Delete the new string,
185 * restore the old match.
186 */
187 backchar(FALSE, replen);
188 status = delins(replen, oldpatmatch, FALSE);
189 if (status != TRUE) {
190 mmove_flag = TRUE;
191 return (status);
192 }
193
194 /* Record one less substitution,
195 * backup, save our place, and
196 * reprompt.
197 */
198 --numsub;
199 backchar(FALSE, oldmatchlen);
200 matchlen = oldmatchlen;
201 matchline = curwp->w_dotp;
202 matchoff = curwp->w_doto;
203 continue;
204
205 case '.': /* abort! and return */
206 /* restore old position */
207 curwp->w_dotp = origline;
208 curwp->w_doto = origoff;
209 curwp->w_flag |= WFMOVE;
210
211 case BELL: /* abort! and stay */
212 mlwrite(TEXT89);
213 /* "Aborted!" */
214 mmove_flag = TRUE;
215 return (FALSE);
216
217 default: /* bitch and beep */
218 TTbeep();
219
220 case '?': /* help me */
221 mlwrite(TEXT90);
222 /*"(Y)es, (N)o, (!)Do rest, (U)ndo last, (^G)Abort, (.)Abort back, (?)Help: "*/
223 goto qprompt;
224
225 } /* end of switch */
226 } /* end of "if kind" */
227
228 /* if this is the point origin, flag so we a can reset it */
229 if (curwp->w_dotp == origline) {
230 origline = NULL;
231 lastline = lback(curwp->w_dotp);
232 }
233
234 /* Delete the sucker, and insert its
235 * replacement.
236 */
237 #if MAGIC
238 status = delins(matchlen, (char *)&rpat[0], rmagical);
239 #else
240 status = delins(matchlen, (char *)&rpat[0], FALSE);
241 #endif
242 if (origline == NULL) {
243 origline = lforw(lastline);
244 origoff = 0;
245 }
246
247 if (status != TRUE) {
248 mmove_flag = TRUE;
249 return (status);
250 }
251
252 numsub++; /* increment # of substitutions */
253
254 /* Save our position, the match length, and the string
255 * matched if we are query-replacing, as we may undo
256 * the replacement. If we are not querying, check to
257 * make sure that we didn't replace an empty string
258 * (possible in MAGIC mode), because we'll infinite loop.
259 */
260 if (kind) {
261 if (c == 'l' || c == 'L')
262 break;
263 lastline = curwp->w_dotp;
264 lastoff = curwp->w_doto;
265 oldmatchlen = matchlen; /* Save the length for un-do.*/
266
267 if ((oldpatmatch = reroom(oldpatmatch, matchlen + 1)) == NULL) {
268 mlabort(TEXT94);
269 /* "%%Out of memory" */
270 mmove_flag = TRUE;
271 return(ABORT);
272 }
273 strcpy(oldpatmatch, patmatch);
274 }
275 else if (matchlen == 0) {
276 mlwrite(TEXT91);
277 /* "Empty string replaced, stopping." */
278 mmove_flag = TRUE;
279 return (FALSE);
280 }
281 }
282
283 /* And report the results.
284 */
285 mlwrite(TEXT92, numsub);
286 /* "%d substitutions" */
287 mmove_flag = TRUE;
288 return (TRUE);
289 }
290
291 /*
292 * mlrquery -- The prompt for query-replace-string.
293 */
mlrquery()294 VOID PASCAL NEAR mlrquery()
295 {
296 register int tcol;
297 #if MAGIC
298 register RMC *rmcptr;
299 #endif
300
301 mlwrite(TEXT87);
302 /* "Replace '" */
303
304 tcol = echostring(patmatch, strlen(TEXT87), NPAT / 2);
305
306 mlputs(TEXT88);
307 /* "' with '" */
308 tcol += strlen(TEXT88);
309
310 #if MAGIC
311 if (rmagical && (curwp->w_bufp->b_mode & MDMAGIC)) {
312 rmcptr = &rmcpat[0];
313
314 while (rmcptr->mc_type != MCNIL && tcol < NPAT - 8) {
315 if (rmcptr->mc_type == LITSTRING)
316 tcol = echostring(rmcptr->u.rstr, tcol, NPAT - 8);
317 else if (rmcptr->mc_type == DITTO)
318 tcol = echostring(patmatch, tcol, NPAT - 8);
319 else
320 tcol = echostring(grpmatch[rmcptr->u.group_no],
321 tcol, NPAT - 8);
322 rmcptr++;
323 }
324 }
325 else
326 #endif
327 echostring((char *) rpat, tcol, NPAT - 8);
328
329 mlputs("'? ");
330 }
331
332 /*
333 * delins -- Delete a specified length from the current point
334 * then either insert the string directly, or make use of
335 * replacement meta-array.
336 */
delins(dlength,instr,use_rmc)337 int PASCAL NEAR delins(dlength, instr, use_rmc)
338 int dlength;
339 char *instr;
340 int use_rmc;
341 {
342 register int status;
343 register char *rstr;
344 #if MAGIC
345 register RMC *rmcptr;
346 #endif
347
348 replen = 0;
349
350 /* Zap what we gotta,
351 * and insert its replacement.
352 */
353 if ((status = ldelete((long) dlength, FALSE)) != TRUE)
354 mlwrite(TEXT93);
355 /* "%%ERROR while deleting" */
356 else
357 #if MAGIC
358 if (use_rmc && (curwp->w_bufp->b_mode & MDMAGIC)) {
359 rmcptr = &rmcpat[0];
360 while (rmcptr->mc_type != MCNIL && status == TRUE) {
361 if (rmcptr->mc_type == LITSTRING)
362 status = linstr(rstr = rmcptr->u.rstr);
363 else if (rmcptr->mc_type == DITTO)
364 status = linstr(rstr = patmatch);
365 else
366 status = linstr(rstr = fixnull(grpmatch[rmcptr->u.group_no]));
367 replen += strlen(rstr);
368 rmcptr++;
369 }
370 }
371 else
372 #endif
373 {
374 status = linstr(instr);
375 replen = strlen(instr);
376 }
377
378 return (status);
379 }
380 #if MAGIC
381 /*
382 * rmcstr -- Set up the replacement 'magic' array. Note that if there
383 * are no meta-characters encountered in the replacement string,
384 * the array is never actually created - we will just use the
385 * character array rpat[] as the replacement string.
386 */
rmcstr()387 int PASCAL NEAR rmcstr()
388 {
389 RMC *rmcptr;
390 char *patptr;
391 int pchr;
392 int status = TRUE;
393 int mj;
394
395 patptr = (char *) &rpat[0];
396 rmcptr = &rmcpat[0];
397 mj = 0;
398 rmagical = FALSE;
399
400 while (*patptr && status == TRUE) {
401 switch (*patptr) {
402 case MC_DITTO:
403
404 /* If there were non-magical characters in
405 * the string before reaching this character
406 * plunk it in the replacement array before
407 * processing the current meta-character.
408 */
409 if (mj != 0) {
410 rmcptr->mc_type = LITSTRING;
411 if ((rmcptr->u.rstr = room(mj + 1)) == NULL) {
412 mlabort(TEXT94);
413 /* "%%Out of memory" */
414 status = FALSE;
415 break;
416 }
417 bytecopy(rmcptr->u.rstr, patptr - mj, mj);
418 rmcptr++;
419 mj = 0;
420 }
421 rmcptr->mc_type = DITTO;
422 rmcptr++;
423 rmagical = TRUE;
424 break;
425
426 case MC_ESC:
427 pchr = *(patptr + 1); /* peek at next char.*/
428 if (pchr <= '9' && pchr >= '1') {
429 if (mj != 0) {
430 rmcptr->mc_type = LITSTRING;
431 if ((rmcptr->u.rstr = room(mj + 1)) == NULL) {
432 mlabort(TEXT94);
433 /* "%%Out of memory" */
434 status = FALSE;
435 break;
436 }
437 bytecopy(rmcptr->u.rstr, patptr - mj, mj);
438 rmcptr++;
439 mj = 0;
440 }
441 rmcptr->mc_type = GROUP;
442 rmcptr->u.group_no = pchr - '0';
443 patptr++;
444 }
445 else
446 {
447 rmcptr->mc_type = LITSTRING;
448
449 /* We room mj plus two here, instead
450 * of one, because we have to count the
451 * current character.
452 */
453 if ((rmcptr->u.rstr = room(mj + 2)) == NULL) {
454 mlabort(TEXT94);
455 /* "%%Out of memory" */
456 status = FALSE;
457 break;
458 }
459
460 bytecopy(rmcptr->u.rstr, patptr - mj, mj + 1);
461
462 /* If MC_ESC is not the last character
463 * in the string, find out what it is
464 * escaping, and overwrite the last
465 * character with it.
466 */
467 if (pchr != '\0') {
468 *((rmcptr->u.rstr) + mj) = pchr;
469 patptr++;
470 }
471 mj = 0;
472 }
473
474 rmcptr++;
475 rmagical = TRUE;
476 break;
477
478 default:
479 mj++;
480 }
481 patptr++;
482 }
483
484 if (rmagical && mj > 0) {
485 rmcptr->mc_type = LITSTRING;
486 if ((rmcptr->u.rstr = room(mj + 1)) == NULL) {
487 mlabort(TEXT94);
488 /* "%%Out of memory" */
489 status = FALSE;
490 }
491 bytecopy(rmcptr->u.rstr, patptr - mj, mj);
492 rmcptr++;
493 }
494
495 rmcptr->mc_type = MCNIL;
496 #if DEBUG_SEARCH
497 rmc_list(0,0);
498 #endif
499 return (status);
500 }
501
502 /*
503 * rmcclear -- Free up any strings, and MCNIL the RMC array.
504 */
rmcclear()505 VOID PASCAL NEAR rmcclear()
506 {
507 register RMC *rmcptr;
508
509 rmcptr = &rmcpat[0];
510
511 while (rmcptr->mc_type != MCNIL) {
512 if (rmcptr->mc_type == LITSTRING)
513 free(rmcptr->u.rstr);
514 rmcptr++;
515 }
516
517 rmcpat[0].mc_type = MCNIL;
518 rmagical = FALSE;
519 if (oldpatmatch != NULL)
520 free(oldpatmatch);
521 oldpatmatch = NULL;
522 }
523 #endif
524