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 16char *tags = "tags"; 17 18static int total; 19static int curseq; 20 21extern int linenums; 22 23enum tag_result { 24 TAG_FOUND, 25 TAG_NOFILE, 26 TAG_NOTAG, 27 TAG_NOTYPE, 28 TAG_INTR 29}; 30 31static enum tag_result findctag(char *); 32static char *nextctag(void); 33static char *prevctag(void); 34static off_t ctagsearch(void); 35 36/* 37 * The list of tags generated by the last findctag() call. 38 */ 39struct taglist { 40 struct tag *tl_first; 41 struct tag *tl_last; 42}; 43#define TAG_END ((struct tag *)&taglist) 44static struct taglist taglist = { TAG_END, TAG_END }; 45struct 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}; 52static 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 */ 67void 68cleantags(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 */ 90static struct tag * 91maketagent(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 */ 109void 110findtag(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 */ 134off_t 135tagsearch(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 */ 147char * 148nexttag(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 */ 160char * 161prevtag(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 */ 173int 174ntags(void) 175{ 176 return (total); 177} 178 179/* 180 * Return the sequence number of current tag. 181 */ 182int 183curr_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 */ 192static enum tag_result 193findctag(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 */ 300int 301edit_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 */ 317static off_t 318ctagsearch(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 380static 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 */ 387static char * 388nextctag(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 */ 415static char * 416prevctag(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