tags.c revision 1.12
1/* 2 * Copyright (C) 1984-2012 Mark Nudelman 3 * 4 * You may distribute under the terms of either the GNU General Public 5 * License or the Less License, as specified in the README file. 6 * 7 * For more information, see the README file. 8 */ 9/* 10 * Modified for use with illumos. 11 * Copyright 2014 Garrett D'Amore <garrett@damore.org> 12 */ 13 14#include "less.h" 15 16#define WHITESP(c) ((c) == ' ' || (c) == '\t') 17 18char *tags = "tags"; 19 20static int total; 21static int curseq; 22 23extern int linenums; 24extern volatile sig_atomic_t sigs; 25 26enum tag_result { 27 TAG_FOUND, 28 TAG_NOFILE, 29 TAG_NOTAG, 30 TAG_NOTYPE, 31 TAG_INTR 32}; 33 34/* 35 * Tag type 36 */ 37enum { 38 T_CTAGS, /* 'tags': standard and extended format (ctags) */ 39 T_CTAGS_X, /* stdin: cross reference format (ctags) */ 40 T_GTAGS, /* 'GTAGS': function defenition (global) */ 41 T_GRTAGS, /* 'GRTAGS': function reference (global) */ 42 T_GSYMS, /* 'GSYMS': other symbols (global) */ 43 T_GPATH /* 'GPATH': path name (global) */ 44}; 45 46static enum tag_result findctag(); 47static enum tag_result findgtag(); 48static char *nextgtag(); 49static char *prevgtag(); 50static off_t ctagsearch(); 51static off_t gtagsearch(); 52static int getentry(); 53 54/* 55 * The list of tags generated by the last findgtag() call. 56 * 57 * Use either pattern or line number. 58 * findgtag() always uses line number, so pattern is always NULL. 59 * findctag() uses either pattern (in which case line number is 0), 60 * or line number (in which case pattern is NULL). 61 */ 62struct taglist { 63 struct tag *tl_first; 64 struct tag *tl_last; 65}; 66#define TAG_END ((struct tag *)&taglist) 67static struct taglist taglist = { TAG_END, TAG_END }; 68struct tag { 69 struct tag *next, *prev; /* List links */ 70 char *tag_file; /* Source file containing the tag */ 71 LINENUM tag_linenum; /* Appropriate line number in source file */ 72 char *tag_pattern; /* Pattern used to find the tag */ 73 int tag_endline; /* True if the pattern includes '$' */ 74}; 75static struct tag *curtag; 76 77#define TAG_INS(tp) \ 78 (tp)->next = TAG_END; \ 79 (tp)->prev = taglist.tl_last; \ 80 taglist.tl_last->next = (tp); \ 81 taglist.tl_last = (tp); 82 83#define TAG_RM(tp) \ 84 (tp)->next->prev = (tp)->prev; \ 85 (tp)->prev->next = (tp)->next; 86 87/* 88 * Delete tag structures. 89 */ 90void 91cleantags(void) 92{ 93 struct tag *tp; 94 95 /* 96 * Delete any existing tag list. 97 * {{ Ideally, we wouldn't do this until after we know that we 98 * can load some other tag information. }} 99 */ 100 while ((tp = taglist.tl_first) != TAG_END) { 101 TAG_RM(tp); 102 free(tp); 103 } 104 curtag = NULL; 105 total = curseq = 0; 106} 107 108/* 109 * Create a new tag entry. 110 */ 111static struct tag * 112maketagent(char *file, LINENUM linenum, char *pattern, int endline) 113{ 114 struct tag *tp; 115 116 tp = ecalloc(sizeof (struct tag), 1); 117 tp->tag_file = save(file); 118 tp->tag_linenum = linenum; 119 tp->tag_endline = endline; 120 if (pattern == NULL) 121 tp->tag_pattern = NULL; 122 else 123 tp->tag_pattern = save(pattern); 124 return (tp); 125} 126 127/* 128 * Get tag mode. 129 */ 130static int 131gettagtype(void) 132{ 133 int f; 134 135 if (strcmp(tags, "GTAGS") == 0) 136 return (T_GTAGS); 137 if (strcmp(tags, "GRTAGS") == 0) 138 return (T_GRTAGS); 139 if (strcmp(tags, "GSYMS") == 0) 140 return (T_GSYMS); 141 if (strcmp(tags, "GPATH") == 0) 142 return (T_GPATH); 143 if (strcmp(tags, "-") == 0) 144 return (T_CTAGS_X); 145 f = open(tags, OPEN_READ); 146 if (f >= 0) { 147 (void) close(f); 148 return (T_CTAGS); 149 } 150 return (T_GTAGS); 151} 152 153/* 154 * Find tags in tag file. 155 * Find a tag in the "tags" file. 156 * Sets "tag_file" to the name of the file containing the tag, 157 * and "tagpattern" to the search pattern which should be used 158 * to find the tag. 159 */ 160void 161findtag(char *tag) 162{ 163 int type = gettagtype(); 164 enum tag_result result; 165 166 if (type == T_CTAGS) 167 result = findctag(tag); 168 else 169 result = findgtag(tag, type); 170 switch (result) { 171 case TAG_FOUND: 172 case TAG_INTR: 173 break; 174 case TAG_NOFILE: 175 error("No tags file", NULL_PARG); 176 break; 177 case TAG_NOTAG: 178 error("No such tag in tags file", NULL_PARG); 179 break; 180 case TAG_NOTYPE: 181 error("unknown tag type", NULL_PARG); 182 break; 183 } 184} 185 186/* 187 * Search for a tag. 188 */ 189off_t 190tagsearch(void) 191{ 192 if (curtag == NULL) 193 return (-1); /* No gtags loaded! */ 194 if (curtag->tag_linenum != 0) 195 return (gtagsearch()); 196 else 197 return (ctagsearch()); 198} 199 200/* 201 * Go to the next tag. 202 */ 203char * 204nexttag(int n) 205{ 206 char *tagfile = NULL; 207 208 while (n-- > 0) 209 tagfile = nextgtag(); 210 return (tagfile); 211} 212 213/* 214 * Go to the previous tag. 215 */ 216char * 217prevtag(int n) 218{ 219 char *tagfile = NULL; 220 221 while (n-- > 0) 222 tagfile = prevgtag(); 223 return (tagfile); 224} 225 226/* 227 * Return the total number of tags. 228 */ 229int 230ntags(void) 231{ 232 return (total); 233} 234 235/* 236 * Return the sequence number of current tag. 237 */ 238int 239curr_tag(void) 240{ 241 return (curseq); 242} 243 244/* 245 * ctags 246 */ 247 248/* 249 * Find tags in the "tags" file. 250 * Sets curtag to the first tag entry. 251 */ 252static enum tag_result 253findctag(char *tag) 254{ 255 char *p; 256 FILE *f; 257 int taglen; 258 LINENUM taglinenum; 259 char *tagfile; 260 char *tagpattern; 261 int tagendline; 262 int search_char; 263 int err; 264 char tline[TAGLINE_SIZE]; 265 struct tag *tp; 266 267 p = shell_unquote(tags); 268 f = fopen(p, "r"); 269 free(p); 270 if (f == NULL) 271 return (TAG_NOFILE); 272 273 cleantags(); 274 total = 0; 275 taglen = strlen(tag); 276 277 /* 278 * Search the tags file for the desired tag. 279 */ 280 while (fgets(tline, sizeof (tline), f) != NULL) { 281 if (tline[0] == '!') 282 /* Skip header of extended format. */ 283 continue; 284 if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen])) 285 continue; 286 287 /* 288 * Found it. 289 * The line contains the tag, the filename and the 290 * location in the file, separated by white space. 291 * The location is either a decimal line number, 292 * or a search pattern surrounded by a pair of delimiters. 293 * Parse the line and extract these parts. 294 */ 295 tagpattern = NULL; 296 297 /* 298 * Skip over the whitespace after the tag name. 299 */ 300 p = skipsp(tline+taglen); 301 if (*p == '\0') 302 /* File name is missing! */ 303 continue; 304 305 /* 306 * Save the file name. 307 * Skip over the whitespace after the file name. 308 */ 309 tagfile = p; 310 while (!WHITESP(*p) && *p != '\0') 311 p++; 312 *p++ = '\0'; 313 p = skipsp(p); 314 if (*p == '\0') 315 /* Pattern is missing! */ 316 continue; 317 318 /* 319 * First see if it is a line number. 320 */ 321 tagendline = 0; 322 taglinenum = getnum(&p, 0, &err); 323 if (err) { 324 /* 325 * No, it must be a pattern. 326 * Delete the initial "^" (if present) and 327 * the final "$" from the pattern. 328 * Delete any backslash in the pattern. 329 */ 330 taglinenum = 0; 331 search_char = *p++; 332 if (*p == '^') 333 p++; 334 tagpattern = p; 335 while (*p != search_char && *p != '\0') { 336 if (*p == '\\') 337 p++; 338 p++; 339 } 340 tagendline = (p[-1] == '$'); 341 if (tagendline) 342 p--; 343 *p = '\0'; 344 } 345 tp = maketagent(tagfile, taglinenum, tagpattern, tagendline); 346 TAG_INS(tp); 347 total++; 348 } 349 fclose(f); 350 if (total == 0) 351 return (TAG_NOTAG); 352 curtag = taglist.tl_first; 353 curseq = 1; 354 return (TAG_FOUND); 355} 356 357/* 358 * Edit current tagged file. 359 */ 360int 361edit_tagfile(void) 362{ 363 if (curtag == NULL) 364 return (1); 365 return (edit(curtag->tag_file)); 366} 367 368/* 369 * Search for a tag. 370 * This is a stripped-down version of search(). 371 * We don't use search() for several reasons: 372 * - We don't want to blow away any search string we may have saved. 373 * - The various regular-expression functions (from different systems: 374 * regcmp vs. re_comp) behave differently in the presence of 375 * parentheses (which are almost always found in a tag). 376 */ 377static off_t 378ctagsearch(void) 379{ 380 off_t pos, linepos; 381 LINENUM linenum; 382 int len; 383 char *line; 384 385 pos = ch_zero(); 386 linenum = find_linenum(pos); 387 388 for (;;) { 389 /* 390 * Get lines until we find a matching one or 391 * until we hit end-of-file. 392 */ 393 if (ABORT_SIGS()) 394 return (-1); 395 396 /* 397 * Read the next line, and save the 398 * starting position of that line in linepos. 399 */ 400 linepos = pos; 401 pos = forw_raw_line(pos, &line, (int *)NULL); 402 if (linenum != 0) 403 linenum++; 404 405 if (pos == -1) { 406 /* 407 * We hit EOF without a match. 408 */ 409 error("Tag not found", NULL_PARG); 410 return (-1); 411 } 412 413 /* 414 * If we're using line numbers, we might as well 415 * remember the information we have now (the position 416 * and line number of the current line). 417 */ 418 if (linenums) 419 add_lnum(linenum, pos); 420 421 /* 422 * Test the line to see if we have a match. 423 * Use strncmp because the pattern may be 424 * truncated (in the tags file) if it is too long. 425 * If tagendline is set, make sure we match all 426 * the way to end of line (no extra chars after the match). 427 */ 428 len = strlen(curtag->tag_pattern); 429 if (strncmp(curtag->tag_pattern, line, len) == 0 && 430 (!curtag->tag_endline || line[len] == '\0' || 431 line[len] == '\r')) { 432 curtag->tag_linenum = find_linenum(linepos); 433 break; 434 } 435 } 436 437 return (linepos); 438} 439 440/* 441 * gtags 442 */ 443 444/* 445 * Find tags in the GLOBAL's tag file. 446 * The findgtag() will try and load information about the requested tag. 447 * It does this by calling "global -x tag" and storing the parsed output 448 * for future use by gtagsearch(). 449 * Sets curtag to the first tag entry. 450 */ 451static enum tag_result 452findgtag(char *tag, int type) 453{ 454 char buf[256]; 455 FILE *fp; 456 struct tag *tp; 457 458 if (type != T_CTAGS_X && tag == NULL) 459 return (TAG_NOFILE); 460 461 cleantags(); 462 total = 0; 463 464 /* 465 * If type == T_CTAGS_X then read ctags's -x format from stdin 466 * else execute global(1) and read from it. 467 */ 468 if (type == T_CTAGS_X) { 469 fp = stdin; 470 /* Set tag default because we cannot read stdin again. */ 471 tags = "tags"; 472 } else { 473 char *command; 474 char *flag; 475 char *qtag; 476 char *cmd = lgetenv("LESSGLOBALTAGS"); 477 478 if (cmd == NULL || *cmd == '\0') 479 return (TAG_NOFILE); 480 /* Get suitable flag value for global(1). */ 481 switch (type) { 482 case T_GTAGS: 483 flag = ""; 484 break; 485 case T_GRTAGS: 486 flag = "r"; 487 break; 488 case T_GSYMS: 489 flag = "s"; 490 break; 491 case T_GPATH: 492 flag = "P"; 493 break; 494 default: 495 return (TAG_NOTYPE); 496 } 497 498 /* Get our data from global(1). */ 499 qtag = shell_quote(tag); 500 if (qtag == NULL) 501 qtag = tag; 502 command = easprintf("%s -x%s %s", cmd, flag, qtag); 503 if (qtag != tag) 504 free(qtag); 505 fp = popen(command, "r"); 506 free(command); 507 } 508 if (fp != NULL) { 509 while (fgets(buf, sizeof (buf), fp)) { 510 char *name, *file, *line; 511 int len; 512 513 if (sigs) { 514 if (fp != stdin) 515 pclose(fp); 516 return (TAG_INTR); 517 } 518 len = strlen(buf); 519 if (len > 0 && buf[len-1] == '\n') { 520 buf[len-1] = '\0'; 521 } else { 522 int c; 523 do { 524 c = fgetc(fp); 525 } while (c != '\n' && c != EOF); 526 } 527 528 if (getentry(buf, &name, &file, &line)) { 529 /* 530 * Couldn't parse this line for some reason. 531 * We'll just pretend it never happened. 532 */ 533 break; 534 } 535 536 /* Make new entry and add to list. */ 537 tp = maketagent(file, (LINENUM) atoi(line), NULL, 0); 538 TAG_INS(tp); 539 total++; 540 } 541 if (fp != stdin) { 542 if (pclose(fp)) { 543 curtag = NULL; 544 total = curseq = 0; 545 return (TAG_NOFILE); 546 } 547 } 548 } 549 550 /* Check to see if we found anything. */ 551 tp = taglist.tl_first; 552 if (tp == TAG_END) 553 return (TAG_NOTAG); 554 curtag = tp; 555 curseq = 1; 556 return (TAG_FOUND); 557} 558 559static int circular = 0; /* 1: circular tag structure */ 560 561/* 562 * Return the filename required for the next gtag in the queue that was setup 563 * by findgtag(). The next call to gtagsearch() will try to position at the 564 * appropriate tag. 565 */ 566static char * 567nextgtag(void) 568{ 569 struct tag *tp; 570 571 if (curtag == NULL) 572 /* No tag loaded */ 573 return (NULL); 574 575 tp = curtag->next; 576 if (tp == TAG_END) { 577 if (!circular) 578 return (NULL); 579 /* Wrapped around to the head of the queue */ 580 curtag = taglist.tl_first; 581 curseq = 1; 582 } else { 583 curtag = tp; 584 curseq++; 585 } 586 return (curtag->tag_file); 587} 588 589/* 590 * Return the filename required for the previous gtag in the queue that was 591 * setup by findgtat(). The next call to gtagsearch() will try to position 592 * at the appropriate tag. 593 */ 594static char * 595prevgtag(void) 596{ 597 struct tag *tp; 598 599 if (curtag == NULL) 600 /* No tag loaded */ 601 return (NULL); 602 603 tp = curtag->prev; 604 if (tp == TAG_END) { 605 if (!circular) 606 return (NULL); 607 /* Wrapped around to the tail of the queue */ 608 curtag = taglist.tl_last; 609 curseq = total; 610 } else { 611 curtag = tp; 612 curseq--; 613 } 614 return (curtag->tag_file); 615} 616 617/* 618 * Position the current file at at what is hopefully the tag that was chosen 619 * using either findtag() or one of nextgtag() and prevgtag(). Returns -1 620 * if it was unable to position at the tag, 0 if successful. 621 */ 622static off_t 623gtagsearch(void) 624{ 625 if (curtag == NULL) 626 return (-1); /* No gtags loaded! */ 627 return (find_pos(curtag->tag_linenum)); 628} 629 630/* 631 * The getentry() parses both standard and extended ctags -x format. 632 * 633 * [standard format] 634 * <tag> <lineno> <file> <image> 635 * +------------------------------------------------ 636 * |main 30 main.c main(argc, argv) 637 * |func 21 subr.c func(arg) 638 * 639 * The following commands write this format. 640 * o Traditinal Ctags with -x option 641 * o Global with -x option 642 * See <http://www.gnu.org/software/global/global.html> 643 * 644 * [extended format] 645 * <tag> <type> <lineno> <file> <image> 646 * +---------------------------------------------------------- 647 * |main function 30 main.c main(argc, argv) 648 * |func function 21 subr.c func(arg) 649 * 650 * The following commands write this format. 651 * o Exuberant Ctags with -x option 652 * See <http://ctags.sourceforge.net> 653 * 654 * Returns 0 on success, -1 on error. 655 * The tag, file, and line will each be NUL-terminated pointers 656 * into buf. 657 */ 658static int 659getentry(char *buf, char **tag, char **file, char **line) 660{ 661 char *p = buf; 662 663 for (*tag = p; *p && !isspace(*p); p++) /* tag name */ 664 ; 665 if (*p == 0) 666 return (-1); 667 *p++ = 0; 668 for (; *p && isspace(*p); p++) /* (skip blanks) */ 669 ; 670 if (*p == 0) 671 return (-1); 672 /* 673 * If the second part begin with other than digit, 674 * it is assumed tag type. Skip it. 675 */ 676 if (!isdigit(*p)) { 677 for (; *p && !isspace(*p); p++) /* (skip tag type) */ 678 ; 679 for (; *p && isspace(*p); p++) /* (skip blanks) */ 680 ; 681 } 682 if (!isdigit(*p)) 683 return (-1); 684 *line = p; /* line number */ 685 for (*line = p; *p && !isspace(*p); p++) 686 ; 687 if (*p == 0) 688 return (-1); 689 *p++ = 0; 690 for (; *p && isspace(*p); p++) /* (skip blanks) */ 691 ; 692 if (*p == 0) 693 return (-1); 694 *file = p; /* file name */ 695 for (*file = p; *p && !isspace(*p); p++) 696 ; 697 if (*p == 0) 698 return (-1); 699 *p = 0; 700 701 /* value check */ 702 if (strlen(*tag) && strlen(*line) && strlen(*file) && atoi(*line) > 0) 703 return (0); 704 return (-1); 705} 706