diff options
Diffstat (limited to 'ex/ex_tag.c')
-rw-r--r-- | ex/ex_tag.c | 1324 |
1 files changed, 1324 insertions, 0 deletions
diff --git a/ex/ex_tag.c b/ex/ex_tag.c new file mode 100644 index 000000000000..461b1526ef00 --- /dev/null +++ b/ex/ex_tag.c @@ -0,0 +1,1324 @@ +/*- + * 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 __P((char *, char *, char *)); +static int compare __P((char *, char *, char *)); +static void ctag_file __P((SCR *, TAGF *, char *, char **, size_t *)); +static int ctag_search __P((SCR *, char *, size_t, char *)); +static int ctag_sfile __P((SCR *, TAGF *, TAGQ *, char *)); +static TAGQ *ctag_slist __P((SCR *, char *)); +static char *linear_search __P((char *, char *, char *)); +static int tag_copy __P((SCR *, TAG *, TAG **)); +static int tag_pop __P((SCR *, TAGQ *, int)); +static int tagf_copy __P((SCR *, TAGF *, TAGF **)); +static int tagf_free __P((SCR *, TAGF *)); +static int tagq_copy __P((SCR *, TAGQ *, TAGQ **)); + +/* + * ex_tag_first -- + * The tag code can be entered from main, e.g., "vi -t tag". + * + * PUBLIC: int ex_tag_first __P((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 __P((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 __P((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 __P((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 __P((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 __P((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 __P((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 __P((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 __P((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 __P((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 __P((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 __P((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 __P((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 __P((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)) == (caddr_t)-1) { + 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; + size_t len; + 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'; + len = snprintf(buf, sizeof(buf), "%s/%s", tfp->name, name); + *p = '/'; + if (stat(buf, &sb) == 0) { + *dirp = tfp->name; + *dlenp = strlen(*dirp); + } + } +} + +/* + * 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) + register char *string, *front, *back; +{ + register 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) + register 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); +} |