Logo Search packages:      
Sourcecode: vigor version File versions  Download package

ex_tag.c

/*-
 * 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);
}

Generated by  Doxygen 1.6.0   Back to index