]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/more/tags.c
Change the way that the queue(3) structures are declared; don't assume that
[FreeBSD/FreeBSD.git] / usr.bin / more / tags.c
1 /*
2  * Copyright (c) 1988 Mark Nudleman
3  * Copyright (c) 1988, 1993
4  *      The Regents of the University of California.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *      This product includes software developed by the University of
17  *      California, Berkeley and its contributors.
18  * 4. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34
35 #ifndef lint
36 static char sccsid[] = "@(#)tags.c      8.1 (Berkeley) 6/6/93";
37 #endif /* not lint */
38
39 #ifndef lint
40 static const char rcsid[] =
41   "$FreeBSD$";
42 #endif /* not lint */
43
44 #include <sys/types.h>
45 #include <sys/queue.h>
46
47 #include <ctype.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51
52 #include "less.h"
53
54 #define WHITESP(c)      ((c)==' ' || (c)=='\t')
55
56 extern int sigs;
57 char *tagfile;          /* Name of source file containing current tag */
58
59 static enum { CTAGS, GTAGS } tagstyle = GTAGS;
60 static char *findctag(), *findgtag(), *nextgtag(), *prevgtag();
61 static ctagsearch(), gtagsearch();
62
63 /*
64  * Load information about the tag.  The global variable tagfile will point to
65  * the file that contains the tag, or will be NULL if information could not be
66  * found (in which case an error message will have been printed).  After
67  * loading the file named by tagfile, tagsearch() should be called to
68  * set the current position to the tag.
69  */
70 findtag(tag)
71         char *tag;      /* The tag to load */
72 {
73         /*
74          * Try using gtags or ctags first, as indicated by tagstyle.  If
75          * that fails, try the other.  Someday there may even be a way to
76          * assert a certain tagstyle...
77          */
78         switch(tagstyle) {
79         case CTAGS:
80                 tagfile = findctag(tag);
81                 if (!tagfile && (tagfile = findgtag(tag))) tagstyle = GTAGS;
82                 if (tagfile) return;
83                 break;
84         case GTAGS:
85                 /* Would be nice to print the number of tag references
86                  * we found (for nexttag() and prevtag()) in a (not-)error()
87                  * message. */
88                 tagfile = findgtag(tag);
89                 if (!tagfile && (tagfile = findctag(tag))) tagstyle = CTAGS;
90                 if (tagfile) return;
91                 break;
92         }
93
94         error("could not find relevent tag information");
95 }
96
97 /*
98  * Load information about the next number'th tag, if the last findtag() call
99  * found multiple tag references.  The global variable tagfile will point to the
100  * file that contains the tag, or will be NULL if information could not be
101  * found (in which case an error message will have been printed).  After
102  * loading the file named by tagfile, tagsearch() should be called to set
103  * the current position to the tag.
104  */
105 nexttag(number)
106         int number;     /* How many tags to go forward by */
107 {
108         if (number < 0) number = -number;  /* positive only, please */
109
110         switch(tagstyle) {
111         case CTAGS:
112                 break;
113         case GTAGS:
114                 while (number--) tagfile = nextgtag();
115                 break;
116         }
117         if (!tagfile)
118                 error("no next tag");
119 }
120
121 /*
122  * The antithesis to nexttag().
123  */
124 prevtag(number)
125         int number;     /* How many tags to go backwards by */
126 {
127         if (number < 0) number = -number;  /* positive only, please */
128
129         switch(tagstyle) {
130         case CTAGS:
131                 break;
132         case GTAGS:
133                 while (number--) tagfile = prevgtag();
134                 break;
135         }
136         if (!tagfile)
137                 error("no previous tag");
138 }
139
140 /*
141  * Try and position the currently loaded file at the last tag that was
142  * succesfully passed to findtag() or chosen with nexttag() and prevtag().
143  * An error message will be printed if unsuccessful.
144  */
145 tagsearch()
146 {
147         switch(tagstyle) {
148         case CTAGS:
149                 if (ctagsearch())
150                         error("could not locate ctag");
151                 return;
152         case GTAGS:
153                 if (gtagsearch())
154                         error("could not locate gtag");
155                 return;
156         }
157 }
158
159
160 /*******************************************************************************
161  *
162  * ctags
163  *
164  */
165
166 extern int linenums;
167 extern char *line;
168
169 static char *ctagpattern;
170 static int ctagflags;
171
172 /* ctag flags */
173 #define START_OF_LINE 0x01
174 #define END_OF_LINE   0x02
175
176 /*
177  * Find specified tag in the ctags(1)-format tag file ctagfile.  Returns
178  * pointer to a static buffer holding the name of the file containing
179  * the tag.  Returns NULL on failure.  The next call to ctagsearch() will
180  * position the currently loaded file at the tag.
181  */
182 static char *
183 findctag(tag)
184         register char *tag;     /* tag to search for */
185 {
186         register char *p;
187         register FILE *f;
188         register int taglen;
189         int search_char;
190         static char tline[200];  /* XXX should be dynamic */
191         const char *ctagfile = "tags";
192         char *retr;
193
194         if ((f = fopen(ctagfile, "r")) == NULL)
195                 return (NULL);
196
197         taglen = strlen(tag);
198
199         /*
200          * Search the tags file for the desired tag.
201          */
202         while (fgets(tline, sizeof(tline), f) != NULL)
203         {
204                 if (sigs)
205                         break;  /* abandon */
206
207                 if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen]))
208                         continue;
209
210                 /*
211                  * Found it.
212                  * The line contains the tag, the filename and the
213                  * pattern, separated by white space.
214                  * The pattern is surrounded by a pair of identical
215                  * search characters.
216                  * Parse the line and extract these parts.
217                  */
218
219                 /*
220                  * Skip over the tag and the whitespace after the tag name.
221                  */
222                 for (p = tline;  !WHITESP(*p) && *p != '\0';  p++)
223                         continue;
224                 while (WHITESP(*p))
225                         p++;
226                 if (*p == '\0')
227                         /* File name is missing! */
228                         continue;
229
230                 /*
231                  * Save the file name.
232                  * Skip over the filename and whitespace after the file name.
233                  */
234                 retr = p;
235                 while (!WHITESP(*p) && *p != '\0')
236                         p++;
237                 *p++ = '\0';
238                 while (WHITESP(*p))
239                         p++;
240                 if (*p == '\0')
241                         /* Pattern is missing! */
242                         continue;
243
244                 /*
245                  * Save the pattern.
246                  * Skip to the end of the pattern.
247                  * Delete the initial "^" and the final "$" from the pattern.
248                  */
249                 search_char = *p++;
250                 if (*p == '^') {
251                         p++;
252                         ctagflags |= START_OF_LINE;
253                 } else {
254                         ctagflags &= ~START_OF_LINE;
255                 }
256                 ctagpattern = p;  /* cock ctagsearch() */
257                 while (*p != search_char && *p != '\0')
258                         p++;
259                 if (p[-1] == '\n')
260                         p--;
261                 if (p[-1] == '$') {
262                         p--;
263                         ctagflags |= END_OF_LINE;
264                 } else {
265                         ctagflags &= ~END_OF_LINE;
266                 }
267                 *p = '\0';
268
269                 (void)fclose(f);
270                 return (retr);
271         }
272         (void)fclose(f);
273         return (NULL);
274 }
275
276 /*
277  * Locate the tag that was loaded by findctag().
278  * This is a stripped-down version of search().
279  * We don't use search() for several reasons:
280  *   -  We don't want to blow away any search string we may have saved.
281  *   -  The various regular-expression functions (from different systems:
282  *      regcmp vs. re_comp) behave differently in the presence of
283  *      parentheses (which are almost always found in a tag).
284  *
285  * Returns -1 if it was unable to position at the requested pattern,
286  * 0 otherwise.
287  */
288 static
289 ctagsearch()
290 {
291         off_t pos, linepos, forw_raw_line();
292         int linenum;
293
294         pos = (off_t)0;
295         linenum = find_linenum(pos);
296
297         for (;;)
298         {
299                 /*
300                  * Get lines until we find a matching one or
301                  * until we hit end-of-file.
302                  */
303                 if (sigs)
304                         return (-1);
305
306                 /*
307                  * Read the next line, and save the
308                  * starting position of that line in linepos.
309                  */
310                 linepos = pos;
311                 pos = forw_raw_line(pos);
312                 if (linenum != 0)
313                         linenum++;
314
315                 if (pos == NULL_POSITION)
316                         return (-1);  /* Tag not found. */
317
318                 /*
319                  * If we're using line numbers, we might as well
320                  * remember the information we have now (the position
321                  * and line number of the current line).
322                  */
323                 if (linenums)
324                         add_lnum(linenum, pos);
325
326                 /*
327                  * Test the line to see if we have a match.  I don't know of
328                  * any tags program that would use START_OF_LINE but not
329                  * END_OF_LINE, or vice-a-versa, but we handle this case anyway.
330                  */
331                 switch (ctagflags) {
332                 case 0: /* !START_OF_LINE and !END_OF_LINE */
333                         if (strstr(line, ctagpattern))
334                                 goto found;
335                         break;
336                 case START_OF_LINE:  /* !END_OF_LINE */
337                         if (!strncmp(ctagpattern, line, strlen(ctagpattern)))
338                                 goto found;
339                         break;
340                 case END_OF_LINE:  /* !START_OF_LINE */
341                 {
342                         char *x = strstr(line, ctagpattern);
343                         if (!x)
344                                 break;
345                         if (x[strlen(ctagpattern)] != '\0')
346                                 break;
347                         goto found;
348                 }
349                 case START_OF_LINE | END_OF_LINE:
350                         if (!strcmp(ctagpattern, line))
351                                 goto found;
352                         break;
353                 }
354         }
355
356 found:
357         jump_loc(linepos);
358         return (0);
359 }
360
361
362 /*******************************************************************************
363  *
364  * gtags
365  *
366  */
367
368 /*
369  * The findgtag() and getentry() functions are stolen, more or less, from the
370  * patches to nvi-1.79 included in Shigio Yamaguchi's global-3.42 distribution.
371  */
372
373 /*
374  * The queue of tags generated by the last findgtag() call.
375  */
376 static CIRCLEQ_HEAD(gtag_q, struct gtag) gtag_q;
377 struct gtag {
378         CIRCLEQ_ENTRY(struct gtag) ptrs;
379         char *file;     /* source file containing the tag */
380         int line;       /* appropriate line number of source file */
381 };
382 static struct gtag *curgtag;
383 static getentry();
384
385 /*
386  * The findgtag() will try and load information about the requested tag.
387  * It does this by calling "global -x tag; global -xr tag;" and storing the
388  * parsed output for future use by gtagsearch_f() and gtagsearch_b().  A
389  * pointer to a static buffer containing the name of the source file will
390  * be returned, or NULL on failure.  The first filename printed by global is
391  * returned (hopefully the function definition) and the other filenames may
392  * be accessed by nextgtag() and prevgtag().
393  */
394 static char *
395 findgtag(tag)
396         char *tag;              /* tag to load */
397 {
398         struct gtag *gtag_p1, *gtag_p2;
399         char command[512];
400         char buf[256];
401         FILE *fp;
402
403         if (!tag) return (NULL);  /* Sanity check */
404
405         /* Clear any existing tag circle queue */
406         /* XXX Ideally, we wouldn't do this until after we know that we
407          * can load some other tag information. */
408         curgtag = NULL;
409         gtag_p1 = gtag_q.cqh_first;
410         if (gtag_p1) while (gtag_p1 != (void *)&gtag_q) {
411                 gtag_p2 = gtag_p1->ptrs.cqe_next;
412                 free(gtag_p1);
413                 gtag_p1 = gtag_p2;
414         }
415
416         /* Allocate and initialize the tag queue structure. */
417         CIRCLEQ_INIT(&gtag_q);
418
419         /* Get our data from global(1) */
420         snprintf(command, sizeof(command),
421             "(global -x '%s'; global -xr '%s') 2>/dev/null", tag, tag);
422         if (fp = popen(command, "r")) {
423                 while (fgets(buf, sizeof(buf), fp)) {
424                         char *name, *file, *line;
425
426                         if (sigs) {
427                                 pclose(fp);
428                                 return (NULL);
429                         }
430                                 
431                         /* chop(buf) */
432                         if (buf[strlen(buf) - 1] == '\n')
433                                 buf[strlen(buf) - 1] = 0;
434                         else
435                                 while (fgetc(fp) != '\n')
436                                         ;
437
438                         if (getentry(buf, &name, &file, &line)) {
439                                 /*
440                                  * Couldn't parse this line for some reason.
441                                  * We'll just pretend it never happened.
442                                  */
443                                 break;
444                         }
445
446                         /* Add to queue */
447                         gtag_p1 = malloc(sizeof(struct gtag));
448                         if (!gtag_p1) {
449                                 pclose(fp);
450                                 error("malloc() failed");
451                                 return (NULL);
452                         }
453                         gtag_p1->file = malloc(strlen(file) + 1);
454                         if (!gtag_p1->file) {
455                                 pclose(fp);
456                                 error("malloc() failed");
457                                 return (NULL);
458                         }
459                         strcpy(gtag_p1->file, file);
460                         gtag_p1->line = atoi(line);
461                         CIRCLEQ_INSERT_TAIL(&gtag_q, gtag_p1, ptrs);
462                 }
463                 pclose(fp);
464         }
465
466         /* Check to see if we found anything. */
467         if (gtag_q.cqh_first == (void *)&gtag_q)
468                 return (NULL);  /* Nope! */
469
470         curgtag = gtag_q.cqh_first;
471         return (curgtag->file);
472 }
473
474 /*
475  * Return the filename required for the next gtag in the queue that was setup
476  * by findgtag().  The next call to gtagsearch() will try to position at the
477  * appropriate tag.
478  */
479 static char *
480 nextgtag()
481 {
482         if (!curgtag) {
483                 /* No tag stack loaded */
484                 return (NULL);
485         }
486
487         curgtag = curgtag->ptrs.cqe_next;
488         if (curgtag == (void *)&gtag_q) {
489                 /* Wrapped around to the head of the queue */
490                 curgtag = ((struct gtag_q *)curgtag)->cqh_first;
491         }
492
493         return (curgtag->file);
494 }
495
496 /*
497  * Return the filename required for the previous gtag in the queue that was
498  * setup by findgtat().  The next call to gtagsearch() will try to position
499  * at the appropriate tag.
500  */
501 static char *
502 prevgtag()
503 {
504         if (!curgtag) {
505                 /* No tag stack loaded */
506                 return (NULL);
507         }
508
509         curgtag = curgtag->ptrs.cqe_prev;
510         if (curgtag == (void *)&gtag_q) {
511                 /* Wrapped around to the head of the queue */
512                 curgtag = ((struct gtag_q *)curgtag)->cqh_last;
513         }
514
515         return (curgtag->file);
516 }
517
518 /*
519  * Position the current file at at what is hopefully the tag that was chosen
520  * using either findtag() or one of nextgtag() and prevgtag().  Returns -1
521  * if it was unable to position at the tag, 0 if succesful.
522  */
523 static
524 gtagsearch()
525 {
526         if (!curgtag)
527                 return (-1);  /* No gtags loaded! */
528
529         jump_back(curgtag->line);
530
531         /*
532          * XXX We'll assume we were successful --- jump_back() will call error()
533          * if it fails, so the user will receive some kind of notification.
534          * Eventually, jump_back() should do its work silently and let us
535          * perform the error notification, eventually allowing our caller
536          * (presumably tagsearch()) to go error("Could not locate tag.");
537          */
538         return (0);
539 }
540
541 /*
542  * The getentry() parses output from the global(1) command.  The output
543  * must be in the format described below.  Returns 0 on success, -1 on
544  * error.  The tag, file, and line will each be NUL-terminated pointers
545  * into buf.
546  *
547  * gtags temporary file format.
548  * <tag>   <lineno>  <file>         <image>
549  *
550  * sample.
551  * +------------------------------------------------
552  * |main     30      main.c         main(argc, argv)
553  * |func     21      subr.c         func(arg)
554  */
555 static
556 getentry(buf, tag, file, line)
557         char *buf;      /* output from global -x */
558         char **tag;     /* name of the tag we actually found */
559         char **file;    /* file in which to find this tag */
560         char **line;    /* line number of file where this tag is found */
561 {
562         char *p = buf;
563
564         for (*tag = p; *p && !isspace(*p); p++)         /* tag name */
565                 ;
566         if (*p == 0)
567                 goto err;
568         *p++ = 0;
569         for (; *p && isspace(*p); p++)                  /* (skip blanks) */
570                 ;
571         if (*p == 0)
572                 goto err;
573         *line = p;                                      /* line no */
574         for (*line = p; *p && !isspace(*p); p++)
575                 ;
576         if (*p == 0)
577                 goto err;
578         *p++ = 0;
579         for (; *p && isspace(*p); p++)                  /* (skip blanks) */
580                 ;
581         if (*p == 0)
582                 goto err;
583         *file = p;                                      /* file name */
584         for (*file = p; *p && !isspace(*p); p++)
585                 ;
586         if (*p == 0)
587                 goto err;
588         *p = 0;
589
590         /* value check */
591         if (strlen(*tag) && strlen(*line) && strlen(*file) && atoi(*line) > 0)
592                 return (0);     /* OK */
593 err:
594         return (-1);            /* ERROR */
595 }