tags.c revision 1.11
1/*
2 * Copyright (C) 1984-2012  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
17public char *tags = "tags";
18
19static int total;
20static int curseq;
21
22extern int linenums;
23extern volatile sig_atomic_t sigs;
24
25enum tag_result {
26	TAG_FOUND,
27	TAG_NOFILE,
28	TAG_NOTAG,
29	TAG_NOTYPE,
30	TAG_INTR
31};
32
33/*
34 * Tag type
35 */
36enum {
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
45static enum tag_result findctag();
46static enum tag_result findgtag();
47static char *nextgtag();
48static char *prevgtag();
49static POSITION ctagsearch();
50static POSITION gtagsearch();
51static 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.
58 * findctag() uses either pattern (in which case line number is 0),
59 * or line number (in which case pattern is NULL).
60 */
61struct taglist {
62	struct tag *tl_first;
63	struct tag *tl_last;
64};
65#define TAG_END  ((struct tag *) &taglist)
66static struct taglist taglist = { TAG_END, TAG_END };
67struct 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};
74static struct tag *curtag;
75
76#define TAG_INS(tp) \
77	(tp)->next = TAG_END; \
78	(tp)->prev = taglist.tl_last; \
79	taglist.tl_last->next = (tp); \
80	taglist.tl_last = (tp);
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
90cleantags()
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 *
112maketagent(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
136gettagtype()
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
167findtag(tag)
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
198tagsearch()
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 *
212nexttag(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 *
226prevtag(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
240ntags()
241{
242	return total;
243}
244
245/*
246 * Return the sequence number of current tag.
247 */
248	public int
249curr_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
263findctag(tag)
264	register char *tag;
265{
266	char *p;
267	register FILE *f;
268	register int taglen;
269	LINENUM taglinenum;
270	char *tagfile;
271	char *tagpattern;
272	int tagendline;
273	int search_char;
274	int err;
275	char tline[TAGLINE_SIZE];
276	struct tag *tp;
277
278	p = shell_unquote(tags);
279	f = fopen(p, "r");
280	free(p);
281	if (f == NULL)
282		return TAG_NOFILE;
283
284	cleantags();
285	total = 0;
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	{
293		if (tline[0] == '!')
294			/* Skip header of extended format. */
295			continue;
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		 */
307		tagpattern = NULL;
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		 */
333		tagendline = 0;
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++;
347			tagpattern = p;
348			while (*p != search_char && *p != '\0')
349			{
350				if (*p == '\\')
351					p++;
352				p++;
353			}
354			tagendline = (p[-1] == '$');
355			if (tagendline)
356				p--;
357			*p = '\0';
358		}
359		tp = maketagent(tag, tagfile, taglinenum, tagpattern, tagendline);
360		TAG_INS(tp);
361		total++;
362	}
363	fclose(f);
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
375edit_tagfile()
376{
377	if (curtag == NULL)
378		return (1);
379	return (edit(curtag->tag_file));
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 */
391	static POSITION
392ctagsearch()
393{
394	POSITION pos, linepos;
395	LINENUM linenum;
396	int len;
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;
416		pos = forw_raw_line(pos, &line, (int *)NULL);
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.
441		 * If tagendline is set, make sure we match all
442		 * the way to end of line (no extra chars after the match).
443		 */
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);
449			break;
450		}
451	}
452
453	return (linepos);
454}
455
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
468findgtag(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;
475	size_t len;
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
497		char *command;
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;
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);
530		if (qtag != tag)
531			free(qtag);
532		fp = popen(command, "r");
533		free(command);
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			}
550			len = strlen(buf);
551			if (len > 0 && buf[len-1] == '\n')
552				buf[len-1] = '\0';
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
595static 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 *
603nextgtag()
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 *
633prevgtag()
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
660 * if it was unable to position at the tag, 0 if successful.
661 */
662	static POSITION
663gtagsearch()
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
699getentry(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
707	for (*tag = p;  *p && !IS_SPACE(*p);  p++)	/* tag name */
708		;
709	if (*p == 0)
710		return (-1);
711	*p++ = 0;
712	for ( ;  *p && IS_SPACE(*p);  p++)		/* (skip blanks) */
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	 */
720	if (!IS_DIGIT(*p))
721	{
722		for ( ;  *p && !IS_SPACE(*p);  p++)	/* (skip tag type) */
723			;
724		for (;  *p && IS_SPACE(*p);  p++)	/* (skip blanks) */
725			;
726	}
727	if (!IS_DIGIT(*p))
728		return (-1);
729	*line = p;					/* line number */
730	for (*line = p;  *p && !IS_SPACE(*p);  p++)
731		;
732	if (*p == 0)
733		return (-1);
734	*p++ = 0;
735	for ( ; *p && IS_SPACE(*p);  p++)		/* (skip blanks) */
736		;
737	if (*p == 0)
738		return (-1);
739	*file = p;					/* file name */
740	for (*file = p;  *p && !IS_SPACE(*p);  p++)
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}
751
752#endif
753