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