/* * Copyright (C) 1984-2000 Mark Nudelman * * You may distribute under the terms of either the GNU General Public * License or the Less License, as specified in the README file. * * For more information about less, or for information on how to * contact the author, see the README file. */ #include "less.h" #define WHITESP(c) ((c)==' ' || (c)=='\t') #if TAGS #include "queue.h" char *tags = "tags"; static int total; static int curseq; extern int linenums; extern int sigs; extern int jump_sline; /* * tag type */ enum { T_CTAGS, /* 'tags': standard and extended format (ctags) */ T_CTAGS_X, /* stdin: cross reference format (ctags) */ T_GTAGS, /* 'GTAGS': function defenition (global) */ T_GRTAGS, /* 'GRTAGS': function reference (global) */ T_GSYMS, /* 'GSYMS': other symbols (global) */ T_GPATH /* 'GPATH': path name (global) */ }; static void findctag(), findgtag(); static char *nextgtag(), *prevgtag(); static POSITION ctagsearch(), gtagsearch(); static int getentry(); /* * The queue of tags generated by the last findgtag() call. * * Use either pattern or line. * findgtag() use line, so pattern is always NULL. * findctag() useally use pattern, then line is 0 but sometimes use * line (old style tags format) instead of pattern. In such case, * pattern is NULL. */ static CIRCLEQ_HEAD(tag_q, tag) tag_q; struct tag { CIRCLEQ_ENTRY(tag) ptrs; char *tagfile; /* source file containing the tag */ int taglinenum; /* appropriate line number of source file */ char *tagpattern; /* pattern which should be used to find the tag */ char tagendline; /* 1: the pattern include '$' */ }; static struct tag *curtag; /* * Cleanup loading tag structure. */ public void cleantags() { struct tag *tag_p, *tag_save_p; /* Clear any existing tag circle queue */ /* XXX Ideally, we wouldn't do this until after we know that we * can load some other tag information. */ curtag = NULL; tag_p = CIRCLEQ_FIRST(&tag_q); if (tag_p) { while (tag_p != (void *)&tag_q) { tag_save_p = CIRCLEQ_NEXT(tag_p, ptrs); free(tag_p); tag_p = tag_save_p; } } CIRCLEQ_INIT(&tag_q); total = curseq = 0; } /* * Make new tag entry. * * return 0 when result is OK, -1 when memory allocation error. */ static struct tag * maketagent(name, file, line, pattern, end) char *name; char *file; int line; char *pattern; int end; { struct tag *tag_p; tag_p = calloc(sizeof(struct tag), 1); if (!tag_p) goto err; tag_p->tagfile = malloc(strlen(file) + 1); if (!tag_p->tagfile) goto err; strcpy(tag_p->tagfile, file); if (pattern) { tag_p->tagpattern = malloc(strlen(pattern) + 1); if (!tag_p->tagpattern) goto err; strcpy(tag_p->tagpattern, pattern); tag_p->tagendline = end; } else { tag_p->taglinenum = line; } return tag_p; err: if (tag_p) { /* These pointers are cleaned by default. */ if (tag_p->tagfile) free(tag_p->tagfile); if (tag_p->tagpattern) free(tag_p->tagpattern); free(tag_p); } return NULL; } /* * Get tag mode. * * Tag file name tell us the mode. */ #include public int gettagtype() { int type; struct stat sb; if (!strcmp(tags, "GTAGS")) type = T_GTAGS; else if (!strcmp(tags, "GRTAGS")) type = T_GRTAGS; else if (!strcmp(tags, "GSYMS")) type = T_GSYMS; else if (!strcmp(tags, "GPATH")) type = T_GPATH; else if (!strcmp(tags, "-")) type = T_CTAGS_X; else if (stat(tags, &sb) == 0) type = T_CTAGS; else type = T_GTAGS; return type; } /* * Find tags in tag file. */ public void findtag(tag) register char *tag; { int type = gettagtype(); switch (type) { case T_CTAGS: findctag(tag); break; case T_GTAGS: case T_GRTAGS: case T_GSYMS: case T_GPATH: case T_CTAGS_X: findgtag(tag, type); break; } } /* * Search for a tag. */ public POSITION tagsearch() { if (!curtag) return (NULL_POSITION); /* No gtags loaded! */ if (curtag->taglinenum) return gtagsearch(); else return ctagsearch(); } /* * Go to the next tag. * * return file name which should be read. */ public char * nexttag(number) int number; { char *tagfile; while (number--) tagfile = nextgtag(); return tagfile; } /* * Go to the previous tag. * * return file name which should be read. */ public char * prevtag(number) int number; { char *tagfile; while (number--) tagfile = prevgtag(); return tagfile; } /* * return the total number of tag. */ public int ntags() { return total; } /* * return the sequence number of current tag. */ public int curr_tag() { return curseq; } /******************************************************************************* * * ctags * */ /* * Find tags in the "tags" file. * Sets curtag to the first tag entry. */ static void findctag(tag) register char *tag; { char *p; char *q; register FILE *f; register int taglen; register int taglinenum; char *tagfile; char *tagpattern; int tagendline; int search_char; int err; char tline[TAGLINE_SIZE]; struct tag *tag_p; p = unquote_file(tags); f = fopen(p, "r"); free(p); if (f == NULL) { error("No tags file", NULL_PARG); return; } /* Cleanup loading tag structure if any. */ cleantags(); total = 0; taglen = strlen(tag); /* * Search the tags file for the desired tag. */ while (fgets(tline, sizeof(tline), f) != NULL) { /* * It's header of extended format. Skip. */ if (tline[0] == '!') continue; if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen])) continue; /* * Found it. * The line contains the tag, the filename and the * location in the file, separated by white space. * The location is either a decimal line number, * or a search pattern surrounded by a pair of delimiters. * Parse the line and extract these parts. */ tagfile = NULL; taglinenum = 0; tagpattern = NULL; /* * Skip over the whitespace after the tag name. */ p = skipsp(tline+taglen); if (*p == '\0') /* File name is missing! */ continue; /* * Save the file name. * Skip over the whitespace after the file name. */ tagfile = p; while (!WHITESP(*p) && *p != '\0') p++; *p++ = '\0'; p = skipsp(p); if (*p == '\0') /* Pattern is missing! */ continue; /* * First see if it is a line number. */ taglinenum = getnum(&p, 0, &err); if (err) { /* * No, it must be a pattern. * Delete the initial "^" (if present) and * the final "$" from the pattern. * Delete any backslash in the pattern. */ taglinenum = 0; search_char = *p++; if (*p == '^') p++; tagpattern = p; while (*p != search_char && *p != '\0') { if (*p == '\\') p++; p++; } tagendline = (p[-1] == '$'); if (tagendline) p--; *p = '\0'; } /* Make new entry and add to queue */ tag_p = maketagent(tag, tagfile, taglinenum, tagpattern, tagendline); if (tag_p) { CIRCLEQ_INSERT_TAIL(&tag_q, tag_p, ptrs); } else { error("malloc() failed", NULL_PARG); if (f != stdin) pclose(f); return; } total++; } fclose(f); if (total == 0) { error("No such tag in tags file", NULL_PARG); return; } curtag = CIRCLEQ_FIRST(&tag_q); curseq = 1; } /* * Edit current tagged file. */ public int edit_tagfile() { int r; if (curtag == NULL) return (1); return edit(curtag->tagfile); } /* * Search for a tag. * This is a stripped-down version of search(). * We don't use search() for several reasons: * - We don't want to blow away any search string we may have saved. * - The various regular-expression functions (from different systems: * regcmp vs. re_comp) behave differently in the presence of * parentheses (which are almost always found in a tag). */ static POSITION ctagsearch() { POSITION pos, linepos; int linenum; int len; char *line; pos = ch_zero(); linenum = find_linenum(pos); for (;;) { /* * Get lines until we find a matching one or * until we hit end-of-file. */ if (ABORT_SIGS()) return (NULL_POSITION); /* * Read the next line, and save the * starting position of that line in linepos. */ linepos = pos; pos = forw_raw_line(pos, &line); if (linenum != 0) linenum++; if (pos == NULL_POSITION) { /* * We hit EOF without a match. */ error("Tag not found", NULL_PARG); return (NULL_POSITION); } /* * If we're using line numbers, we might as well * remember the information we have now (the position * and line number of the current line). */ if (linenums) add_lnum(linenum, pos); /* * Test the line to see if we have a match. * Use strncmp because the pattern may be * truncated (in the tags file) if it is too long. * If tagendline is set, make sure we match all * the way to end of line (no extra chars after the match). */ len = strlen(curtag->tagpattern); if (strncmp(curtag->tagpattern, line, len) == 0 && (!curtag->tagendline || line[len] == '\0' || line[len] == '\r')) { /* * Set line number to tag entry while forwarding. * It can be used while back fowarding. */ curtag->taglinenum = find_linenum(linepos); jump_back(curtag->taglinenum); break; } } return (linepos); } /******************************************************************************* * * gtags * */ /* * Find tags in the GLOBAL's tag file. * The findgtag() will try and load information about the requested tag. * It does this by calling "global -x tag" and storing the parsed output * for future use by gtagsearch(). * Sets curtag to the first tag entry. */ static void findgtag(tag, type) char *tag; /* tag to load */ int type; /* tags type */ { char command[512]; char *flag; char buf[256]; int status; FILE *fp; struct tag *tag_p; if (type != T_CTAGS_X && !tag) { return; } /* Cleanup loading tag structure if any. */ cleantags(); total = 0; /* * If type == T_CTAGS_X then read ctags's -x format from stdin * else execute global(1) and read from it; */ if (type == T_CTAGS_X) { fp = stdin; /* * Set tag default because we cannot read stdin again. */ tags = "tags"; } else { /* Get suitable flag value for global(1). */ switch (type) { case T_GTAGS: flag = "" ; break; case T_GRTAGS: flag = "r"; break; case T_GSYMS: flag = "s"; break; case T_GPATH: flag = "P"; break; default: error("unknown tag type", NULL_PARG); } /* Get our data from global(1) */ #ifdef HAVE_SNPRINTF snprintf(command, sizeof(command), #else sprintf(command, #endif /* HAVE_SNPRINTF */ "global -x%s '%s' 2>/dev/null", flag, tag); fp = popen(command, "r"); } if (fp) { while (fgets(buf, sizeof(buf), fp)) { char *name, *file, *line; if (sigs) { if (fp != stdin) pclose(fp); return; } /* chop(buf) */ if (buf[strlen(buf) - 1] == '\n') buf[strlen(buf) - 1] = 0; else while (fgetc(fp) != '\n') ; if (getentry(buf, &name, &file, &line)) { /* * Couldn't parse this line for some reason. * We'll just pretend it never happened. */ break; } /* Make new entry and add to queue */ tag_p = maketagent(name, file, atoi(line), NULL, 0); if (tag_p) { CIRCLEQ_INSERT_TAIL(&tag_q, tag_p, ptrs); } else { error("malloc() failed", NULL_PARG); if (fp != stdin) pclose(fp); return; } total++; } if (fp != stdin) { status = pclose(fp); if (status) { error("No tags file", NULL_PARG); curtag = NULL; total = curseq = 0; return; } } } /* Check to see if we found anything. */ if (CIRCLEQ_EMPTY(&tag_q)) return; /* Nope! */ curtag = CIRCLEQ_FIRST(&tag_q); curseq = 1; } static int circular = 0; /* 1: circlular tag structure */ /* * Return the filename required for the next gtag in the queue that was setup * by findgtag(). The next call to gtagsearch() will try to position at the * appropriate tag. */ static char * nextgtag() { if (!curtag) /* No tag loaded */ return NULL; /* * It is the last tag entry. */ if (CIRCLEQ_NEXT(curtag, ptrs) == (void *)&tag_q) { if (!circular) return NULL; /* Wrapped around to the head of the queue */ curtag = CIRCLEQ_FIRST(&tag_q); curseq = 1; } else { curtag = CIRCLEQ_NEXT(curtag, ptrs); curseq++; } return curtag->tagfile; } /* * Return the filename required for the previous gtag in the queue that was * setup by findgtat(). The next call to gtagsearch() will try to position * at the appropriate tag. */ static char * prevgtag() { if (!curtag) /* No tag loaded */ return NULL; /* * It is the first tag entry. */ if (CIRCLEQ_PREV(curtag, ptrs) == (void *)&tag_q) { if (!circular) return NULL; /* Wrapped around to the tail of the queue */ curtag = CIRCLEQ_LAST(&tag_q); curseq = total; } else { curtag = CIRCLEQ_PREV(curtag, ptrs); curseq--; } return curtag->tagfile; } /* * Position the current file at at what is hopefully the tag that was chosen * using either findtag() or one of nextgtag() and prevgtag(). Returns -1 * if it was unable to position at the tag, 0 if succesful. */ static POSITION gtagsearch() { if (!curtag) return (NULL_POSITION); /* No gtags loaded! */ jump_back(curtag->taglinenum); /* * XXX We'll assume we were successful --- jump_back() will call error() * if it fails, so the user will receive some kind of notification. * Eventually, jump_back() should do its work silently and let us * perform the error notification, eventually allowing our caller * (presumably tagsearch()) to go error("Could not locate tag."); */ return (find_pos(curtag->taglinenum)); } /* * The getentry() parses both standard and extended ctags -x format. * * [standard format] * * +------------------------------------------------ * |main 30 main.c main(argc, argv) * |func 21 subr.c func(arg) * * The following commands write this format. * o Traditinal Ctags with -x option * o Global with -x option * See * * [extended format] * * +---------------------------------------------------------- * |main function 30 main.c main(argc, argv) * |func function 21 subr.c func(arg) * * The following commands write this format. * o Exuberant Ctags with -x option * See * * Returns 0 on success, -1 on error. * The tag, file, and line will each be NUL-terminated pointers * into buf. */ static int getentry(buf, tag, file, line) char *buf; /* standard or extended ctags -x format data */ char **tag; /* name of the tag we actually found */ char **file; /* file in which to find this tag */ char **line; /* line number of file where this tag is found */ { char *p = buf; for (*tag = p; *p && !isspace(*p); p++) /* tag name */ ; if (*p == 0) goto err; *p++ = 0; for (; *p && isspace(*p); p++) /* (skip blanks) */ ; if (*p == 0) goto err; /* * If the second part begin with other than digit, * it is assumed tag type. Skip it. */ if (!isdigit(*p)) { for (; *p && !isspace(*p); p++) /* (skip tag type) */ ; for (; *p && isspace(*p); p++) /* (skip blanks) */ ; } if (!isdigit(*p)) goto err; *line = p; /* line no */ for (*line = p; *p && !isspace(*p); p++) ; if (*p == 0) goto err; *p++ = 0; for (; *p && isspace(*p); p++) /* (skip blanks) */ ; if (*p == 0) goto err; *file = p; /* file name */ for (*file = p; *p && !isspace(*p); p++) ; if (*p == 0) goto err; *p = 0; /* value check */ if (strlen(*tag) && strlen(*line) && strlen(*file) && atoi(*line) > 0) return (0); /* OK */ err: return (-1); /* ERROR */ } #endif