aboutsummaryrefslogtreecommitdiffstats
path: root/isearch.c
blob: dcc408bcbff48c41a4f4b5f7083abc78d5cf7d92 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
/*	isearch.c
 *
 * The functions in this file implement commands that perform incremental
 * searches in the forward and backward directions.  This "ISearch" command
 * is intended to emulate the same command from the original EMACS
 * implementation (ITS).  Contains references to routines internal to
 * SEARCH.C.
 *
 * REVISION HISTORY:
 *
 *	D. R. Banks 9-May-86
 *	- added ITS EMACSlike ISearch
 *
 *	John M. Gamble 5-Oct-86
 *	- Made iterative search use search.c's scanner() routine.
 *	  This allowed the elimination of bakscan().
 *	- Put isearch constants into estruct.h
 *	- Eliminated the passing of 'status' to scanmore() and
 *	  checknext(), since there were no circumstances where
 *	  it ever equalled FALSE.
 *
 *	Modified by Petri Kutvonen
 */

#include <stdio.h>

#include "estruct.h"
#include "edef.h"
#include "efunc.h"
#include "line.h"

#if	ISRCH

static int echo_char(int c, int col);

/* A couple of "own" variables for re-eat */

static int (*saved_get_char) (void);	/* Get character routine */
static int eaten_char = -1;		/* Re-eaten char */

/* A couple more "own" variables for the command string */

static int cmd_buff[CMDBUFLEN];	/* Save the command args here */
static int cmd_offset;			/* Current offset into command buff */
static int cmd_reexecute = -1;		/* > 0 if re-executing command */


/*
 * Subroutine to do incremental reverse search.  It actually uses the
 * same code as the normal incremental search, as both can go both ways.
 */
int risearch(int f, int n)
{
	struct line *curline;		/* Current line on entry              */
	int curoff;		/* Current offset on entry            */

	/* remember the initial . on entry: */

	curline = curwp->w_dotp;	/* Save the current line pointer      */
	curoff = curwp->w_doto;	/* Save the current offset            */

	/* Make sure the search doesn't match where we already are:               */

	backchar(TRUE, 1);	/* Back up a character                */

	if (!(isearch(f, -n))) {	/* Call ISearch backwards       *//* If error in search:                */
		curwp->w_dotp = curline;	/* Reset the line pointer             */
		curwp->w_doto = curoff;	/*  and the offset to original value  */
		curwp->w_flag |= WFMOVE;	/* Say we've moved                    */
		update(FALSE);	/* And force an update                */
		mlwrite("(search failed)");	/* Say we died                        */
#if	PKCODE
		matchlen = strlen(pat);
#endif
	} else
		mlerase();	/* If happy, just erase the cmd line  */
#if	PKCODE
	matchlen = strlen(pat);
#endif
	return TRUE;
}

/*
 * Again, but for the forward direction
 */
int fisearch(int f, int n)
{
	struct line *curline;		/* Current line on entry              */
	int curoff;		/* Current offset on entry            */

	/* remember the initial . on entry: */

	curline = curwp->w_dotp;	/* Save the current line pointer      */
	curoff = curwp->w_doto;	/* Save the current offset            */

	/* do the search */

	if (!(isearch(f, n))) {	/* Call ISearch forwards        *//* If error in search:                */
		curwp->w_dotp = curline;	/* Reset the line pointer             */
		curwp->w_doto = curoff;	/*  and the offset to original value  */
		curwp->w_flag |= WFMOVE;	/* Say we've moved                    */
		update(FALSE);	/* And force an update                */
		mlwrite("(search failed)");	/* Say we died                        */
#if	PKCODE
		matchlen = strlen(pat);
#endif
	} else
		mlerase();	/* If happy, just erase the cmd line  */
#if	PKCODE
	matchlen = strlen(pat);
#endif
	return TRUE;
}

/*
 * Subroutine to do an incremental search.  In general, this works similarly
 * to the older micro-emacs search function, except that the search happens
 * as each character is typed, with the screen and cursor updated with each
 * new search character.
 *
 * While searching forward, each successive character will leave the cursor
 * at the end of the entire matched string.  Typing a Control-S or Control-X
 * will cause the next occurrence of the string to be searched for (where the
 * next occurrence does NOT overlap the current occurrence).  A Control-R will
 * change to a backwards search, META will terminate the search and Control-G
 * will abort the search.  Rubout will back up to the previous match of the
 * string, or if the starting point is reached first, it will delete the
 * last character from the search string.
 *
 * While searching backward, each successive character will leave the cursor
 * at the beginning of the matched string.  Typing a Control-R will search
 * backward for the next occurrence of the string.  Control-S or Control-X
 * will revert the search to the forward direction.  In general, the reverse
 * incremental search is just like the forward incremental search inverted.
 *
 * In all cases, if the search fails, the user will be feeped, and the search
 * will stall until the pattern string is edited back into something that
 * exists (or until the search is aborted).
 */

int isearch(int f, int n)
{
	int status;		/* Search status */
	int col;		/* prompt column */
	int cpos;	/* character number in search string  */
	int c;		/* current input character */
	int expc;	/* function expanded input char       */
	char pat_save[NPAT];	/* Saved copy of the old pattern str  */
	struct line *curline;		/* Current line on entry              */
	int curoff;		/* Current offset on entry            */
	int init_direction;	/* The initial search direction       */

	/* Initialize starting conditions */

	cmd_reexecute = -1;	/* We're not re-executing (yet?)      */
	cmd_offset = 0;		/* Start at the beginning of the buff */
	cmd_buff[0] = '\0';	/* Init the command buffer            */
	strncpy(pat_save, pat, NPAT);	/* Save the old pattern string        */
	curline = curwp->w_dotp;	/* Save the current line pointer      */
	curoff = curwp->w_doto;	/* Save the current offset            */
	init_direction = n;	/* Save the initial search direction  */

	/* This is a good place to start a re-execution: */

      start_over:

	/* ask the user for the text of a pattern */
	col = promptpattern("ISearch: ");	/* Prompt, remember the col   */

	cpos = 0;		/* Start afresh               */
	status = TRUE;		/* Assume everything's cool   */

	/*
	   Get the first character in the pattern.  If we get an initial Control-S
	   or Control-R, re-use the old search string and find the first occurrence
	 */

	c = ectoc(expc = get_char());	/* Get the first character    */
	if ((c == IS_FORWARD) || (c == IS_REVERSE) || (c == IS_VMSFORW)) {	/* Reuse old search string?   */
		for (cpos = 0; pat[cpos] != 0; cpos++)	/* Yup, find the length           */
			col = echo_char(pat[cpos], col);	/*  and re-echo the string    */
		if (c == IS_REVERSE) {	/* forward search?            */
			n = -1;	/* No, search in reverse      */
			backchar(TRUE, 1);	/* Be defensive about EOB     */
		} else
			n = 1;	/* Yes, search forward        */
		status = scanmore(pat, n);	/* Do the search              */
		c = ectoc(expc = get_char());	/* Get another character      */
	}

	/* Top of the per character loop */

	for (;;) {		/* ISearch per character loop */
		/* Check for special characters first: */
		/* Most cases here change the search */

		if (expc == metac)	/* Want to quit searching?    */
			return TRUE;	/* Quit searching now         */

		switch (c) {	/* dispatch on the input char */
		case IS_ABORT:	/* If abort search request    */
			return FALSE;	/* Quit searching again       */

		case IS_REVERSE:	/* If backward search         */
		case IS_FORWARD:	/* If forward search          */
		case IS_VMSFORW:	/*  of either flavor          */
			if (c == IS_REVERSE)	/* If reverse search              */
				n = -1;	/* Set the reverse direction  */
			else	/* Otherwise,                     */
				n = 1;	/*  go forward                */
			status = scanmore(pat, n);	/* Start the search again     */
			c = ectoc(expc = get_char());	/* Get the next char          */
			continue;	/* Go continue with the search */

		case IS_NEWLINE:	/* Carriage return            */
			c = '\n';	/* Make it a new line         */
			break;	/* Make sure we use it        */

		case IS_QUOTE:	/* Quote character            */
		case IS_VMSQUOTE:	/*  of either variety         */
			c = ectoc(expc = get_char());	/* Get the next char          */

		case IS_TAB:	/* Generically allowed        */
		case '\n':	/*  controlled characters     */
			break;	/* Make sure we use it        */

		case IS_BACKSP:	/* If a backspace:            */
		case IS_RUBOUT:	/*  or if a Rubout:           */
			if (cmd_offset <= 1)	/* Anything to delete?            */
				return TRUE;	/* No, just exit              */
			--cmd_offset;	/* Back up over the Rubout    */
			cmd_buff[--cmd_offset] = '\0';	/* Yes, delete last char   */
			curwp->w_dotp = curline;	/* Reset the line pointer     */
			curwp->w_doto = curoff;	/*  and the offset            */
			n = init_direction;	/* Reset the search direction */
			strncpy(pat, pat_save, NPAT);	/* Restore the old search str */
			cmd_reexecute = 0;	/* Start the whole mess over  */
			goto start_over;	/* Let it take care of itself */

			/* Presumably a quasi-normal character comes here */

		default:	/* All other chars            */
			if (c < ' ') {	/* Is it printable?             *//* Nope.                      */
				reeat(c);	/* Re-eat the char            */
				return TRUE;	/* And return the last status */
			}
		}		/* Switch */

		/* I guess we got something to search for, so search for it           */

		pat[cpos++] = c;	/* put the char in the buffer */
		if (cpos >= NPAT) {	/* too many chars in string?  *//* Yup.  Complain about it    */
			mlwrite("? Search string too long");
			return TRUE;	/* Return an error            */
		}
		pat[cpos] = 0;	/* null terminate the buffer  */
		col = echo_char(c, col);	/* Echo the character         */
		if (!status) {	/* If we lost last time       */
			TTputc(BELL);	/* Feep again                 */
			TTflush();	/* see that the feep feeps    */
		} else /* Otherwise, we must have won */ if (!(status = checknext(c, pat, n)))	/* See if match         */
			status = scanmore(pat, n);	/*  or find the next match    */
		c = ectoc(expc = get_char());	/* Get the next char          */
	}			/* for {;;} */
}

/*
 * Trivial routine to insure that the next character in the search string is
 * still true to whatever we're pointing to in the buffer.  This routine will
 * not attempt to move the "point" if the match fails, although it will 
 * implicitly move the "point" if we're forward searching, and find a match,
 * since that's the way forward isearch works.
 *
 * If the compare fails, we return FALSE and assume the caller will call
 * scanmore or something.
 *
 * char chr;		Next char to look for
 * char *patrn;		The entire search string (incl chr)
 * int dir;		Search direction
 */
int checknext(char chr, char *patrn, int dir)	/* Check next character in search string */
{
	struct line *curline;	/* current line during scan           */
	int curoff;	/* position within current line       */
	int buffchar;	/* character at current position      */
	int status;		/* how well things go                 */


	/* setup the local scan pointer to current "." */

	curline = curwp->w_dotp;		/* Get the current line structure     */
	curoff = curwp->w_doto;			/* Get the offset within that line    */

	if (dir > 0) {				/* If searching forward                 */
		if (curoff == llength(curline)) {		/* If at end of line                    */
			curline = lforw(curline);		/* Skip to the next line              */
			if (curline == curbp->b_linep)
				return FALSE;			/* Abort if at end of buffer          */
			curoff = 0;				/* Start at the beginning of the line */
			buffchar = '\n';			/* And say the next char is NL        */
		} else
			buffchar = lgetc(curline, curoff++);	/* Get the next char         */
		if ((status = eq(buffchar, chr)) != 0) {	/* Is it what we're looking for?      */
			curwp->w_dotp = curline;		/* Yes, set the buffer's point        */
			curwp->w_doto = curoff;			/*  to the matched character          */
			curwp->w_flag |= WFMOVE;		/* Say that we've moved               */
		}
		return status;		/* And return the status              */
	} else					/* Else, if reverse search:       */
		return match_pat(patrn);	/* See if we're in the right place    */
}

/*
 * This hack will search for the next occurrence of <pat> in the buffer, either
 * forward or backward.  It is called with the status of the prior search
 * attempt, so that it knows not to bother if it didn't work last time.  If
 * we can't find any more matches, "point" is left where it was before.  If
 * we do find a match, "point" will be at the end of the matched string for
 * forward searches and at the beginning of the matched string for reverse
 * searches.
 *
 * char *patrn;			string to scan for
 * int dir;			direction to search
 */
int scanmore(char *patrn, int dir)	/* search forward or back for a pattern           */
{
	int sts;		/* search status                      */

	if (dir < 0) {		/* reverse search?              */
		rvstrcpy(tap, patrn);	/* Put reversed string in tap */
		sts = scanner(tap, REVERSE, PTBEG);
	} else
		sts = scanner(patrn, FORWARD, PTEND);	/* Nope. Go forward   */

	if (!sts) {
		TTputc(BELL);	/* Feep if search fails       */
		TTflush();	/* see that the feep feeps    */
	}

	return sts;		/* else, don't even try       */
}

/*
 * The following is a worker subroutine used by the reverse search.  It
 * compares the pattern string with the characters at "." for equality. If
 * any characters mismatch, it will return FALSE.
 *
 * This isn't used for forward searches, because forward searches leave "."
 * at the end of the search string (instead of in front), so all that needs to
 * be done is match the last char input.
 *
 * char *patrn;			String to match to buffer
 */
int match_pat(char *patrn)	/* See if the pattern string matches string at "."   */
{
	int i;		/* Generic loop index/offset          */
	int buffchar;	/* character at current position      */
	struct line *curline;	/* current line during scan           */
	int curoff;	/* position within current line       */

	/* setup the local scan pointer to current "." */

	curline = curwp->w_dotp;	/* Get the current line structure     */
	curoff = curwp->w_doto;	/* Get the offset within that line    */

	/* top of per character compare loop: */

	for (i = 0; i < strlen(patrn); i++) {	/* Loop for all characters in patrn   */
		if (curoff == llength(curline)) {	/* If at end of line                    */
			curline = lforw(curline);	/* Skip to the next line              */
			curoff = 0;	/* Start at the beginning of the line */
			if (curline == curbp->b_linep)
				return FALSE;	/* Abort if at end of buffer          */
			buffchar = '\n';	/* And say the next char is NL        */
		} else
			buffchar = lgetc(curline, curoff++);	/* Get the next char         */
		if (!eq(buffchar, patrn[i]))	/* Is it what we're looking for?      */
			return FALSE;	/* Nope, just punt it then            */
	}
	return TRUE;		/* Everything matched? Let's celebrate */
}

/*
 * Routine to prompt for I-Search string.
 */
int promptpattern(char *prompt)
{
	char tpat[NPAT + 20];

	strcpy(tpat, prompt);	/* copy prompt to output string */
	strcat(tpat, " (");	/* build new prompt string */
	expandp(pat, &tpat[strlen(tpat)], NPAT / 2);	/* add old pattern */
	strcat(tpat, ")<Meta>: ");

	/* check to see if we are executing a command line */
	if (!clexec) {
		mlwrite(tpat);
	}
	return strlen(tpat);
}

/*
 * routine to echo i-search characters
 *
 * int c;		character to be echoed
 * int col;		column to be echoed in
 */
static int echo_char(int c, int col)
{
	movecursor(term.t_nrow, col);	/* Position the cursor        */
	if ((c < ' ') || (c == 0x7F)) {	/* Control character?           */
		switch (c) {	/* Yes, dispatch special cases */
		case '\n':	/* Newline                    */
			TTputc('<');
			TTputc('N');
			TTputc('L');
			TTputc('>');
			col += 3;
			break;

		case '\t':	/* Tab                        */
			TTputc('<');
			TTputc('T');
			TTputc('A');
			TTputc('B');
			TTputc('>');
			col += 4;
			break;

		case 0x7F:	/* Rubout:                    */
			TTputc('^');	/* Output a funny looking     */
			TTputc('?');	/*  indication of Rubout      */
			col++;	/* Count the extra char       */
			break;

		default:	/* Vanilla control char       */
			TTputc('^');	/* Yes, output prefix         */
			TTputc(c + 0x40);	/* Make it "^X"               */
			col++;	/* Count this char            */
		}
	} else
		TTputc(c);	/* Otherwise, output raw char */
	TTflush();		/* Flush the output           */
	return ++col;		/* return the new column no   */
}

/*
 * Routine to get the next character from the input stream.  If we're reading
 * from the real terminal, force a screen update before we get the char. 
 * Otherwise, we must be re-executing the command string, so just return the
 * next character.
 */
int get_char(void)
{
	int c;			/* A place to get a character         */

	/* See if we're re-executing: */

	if (cmd_reexecute >= 0)	/* Is there an offset?                    */
		if ((c = cmd_buff[cmd_reexecute++]) != 0)
			return c;	/* Yes, return any character          */

	/* We're not re-executing (or aren't any more).  Try for a real char      */

	cmd_reexecute = -1;	/* Say we're in real mode again       */
	update(FALSE);		/* Pretty up the screen               */
	if (cmd_offset >= CMDBUFLEN - 1) {	/* If we're getting too big ...         */
		mlwrite("? command too long");	/* Complain loudly and bitterly       */
		return metac;	/* And force a quit                   */
	}
	c = get1key();		/* Get the next character             */
	cmd_buff[cmd_offset++] = c;	/* Save the char for next time        */
	cmd_buff[cmd_offset] = '\0';	/* And terminate the buffer           */
	return c;		/* Return the character               */
}

/*
 * Hacky routine to re-eat a character.  This will save the character to be
 * re-eaten by redirecting the input call to a routine here.  Hack, etc.
 */

/* Come here on the next term.t_getchar call: */

int uneat(void)
{
	int c;

	term.t_getchar = saved_get_char;	/* restore the routine address        */
	c = eaten_char;		/* Get the re-eaten char              */
	eaten_char = -1;	/* Clear the old char                 */
	return c;		/* and return the last char           */
}

void reeat(int c)
{
	if (eaten_char != -1)	/* If we've already been here             */
		return /*(NULL) */ ;	/* Don't do it again                  */
	eaten_char = c;		/* Else, save the char for later      */
	saved_get_char = term.t_getchar;	/* Save the char get routine          */
	term.t_getchar = uneat;	/* Replace it with ours               */
}
#else
int isearch(int f, int n)
{
}
#endif