tags.c revision 161475
1194955Strasz/* 2194955Strasz * Copyright (C) 1984-2004 Mark Nudelman 3194955Strasz * 4194955Strasz * You may distribute under the terms of either the GNU General Public 5194955Strasz * License or the Less License, as specified in the README file. 6194955Strasz * 7194955Strasz * For more information about less, or for information on how to 8194955Strasz * contact the author, see the README file. 9194955Strasz */ 10194955Strasz 11194955Strasz 12194955Strasz#include "less.h" 13194955Strasz 14194955Strasz#define WHITESP(c) ((c)==' ' || (c)=='\t') 15194955Strasz 16194955Strasz#if TAGS 17194955Strasz 18194955Straszpublic char *tags = "tags"; 19194955Strasz 20194955Straszstatic int total; 21194955Straszstatic int curseq; 22194955Strasz 23194955Straszextern int linenums; 24194955Straszextern int sigs; 25194955Strasz 26194955Straszenum tag_result { 27194955Strasz TAG_FOUND, 28194955Strasz TAG_NOFILE, 29194955Strasz TAG_NOTAG, 30194955Strasz TAG_NOTYPE, 31194955Strasz TAG_INTR 32194955Strasz}; 33194955Strasz 34194955Strasz/* 35194955Strasz * Tag type 36194955Strasz */ 37194955Straszenum { 38194955Strasz T_CTAGS, /* 'tags': standard and extended format (ctags) */ 39194955Strasz T_CTAGS_X, /* stdin: cross reference format (ctags) */ 40194955Strasz T_GTAGS, /* 'GTAGS': function defenition (global) */ 41194955Strasz T_GRTAGS, /* 'GRTAGS': function reference (global) */ 42194955Strasz T_GSYMS, /* 'GSYMS': other symbols (global) */ 43194955Strasz T_GPATH /* 'GPATH': path name (global) */ 44194955Strasz}; 45194955Strasz 46194955Straszstatic enum tag_result findctag(); 47194955Straszstatic enum tag_result findgtag(); 48194955Straszstatic char *nextgtag(); 49194955Straszstatic char *prevgtag(); 50194955Straszstatic POSITION ctagsearch(); 51194955Straszstatic POSITION gtagsearch(); 52194955Straszstatic int getentry(); 53208811Strasz 54194955Strasz/* 55194955Strasz * The list of tags generated by the last findgtag() call. 56194955Strasz * 57194955Strasz * Use either pattern or line number. 58194955Strasz * findgtag() always uses line number, so pattern is always NULL. 59194955Strasz * findctag() usually either pattern (in which case line number is 0), 60194955Strasz * or line number (in which case pattern is NULL). 61194955Strasz */ 62194955Straszstruct taglist { 63194955Strasz struct tag *tl_first; 64194955Strasz struct tag *tl_last; 65208811Strasz}; 66194955Strasz#define TAG_END ((struct tag *) &taglist) 67194955Straszstatic struct taglist taglist = { TAG_END, TAG_END }; 68194955Straszstruct tag { 69194955Strasz struct tag *next, *prev; /* List links */ 70194955Strasz char *tag_file; /* Source file containing the tag */ 71194955Strasz LINENUM tag_linenum; /* Appropriate line number in source file */ 72194955Strasz char *tag_pattern; /* Pattern used to find the tag */ 73194955Strasz char tag_endline; /* True if the pattern includes '$' */ 74194955Strasz}; 75194955Straszstatic struct tag *curtag; 76194955Strasz 77194955Strasz#define TAG_INS(tp) \ 78194955Strasz (tp)->next = taglist.tl_first; \ 79194955Strasz (tp)->prev = TAG_END; \ 80194955Strasz taglist.tl_first->prev = (tp); \ 81194955Strasz taglist.tl_first = (tp); 82194955Strasz 83194955Strasz#define TAG_RM(tp) \ 84208811Strasz (tp)->next->prev = (tp)->prev; \ 85194955Strasz (tp)->prev->next = (tp)->next; 86194955Strasz 87194955Strasz/* 88194955Strasz * Delete tag structures. 89194955Strasz */ 90194955Strasz public void 91194955Straszcleantags() 92194955Strasz{ 93194955Strasz register struct tag *tp; 94194955Strasz 95194955Strasz /* 96194955Strasz * Delete any existing tag list. 97194955Strasz * {{ Ideally, we wouldn't do this until after we know that we 98194955Strasz * can load some other tag information. }} 99194955Strasz */ 100194955Strasz while ((tp = taglist.tl_first) != TAG_END) 101194955Strasz { 102194955Strasz TAG_RM(tp); 103194955Strasz free(tp); 104194955Strasz } 105194955Strasz curtag = NULL; 106194955Strasz total = curseq = 0; 107194955Strasz} 108194955Strasz 109194955Strasz/* 110194955Strasz * Create a new tag entry. 111194955Strasz */ 112194955Strasz static struct tag * 113194955Straszmaketagent(name, file, linenum, pattern, endline) 114194955Strasz char *name; 115194955Strasz char *file; 116194955Strasz LINENUM linenum; 117194955Strasz char *pattern; 118194955Strasz int endline; 119194955Strasz{ 120194955Strasz register struct tag *tp; 121194955Strasz 122194955Strasz tp = (struct tag *) ecalloc(sizeof(struct tag), 1); 123194955Strasz tp->tag_file = (char *) ecalloc(strlen(file) + 1, sizeof(char)); 124194955Strasz strcpy(tp->tag_file, file); 125194955Strasz tp->tag_linenum = linenum; 126194955Strasz tp->tag_endline = endline; 127194955Strasz if (pattern == NULL) 128194955Strasz tp->tag_pattern = NULL; 129194955Strasz else 130194955Strasz { 131194955Strasz tp->tag_pattern = (char *) ecalloc(strlen(pattern) + 1, sizeof(char)); 132194955Strasz strcpy(tp->tag_pattern, pattern); 133194955Strasz } 134194955Strasz return (tp); 135194955Strasz} 136194955Strasz 137194955Strasz/* 138194955Strasz * Get tag mode. 139194955Strasz */ 140194955Strasz public int 141194955Straszgettagtype() 142194955Strasz{ 143194955Strasz int f; 144208811Strasz 145194955Strasz if (strcmp(tags, "GTAGS") == 0) 146194955Strasz return T_GTAGS; 147194955Strasz if (strcmp(tags, "GRTAGS") == 0) 148194955Strasz return T_GRTAGS; 149194955Strasz if (strcmp(tags, "GSYMS") == 0) 150194955Strasz return T_GSYMS; 151194955Strasz if (strcmp(tags, "GPATH") == 0) 152194955Strasz return T_GPATH; 153194955Strasz if (strcmp(tags, "-") == 0) 154194955Strasz return T_CTAGS_X; 155194955Strasz f = open(tags, OPEN_READ); 156194955Strasz if (f >= 0) 157194955Strasz { 158208811Strasz close(f); 159194955Strasz return T_CTAGS; 160194955Strasz } 161194955Strasz return T_GTAGS; 162194955Strasz} 163194955Strasz 164194955Strasz/* 165194955Strasz * Find tags in tag file. 166194955Strasz * Find a tag in the "tags" file. 167194955Strasz * Sets "tag_file" to the name of the file containing the tag, 168194955Strasz * and "tagpattern" to the search pattern which should be used 169194955Strasz * to find the tag. 170205796Strasz */ 171194955Strasz public void 172194955Straszfindtag(tag) 173194955Strasz register char *tag; 174194955Strasz{ 175194955Strasz int type = gettagtype(); 176194955Strasz enum tag_result result; 177194955Strasz 178194955Strasz if (type == T_CTAGS) 179194955Strasz result = findctag(tag); 180194955Strasz else 181194955Strasz result = findgtag(tag, type); 182194955Strasz switch (result) 183194955Strasz { 184194955Strasz case TAG_FOUND: 185194955Strasz case TAG_INTR: 186194955Strasz break; 187194955Strasz case TAG_NOFILE: 188194955Strasz error("No tags file", NULL_PARG); 189194955Strasz break; 190194955Strasz case TAG_NOTAG: 191205796Strasz error("No such tag in tags file", NULL_PARG); 192205796Strasz break; 193205796Strasz case TAG_NOTYPE: 194194955Strasz error("unknown tag type", NULL_PARG); 195194955Strasz break; 196194955Strasz } 197194955Strasz} 198194955Strasz 199194955Strasz/* 200194955Strasz * Search for a tag. 201194955Strasz */ 202194955Strasz public POSITION 203194955Strasztagsearch() 204194955Strasz{ 205194955Strasz if (curtag == NULL) 206194955Strasz return (NULL_POSITION); /* No gtags loaded! */ 207194955Strasz if (curtag->tag_linenum != 0) 208194955Strasz return gtagsearch(); 209194955Strasz else 210194955Strasz return ctagsearch(); 211194955Strasz} 212194955Strasz 213194955Strasz/* 214194955Strasz * Go to the next tag. 215194955Strasz */ 216194955Strasz public char * 217194955Strasznexttag(n) 218194955Strasz int n; 219194955Strasz{ 220194955Strasz char *tagfile = (char *) NULL; 221194955Strasz 222194955Strasz while (n-- > 0) 223194955Strasz tagfile = nextgtag(); 224194955Strasz return tagfile; 225194955Strasz} 226194955Strasz 227194955Strasz/* 228194955Strasz * Go to the previous tag. 229194955Strasz */ 230194955Strasz public char * 231194955Straszprevtag(n) 232194955Strasz int n; 233194955Strasz{ 234194955Strasz char *tagfile = (char *) NULL; 235194955Strasz 236194955Strasz while (n-- > 0) 237194955Strasz tagfile = prevgtag(); 238194955Strasz return tagfile; 239194955Strasz} 240194955Strasz 241194955Strasz/* 242194955Strasz * Return the total number of tags. 243194955Strasz */ 244194955Strasz public int 245194955Straszntags() 246194955Strasz{ 247194955Strasz return total; 248194955Strasz} 249208786Strasz 250194955Strasz/* 251194955Strasz * Return the sequence number of current tag. 252194955Strasz */ 253194955Strasz public int 254194955Straszcurr_tag() 255194955Strasz{ 256194955Strasz return curseq; 257194955Strasz} 258194955Strasz 259194955Strasz/***************************************************************************** 260194955Strasz * ctags 261194955Strasz */ 262194955Strasz 263194955Strasz/* 264194955Strasz * 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