1 /*    e_search.cpp
2  *
3  *    Copyright (c) 1994-1996, Marko Macek
4  *
5  *    You may distribute under the terms of either the GNU General Public
6  *    License or the Artistic License, as specified in the README file.
7  *
8  */
9 
10 #include "c_history.h"
11 #include "e_tags.h"
12 #include "i_modelview.h"
13 #include "i_view.h"
14 #include "o_buflist.h"
15 #include "s_util.h"
16 #include "sysdep.h"
17 
18 #include <ctype.h>
19 
SearchReplaceOptions()20 SearchReplaceOptions::SearchReplaceOptions()
21 {
22     memset(this, 0, sizeof(*this));
23 }
24 
25 // *INDENT-OFF*
ParseSearchOption(int replace,char c,unsigned long & opt)26 int ParseSearchOption(int replace, char c, unsigned long &opt) {
27     switch (tolower(c)) {
28     case 'a': opt |= SEARCH_ALL; break;      // search all occurances
29     case 'b': opt |= SEARCH_BLOCK; break;    // search in block only
30     case 'c': opt &= ~SEARCH_NEXT; break;    // search from current position
31     case 'd': opt |= SEARCH_DELETE; break;   // delete found line
32     case 'g': opt |= SEARCH_GLOBAL; break;   // search globally
33     case 'i': opt |= SEARCH_NCASE; break;    // don't match case
34     case 'j': opt |= SEARCH_JOIN; break;     // join found line
35     case 'r': opt |= SEARCH_BACK; break;     // search reverse
36     case 'w': opt |= SEARCH_WORDBEG | SEARCH_WORDEND; break;
37     case '<': opt |= SEARCH_WORDBEG; break;
38     case '>': opt |= SEARCH_WORDEND; break;
39     case 'x': opt |= SEARCH_RE; break;       // search using regexps
40     default:
41         if (!replace) return 0;
42         switch (c) {
43         case 'n': opt |= SEARCH_NASK; break; // don't ask before replacing
44         default:
45             return 0;
46         }
47     }
48     return 1;
49 }
50 
UnquoteString(char * str)51 static int UnquoteString(char *str) {
52     char *s, *d;
53 
54     s = str;
55     d = str;
56     while (*s) {
57         if (*s == '\\') {
58             s++;
59             if (*s == 0) return 0;
60         }
61         *d++ = *s++;
62     }
63     *d = 0;
64     return 1;
65 }
66 
ParseSearchOptions(int replace,const char * str,unsigned long & Options)67 int ParseSearchOptions(int replace, const char *str, unsigned long &Options) {
68     const char *p = str;
69 
70     Options = SEARCH_NEXT;
71     while (*p) {
72         if (ParseSearchOption(replace, *p, Options) == 0) return 0;
73         p++;
74     }
75     return 1;
76 }
77 
ParseSearchReplace(EBuffer * B,const char * str,int replace,SearchReplaceOptions & opt)78 int ParseSearchReplace(EBuffer *B, const char *str, int replace, SearchReplaceOptions &opt) {
79     int where = 0;
80     int len = 0;
81     const char *p = str;
82 
83     memset((void *)&opt, 0, sizeof(opt));
84     if (p == 0) return 0;
85     if (replace) {
86         if (ParseSearchOptions(replace, BFS(B, BFS_DefFindReplaceOpt), opt.Options) == 0) return 0;
87         opt.Options |= SEARCH_REPLACE;
88     } else {
89         if (ParseSearchOptions(replace, BFS(B, BFS_DefFindOpt), opt.Options) == 0) return 0;
90     }
91 
92     while (*p) {
93         switch (*p) {
94         case '\\':
95             if (where == 0) {
96                 opt.strSearch[len++] = *p++;
97                 if (*p == 0) return 0;
98                 opt.strSearch[len++] = *p++;
99             } else if (where == 1) {
100                 opt.strReplace[len++] = *p++;
101                 if (*p == 0) return 0;
102                 opt.strReplace[len++] = *p++;
103             } else
104                 return 0;
105             break;
106         case '/':
107             where++;
108             if (!replace && where == 1) where++;
109             if (where == 2)
110                 opt.Options = SEARCH_NEXT;
111             if (where > 2) return 0;
112             len = 0;
113             p++;
114             break;
115         default:
116             if (where == 0)
117                 opt.strSearch[len++] = *p++;
118             else if (where == 1)
119                 opt.strReplace[len++] = *p++;
120             else {
121                 char c = *p;
122 
123                 p++;
124                 if (ParseSearchOption(replace, c, opt.Options) == 0) return 0;
125             }
126         }
127     }
128     if (opt.Options & SEARCH_RE);
129     else {
130         if (UnquoteString(opt.strSearch) == 0) return 0;
131         if (opt.Options & SEARCH_REPLACE)
132             if (UnquoteString(opt.strReplace) == 0) return 0;
133     }
134 
135     opt.ok = 1;
136     return 1;
137 }
138 
FindStr(const char * Data,int Len,int Options)139 int EBuffer::FindStr(const char *Data, int Len, int Options) {
140     SearchReplaceOptions opt;
141 
142     opt.Options = Options;
143 
144     return FindStr(Data, Len, opt);
145 }
146 
FindStr(const char * Data,int Len,SearchReplaceOptions & opt)147 int EBuffer::FindStr(const char *Data, int Len, SearchReplaceOptions &opt) {
148     int Options = opt.Options;
149     int LLen, Start, End;
150     int C, L;
151     PELine X;
152     char *P;
153 
154     if (Options & SEARCH_RE)
155         return 0;
156     if (Len <= 0)
157         return 0;
158 
159     if (Options & SEARCH_NOPOS) {
160         C = Match.Col;
161         L = Match.Row;
162     } else {
163         C = CP.Col;
164         L = VToR(CP.Row);
165     }
166     if (Match.Row != -1)
167         Draw(Match.Row, Match.Row);
168     Match.Row = -1;
169     Match.Col = -1;
170     X = RLine(L);
171     C = CharOffset(X, C);
172 
173     if (Options & SEARCH_NEXT) {
174         int CC = MatchCount ? 1 : 0;
175 
176         if (Options & SEARCH_BACK) {
177             C -= CC;
178             if (C < 0) {
179                 if (L == 0) return 0;
180                 L--;
181                 X = RLine(L);
182                 C = X->Count;
183             }
184         } else {
185             if (Options & SEARCH_REPLACE &&
186                 opt.lastInsertLen > 0)
187             {
188                 C += CC * opt.lastInsertLen; // 0 or opt.lastInsertLen
189             } else
190             {
191                 C += CC;
192             }
193 
194             if (C >= X->Count) {
195                 C = 0;
196                 L++;
197                 if (L == RCount) return 0;
198             }
199         }
200     }
201     MatchLen = 0;
202     MatchCount = 0;
203 
204     if (Options & SEARCH_BLOCK) {
205         if (Options & SEARCH_BACK) {
206             if (BlockMode == bmStream) {
207                 if (L > BE.Row) {
208                     L = BE.Row;
209                     C = BE.Col;
210                 }
211                 if (L == BE.Row && C > BE.Col)
212                     C = BE.Col;
213             } else {
214                 if (L >= BE.Row && BE.Row > 0) {
215                     L = BE.Row - 1;
216                     C = RLine(L)->Count;
217                 }
218                 if (BlockMode == bmColumn)
219                     if (L == BE.Row - 1 && C >= BE.Col)
220                         C = BE.Col;
221             }
222         } else {
223             if (L < BB.Row) {
224                 L = BB.Row;
225                 C = 0;
226             }
227             if (L == BB.Row && C < BB.Col)
228                 C = BB.Col;
229         }
230     }
231     while (1) {
232         if (Options & SEARCH_BLOCK) {
233             if (BlockMode == bmStream) {
234                 if (L > BE.Row || L < BB.Row) break;
235             } else
236                 if (L >= BE.Row || L < BB.Row) break;
237         } else
238             if (L >= RCount || L < 0) break;
239 
240         X = RLine(L);
241 
242         LLen = X->Count;
243         P = X->Chars;
244         Start = 0;
245         End = LLen;
246 
247         if (Options & SEARCH_BLOCK) {
248             if (BlockMode == bmColumn) {
249                 Start = CharOffset(X, BB.Col);
250                 End = CharOffset(X, BE.Col);
251             } else if (BlockMode == bmStream) {
252                 if (L == BB.Row)
253                     Start = CharOffset(X, BB.Col);
254                 if (L == BE.Row)
255                     End = CharOffset(X, BE.Col);
256             }
257         }
258         if (Options & SEARCH_BACK) {
259             if (C >= End - Len)
260                 C = End - Len;
261         } else {
262             if (C < Start)
263                 C = Start;
264         }
265 
266         while (((!(Options & SEARCH_BACK)) && (C <= End - Len))
267                || ((Options & SEARCH_BACK) && (C >= Start))) {
268             if ((!(Options & SEARCH_WORDBEG)
269                  || (C == 0)
270                  || (WGETBIT(Flags.WordChars, P[C - 1]) == 0))
271                 &&
272                 (!(Options & SEARCH_WORDEND)
273                  || (C + Len >= End)
274                  || (WGETBIT(Flags.WordChars, P[C + Len]) == 0))
275                 &&
276                 ((!(Options & SEARCH_NCASE)
277                   && (P[C] == Data[0])
278                   && (memcmp(P + C, Data, Len) == 0))
279                  ||
280                  ((Options & SEARCH_NCASE)
281                   && (toupper(P[C]) == toupper(Data[0]))
282                   && (strnicmp(P + C, Data, Len) == 0))) /* && BOL | EOL */
283                )
284             {
285                 Match.Col = ScreenPos(X, C);
286                 Match.Row = L;
287                 MatchCount = Len;
288                 MatchLen = ScreenPos(X, C + Len) - Match.Col;
289                 if (!(Options & SEARCH_NOPOS)) {
290                     if (Options & SEARCH_CENTER)
291                         CenterPosR(Match.Col, Match.Row);
292                     else
293                         SetPosR(Match.Col, Match.Row);
294                 }
295                 Draw(L, L);
296                 return 1;
297             }
298             if (Options & SEARCH_BACK) C--; else C++;
299         }
300         if (Options & SEARCH_BACK) {
301             L--;
302             if (L >= 0)
303                 C = RLine(L)->Count;
304         } else {
305             C = 0;
306             L++;
307         }
308     }
309     //SetPos(OC, OL);
310     return 0;
311 }
312 
FindRx(RxNode * Rx,SearchReplaceOptions & opt)313 int EBuffer::FindRx(RxNode *Rx, SearchReplaceOptions &opt) {
314     int Options = opt.Options;
315     int LLen, Start, End;
316     int C, L;
317     char *P;
318     PELine X;
319     RxMatchRes b;
320 
321     if (!(Options & SEARCH_RE))
322         return 0;
323     if (Options & SEARCH_BACK) { // not supported
324         View->MView->Win->Choice(GPC_ERROR, "FindRx", 1, "O&K", "Reverse regexp search not supported.");
325         return 0;
326     }
327     if (Rx == 0)
328         return 0;
329 
330     if (Match.Row != -1)
331         Draw(Match.Row, Match.Row);
332     Match.Row = -1;
333     Match.Col = -1;
334 
335     C = CP.Col;
336     L = VToR(CP.Row);
337     X = RLine(L);
338     C = CharOffset(X, C);
339 
340     if (Options & SEARCH_NEXT) {
341         int CC = MatchCount ? MatchCount : 1;
342 
343         if (Options & SEARCH_BACK) {
344             C -= CC;
345             if (Options & SEARCH_BLOCK) {
346                 if (C < BB.Col && L == BB.Row)
347                     return 0;
348                 L--;
349                 X = RLine(L);
350                 C = X->Count;
351                 if (BlockMode == bmColumn)
352                     if (BE.Col < C)
353                         C = BE.Col;
354             } else {
355                 if (C < 0 && L == 0)
356                     return 0;
357                 L--;
358                 X = RLine(L);
359                 C = X->Count;
360             }
361         } else {
362             C += CC;
363             if (Options & SEARCH_BLOCK) {
364                 if (BlockMode == bmStream || BlockMode == bmLine) {
365                     if (C >= X->Count) {
366                         C = 0;
367                         L++;
368                         if (BlockMode == bmLine) {
369                             if (L == BE.Row) return 0;
370                         } else
371                             if (L == BE.Row && (C >= BE.Col || C >= X->Count))
372                                 return 0;
373                     }
374                 } else if (BlockMode == bmColumn) {
375                     if (C >= X->Count || C >= BE.Col) {
376                         C = BB.Col;
377                         L++;
378                         if (L == BE.Row) return 0;
379                     }
380                 }
381             } else {
382                 if (C >= X->Count) {
383                     C = 0;
384                     L++;
385                     if (L == RCount) return 0;
386                 }
387             }
388         }
389     }
390     MatchLen = 0;
391     MatchCount = 0;
392 
393     if (Options & SEARCH_BLOCK) {
394         if (Options & SEARCH_BACK) {
395             if (BlockMode == bmStream) {
396                 if (L > BE.Row) {
397                     L = BE.Row;
398                     C = BE.Col;
399                 }
400                 if (L == BE.Row && C > BE.Col)
401                     C = BE.Col;
402             } else {
403                 if (L >= BE.Row && BE.Row > 0) {
404                     L = BE.Row - 1;
405                     C = RLine(L)->Count;
406                 }
407                 if (BlockMode == bmColumn)
408                     if (L == BE.Row - 1 && C >= BE.Col)
409                         C = BE.Col;
410             }
411         } else {
412             if (L < BB.Row) {
413                 L = BB.Row;
414                 C = 0;
415             }
416             if (L == BB.Row && C < BB.Col)
417                 C = BB.Col;
418         }
419     }
420 
421     while (1) {
422         if (Options & SEARCH_BLOCK) {
423             if (BlockMode == bmStream) {
424                 if (L > BE.Row || L < BB.Row) break;
425             } else
426                 if (L >= BE.Row || L < BB.Row) break;
427         } else
428             if (L >= RCount || L < 0) break;
429 
430         X = RLine(L);
431         LLen = X->Count;
432         P = X->Chars;
433         Start = 0;
434         End = LLen;
435 
436         if (Options & SEARCH_BLOCK) {
437             if (BlockMode == bmColumn) {
438                 Start = CharOffset(X, BB.Col);
439                 End = CharOffset(X, BE.Col);
440             } else if (BlockMode == bmStream) {
441                 if (L == BB.Row)
442                     Start = CharOffset(X, BB.Col);
443                 if (L == BE.Row)
444                     End = CharOffset(X, BE.Col);
445             }
446             if (End > LLen)
447                 End = LLen;
448         }
449         if (Options & SEARCH_BACK) {
450             if (C >= End)
451                 C = End;
452         } else {
453             if (C < Start)
454                 C = Start;
455         }
456 
457         if (Start <= End) {
458             if (RxExec(Rx, P + Start, End - Start, P + C, &b, (Options & SEARCH_NCASE) ? 0 : RX_CASE) == 1) {
459                 C = ScreenPos(X, b.Open[0] + Start);
460                 Match.Col = C;
461                 Match.Row = L;
462                 MatchCount = b.Close[0] - b.Open[0];
463                 MatchLen = ScreenPos(X, b.Close[0] + Start) - C;
464                 for (int mm = 0; mm < NSEXPS; mm++) {
465                     b.Open[mm] += Start;
466                     b.Close[mm] += Start;
467                 }
468                 MatchRes = b;
469                 if (!(Options & SEARCH_NOPOS)) {
470                     if (Options & SEARCH_CENTER)
471                         CenterPosR(C, L);
472                     else
473                         SetPosR(C, L);
474                 }
475                 Draw(L, L);
476                 return 1;
477             }
478         }
479         C = 0;
480         L++;
481     }
482     //SetPos(OC, OL);
483     return 0;
484 
485 }
486 
Find(SearchReplaceOptions & opt)487 int EBuffer::Find(SearchReplaceOptions &opt) {
488     size_t slen = strlen(opt.strSearch);
489     int Options = opt.Options;
490     size_t rlen = strlen(opt.strReplace);
491     RxNode *R = NULL;
492 
493     opt.resCount = -1;
494     opt.lastInsertLen = 0;
495 
496     if (slen == 0) return 0;
497     if (Options & SEARCH_BLOCK) {
498         if (CheckBlock() == 0) return 0;
499     }
500     if (Options & SEARCH_RE) {
501         R = RxCompile(opt.strSearch);
502         if (R == 0) {
503             View->MView->Win->Choice(GPC_ERROR, "Find", 1, "O&K", "Invalid regular expression.");
504             return 0;
505         }
506     }
507     if (Options & SEARCH_GLOBAL) {
508         if (Options & SEARCH_BLOCK) {
509             if (Options & SEARCH_BACK) {
510                 if (SetPosR(BE.Col, BE.Row) == 0) goto error;
511             } else {
512                 if (SetPosR(BB.Col, BB.Row) == 0) goto error;
513             }
514         } else {
515             if (Options & SEARCH_BACK) {
516                 if (RCount < 1) goto error;
517                 if (SetPosR(LineLen(RCount - 1), RCount - 1) == 0) goto error;
518             } else {
519                 if (SetPosR(0, 0) == 0) goto error;
520             }
521         }
522     }
523     opt.resCount = 0;
524     while (1) {
525         if (Options & SEARCH_RE) {
526             if (FindRx(R, opt) == 0) goto end;
527         } else {
528             if (FindStr(opt.strSearch, slen, opt) == 0) goto end;
529         }
530         opt.resCount++;
531 
532         if (opt.Options & SEARCH_REPLACE) {
533             char ask = 'A';
534 
535             if (!(Options & SEARCH_NASK)) {
536                 char ch;
537 
538                 while (1) {
539                     Draw(VToR(CP.Row), 1);
540                     Redraw();
541                     switch (View->MView->Win->Choice(0, "Replace",
542                                                      5,
543                                                      "&Yes",
544                                                      "&All",
545                                                      "&Once",
546                                                      "&Skip",
547                                                      "&Cancel",
548                                                      "Replace with %s?", opt.strReplace))
549                     {
550                     case 0: ch = 'Y'; break;
551                     case 1: ch = 'A'; break;
552                     case 2: ch = 'O'; break;
553                     case 3: ch = 'N'; break;
554                     case 4:
555                     case -1:
556                     default:
557                         ch = 'Q'; break;
558                     }
559                     if (ch == 'Y') { ask = 'Y'; goto ok_rep; }
560                     if (ch == 'N') { ask = 'N'; goto ok_rep; }
561                     if (ch == 'Q') { ask = 'Q'; goto ok_rep; }
562                     if (ch == 'A') { ask = 'A'; goto ok_rep; }
563                     if (ch == 'O') { ask = 'O'; goto ok_rep; }
564                 }
565             ok_rep:
566                 if (ask == 'N') goto try_join;
567                 if (ask == 'Q') goto end;
568                 if (ask == 'A') Options |= SEARCH_NASK;
569             }
570 
571             if (Options & SEARCH_RE) {
572                 PELine L = RLine(Match.Row);
573                 int P, R;
574                 char *PR = 0;
575                 size_t LR = 0;
576 
577                 R = Match.Row;
578                 P = Match.Col;
579                 P = CharOffset(L, P);
580 
581                 if (0 == RxReplace(opt.strReplace, L->Chars, L->Count, MatchRes, &PR, &LR)) {
582                     if (DelText(R, Match.Col, MatchLen) == 0) goto error;
583                     if (PR && LR > 0)
584                         if (InsText(R, Match.Col, LR, PR) == 0) goto error;
585                     if (PR)
586                         free(PR);
587                     rlen = LR;
588                 }
589             } else {
590                 if (DelText(Match.Row, Match.Col, MatchLen) == 0) goto error;
591                 if (InsText(Match.Row, Match.Col, rlen, opt.strReplace) == 0) goto error;
592 
593                 // Cursor remains at start of inserted string. If there is recursive
594                 // replace pattern, fte can go it infinite loop.
595                 // Workaround: Move cursor to end of inserted string
596                 opt.lastInsertLen = strlen(opt.strReplace);
597             }
598             if (!(Options & SEARCH_BACK)) {
599                 MatchLen = rlen;
600                 MatchCount = rlen;
601             }
602             if (ask == 'O')
603                 goto end;
604         }
605     try_join:
606         if (Options & SEARCH_JOIN) {
607             char ask = 'A';
608 
609             if (!(Options & SEARCH_NASK)) {
610                 char ch;
611 
612                 while (1) {
613                     Draw(VToR(CP.Row), 1);
614                     Redraw();
615                     switch (View->MView->Win->Choice(0, "Join Line",
616                                                      5,
617                                                      "&Yes",
618                                                      "&All",
619                                                      "&Once",
620                                                      "&Skip",
621                                                      "&Cancel",
622                                                      "Join lines %d and %d?", 1 + VToR(CP.Row), 1 + VToR(CP.Row) + 1))
623                     {
624                     case 0: ch = 'Y'; break;
625                     case 1: ch = 'A'; break;
626                     case 2: ch = 'O'; break;
627                     case 3: ch = 'N'; break;
628                     case 4:
629                     case -1:
630                     default:
631                         ch = 'Q'; break;
632                     }
633                     if (ch == 'Y') { ask = 'Y'; goto ok_join; }
634                     if (ch == 'N') { ask = 'N'; goto ok_join; }
635                     if (ch == 'Q') { ask = 'Q'; goto ok_join; }
636                     if (ch == 'A') { ask = 'A'; goto ok_join; }
637                     if (ch == 'O') { ask = 'O'; goto ok_join; }
638                 }
639             ok_join:
640                 if (ask == 'N') goto try_delete;
641                 if (ask == 'Q') goto end;
642                 if (ask == 'A') Options |= SEARCH_NASK;
643             }
644 
645             if (JoinLine(Match.Row, Match.Col) == 0) goto error;
646 
647             if (ask == 'O')
648                 goto end;
649         }
650     try_delete:
651         if (Options & SEARCH_DELETE) {
652             char ask = 'A';
653 
654             if (!(Options & SEARCH_NASK)) {
655                 char ch;
656 
657                 while (1) {
658                     Draw(VToR(CP.Row), 1);
659                     Redraw();
660                     switch (View->MView->Win->Choice(0, "Delete Line",
661                                                      5,
662                                                      "&Yes",
663                                                      "&All",
664                                                      "&Once",
665                                                      "&Skip",
666                                                      "&Cancel",
667                                                      "Delete line %d?", VToR(CP.Row)))
668                     {
669                     case 0: ch = 'Y'; break;
670                     case 1: ch = 'A'; break;
671                     case 2: ch = 'O'; break;
672                     case 3: ch = 'N'; break;
673                     case 4:
674                     case -1:
675                     default:
676                         ch = 'Q'; break;
677                     }
678                     if (ch == 'Y') { ask = 'Y'; goto ok_delete; }
679                     if (ch == 'N') { ask = 'N'; goto ok_delete; }
680                     if (ch == 'Q') { ask = 'Q'; goto ok_delete; }
681                     if (ch == 'A') { ask = 'A'; goto ok_delete; }
682                     if (ch == 'O') { ask = 'O'; goto ok_delete; }
683                 }
684             ok_delete:
685                 if (ask == 'N') goto next;
686                 if (ask == 'Q') goto end;
687                 if (ask == 'A') Options |= SEARCH_NASK;
688             }
689 
690             if (Match.Row == RCount - 1) {
691                 if (DelText(Match.Row, 0, LineLen()) == 0) goto error;
692             } else
693                 if (DelLine(Match.Row) == 0) goto error;
694 
695             if (ask == 'O')
696                 goto end;
697             if (!(Options & SEARCH_ALL))
698                 break;
699             goto last;
700         }
701     next:
702         if (!(Options & SEARCH_ALL))
703             break;
704         Options |= SEARCH_NEXT;
705     last:
706         ;
707     }
708 end:
709     // end of search
710     if (R)
711         RxFree(R);
712 
713     if (Options & SEARCH_ALL)
714         Msg(S_INFO, "%d match(es) found.", opt.resCount);
715     else {
716         if (opt.resCount == 0) {
717             Msg(S_INFO, "[%s] not found", opt.strSearch);
718             return 0;
719         }
720     }
721     return 1;
722 error:
723 
724     if (R)
725     {
726         RxFree(R);
727     }
728     View->MView->Win->Choice(GPC_ERROR, "Find", 1, "O&K", "Error in search/replace.");
729     return 0;
730 }
731 
732 
CompleteWord()733 int EBuffer::CompleteWord() {
734 #ifdef CONFIG_I_COMPLETE
735     return View->MView->Win->ICompleteWord(View);
736 #else
737     PELine L = VLine(CP.Row), M;
738     int C, P, X, P1, N, xlen, XL;
739     EPoint O = CP;
740 
741     if (CP.Col == 0) return 0;
742 
743     if (SetPos(CP.Col, CP.Row) == 0) return 0;
744 
745     C = CP.Col;
746     P = CharOffset(L, C);
747     P1 = P;
748     N = VToR(CP.Row);
749 
750     if (P > L->Count) return 0;
751 
752     if (P > 0) {
753         while ((P > 0) && ((ChClass(L->Chars[P - 1]) == 1) || (L->Chars[P - 1] == '_'))) P--;
754         if (P == P1) return 0;
755         xlen = P1 - P;
756         C = ScreenPos(L, P);
757         Match.Row = N;
758         Match.Col = C;
759         //if (SetPos(C, CP.Row) == 0) return 0;
760         //again:
761         if (FindStr(L->Chars + P, xlen, SEARCH_BACK | SEARCH_NEXT | SEARCH_NOPOS | SEARCH_WORDBEG) == 1) {
762             M = RLine(Match.Row);
763             X = CharOffset(M, Match.Col);
764             XL = X;
765             //if ((XL > 0) && ((ChClass(M->Chars[XL - 1]) == 1) || (M->Chars[XL - 1] == '_'))) goto again;
766             while ((XL < M->Count) && ((ChClass(M->Chars[XL]) == 1) || (M->Chars[XL] == '_'))) XL++;
767             {
768                 char *pp = (char *)malloc(XL - X - xlen);
769 
770                 if (pp) {
771                     memcpy(pp, M->Chars + X + xlen, XL - X - xlen);
772 
773                     if (InsText(N, O.Col,
774                                 XL - X - xlen,
775                                 pp,
776                                 1) == 0) return 0;
777                     free(pp);
778                 }
779             }
780             if (SetPos(O.Col + XL - X - xlen, O.Row) == 0) return 0;
781             Draw(Match.Row, Match.Row);
782             Match.Row = -1;
783             Match.Col = -1;
784             MatchLen = 0;
785             MatchCount = 0;
786             return 1;
787         }
788     }
789     if (Match.Row != -1)
790         Draw(Match.Row, Match.Row);
791     Match.Col = Match.Row = -1;
792     MatchLen = 0;
793     MatchCount = 0;
794     return 0;
795 #endif
796 }
797 
Search(ExState & State,const char * aString,int Options,int)798 int EBuffer::Search(ExState &State, const char *aString, int Options, int /*CanResume*/) {
799     char find[MAXSEARCH+1] = "";
800     int Case = BFI(this, BFI_MatchCase) ? 0 : SEARCH_NCASE;
801     int Next = 0;
802     int erc = 0;
803     //int Changed;
804 
805     if (aString)
806         strcpy(find, aString);
807     else
808         if (State.GetStrParam(View, find, sizeof(find)) == 0)
809             if ((erc = View->MView->Win->GetStr("Find", sizeof(find), find, HIST_SEARCH)) == 0) return 0;
810     if (strlen(find) == 0) return 0;
811 
812     if (erc == 2)
813         Case ^= SEARCH_NCASE;
814 
815     //if (Changed == 0 && CanResume)
816     //    Next |= SEARCH_NEXT;
817 
818     LSearch.ok = 0;
819     strcpy(LSearch.strSearch, find);
820     LSearch.Options = Case | Next | (Options & ~SEARCH_NCASE);
821     LSearch.ok = 1;
822     if (Find(LSearch) == 0) return 0;
823     return 1;
824 }
825 
SearchAgain(ExState &,unsigned int Options)826 int EBuffer::SearchAgain(ExState &/*State*/, unsigned int Options) {
827     if (LSearch.ok == 0) return 0;
828     LSearch.Options |= SEARCH_NEXT;
829     if ((Options & SEARCH_BACK) != (LSearch.Options & SEARCH_BACK))
830         LSearch.Options ^= SEARCH_BACK;
831     if (Find(LSearch) == 0) return 0;
832     return 1;
833 }
834 
SearchReplace(ExState & State,const char * aString,const char * aReplaceString,int Options)835 int EBuffer::SearchReplace(ExState &State, const char *aString, const char *aReplaceString, int Options) {
836     char find[MAXSEARCH+1] = "";
837     char replace[MAXSEARCH+1] = "";
838     int Case = BFI(this, BFI_MatchCase) ? 0 : SEARCH_NCASE;
839 
840     if (aString)
841         strcpy(find, aString);
842     else
843         if (State.GetStrParam(View, find, sizeof(find)) == 0)
844             if (View->MView->Win->GetStr("Find", sizeof(find), find, HIST_SEARCH) == 0) return 0;
845     if (strlen(find) == 0) return 0;
846     if (aReplaceString)
847         strcpy(replace, aReplaceString);
848     else
849         if (State.GetStrParam(View, replace, sizeof(replace)) == 0)
850             if (View->MView->Win->GetStr("Replace", sizeof(replace), replace, HIST_SEARCH) == 0) return 0;
851 
852     LSearch.ok = 0;
853     strcpy(LSearch.strSearch, find);
854     strcpy(LSearch.strReplace, replace);
855     LSearch.Options = Case | (Options & ~SEARCH_NCASE) | SEARCH_ALL | SEARCH_REPLACE;
856     LSearch.ok = 1;
857     if (Find(LSearch) == 0) return 0;
858     return 1;
859 }
860 
Search(ExState & State)861 int EBuffer::Search(ExState &State)          { return Search(State, 0, 0, 1); }
SearchB(ExState & State)862 int EBuffer::SearchB(ExState &State)         { return Search(State, 0, SEARCH_BACK, 1); }
SearchRx(ExState & State)863 int EBuffer::SearchRx(ExState &State)        { return Search(State, 0, SEARCH_RE, 1); }
SearchAgain(ExState & State)864 int EBuffer::SearchAgain(ExState &State)     { return SearchAgain(State, 0); }
SearchAgainB(ExState & State)865 int EBuffer::SearchAgainB(ExState &State)    { return SearchAgain(State, SEARCH_BACK); }
SearchReplace(ExState & State)866 int EBuffer::SearchReplace(ExState &State)   { return SearchReplace(State, 0, 0, 0); }
SearchReplaceB(ExState & State)867 int EBuffer::SearchReplaceB(ExState &State)  { return SearchReplace(State, 0, 0, SEARCH_BACK); }
SearchReplaceRx(ExState & State)868 int EBuffer::SearchReplaceRx(ExState &State) { return SearchReplace(State, 0, 0, SEARCH_RE); }
869 
870 #ifdef CONFIG_OBJ_ROUTINE
ScanForRoutines()871 int EBuffer::ScanForRoutines() {
872     RxNode *regx;
873     int line;
874     PELine L;
875     RxMatchRes res;
876 
877     if (BFS(this, BFS_RoutineRegexp) == 0) {
878         View->MView->Win->Choice(GPC_ERROR, "Error", 1, "O&K", "No routine regexp.");
879         return 0;
880     }
881     regx = RxCompile(BFS(this, BFS_RoutineRegexp));
882     if (regx == 0) {
883         View->MView->Win->Choice(GPC_ERROR, "Error", 1, "O&K", "Failed to compile regexp '%s'", BFS(this, BFS_RoutineRegexp));
884         return 0;
885     }
886 
887     if (rlst.Lines) {
888         free(rlst.Lines);
889         rlst.Lines = 0;
890     }
891     rlst.Lines = 0;
892     rlst.Count = 0;
893 
894     Msg(S_BUSY, "Matching %s", BFS(this, BFS_RoutineRegexp));
895     for (line = 0; line < RCount; line++) {
896         L = RLine(line);
897         if (RxExec(regx, L->Chars, L->Count, L->Chars, &res) == 1) {
898             rlst.Count++;
899             rlst.Lines = (int *) realloc((void *) rlst.Lines, sizeof(int) * (rlst.Count | 0x1F));
900             rlst.Lines[rlst.Count - 1] = line;
901             Msg(S_BUSY, "Routines: %d", rlst.Count);
902         }
903     }
904     RxFree(regx);
905     return 1;
906 }
907 #endif
908 
ShowPosition()909 int EBuffer::ShowPosition() {
910     int CLine, NLines;
911     int CAct, NAct;
912     int CColumn, NColumns;
913     int CCharPos, NChars;
914 #ifdef HEAPWALK
915     unsigned long MemUsed = 0, MemFree = 0, BlkUsed = 0, BlkFree = 0, BigFree = 0, BigUsed = 0;
916 #endif
917 
918     if (!View)
919         return 0;
920 
921     CLine = CP.Row + 1;
922     NLines = VCount;
923     CAct = VToR(CP.Row) + 1;
924     NAct = RCount;
925     CColumn = CP.Col + 1;
926     NColumns = LineLen(CP.Row);
927     CCharPos = CharOffset(VLine(CP.Row), CP.Col) + 1;
928     NChars = VLine(CP.Row)->Count;
929 
930 #ifdef HEAPWALK
931     if (_heapchk() != _HEAPOK) {
932         MemUsed = -1;
933     } else {
934         _HEAPINFO hi;
935 
936         hi._pentry = NULL;
937         while (_heapwalk(&hi) == _HEAPOK) {
938             if (hi._useflag == _USEDENTRY) {
939                 BlkUsed++;
940                 MemUsed += hi._size;
941                 if (hi._size > BigUsed)
942                     BigUsed = hi._size;
943                 //fprintf(stderr, "USED %d\n", hi._size);
944             } else {
945                 BlkFree++;
946                 MemFree += hi._size;
947                 if (hi._size > BigFree)
948                     BigFree = hi._size;
949                 //fprintf(stderr, "FREE %d\n", hi._size);
950             }
951         }
952     }
953 #endif
954 
955 #ifdef CONFIG_UNDOREDO
956     int NN = -1;
957     if (US.UndoPtr > 0)
958         NN = US.Top[US.UndoPtr - 1];
959 #endif
960     Msg(S_INFO,
961 #ifdef HEAPWALK
962         "M%ld,%ld B%ld,%ld S%ld,%ld"
963 #endif
964         "L%d/%d G%d/%d/%d A%d/%d C%d/%d P%d/%d "
965 #ifdef CONFIG_UNDOREDO
966         "U%d/%d/%d "
967 #endif
968         "H%d/%d/%d",
969 #ifdef HEAPWALK
970         MemUsed, MemFree, BlkUsed, BlkFree, BigUsed, BigFree,
971 #endif
972         CLine, NLines,
973         RGap, RCount, RAllocated,
974         CAct, NAct,
975         CColumn, NColumns,
976         CCharPos, NChars,
977 #ifdef CONFIG_UNDOREDO
978         US.UndoPtr, US.Num, NN,
979 #endif
980         StartHilit, MinRedraw, MaxRedraw);
981     return 1;
982 }
983 
984 #ifdef CONFIG_BOOKMARKS
PlaceBookmark(const char * Name,const EPoint & P)985 int EBuffer::PlaceBookmark(const char *Name, const EPoint &P) {
986     assert(P.Row >= 0 && P.Row < RCount && P.Col >= 0);
987 
988     vector_iterate(EBookmark*, BMarks, it)
989         if ((*it)->IsName(Name)) {
990 	    (*it)->SetPoint(P);
991             return 1;
992         }
993 
994     EBookmark* b = new EBookmark(Name, P);
995     BMarks.push_back(b);
996 
997     return 1;
998 }
999 
RemoveBookmark(const char * Name)1000 int EBuffer::RemoveBookmark(const char *Name) {
1001     vector_iterate(EBookmark*, BMarks, it)
1002 	if ((*it)->IsName(Name)) {
1003 	    delete (*it);
1004 	    BMarks.erase(it);
1005             return 1;
1006         }
1007 
1008     View->MView->Win->Choice(GPC_ERROR, "RemoveBookmark", 1, "O&K", "Bookmark %s not found.", Name);
1009     return 0;
1010 }
1011 
GetBookmark(const char * Name,EPoint & P)1012 int EBuffer::GetBookmark(const char *Name, EPoint &P) {
1013     vector_iterate(EBookmark*, BMarks, it)
1014         if ((*it)->IsName(Name)) {
1015             P = (*it)->GetPoint();
1016             return 1;
1017         }
1018     return 0;
1019 }
1020 
1021 /*
1022  * Searches bookmark list starting at given index (searchFrom) for next
1023  * bookmark for line searchForLine. It then returns its name and position
1024  * and index (used for next search) or -1 if none found. Name is pointer
1025  * directly into bookmark structure (not copied!). If searchForLine is -1,
1026  * this function returns any next bookmark -> can be used to enumerate
1027  * bookmarks.
1028  */
GetBookmarkForLine(int searchFrom,int searchForLine,const EBookmark * & b)1029 int EBuffer::GetBookmarkForLine(int searchFrom, int searchForLine, const EBookmark* &b) {
1030     for (int i = searchFrom; i < (int)BMarks.size(); i++)
1031 	if (searchForLine == -1 || BMarks[i]->GetPoint().Row == searchForLine) {
1032             b = BMarks[i];
1033             return i + 1;
1034         }
1035     return -1;
1036 }
1037 
GotoBookmark(const char * Name)1038 int EBuffer::GotoBookmark(const char *Name) {
1039     vector_iterate(EBookmark*, BMarks, it)
1040         if ((*it)->IsName(Name))
1041             return CenterNearPosR((*it)->GetPoint().Col, (*it)->GetPoint().Row);
1042 
1043     View->MView->Win->Choice(GPC_ERROR, "GotoBookmark", 1, "O&K", "Bookmark %s not found.", Name);
1044     return 0;
1045 }
1046 #endif
1047 
GetMatchBrace(EPoint & M,int MinLine,int MaxLine,int show)1048 int EBuffer::GetMatchBrace(EPoint &M, int MinLine, int MaxLine, int show) {
1049     int StateLen;
1050     hsState *StateMap = 0;
1051     int Pos;
1052     PELine L = VLine(M.Row);
1053     int dir = 0;
1054     hsState State;
1055     char Ch1, Ch2;
1056     int CountX = 0;
1057     int StateRow = -1;
1058 
1059     M.Row = VToR(CP.Row);
1060 
1061     Pos = CharOffset(L, M.Col);
1062     if (Pos >= L->Count) return 0;
1063     switch(L->Chars[Pos]) {
1064     case '{': dir = +1; Ch1 = '{'; Ch2 = '}'; break;
1065     case '[': dir = +1; Ch1 = '['; Ch2 = ']'; break;
1066     case '<': dir = +1; Ch1 = '<'; Ch2 = '>'; break;
1067     case '(': dir = +1; Ch1 = '('; Ch2 = ')'; break;
1068     case '}': dir = -1; Ch1 = '}'; Ch2 = '{'; break;
1069     case ']': dir = -1; Ch1 = ']'; Ch2 = '['; break;
1070     case '>': dir = -1; Ch1 = '>'; Ch2 = '<'; break;
1071     case ')': dir = -1; Ch1 = ')'; Ch2 = '('; break;
1072     default:
1073         return 0;
1074     }
1075     StateMap = 0;
1076     if (GetMap(M.Row, &StateLen, &StateMap) == 0) return 0;
1077     State = StateMap[Pos];
1078     StateRow = M.Row;
1079 
1080     while (M.Row >= MinLine && M.Row < MaxLine) {
1081         while (Pos >= 0 && Pos < L->Count) {
1082             if (L->Chars[Pos] == Ch1 || L->Chars[Pos] == Ch2) {
1083                 // update syntax state if needed
1084                 if (StateRow != M.Row) {
1085                     free(StateMap);
1086                     StateMap = 0;
1087                     GetMap(M.Row, &StateLen, &StateMap);
1088                     if (StateMap == 0) return 0;
1089                     StateRow = M.Row;
1090                 }
1091                 if (StateMap[Pos] == State) {
1092                     if (L->Chars[Pos] == Ch1) CountX++;
1093                     if (L->Chars[Pos] == Ch2) CountX--;
1094                     if (CountX == 0) {
1095                         M.Col = ScreenPos(L, Pos);
1096                         free(StateMap);
1097                         return 1;
1098                     }
1099                 }
1100             }
1101             Pos += dir;
1102         }
1103         M.Row += dir;
1104         if (M.Row >= 0 && M.Row < RCount) {
1105             L = RLine(M.Row);
1106             Pos = (dir == 1) ? 0 : (L->Count - 1);
1107         }
1108     }
1109     if (StateMap) free(StateMap);
1110     if (show)
1111         Msg(S_INFO, "No match (%d missing).", CountX);
1112     return 0;
1113 }
1114 
MatchBracket()1115 int EBuffer::MatchBracket() {
1116     EPoint M = CP;
1117 
1118     if (GetMatchBrace(M, 0, RCount, 1) == 1)
1119         return SetPosR(M.Col, M.Row);
1120     return 0;
1121 }
1122 
HilitMatchBracket()1123 int EBuffer::HilitMatchBracket() {
1124     EPoint M = CP;
1125 
1126     if (View == 0)
1127         return 0;
1128 
1129     int Min = VToR(GetVPort()->TP.Row);
1130     int Max = GetVPort()->TP.Row + GetVPort()->Rows;
1131     if (Max >= VCount)
1132         Max = RCount;
1133     else
1134         Max = VToR(Max);
1135     if (Min < 0)
1136         Min = 0;
1137     if (Max < Min)
1138         return 0;
1139     if (GetMatchBrace(M, Min, Max, 0) == 1) {
1140         Match = M;
1141         MatchLen = 1;
1142         MatchCount = 1;
1143         Draw(Match.Row, Match.Row);
1144         return 1;
1145     }
1146     return 0;
1147 }
1148 
SearchWord(int SearchFlags)1149 int EBuffer::SearchWord(int SearchFlags) {
1150     char word[MAXSEARCH + 1];
1151     PELine L = VLine(CP.Row);
1152     int P, len = 0;
1153     int Case = BFI(this, BFI_MatchCase) ? 0 : SEARCH_NCASE;
1154 
1155     P = CharOffset(L, CP.Col);
1156     while ((P > 0) && ((ChClass(L->Chars[P - 1]) == 1) || (L->Chars[P - 1] == '_')))
1157         P--;
1158     while (len < int(sizeof(word)) && P < L->Count && (ChClass(L->Chars[P]) == 1 || L->Chars[P] == '_'))
1159         word[len++] = L->Chars[P++];
1160     word[len] = 0;
1161     if (len == 0)
1162         return 0;
1163 
1164     return FindStr(word, len, Case | SearchFlags | SEARCH_WORD);
1165 }
1166 
FindTagWord(ExState & State)1167 int EBuffer::FindTagWord(ExState &State) {
1168     char word[MAXSEARCH + 1];
1169     PELine L = VLine(CP.Row);
1170     int P, len = 0;
1171 
1172     P = CharOffset(L, CP.Col);
1173     while ((P > 0) && ((ChClass(L->Chars[P - 1]) == 1) || (L->Chars[P - 1] == '_')))
1174         P--;
1175     while (len < int(sizeof(word)) && P < L->Count && (ChClass(L->Chars[P]) == 1 || L->Chars[P] == '_'))
1176         word[len++] = L->Chars[P++];
1177     word[len] = 0;
1178     if (len == 0) {
1179         Msg(S_INFO, "No word at cursor.");
1180         return 0;
1181     }
1182 
1183     for (int j = 2; j; j--) {
1184         if (TagFind(this, View, word))
1185             return 1;
1186 
1187         if (j == 2) {
1188             /* Try autoload tags */
1189             if (!View->ExecCommand(ExTagLoad, State))
1190                 break;
1191         } else {
1192             Msg(S_INFO, "Tag '%s' not found.", word);
1193             break;
1194         }
1195     }
1196 
1197     return 0;
1198 }
1199 // *INDENT-ON*
1200