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