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