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