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