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