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