Added all of the MH sources, including RCS files, in
[mmh] / docs / historical / mh-6.8.5 / miscellany / less-177 / search.c
1 /*
2  * Routines to search a file for a pattern.
3  */
4
5 #include "less.h"
6 #include "position.h"
7 #if REGCOMP
8 #include "regexp.h"
9 #endif
10
11 extern int sigs;
12 extern int how_search;
13 extern int caseless;
14 extern int linenums;
15 extern int jump_sline;
16
17 /*
18  * Search for the n-th occurrence of a specified pattern, 
19  * either forward or backward.
20  * Return the number of matches not yet found in this file
21  * (that is, n minus the number of matches found).
22  * Return -1 if the search should be aborted.
23  * Caller may continue the search in another file 
24  * if less than n matches are found in this file.
25  */
26         public int
27 search(search_type, pattern, n)
28         int search_type;
29         char *pattern;
30         int n;
31 {
32         POSITION pos, linepos, oldpos;
33         register char *p;
34         register char *q;
35         register int goforw;
36         register int want_match;
37         char *line;
38         int linenum;
39         int line_match;
40         static int is_caseless;
41 #if RECOMP
42         char *re_comp();
43         PARG parg;
44 #else
45 #if REGCMP
46         char *regcmp();
47         static char *cpattern = NULL;
48 #else
49 #if REGCOMP
50         static struct regexp *regpattern = NULL;
51 #else
52         static char lpbuf[100];
53         static char *last_pattern = NULL;
54 #endif
55 #endif
56 #endif
57
58         /*
59          * Extract flags and type of search.
60          */
61         goforw = (SRCH_DIR(search_type) == SRCH_FORW);
62         want_match = !(search_type & SRCH_NOMATCH);
63
64         if (pattern != NULL && *pattern != '\0' && (is_caseless = caseless))
65         {
66                 /*
67                  * Search will ignore case, unless
68                  * there are any uppercase letters in the pattern.
69                  */
70                 for (p = pattern;  *p != '\0';  p++)
71                         if (*p >= 'A' && *p <= 'Z')
72                         {
73                                 is_caseless = 0;
74                                 break;
75                         }
76         }
77 #if RECOMP
78
79         /*
80          * (re_comp handles a null pattern internally, 
81          *  so there is no need to check for a null pattern here.)
82          */
83         if ((parg.p_string = re_comp(pattern)) != NULL)
84         {
85                 error("%s", &parg);
86                 return (-1);
87         }
88 #else
89 #if REGCMP
90         if (pattern == NULL || *pattern == '\0')
91         {
92                 /*
93                  * A null pattern means use the previous pattern.
94                  * The compiled previous pattern is in cpattern, so just use it.
95                  */
96                 if (cpattern == NULL)
97                 {
98                         error("No previous regular expression", NULL_PARG);
99                         return (-1);
100                 }
101         } else
102         {
103                 /*
104                  * Otherwise compile the given pattern.
105                  */
106                 char *s;
107                 if ((s = regcmp(pattern, 0)) == NULL)
108                 {
109                         error("Invalid pattern", NULL_PARG);
110                         return (-1);
111                 }
112                 if (cpattern != NULL)
113                         free(cpattern);
114                 cpattern = s;
115         }
116 #else
117 #if REGCOMP
118         if (pattern == NULL || *pattern == '\0')
119         {
120                 /*
121                  * A null pattern means use the previous pattern.
122                  * The compiled previous pattern is in regpattern, 
123                  * so just use it.
124                  */
125                 if (regpattern == NULL)
126                 {
127                         error("No previous regular expression", NULL_PARG);
128                         return (-1);
129                 }
130         } else
131         {
132                 /*
133                  * Otherwise compile the given pattern.
134                  */
135                 struct regexp *s;
136                 if ((s = regcomp(pattern)) == NULL)
137                 {
138                         error("Invalid pattern", NULL_PARG);
139                         return (-1);
140                 }
141                 if (regpattern != NULL)
142                         free(regpattern);
143                 regpattern = s;
144         }
145 #else
146         if (pattern == NULL || *pattern == '\0')
147         {
148                 /*
149                  * Null pattern means use the previous pattern.
150                  */
151                 if (last_pattern == NULL)
152                 {
153                         error("No previous regular expression", NULL_PARG);
154                         return (-1);
155                 }
156                 pattern = last_pattern;
157         } else
158         {
159                 strcpy(lpbuf, pattern);
160                 last_pattern = lpbuf;
161         }
162 #endif
163 #endif
164 #endif
165
166         /*
167          * Figure out where to start the search.
168          */
169         if (empty_screen())
170         {
171                 /*
172                  * Start at the beginning (or end) of the file.
173                  * (The empty_screen() case is mainly for 
174                  * command line initiated searches;
175                  * for example, "+/xyz" on the command line.)
176                  */
177                 if (goforw)
178                         pos = ch_zero();
179                 else 
180                 {
181                         pos = ch_length();
182                         if (pos == NULL_POSITION)
183                                 pos = ch_zero();
184                 }
185         } else 
186         {
187                 if (how_search)
188                 {
189                         if (goforw)
190                                 linenum = BOTTOM_PLUS_ONE;
191                         else
192                                 linenum = TOP;
193                         pos = position(linenum);
194                 } else
195                 {
196                         linenum = adjsline(jump_sline);
197                         pos = position(linenum);
198                         if (goforw)
199                                 pos = forw_raw_line(pos, (char **)NULL);
200                 }
201         }
202
203         if (pos == NULL_POSITION)
204         {
205                 /*
206                  * Can't find anyplace to start searching from.
207                  */
208                 error("Nothing to search", NULL_PARG);
209                 return (-1);
210         }
211
212         linenum = find_linenum(pos);
213         oldpos = pos;
214         for (;;)
215         {
216                 /*
217                  * Get lines until we find a matching one or 
218                  * until we hit end-of-file (or beginning-of-file 
219                  * if we're going backwards).
220                  */
221                 if (sigs)
222                         /*
223                          * A signal aborts the search.
224                          */
225                         return (-1);
226
227                 if (goforw)
228                 {
229                         /*
230                          * Read the next line, and save the 
231                          * starting position of that line in linepos.
232                          */
233                         linepos = pos;
234                         pos = forw_raw_line(pos, &line);
235                         if (linenum != 0)
236                                 linenum++;
237                 } else
238                 {
239                         /*
240                          * Read the previous line and save the
241                          * starting position of that line in linepos.
242                          */
243                         pos = back_raw_line(pos, &line);
244                         linepos = pos;
245                         if (linenum != 0)
246                                 linenum--;
247                 }
248
249                 if (pos == NULL_POSITION)
250                 {
251                         /*
252                          * We hit EOF/BOF without a match.
253                          */
254                         return (n);
255                 }
256
257                 /*
258                  * If we're using line numbers, we might as well
259                  * remember the information we have now (the position
260                  * and line number of the current line).
261                  * Don't do it for every line because it slows down
262                  * the search.  Remember the line number only if
263                  * we're "far" from the last place we remembered it.
264                  */
265                 if (linenums && abs(pos - oldpos) > 1024)
266                 {
267                         add_lnum(linenum, pos);
268                         oldpos = pos;
269                 }
270
271                 if (is_caseless)
272                 {
273                         /*
274                          * If this is a caseless search, convert 
275                          * uppercase in the input line to lowercase.
276                          * While we're at it, remove any backspaces
277                          * along with the preceding char.
278                          * This allows us to match text which is 
279                          * underlined or overstruck.
280                          */
281                         for (p = q = line;  *p != '\0';  p++, q++)
282                         {
283                                 if (*p >= 'A' && *p <= 'Z')
284                                         /* Convert uppercase to lowercase. */
285                                         *q = *p + 'a' - 'A';
286                                 else if (q > line && *p == '\b')
287                                         /* Delete BS and preceding char. */
288                                         q -= 2;
289                                 else
290                                         /* Otherwise, just copy. */
291                                         *q = *p;
292                         }
293                 }
294
295                 /*
296                  * Test the next line to see if we have a match.
297                  * This is done in a variety of ways, depending
298                  * on what pattern matching functions are available.
299                  */
300 #if REGCMP
301                 line_match = (regex(cpattern, line) != NULL);
302 #else
303 #if RECOMP
304                 line_match = (re_exec(line) == 1);
305 #else
306 #if REGCOMP
307                 line_match = regexec(regpattern, line);
308 #else
309                 line_match = match(pattern, line);
310 #endif
311 #endif
312 #endif
313                 /*
314                  * We are successful if want_match and line_match are
315                  * both true (want a match and got it),
316                  * or both false (want a non-match and got it).
317                  */
318                 if (((want_match && line_match) || (!want_match && !line_match)) &&
319                       --n <= 0)
320                         /*
321                          * Found the line.
322                          */
323                         break;
324         }
325
326         jump_loc(linepos, jump_sline);
327         return (0);
328 }
329
330 #if (!REGCMP) && (!RECOMP) && (!REGCOMP)
331 /*
332  * We have neither regcmp() nor re_comp().
333  * We use this function to do simple pattern matching.
334  * It supports no metacharacters like *, etc.
335  */
336         static int
337 match(pattern, buf)
338         char *pattern, *buf;
339 {
340         register char *pp, *lp;
341
342         for ( ;  *buf != '\0';  buf++)
343         {
344                 for (pp = pattern, lp = buf;  *pp == *lp;  pp++, lp++)
345                         if (*pp == '\0' || *lp == '\0')
346                                 break;
347                 if (*pp == '\0')
348                         return (1);
349         }
350         return (0);
351 }
352 #endif