1 
2 
3 /* This version of microEmacs is based on the public domain C
4  * version written by Dave G. Conroy.
5  * The D programming language version is written by Walter Bright.
6  * http://www.digitalmars.com/d/
7  * This program is in the public domain.
8  */
9 
10 /*
11  * The functions in this file implement commands that search in the forward
12  * and backward directions. There are no special characters in the search
13  * strings. Probably should have a regular expression search, or something
14  * like that.
15  *
16  * REVISION HISTORY:
17  *
18  * ?    Steve Wilhite, 1-Dec-85
19  *      - massive cleanup on code.
20  */
21 
22 module search;
23 
24 import core.stdc.stdio;
25 import core.stdc.ctype;
26 import std..string;
27 import std.ascii;
28 import std.uni;
29 
30 import ed;
31 import line;
32 import display;
33 import window;
34 import main;
35 import buffer;
36 import basic;
37 import terminal;
38 
39 enum CASESENSITIVE = true;	/* TRUE means case sensitive		*/
40 enum WORDPREFIX = 'W' & 0x1F;	// prefix to trigger word search
41 
42 int Dnoask_search;
43 
44 /* Returns:
45  *	true if word character
46  */
47 bool isWordChar(char c)
48 {
49     return isalnum(c) || c == '_';
50 }
51 
52 /*
53  * Search forward. Get a search string from the user, and search, beginning at
54  * ".", for the string. If found, reset the "." to be just after the match
55  * string, and [perhaps] repaint the display. Bound to "C-S".
56  */
57 int forwsearch(bool f, int n)
58 {
59     if (winSearchPat)
60     {
61 	winSearchPat.w_flag |= WFHARD;
62 	winSearchPat = null;
63     }
64 
65     int s;
66     if ((s = readpattern("Search: ",pat)) != TRUE)
67         return (s);
68 
69     static bool notFound()
70     {
71 	mlwrite("Not found");
72 	return FALSE;
73     }
74 
75     bool word;
76     string pattern = pat;	// pattern to match
77     if (pattern.length == 0)
78 	return notFound();
79     word = pattern[0] == WORDPREFIX;  // ^D means only match words
80     if (word)
81     {
82 	pattern = pattern[1 .. $];
83 	if (pattern.length == 0)
84 	    return notFound();
85     }
86 
87     char p0 = pattern[0];		// first char to match
88 
89     LINE* clp = curwp.w_dotp;		/* get pointer to current line	*/
90     int cbo = curwp.w_doto;		/* and offset into that line	*/
91 
92     char lastc;
93 
94 again:
95     while (!empty(clp, cbo))		/* while not end of buffer	*/
96     {
97 	int c = front(clp, cbo);
98 	popFront(clp, cbo);
99 	if (!eq(c, p0))
100 	{
101 	    lastc = cast(char)c;
102 	    continue;
103 	}
104 	if (word && lastc != lastc.init && isWordChar(lastc))
105 	    continue;
106 	lastc = cast(char)c;
107 
108 	{
109             LINE* tlp = clp;
110             int tbo = cbo;			/* remember where start of pattern */
111 
112 	    foreach (pc; pattern[1 .. $])
113 	    {
114 		if (empty(tlp, tbo))
115 		    continue again;
116 		c = front(tlp, tbo);
117 		popFront(tlp, tbo);
118 
119 		lastc = cast(char)c;
120 
121                 if (!eq(c, pc))
122                     continue again;
123 	    }
124 
125 	    if (word && !empty(tlp, tbo) && isWordChar(cast(char)front(tlp, tbo)))
126 	    {
127 		continue again;
128 	    }
129 
130 	    /* We've found it. It starts at clp,cbo and ends before tlp,tbo */
131             curwp.w_dotp  = tlp;
132             curwp.w_doto  = tbo;
133             curwp.w_flag |= WFMOVE;
134             curwp.w_flag |= WFHARD;
135 	    winSearchPat = curwp;
136             return (TRUE);
137 	}
138     }
139 
140     return notFound();
141 }
142 
143 /*
144  * Reverse search. Get a search string from the user, and search, starting at
145  * "." and proceeding toward the front of the buffer. If found "." is left
146  * pointing at the first character of the pattern [the last character that was
147  * matched]. Bound to "C-R".
148  */
149 int backsearch(bool f, int n)
150 {
151     if (winSearchPat)
152     {
153 	winSearchPat.w_flag |= WFHARD;
154 	winSearchPat = null;
155     }
156 
157     int s;
158     if ((s = readpattern("Reverse search: ",pat)) != TRUE)
159         return (s);
160 
161     static bool notFound()
162     {
163 	mlwrite("Not found");
164 	return FALSE;
165     }
166 
167     bool word;
168     string pattern = pat;	// pattern to match
169     if (pattern.length == 0)
170 	return notFound();
171     word = pattern[0] == WORDPREFIX;  // ^D means only match words
172     if (word)
173     {
174 	pattern = pattern[1 .. $];
175 	if (pattern.length == 0)
176 	    return notFound();
177     }
178 
179     immutable(char)* epp = &pattern[$ - 1];
180 
181     LINE* clp = curwp.w_dotp;
182     int cbo = curwp.w_doto;
183 
184 again:
185     for (;;)
186     {
187 	if (atFront(clp, cbo))
188 	    return notFound();
189 
190         if (word && !empty(clp, cbo) && isWordChar(cast(char)front(clp, cbo)))
191 	{
192 	    popBack(clp, cbo);
193 	    continue;
194 	}
195 
196 	popBack(clp, cbo);
197 	int c = front(clp, cbo);
198 
199         if (eq(c, *epp))
200 	{
201             LINE* tlp = clp;
202             int tbo = cbo;
203             auto pp  = epp;
204 
205             while (pp != &pattern[0])
206 	    {
207 		if (atFront(tlp, tbo))
208 		    continue again;
209 		popBack(tlp, tbo);
210 		c = front(tlp, tbo);
211 
212                 if (!eq(c, *--pp))
213                     continue again;
214 	    }
215 
216 	    if (word && !atFront(tlp, tbo) && isWordChar(cast(char)peekBack(tlp, tbo)))
217 	    {
218 		continue again;
219 	    }
220 
221             curwp.w_dotp  = tlp;
222             curwp.w_doto  = tbo;
223             curwp.w_flag |= WFMOVE;
224             curwp.w_flag |= WFHARD;
225 	    winSearchPat = curwp;
226             return (TRUE);
227 	}
228     }
229     assert(0);
230 }
231 
232 /*
233  * Compare two characters. The "bc" comes from the buffer. It has it's case
234  * folded out. The "pc" is from the pattern.
235  */
236 
237 bool eq(int bc, int pc)
238 {
239     if (CASESENSITIVE)
240 	return bc == pc;
241 
242     if (bc>='a' && bc<='z')
243         bc -= 0x20;
244 
245     if (pc>='a' && pc<='z')
246         pc -= 0x20;
247 
248     return (bc == pc);
249 }
250 
251 /*********************************
252  * Replace occurrences of pat with withpat.
253  */
254 
255 int replacestring(bool f, int n)
256 {
257 	return replace(FALSE);
258 }
259 
260 /********************************
261  */
262 
263 int queryreplacestring(bool f, int n)
264 {
265 	return replace(TRUE);
266 }
267 
268 /*************************
269  * Do the replacements.
270  * Input:
271  *	query	if TRUE then it's a query-search-replace
272  */
273 
274 private int replace(bool query)
275 {
276     LINE * clp;
277     int    cbo;
278     LINE * tlp;
279     int    tbo;
280     int    c;
281     int    s;
282     int    numreplacements;
283     int    retval;
284     LINE*  dotpsave;
285     int dotosave;
286     string withpat;
287     int stop;
288 
289     if (winSearchPat)
290     {
291 	winSearchPat.w_flag |= WFHARD;
292 	winSearchPat = null;
293     }
294 
295     if ((s = readpattern("Replace: ", pat)) != TRUE)
296 	return (s);			/* must have search pattern	*/
297 
298     bool word;
299     string pattern = pat;	// pattern to match
300     if (pattern.length == 0)
301 	return FALSE;
302     word = pattern[0] == WORDPREFIX;  // ^D means only match words
303     if (word)
304     {
305 	pattern = pattern[1 .. $];
306 	if (pattern.length == 0)
307 	    return FALSE;
308     }
309 
310     readpattern ("With: ", withpat);	/* replacement pattern can be null */
311 
312     stop = FALSE;
313     retval = TRUE;
314     numreplacements = 0;
315     dotpsave = curwp.w_dotp;
316     dotosave = curwp.w_doto;		/* save original position	*/
317     clp = curwp.w_dotp;			/* get pointer to current line	 */
318     cbo = curwp.w_doto;			/* and offset into that line	 */
319 
320     curwp.w_flag |= WFHARD;		// repaint window with matches
321     winSearchPat = curwp;
322     update();
323 
324     auto p0 = pattern[0];
325 
326     char lastc;
327 
328 again:
329     while (!empty(clp, cbo))		/* while not end of buffer	 */
330     {
331 	/* Compute c, the character at the current position		 */
332 	c = front(clp, cbo);
333 	popFront(clp, cbo);
334 
335 	if (!eq(c, p0))
336 	{
337 	    lastc = cast(char)c;
338 	    continue;
339 	}
340 	if (word && lastc != lastc.init && isWordChar(lastc))
341 	    continue;
342 	lastc = cast(char)c;
343 
344 	{
345 	    tlp = clp;
346 	    tbo = cbo;			/* remember where start of pattern */
347 	    int i = 1;
348 
349 	    foreach (pc; pattern[1 .. $])
350 	    {
351 		if (empty(tlp, tbo))
352 		    continue again;
353 		c = front(tlp, tbo);
354 		popFront(tlp, tbo);
355 
356 		lastc = cast(char)c;
357 
358 		if (!eq(c, pc))
359 		    continue again;
360 		i++;
361 	    }
362 
363 	    if (word && !empty(tlp, tbo) && isWordChar(cast(char)front(tlp, tbo)))
364 	    {
365 		continue again;
366 	    }
367 
368 	    /* We've found it. It starts before clp,cbo and ends	*/
369 	    /* before tlp,tbo						*/
370 
371 	    /* If query, get user input about this			*/
372 	    if (query)
373 	    {
374 		curwp.w_dotp= clp;
375 		curwp.w_doto = cbo;
376 		backchar(FALSE,1);
377 		mlwrite("' ' change 'n' continue '!' change rest '.' change and stop ^G abort");
378 		curwp.w_flag |= WFMOVE;
379 	      tryagain:
380 		update();
381 		switch (getkey())
382 		{
383 		    case 'n':		/* don't change, but continue	*/
384 			continue again;
385 
386 		    /*case 'R':*/	/* enter recursive edit		*/
387 		    case '!':		/* change rest w/o asking	*/
388 			query = FALSE;
389 			goto case;
390 
391 		    case ' ':		/* change and continue to next	*/
392 			break;
393 		    case '.':		/* change and stop		*/
394 			stop = TRUE;
395 			break;
396 		    case 'G' & 0x1F:	/* abort			*/
397 			goto abortreplace;
398 		    default:		/* illegal command		*/
399 			term.t_beep();
400 			goto tryagain;
401 		}
402 	    }
403 
404 	    /* Delete the pattern by setting the current position to	*/
405 	    /* the start of the pattern, and deleting 'n' characters	*/
406 	    curwp.w_flag |= WFHARD;
407 	    curwp.w_dotp= clp;
408 	    curwp.w_doto = cbo;
409 	    if (backchar(FALSE,1) == FALSE ||
410 		line_delete(i,FALSE) == FALSE)
411 		goto L1;
412 
413 	    /* 'Yank' the replacement pattern back in at dot (also	*/
414 	    /* moving cursor past end of replacement pattern to prevent	*/
415 	    /* recursive replaces).					*/
416 	    foreach (wc; withpat)
417 		if (line_insert(1, wc) == FALSE)
418 		{
419 		    goto L1;
420 		}
421 
422 	    /* Take care of case where line_insert() reallocated the line	*/
423 	    if (dotpsave == clp)
424 		dotpsave = curwp.w_dotp;
425 	    clp = curwp.w_dotp;
426 	    cbo = curwp.w_doto;		/* continue from end of with text */
427 	    numreplacements++;
428 	    if (stop)
429 		break;
430 	}
431     }
432 
433 abortreplace:
434     curwp.w_dotp = dotpsave;
435     curwp.w_doto = dotosave;		/* back to original position	*/
436     curwp.w_flag |= WFMOVE;
437     mlwrite("%d replacements done", numreplacements);
438     return retval;
439 
440 L1:
441     if (dotpsave == clp)
442 	dotpsave = curwp.w_dotp;
443     retval = FALSE;
444     goto abortreplace;
445 }
446 
447 /*
448  * Read a pattern. Stash it in the external variable "pat". The "pat" is not
449  * updated if the user types in an empty line. If the user typed an empty line,
450  * and there is no old pattern, it is an error. Display the old pattern, in the
451  * style of Jeff Lomicka. There is some do-it-yourself control expansion.
452  */
453 private int readpattern(string prompt, ref string pat)
454 {
455     if( Dnoask_search )
456 	return( pat.length != 0 );
457     auto tpat = pat;
458     auto s = mlreply(prompt, pat, tpat);
459     if (s == TRUE)                      /* Specified */
460         pat = tpat;
461     else if (s == FALSE && pat.length != 0)         /* CR, but old one */
462         s = TRUE;
463 
464     return (s);
465 }
466 
467 /*********************************
468  * Examine line at '.'.
469  * Returns:
470  *	HASH_xxx
471  *	0	anything else
472  */
473 
474 enum
475 {
476 	HASH_IF		= 1,
477 	HASH_ELIF	= 2,
478 	HASH_ELSE	= 3,
479 	HASH_ENDIF	= 4,
480 }
481 
482 static int ifhash(LINE* clp)
483 {
484     int len;
485     int i;
486     static string[] hash = ["if","elif","else","endif"];
487 
488     len = cast(int)clp.l_text.length;
489     if (len < 3 || lgetc(clp,0) != '#')
490 	goto ret0;
491     for (i = 1; ; i++)
492     {
493 	if (i >= len)
494 	    goto ret0;
495 	if (!isSpace(clp.l_text[i]))
496 	    break;
497     }
498     for (int h = 0; h < hash.length; h++)
499 	if (len - i >= hash[h].length &&
500 	    clp.l_text[i .. i + hash[h].length] == hash[h])
501 	    return h + 1;
502 ret0:
503     return 0;
504 }
505 
506 /*********************************
507  * Search for the next occurence of the character at '.'.
508  * If character is a (){}[]<>, search for matching bracket.
509  * If '.' is on #if, #elif, or #else search for next #elif, #else or #endif.
510  * If '.' is on #endif, search backwards for corresponding #if.
511  */
512 
513 int search_paren(bool f, int n)
514 {
515     LINE* clp;
516     int cbo;
517     int len;
518     int i;
519     char chinc,chdec,ch;
520     int count;
521     int forward;
522     int h;
523     static char[2][] bracket = [['(',')'],['<','>'],['[',']'],['{','}']];
524 
525     clp = curwp.w_dotp;		/* get pointer to current line	*/
526     cbo = curwp.w_doto;		/* and offset into that line	*/
527     count = 0;
528 
529     len = llength(clp);
530     if (cbo >= len)
531 	chinc = '\n';
532     else
533 	chinc = lgetc(clp,cbo);
534 
535     if (cbo == 0 && (h = ifhash(clp)) != 0)
536     {	forward = h != HASH_ENDIF;
537     }
538     else
539     {
540 	forward = TRUE;			/* forward			*/
541 	h = 0;
542 	chdec = chinc;
543 	for (i = 0; i < bracket.length; i++)
544 	    if (bracket[i][0] == chinc)
545 	    {	chdec = bracket[i][1];
546 		break;
547 	    }
548 	for (i = 0; i < bracket.length; i++)
549 	    if (bracket[i][1] == chinc)
550 	    {	chdec = bracket[i][0];
551 		forward = FALSE;	/* search backwards		*/
552 		break;
553 	    }
554     }
555 
556     while (1)				/* while not end of buffer	*/
557     {
558 	if (forward)
559 	{
560 	    if (h || cbo >= len)
561 	    {
562 		clp = lforw(clp);
563 		if (clp == curbp.b_linep)	/* if end of buffer	*/
564 		    break;
565 		len = llength(clp);
566 		cbo = 0;
567 	    }
568 	    else
569 		cbo++;
570 	}
571 	else /* backward */
572 	{
573 	    if (h || cbo == 0)
574             {
575 		clp = lback(clp);
576 		if (clp == curbp.b_linep)
577 		    break;
578 		len = llength(clp);
579 		cbo = len;
580             }
581 	    else
582 		--cbo;
583 	}
584 
585 	if (h)
586 	{   int h2;
587 
588 	    cbo = 0;
589 	    h2 = ifhash(clp);
590 	    if (h2)
591 	    {	if (h == HASH_ENDIF)
592 		{
593 		    if (h2 == HASH_ENDIF)
594 			count++;
595 		    else if (h2 == HASH_IF)
596 		    {	if (count-- == 0)
597 			    goto found;
598 		    }
599 		}
600 		else
601 		{   if (h2 == HASH_IF)
602 			count++;
603 		    else
604 		    {	if (count == 0)
605 			    goto found;
606 			if (h2 == HASH_ENDIF)
607 			    count--;
608 		    }
609 		}
610 	    }
611 	}
612 	else
613 	{
614 	    ch = (cbo < len) ? lgetc(clp,cbo) : '\n';
615 	    if (eq(ch,chdec))
616 	    {   if (count-- == 0)
617 		{
618 		    /* We've found it	*/
619 		found:
620 		    curwp.w_dotp  = clp;
621 		    curwp.w_doto  = cbo;
622 		    curwp.w_flag |= WFMOVE;
623 		    return (TRUE);
624 		}
625 	    }
626 	    else if (eq(ch,chinc))
627 		count++;
628 	}
629     }
630     mlwrite("Not found");
631     return (FALSE);
632 }
633 
634 /****************************************************
635  * Determine if index is in a URL or not.
636  */
637 
638 bool inSearch(const(char)[] s, size_t index)
639 {
640     if (curwp != winSearchPat || pat.length == 0)
641 	return false;
642 
643     string pattern = pat;
644     bool word = pattern[0] == WORDPREFIX;
645     if (word)
646     {
647 	pattern = pattern[1 .. $];
648 	if (pattern.length == 0)
649 	    return false;
650     }
651 
652     char p0 = pattern[0];		// first char to match
653 
654     char lastc;
655 
656 again:
657     for (int i = 0; i <= s.length;)
658     {
659 	char front(int i) { return i < s.length ? s[i] : '\n'; }
660 
661 	if (index < i)
662 	    break;
663 
664 	char c = front(i);
665 	++i;
666 
667 	if (!eq(c, p0))
668 	{
669 	    lastc = cast(char)c;
670 	    continue;
671 	}
672 	if (word && lastc != lastc.init && isWordChar(lastc))
673 	    continue;
674 	lastc = c;
675 
676 	{
677 	    foreach (pc; pattern[1 .. $])
678 	    {
679 		if (i > s.length)
680 		    break again;
681 
682 		char c2 = front(i);
683 		++i;
684 
685 		lastc = c2;
686 
687                 if (!eq(c2, pc))
688                     continue again;
689 	    }
690 
691 	    if (word && i <= s.length && isWordChar(front(i)))
692 	    {
693 		continue again;
694 	    }
695 
696 	    if (index < i)
697 		return true;
698 	}
699     }
700 
701     return false;
702 }
703