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