File: [local] / src / usr.bin / vi / ex / ex_tag.c (download)
Revision 1.10, Sat Feb 16 21:27:57 2002 UTC (22 years, 3 months ago) by millert
Branch: MAIN
CVS Tags: OPENBSD_3_8_BASE, OPENBSD_3_8, OPENBSD_3_7_BASE, OPENBSD_3_7, OPENBSD_3_6_BASE, OPENBSD_3_6, OPENBSD_3_5_BASE, OPENBSD_3_5, OPENBSD_3_4_BASE, OPENBSD_3_4, OPENBSD_3_3_BASE, OPENBSD_3_3, OPENBSD_3_2_BASE, OPENBSD_3_2, OPENBSD_3_1_BASE, OPENBSD_3_1 Changes since 1.9: +27 -27 lines
Part one of userland __P removal. Done with a simple regexp with some minor hand editing to make comments line up correctly. Another pass is forthcoming that handles the cases that could not be done automatically.
|
/* $OpenBSD: ex_tag.c,v 1.10 2002/02/16 21:27:57 millert Exp $ */
/*-
* Copyright (c) 1992, 1993, 1994
* The Regents of the University of California. All rights reserved.
* Copyright (c) 1992, 1993, 1994, 1995, 1996
* Keith Bostic. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* David Hitz of Auspex Systems, Inc.
*
* See the LICENSE file for redistribution information.
*/
#include "config.h"
#ifndef lint
static const char sccsid[] = "@(#)ex_tag.c 10.36 (Berkeley) 9/15/96";
#endif /* not lint */
#include <sys/param.h>
#include <sys/types.h> /* XXX: param.h may not have included types.h */
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <bitstring.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../common/common.h"
#include "../vi/vi.h"
#include "tag.h"
static char *binary_search(char *, char *, char *);
static int compare(char *, char *, char *);
static void ctag_file(SCR *, TAGF *, char *, char **, size_t *);
static int ctag_search(SCR *, char *, size_t, char *);
static int ctag_sfile(SCR *, TAGF *, TAGQ *, char *);
static TAGQ *ctag_slist(SCR *, char *);
static char *linear_search(char *, char *, char *);
static int tag_copy(SCR *, TAG *, TAG **);
static int tag_pop(SCR *, TAGQ *, int);
static int tagf_copy(SCR *, TAGF *, TAGF **);
static int tagf_free(SCR *, TAGF *);
static int tagq_copy(SCR *, TAGQ *, TAGQ **);
/*
* ex_tag_first --
* The tag code can be entered from main, e.g., "vi -t tag".
*
* PUBLIC: int ex_tag_first(SCR *, char *);
*/
int
ex_tag_first(sp, tagarg)
SCR *sp;
char *tagarg;
{
ARGS *ap[2], a;
EXCMD cmd;
/* Build an argument for the ex :tag command. */
ex_cinit(&cmd, C_TAG, 0, OOBLNO, OOBLNO, 0, ap);
ex_cadd(&cmd, &a, tagarg, strlen(tagarg));
/*
* XXX
* Historic vi went ahead and created a temporary file when it failed
* to find the tag. We match historic practice, but don't distinguish
* between real error and failure to find the tag.
*/
if (ex_tag_push(sp, &cmd))
return (0);
/* Display tags in the center of the screen. */
F_CLR(sp, SC_SCR_TOP);
F_SET(sp, SC_SCR_CENTER);
return (0);
}
/*
* ex_tag_push -- ^]
* :tag[!] [string]
*
* Enter a new TAGQ context based on a ctag string.
*
* PUBLIC: int ex_tag_push(SCR *, EXCMD *);
*/
int
ex_tag_push(sp, cmdp)
SCR *sp;
EXCMD *cmdp;
{
EX_PRIVATE *exp;
FREF *frp;
TAG *rtp;
TAGQ *rtqp, *tqp;
recno_t lno;
size_t cno;
long tl;
int force, istmp;
exp = EXP(sp);
switch (cmdp->argc) {
case 1:
if (exp->tag_last != NULL)
free(exp->tag_last);
if ((exp->tag_last = strdup(cmdp->argv[0]->bp)) == NULL) {
msgq(sp, M_SYSERR, NULL);
return (1);
}
/* Taglength may limit the number of characters. */
if ((tl =
O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(exp->tag_last) > tl)
exp->tag_last[tl] = '\0';
break;
case 0:
if (exp->tag_last == NULL) {
msgq(sp, M_ERR, "158|No previous tag entered");
return (1);
}
break;
default:
abort();
}
/* Get the tag information. */
if ((tqp = ctag_slist(sp, exp->tag_last)) == NULL)
return (1);
/*
* Allocate all necessary memory before swapping screens. Initialize
* flags so we know what to free.
*/
rtp = NULL;
rtqp = NULL;
if (exp->tq.cqh_first == (void *)&exp->tq) {
/* Initialize the `local context' tag queue structure. */
CALLOC_GOTO(sp, rtqp, TAGQ *, 1, sizeof(TAGQ));
CIRCLEQ_INIT(&rtqp->tagq);
/* Initialize and link in its tag structure. */
CALLOC_GOTO(sp, rtp, TAG *, 1, sizeof(TAG));
CIRCLEQ_INSERT_HEAD(&rtqp->tagq, rtp, q);
rtqp->current = rtp;
}
/*
* Stick the current context information in a convenient place, we're
* about to lose it. Note, if we're called on editor startup, there
* will be no FREF structure.
*/
frp = sp->frp;
lno = sp->lno;
cno = sp->cno;
istmp = frp == NULL ||
F_ISSET(frp, FR_TMPFILE) && !F_ISSET(cmdp, E_NEWSCREEN);
/* Try to switch to the tag. */
force = FL_ISSET(cmdp->iflags, E_C_FORCE);
if (F_ISSET(cmdp, E_NEWSCREEN)) {
if (ex_tag_Nswitch(sp, tqp->tagq.cqh_first, force))
goto err;
/* Everything else gets done in the new screen. */
sp = sp->nextdisp;
exp = EXP(sp);
} else
if (ex_tag_nswitch(sp, tqp->tagq.cqh_first, force))
goto err;
/*
* If this is the first tag, put a `current location' queue entry
* in place, so we can pop all the way back to the current mark.
* Note, it doesn't point to much of anything, it's a placeholder.
*/
if (exp->tq.cqh_first == (void *)&exp->tq) {
CIRCLEQ_INSERT_HEAD(&exp->tq, rtqp, q);
} else
rtqp = exp->tq.cqh_first;
/* Link the new TAGQ structure into place. */
CIRCLEQ_INSERT_HEAD(&exp->tq, tqp, q);
(void)ctag_search(sp,
tqp->current->search, tqp->current->slen, tqp->tag);
/*
* Move the current context from the temporary save area into the
* right structure.
*
* If we were in a temporary file, we don't have a context to which
* we can return, so just make it be the same as what we're moving
* to. It will be a little odd that ^T doesn't change anything, but
* I don't think it's a big deal.
*/
if (istmp) {
rtqp->current->frp = sp->frp;
rtqp->current->lno = sp->lno;
rtqp->current->cno = sp->cno;
} else {
rtqp->current->frp = frp;
rtqp->current->lno = lno;
rtqp->current->cno = cno;
}
return (0);
err:
alloc_err:
if (rtqp != NULL)
free(rtqp);
if (rtp != NULL)
free(rtp);
tagq_free(sp, tqp);
return (1);
}
/*
* ex_tag_next --
* Switch context to the next TAG.
*
* PUBLIC: int ex_tag_next(SCR *, EXCMD *);
*/
int
ex_tag_next(sp, cmdp)
SCR *sp;
EXCMD *cmdp;
{
EX_PRIVATE *exp;
TAG *tp;
TAGQ *tqp;
exp = EXP(sp);
if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
tag_msg(sp, TAG_EMPTY, NULL);
return (1);
}
if ((tp = tqp->current->q.cqe_next) == (void *)&tqp->tagq) {
msgq(sp, M_ERR, "282|Already at the last tag of this group");
return (1);
}
if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
return (1);
tqp->current = tp;
if (F_ISSET(tqp, TAG_CSCOPE))
(void)cscope_search(sp, tqp, tp);
else
(void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
return (0);
}
/*
* ex_tag_prev --
* Switch context to the next TAG.
*
* PUBLIC: int ex_tag_prev(SCR *, EXCMD *);
*/
int
ex_tag_prev(sp, cmdp)
SCR *sp;
EXCMD *cmdp;
{
EX_PRIVATE *exp;
TAG *tp;
TAGQ *tqp;
exp = EXP(sp);
if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
tag_msg(sp, TAG_EMPTY, NULL);
return (0);
}
if ((tp = tqp->current->q.cqe_prev) == (void *)&tqp->tagq) {
msgq(sp, M_ERR, "255|Already at the first tag of this group");
return (1);
}
if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
return (1);
tqp->current = tp;
if (F_ISSET(tqp, TAG_CSCOPE))
(void)cscope_search(sp, tqp, tp);
else
(void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
return (0);
}
/*
* ex_tag_nswitch --
* Switch context to the specified TAG.
*
* PUBLIC: int ex_tag_nswitch(SCR *, TAG *, int);
*/
int
ex_tag_nswitch(sp, tp, force)
SCR *sp;
TAG *tp;
int force;
{
/* Get a file structure. */
if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)
return (1);
/* If not changing files, return, we're done. */
if (tp->frp == sp->frp)
return (0);
/* Check for permission to leave. */
if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
return (1);
/* Initialize the new file. */
if (file_init(sp, tp->frp, NULL, FS_SETALT))
return (1);
/* Display tags in the center of the screen. */
F_CLR(sp, SC_SCR_TOP);
F_SET(sp, SC_SCR_CENTER);
/* Switch. */
F_SET(sp, SC_FSWITCH);
return (0);
}
/*
* ex_tag_Nswitch --
* Switch context to the specified TAG in a new screen.
*
* PUBLIC: int ex_tag_Nswitch(SCR *, TAG *, int);
*/
int
ex_tag_Nswitch(sp, tp, force)
SCR *sp;
TAG *tp;
int force;
{
SCR *new;
/* Get a file structure. */
if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)
return (1);
/* Get a new screen. */
if (screen_init(sp->gp, sp, &new))
return (1);
if (vs_split(sp, new, 0)) {
(void)file_end(new, new->ep, 1);
(void)screen_end(new);
return (1);
}
/* Get a backing file. */
if (tp->frp == sp->frp) {
/* Copy file state. */
new->ep = sp->ep;
++new->ep->refcnt;
new->frp = tp->frp;
new->frp->flags = sp->frp->flags;
} else if (file_init(new, tp->frp, NULL, force)) {
(void)vs_discard(new, NULL);
(void)screen_end(new);
return (1);
}
/* Create the argument list. */
new->cargv = new->argv = ex_buildargv(sp, NULL, tp->frp->name);
/* Display tags in the center of the screen. */
F_CLR(new, SC_SCR_TOP);
F_SET(new, SC_SCR_CENTER);
/* Switch. */
sp->nextdisp = new;
F_SET(sp, SC_SSWITCH);
return (0);
}
/*
* ex_tag_pop -- ^T
* :tagp[op][!] [number | file]
*
* Pop to a previous TAGQ context.
*
* PUBLIC: int ex_tag_pop(SCR *, EXCMD *);
*/
int
ex_tag_pop(sp, cmdp)
SCR *sp;
EXCMD *cmdp;
{
EX_PRIVATE *exp;
TAGQ *tqp, *dtqp;
size_t arglen;
long off;
char *arg, *p, *t;
/* Check for an empty stack. */
exp = EXP(sp);
if (exp->tq.cqh_first == (void *)&exp->tq) {
tag_msg(sp, TAG_EMPTY, NULL);
return (1);
}
/* Find the last TAG structure that we're going to DISCARD! */
switch (cmdp->argc) {
case 0: /* Pop one tag. */
dtqp = exp->tq.cqh_first;
break;
case 1: /* Name or number. */
arg = cmdp->argv[0]->bp;
off = strtol(arg, &p, 10);
if (*p != '\0')
goto filearg;
/* Number: pop that many queue entries. */
if (off < 1)
return (0);
for (tqp = exp->tq.cqh_first;
tqp != (void *)&exp->tq && --off > 1;
tqp = tqp->q.cqe_next);
if (tqp == (void *)&exp->tq) {
msgq(sp, M_ERR,
"159|Less than %s entries on the tags stack; use :display t[ags]",
arg);
return (1);
}
dtqp = tqp;
break;
/* File argument: pop to that queue entry. */
filearg: arglen = strlen(arg);
for (tqp = exp->tq.cqh_first;
tqp != (void *)&exp->tq;
dtqp = tqp, tqp = tqp->q.cqe_next) {
/* Don't pop to the current file. */
if (tqp == exp->tq.cqh_first)
continue;
p = tqp->current->frp->name;
if ((t = strrchr(p, '/')) == NULL)
t = p;
else
++t;
if (!strncmp(arg, t, arglen))
break;
}
if (tqp == (void *)&exp->tq) {
msgq_str(sp, M_ERR, arg,
"160|No file %s on the tags stack to return to; use :display t[ags]");
return (1);
}
if (tqp == exp->tq.cqh_first)
return (0);
break;
default:
abort();
}
return (tag_pop(sp, dtqp, FL_ISSET(cmdp->iflags, E_C_FORCE)));
}
/*
* ex_tag_top -- :tagt[op][!]
* Clear the tag stack.
*
* PUBLIC: int ex_tag_top(SCR *, EXCMD *);
*/
int
ex_tag_top(sp, cmdp)
SCR *sp;
EXCMD *cmdp;
{
EX_PRIVATE *exp;
exp = EXP(sp);
/* Check for an empty stack. */
if (exp->tq.cqh_first == (void *)&exp->tq) {
tag_msg(sp, TAG_EMPTY, NULL);
return (1);
}
/* Return to the oldest information. */
return (tag_pop(sp,
exp->tq.cqh_last->q.cqe_prev, FL_ISSET(cmdp->iflags, E_C_FORCE)));
}
/*
* tag_pop --
* Pop up to and including the specified TAGQ context.
*/
static int
tag_pop(sp, dtqp, force)
SCR *sp;
TAGQ *dtqp;
int force;
{
EX_PRIVATE *exp;
TAG *tp;
TAGQ *tqp;
exp = EXP(sp);
/*
* Update the cursor from the saved TAG information of the TAG
* structure we're moving to.
*/
tp = dtqp->q.cqe_next->current;
if (tp->frp == sp->frp) {
sp->lno = tp->lno;
sp->cno = tp->cno;
} else {
if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
return (1);
tp->frp->lno = tp->lno;
tp->frp->cno = tp->cno;
F_SET(sp->frp, FR_CURSORSET);
if (file_init(sp, tp->frp, NULL, FS_SETALT))
return (1);
F_SET(sp, SC_FSWITCH);
}
/* Pop entries off the queue up to and including dtqp. */
do {
tqp = exp->tq.cqh_first;
if (tagq_free(sp, tqp))
return (0);
} while (tqp != dtqp);
/*
* If only a single tag left, we've returned to the first tag point,
* and the stack is now empty.
*/
if (exp->tq.cqh_first->q.cqe_next == (void *)&exp->tq)
tagq_free(sp, exp->tq.cqh_first);
return (0);
}
/*
* ex_tag_display --
* Display the list of tags.
*
* PUBLIC: int ex_tag_display(SCR *);
*/
int
ex_tag_display(sp)
SCR *sp;
{
EX_PRIVATE *exp;
TAG *tp;
TAGQ *tqp;
int cnt;
size_t len;
char *p, *sep;
exp = EXP(sp);
if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
tag_msg(sp, TAG_EMPTY, NULL);
return (0);
}
/*
* We give the file name 20 columns and the search string the rest.
* If there's not enough room, we don't do anything special, it's
* not worth the effort, it just makes the display more confusing.
*
* We also assume that characters in file names map 1-1 to printing
* characters. This might not be true, but I don't think it's worth
* fixing. (The obvious fix is to pass the filenames through the
* msg_print function.)
*/
#define L_NAME 30 /* Name. */
#define L_SLOP 4 /* Leading number plus trailing *. */
#define L_SPACE 5 /* Spaces after name, before tag. */
#define L_TAG 20 /* Tag. */
if (sp->cols <= L_NAME + L_SLOP) {
msgq(sp, M_ERR, "292|Display too small.");
return (0);
}
/*
* Display the list of tags for each queue entry. The first entry
* is numbered, and the current tag entry has an asterisk appended.
*/
for (cnt = 1, tqp = exp->tq.cqh_first; !INTERRUPTED(sp) &&
tqp != (void *)&exp->tq; ++cnt, tqp = tqp->q.cqe_next)
for (tp = tqp->tagq.cqh_first;
tp != (void *)&tqp->tagq; tp = tp->q.cqe_next) {
if (tp == tqp->tagq.cqh_first)
(void)ex_printf(sp, "%2d ", cnt);
else
(void)ex_printf(sp, " ");
p = tp->frp == NULL ? tp->fname : tp->frp->name;
if ((len = strlen(p)) > L_NAME) {
len = len - (L_NAME - 4);
(void)ex_printf(sp, " ... %*.*s",
L_NAME - 4, L_NAME - 4, p + len);
} else
(void)ex_printf(sp,
" %*.*s", L_NAME, L_NAME, p);
if (tqp->current == tp)
(void)ex_printf(sp, "*");
if (tp == tqp->tagq.cqh_first && tqp->tag != NULL &&
(sp->cols - L_NAME) >= L_TAG + L_SPACE) {
len = strlen(tqp->tag);
if (len > sp->cols - (L_NAME + L_SPACE))
len = sp->cols - (L_NAME + L_SPACE);
(void)ex_printf(sp, "%s%.*s",
tqp->current == tp ? " " : " ",
(int)len, tqp->tag);
}
(void)ex_printf(sp, "\n");
}
return (0);
}
/*
* ex_tag_copy --
* Copy a screen's tag structures.
*
* PUBLIC: int ex_tag_copy(SCR *, SCR *);
*/
int
ex_tag_copy(orig, sp)
SCR *orig, *sp;
{
EX_PRIVATE *oexp, *nexp;
TAGQ *aqp, *tqp;
TAG *ap, *tp;
TAGF *atfp, *tfp;
oexp = EXP(orig);
nexp = EXP(sp);
/* Copy tag queue and tags stack. */
for (aqp = oexp->tq.cqh_first;
aqp != (void *)&oexp->tq; aqp = aqp->q.cqe_next) {
if (tagq_copy(sp, aqp, &tqp))
return (1);
for (ap = aqp->tagq.cqh_first;
ap != (void *)&aqp->tagq; ap = ap->q.cqe_next) {
if (tag_copy(sp, ap, &tp))
return (1);
/* Set the current pointer. */
if (aqp->current == ap)
tqp->current = tp;
CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q);
}
CIRCLEQ_INSERT_TAIL(&nexp->tq, tqp, q);
}
/* Copy list of tag files. */
for (atfp = oexp->tagfq.tqh_first;
atfp != NULL; atfp = atfp->q.tqe_next) {
if (tagf_copy(sp, atfp, &tfp))
return (1);
TAILQ_INSERT_TAIL(&nexp->tagfq, tfp, q);
}
/* Copy the last tag. */
if (oexp->tag_last != NULL &&
(nexp->tag_last = strdup(oexp->tag_last)) == NULL) {
msgq(sp, M_SYSERR, NULL);
return (1);
}
return (0);
}
/*
* tagf_copy --
* Copy a TAGF structure and return it in new memory.
*/
static int
tagf_copy(sp, otfp, tfpp)
SCR *sp;
TAGF *otfp, **tfpp;
{
TAGF *tfp;
MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
*tfp = *otfp;
/* XXX: Allocate as part of the TAGF structure!!! */
if ((tfp->name = strdup(otfp->name)) == NULL)
return (1);
*tfpp = tfp;
return (0);
}
/*
* tagq_copy --
* Copy a TAGQ structure and return it in new memory.
*/
static int
tagq_copy(sp, otqp, tqpp)
SCR *sp;
TAGQ *otqp, **tqpp;
{
TAGQ *tqp;
size_t len;
len = sizeof(TAGQ);
if (otqp->tag != NULL)
len += otqp->tlen + 1;
MALLOC_RET(sp, tqp, TAGQ *, len);
memcpy(tqp, otqp, len);
CIRCLEQ_INIT(&tqp->tagq);
tqp->current = NULL;
if (otqp->tag != NULL)
tqp->tag = tqp->buf;
*tqpp = tqp;
return (0);
}
/*
* tag_copy --
* Copy a TAG structure and return it in new memory.
*/
static int
tag_copy(sp, otp, tpp)
SCR *sp;
TAG *otp, **tpp;
{
TAG *tp;
size_t len;
len = sizeof(TAG);
if (otp->fname != NULL)
len += otp->fnlen + 1;
if (otp->search != NULL)
len += otp->slen + 1;
MALLOC_RET(sp, tp, TAG *, len);
memcpy(tp, otp, len);
if (otp->fname != NULL)
tp->fname = tp->buf;
if (otp->search != NULL)
tp->search = tp->fname + otp->fnlen + 1;
*tpp = tp;
return (0);
}
/*
* tagf_free --
* Free a TAGF structure.
*/
static int
tagf_free(sp, tfp)
SCR *sp;
TAGF *tfp;
{
EX_PRIVATE *exp;
exp = EXP(sp);
TAILQ_REMOVE(&exp->tagfq, tfp, q);
free(tfp->name);
free(tfp);
return (0);
}
/*
* tagq_free --
* Free a TAGQ structure (and associated TAG structures).
*
* PUBLIC: int tagq_free(SCR *, TAGQ *);
*/
int
tagq_free(sp, tqp)
SCR *sp;
TAGQ *tqp;
{
EX_PRIVATE *exp;
TAG *tp;
exp = EXP(sp);
while ((tp = tqp->tagq.cqh_first) != (void *)&tqp->tagq) {
CIRCLEQ_REMOVE(&tqp->tagq, tp, q);
free(tp);
}
/*
* !!!
* If allocated and then the user failed to switch files, the TAGQ
* structure was never attached to any list.
*/
if (tqp->q.cqe_next != NULL)
CIRCLEQ_REMOVE(&exp->tq, tqp, q);
free(tqp);
return (0);
}
/*
* tag_msg
* A few common messages.
*
* PUBLIC: void tag_msg(SCR *, tagmsg_t, char *);
*/
void
tag_msg(sp, msg, tag)
SCR *sp;
tagmsg_t msg;
char *tag;
{
switch (msg) {
case TAG_BADLNO:
msgq_str(sp, M_ERR, tag,
"164|%s: the tag's line number is past the end of the file");
break;
case TAG_EMPTY:
msgq(sp, M_INFO, "165|The tags stack is empty");
break;
case TAG_SEARCH:
msgq_str(sp, M_ERR, tag, "166|%s: search pattern not found");
break;
default:
abort();
}
}
/*
* ex_tagf_alloc --
* Create a new list of ctag files.
*
* PUBLIC: int ex_tagf_alloc(SCR *, char *);
*/
int
ex_tagf_alloc(sp, str)
SCR *sp;
char *str;
{
EX_PRIVATE *exp;
TAGF *tfp;
size_t len;
char *p, *t;
/* Free current queue. */
exp = EXP(sp);
while ((tfp = exp->tagfq.tqh_first) != NULL)
tagf_free(sp, tfp);
/* Create new queue. */
for (p = t = str;; ++p) {
if (*p == '\0' || isblank(*p)) {
if ((len = p - t) > 1) {
MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
MALLOC(sp, tfp->name, char *, len + 1);
if (tfp->name == NULL) {
free(tfp);
return (1);
}
memcpy(tfp->name, t, len);
tfp->name[len] = '\0';
tfp->flags = 0;
TAILQ_INSERT_TAIL(&exp->tagfq, tfp, q);
}
t = p + 1;
}
if (*p == '\0')
break;
}
return (0);
}
/* Free previous queue. */
/*
* ex_tag_free --
* Free the ex tag information.
*
* PUBLIC: int ex_tag_free(SCR *);
*/
int
ex_tag_free(sp)
SCR *sp;
{
EX_PRIVATE *exp;
TAGF *tfp;
TAGQ *tqp;
/* Free up tag information. */
exp = EXP(sp);
while ((tqp = exp->tq.cqh_first) != (void *)&exp->tq)
tagq_free(sp, tqp);
while ((tfp = exp->tagfq.tqh_first) != NULL)
tagf_free(sp, tfp);
if (exp->tag_last != NULL)
free(exp->tag_last);
return (0);
}
/*
* ctag_search --
* Search a file for a tag.
*/
static int
ctag_search(sp, search, slen, tag)
SCR *sp;
char *search, *tag;
size_t slen;
{
MARK m;
char *p;
/*
* !!!
* The historic tags file format (from a long, long time ago...)
* used a line number, not a search string. I got complaints, so
* people are still using the format. POSIX 1003.2 permits it.
*/
if (isdigit(search[0])) {
m.lno = atoi(search);
if (!db_exist(sp, m.lno)) {
tag_msg(sp, TAG_BADLNO, tag);
return (1);
}
} else {
/*
* Search for the tag; cheap fallback for C functions
* if the name is the same but the arguments have changed.
*/
m.lno = 1;
m.cno = 0;
if (f_search(sp, &m, &m,
search, slen, NULL, SEARCH_FILE | SEARCH_TAG))
if ((p = strrchr(search, '(')) != NULL) {
slen = p - search;
if (f_search(sp, &m, &m, search, slen,
NULL, SEARCH_FILE | SEARCH_TAG))
goto notfound;
} else {
notfound: tag_msg(sp, TAG_SEARCH, tag);
return (1);
}
/*
* !!!
* Historically, tags set the search direction if it wasn't
* already set.
*/
if (sp->searchdir == NOTSET)
sp->searchdir = FORWARD;
}
/*
* !!!
* Tags move to the first non-blank, NOT the search pattern start.
*/
sp->lno = m.lno;
sp->cno = 0;
(void)nonblank(sp, sp->lno, &sp->cno);
return (0);
}
/*
* ctag_slist --
* Search the list of tags files for a tag, and return tag queue.
*/
static TAGQ *
ctag_slist(sp, tag)
SCR *sp;
char *tag;
{
EX_PRIVATE *exp;
TAGF *tfp;
TAGQ *tqp;
size_t len;
int echk;
exp = EXP(sp);
/* Allocate and initialize the tag queue structure. */
len = strlen(tag);
CALLOC_GOTO(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + len + 1);
CIRCLEQ_INIT(&tqp->tagq);
tqp->tag = tqp->buf;
memcpy(tqp->tag, tag, (tqp->tlen = len) + 1);
/*
* Find the tag, only display missing file messages once, and
* then only if we didn't find the tag.
*/
for (echk = 0,
tfp = exp->tagfq.tqh_first; tfp != NULL; tfp = tfp->q.tqe_next)
if (ctag_sfile(sp, tfp, tqp, tag)) {
echk = 1;
F_SET(tfp, TAGF_ERR);
} else
F_CLR(tfp, TAGF_ERR | TAGF_ERR_WARN);
/* Check to see if we found anything. */
if (tqp->tagq.cqh_first == (void *)&tqp->tagq) {
msgq_str(sp, M_ERR, tag, "162|%s: tag not found");
if (echk)
for (tfp = exp->tagfq.tqh_first;
tfp != NULL; tfp = tfp->q.tqe_next)
if (F_ISSET(tfp, TAGF_ERR) &&
!F_ISSET(tfp, TAGF_ERR_WARN)) {
errno = tfp->errnum;
msgq_str(sp, M_SYSERR, tfp->name, "%s");
F_SET(tfp, TAGF_ERR_WARN);
}
free(tqp);
return (NULL);
}
tqp->current = tqp->tagq.cqh_first;
return (tqp);
alloc_err:
return (NULL);
}
/*
* ctag_sfile --
* Search a tags file for a tag, adding any found to the tag queue.
*/
static int
ctag_sfile(sp, tfp, tqp, tname)
SCR *sp;
TAGF *tfp;
TAGQ *tqp;
char *tname;
{
struct stat sb;
TAG *tp;
size_t dlen, nlen, slen;
int fd, i, nf1, nf2;
char *back, *cname, *dname, *front, *map, *name, *p, *search, *t;
if ((fd = open(tfp->name, O_RDONLY, 0)) < 0) {
tfp->errnum = errno;
return (1);
}
/*
* XXX
* Some old BSD systems require MAP_FILE as an argument when mapping
* regular files.
*/
#ifndef MAP_FILE
#define MAP_FILE 0
#endif
/*
* XXX
* We'd like to test if the file is too big to mmap. Since we don't
* know what size or type off_t's or size_t's are, what the largest
* unsigned integral type is, or what random insanity the local C
* compiler will perpetrate, doing the comparison in a portable way
* is flatly impossible. Hope mmap fails if the file is too large.
*/
if (fstat(fd, &sb) != 0 ||
(map = mmap(NULL, (size_t)sb.st_size, PROT_READ | PROT_WRITE,
MAP_FILE | MAP_PRIVATE, fd, (off_t)0)) == MAP_FAILED) {
tfp->errnum = errno;
(void)close(fd);
return (1);
}
front = map;
back = front + sb.st_size;
front = binary_search(tname, front, back);
front = linear_search(tname, front, back);
if (front == NULL)
goto done;
/*
* Initialize and link in the tag structure(s). The historic ctags
* file format only permitted a single tag location per tag. The
* obvious extension to permit multiple tags locations per tag is to
* output multiple records in the standard format. Unfortunately,
* this won't work correctly with historic ex/vi implementations,
* because their binary search assumes that there's only one record
* per tag, and so will use a random tag entry if there si more than
* one. This code handles either format.
*
* The tags file is in the following format:
*
* <tag> <filename> <line number> | <pattern>
*
* Figure out how long everything is so we can allocate in one swell
* foop, but discard anything that looks wrong.
*/
for (;;) {
/* Nul-terminate the end of the line. */
for (p = front; p < back && *p != '\n'; ++p);
if (p == back || *p != '\n')
break;
*p = '\0';
/* Update the pointers for the next time. */
t = p + 1;
p = front;
front = t;
/* Break the line into tokens. */
for (i = 0; i < 2 && (t = strsep(&p, "\t ")) != NULL; ++i)
switch (i) {
case 0: /* Tag. */
cname = t;
break;
case 1: /* Filename. */
name = t;
nlen = strlen(name);
break;
}
/* Check for corruption. */
if (i != 2 || p == NULL || t == NULL)
goto corrupt;
/* The rest of the string is the search pattern. */
search = p;
if ((slen = strlen(p)) == 0) {
corrupt: p = msg_print(sp, tname, &nf1);
t = msg_print(sp, tfp->name, &nf2);
msgq(sp, M_ERR, "163|%s: corrupted tag in %s", p, t);
if (nf1)
FREE_SPACE(sp, p, 0);
if (nf2)
FREE_SPACE(sp, t, 0);
continue;
}
/* Check for passing the last entry. */
if (strcmp(tname, cname))
break;
/* Resolve the file name. */
ctag_file(sp, tfp, name, &dname, &dlen);
CALLOC_GOTO(sp, tp,
TAG *, 1, sizeof(TAG) + dlen + 2 + nlen + 1 + slen + 1);
tp->fname = tp->buf;
if (dlen != 0) {
memcpy(tp->fname, dname, dlen);
tp->fname[dlen] = '/';
++dlen;
}
memcpy(tp->fname + dlen, name, nlen + 1);
tp->fnlen = dlen + nlen;
tp->search = tp->fname + tp->fnlen + 1;
memcpy(tp->search, search, (tp->slen = slen) + 1);
CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q);
}
alloc_err:
done: if (munmap(map, (size_t)sb.st_size))
msgq(sp, M_SYSERR, "munmap");
if (close(fd))
msgq(sp, M_SYSERR, "close");
return (0);
}
/*
* ctag_file --
* Search for the right path to this file.
*/
static void
ctag_file(sp, tfp, name, dirp, dlenp)
SCR *sp;
TAGF *tfp;
char *name, **dirp;
size_t *dlenp;
{
struct stat sb;
char *p, buf[MAXPATHLEN];
/*
* !!!
* If the tag file path is a relative path, see if it exists. If it
* doesn't, look relative to the tags file path. It's okay for a tag
* file to not exist, and historically, vi simply displayed a "new"
* file. However, if the path exists relative to the tag file, it's
* pretty clear what's happening, so we may as well get it right.
*/
*dlenp = 0;
if (name[0] != '/' &&
stat(name, &sb) && (p = strrchr(tfp->name, '/')) != NULL) {
*p = '\0';
(void)snprintf(buf, sizeof(buf), "%s/%s", tfp->name, name);
if (stat(buf, &sb) == 0) {
*dirp = tfp->name;
*dlenp = strlen(*dirp);
}
*p = '/';
}
}
/*
* Binary search for "string" in memory between "front" and "back".
*
* This routine is expected to return a pointer to the start of a line at
* *or before* the first word matching "string". Relaxing the constraint
* this way simplifies the algorithm.
*
* Invariants:
* front points to the beginning of a line at or before the first
* matching string.
*
* back points to the beginning of a line at or after the first
* matching line.
*
* Base of the Invariants.
* front = NULL;
* back = EOF;
*
* Advancing the Invariants:
*
* p = first newline after halfway point from front to back.
*
* If the string at "p" is not greater than the string to match,
* p is the new front. Otherwise it is the new back.
*
* Termination:
*
* The definition of the routine allows it return at any point,
* since front is always at or before the line to print.
*
* In fact, it returns when the chosen "p" equals "back". This
* implies that there exists a string is least half as long as
* (back - front), which in turn implies that a linear search will
* be no more expensive than the cost of simply printing a string or two.
*
* Trying to continue with binary search at this point would be
* more trouble than it's worth.
*/
#define EQUAL 0
#define GREATER 1
#define LESS (-1)
#define SKIP_PAST_NEWLINE(p, back) while (p < back && *p++ != '\n');
static char *
binary_search(string, front, back)
char *string, *front, *back;
{
char *p;
p = front + (back - front) / 2;
SKIP_PAST_NEWLINE(p, back);
while (p != back) {
if (compare(string, p, back) == GREATER)
front = p;
else
back = p;
p = front + (back - front) / 2;
SKIP_PAST_NEWLINE(p, back);
}
return (front);
}
/*
* Find the first line that starts with string, linearly searching from front
* to back.
*
* Return NULL for no such line.
*
* This routine assumes:
*
* o front points at the first character in a line.
* o front is before or at the first line to be printed.
*/
static char *
linear_search(string, front, back)
char *string, *front, *back;
{
while (front < back) {
switch (compare(string, front, back)) {
case EQUAL: /* Found it. */
return (front);
case LESS: /* No such string. */
return (NULL);
case GREATER: /* Keep going. */
break;
}
SKIP_PAST_NEWLINE(front, back);
}
return (NULL);
}
/*
* Return LESS, GREATER, or EQUAL depending on how the string1 compares
* with string2 (s1 ??? s2).
*
* o Matches up to len(s1) are EQUAL.
* o Matches up to len(s2) are GREATER.
*
* The string "s1" is null terminated. The string s2 is '\t', space, (or
* "back") terminated.
*
* !!!
* Reasonably modern ctags programs use tabs as separators, not spaces.
* However, historic programs did use spaces, and, I got complaints.
*/
static int
compare(s1, s2, back)
char *s1, *s2, *back;
{
for (; *s1 && s2 < back && (*s2 != '\t' && *s2 != ' '); ++s1, ++s2)
if (*s1 != *s2)
return (*s1 < *s2 ? LESS : GREATER);
return (*s1 ? GREATER : s2 < back &&
(*s2 != '\t' && *s2 != ' ') ? LESS : EQUAL);
}