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