#include "less.h"
#define WHITESP(c) ((c) == ' ' || (c) == '\t')
char *tags = "tags";
static int total;
static int curseq;
extern int linenums;
enum tag_result {
TAG_FOUND,
TAG_NOFILE,
TAG_NOTAG,
TAG_NOTYPE,
TAG_INTR
};
static enum tag_result findctag(char *);
static char *nextctag(void);
static char *prevctag(void);
static off_t ctagsearch(void);
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;
char *tag_file;
off_t tag_linenum;
char *tag_pattern;
int tag_endline;
};
static struct tag *curtag;
#define TAG_INS(tp) \
(tp)->next = TAG_END; \
(tp)->prev = taglist.tl_last; \
taglist.tl_last->next = (tp); \
taglist.tl_last = (tp);
#define TAG_RM(tp) \
(tp)->next->prev = (tp)->prev; \
(tp)->prev->next = (tp)->next;
void
cleantags(void)
{
struct tag *tp;
while ((tp = taglist.tl_first) != TAG_END) {
TAG_RM(tp);
free(tp->tag_file);
free(tp->tag_pattern);
free(tp);
}
curtag = NULL;
total = curseq = 0;
}
static struct tag *
maketagent(char *file, off_t linenum, char *pattern, int endline)
{
struct tag *tp;
tp = ecalloc(sizeof (struct tag), 1);
tp->tag_file = estrdup(file);
tp->tag_linenum = linenum;
tp->tag_endline = endline;
if (pattern == NULL)
tp->tag_pattern = NULL;
else
tp->tag_pattern = estrdup(pattern);
return (tp);
}
void
findtag(char *tag)
{
enum tag_result result;
result = findctag(tag);
switch (result) {
case TAG_FOUND:
case TAG_INTR:
break;
case TAG_NOFILE:
error("No tags file", NULL);
break;
case TAG_NOTAG:
error("No such tag in tags file", NULL);
break;
case TAG_NOTYPE:
error("unknown tag type", NULL);
break;
}
}
off_t
tagsearch(void)
{
if (curtag == NULL)
return (-1);
if (curtag->tag_linenum != 0)
return (find_pos(curtag->tag_linenum));
return (ctagsearch());
}
char *
nexttag(int n)
{
char *tagfile = NULL;
while (n-- > 0)
tagfile = nextctag();
return (tagfile);
}
char *
prevtag(int n)
{
char *tagfile = NULL;
while (n-- > 0)
tagfile = prevctag();
return (tagfile);
}
int
ntags(void)
{
return (total);
}
int
curr_tag(void)
{
return (curseq);
}
static enum tag_result
findctag(char *tag)
{
char *p;
FILE *f;
int taglen;
off_t taglinenum;
char *tagfile;
char *tagpattern;
int tagendline;
int search_char;
int err;
char tline[TAGLINE_SIZE];
struct tag *tp;
p = shell_unquote(tags);
f = fopen(p, "r");
free(p);
if (f == NULL)
return (TAG_NOFILE);
cleantags();
total = 0;
taglen = strlen(tag);
while (fgets(tline, sizeof (tline), f) != NULL) {
if (tline[0] == '!')
continue;
if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen]))
continue;
tagpattern = NULL;
p = skipsp(tline+taglen);
if (*p == '\0')
continue;
tagfile = p;
while (!WHITESP(*p) && *p != '\0')
p++;
*p++ = '\0';
p = skipsp(p);
if (*p == '\0')
continue;
tagendline = 0;
taglinenum = getnum(&p, 0, &err);
if (!err && taglinenum <= 0) {
continue;
}
if (err) {
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';
}
tp = maketagent(tagfile, taglinenum, tagpattern, tagendline);
TAG_INS(tp);
total++;
}
fclose(f);
if (total == 0)
return (TAG_NOTAG);
curtag = taglist.tl_first;
curseq = 1;
return (TAG_FOUND);
}
int
edit_tagfile(void)
{
if (curtag == NULL)
return (1);
return (edit(curtag->tag_file));
}
static off_t
ctagsearch(void)
{
off_t pos, linepos;
off_t linenum;
int len;
char *line;
pos = ch_zero();
linenum = find_linenum(pos);
for (;;) {
if (abort_sigs())
return (-1);
linepos = pos;
pos = forw_raw_line(pos, &line, (int *)NULL);
if (linenum != 0)
linenum++;
if (pos == -1) {
error("Tag not found", NULL);
return (-1);
}
if (linenums)
add_lnum(linenum, pos);
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;
}
}
return (linepos);
}
static int circular = 0;
static char *
nextctag(void)
{
struct tag *tp;
if (curtag == NULL)
return (NULL);
tp = curtag->next;
if (tp == TAG_END) {
if (!circular)
return (NULL);
curtag = taglist.tl_first;
curseq = 1;
} else {
curtag = tp;
curseq++;
}
return (curtag->tag_file);
}
static char *
prevctag(void)
{
struct tag *tp;
if (curtag == NULL)
return (NULL);
tp = curtag->prev;
if (tp == TAG_END) {
if (!circular)
return (NULL);
curtag = taglist.tl_last;
curseq = total;
} else {
curtag = tp;
curseq--;
}
return (curtag->tag_file);
}