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