Merge commit 'db1c88f6dab43484b6c33636600ac4596ff4c354'
[unleashed.git] / bin / less / tags.c
blob9c7cba9688328db99176e313ae044ee8ccaa537c
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>
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.
9 * For more information, see the README file.
12 #include "less.h"
14 #define WHITESP(c) ((c) == ' ' || (c) == '\t')
16 char *tags = "tags";
18 static int total;
19 static int curseq;
21 extern int linenums;
22 extern volatile sig_atomic_t sigs;
24 enum tag_result {
25 TAG_FOUND,
26 TAG_NOFILE,
27 TAG_NOTAG,
28 TAG_NOTYPE,
29 TAG_INTR
32 static enum tag_result findctag(char *);
33 static char *nextctag(void);
34 static char *prevctag(void);
35 static off_t ctagsearch(void);
38 * The list of tags generated by the last findctag() call.
40 struct taglist {
41 struct tag *tl_first;
42 struct tag *tl_last;
44 #define TAG_END ((struct tag *)&taglist)
45 static struct taglist taglist = { TAG_END, TAG_END };
46 struct 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 '$' */
53 static struct tag *curtag;
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);
61 #define TAG_RM(tp) \
62 (tp)->next->prev = (tp)->prev; \
63 (tp)->prev->next = (tp)->next;
66 * Delete tag structures.
68 void
69 cleantags(void)
71 struct tag *tp;
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. }}
78 while ((tp = taglist.tl_first) != TAG_END) {
79 TAG_RM(tp);
80 free(tp->tag_file);
81 free(tp->tag_pattern);
82 free(tp);
84 curtag = NULL;
85 total = curseq = 0;
89 * Create a new tag entry.
91 static struct tag *
92 maketagent(char *file, off_t linenum, char *pattern, int endline)
94 struct tag *tp;
96 tp = ecalloc(sizeof (struct tag), 1);
97 tp->tag_file = estrdup(file);
98 tp->tag_linenum = linenum;
99 tp->tag_endline = endline;
100 if (pattern == NULL)
101 tp->tag_pattern = NULL;
102 else
103 tp->tag_pattern = estrdup(pattern);
104 return (tp);
108 * Find tags in tag file.
110 void
111 findtag(char *tag)
113 enum tag_result result;
115 result = findctag(tag);
116 switch (result) {
117 case TAG_FOUND:
118 case TAG_INTR:
119 break;
120 case TAG_NOFILE:
121 error("No tags file", NULL);
122 break;
123 case TAG_NOTAG:
124 error("No such tag in tags file", NULL);
125 break;
126 case TAG_NOTYPE:
127 error("unknown tag type", NULL);
128 break;
133 * Search for a tag.
135 off_t
136 tagsearch(void)
138 if (curtag == NULL)
139 return (-1); /* No tags loaded! */
140 if (curtag->tag_linenum != 0)
141 return (find_pos(curtag->tag_linenum));
142 return (ctagsearch());
146 * Go to the next tag.
148 char *
149 nexttag(int n)
151 char *tagfile = NULL;
153 while (n-- > 0)
154 tagfile = nextctag();
155 return (tagfile);
159 * Go to the previous tag.
161 char *
162 prevtag(int n)
164 char *tagfile = NULL;
166 while (n-- > 0)
167 tagfile = prevctag();
168 return (tagfile);
172 * Return the total number of tags.
175 ntags(void)
177 return (total);
181 * Return the sequence number of current tag.
184 curr_tag(void)
186 return (curseq);
190 * Find tags in the "tags" file.
191 * Sets curtag to the first tag entry.
193 static enum tag_result
194 findctag(char *tag)
196 char *p;
197 FILE *f;
198 int taglen;
199 off_t taglinenum;
200 char *tagfile;
201 char *tagpattern;
202 int tagendline;
203 int search_char;
204 int err;
205 char tline[TAGLINE_SIZE];
206 struct tag *tp;
208 p = shell_unquote(tags);
209 f = fopen(p, "r");
210 free(p);
211 if (f == NULL)
212 return (TAG_NOFILE);
214 cleantags();
215 total = 0;
216 taglen = strlen(tag);
219 * Search the tags file for the desired tag.
221 while (fgets(tline, sizeof (tline), f) != NULL) {
222 if (tline[0] == '!')
223 /* Skip header of extended format. */
224 continue;
225 if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen]))
226 continue;
229 * Found it.
230 * The line contains the tag, the filename and the
231 * location in the file, separated by white space.
232 * The location is either a decimal line number,
233 * or a search pattern surrounded by a pair of delimiters.
234 * Parse the line and extract these parts.
236 tagpattern = NULL;
239 * Skip over the whitespace after the tag name.
241 p = skipsp(tline+taglen);
242 if (*p == '\0')
243 /* File name is missing! */
244 continue;
247 * Save the file name.
248 * Skip over the whitespace after the file name.
250 tagfile = p;
251 while (!WHITESP(*p) && *p != '\0')
252 p++;
253 *p++ = '\0';
254 p = skipsp(p);
255 if (*p == '\0')
256 /* Pattern is missing! */
257 continue;
260 * First see if it is a line number.
262 tagendline = 0;
263 taglinenum = getnum(&p, 0, &err);
264 if (err) {
266 * No, it must be a pattern.
267 * Delete the initial "^" (if present) and
268 * the final "$" from the pattern.
269 * Delete any backslash in the pattern.
271 taglinenum = 0;
272 search_char = *p++;
273 if (*p == '^')
274 p++;
275 tagpattern = p;
276 while (*p != search_char && *p != '\0') {
277 if (*p == '\\')
278 p++;
279 p++;
281 tagendline = (p[-1] == '$');
282 if (tagendline)
283 p--;
284 *p = '\0';
286 tp = maketagent(tagfile, taglinenum, tagpattern, tagendline);
287 TAG_INS(tp);
288 total++;
290 fclose(f);
291 if (total == 0)
292 return (TAG_NOTAG);
293 curtag = taglist.tl_first;
294 curseq = 1;
295 return (TAG_FOUND);
299 * Edit current tagged file.
302 edit_tagfile(void)
304 if (curtag == NULL)
305 return (1);
306 return (edit(curtag->tag_file));
310 * Search for a tag.
311 * This is a stripped-down version of search().
312 * We don't use search() for several reasons:
313 * - We don't want to blow away any search string we may have saved.
314 * - The various regular-expression functions (from different systems:
315 * regcmp vs. re_comp) behave differently in the presence of
316 * parentheses (which are almost always found in a tag).
318 static off_t
319 ctagsearch(void)
321 off_t pos, linepos;
322 off_t linenum;
323 int len;
324 char *line;
326 pos = ch_zero();
327 linenum = find_linenum(pos);
329 for (;;) {
331 * Get lines until we find a matching one or
332 * until we hit end-of-file.
334 if (ABORT_SIGS())
335 return (-1);
338 * Read the next line, and save the
339 * starting position of that line in linepos.
341 linepos = pos;
342 pos = forw_raw_line(pos, &line, (int *)NULL);
343 if (linenum != 0)
344 linenum++;
346 if (pos == -1) {
348 * We hit EOF without a match.
350 error("Tag not found", NULL);
351 return (-1);
355 * If we're using line numbers, we might as well
356 * remember the information we have now (the position
357 * and line number of the current line).
359 if (linenums)
360 add_lnum(linenum, pos);
363 * Test the line to see if we have a match.
364 * Use strncmp because the pattern may be
365 * truncated (in the tags file) if it is too long.
366 * If tagendline is set, make sure we match all
367 * the way to end of line (no extra chars after the match).
369 len = strlen(curtag->tag_pattern);
370 if (strncmp(curtag->tag_pattern, line, len) == 0 &&
371 (!curtag->tag_endline || line[len] == '\0' ||
372 line[len] == '\r')) {
373 curtag->tag_linenum = find_linenum(linepos);
374 break;
378 return (linepos);
381 static int circular = 0; /* 1: circular tag structure */
384 * Return the filename required for the next tag in the queue that was setup
385 * by findctag(). The next call to ctagsearch() will try to position at the
386 * appropriate tag.
388 static char *
389 nextctag(void)
391 struct tag *tp;
393 if (curtag == NULL)
394 /* No tag loaded */
395 return (NULL);
397 tp = curtag->next;
398 if (tp == TAG_END) {
399 if (!circular)
400 return (NULL);
401 /* Wrapped around to the head of the queue */
402 curtag = taglist.tl_first;
403 curseq = 1;
404 } else {
405 curtag = tp;
406 curseq++;
408 return (curtag->tag_file);
412 * Return the filename required for the previous ctag in the queue that was
413 * setup by findctag(). The next call to ctagsearch() will try to position
414 * at the appropriate tag.
416 static char *
417 prevctag(void)
419 struct tag *tp;
421 if (curtag == NULL)
422 /* No tag loaded */
423 return (NULL);
425 tp = curtag->prev;
426 if (tp == TAG_END) {
427 if (!circular)
428 return (NULL);
429 /* Wrapped around to the tail of the queue */
430 curtag = taglist.tl_last;
431 curseq = total;
432 } else {
433 curtag = tp;
434 curseq--;
436 return (curtag->tag_file);