version 1.1.1.1, 1996/09/21 05:39:43 |
version 1.1.1.2, 2003/04/13 18:21:21 |
|
|
/* |
/* |
* Copyright (c) 1984,1985,1989,1994,1995 Mark Nudelman |
* Copyright (C) 1984-2002 Mark Nudelman |
* All rights reserved. |
|
* |
* |
* Redistribution and use in source and binary forms, with or without |
* You may distribute under the terms of either the GNU General Public |
* modification, are permitted provided that the following conditions |
* License or the Less License, as specified in the README file. |
* are met: |
|
* 1. Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions and the following disclaimer. |
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
* notice in the documentation and/or other materials provided with |
|
* the distribution. |
|
* |
* |
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY |
* For more information about less, or for information on how to |
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
* contact the author, see the README file. |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE |
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT |
|
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
|
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
*/ |
*/ |
|
|
|
|
|
|
|
|
#if TAGS |
#if TAGS |
|
|
public char *tagfile; |
|
public char *tags = "tags"; |
public char *tags = "tags"; |
|
|
static char *tagpattern; |
static int total; |
static int taglinenum; |
static int curseq; |
|
|
extern int linenums; |
extern int linenums; |
extern int sigs; |
extern int sigs; |
extern int jump_sline; |
|
|
|
|
enum tag_result { |
|
TAG_FOUND, |
|
TAG_NOFILE, |
|
TAG_NOTAG, |
|
TAG_NOTYPE, |
|
TAG_INTR |
|
}; |
|
|
/* |
/* |
|
* 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 enum tag_result findctag(); |
|
static enum tag_result findgtag(); |
|
static char *nextgtag(); |
|
static char *prevgtag(); |
|
static POSITION ctagsearch(); |
|
static POSITION gtagsearch(); |
|
static int getentry(); |
|
|
|
/* |
|
* The list of tags generated by the last findgtag() call. |
|
* |
|
* Use either pattern or line number. |
|
* findgtag() always uses line number, so pattern is always NULL. |
|
* findctag() usually either pattern (in which case line number is 0), |
|
* or line number (in which case pattern is NULL). |
|
*/ |
|
struct taglist { |
|
struct tag *tl_first; |
|
struct tag *tl_last; |
|
}; |
|
#define TAG_END ((struct tag *) &taglist) |
|
static struct taglist taglist = { TAG_END, TAG_END }; |
|
struct tag { |
|
struct tag *next, *prev; /* List links */ |
|
char *tag_file; /* Source file containing the tag */ |
|
LINENUM tag_linenum; /* Appropriate line number in source file */ |
|
char *tag_pattern; /* Pattern used to find the tag */ |
|
char tag_endline; /* True if the pattern includes '$' */ |
|
}; |
|
static struct tag *curtag; |
|
|
|
#define TAG_INS(tp) \ |
|
(tp)->next = taglist.tl_first; \ |
|
(tp)->prev = TAG_END; \ |
|
taglist.tl_first->prev = (tp); \ |
|
taglist.tl_first = (tp); |
|
|
|
#define TAG_RM(tp) \ |
|
(tp)->next->prev = (tp)->prev; \ |
|
(tp)->prev->next = (tp)->next; |
|
|
|
/* |
|
* Delete tag structures. |
|
*/ |
|
public void |
|
cleantags() |
|
{ |
|
register struct tag *tp; |
|
|
|
/* |
|
* Delete any existing tag list. |
|
* {{ Ideally, we wouldn't do this until after we know that we |
|
* can load some other tag information. }} |
|
*/ |
|
while ((tp = taglist.tl_first) != TAG_END) |
|
{ |
|
TAG_RM(tp); |
|
free(tp); |
|
} |
|
curtag = NULL; |
|
total = curseq = 0; |
|
} |
|
|
|
/* |
|
* Create a new tag entry. |
|
*/ |
|
static struct tag * |
|
maketagent(name, file, linenum, pattern, endline) |
|
char *name; |
|
char *file; |
|
LINENUM linenum; |
|
char *pattern; |
|
int endline; |
|
{ |
|
register struct tag *tp; |
|
|
|
tp = (struct tag *) ecalloc(sizeof(struct tag), 1); |
|
tp->tag_file = (char *) ecalloc(strlen(file) + 1, sizeof(char)); |
|
strcpy(tp->tag_file, file); |
|
tp->tag_linenum = linenum; |
|
tp->tag_endline = endline; |
|
if (pattern == NULL) |
|
tp->tag_pattern = NULL; |
|
else |
|
{ |
|
tp->tag_pattern = (char *) ecalloc(strlen(pattern) + 1, sizeof(char)); |
|
strcpy(tp->tag_pattern, pattern); |
|
} |
|
return (tp); |
|
} |
|
|
|
/* |
|
* Get tag mode. |
|
*/ |
|
public int |
|
gettagtype() |
|
{ |
|
int f; |
|
|
|
if (strcmp(tags, "GTAGS") == 0) |
|
return T_GTAGS; |
|
if (strcmp(tags, "GRTAGS") == 0) |
|
return T_GRTAGS; |
|
if (strcmp(tags, "GSYMS") == 0) |
|
return T_GSYMS; |
|
if (strcmp(tags, "GPATH") == 0) |
|
return T_GPATH; |
|
if (strcmp(tags, "-") == 0) |
|
return T_CTAGS_X; |
|
f = open(tags, OPEN_READ); |
|
if (f >= 0) |
|
{ |
|
close(f); |
|
return T_CTAGS; |
|
} |
|
return T_GTAGS; |
|
} |
|
|
|
/* |
|
* Find tags in tag file. |
* Find a tag in the "tags" file. |
* Find a tag in the "tags" file. |
* Sets "tagfile" to the name of the file containing the tag, |
* Sets "tag_file" to the name of the file containing the tag, |
* and "tagpattern" to the search pattern which should be used |
* and "tagpattern" to the search pattern which should be used |
* to find the tag. |
* to find the tag. |
*/ |
*/ |
|
|
findtag(tag) |
findtag(tag) |
register char *tag; |
register char *tag; |
{ |
{ |
|
int type = gettagtype(); |
|
enum tag_result result; |
|
|
|
if (type == T_CTAGS) |
|
result = findctag(tag); |
|
else |
|
result = findgtag(tag, type); |
|
switch (result) |
|
{ |
|
case TAG_FOUND: |
|
case TAG_INTR: |
|
break; |
|
case TAG_NOFILE: |
|
error("No tags file", NULL_PARG); |
|
break; |
|
case TAG_NOTAG: |
|
error("No such tag in tags file", NULL_PARG); |
|
break; |
|
case TAG_NOTYPE: |
|
error("unknown tag type", NULL_PARG); |
|
break; |
|
} |
|
} |
|
|
|
/* |
|
* Search for a tag. |
|
*/ |
|
public POSITION |
|
tagsearch() |
|
{ |
|
if (curtag == NULL) |
|
return (NULL_POSITION); /* No gtags loaded! */ |
|
if (curtag->tag_linenum != 0) |
|
return gtagsearch(); |
|
else |
|
return ctagsearch(); |
|
} |
|
|
|
/* |
|
* Go to the next tag. |
|
*/ |
|
public char * |
|
nexttag(n) |
|
int n; |
|
{ |
|
char *tagfile = (char *) NULL; |
|
|
|
while (n-- > 0) |
|
tagfile = nextgtag(); |
|
return tagfile; |
|
} |
|
|
|
/* |
|
* Go to the previous tag. |
|
*/ |
|
public char * |
|
prevtag(n) |
|
int n; |
|
{ |
|
char *tagfile = (char *) NULL; |
|
|
|
while (n-- > 0) |
|
tagfile = prevgtag(); |
|
return tagfile; |
|
} |
|
|
|
/* |
|
* Return the total number of tags. |
|
*/ |
|
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 enum tag_result |
|
findctag(tag) |
|
register char *tag; |
|
{ |
char *p; |
char *p; |
char *q; |
|
register FILE *f; |
register FILE *f; |
register int taglen; |
register int taglen; |
|
LINENUM taglinenum; |
|
char *tagfile; |
|
char *tagpattern; |
|
int tagendline; |
int search_char; |
int search_char; |
int err; |
int err; |
static char tline[200]; |
char tline[TAGLINE_SIZE]; |
|
struct tag *tp; |
|
|
if ((f = fopen(tags, "r")) == NULL) |
p = shell_unquote(tags); |
{ |
f = fopen(p, "r"); |
error("No tags file", NULL_PARG); |
free(p); |
tagfile = NULL; |
if (f == NULL) |
return; |
return TAG_NOFILE; |
} |
|
|
|
|
cleantags(); |
|
total = 0; |
taglen = strlen(tag); |
taglen = strlen(tag); |
|
|
/* |
/* |
|
|
*/ |
*/ |
while (fgets(tline, sizeof(tline), f) != NULL) |
while (fgets(tline, sizeof(tline), f) != NULL) |
{ |
{ |
|
if (tline[0] == '!') |
|
/* Skip header of extended format. */ |
|
continue; |
if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen])) |
if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen])) |
continue; |
continue; |
|
|
|
|
* or a search pattern surrounded by a pair of delimiters. |
* or a search pattern surrounded by a pair of delimiters. |
* Parse the line and extract these parts. |
* Parse the line and extract these parts. |
*/ |
*/ |
tagfile = tagpattern = NULL; |
tagpattern = NULL; |
taglinenum = 0; |
|
|
|
/* |
/* |
* Skip over the whitespace after the tag name. |
* Skip over the whitespace after the tag name. |
|
|
/* |
/* |
* First see if it is a line number. |
* First see if it is a line number. |
*/ |
*/ |
|
tagendline = 0; |
taglinenum = getnum(&p, 0, &err); |
taglinenum = getnum(&p, 0, &err); |
if (err) |
if (err) |
{ |
{ |
|
|
search_char = *p++; |
search_char = *p++; |
if (*p == '^') |
if (*p == '^') |
p++; |
p++; |
tagpattern = q = p; |
tagpattern = p; |
while (*p != search_char && *p != '\0') |
while (*p != search_char && *p != '\0') |
{ |
{ |
if (*p == '\\') |
if (*p == '\\') |
p++; |
p++; |
*q++ = *p++; |
p++; |
} |
} |
if (q[-1] == '$') |
tagendline = (p[-1] == '$'); |
q--; |
if (tagendline) |
*q = '\0'; |
p--; |
|
*p = '\0'; |
} |
} |
|
tp = maketagent(tag, tagfile, taglinenum, tagpattern, tagendline); |
fclose(f); |
TAG_INS(tp); |
return; |
total++; |
} |
} |
fclose(f); |
fclose(f); |
error("No such tag in tags file", NULL_PARG); |
if (total == 0) |
tagfile = NULL; |
return TAG_NOTAG; |
|
curtag = taglist.tl_first; |
|
curseq = 1; |
|
return TAG_FOUND; |
} |
} |
|
|
/* |
/* |
|
* Edit current tagged file. |
|
*/ |
|
public int |
|
edit_tagfile() |
|
{ |
|
if (curtag == NULL) |
|
return (1); |
|
return (edit(curtag->tag_file)); |
|
} |
|
|
|
/* |
* Search for a tag. |
* Search for a tag. |
* This is a stripped-down version of search(). |
* This is a stripped-down version of search(). |
* We don't use search() for several reasons: |
* We don't use search() for several reasons: |
|
|
* regcmp vs. re_comp) behave differently in the presence of |
* regcmp vs. re_comp) behave differently in the presence of |
* parentheses (which are almost always found in a tag). |
* parentheses (which are almost always found in a tag). |
*/ |
*/ |
public POSITION |
static POSITION |
tagsearch() |
ctagsearch() |
{ |
{ |
POSITION pos, linepos; |
POSITION pos, linepos; |
int linenum; |
LINENUM linenum; |
|
int len; |
char *line; |
char *line; |
|
|
/* |
|
* If we have the line number of the tag instead of the pattern, |
|
* just use find_pos. |
|
*/ |
|
if (taglinenum) |
|
return (find_pos(taglinenum)); |
|
|
|
pos = ch_zero(); |
pos = ch_zero(); |
linenum = find_linenum(pos); |
linenum = find_linenum(pos); |
|
|
|
|
* Test the line to see if we have a match. |
* Test the line to see if we have a match. |
* Use strncmp because the pattern may be |
* Use strncmp because the pattern may be |
* truncated (in the tags file) if it is too long. |
* 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). |
*/ |
*/ |
if (strncmp(tagpattern, line, strlen(tagpattern)) == 0) |
len = strlen(curtag->tag_pattern); |
|
if (strncmp(curtag->tag_pattern, line, len) == 0 && |
|
(!curtag->tag_endline || line[len] == '\0' || line[len] == '\r')) |
|
{ |
|
curtag->tag_linenum = find_linenum(linepos); |
break; |
break; |
|
} |
} |
} |
|
|
return (linepos); |
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 enum tag_result |
|
findgtag(tag, type) |
|
char *tag; /* tag to load */ |
|
int type; /* tags type */ |
|
{ |
|
char buf[256]; |
|
FILE *fp; |
|
struct tag *tp; |
|
|
|
if (type != T_CTAGS_X && tag == NULL) |
|
return TAG_NOFILE; |
|
|
|
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 |
|
{ |
|
#if !HAVE_POPEN |
|
return TAG_NOFILE; |
|
#else |
|
char command[512]; |
|
char *flag; |
|
char *qtag; |
|
char *cmd = lgetenv("LESSGLOBALTAGS"); |
|
|
|
if (cmd == NULL || *cmd == '\0') |
|
return TAG_NOFILE; |
|
/* 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: |
|
return TAG_NOTYPE; |
|
} |
|
|
|
/* Get our data from global(1). */ |
|
qtag = shell_quote(tag); |
|
if (qtag == NULL) |
|
qtag = tag; |
|
sprintf(command, "%s -x%s %s", cmd, flag, qtag); |
|
if (qtag != tag) |
|
free(qtag); |
|
fp = popen(command, "r"); |
|
#endif |
|
} |
|
if (fp != NULL) |
|
{ |
|
while (fgets(buf, sizeof(buf), fp)) |
|
{ |
|
char *name, *file, *line; |
|
|
|
if (sigs) |
|
{ |
|
#if HAVE_POPEN |
|
if (fp != stdin) |
|
pclose(fp); |
|
#endif |
|
return TAG_INTR; |
|
} |
|
if (buf[strlen(buf) - 1] == '\n') |
|
buf[strlen(buf) - 1] = 0; |
|
else |
|
{ |
|
int c; |
|
do { |
|
c = fgetc(fp); |
|
} while (c != '\n' && c != EOF); |
|
} |
|
|
|
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 list. */ |
|
tp = maketagent(name, file, (LINENUM) atoi(line), NULL, 0); |
|
TAG_INS(tp); |
|
total++; |
|
} |
|
if (fp != stdin) |
|
{ |
|
if (pclose(fp)) |
|
{ |
|
curtag = NULL; |
|
total = curseq = 0; |
|
return TAG_NOFILE; |
|
} |
|
} |
|
} |
|
|
|
/* Check to see if we found anything. */ |
|
tp = taglist.tl_first; |
|
if (tp == TAG_END) |
|
return TAG_NOTAG; |
|
curtag = tp; |
|
curseq = 1; |
|
return TAG_FOUND; |
|
} |
|
|
|
static int circular = 0; /* 1: circular 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() |
|
{ |
|
struct tag *tp; |
|
|
|
if (curtag == NULL) |
|
/* No tag loaded */ |
|
return NULL; |
|
|
|
tp = curtag->next; |
|
if (tp == TAG_END) |
|
{ |
|
if (!circular) |
|
return NULL; |
|
/* Wrapped around to the head of the queue */ |
|
curtag = taglist.tl_first; |
|
curseq = 1; |
|
} else |
|
{ |
|
curtag = tp; |
|
curseq++; |
|
} |
|
return (curtag->tag_file); |
|
} |
|
|
|
/* |
|
* 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() |
|
{ |
|
struct tag *tp; |
|
|
|
if (curtag == NULL) |
|
/* No tag loaded */ |
|
return NULL; |
|
|
|
tp = curtag->prev; |
|
if (tp == TAG_END) |
|
{ |
|
if (!circular) |
|
return NULL; |
|
/* Wrapped around to the tail of the queue */ |
|
curtag = taglist.tl_last; |
|
curseq = total; |
|
} else |
|
{ |
|
curtag = tp; |
|
curseq--; |
|
} |
|
return (curtag->tag_file); |
|
} |
|
|
|
/* |
|
* 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 == NULL) |
|
return (NULL_POSITION); /* No gtags loaded! */ |
|
return (find_pos(curtag->tag_linenum)); |
|
} |
|
|
|
/* |
|
* The getentry() parses both standard and extended ctags -x format. |
|
* |
|
* [standard format] |
|
* <tag> <lineno> <file> <image> |
|
* +------------------------------------------------ |
|
* |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 <http://www.gnu.org/software/global/global.html> |
|
* |
|
* [extended format] |
|
* <tag> <type> <lineno> <file> <image> |
|
* +---------------------------------------------------------- |
|
* |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 <http://ctags.sourceforge.net> |
|
* |
|
* Returns 0 on success, -1 on error. |
|
* The tag, file, and line will each be NUL-terminated pointers |
|
* into buf. |
|
*/ |
|
|
|
#ifndef isspace |
|
#define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r' || (c) == '\f') |
|
#endif |
|
#ifndef isdigit |
|
#define isdigit(c) ((c) >= '0' && (c <= '9')) |
|
#endif |
|
|
|
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) |
|
return (-1); |
|
*p++ = 0; |
|
for ( ; *p && isspace(*p); p++) /* (skip blanks) */ |
|
; |
|
if (*p == 0) |
|
return (-1); |
|
/* |
|
* 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)) |
|
return (-1); |
|
*line = p; /* line number */ |
|
for (*line = p; *p && !isspace(*p); p++) |
|
; |
|
if (*p == 0) |
|
return (-1); |
|
*p++ = 0; |
|
for ( ; *p && isspace(*p); p++) /* (skip blanks) */ |
|
; |
|
if (*p == 0) |
|
return (-1); |
|
*file = p; /* file name */ |
|
for (*file = p; *p && !isspace(*p); p++) |
|
; |
|
if (*p == 0) |
|
return (-1); |
|
*p = 0; |
|
|
|
/* value check */ |
|
if (strlen(*tag) && strlen(*line) && strlen(*file) && atoi(*line) > 0) |
|
return (0); |
|
return (-1); |
|
} |
|
|
#endif |
#endif |