]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tags.c
Apply r316339 to vendor area.
[FreeBSD/FreeBSD.git] / tags.c
1 /*
2  * Copyright (C) 1984-2015  Mark Nudelman
3  *
4  * You may distribute under the terms of either the GNU General Public
5  * License or the Less License, as specified in the README file.
6  *
7  * For more information, see the README file.
8  */
9
10
11 #include "less.h"
12
13 #define WHITESP(c)      ((c)==' ' || (c)=='\t')
14
15 #if TAGS
16
17 public char ztags[] = "tags";
18 public char *tags = ztags;
19
20 static int total;
21 static int curseq;
22
23 extern int linenums;
24 extern int sigs;
25
26 enum tag_result {
27         TAG_FOUND,
28         TAG_NOFILE,
29         TAG_NOTAG,
30         TAG_NOTYPE,
31         TAG_INTR
32 };
33
34 /*
35  * Tag type
36  */
37 enum {
38         T_CTAGS,        /* 'tags': standard and extended format (ctags) */
39         T_CTAGS_X,      /* stdin: cross reference format (ctags) */
40         T_GTAGS,        /* 'GTAGS': function defenition (global) */
41         T_GRTAGS,       /* 'GRTAGS': function reference (global) */
42         T_GSYMS,        /* 'GSYMS': other symbols (global) */
43         T_GPATH         /* 'GPATH': path name (global) */
44 };
45
46 static enum tag_result findctag(char *tag);
47 static enum tag_result findgtag(char *tag, int type);
48 static char *nextgtag(void);
49 static char *prevgtag(void);
50 static POSITION ctagsearch(void);
51 static POSITION gtagsearch(void);
52 static int getentry(char *buf, char **tag, char **file, char **line);
53
54 /*
55  * The list of tags generated by the last findgtag() call.
56  *
57  * Use either pattern or line number.
58  * findgtag() always uses line number, so pattern is always NULL.
59  * findctag() uses either pattern (in which case line number is 0),
60  * or line number (in which case pattern is NULL).
61  */
62 struct taglist {
63         struct tag *tl_first;
64         struct tag *tl_last;
65 };
66 #define TAG_END  ((struct tag *) &taglist)
67 static struct taglist taglist = { TAG_END, TAG_END };
68 struct tag {
69         struct tag *next, *prev; /* List links */
70         char *tag_file;         /* Source file containing the tag */
71         LINENUM tag_linenum;    /* Appropriate line number in source file */
72         char *tag_pattern;      /* Pattern used to find the tag */
73         char tag_endline;       /* True if the pattern includes '$' */
74 };
75 static struct tag *curtag;
76
77 #define TAG_INS(tp) \
78         (tp)->next = TAG_END; \
79         (tp)->prev = taglist.tl_last; \
80         taglist.tl_last->next = (tp); \
81         taglist.tl_last = (tp);
82
83 #define TAG_RM(tp) \
84         (tp)->next->prev = (tp)->prev; \
85         (tp)->prev->next = (tp)->next;
86
87 /*
88  * Delete tag structures.
89  */
90         public void
91 cleantags(void)
92 {
93         struct tag *tp;
94
95         /*
96          * Delete any existing tag list.
97          * {{ Ideally, we wouldn't do this until after we know that we
98          *    can load some other tag information. }}
99          */
100         while ((tp = taglist.tl_first) != TAG_END)
101         {
102                 TAG_RM(tp);
103                 free(tp);
104         }
105         curtag = NULL;
106         total = curseq = 0;
107 }
108
109 /*
110  * Create a new tag entry.
111  */
112         static struct tag *
113 maketagent(char *name, char *file, LINENUM linenum, char *pattern, int endline)
114 {
115         struct tag *tp;
116
117         tp = (struct tag *) ecalloc(sizeof(struct tag), 1);
118         tp->tag_file = (char *) ecalloc(strlen(file) + 1, sizeof(char));
119         strcpy(tp->tag_file, file);
120         tp->tag_linenum = linenum;
121         tp->tag_endline = endline;
122         if (pattern == NULL)
123                 tp->tag_pattern = NULL;
124         else
125         {
126                 tp->tag_pattern = (char *) ecalloc(strlen(pattern) + 1, sizeof(char));
127                 strcpy(tp->tag_pattern, pattern);
128         }
129         return (tp);
130 }
131
132 /*
133  * Get tag mode.
134  */
135         public int
136 gettagtype(void)
137 {
138         int f;
139
140         if (strcmp(tags, "GTAGS") == 0)
141                 return T_GTAGS;
142         if (strcmp(tags, "GRTAGS") == 0)
143                 return T_GRTAGS;
144         if (strcmp(tags, "GSYMS") == 0)
145                 return T_GSYMS;
146         if (strcmp(tags, "GPATH") == 0)
147                 return T_GPATH;
148         if (strcmp(tags, "-") == 0)
149                 return T_CTAGS_X;
150         f = open(tags, OPEN_READ);
151         if (f >= 0)
152         {
153                 close(f);
154                 return T_CTAGS;
155         }
156         return T_GTAGS;
157 }
158
159 /*
160  * Find tags in tag file.
161  * Find a tag in the "tags" file.
162  * Sets "tag_file" to the name of the file containing the tag,
163  * and "tagpattern" to the search pattern which should be used
164  * to find the tag.
165  */
166         public void
167 findtag(char *tag)
168 {
169         int type = gettagtype();
170         enum tag_result result;
171
172         if (type == T_CTAGS)
173                 result = findctag(tag);
174         else
175                 result = findgtag(tag, type);
176         switch (result)
177         {
178         case TAG_FOUND:
179         case TAG_INTR:
180                 break;
181         case TAG_NOFILE:
182                 error("No tags file", NULL_PARG);
183                 break;
184         case TAG_NOTAG:
185                 error("No such tag in tags file", NULL_PARG);
186                 break;
187         case TAG_NOTYPE:
188                 error("unknown tag type", NULL_PARG);
189                 break;
190         }
191 }
192
193 /*
194  * Search for a tag.
195  */
196         public POSITION
197 tagsearch(void)
198 {
199         if (curtag == NULL)
200                 return (NULL_POSITION);  /* No gtags loaded! */
201         if (curtag->tag_linenum != 0)
202                 return gtagsearch();
203         else
204                 return ctagsearch();
205 }
206
207 /*
208  * Go to the next tag.
209  */
210         public char *
211 nexttag(int n)
212 {
213         char *tagfile = (char *) NULL;
214
215         while (n-- > 0)
216                 tagfile = nextgtag();
217         return tagfile;
218 }
219
220 /*
221  * Go to the previous tag.
222  */
223         public char *
224 prevtag(int n)
225 {
226         char *tagfile = (char *) NULL;
227
228         while (n-- > 0)
229                 tagfile = prevgtag();
230         return tagfile;
231 }
232
233 /*
234  * Return the total number of tags.
235  */
236         public int
237 ntags(void)
238 {
239         return total;
240 }
241
242 /*
243  * Return the sequence number of current tag.
244  */
245         public int
246 curr_tag(void)
247 {
248         return curseq;
249 }
250
251 /*****************************************************************************
252  * ctags
253  */
254
255 /*
256  * Find tags in the "tags" file.
257  * Sets curtag to the first tag entry.
258  */
259         static enum tag_result
260 findctag(char *tag)
261 {
262         char *p;
263         FILE *f;
264         int taglen;
265         LINENUM taglinenum;
266         char *tagfile;
267         char *tagpattern;
268         int tagendline;
269         int search_char;
270         int err;
271         char tline[TAGLINE_SIZE];
272         struct tag *tp;
273
274         p = shell_unquote(tags);
275         f = fopen(p, "r");
276         free(p);
277         if (f == NULL)
278                 return TAG_NOFILE;
279
280         cleantags();
281         total = 0;
282         taglen = (int) strlen(tag);
283
284         /*
285          * Search the tags file for the desired tag.
286          */
287         while (fgets(tline, sizeof(tline), f) != NULL)
288         {
289                 if (tline[0] == '!')
290                         /* Skip header of extended format. */
291                         continue;
292                 if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen]))
293                         continue;
294
295                 /*
296                  * Found it.
297                  * The line contains the tag, the filename and the
298                  * location in the file, separated by white space.
299                  * The location is either a decimal line number, 
300                  * or a search pattern surrounded by a pair of delimiters.
301                  * Parse the line and extract these parts.
302                  */
303                 tagpattern = NULL;
304
305                 /*
306                  * Skip over the whitespace after the tag name.
307                  */
308                 p = skipsp(tline+taglen);
309                 if (*p == '\0')
310                         /* File name is missing! */
311                         continue;
312
313                 /*
314                  * Save the file name.
315                  * Skip over the whitespace after the file name.
316                  */
317                 tagfile = p;
318                 while (!WHITESP(*p) && *p != '\0')
319                         p++;
320                 *p++ = '\0';
321                 p = skipsp(p);
322                 if (*p == '\0')
323                         /* Pattern is missing! */
324                         continue;
325
326                 /*
327                  * First see if it is a line number. 
328                  */
329                 tagendline = 0;
330                 taglinenum = getnum(&p, 0, &err);
331                 if (err)
332                 {
333                         /*
334                          * No, it must be a pattern.
335                          * Delete the initial "^" (if present) and 
336                          * the final "$" from the pattern.
337                          * Delete any backslash in the pattern.
338                          */
339                         taglinenum = 0;
340                         search_char = *p++;
341                         if (*p == '^')
342                                 p++;
343                         tagpattern = p;
344                         while (*p != search_char && *p != '\0')
345                         {
346                                 if (*p == '\\')
347                                         p++;
348                                 p++;
349                         }
350                         tagendline = (p[-1] == '$');
351                         if (tagendline)
352                                 p--;
353                         *p = '\0';
354                 }
355                 tp = maketagent(tag, tagfile, taglinenum, tagpattern, tagendline);
356                 TAG_INS(tp);
357                 total++;
358         }
359         fclose(f);
360         if (total == 0)
361                 return TAG_NOTAG;
362         curtag = taglist.tl_first;
363         curseq = 1;
364         return TAG_FOUND;
365 }
366
367 /*
368  * Edit current tagged file.
369  */
370         public int
371 edit_tagfile(void)
372 {
373         if (curtag == NULL)
374                 return (1);
375         return (edit(curtag->tag_file));
376 }
377
378 /*
379  * Search for a tag.
380  * This is a stripped-down version of search().
381  * We don't use search() for several reasons:
382  *   -  We don't want to blow away any search string we may have saved.
383  *   -  The various regular-expression functions (from different systems:
384  *      regcmp vs. re_comp) behave differently in the presence of 
385  *      parentheses (which are almost always found in a tag).
386  */
387         static POSITION
388 ctagsearch(void)
389 {
390         POSITION pos, linepos;
391         LINENUM linenum;
392         int len;
393         char *line;
394
395         pos = ch_zero();
396         linenum = find_linenum(pos);
397
398         for (;;)
399         {
400                 /*
401                  * Get lines until we find a matching one or 
402                  * until we hit end-of-file.
403                  */
404                 if (ABORT_SIGS())
405                         return (NULL_POSITION);
406
407                 /*
408                  * Read the next line, and save the 
409                  * starting position of that line in linepos.
410                  */
411                 linepos = pos;
412                 pos = forw_raw_line(pos, &line, (int *)NULL);
413                 if (linenum != 0)
414                         linenum++;
415
416                 if (pos == NULL_POSITION)
417                 {
418                         /*
419                          * We hit EOF without a match.
420                          */
421                         error("Tag not found", NULL_PARG);
422                         return (NULL_POSITION);
423                 }
424
425                 /*
426                  * If we're using line numbers, we might as well
427                  * remember the information we have now (the position
428                  * and line number of the current line).
429                  */
430                 if (linenums)
431                         add_lnum(linenum, pos);
432
433                 /*
434                  * Test the line to see if we have a match.
435                  * Use strncmp because the pattern may be
436                  * truncated (in the tags file) if it is too long.
437                  * If tagendline is set, make sure we match all
438                  * the way to end of line (no extra chars after the match).
439                  */
440                 len = (int) strlen(curtag->tag_pattern);
441                 if (strncmp(curtag->tag_pattern, line, len) == 0 &&
442                     (!curtag->tag_endline || line[len] == '\0' || line[len] == '\r'))
443                 {
444                         curtag->tag_linenum = find_linenum(linepos);
445                         break;
446                 }
447         }
448
449         return (linepos);
450 }
451
452 /*******************************************************************************
453  * gtags
454  */
455
456 /*
457  * Find tags in the GLOBAL's tag file.
458  * The findgtag() will try and load information about the requested tag.
459  * It does this by calling "global -x tag" and storing the parsed output
460  * for future use by gtagsearch().
461  * Sets curtag to the first tag entry.
462  */
463         static enum tag_result
464 findgtag(char *tag, int type)
465 {
466         char buf[256];
467         FILE *fp;
468         struct tag *tp;
469
470         if (type != T_CTAGS_X && tag == NULL)
471                 return TAG_NOFILE;
472
473         cleantags();
474         total = 0;
475
476         /*
477          * If type == T_CTAGS_X then read ctags's -x format from stdin
478          * else execute global(1) and read from it.
479          */
480         if (type == T_CTAGS_X)
481         {
482                 fp = stdin;
483                 /* Set tag default because we cannot read stdin again. */
484                 tags = ztags;
485         } else
486         {
487 #if !HAVE_POPEN
488                 return TAG_NOFILE;
489 #else
490                 char *command;
491                 char *flag;
492                 char *qtag;
493                 char *cmd = lgetenv("LESSGLOBALTAGS");
494
495                 if (cmd == NULL || *cmd == '\0')
496                         return TAG_NOFILE;
497                 /* Get suitable flag value for global(1). */
498                 switch (type)
499                 {
500                 case T_GTAGS:
501                         flag = "" ;
502                         break;
503                 case T_GRTAGS:
504                         flag = "r";
505                         break;
506                 case T_GSYMS:
507                         flag = "s";
508                         break;
509                 case T_GPATH:
510                         flag = "P";
511                         break;
512                 default:
513                         return TAG_NOTYPE;
514                 }
515
516                 /* Get our data from global(1). */
517                 qtag = shell_quote(tag);
518                 if (qtag == NULL)
519                         qtag = tag;
520                 command = (char *) ecalloc(strlen(cmd) + strlen(flag) +
521                                 strlen(qtag) + 5, sizeof(char));
522                 sprintf(command, "%s -x%s %s", cmd, flag, qtag);
523                 if (qtag != tag)
524                         free(qtag);
525                 fp = popen(command, "r");
526                 free(command);
527 #endif
528         }
529         if (fp != NULL)
530         {
531                 while (fgets(buf, sizeof(buf), fp))
532                 {
533                         char *name, *file, *line;
534                         int len;
535
536                         if (sigs)
537                         {
538 #if HAVE_POPEN
539                                 if (fp != stdin)
540                                         pclose(fp);
541 #endif
542                                 return TAG_INTR;
543                         }
544                         len = (int) strlen(buf);
545                         if (len > 0 && buf[len-1] == '\n')
546                                 buf[len-1] = '\0';
547                         else
548                         {
549                                 int c;
550                                 do {
551                                         c = fgetc(fp);
552                                 } while (c != '\n' && c != EOF);
553                         }
554
555                         if (getentry(buf, &name, &file, &line))
556                         {
557                                 /*
558                                  * Couldn't parse this line for some reason.
559                                  * We'll just pretend it never happened.
560                                  */
561                                 break;
562                         }
563
564                         /* Make new entry and add to list. */
565                         tp = maketagent(name, file, (LINENUM) atoi(line), NULL, 0);
566                         TAG_INS(tp);
567                         total++;
568                 }
569                 if (fp != stdin)
570                 {
571                         if (pclose(fp))
572                         {
573                                 curtag = NULL;
574                                 total = curseq = 0;
575                                 return TAG_NOFILE;
576                         }
577                 }
578         }
579
580         /* Check to see if we found anything. */
581         tp = taglist.tl_first;
582         if (tp == TAG_END)
583                 return TAG_NOTAG;
584         curtag = tp;
585         curseq = 1;
586         return TAG_FOUND;
587 }
588
589 static int circular = 0;        /* 1: circular tag structure */
590
591 /*
592  * Return the filename required for the next gtag in the queue that was setup
593  * by findgtag().  The next call to gtagsearch() will try to position at the
594  * appropriate tag.
595  */
596         static char *
597 nextgtag(void)
598 {
599         struct tag *tp;
600
601         if (curtag == NULL)
602                 /* No tag loaded */
603                 return NULL;
604
605         tp = curtag->next;
606         if (tp == TAG_END)
607         {
608                 if (!circular)
609                         return NULL;
610                 /* Wrapped around to the head of the queue */
611                 curtag = taglist.tl_first;
612                 curseq = 1;
613         } else
614         {
615                 curtag = tp;
616                 curseq++;
617         }
618         return (curtag->tag_file);
619 }
620
621 /*
622  * Return the filename required for the previous gtag in the queue that was
623  * setup by findgtat().  The next call to gtagsearch() will try to position
624  * at the appropriate tag.
625  */
626         static char *
627 prevgtag(void)
628 {
629         struct tag *tp;
630
631         if (curtag == NULL)
632                 /* No tag loaded */
633                 return NULL;
634
635         tp = curtag->prev;
636         if (tp == TAG_END)
637         {
638                 if (!circular)
639                         return NULL;
640                 /* Wrapped around to the tail of the queue */
641                 curtag = taglist.tl_last;
642                 curseq = total;
643         } else
644         {
645                 curtag = tp;
646                 curseq--;
647         }
648         return (curtag->tag_file);
649 }
650
651 /*
652  * Position the current file at at what is hopefully the tag that was chosen
653  * using either findtag() or one of nextgtag() and prevgtag().  Returns -1
654  * if it was unable to position at the tag, 0 if successful.
655  */
656         static POSITION
657 gtagsearch(void)
658 {
659         if (curtag == NULL)
660                 return (NULL_POSITION);  /* No gtags loaded! */
661         return (find_pos(curtag->tag_linenum));
662 }
663
664 /*
665  * The getentry() parses both standard and extended ctags -x format.
666  *
667  * [standard format]
668  * <tag>   <lineno>  <file>         <image>
669  * +------------------------------------------------
670  * |main     30      main.c         main(argc, argv)
671  * |func     21      subr.c         func(arg)
672  *
673  * The following commands write this format.
674  *      o Traditinal Ctags with -x option
675  *      o Global with -x option
676  *              See <http://www.gnu.org/software/global/global.html>
677  *
678  * [extended format]
679  * <tag>   <type>  <lineno>   <file>        <image>
680  * +----------------------------------------------------------
681  * |main     function 30      main.c         main(argc, argv)
682  * |func     function 21      subr.c         func(arg)
683  *
684  * The following commands write this format.
685  *      o Exuberant Ctags with -x option
686  *              See <http://ctags.sourceforge.net>
687  *
688  * Returns 0 on success, -1 on error.
689  * The tag, file, and line will each be NUL-terminated pointers
690  * into buf.
691  */
692         static int
693 getentry(char *buf, char **tag, char **file, char **line)
694 {
695         char *p = buf;
696
697         for (*tag = p;  *p && !IS_SPACE(*p);  p++)      /* tag name */
698                 ;
699         if (*p == 0)
700                 return (-1);
701         *p++ = 0;
702         for ( ;  *p && IS_SPACE(*p);  p++)              /* (skip blanks) */
703                 ;
704         if (*p == 0)
705                 return (-1);
706         /*
707          * If the second part begin with other than digit,
708          * it is assumed tag type. Skip it.
709          */
710         if (!IS_DIGIT(*p))
711         {
712                 for ( ;  *p && !IS_SPACE(*p);  p++)     /* (skip tag type) */
713                         ;
714                 for (;  *p && IS_SPACE(*p);  p++)       /* (skip blanks) */
715                         ;
716         }
717         if (!IS_DIGIT(*p))
718                 return (-1);
719         *line = p;                                      /* line number */
720         for (*line = p;  *p && !IS_SPACE(*p);  p++)
721                 ;
722         if (*p == 0)
723                 return (-1);
724         *p++ = 0;
725         for ( ; *p && IS_SPACE(*p);  p++)               /* (skip blanks) */
726                 ;
727         if (*p == 0)
728                 return (-1);
729         *file = p;                                      /* file name */
730         for (*file = p;  *p && !IS_SPACE(*p);  p++)
731                 ;
732         if (*p == 0)
733                 return (-1);
734         *p = 0;
735
736         /* value check */
737         if (strlen(*tag) && strlen(*line) && strlen(*file) && atoi(*line) > 0)
738                 return (0);
739         return (-1);
740 }
741   
742 #endif