1 /*
2  * The functions in this file implement commands that perform incremental
3  * searches in the forward and backward directions.  This "ISearch" command
4  * is intended to emulate the same command from the original EMACS
5  * implementation (ITS).  Contains references to routines internal to
6  * SEARCH.C.
7  *
8  * REVISION HISTORY:
9  *
10  *	D. R. Banks 9-May-86
11  *	- added ITS EMACSlike ISearch
12  *
13  *	John M. Gamble 5-Oct-86
14  *	- Made iterative search use search.c's scanner() routine.
15  *	  This allowed the elimination of bakscan().
16  *	- Put isearch constants into estruct.h
17  *	- Eliminated the passing of 'status' to scanmore() and
18  *	  checknext(), since there were no circumstances where
19  *	  it ever equalled FALSE.
20  *	Dan Corkill 6-Oct-87
21  *      - Changed character loop to terminate with extended characters
22  *        (thus arrow keys, and most commands behave intuitively).
23  *      - Changed META to be reread rather than simply aborting.
24  *      - Conditionalized VMS alternates for ^S and ^Q to only apply
25  *        to VMS ports.  (Allowing ^X as a synonym for ^S defeats some
26  *        of the benefits of the first change above.)
27  *
28  *	(Further comments are in history.c)
29  */
30 
31 #include <stdio.h>
32 #include "estruct.h"
33 #include "eproto.h"
34 #include "edef.h"
35 #include "elang.h"
36 
37 #if	ISRCH
38 
39 #define CMDBUFLEN	256	/* Length of our command buffer */
40 
41 /* A couple of "own" variables for re-eat */
42 /* Hey, BLISS user, these were "GLOBAL", I made them "OWN". */
43 static int	(PASCAL NEAR *saved_get_char)();	/* Get character routine */
44 static int	eaten_char = -1;	/* Re-eaten char */
45 
46 /* A couple more "own" variables for the command string */
47 
48 static int cmd_buff[CMDBUFLEN];	/* Save the command args here */
49 static int cmd_offset;	/* Current offset into command buff */
50 static int cmd_reexecute = -1;	/* > 0 if re-executing command */
51 
52 /*
53  * Subroutine to do incremental reverse search.  It actually uses the
54  * same code as the normal incremental search, as both can go both ways.
55  */
56 
risearch(f,n)57 int PASCAL NEAR risearch(f, n)
58 int f, n;				/* prefix flag and argument */
59 {
60 	register int	status;
61 
62 	/* Make sure the search doesn't match where we already
63 	 * are by backing up a character.
64 	 */
65 	backchar(TRUE, 1);
66 
67 	if (status = isearch(REVERSE))
68 		mlerase();		/* If happy, just erase the cmd line  */
69 	else
70 		mlwrite(TEXT164);
71 /*               "[search failed]" */
72 	return (status);
73 }
74 
75 /* Again, but for the forward direction */
76 
fisearch(f,n)77 int PASCAL NEAR fisearch(f, n)
78 int f, n;
79 {
80 	register int	 status;
81 
82 	if (status = isearch(FORWARD))
83 		mlerase();		/* If happy, just erase the cmd line  */
84 	else
85 		mlwrite(TEXT164);
86 /*               "[search failed]" */
87 	return (status);
88 }
89 
90 /*
91  * Subroutine to do an incremental search.  In general, this works similarly
92  * to the older micro-emacs search function, except that the search happens
93  * as each character is typed, with the screen and cursor updated with each
94  * new search character.
95  *
96  * While searching forward, each successive character will leave the cursor
97  * at the end of the entire matched string.  Typing a Control-S
98  * will cause the next occurrence of the string to be searched for (where the
99  * next occurrence does NOT overlap the current occurrence).  A Control-R will
100  * change to a backwards search, META will terminate the search and Control-G
101  * will abort the search.  Rubout will back up to the previous match of the
102  * string, or if the starting point is reached first, it will delete the
103  * last character from the search string.
104  *
105  * While searching backward, each successive character will leave the cursor
106  * at the beginning of the matched string.  Typing a Control-R will search
107  * backward for the next occurrence of the string.  Control-S
108  * will revert the search to the forward direction.  In general, the reverse
109  * incremental search is just like the forward incremental search inverted.
110  *
111  * In all cases, if the search fails, the user will be feeped, and the search
112  * will stall until the pattern string is edited back into something that
113  * exists (or until the search is aborted).
114  */
115 
isearch(dir)116 int PASCAL NEAR isearch(dir)
117 
118 int dir;
119 
120 {
121 	int 		status;	/* Search status */
122 	int 		col;	/* prompt column */
123 	register int	cpos;	/* character number in search string  */
124 	register int	c;	/* current input character */
125 	register int	expc;	/* function expanded input char 	  */
126 	char		pat_save[NPAT];	/* Saved copy of the old pattern str  */
127 	LINE		*curline;	/* Current line on entry		  */
128 	int 		curoff;	/* Current offset on entry		  */
129 	int 		init_direction;	/* The initial search direction 	  */
130 	KEYTAB		*ktp;	/* The command bound to the key 	  */
131 	register int (PASCAL NEAR *kfunc)();	/* ptr to the requested function to bind to */
132 
133 	/* Set up the starting conditions */
134 
135 	cmd_reexecute = -1;	/* We're not re-executing (yet?) */
136 	cmd_offset = 0;		/* Start at the beginning of cmd_buff */
137 	cmd_buff[0] = '\0';	/* Reset the command buffer */
138 	init_direction = dir;	/* Save the initial search direction */
139 	mmove_flag = FALSE;	/* disable mouse move events */
140 
141 	/*
142 	 * Save the current search string and line position, in case
143 	 * we bounce out if isearch.  Unmake the meta-character array,
144 	 * so that it will get re-made automatically with the (maybe)
145 	 * new search string on a MAGIC mode search.
146 	 */
147 	bytecopy(pat_save, (char *) pat, NPAT);	/* Save the old pattern string */
148 	curline = curwp->w_dotp;	/* Save the current line pointer */
149 	curoff = curwp->w_doto;	/* Save the current offset */
150 #if MAGIC
151 	mcclear();
152 #endif
153 
154 
155 start_over:				/* This is a good place to start a re-execution: */
156 
157 	/*
158 	 * Ask the user for the text of a pattern,
159 	 * and remember the column.
160 	 */
161 	col = (clexec) ? 0 : mlprompt(TEXT165, (char *) pat, isterm);
162 /*				"ISearch: " */
163 
164 	cpos = 0;			/* Start afresh 		  */
165 	status = TRUE;		/* Assume everything's cool   */
166 
167 	for (;;)
168 	{				/* ISearch per character loop */
169 
170 		/*
171 		 * Check for special characters first.
172 		 * That is, a control or ^X or FN or mouse function.
173 		 * Most cases here change the search.
174 		 */
175 		c = ectoc(expc = get_char());
176 
177 		if (expc == isterm)
178 		{			/* Want to quit searching?	  */
179 			setjtable();	/* Update jump tables...	  */
180 			mmove_flag = TRUE;
181 			return (TRUE);	/* Quit searching now		  */
182 		}
183 
184 		if (expc == abortc)	/* If abort search request	  */
185 			break;		/* Quit searching		  */
186 
187 		if (expc == quotec)			/* Quote character? 	  */
188 			c = ectoc(expc = get_char());	/* Get the next char		  */
189 		else if ((expc > 255 || expc == 0) &&
190 			(c != '\t' && c != '\r'))
191 		{
192 
193 			kfunc = ((ktp = getbind(expc)) == NULL) ? NULL : ktp->k_ptr.fp;
194 
195 			if (kfunc == forwsearch || kfunc == forwhunt ||
196 				kfunc == fisearch || kfunc == backsearch ||
197 				kfunc == backhunt || kfunc == risearch)
198 			{
199 				dir = (kfunc == backsearch ||
200 					kfunc == backhunt ||
201 					kfunc == risearch) ? REVERSE : FORWARD;
202 
203 				/*
204 				 * if cpos == 0 then we are either just starting
205 				 * or starting over.  Use the original pattern
206 				 * in pat, which has either not been changed or
207 				 * has just been restored. Find the length and
208 				 * re-echo the string.
209 				 */
210 				if (cpos == 0)
211 					{
212 					cpos = strlen((char *) pat);
213 					col = echostring((char *) pat, col, NPAT / 2);
214 					}
215 
216 				status = scanmore(dir);
217 				continue;
218 			}
219 			else if (kfunc == backdel)
220 			{
221 				/*
222 				 * If there's nothing to delete, just exit.
223 				 */
224 				if (cmd_offset <= 1)
225 				{
226 					mmove_flag = TRUE;
227 					return (TRUE);
228 				}
229 
230 				/* Back up over the Rubout and the character
231 				 * it's rubbing out.  If it is a quoted
232 				 * char, rub the quote char out too.
233 				 */
234 				cmd_offset -= 2;
235 				if (cmd_offset > 0 &&
236 					cmd_buff[cmd_offset - 1] == quotec)
237 					cmd_offset--;
238 				cmd_buff[cmd_offset] = '\0';
239 
240 				/*
241 				 * Reset everything - line and offset,
242 				 * search direction and pattern and
243 				 * jump tables, start the whole mess
244 				 * over (cmd_reexecute = 0) and
245 				 * let it take care of itself.
246 				 */
247 				curwp->w_dotp = curline;
248 				curwp->w_doto = curoff;
249 				dir = init_direction;
250 				bytecopy((char *) pat, pat_save, NPAT);
251 				setjtable();
252 				cmd_reexecute = 0;
253 				goto start_over;
254 			}
255 
256 			/*
257 			 * Presumably the key was a command key,
258 			 * yet strangely uninteresting...
259 			 */
260 			reeat(expc);	/* Re-eat the char		  */
261 			setjtable();
262 			mmove_flag = TRUE;
263 			return (TRUE);	/* And return the last status */
264 		}
265 
266 		/*
267 		 * I guess we got something to search for, so put the
268 		 * character in the buffer and search for it.
269 		 */
270 		pat[cpos++] = c;
271 
272 		/*
273 		 * Too many characters in the string?  Yup.  Complain
274 		 * about it, and restore the old search string and
275 		 * its jump tables.
276 		 */
277 		if (cpos >= NPAT)
278 		{
279 			mlwrite(TEXT166);
280 /*				"? Search string too long" */
281 			bytecopy((char *) pat, pat_save, NPAT);
282 			setjtable();
283 			mmove_flag = TRUE;
284 			return (FALSE);	/* Return an error, but stay. */
285 		}
286 
287 		pat[cpos] = 0;	/* null terminate the buffer  */
288 #if	COLOR
289 		/* set up the proper colors for the command line */
290 		TTforg(gfcolor);
291 		TTbacg(gbcolor);
292 #endif
293 		movecursor(term.t_nrow, col);	/* Position the cursor	*/
294 		col += echochar(c);	/* Echo the character		  */
295 		if (!status)	/* If we lost last time 	  */
296 			TTbeep();	/* Feep again		*/
297 		else 			/* Otherwise, we must have won*/
298 			status = checknext(c, dir);	/* See if still matches or find next */
299 	}
300 
301 	curwp->w_dotp = curline;	/* Reset the line pointer		*/
302 	curwp->w_doto = curoff;	/*   and the offset to original value	*/
303 	curwp->w_flag |= WFMOVE;	/* Say we've moved			*/
304 	update(FALSE);		/* And force an update			*/
305 	mmove_flag = TRUE;
306 	return (FALSE);
307 }
308 
309 /*
310  * This hack will search for the next occurrence of <pat> in the buffer,
311  * either forward or backward.  If we can't find any more matches, "point"
312  * is left where it was before.  If we do find a match, "point" will be at
313  * the end of the matched string for forward searches and at the beginning
314  * of the matched string for reverse searches.
315  */
316 
scanmore(dir)317 int PASCAL NEAR scanmore(dir)
318 int dir;				/* direction to search		*/
319 {
320 	register int	status;	/* search status		*/
321 
322 	setjtable();		/* Set up fast search arrays	*/
323 
324 #if MAGIC
325 	if (dir == FORWARD)
326 		status = mcscanner(&mcdeltapat[0], dir, PTEND, 1);
327 	else
328 		status = mcscanner(&tapatledcm[0], dir, PTBEG, 1);
329 #else
330 	status = scanner(dir, (dir == REVERSE) ? PTBEG : PTEND, 1);
331 #endif
332 
333 	if (!status)
334 		TTbeep();		/* Feep if search fails       */
335 
336 	return (status);
337 }
338 
339 /*
340  * Trivial routine to insure that the next character in the search
341  * string is still true to whatever we're pointing to in the buffer.
342  * This routine will not attempt to move the "point" if the match
343  * fails, although it will implicitly move the "point" if we're
344  * forward searching, and find a match, since that's the way forward
345  * isearch works.  If we are reverse searching we compare all
346  * characters in the pattern string from "point" to the new end.
347  *
348  * If the compare fails, we return FALSE and call scanmore or something.
349  */
350 
checknext(chr,dir)351 int PASCAL NEAR checknext(chr, dir)
352 int chr;				/* Next char to look for	*/
353 int dir;				/* Search direction 		*/
354 {
355 	LINE *curline;		/* current line during scan	*/
356 	int curoff;			/* position within current line	*/
357 	register char *patrn;	/* The entire search string (incl chr) */
358 	register int sts;	/* how well things go		*/
359 
360 	/* setup the local scan pointer to current "." */
361 
362 	curline = curwp->w_dotp;	/* Get the current line structure */
363 	curoff = curwp->w_doto;	/* Get the offset within that line */
364 
365 	if (dir == FORWARD)
366 		{				/* If searching forward		*/
367 		if (sts = !boundry(curline, curoff, FORWARD))
368 			{
369 			/* If it's what we're looking for, set the point
370 			 * and say that we've moved.
371 			 */
372 			if (sts = eq(nextch(&curline, &curoff, FORWARD), chr))
373 				{
374 				curwp->w_dotp = curline;
375 				curwp->w_doto = curoff;
376 				curwp->w_flag |= WFMOVE;
377 				}
378 			}
379 		}
380 	else {				/* Else, reverse search check. */
381 		patrn = (char *) pat;
382 		while (*patrn)
383 			{			/* Loop for all characters in patrn   */
384 			if ((sts = !boundry(curline, curoff, FORWARD)) == FALSE ||
385 				(sts = eq(nextch(&curline, &curoff, FORWARD), *patrn)) == FALSE)
386 				break;	/* Nope, just punt it then */
387 			patrn++;
388 			}
389 		}
390 
391 	/*
392 	 * If the 'next' character didn't fit in the pattern,
393 	 * let's go search for it somewhere else.
394 	 */
395 	if (sts == FALSE)
396 		sts = scanmore(dir);
397 
398 	return (sts);		/* And return the status */
399 }
400 
401 /*
402  * Routine to get the next character or extended character from the input
403  * stream.  If we're reading from the real terminal, force a screen update
404  * before we get the char.  Otherwise, we must be re-executing the command
405  * string, so just return the next character.
406  */
407 
get_char()408 int PASCAL NEAR get_char()
409 {
410 	int	c;
411 	KEYTAB	*key;
412 
413 	/* See if we're re-executing: */
414 
415 	if (cmd_reexecute >= 0)	/* Is there an offset?		*/
416 		if ((c = cmd_buff[cmd_reexecute++]) != 0)
417 			return (c);	/* Yes, return any character	*/
418 
419 	/* We're not re-executing (or aren't any more).  Try for a real char
420 	 */
421 	cmd_reexecute = -1;	/* Say we're in real mode again	*/
422 	update(FALSE);		/* Pretty up the screen		*/
423 	if (posflag)
424 		upmode();		/* and the modeline, if need be.*/
425 	if (cmd_offset >= CMDBUFLEN - 1)
426 		{				/* If we're getting too big ...	*/
427 		mlwrite(TEXT167);	/* Complain loudly and bitterly	*/
428 /*               "? command too long" */
429 		return (isterm);	/* And force a quit		*/
430 		}
431 
432 	/*
433 	 * If $isterm != meta character, then create the extended
434 	 * character as getcmd() does.
435 	 */
436 	if ((c = get_key()) == isterm)
437 		return (isterm);
438 	if ((key = getbind(c)) != NULL)
439 		{
440 		if (key->k_ptr.fp == cex || key->k_ptr.fp == meta)
441 			{
442 			c = get_key();
443 #if	SMOS
444 			c = upperc(c&255) | (c & ~255); /* Force to upper */
445 #else
446 			c = upperc(c) | (c & ~255);	/* Force to upper */
447 #endif
448 			c |= (key->k_ptr.fp == cex) ? CTLX : META;
449 			}
450 		}
451 
452 	cmd_buff[cmd_offset++] = c;	/* Save the char for next time	*/
453 	cmd_buff[cmd_offset] = '\0';	/* And terminate the buffer	*/
454 	return (c);			/* Return the character		*/
455 }
456 
457 /*
458  * Hacky routine to re-eat a character.  This will save the character to be
459  * re-eaten by redirecting the input call to a routine here.  Hack, etc.
460  *
461  * Come here on the next term.t_getchar call:
462  */
463 
uneat()464 int PASCAL NEAR uneat()
465 {
466 	int c;
467 
468 	term.t_getchar = saved_get_char;	/* restore the routine address	*/
469 	c = eaten_char;		/* Get the re-eaten char	*/
470 	eaten_char = -1;	/* Clear the old char		*/
471 	return (c);			/* and return the last char	*/
472 }
473 
reeat(c)474 VOID PASCAL NEAR reeat(c)
475 int	c;
476 {
477 	if (eaten_char != -1)	/* If we've already been here	*/
478 		return;			/* Don't do it again		*/
479 	eaten_char = c;		/* Else, save the char for later*/
480 	saved_get_char = term.t_getchar;	/* Save the char get routine	*/
481 	term.t_getchar = uneat;	/* Replace it with ours		*/
482 }
483 
484 #else
isearch(dir)485 int PASCAL NEAR isearch(dir)
486 int dir;
487 {
488 }
489 #endif
490