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