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