tags.c revision 1.17
1/*
2 * Copyright (C) 1984-2012  Mark Nudelman
3 * Modified for use with illumos by Garrett D'Amore.
4 * Copyright 2014 Garrett D'Amore <garrett@damore.org>
5 *
6 * You may distribute under the terms of either the GNU General Public
7 * License or the Less License, as specified in the README file.
8 *
9 * For more information, see the README file.
10 */
11
12#include "less.h"
13
14#define	WHITESP(c)	((c) == ' ' || (c) == '\t')
15
16char *tags = "tags";
17
18static int total;
19static int curseq;
20
21extern int linenums;
22extern volatile sig_atomic_t sigs;
23
24enum tag_result {
25	TAG_FOUND,
26	TAG_NOFILE,
27	TAG_NOTAG,
28	TAG_NOTYPE,
29	TAG_INTR
30};
31
32static enum tag_result findctag(char *);
33static char *nextctag(void);
34static char *prevctag(void);
35static off_t ctagsearch(void);
36
37/*
38 * The list of tags generated by the last findctag() call.
39 */
40struct taglist {
41	struct tag *tl_first;
42	struct tag *tl_last;
43};
44#define	TAG_END  ((struct tag *)&taglist)
45static struct taglist taglist = { TAG_END, TAG_END };
46struct tag {
47	struct tag *next, *prev; /* List links */
48	char *tag_file;		/* Source file containing the tag */
49	off_t tag_linenum;	/* Appropriate line number in source file */
50	char *tag_pattern;	/* Pattern used to find the tag */
51	int tag_endline;	/* True if the pattern includes '$' */
52};
53static struct tag *curtag;
54
55#define	TAG_INS(tp) \
56	(tp)->next = TAG_END; \
57	(tp)->prev = taglist.tl_last; \
58	taglist.tl_last->next = (tp); \
59	taglist.tl_last = (tp);
60
61#define	TAG_RM(tp) \
62	(tp)->next->prev = (tp)->prev; \
63	(tp)->prev->next = (tp)->next;
64
65/*
66 * Delete tag structures.
67 */
68void
69cleantags(void)
70{
71	struct tag *tp;
72
73	/*
74	 * Delete any existing tag list.
75	 * {{ Ideally, we wouldn't do this until after we know that we
76	 *    can load some other tag information. }}
77	 */
78	while ((tp = taglist.tl_first) != TAG_END) {
79		TAG_RM(tp);
80		free(tp);
81	}
82	curtag = NULL;
83	total = curseq = 0;
84}
85
86/*
87 * Create a new tag entry.
88 */
89static struct tag *
90maketagent(char *file, off_t linenum, char *pattern, int endline)
91{
92	struct tag *tp;
93
94	tp = ecalloc(sizeof (struct tag), 1);
95	tp->tag_file = estrdup(file);
96	tp->tag_linenum = linenum;
97	tp->tag_endline = endline;
98	if (pattern == NULL)
99		tp->tag_pattern = NULL;
100	else
101		tp->tag_pattern = estrdup(pattern);
102	return (tp);
103}
104
105/*
106 * Find tags in tag file.
107 */
108void
109findtag(char *tag)
110{
111	enum tag_result result;
112
113	result = findctag(tag);
114	switch (result) {
115	case TAG_FOUND:
116	case TAG_INTR:
117		break;
118	case TAG_NOFILE:
119		error("No tags file", NULL);
120		break;
121	case TAG_NOTAG:
122		error("No such tag in tags file", NULL);
123		break;
124	case TAG_NOTYPE:
125		error("unknown tag type", NULL);
126		break;
127	}
128}
129
130/*
131 * Search for a tag.
132 */
133off_t
134tagsearch(void)
135{
136	if (curtag == NULL)
137		return (-1);   /* No tags loaded! */
138	if (curtag->tag_linenum != 0)
139		return (find_pos(curtag->tag_linenum));
140	return (ctagsearch());
141}
142
143/*
144 * Go to the next tag.
145 */
146char *
147nexttag(int n)
148{
149	char *tagfile = NULL;
150
151	while (n-- > 0)
152		tagfile = nextctag();
153	return (tagfile);
154}
155
156/*
157 * Go to the previous tag.
158 */
159char *
160prevtag(int n)
161{
162	char *tagfile = NULL;
163
164	while (n-- > 0)
165		tagfile = prevctag();
166	return (tagfile);
167}
168
169/*
170 * Return the total number of tags.
171 */
172int
173ntags(void)
174{
175	return (total);
176}
177
178/*
179 * Return the sequence number of current tag.
180 */
181int
182curr_tag(void)
183{
184	return (curseq);
185}
186
187/*
188 * Find tags in the "tags" file.
189 * Sets curtag to the first tag entry.
190 */
191static enum tag_result
192findctag(char *tag)
193{
194	char *p;
195	FILE *f;
196	int taglen;
197	off_t taglinenum;
198	char *tagfile;
199	char *tagpattern;
200	int tagendline;
201	int search_char;
202	int err;
203	char tline[TAGLINE_SIZE];
204	struct tag *tp;
205
206	p = shell_unquote(tags);
207	f = fopen(p, "r");
208	free(p);
209	if (f == NULL)
210		return (TAG_NOFILE);
211
212	cleantags();
213	total = 0;
214	taglen = strlen(tag);
215
216	/*
217	 * Search the tags file for the desired tag.
218	 */
219	while (fgets(tline, sizeof (tline), f) != NULL) {
220		if (tline[0] == '!')
221			/* Skip header of extended format. */
222			continue;
223		if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen]))
224			continue;
225
226		/*
227		 * Found it.
228		 * The line contains the tag, the filename and the
229		 * location in the file, separated by white space.
230		 * The location is either a decimal line number,
231		 * or a search pattern surrounded by a pair of delimiters.
232		 * Parse the line and extract these parts.
233		 */
234		tagpattern = NULL;
235
236		/*
237		 * Skip over the whitespace after the tag name.
238		 */
239		p = skipsp(tline+taglen);
240		if (*p == '\0')
241			/* File name is missing! */
242			continue;
243
244		/*
245		 * Save the file name.
246		 * Skip over the whitespace after the file name.
247		 */
248		tagfile = p;
249		while (!WHITESP(*p) && *p != '\0')
250			p++;
251		*p++ = '\0';
252		p = skipsp(p);
253		if (*p == '\0')
254			/* Pattern is missing! */
255			continue;
256
257		/*
258		 * First see if it is a line number.
259		 */
260		tagendline = 0;
261		taglinenum = getnum(&p, 0, &err);
262		if (err) {
263			/*
264			 * No, it must be a pattern.
265			 * Delete the initial "^" (if present) and
266			 * the final "$" from the pattern.
267			 * Delete any backslash in the pattern.
268			 */
269			taglinenum = 0;
270			search_char = *p++;
271			if (*p == '^')
272				p++;
273			tagpattern = p;
274			while (*p != search_char && *p != '\0') {
275				if (*p == '\\')
276					p++;
277				p++;
278			}
279			tagendline = (p[-1] == '$');
280			if (tagendline)
281				p--;
282			*p = '\0';
283		}
284		tp = maketagent(tagfile, taglinenum, tagpattern, tagendline);
285		TAG_INS(tp);
286		total++;
287	}
288	fclose(f);
289	if (total == 0)
290		return (TAG_NOTAG);
291	curtag = taglist.tl_first;
292	curseq = 1;
293	return (TAG_FOUND);
294}
295
296/*
297 * Edit current tagged file.
298 */
299int
300edit_tagfile(void)
301{
302	if (curtag == NULL)
303		return (1);
304	return (edit(curtag->tag_file));
305}
306
307/*
308 * Search for a tag.
309 * This is a stripped-down version of search().
310 * We don't use search() for several reasons:
311 *   -	We don't want to blow away any search string we may have saved.
312 *   -	The various regular-expression functions (from different systems:
313 *	regcmp vs. re_comp) behave differently in the presence of
314 *	parentheses (which are almost always found in a tag).
315 */
316static off_t
317ctagsearch(void)
318{
319	off_t pos, linepos;
320	off_t linenum;
321	int len;
322	char *line;
323
324	pos = ch_zero();
325	linenum = find_linenum(pos);
326
327	for (;;) {
328		/*
329		 * Get lines until we find a matching one or
330		 * until we hit end-of-file.
331		 */
332		if (ABORT_SIGS())
333			return (-1);
334
335		/*
336		 * Read the next line, and save the
337		 * starting position of that line in linepos.
338		 */
339		linepos = pos;
340		pos = forw_raw_line(pos, &line, (int *)NULL);
341		if (linenum != 0)
342			linenum++;
343
344		if (pos == -1) {
345			/*
346			 * We hit EOF without a match.
347			 */
348			error("Tag not found", NULL);
349			return (-1);
350		}
351
352		/*
353		 * If we're using line numbers, we might as well
354		 * remember the information we have now (the position
355		 * and line number of the current line).
356		 */
357		if (linenums)
358			add_lnum(linenum, pos);
359
360		/*
361		 * Test the line to see if we have a match.
362		 * Use strncmp because the pattern may be
363		 * truncated (in the tags file) if it is too long.
364		 * If tagendline is set, make sure we match all
365		 * the way to end of line (no extra chars after the match).
366		 */
367		len = strlen(curtag->tag_pattern);
368		if (strncmp(curtag->tag_pattern, line, len) == 0 &&
369		    (!curtag->tag_endline || line[len] == '\0' ||
370		    line[len] == '\r')) {
371			curtag->tag_linenum = find_linenum(linepos);
372			break;
373		}
374	}
375
376	return (linepos);
377}
378
379static int circular = 0;	/* 1: circular tag structure */
380
381/*
382 * Return the filename required for the next tag in the queue that was setup
383 * by findctag().  The next call to ctagsearch() will try to position at the
384 * appropriate tag.
385 */
386static char *
387nextctag(void)
388{
389	struct tag *tp;
390
391	if (curtag == NULL)
392		/* No tag loaded */
393		return (NULL);
394
395	tp = curtag->next;
396	if (tp == TAG_END) {
397		if (!circular)
398			return (NULL);
399		/* Wrapped around to the head of the queue */
400		curtag = taglist.tl_first;
401		curseq = 1;
402	} else {
403		curtag = tp;
404		curseq++;
405	}
406	return (curtag->tag_file);
407}
408
409/*
410 * Return the filename required for the previous ctag in the queue that was
411 * setup by findctag().  The next call to ctagsearch() will try to position
412 * at the appropriate tag.
413 */
414static char *
415prevctag(void)
416{
417	struct tag *tp;
418
419	if (curtag == NULL)
420		/* No tag loaded */
421		return (NULL);
422
423	tp = curtag->prev;
424	if (tp == TAG_END) {
425		if (!circular)
426			return (NULL);
427		/* Wrapped around to the tail of the queue */
428		curtag = taglist.tl_last;
429		curseq = total;
430	} else {
431		curtag = tp;
432		curseq--;
433	}
434	return (curtag->tag_file);
435}
436