head	1.20;
access;
symbols
	perseant-exfatfs-base-20250801:1.20
	perseant-exfatfs-base-20240630:1.20
	perseant-exfatfs:1.20.0.36
	perseant-exfatfs-base:1.20
	cjep_sun2x:1.20.0.34
	cjep_sun2x-base:1.20
	cjep_staticlib_x-base1:1.20
	cjep_staticlib_x:1.20.0.32
	cjep_staticlib_x-base:1.20
	phil-wifi-20200421:1.20
	phil-wifi-20200411:1.20
	phil-wifi-20200406:1.20
	pgoyette-compat-merge-20190127:1.20
	pgoyette-compat-20190127:1.20
	pgoyette-compat-20190118:1.20
	pgoyette-compat-1226:1.20
	pgoyette-compat-1126:1.20
	pgoyette-compat-1020:1.20
	pgoyette-compat-0930:1.20
	pgoyette-compat-0906:1.20
	pgoyette-compat-0728:1.20
	pgoyette-compat-0625:1.20
	pgoyette-compat-0521:1.20
	pgoyette-compat-0502:1.20
	pgoyette-compat-0422:1.20
	pgoyette-compat-0415:1.20
	pgoyette-compat-0407:1.20
	pgoyette-compat-0330:1.20
	pgoyette-compat-0322:1.20
	pgoyette-compat-0315:1.20
	pgoyette-compat:1.20.0.30
	pgoyette-compat-base:1.20
	perseant-stdc-iso10646:1.20.0.28
	perseant-stdc-iso10646-base:1.20
	prg-localcount2-base3:1.20
	prg-localcount2-base2:1.20
	prg-localcount2-base1:1.20
	prg-localcount2:1.20.0.26
	prg-localcount2-base:1.20
	pgoyette-localcount-20170426:1.20
	bouyer-socketcan-base1:1.20
	pgoyette-localcount-20170320:1.20
	bouyer-socketcan:1.20.0.24
	bouyer-socketcan-base:1.20
	pgoyette-localcount-20170107:1.20
	pgoyette-localcount-20161104:1.20
	localcount-20160914:1.20
	pgoyette-localcount-20160806:1.20
	pgoyette-localcount-20160726:1.20
	pgoyette-localcount:1.20.0.22
	pgoyette-localcount-base:1.20
	netbsd-5-2-3-RELEASE:1.20
	netbsd-5-1-5-RELEASE:1.20
	yamt-pagecache-base9:1.20
	yamt-pagecache-tag8:1.20
	tls-earlyentropy:1.20.0.18
	tls-earlyentropy-base:1.20
	riastradh-xf86-video-intel-2-7-1-pre-2-21-15:1.20
	riastradh-drm2-base3:1.20
	netbsd-5-2-2-RELEASE:1.20
	netbsd-5-1-4-RELEASE:1.20
	netbsd-5-2-1-RELEASE:1.20
	netbsd-5-1-3-RELEASE:1.20
	agc-symver:1.20.0.20
	agc-symver-base:1.20
	tls-maxphys-base:1.20
	yamt-pagecache-base8:1.20
	netbsd-5-2:1.20.0.16
	yamt-pagecache-base7:1.20
	netbsd-5-2-RELEASE:1.20
	netbsd-5-2-RC1:1.20
	yamt-pagecache-base6:1.20
	yamt-pagecache-base5:1.20
	yamt-pagecache-base4:1.20
	netbsd-5-1-2-RELEASE:1.20
	netbsd-5-1-1-RELEASE:1.20
	yamt-pagecache-base3:1.20
	yamt-pagecache-base2:1.20
	yamt-pagecache:1.20.0.14
	yamt-pagecache-base:1.20
	bouyer-quota2-nbase:1.20
	bouyer-quota2:1.20.0.12
	bouyer-quota2-base:1.20
	matt-nb5-pq3:1.20.0.10
	matt-nb5-pq3-base:1.20
	netbsd-5-1:1.20.0.8
	netbsd-5-1-RELEASE:1.20
	netbsd-5-1-RC4:1.20
	netbsd-5-1-RC3:1.20
	netbsd-5-1-RC2:1.20
	netbsd-5-1-RC1:1.20
	netbsd-5-0-2-RELEASE:1.20
	netbsd-5-0-1-RELEASE:1.20
	jym-xensuspend-nbase:1.20
	netbsd-5-0:1.20.0.6
	netbsd-5-0-RELEASE:1.20
	netbsd-5-0-RC4:1.20
	netbsd-5-0-RC3:1.20
	netbsd-5-0-RC2:1.20
	jym-xensuspend:1.20.0.4
	jym-xensuspend-base:1.20
	netbsd-5-0-RC1:1.20
	netbsd-5:1.20.0.2
	netbsd-5-base:1.20
	matt-mips64-base2:1.19
	matt-mips64:1.19.0.26
	mjf-devfs2:1.19.0.24
	mjf-devfs2-base:1.19
	netbsd-4-0-1-RELEASE:1.19
	wrstuden-revivesa-base-3:1.19
	wrstuden-revivesa-base-2:1.19
	wrstuden-fixsa-newbase:1.19
	wrstuden-revivesa-base-1:1.19
	yamt-pf42-base4:1.19
	yamt-pf42-base3:1.19
	hpcarm-cleanup-nbase:1.19
	yamt-pf42-baseX:1.19
	yamt-pf42-base2:1.19
	wrstuden-revivesa:1.19.0.22
	wrstuden-revivesa-base:1.19
	yamt-pf42:1.19.0.20
	yamt-pf42-base:1.19
	keiichi-mipv6-nbase:1.19
	keiichi-mipv6:1.19.0.18
	keiichi-mipv6-base:1.19
	matt-armv6-nbase:1.19
	matt-armv6-prevmlocking:1.19
	wrstuden-fixsa-base-1:1.19
	netbsd-4-0:1.19.0.16
	netbsd-4-0-RELEASE:1.19
	cube-autoconf:1.19.0.14
	cube-autoconf-base:1.19
	netbsd-4-0-RC5:1.19
	netbsd-4-0-RC4:1.19
	netbsd-4-0-RC3:1.19
	netbsd-4-0-RC2:1.19
	netbsd-4-0-RC1:1.19
	matt-armv6:1.19.0.12
	matt-armv6-base:1.19
	matt-mips64-base:1.19
	hpcarm-cleanup:1.19.0.10
	hpcarm-cleanup-base:1.19
	netbsd-3-1-1-RELEASE:1.16.2.1
	netbsd-3-0-3-RELEASE:1.16.2.1
	wrstuden-fixsa:1.19.0.8
	wrstuden-fixsa-base:1.19
	abandoned-netbsd-4-base:1.19
	abandoned-netbsd-4:1.19.0.4
	netbsd-3-1:1.16.2.1.0.4
	netbsd-3-1-RELEASE:1.16.2.1
	netbsd-3-0-2-RELEASE:1.16.2.1
	netbsd-3-1-RC4:1.16.2.1
	netbsd-3-1-RC3:1.16.2.1
	netbsd-3-1-RC2:1.16.2.1
	netbsd-3-1-RC1:1.16.2.1
	netbsd-4:1.19.0.6
	netbsd-4-base:1.19
	chap-midi-nbase:1.19
	netbsd-3-0-1-RELEASE:1.16.2.1
	chap-midi:1.19.0.2
	chap-midi-base:1.19
	netbsd-3-0:1.16.2.1.0.2
	netbsd-3-0-RELEASE:1.16.2.1
	netbsd-3-0-RC6:1.16.2.1
	netbsd-3-0-RC5:1.16.2.1
	netbsd-3-0-RC4:1.16.2.1
	netbsd-3-0-RC3:1.16.2.1
	netbsd-3-0-RC2:1.16.2.1
	netbsd-3-0-RC1:1.16.2.1
	netbsd-2-0-3-RELEASE:1.14.2.2
	netbsd-2-1:1.14.4.1.0.2
	netbsd-2-1-RELEASE:1.14.4.1
	netbsd-2-1-RC6:1.14.4.1
	netbsd-2-1-RC5:1.14.4.1
	netbsd-2-1-RC4:1.14.4.1
	netbsd-2-1-RC3:1.14.4.1
	netbsd-2-1-RC2:1.14.4.1
	netbsd-2-1-RC1:1.14.4.1
	netbsd-2-0-2-RELEASE:1.14.2.2
	netbsd-3:1.16.0.2
	netbsd-3-base:1.16
	netbsd-2-0-1-RELEASE:1.14
	netbsd-2:1.14.0.4
	netbsd-2-base:1.14
	netbsd-2-0-RELEASE:1.14
	netbsd-2-0-RC5:1.14
	netbsd-2-0-RC4:1.14
	netbsd-2-0-RC3:1.14
	netbsd-2-0-RC2:1.14
	netbsd-2-0-RC1:1.14
	netbsd-2-0:1.14.0.2
	netbsd-2-0-base:1.14
	netbsd-1-6-PATCH002-RELEASE:1.13
	netbsd-1-6-PATCH002:1.13
	netbsd-1-6-PATCH002-RC4:1.13
	netbsd-1-6-PATCH002-RC3:1.13
	netbsd-1-6-PATCH002-RC2:1.13
	netbsd-1-6-PATCH002-RC1:1.13
	netbsd-1-6-PATCH001:1.13
	netbsd-1-6-PATCH001-RELEASE:1.13
	netbsd-1-6-PATCH001-RC3:1.13
	netbsd-1-6-PATCH001-RC2:1.13
	netbsd-1-6-PATCH001-RC1:1.13
	fvdl_fs64_base:1.13
	netbsd-1-6-RELEASE:1.13
	netbsd-1-6-RC3:1.13
	netbsd-1-6-RC2:1.13
	netbsd-1-6-RC1:1.13
	netbsd-1-6:1.13.0.2
	netbsd-1-6-base:1.13
	netbsd-1-5-PATCH003:1.10
	netbsd-1-5-PATCH002:1.10
	netbsd-1-5-PATCH001:1.10
	nvi_1_79:1.1.1.6
	netbsd-1-5-RELEASE:1.10
	netbsd-1-5-BETA2:1.10
	netbsd-1-5-BETA:1.10
	netbsd-1-4-PATCH003:1.9.2.1
	netbsd-1-5-ALPHA2:1.10
	netbsd-1-5:1.10.0.4
	netbsd-1-5-base:1.10
	minoura-xpg4dl-base:1.10
	minoura-xpg4dl:1.10.0.2
	netbsd-1-4-PATCH002:1.9.2.1
	wrstuden-devbsize-19991221:1.10
	wrstuden-devbsize:1.9.0.6
	wrstuden-devbsize-base:1.10
	comdex-fall-1999:1.9.0.4
	comdex-fall-1999-base:1.9
	netbsd-1-4-PATCH001:1.9
	netbsd-1-4-RELEASE:1.9
	netbsd-1-4:1.9.0.2
	netbsd-1-4-base:1.9
	netbsd-1-3-PATCH003:1.7
	netbsd-1-3-PATCH003-CANDIDATE2:1.7
	netbsd-1-3-PATCH003-CANDIDATE1:1.7
	netbsd-1-3-PATCH003-CANDIDATE0:1.7
	netbsd-1-3-PATCH002:1.7
	netbsd-1-3-PATCH001:1.7
	netbsd-1-3-RELEASE:1.7
	netbsd-1-3-BETA:1.7
	netbsd-1-3:1.7.0.2
	netbsd-1-3-base:1.7
	netbsd-1-2-PATCH001:1.6
	netbsd-1-2-RELEASE:1.6
	netbsd-1-2-BETA:1.6
	netbsd-1-2:1.6.0.4
	netbsd-1-2-base:1.6
	nvi_1_66:1.1.1.5
	BOSTIC:1.1.1
	netbsd-1-1-PATCH001:1.5
	netbsd-1-1-RELEASE:1.5
	netbsd-1-1:1.5.0.4
	netbsd-1-1-base:1.5
	netbsd-1-0-PATCH06:1.5
	netbsd-1-0-PATCH05:1.5
	netbsd-1-0-PATCH04:1.5
	netbsd-1-0-PATCH03:1.5
	netbsd-1-0-PATCH02:1.5
	netbsd-1-0-PATCH1:1.5
	netbsd-1-0-PATCH0:1.5
	netbsd-1-0-RELEASE:1.5
	netbsd-1-0:1.5.0.2
	nvi-1-34b:1.1.1.4
	nvi-1-33b:1.1.1.3
	netbsd-1-0-base:1.3
	nvi-1-11b:1.1.1.2
	nvi-1-03:1.1.1.1
	bostic-nvi:1.1.1;
locks; strict;
comment	@ * @;


1.20
date	2008.10.29.16.49.37;	author christos;	state dead;
branches;
next	1.19;

1.19
date	2006.05.10.21.53.48;	author mrg;	state Exp;
branches;
next	1.18;

1.18
date	2005.06.02.04.25.16;	author lukem;	state Exp;
branches;
next	1.17;

1.17
date	2005.06.02.03.51.14;	author lukem;	state Exp;
branches;
next	1.16;

1.16
date	2005.02.12.12.53.23;	author aymeric;	state Exp;
branches
	1.16.2.1;
next	1.15;

1.15
date	2004.11.05.19.50.13;	author dsl;	state Exp;
branches;
next	1.14;

1.14
date	2003.09.09.21.03.15;	author erh;	state Exp;
branches
	1.14.2.1
	1.14.4.1;
next	1.13;

1.13
date	2002.04.09.01.47.34;	author thorpej;	state Exp;
branches;
next	1.12;

1.12
date	2001.05.01.16.46.12;	author aymeric;	state Exp;
branches;
next	1.11;

1.11
date	2001.03.31.11.37.50;	author aymeric;	state Exp;
branches;
next	1.10;

1.10
date	99.11.22.05.53.58;	author chopps;	state Exp;
branches;
next	1.9;

1.9
date	99.01.08.06.16.55;	author abs;	state Exp;
branches
	1.9.2.1
	1.9.6.1;
next	1.8;

1.8
date	98.01.09.08.08.06;	author perry;	state Exp;
branches;
next	1.7;

1.7
date	97.04.15.05.39.46;	author mikel;	state Exp;
branches;
next	1.6;

1.6
date	96.05.20.03.48.03;	author mrg;	state Exp;
branches;
next	1.5;

1.5
date	94.08.17.20.13.01;	author cgd;	state Exp;
branches
	1.5.2.1;
next	1.4;

1.4
date	94.08.17.16.36.17;	author cgd;	state Exp;
branches;
next	1.3;

1.3
date	94.03.28.04.29.48;	author cgd;	state Exp;
branches;
next	1.2;

1.2
date	94.01.24.06.40.43;	author cgd;	state Exp;
branches;
next	1.1;

1.1
date	94.01.24.05.53.05;	author cgd;	state Exp;
branches
	1.1.1.1;
next	;

1.16.2.1
date	2005.06.15.05.24.06;	author snj;	state Exp;
branches;
next	;

1.14.2.1
date	2005.02.12.12.24.13;	author aymeric;	state Exp;
branches;
next	1.14.2.2;

1.14.2.2
date	2005.02.12.12.46.26;	author aymeric;	state Exp;
branches;
next	;

1.14.4.1
date	2005.05.06.14.42.16;	author riz;	state Exp;
branches;
next	;

1.9.2.1
date	99.11.27.15.17.31;	author he;	state Exp;
branches;
next	;

1.9.6.1
date	99.12.27.18.37.16;	author wrstuden;	state Exp;
branches;
next	;

1.5.2.1
date	94.08.17.20.13.01;	author cgd;	state dead;
branches;
next	1.5.2.2;

1.5.2.2
date	94.08.17.20.13.02;	author cgd;	state Exp;
branches;
next	;

1.1.1.1
date	94.01.24.05.53.06;	author cgd;	state Exp;
branches;
next	1.1.1.2;

1.1.1.2
date	94.03.28.02.56.05;	author cgd;	state Exp;
branches;
next	1.1.1.3;

1.1.1.3
date	94.08.17.16.17.04;	author cgd;	state Exp;
branches;
next	1.1.1.4;

1.1.1.4
date	94.08.17.19.18.54;	author cgd;	state Exp;
branches;
next	1.1.1.5;

1.1.1.5
date	96.05.20.01.57.21;	author mrg;	state Exp;
branches;
next	1.1.1.6;

1.1.1.6
date	2001.03.31.11.29.49;	author aymeric;	state Exp;
branches;
next	;


desc
@@


1.20
log
@bye old vi!
@
text
@/*	$NetBSD: ex_tag.c,v 1.19 2006/05/10 21:53:48 mrg 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"

#include <sys/cdefs.h>
#ifndef lint
#if 0
static const char sccsid[] = "@@(#)ex_tag.c	10.36 (Berkeley) 9/15/96";
#else
__RCSID("$NetBSD: ex_tag.c,v 1.19 2006/05/10 21:53:48 mrg Exp $");
#endif
#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 *));
#ifdef GTAGS
static int	 getentry __P((char *, char **, char **, char **));
static TAGQ	*gtag_slist __P((SCR *, char *, int));
#endif
static int	 ctag_sfile __P((SCR *, TAGF *, TAGQ *, char *, int));
static TAGQ	*ctag_slist __P((SCR *, char *));
static char	*linear_search __P((char *, char *, char *));
static char	*full_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);
}

#ifdef GTAGS
/*
 * ex_rtag_push -- ^]
 *              :rtag[!] [string]
 *
 * Enter a new TAGQ context based on a ctag string.
 *
 * PUBLIC: int ex_rtag_push __P((SCR *, EXCMD *));
 */
int
ex_rtag_push(sp, cmdp)
	SCR *sp;
	EXCMD *cmdp;
{
	F_SET(cmdp, E_REFERENCE);
	return ex_tag_push(sp, cmdp);
}
#endif

/*
 * 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. */
#ifdef GTAGS
	if (O_ISSET(sp, O_GTAGSMODE)) {
		tqp = gtag_slist(sp, exp->tag_last, F_ISSET(cmdp, E_REFERENCE));
		if (tqp == NULL)
			return (1);
	} else
#endif

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

	dtqp = NULL;	/* XXXGCC -Wuninitialized */

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

	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((unsigned char)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);
}

#ifdef GTAGS
/*
 * getentry --
 *	get tag information from current line.
 *
 * gtags temporary file format.
 * <tag>   <lineno>  <file>         <image>
 *
 * sample.
 * +------------------------------------------------
 * |main     30      main.c         main(argc, argv)
 * |func     21      subr.c         func(arg)
 */
static int
getentry(buf, tag, file, line)
	char *buf, **tag, **file, **line;
{
	char *p = buf;

	for (*tag = p; *p && !isspace((unsigned char)*p); p++)		/* tag name */
		;
	if (*p == 0)
		goto err;
	*p++ = 0;
	for (; *p && isspace((unsigned char)*p); p++)			/* (skip blanks) */
		;
	if (*p == 0)
		goto err;
	*line = p;					/* line no */
	for (*line = p; *p && !isspace((unsigned char)*p); p++)
		;
	if (*p == 0)
		goto err;
	*p++ = 0;
	for (; *p && isspace((unsigned char)*p); p++)			/* (skip blanks) */
		;
	if (*p == 0)
		goto err;
	*file = p;					/* file name */
	for (*file = p; *p && !isspace((unsigned char)*p); p++)
		;
	if (*p == 0)
		goto err;
	*p = 0;

	/* value check */
	if (strlen(*tag) && strlen(*line) && strlen(*file) && atoi(*line) > 0)
		return 1;	/* OK */
err:
	return 0;		/* ERROR */
}

/*
 * gtag_slist --
 *	Search the list of tags files for a tag, and return tag queue.
 */
static TAGQ *
gtag_slist(sp, tag, ref)
	SCR *sp;
	char *tag;
	int ref;
{
	TAGQ *tqp;
	size_t len;
	TAG *tp;
	char *name, *file, *line;
	char command[BUFSIZ];
	char buf[BUFSIZ];
	FILE *fp;

	/* 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.
	 */
	snprintf(command, sizeof(command), "global -%s '%s'", ref ? "rx" : "x", tag);
	if (fp = popen(command, "r")) {
		while (fgets(buf, sizeof(buf), fp)) {
			if (buf[strlen(buf)-1] == '\n')		/* chop(buf) */
				buf[strlen(buf)-1] = 0;
			else
				while (fgetc(fp) != '\n')
					;
			if (getentry(buf, &name, &file, &line) == 0) {
				break;
			}
			CALLOC_GOTO(sp, tp,
			    TAG *, 1, sizeof(TAG) + strlen(file) + 1 + strlen(line) + 1);
			tp->fname = tp->buf;
			strcpy(tp->fname, file);
			tp->fnlen = strlen(file);
			tp->search = tp->fname + tp->fnlen + 1;
			strcpy(tp->search, line);
			CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q);
		}
		pclose(fp);
	}

	/* 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");
		free(tqp);
		return (NULL);
	}

	tqp->current = tqp->tagq.cqh_first;
	return (tqp);

alloc_err:
	return (NULL);
}
#endif

/*
 * 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;
	int slow_search = 0;

	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.
	 */
	do {
		for (echk = 0,
			tfp = exp->tagfq.tqh_first; tfp != NULL; tfp = tfp->q.tqe_next)
			if (ctag_sfile(sp, tfp, tqp, tag, slow_search)) {
				echk = 1;
				F_SET(tfp, TAGF_ERR);
			} else
				F_CLR(tfp, TAGF_ERR | TAGF_ERR_WARN);
			if (slow_search)
				break;
			slow_search = 1;
	} while (CIRCLEQ_EMPTY(&tqp->tagq));

	/* Check to see if we found anything. */
	if (CIRCLEQ_EMPTY(&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, slow_search)
	SCR *sp;
	TAGF *tfp;
	TAGQ *tqp;
	char *tname;
	int slow_search;
{
	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;

	dname = NULL;	/* XXX gcc */

	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;
	if (slow_search)
		front = full_search(tname, map, back);
	else
	{
		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;

		nlen = 0;	/* XXXGCC -Wuninitialized */
		cname = NULL;	/* XXXGCC -Wuninitialized */
		name = NULL;	/* XXXGCC -Wuninitialized */

		/* 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.
 *  o the lines are sorted.
 */
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);
}

/*
 * 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 *
full_search(string, front, back)
	char *string, *front, *back;
{
	while (front < back) {
		if (compare(string, front, back) == EQUAL)
		{
			return (front);
		}
		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);
}
@


1.19
log
@quell GCC 4.1 uninitialised variable warnings.

XXX: we should audit the tree for which old ones are no longer needed
after getting the older compilers out of the tree..
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.18 2005/06/02 04:25:16 lukem Exp $	*/
d22 1
a22 1
__RCSID("$NetBSD: ex_tag.c,v 1.18 2005/06/02 04:25:16 lukem Exp $");
@


1.18
log
@appease gcc -Wuninitialized.
Each is marked with XXXGCC, because in some cases it looks like gcc
isn't correctly detecting that
	for ( ; xxx ; yyy)
		foo = 0;
always sets foo ...
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.17 2005/06/02 03:51:14 lukem Exp $	*/
d22 1
a22 1
__RCSID("$NetBSD: ex_tag.c,v 1.17 2005/06/02 03:51:14 lukem Exp $");
d1215 2
@


1.17
log
@Don't attempt to dereference an uninitialized pointer when an error
occurs in gtags parsing.   For that matter, remove the unused 'tftp'
and 'echk' variable, as they're set but not used afterwards.
Detected with gcc -Wuninitialized.
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.16 2005/02/12 12:53:23 aymeric Exp $	*/
d22 1
a22 1
__RCSID("$NetBSD: ex_tag.c,v 1.16 2005/02/12 12:53:23 aymeric Exp $");
d458 2
d1285 4
@


1.16
log
@Fix the RCSID's to be $NetBSD$ instead of $NetBSD
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.15 2004/11/05 19:50:13 dsl Exp $	*/
d22 1
a22 1
__RCSID("$NetBSD$");
a1072 1
	TAGF *tfp;
a1074 1
	int echk;
a1100 2
				echk = 1;
				F_SET(tfp, TAGF_ERR);
@


1.16.2.1
log
@Pull up revision 1.17 (requested by lukem in ticket #408):
Don't attempt to dereference an uninitialized pointer when an error
occurs in gtags parsing.   For that matter, remove the unused 'tftp'
and 'echk' variable, as they're set but not used afterwards.
Detected with gcc -Wuninitialized.
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.16 2005/02/12 12:53:23 aymeric Exp $	*/
d22 1
a22 1
__RCSID("$NetBSD: ex_tag.c,v 1.16 2005/02/12 12:53:23 aymeric Exp $");
d1073 1
d1076 1
d1103 2
@


1.15
log
@Add (unsigned char) cast to ctype functions
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.14 2003/09/09 21:03:15 erh Exp $	*/
d22 1
a22 1
__RCSID("$NetBSD");
@


1.14
log
@PR# bin/22736:  Allow unsorted tags file to work.  If the initial binary
search fails to find a matching tag in any of the tags files, try a full
linear search.
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.13 2002/04/09 01:47:34 thorpej Exp $	*/
d968 1
a968 1
	if (isdigit(search[0])) {
d1030 1
a1030 1
	for (*tag = p; *p && !isspace(*p); p++)		/* tag name */
d1035 1
a1035 1
	for (; *p && isspace(*p); p++)			/* (skip blanks) */
d1040 1
a1040 1
	for (*line = p; *p && !isspace(*p); p++)
d1045 1
a1045 1
	for (; *p && isspace(*p); p++)			/* (skip blanks) */
d1050 1
a1050 1
	for (*file = p; *p && !isspace(*p); p++)
@


1.14.4.1
log
@Pull up revision 1.16 (requested by aymeric in ticket #1195):
Fix the RCSID's to be $NetBSD$ instead of $NetBSD
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.14 2003/09/09 21:03:15 erh Exp $	*/
d22 1
a22 1
__RCSID("$NetBSD$");
@


1.14.2.1
log
@Fix the RCSID's to be $NetBSD$ instead of $NetBSD
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.14 2003/09/09 21:03:15 erh Exp $	*/
d22 1
a22 1
__RCSID("$NetBSD$");
@


1.14.2.2
log
@Backout previous. Sorry.
@
text
@d22 1
a22 1
__RCSID("$NetBSD");
@


1.13
log
@Use __RCSID() and __COPYRIGHT().
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.12 2001/05/01 16:46:12 aymeric Exp $	*/
d60 1
a60 1
static int	 ctag_sfile __P((SCR *, TAGF *, TAGQ *, char *));
d63 1
d1148 1
d1163 12
a1174 7
	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);
d1177 1
a1177 1
	if (tqp->tagq.cqh_first == (void *)&tqp->tagq) {
d1204 1
a1204 1
ctag_sfile(sp, tfp, tqp, tname)
d1209 1
d1248 7
a1254 2
	front = binary_search(tname, front, back);
	front = linear_search(tname, front, back);
d1454 3
a1456 2
 * 	o front points at the first character in a line.
 *	o front is before or at the first line to be printed.
d1470 25
@


1.12
log
@shutup most (non-intrusive) gcc warnings
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.11 2001/03/31 11:37:50 aymeric Exp $	*/
d17 1
d19 1
d21 3
@


1.11
log
@merge changes after import of nvi 1.79
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.10 1999/11/22 05:53:58 chopps Exp $	*/
d604 1
a604 1
	char *p, *sep;
a1066 1
	EX_PRIVATE *exp;
@


1.10
log
@Don't restore the tag file name's trailing '/' until after the length
is taken.  This fixes relative paths in tags files.
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.9 1999/01/08 06:16:55 abs Exp $	*/
d18 1
a18 1
static const char sccsid[] = "@@(#)ex_tag.c	10.32 (Berkeley) 5/16/96";
d50 1
a50 1
static int	 ctag_search __P((SCR *, char *, char *));
d52 2
a53 2
static int     getentry __P((char *, char **, char **, char **));
static TAGQ   *gtag_slist __P((SCR *, char *, int));
d168 2
a169 1
		if ((tqp = gtag_slist(sp, exp->tag_last, F_ISSET(cmdp, E_REFERENCE))) == NULL)
d173 1
d231 2
a232 1
	(void)ctag_search(sp, tqp->current->search, tqp->tag);
d295 1
a295 1
		(void)ctag_search(sp, tp->search, tqp->tag);
d330 1
a330 1
		(void)ctag_search(sp, tp->search, tqp->tag);
d835 5
d860 2
a861 1
		msgq_str(sp, M_ERR, tag, "164|%s: the tag line doesn't exist");
d905 1
a905 1
				memmove(tfp->name, t, len);
d948 1
a948 1
ctag_search(sp, search, tag)
d951 1
d976 1
a976 1
		    search, NULL, SEARCH_FILE | SEARCH_TAG))
d978 3
a980 4
				p[1] = '\0';
				if (f_search(sp, &m, &m,
				    search, NULL, SEARCH_FILE | SEARCH_TAG)) {
					p[1] = '(';
a981 2
				}
				p[1] = '(';
d1004 1
d1128 1
d1357 1
a1361 1
		*p = '/';
@


1.9
log
@Add Gtags support from shigio@@wafu.netgate.net (PR 5978). Gtags are produced
by global (http://wafu.netgate.net/tama/unix/global.html)
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.8 1998/01/09 08:08:06 perry Exp $	*/
a1347 1
		*p = '/';
d1352 1
@


1.9.6.1
log
@Pull up to last week's -current.
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.10 1999/11/22 05:53:58 chopps Exp $	*/
d1348 1
a1352 1
		*p = '/';
@


1.9.2.1
log
@Pull up revision 1.10 (requested by chopps):
  Fix vi to handle tags files with relative paths in them.
@
text
@d1 1
a1 1
/*	$NetBSD: ex_tag.c,v 1.9 1999/01/08 06:16:55 abs Exp $	*/
d1348 1
a1352 1
		*p = '/';
@


1.8
log
@RCS Id Police.
@
text
@d1 1
a1 1
/*	$NetBSD$	*/
d51 4
d98 19
d166 6
d997 108
d1106 14
@


1.7
log
@be a bit more careful about removing TAGQ from CIRCLEQ; from Ross
  Harvey in PR bin/3489.
@
text
@d1 2
@


1.6
log
@merge in nvi 1.66
@
text
@d801 2
a802 1
	CIRCLEQ_REMOVE(&exp->tq, tqp, q);
@


1.5
log
@clean up import.
@
text
@d4 2
d10 1
a10 27
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
d13 2
d16 1
a16 1
static char sccsid[] = "@@(#)ex_tag.c	8.45 (Berkeley) 8/17/94";
d20 3
d24 2
a34 1
#include <signal.h>
a38 1
#include <termios.h>
d41 2
a42 6
#include "compat.h"
#include <db.h>
#include <regex.h>

#include "vi.h"
#include "excmd.h"
d47 4
d52 5
a56 2
static int	 search __P((SCR *, char *, char *, char **));
static int	 tag_get __P((SCR *, char *, char **, char **, char **));
d59 4
a62 2
 * ex_tagfirst --
 *	The tag code can be entered from main, i.e. "vi -t tag".
d65 1
a65 1
ex_tagfirst(sp, tagarg)
d69 2
a70 10
	FREF *frp;
	MARK m;
	long tl;
	u_int flags;
	int sval;
	char *p, *tag, *name, *search;

	/* Taglength may limit the number of characters. */
	if ((tl = O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(tagarg) > tl)
		tagarg[tl] = '\0';
d72 3
a74 9
	/* Get the tag information. */
	if (tag_get(sp, tagarg, &tag, &name, &search))
		return (1);

	/* Create the file entry. */
	if ((frp = file_add(sp, name)) == NULL)
		return (1);
	if (file_init(sp, frp, NULL, 0))
		return (1);
d77 4
a80 4
	 * !!!
	 * 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.
d82 6
a87 25
	if (isdigit(search[0])) {
		m.lno = atoi(search);
		m.cno = 0;
	} 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;
		flags = SEARCH_FILE | SEARCH_TAG | SEARCH_TERM;
		sval = f_search(sp, sp->ep, &m, &m, search, NULL, &flags);
		if (sval && (p = strrchr(search, '(')) != NULL) {
			p[1] = '\0';
			sval = f_search(sp, sp->ep,
			    &m, &m, search, NULL, &flags);
		}
		if (sval)
			msgq(sp, M_ERR, "%s: search pattern not found", tag);
	}

	/* Set up the screen. */
	frp->lno = m.lno;
	frp->cno = m.cno;
	F_SET(frp, FR_CURSORSET);
a88 5
	/* Might as well make this the default tag. */
	if ((EXP(sp)->tlast = strdup(tagarg)) == NULL) {
		msgq(sp, M_SYSERR, NULL);
		return (1);
	}
a91 13
/* Free a tag or tagf structure from a queue. */
#define	FREETAG(tp) {							\
	TAILQ_REMOVE(&exp->tagq, (tp), q);				\
	if ((tp)->search != NULL)					\
		free((tp)->search);					\
	FREE((tp), sizeof(TAGF));					\
}
#define	FREETAGF(tfp) {							\
	TAILQ_REMOVE(&exp->tagfq, (tfp), q);				\
	free((tfp)->name);						\
	FREE((tfp), sizeof(TAGF));					\
}

d93 2
a94 2
 * ex_tagpush -- :tag [file]
 *	Move to a new tag.
d96 3
a98 8
 * The tags stacks in nvi are a bit tricky.  Each tag contains a file name,
 * search string, and line/column numbers.  The search string is only used
 * for the first access and for user display.  The first record on the stack
 * is the place where we first did a tag, so it has no search string.  The
 * second record is the first tag, and so on.  Note, this means that the
 * "current" tag is always on the stack.  Each tag has a line/column which is
 * the location from which the user tagged the following TAG entry, and which
 * is used as the return location.
d101 1
a101 1
ex_tagpush(sp, ep, cmdp)
d103 1
a103 2
	EXF *ep;
	EXCMDARG *cmdp;
a104 1
	enum {TC_CHANGE, TC_CURRENT} which;
d107 4
a110 4
	MARK m;
	TAG *tp;
	u_int flags;
	int sval;
d112 1
a112 1
	char *name, *p, *search, *tag;
d117 4
a120 3
		if (exp->tlast != NULL)
			FREE(exp->tlast, strlen(exp->tlast) + 1);
		if ((exp->tlast = strdup(cmdp->argv[0]->bp)) == NULL) {
d124 5
d131 2
a132 2
		if (exp->tlast == NULL) {
			msgq(sp, M_ERR, "No previous tag entered");
a139 4
	/* Taglength may limit the number of characters. */
	if ((tl = O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(exp->tlast) > tl)
		exp->tlast[tl] = '\0';

d141 1
a141 1
	if (tag_get(sp, exp->tlast, &tag, &name, &search))
d144 39
a182 9
	/* Get the (possibly new) FREF structure. */
	if ((frp = file_add(sp, name)) == NULL)
		goto err;

	if (sp->frp == frp)
		which = TC_CURRENT;
	else {
		if (file_m1(sp, sp->ep,
		    F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE))
a183 2
		which = TC_CHANGE;
	}
d186 22
a207 6
	 * Get a tag structure -- if this is the first tag, push it on the
	 * stack as a placeholder and get another tag structure.  Set the
	 * line/column of the most recent element on the stack to be the
	 * current values, including the file pointer.  Then push the new
	 * TAG onto the stack with the new file and search string for user
	 * display.
d209 8
a216 4
	CALLOC(sp, tp, TAG *, 1, sizeof(TAG));
	if (tp != NULL && exp->tagq.tqh_first == NULL) {
		TAILQ_INSERT_HEAD(&exp->tagq, tp, q);
		CALLOC(sp, tp, TAG *, 1, sizeof(TAG));
d218 31
a248 4
	if (exp->tagq.tqh_first != NULL) {
		exp->tagq.tqh_first->frp = sp->frp;
		exp->tagq.tqh_first->lno = sp->lno;
		exp->tagq.tqh_first->cno = sp->cno;
d250 3
a252 7
	if (tp != NULL) {
		if ((tp->search = strdup(search)) == NULL)
			msgq(sp, M_SYSERR, NULL);
		else
			tp->slen = strlen(search);
		tp->frp = frp;
		TAILQ_INSERT_HEAD(&exp->tagq, tp, q);
d254 3
d258 29
a286 8
	/* Switch files. */
	if (which == TC_CHANGE && file_init(sp, frp, NULL, 0)) {
		if (tp != NULL)
			FREETAG(tp);
		/* Handle special, first-tag case. */
		if (exp->tagq.tqh_first->q.tqe_next == NULL)
			TAILQ_REMOVE(&exp->tagq, exp->tagq.tqh_first, q);
err:		free(tag);
d289 3
d293 83
a375 42
	/*
	 * !!!
	 * Historic vi accepted a line number as well as a search
	 * string, and people are apparently still using the format.
	 */
	if (isdigit(search[0])) {
		m.lno = atoi(search);
		m.cno = 0;
		sval = 0;
	} 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;
		flags = SEARCH_FILE | SEARCH_TAG | SEARCH_TERM;
		sval = f_search(sp, sp->ep, &m, &m, search, NULL, &flags);
		if (sval && (p = strrchr(search, '(')) != NULL) {
			p[1] = '\0';
			sval = f_search(sp, sp->ep,
			     &m, &m, search, NULL, &flags);
			p[1] = '(';
		}
		if (sval)
			msgq(sp, M_ERR, "%s: search pattern not found", tag);
	}
	free(tag);

	switch (which) {
	case TC_CHANGE:
		frp->lno = m.lno;
		frp->cno = m.cno;
		F_SET(frp, FR_CURSORSET);
		F_SET(sp, S_FSWITCH);
		break;
	case TC_CURRENT:
		if (sval)
			return (1);
		sp->lno = m.lno;
		sp->cno = m.cno;
		break;
d377 12
d393 6
a398 2
 * ex_tagpop -- :tagp[op][!] [number | file]
 *	Pop the tag stack.
d401 1
a401 1
ex_tagpop(sp, ep, cmdp)
d403 1
a403 2
	EXF *ep;
	EXCMDARG *cmdp;
d406 2
a407 1
	TAG *ntp, *tp;
a408 1
	size_t arglen;
d413 2
a414 2
	if (exp->tagq.tqh_first == NULL) {
		msgq(sp, M_INFO, "The tags stack is empty");
d418 1
d421 1
a421 1
		ntp = exp->tagq.tqh_first;
d426 38
a463 31
		if (*p == '\0') {
			if (off < 1)
				return (0);
			for (tp = exp->tagq.tqh_first;
			    tp != NULL && --off > 1; tp = tp->q.tqe_next);
			if (tp == NULL) {
				msgq(sp, M_ERR,
"Less than %s entries on the tags stack; use :display to see the tags stack",
				    arg);
				return (1);
			}
			ntp = tp;
		} else {
			arglen = strlen(arg);
			for (tp = exp->tagq.tqh_first;
			    tp != NULL; ntp = tp, tp = tp->q.tqe_next) {
				/* Use the user's original file name. */
				p = tp->frp->name;
				if ((t = strrchr(p, '/')) == NULL)
					t = p;
				else
					++t;
				if (!strncmp(arg, t, arglen))
					break;
			}
			if (tp == NULL) {
				msgq(sp, M_ERR,
"No file named %s on the tags stack; use :display to see the tags stack",
				    arg);
				return (1);
			}
d465 2
d472 2
a473 11
	/* Update the cursor from the saved TAG information. */
	tp = ntp->q.tqe_next;
	if (tp->frp == sp->frp) {
		sp->lno = tp->lno;
		sp->cno = tp->cno;
	} else {
		if (file_m1(sp, ep,
		    F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE))
			return (1);
		if (file_init(sp, tp->frp, NULL, 0))
			return (1);
d475 12
a486 3
		tp->frp->lno = tp->lno;
		tp->frp->cno = tp->cno;
		F_SET(sp->frp, FR_CURSORSET);
d488 1
a488 2
		F_SET(sp, S_FSWITCH);
	}
d490 4
a493 6
	/* Pop entries off the queue up to ntp. */
	for (;;) {
		tp = exp->tagq.tqh_first;
		FREETAG(tp);
		if (tp == ntp)
			break;
d496 3
a498 4
	/* If returning to the first tag, the stack is now empty. */
	if (exp->tagq.tqh_first->q.tqe_next == NULL)
		TAILQ_REMOVE(&exp->tagq, exp->tagq.tqh_first, q);
	return (0);
d502 2
a503 2
 * ex_tagtop -- :tagt[op][!]
 *	Clear the tag stack.
d505 2
a506 2
int
ex_tagtop(sp, ep, cmdp)
d508 2
a509 2
	EXF *ep;
	EXCMDARG *cmdp;
d513 1
a514 1
	/* Find oldest saved information. */
a515 6
	for (tp = exp->tagq.tqh_first;
	    tp != NULL && tp->q.tqe_next != NULL; tp = tp->q.tqe_next);
	if (tp == NULL) {
		msgq(sp, M_INFO, "The tags stack is empty");
		return (1);
	}
d517 5
a521 1
	/* If not switching files, it's easy; else do the work. */
d526 1
a526 4
		if (file_m1(sp, sp->ep,
		    F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE))
			return (1);
		if (file_init(sp, tp->frp, NULL, 0))
d531 3
d535 1
a535 2
		F_SET(sp->frp, FR_CURSORSET);
		F_SET(sp, S_FSWITCH);
d538 14
a551 3
	/* Empty out the queue. */
	while ((tp = exp->tagq.tqh_first) != NULL)
		FREETAG(tp);
d556 1
a556 1
 * ex_tagdisplay --
d558 2
d562 1
a562 1
ex_tagdisplay(sp, ep)
a563 1
	EXF *ep;
d567 1
a567 1
	size_t len, maxlen;
d569 2
a570 1
	char *name;
d573 21
a593 2
	if ((tp = exp->tagq.tqh_first) == NULL) {
		(void)ex_printf(EXCOOKIE, "No tags to display.\n");
d598 2
a599 2
	 * Figure out the formatting.  MNOC is the maximum
	 * number of file name columns before we split the line.
d601 6
a606 15
#define	MNOC	15
	for (maxlen = 0,
	    tp = exp->tagq.tqh_first; tp != NULL; tp = tp->q.tqe_next) {
		len = strlen(name = tp->frp->name);	/* The original name. */
		if (maxlen < len && len < MNOC)
			maxlen = len;
	}

	for (cnt = 1, tp = exp->tagq.tqh_first; tp != NULL;
	    ++cnt, tp = tp->q.tqe_next) {
		len = strlen(name = tp->frp->name);	/* The original name. */
		if (len > maxlen || len + tp->slen > sp->cols)
			if (tp == NULL || tp->search == NULL)
				(void)ex_printf(EXCOOKIE,
				    "%2d %s\n", cnt, name);
d608 192
a799 11
				(void)ex_printf(EXCOOKIE,
				     "%2d %s\n** %*.*s %s\n", cnt, name,
				     (int)maxlen, (int)maxlen, "", tp->search);
		else
			if (tp == NULL || tp->search == NULL)
				(void)ex_printf(EXCOOKIE, "%2d %*.*s\n",
				    cnt, (int)maxlen, (int)len, name);
			else
				(void)ex_printf(EXCOOKIE, "%2d %*.*s %s\n",
				    cnt, (int)maxlen, (int)len, name,
				    tp->search);
d801 2
d807 31
a837 2
 * ex_tagalloc --
 *	Create a new list of tag files.
d840 1
a840 1
ex_tagalloc(sp, str)
d845 1
a845 1
	TAGF *tp;
d851 2
a852 2
	while ((tp = exp->tagfq.tqh_first) != NULL)
		FREETAGF(tp);
d858 4
a861 4
				MALLOC_RET(sp, tp, TAGF *, sizeof(TAGF));
				MALLOC(sp, tp->name, char *, len + 1);
				if (tp->name == NULL) {
					FREE(tp, sizeof(TAGF));
d864 4
a867 4
				memmove(tp->name, t, len);
				tp->name[len] = '\0';
				tp->flags = 0;
				TAILQ_INSERT_TAIL(&exp->tagfq, tp, q);
d878 4
a881 2
 * ex_tagfree --
 *	Free the tags file list.
d884 1
a884 1
ex_tagfree(sp)
a887 1
	TAG *tp;
d889 1
d893 2
a894 2
	while ((tp = exp->tagq.tqh_first) != NULL)
		FREETAG(tp);
d896 3
a898 3
		FREETAGF(tfp);
	if (exp->tlast != NULL)
		free(exp->tlast);
d903 2
a904 2
 * ex_tagcopy --
 *	Copy a screen's tag structures.
d906 4
a909 3
int
ex_tagcopy(orig, sp)
	SCR *orig, *sp;
d911 2
a912 3
	EX_PRIVATE *oexp, *nexp;
	TAG *ap, *tp;
	TAGF *atfp, *tfp;
d914 40
a953 12
	/* Copy tag stack. */
	oexp = EXP(orig);
	nexp = EXP(sp);
	for (ap = oexp->tagq.tqh_first; ap != NULL; ap = ap->q.tqe_next) {
		MALLOC(sp, tp, TAG *, sizeof(TAG));
		if (tp == NULL)
			goto nomem;
		*tp = *ap;
		if (ap->search != NULL &&
		    (tp->search = strdup(ap->search)) == NULL)
			goto nomem;
		TAILQ_INSERT_TAIL(&nexp->tagq, tp, q);
d956 7
a962 18
	/* Copy list of tag files. */
	for (atfp = oexp->tagfq.tqh_first;
	    atfp != NULL; atfp = atfp->q.tqe_next) {
		MALLOC(sp, tfp, TAGF *, sizeof(TAGF));
		if (tfp == NULL)
			goto nomem;
		*tfp = *atfp;
		if ((tfp->name = strdup(atfp->name)) == NULL)
			goto nomem;
		TAILQ_INSERT_TAIL(&nexp->tagfq, tfp, q);
	}

	/* Copy the last tag. */
	if (oexp->tlast != NULL &&
	    (nexp->tlast = strdup(oexp->tlast)) == NULL) {
nomem:		msgq(sp, M_SYSERR, NULL);
		return (1);
	}
d967 2
a968 2
 * tag_get --
 *	Get a tag from the tags files.
d970 2
a971 2
static int
tag_get(sp, tag, tagp, filep, searchp)
d973 1
a973 1
	char *tag, **tagp, **filep, **searchp;
a974 1
	struct stat sb;
d977 12
a988 3
	size_t plen, slen, tlen;
	int dne;
	char *p, pbuf[MAXPATHLEN];
d994 12
a1005 22
	dne = 0;
	exp = EXP(sp);
	for (p = NULL, tfp = exp->tagfq.tqh_first;
	    tfp != NULL && p == NULL; tfp = tfp->q.tqe_next) {
		errno = 0;
		F_CLR(tfp, TAGF_DNE);
		if (search(sp, tfp->name, tag, &p))
			if (errno == ENOENT) {
				if (!F_ISSET(tfp, TAGF_DNE_WARN)) {
					dne = 1;
					F_SET(tfp, TAGF_DNE);
				}
			} else
				msgq(sp, M_SYSERR, tfp->name);
		else
			if (p != NULL)
				break;
	}

	if (p == NULL) {
		msgq(sp, M_ERR, "%s: tag not found", tag);
		if (dne)
d1008 5
a1012 4
				if (F_ISSET(tfp, TAGF_DNE)) {
					errno = ENOENT;
					msgq(sp, M_SYSERR, tfp->name);
					F_SET(tfp, TAGF_DNE_WARN);
d1014 2
a1015 1
		return (1);
d1018 2
a1019 19
	/*
	 * Set the return pointers; tagp points to the tag, and, incidentally
	 * the allocated string, filep points to the file name, and searchp
	 * points to the search string.  All three are nul-terminated.
	 */
	for (*tagp = p; *p && !isblank(*p); ++p);
	if (*p == '\0')
		goto malformed;
	for (*p++ = '\0'; isblank(*p); ++p);
	for (*filep = p; *p && !isblank(*p); ++p);
	if (*p == '\0')
		goto malformed;
	for (*p++ = '\0'; isblank(*p); ++p);
	*searchp = p;
	if (*p == '\0') {
malformed:	free(*tagp);
		msgq(sp, M_ERR, "%s: corrupted tag in %s", tag, tfp->name);
		return (1);
	}
d1021 2
a1022 32
	/*
	 * !!!
	 * 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 do it right.
	 */
	if ((*filep)[0] != '/'
	    && stat(*filep, &sb) && (p = strrchr(tfp->name, '/')) != NULL) {
		*p = '\0';
		plen = snprintf(pbuf, sizeof(pbuf), "%s/%s", tfp->name, *filep);
		*p = '/';
		if (stat(pbuf, &sb) == 0) {
			slen = strlen(*searchp);
			tlen = strlen(*tagp);
			MALLOC(sp, p, char *, plen + slen + tlen + 5);
			if (p != NULL) {
				memmove(p, *tagp, tlen);
				free(*tagp);
				*tagp = p;
				*(p += tlen) = '\0';
				memmove(++p, pbuf, plen);
				*filep = p;
				*(p += plen) = '\0';
				memmove(++p, *searchp, slen);
				*searchp = p;
				*(p += slen) = '\0';
			}
		}
	}
	return (0);
a1024 4
#define	EQUAL		0
#define	GREATER		1
#define	LESS		(-1)

d1026 2
a1027 2
 * search --
 *	Search a file for a tag.
d1030 1
a1030 1
search(sp, name, tname, tag)
d1032 3
a1034 1
	char *name, *tname, **tag;
d1037 4
a1040 2
	int fd, len;
	char *endp, *back, *front, *map, *p;
d1042 2
a1043 1
	if ((fd = open(name, O_RDONLY, 0)) < 0)
d1045 1
d1049 8
d1061 1
a1061 2
	 * is flatly impossible.  Hope that malloc fails if the file is too
	 * large.
d1063 4
a1066 2
	if (fstat(fd, &sb) || (map = mmap(NULL, (size_t)sb.st_size,
	    PROT_READ, MAP_PRIVATE, fd, (off_t)0)) == (caddr_t)-1) {
d1070 1
a1072 1

d1075 64
d1140 2
a1141 4
	if (front == NULL || (endp = strchr(front, '\n')) == NULL) {
		*tag = NULL;
		goto done;
	}
d1143 13
a1155 5
	len = endp - front;
	MALLOC(sp, p, char *, len + 1);
	if (p == NULL) {
		*tag = NULL;
		goto done;
a1156 3
	memmove(p, front, len);
	p[len] = '\0';
	*tag = p;
d1158 1
d1167 36
d1240 4
@


1.5.2.1
log
@file ex_tag.c was added on branch netbsd-1-0 on 1994-08-17 20:13:02 +0000
@
text
@d1 905
@


1.5.2.2
log
@clean up import.
@
text
@a0 905
/*-
 * Copyright (c) 1992, 1993, 1994
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * David Hitz of Auspex Systems, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
static char sccsid[] = "@@(#)ex_tag.c	8.45 (Berkeley) 8/17/94";
#endif /* not lint */

#include <sys/param.h>
#include <sys/mman.h>
#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 <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#include "compat.h"
#include <db.h>
#include <regex.h>

#include "vi.h"
#include "excmd.h"
#include "tag.h"

static char	*binary_search __P((char *, char *, char *));
static int	 compare __P((char *, char *, char *));
static char	*linear_search __P((char *, char *, char *));
static int	 search __P((SCR *, char *, char *, char **));
static int	 tag_get __P((SCR *, char *, char **, char **, char **));

/*
 * ex_tagfirst --
 *	The tag code can be entered from main, i.e. "vi -t tag".
 */
int
ex_tagfirst(sp, tagarg)
	SCR *sp;
	char *tagarg;
{
	FREF *frp;
	MARK m;
	long tl;
	u_int flags;
	int sval;
	char *p, *tag, *name, *search;

	/* Taglength may limit the number of characters. */
	if ((tl = O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(tagarg) > tl)
		tagarg[tl] = '\0';

	/* Get the tag information. */
	if (tag_get(sp, tagarg, &tag, &name, &search))
		return (1);

	/* Create the file entry. */
	if ((frp = file_add(sp, name)) == NULL)
		return (1);
	if (file_init(sp, frp, NULL, 0))
		return (1);

	/*
	 * !!!
	 * 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.
	 */
	if (isdigit(search[0])) {
		m.lno = atoi(search);
		m.cno = 0;
	} 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;
		flags = SEARCH_FILE | SEARCH_TAG | SEARCH_TERM;
		sval = f_search(sp, sp->ep, &m, &m, search, NULL, &flags);
		if (sval && (p = strrchr(search, '(')) != NULL) {
			p[1] = '\0';
			sval = f_search(sp, sp->ep,
			    &m, &m, search, NULL, &flags);
		}
		if (sval)
			msgq(sp, M_ERR, "%s: search pattern not found", tag);
	}

	/* Set up the screen. */
	frp->lno = m.lno;
	frp->cno = m.cno;
	F_SET(frp, FR_CURSORSET);

	/* Might as well make this the default tag. */
	if ((EXP(sp)->tlast = strdup(tagarg)) == NULL) {
		msgq(sp, M_SYSERR, NULL);
		return (1);
	}
	return (0);
}

/* Free a tag or tagf structure from a queue. */
#define	FREETAG(tp) {							\
	TAILQ_REMOVE(&exp->tagq, (tp), q);				\
	if ((tp)->search != NULL)					\
		free((tp)->search);					\
	FREE((tp), sizeof(TAGF));					\
}
#define	FREETAGF(tfp) {							\
	TAILQ_REMOVE(&exp->tagfq, (tfp), q);				\
	free((tfp)->name);						\
	FREE((tfp), sizeof(TAGF));					\
}

/*
 * ex_tagpush -- :tag [file]
 *	Move to a new tag.
 *
 * The tags stacks in nvi are a bit tricky.  Each tag contains a file name,
 * search string, and line/column numbers.  The search string is only used
 * for the first access and for user display.  The first record on the stack
 * is the place where we first did a tag, so it has no search string.  The
 * second record is the first tag, and so on.  Note, this means that the
 * "current" tag is always on the stack.  Each tag has a line/column which is
 * the location from which the user tagged the following TAG entry, and which
 * is used as the return location.
 */
int
ex_tagpush(sp, ep, cmdp)
	SCR *sp;
	EXF *ep;
	EXCMDARG *cmdp;
{
	enum {TC_CHANGE, TC_CURRENT} which;
	EX_PRIVATE *exp;
	FREF *frp;
	MARK m;
	TAG *tp;
	u_int flags;
	int sval;
	long tl;
	char *name, *p, *search, *tag;

	exp = EXP(sp);
	switch (cmdp->argc) {
	case 1:
		if (exp->tlast != NULL)
			FREE(exp->tlast, strlen(exp->tlast) + 1);
		if ((exp->tlast = strdup(cmdp->argv[0]->bp)) == NULL) {
			msgq(sp, M_SYSERR, NULL);
			return (1);
		}
		break;
	case 0:
		if (exp->tlast == NULL) {
			msgq(sp, M_ERR, "No previous tag entered");
			return (1);
		}
		break;
	default:
		abort();
	}

	/* Taglength may limit the number of characters. */
	if ((tl = O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(exp->tlast) > tl)
		exp->tlast[tl] = '\0';

	/* Get the tag information. */
	if (tag_get(sp, exp->tlast, &tag, &name, &search))
		return (1);

	/* Get the (possibly new) FREF structure. */
	if ((frp = file_add(sp, name)) == NULL)
		goto err;

	if (sp->frp == frp)
		which = TC_CURRENT;
	else {
		if (file_m1(sp, sp->ep,
		    F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE))
			goto err;
		which = TC_CHANGE;
	}

	/*
	 * Get a tag structure -- if this is the first tag, push it on the
	 * stack as a placeholder and get another tag structure.  Set the
	 * line/column of the most recent element on the stack to be the
	 * current values, including the file pointer.  Then push the new
	 * TAG onto the stack with the new file and search string for user
	 * display.
	 */
	CALLOC(sp, tp, TAG *, 1, sizeof(TAG));
	if (tp != NULL && exp->tagq.tqh_first == NULL) {
		TAILQ_INSERT_HEAD(&exp->tagq, tp, q);
		CALLOC(sp, tp, TAG *, 1, sizeof(TAG));
	}
	if (exp->tagq.tqh_first != NULL) {
		exp->tagq.tqh_first->frp = sp->frp;
		exp->tagq.tqh_first->lno = sp->lno;
		exp->tagq.tqh_first->cno = sp->cno;
	}
	if (tp != NULL) {
		if ((tp->search = strdup(search)) == NULL)
			msgq(sp, M_SYSERR, NULL);
		else
			tp->slen = strlen(search);
		tp->frp = frp;
		TAILQ_INSERT_HEAD(&exp->tagq, tp, q);
	}

	/* Switch files. */
	if (which == TC_CHANGE && file_init(sp, frp, NULL, 0)) {
		if (tp != NULL)
			FREETAG(tp);
		/* Handle special, first-tag case. */
		if (exp->tagq.tqh_first->q.tqe_next == NULL)
			TAILQ_REMOVE(&exp->tagq, exp->tagq.tqh_first, q);
err:		free(tag);
		return (1);
	}

	/*
	 * !!!
	 * Historic vi accepted a line number as well as a search
	 * string, and people are apparently still using the format.
	 */
	if (isdigit(search[0])) {
		m.lno = atoi(search);
		m.cno = 0;
		sval = 0;
	} 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;
		flags = SEARCH_FILE | SEARCH_TAG | SEARCH_TERM;
		sval = f_search(sp, sp->ep, &m, &m, search, NULL, &flags);
		if (sval && (p = strrchr(search, '(')) != NULL) {
			p[1] = '\0';
			sval = f_search(sp, sp->ep,
			     &m, &m, search, NULL, &flags);
			p[1] = '(';
		}
		if (sval)
			msgq(sp, M_ERR, "%s: search pattern not found", tag);
	}
	free(tag);

	switch (which) {
	case TC_CHANGE:
		frp->lno = m.lno;
		frp->cno = m.cno;
		F_SET(frp, FR_CURSORSET);
		F_SET(sp, S_FSWITCH);
		break;
	case TC_CURRENT:
		if (sval)
			return (1);
		sp->lno = m.lno;
		sp->cno = m.cno;
		break;
	}
	return (0);
}

/*
 * ex_tagpop -- :tagp[op][!] [number | file]
 *	Pop the tag stack.
 */
int
ex_tagpop(sp, ep, cmdp)
	SCR *sp;
	EXF *ep;
	EXCMDARG *cmdp;
{
	EX_PRIVATE *exp;
	TAG *ntp, *tp;
	long off;
	size_t arglen;
	char *arg, *p, *t;

	/* Check for an empty stack. */
	exp = EXP(sp);
	if (exp->tagq.tqh_first == NULL) {
		msgq(sp, M_INFO, "The tags stack is empty");
		return (1);
	}

	switch (cmdp->argc) {
	case 0:				/* Pop one tag. */
		ntp = exp->tagq.tqh_first;
		break;
	case 1:				/* Name or number. */
		arg = cmdp->argv[0]->bp;
		off = strtol(arg, &p, 10);
		if (*p == '\0') {
			if (off < 1)
				return (0);
			for (tp = exp->tagq.tqh_first;
			    tp != NULL && --off > 1; tp = tp->q.tqe_next);
			if (tp == NULL) {
				msgq(sp, M_ERR,
"Less than %s entries on the tags stack; use :display to see the tags stack",
				    arg);
				return (1);
			}
			ntp = tp;
		} else {
			arglen = strlen(arg);
			for (tp = exp->tagq.tqh_first;
			    tp != NULL; ntp = tp, tp = tp->q.tqe_next) {
				/* Use the user's original file name. */
				p = tp->frp->name;
				if ((t = strrchr(p, '/')) == NULL)
					t = p;
				else
					++t;
				if (!strncmp(arg, t, arglen))
					break;
			}
			if (tp == NULL) {
				msgq(sp, M_ERR,
"No file named %s on the tags stack; use :display to see the tags stack",
				    arg);
				return (1);
			}
		}
		break;
	default:
		abort();
	}

	/* Update the cursor from the saved TAG information. */
	tp = ntp->q.tqe_next;
	if (tp->frp == sp->frp) {
		sp->lno = tp->lno;
		sp->cno = tp->cno;
	} else {
		if (file_m1(sp, ep,
		    F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE))
			return (1);
		if (file_init(sp, tp->frp, NULL, 0))
			return (1);

		tp->frp->lno = tp->lno;
		tp->frp->cno = tp->cno;
		F_SET(sp->frp, FR_CURSORSET);

		F_SET(sp, S_FSWITCH);
	}

	/* Pop entries off the queue up to ntp. */
	for (;;) {
		tp = exp->tagq.tqh_first;
		FREETAG(tp);
		if (tp == ntp)
			break;
	}

	/* If returning to the first tag, the stack is now empty. */
	if (exp->tagq.tqh_first->q.tqe_next == NULL)
		TAILQ_REMOVE(&exp->tagq, exp->tagq.tqh_first, q);
	return (0);
}

/*
 * ex_tagtop -- :tagt[op][!]
 *	Clear the tag stack.
 */
int
ex_tagtop(sp, ep, cmdp)
	SCR *sp;
	EXF *ep;
	EXCMDARG *cmdp;
{
	EX_PRIVATE *exp;
	TAG *tp;

	/* Find oldest saved information. */
	exp = EXP(sp);
	for (tp = exp->tagq.tqh_first;
	    tp != NULL && tp->q.tqe_next != NULL; tp = tp->q.tqe_next);
	if (tp == NULL) {
		msgq(sp, M_INFO, "The tags stack is empty");
		return (1);
	}

	/* If not switching files, it's easy; else do the work. */
	if (tp->frp == sp->frp) {
		sp->lno = tp->lno;
		sp->cno = tp->cno;
	} else {
		if (file_m1(sp, sp->ep,
		    F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE))
			return (1);
		if (file_init(sp, tp->frp, NULL, 0))
			return (1);

		tp->frp->lno = tp->lno;
		tp->frp->cno = tp->cno;

		F_SET(sp->frp, FR_CURSORSET);
		F_SET(sp, S_FSWITCH);
	}

	/* Empty out the queue. */
	while ((tp = exp->tagq.tqh_first) != NULL)
		FREETAG(tp);
	return (0);
}

/*
 * ex_tagdisplay --
 *	Display the list of tags.
 */
int
ex_tagdisplay(sp, ep)
	SCR *sp;
	EXF *ep;
{
	EX_PRIVATE *exp;
	TAG *tp;
	size_t len, maxlen;
	int cnt;
	char *name;

	exp = EXP(sp);
	if ((tp = exp->tagq.tqh_first) == NULL) {
		(void)ex_printf(EXCOOKIE, "No tags to display.\n");
		return (0);
	}

	/*
	 * Figure out the formatting.  MNOC is the maximum
	 * number of file name columns before we split the line.
	 */
#define	MNOC	15
	for (maxlen = 0,
	    tp = exp->tagq.tqh_first; tp != NULL; tp = tp->q.tqe_next) {
		len = strlen(name = tp->frp->name);	/* The original name. */
		if (maxlen < len && len < MNOC)
			maxlen = len;
	}

	for (cnt = 1, tp = exp->tagq.tqh_first; tp != NULL;
	    ++cnt, tp = tp->q.tqe_next) {
		len = strlen(name = tp->frp->name);	/* The original name. */
		if (len > maxlen || len + tp->slen > sp->cols)
			if (tp == NULL || tp->search == NULL)
				(void)ex_printf(EXCOOKIE,
				    "%2d %s\n", cnt, name);
			else
				(void)ex_printf(EXCOOKIE,
				     "%2d %s\n** %*.*s %s\n", cnt, name,
				     (int)maxlen, (int)maxlen, "", tp->search);
		else
			if (tp == NULL || tp->search == NULL)
				(void)ex_printf(EXCOOKIE, "%2d %*.*s\n",
				    cnt, (int)maxlen, (int)len, name);
			else
				(void)ex_printf(EXCOOKIE, "%2d %*.*s %s\n",
				    cnt, (int)maxlen, (int)len, name,
				    tp->search);
	}
	return (0);
}

/*
 * ex_tagalloc --
 *	Create a new list of tag files.
 */
int
ex_tagalloc(sp, str)
	SCR *sp;
	char *str;
{
	EX_PRIVATE *exp;
	TAGF *tp;
	size_t len;
	char *p, *t;

	/* Free current queue. */
	exp = EXP(sp);
	while ((tp = exp->tagfq.tqh_first) != NULL)
		FREETAGF(tp);

	/* Create new queue. */
	for (p = t = str;; ++p) {
		if (*p == '\0' || isblank(*p)) {
			if ((len = p - t) > 1) {
				MALLOC_RET(sp, tp, TAGF *, sizeof(TAGF));
				MALLOC(sp, tp->name, char *, len + 1);
				if (tp->name == NULL) {
					FREE(tp, sizeof(TAGF));
					return (1);
				}
				memmove(tp->name, t, len);
				tp->name[len] = '\0';
				tp->flags = 0;
				TAILQ_INSERT_TAIL(&exp->tagfq, tp, q);
			}
			t = p + 1;
		}
		if (*p == '\0')
			 break;
	}
	return (0);
}
						/* Free previous queue. */
/*
 * ex_tagfree --
 *	Free the tags file list.
 */
int
ex_tagfree(sp)
	SCR *sp;
{
	EX_PRIVATE *exp;
	TAG *tp;
	TAGF *tfp;

	/* Free up tag information. */
	exp = EXP(sp);
	while ((tp = exp->tagq.tqh_first) != NULL)
		FREETAG(tp);
	while ((tfp = exp->tagfq.tqh_first) != NULL)
		FREETAGF(tfp);
	if (exp->tlast != NULL)
		free(exp->tlast);
	return (0);
}

/*
 * ex_tagcopy --
 *	Copy a screen's tag structures.
 */
int
ex_tagcopy(orig, sp)
	SCR *orig, *sp;
{
	EX_PRIVATE *oexp, *nexp;
	TAG *ap, *tp;
	TAGF *atfp, *tfp;

	/* Copy tag stack. */
	oexp = EXP(orig);
	nexp = EXP(sp);
	for (ap = oexp->tagq.tqh_first; ap != NULL; ap = ap->q.tqe_next) {
		MALLOC(sp, tp, TAG *, sizeof(TAG));
		if (tp == NULL)
			goto nomem;
		*tp = *ap;
		if (ap->search != NULL &&
		    (tp->search = strdup(ap->search)) == NULL)
			goto nomem;
		TAILQ_INSERT_TAIL(&nexp->tagq, tp, q);
	}

	/* Copy list of tag files. */
	for (atfp = oexp->tagfq.tqh_first;
	    atfp != NULL; atfp = atfp->q.tqe_next) {
		MALLOC(sp, tfp, TAGF *, sizeof(TAGF));
		if (tfp == NULL)
			goto nomem;
		*tfp = *atfp;
		if ((tfp->name = strdup(atfp->name)) == NULL)
			goto nomem;
		TAILQ_INSERT_TAIL(&nexp->tagfq, tfp, q);
	}

	/* Copy the last tag. */
	if (oexp->tlast != NULL &&
	    (nexp->tlast = strdup(oexp->tlast)) == NULL) {
nomem:		msgq(sp, M_SYSERR, NULL);
		return (1);
	}
	return (0);
}

/*
 * tag_get --
 *	Get a tag from the tags files.
 */
static int
tag_get(sp, tag, tagp, filep, searchp)
	SCR *sp;
	char *tag, **tagp, **filep, **searchp;
{
	struct stat sb;
	EX_PRIVATE *exp;
	TAGF *tfp;
	size_t plen, slen, tlen;
	int dne;
	char *p, pbuf[MAXPATHLEN];

	/*
	 * Find the tag, only display missing file messages once, and
	 * then only if we didn't find the tag.
	 */
	dne = 0;
	exp = EXP(sp);
	for (p = NULL, tfp = exp->tagfq.tqh_first;
	    tfp != NULL && p == NULL; tfp = tfp->q.tqe_next) {
		errno = 0;
		F_CLR(tfp, TAGF_DNE);
		if (search(sp, tfp->name, tag, &p))
			if (errno == ENOENT) {
				if (!F_ISSET(tfp, TAGF_DNE_WARN)) {
					dne = 1;
					F_SET(tfp, TAGF_DNE);
				}
			} else
				msgq(sp, M_SYSERR, tfp->name);
		else
			if (p != NULL)
				break;
	}

	if (p == NULL) {
		msgq(sp, M_ERR, "%s: tag not found", tag);
		if (dne)
			for (tfp = exp->tagfq.tqh_first;
			    tfp != NULL; tfp = tfp->q.tqe_next)
				if (F_ISSET(tfp, TAGF_DNE)) {
					errno = ENOENT;
					msgq(sp, M_SYSERR, tfp->name);
					F_SET(tfp, TAGF_DNE_WARN);
				}
		return (1);
	}

	/*
	 * Set the return pointers; tagp points to the tag, and, incidentally
	 * the allocated string, filep points to the file name, and searchp
	 * points to the search string.  All three are nul-terminated.
	 */
	for (*tagp = p; *p && !isblank(*p); ++p);
	if (*p == '\0')
		goto malformed;
	for (*p++ = '\0'; isblank(*p); ++p);
	for (*filep = p; *p && !isblank(*p); ++p);
	if (*p == '\0')
		goto malformed;
	for (*p++ = '\0'; isblank(*p); ++p);
	*searchp = p;
	if (*p == '\0') {
malformed:	free(*tagp);
		msgq(sp, M_ERR, "%s: corrupted tag in %s", tag, tfp->name);
		return (1);
	}

	/*
	 * !!!
	 * 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 do it right.
	 */
	if ((*filep)[0] != '/'
	    && stat(*filep, &sb) && (p = strrchr(tfp->name, '/')) != NULL) {
		*p = '\0';
		plen = snprintf(pbuf, sizeof(pbuf), "%s/%s", tfp->name, *filep);
		*p = '/';
		if (stat(pbuf, &sb) == 0) {
			slen = strlen(*searchp);
			tlen = strlen(*tagp);
			MALLOC(sp, p, char *, plen + slen + tlen + 5);
			if (p != NULL) {
				memmove(p, *tagp, tlen);
				free(*tagp);
				*tagp = p;
				*(p += tlen) = '\0';
				memmove(++p, pbuf, plen);
				*filep = p;
				*(p += plen) = '\0';
				memmove(++p, *searchp, slen);
				*searchp = p;
				*(p += slen) = '\0';
			}
		}
	}
	return (0);
}

#define	EQUAL		0
#define	GREATER		1
#define	LESS		(-1)

/*
 * search --
 *	Search a file for a tag.
 */
static int
search(sp, name, tname, tag)
	SCR *sp;
	char *name, *tname, **tag;
{
	struct stat sb;
	int fd, len;
	char *endp, *back, *front, *map, *p;

	if ((fd = open(name, O_RDONLY, 0)) < 0)
		return (1);

	/*
	 * 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 that malloc fails if the file is too
	 * large.
	 */
	if (fstat(fd, &sb) || (map = mmap(NULL, (size_t)sb.st_size,
	    PROT_READ, MAP_PRIVATE, fd, (off_t)0)) == (caddr_t)-1) {
		(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 || (endp = strchr(front, '\n')) == NULL) {
		*tag = NULL;
		goto done;
	}

	len = endp - front;
	MALLOC(sp, p, char *, len + 1);
	if (p == NULL) {
		*tag = NULL;
		goto done;
	}
	memmove(p, front, len);
	p[len] = '\0';
	*tag = p;

done:	if (munmap(map, (size_t)sb.st_size))
		msgq(sp, M_SYSERR, "munmap");
	if (close(fd))
		msgq(sp, M_SYSERR, "close");
	return (0);
}

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


1.4
log
@clean up import.  still have to hack some things.
@
text
@d38 1
a38 1
static const char sccsid[] = "@@(#)ex_tag.c	8.44 (Berkeley) 8/17/94";
@


1.3
log
@nvi 1.11(beta) from bostic.  reconcile conflicts/kill rcsids.
@
text
@d38 1
a38 1
static char sccsid[] = "@@(#)ex_tag.c	8.40 (Berkeley) 3/22/94";
d99 1
a99 1
	if ((frp = file_add(sp, NULL, name, 0)) == NULL)
d128 1
a128 1
			msgq(sp, M_ERR, "%s: search pattern not found.", tag);
d198 1
a198 1
			msgq(sp, M_ERR, "No previous tag entered.");
d215 2
a216 2
	if ((frp = file_add(sp, sp->frp, name, 1)) == NULL)
		goto modify_err;
d221 3
a223 1
		MODIFY_GOTO(sp, sp->ep, F_ISSET(cmdp, E_FORCE));
d261 1
a261 1
modify_err:	free(tag);
d290 1
a290 1
			msgq(sp, M_ERR, "%s: search pattern not found.", tag);
d330 1
a330 1
		msgq(sp, M_INFO, "The tags stack is empty.");
d348 1
a348 1
"Less than %s entries on the tags stack; use :display to see the tags stack.",
d368 1
a368 1
"No file named %s on the tags stack; use :display to see the tags stack.",
d384 3
a386 1
		MODIFY_RET(sp, ep, F_ISSET(cmdp, E_FORCE));
d429 1
a429 1
		msgq(sp, M_INFO, "The tags stack is empty.");
d438 3
a440 1
		MODIFY_RET(sp, sp->ep, F_ISSET(cmdp, E_FORCE));
d665 1
a665 1
		msgq(sp, M_ERR, "%s: tag not found.", tag);
d693 1
a693 1
		msgq(sp, M_ERR, "%s: corrupted tag in %s.", tag, tfp->name);
@


1.2
log
@more Ids than you'll ever want.
@
text
@d2 1
a2 1
 * Copyright (c) 1992, 1993
d38 1
a38 2
/* from: static char sccsid[] = "@@(#)ex_tag.c	8.31 (Berkeley) 1/22/94"; */
static char *rcsid = "$Id$";
d41 1
a41 1
#include <sys/types.h>
d43 1
d45 1
d47 1
d51 2
d54 1
d57 1
d60 4
d106 3
a108 2
	 * Historic vi accepted a line number as well as a search
	 * string, and people are apparently still using the format.
d144 13
d185 1
a185 1
	
d214 9
a222 4
	/* Get a new FREF structure. */
	if ((frp = file_add(sp, sp->frp, name, 1)) == NULL) {
		FREE(tag, strlen(tag));
		return (1);
d252 9
a260 13
	/* Switch to the new file. */
	if (sp->frp == frp)
		which = TC_CURRENT;
	else {
		MODIFY_CHECK(sp, sp->ep, F_ISSET(cmdp, E_FORCE));

		if (file_init(sp, frp, NULL, 0)) {
			if (tp != NULL)
				FREE(tp, sizeof(TAG));
			FREE(tag, strlen(tag));
			return (1);
		}
		which = TC_CHANGE;
a308 13
/* Free a tag or tagf structure from a queue. */
#define	FREETAG(tp) {							\
	TAILQ_REMOVE(&exp->tagq, (tp), q);				\
	if ((tp)->search != NULL)					\
		free((tp)->search);					\
	FREE((tp), sizeof(TAGF));					\
}
#define	FREETAGF(tfp) {							\
	TAILQ_REMOVE(&exp->tagfq, (tfp), q);				\
	free((tfp)->name);						\
	FREE((tfp), sizeof(TAGF));					\
}

d321 2
a322 3
	recno_t lno;
	long off, saved_off;
	size_t arglen, cno;
d334 1
a334 2
		tp = exp->tagq.tqh_first;
		FREETAG(tp);
d338 1
a338 1
		saved_off = strtol(arg, &p, 10);
d340 1
a340 1
			if (saved_off < 1)
d342 2
a343 2
			for (tp = exp->tagq.tqh_first, off = saved_off;
			    tp != NULL && off-- > 1; tp = tp->q.tqe_next);
d346 2
a347 2
"Less than %d entries on the tags stack; use :display to see the tags stack.",
				    saved_off);
d350 1
a350 4
			for (off = saved_off; off-- > 1;) {
				tp = exp->tagq.tqh_first;
				FREETAG(tp);
			}
d354 1
a354 1
			    tp != NULL; tp = tp->q.tqe_next) {
d361 1
a361 2
				if (!strncmp(arg, t, arglen)) {
					ntp = tp;
a362 1
				}
a369 6
			for (;;) {
				tp = exp->tagq.tqh_first;
				if (tp == ntp)
					break;
				FREETAG(tp);
			}
d377 1
a377 1
	tp = exp->tagq.tqh_first;
d382 1
a382 2
		MODIFY_CHECK(sp, ep, F_ISSET(cmdp, E_FORCE));

d393 8
d402 2
a403 2
	if (tp->q.tqe_next == NULL)
		FREETAG(tp);
d410 1
a410 1
 */	
d418 1
a418 2
	TAG *tp, tmp;
	int found;
d420 1
a420 1
	/* Pop to oldest saved information. */
d422 3
a424 7
	for (found = 0; (tp = exp->tagq.tqh_first) != NULL; found = 1) {
		if (exp->tagq.tqh_first == NULL)
			tmp = *tp;
		FREETAG(tp);
	}

	if (!found) {
d430 3
a432 3
	if (tmp.frp == sp->frp) {
		sp->lno = tmp.lno;
		sp->cno = tmp.cno;
d434 2
a435 3
		MODIFY_CHECK(sp, sp->ep, F_ISSET(cmdp, E_FORCE));

		if (file_init(sp, tmp.frp, NULL, 0))
d438 2
a439 2
		tmp.frp->lno = tmp.lno;
		tmp.frp->cno = tmp.cno;
a441 1

d444 4
d567 2
a568 1
	FREE(exp->tlast, strlen(exp->tlast) + 1);
d609 1
a609 1
		
d628 1
d631 1
d633 1
a633 1
	char *p;
d653 3
d657 1
a657 1
	
d673 2
a674 2
	 * the allocated string, filep points to the nul-terminated file name,
	 * searchp points to the nul-terminated search string.
d690 32
d789 1
a789 1
 * 
d793 1
a793 1
 * 
d795 1
a795 1
 * 	front points to the beginning of a line at or before the first 
d797 2
a798 2
 * 
 * 	back points to the beginning of a line at or after the first 
d800 1
a800 1
 * 
d802 1
a802 1
 * 	front = NULL; 
d804 1
a804 1
 * 
d806 1
a806 1
 * 
d808 2
a809 2
 * 
 * 	If the string at "p" is not greater than the string to match, 
d811 1
a811 1
 * 
d813 2
a814 2
 * 
 * 	The definition of the routine allows it return at any point, 
d816 4
a819 4
 * 
 * 	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 
d821 2
a822 2
 * 
 * 	Trying to continue with binary search at this point would be 
d850 1
a850 1
 * 
d852 1
a852 1
 * 
d854 2
a855 2
 * 
 * 	o front points at the first character in a line. 
a865 1
			break;
a867 1
			break;
d879 2
a880 2
 * 
 * 	o Matches up to len(s1) are EQUAL. 
d882 1
a882 1
 * 
@


1.1
log
@Initial revision
@
text
@d38 2
a39 1
static char sccsid[] = "@@(#)ex_tag.c	8.31 (Berkeley) 1/22/94";
@


1.1.1.1
log
@nvi 1.03, from ftp.cs.berkeley.edu, per keith bostic's permission.
@
text
@@


1.1.1.2
log
@nvi/nex 1.11beta from bostic.
@
text
@d2 1
a2 1
 * Copyright (c) 1992, 1993, 1994
d38 1
a38 1
static char sccsid[] = "@@(#)ex_tag.c	8.40 (Berkeley) 3/22/94";
d41 1
a41 1
#include <sys/param.h>
a42 1
#include <sys/queue.h>
a43 1
#include <sys/time.h>
a44 1
#include <bitstring.h>
a47 2
#include <limits.h>
#include <signal.h>
a48 1
#include <stdio.h>
a50 1
#include <termios.h>
a52 4
#include "compat.h"
#include <db.h>
#include <regex.h>

d95 2
a96 3
	 * 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.
a131 13
/* Free a tag or tagf structure from a queue. */
#define	FREETAG(tp) {							\
	TAILQ_REMOVE(&exp->tagq, (tp), q);				\
	if ((tp)->search != NULL)					\
		free((tp)->search);					\
	FREE((tp), sizeof(TAGF));					\
}
#define	FREETAGF(tfp) {							\
	TAILQ_REMOVE(&exp->tagfq, (tfp), q);				\
	free((tfp)->name);						\
	FREE((tfp), sizeof(TAGF));					\
}

d160 1
a160 1

d189 4
a192 9
	/* Get the (possibly new) FREF structure. */
	if ((frp = file_add(sp, sp->frp, name, 1)) == NULL)
		goto modify_err;

	if (sp->frp == frp)
		which = TC_CURRENT;
	else {
		MODIFY_GOTO(sp, sp->ep, F_ISSET(cmdp, E_FORCE));
		which = TC_CHANGE;
d222 13
a234 9
	/* Switch files. */
	if (which == TC_CHANGE && file_init(sp, frp, NULL, 0)) {
		if (tp != NULL)
			FREETAG(tp);
		/* Handle special, first-tag case. */
		if (exp->tagq.tqh_first->q.tqe_next == NULL)
			TAILQ_REMOVE(&exp->tagq, exp->tagq.tqh_first, q);
modify_err:	free(tag);
		return (1);
d283 13
d308 3
a310 2
	long off;
	size_t arglen;
d322 2
a323 1
		ntp = exp->tagq.tqh_first;
d327 1
a327 1
		off = strtol(arg, &p, 10);
d329 1
a329 1
			if (off < 1)
d331 2
a332 2
			for (tp = exp->tagq.tqh_first;
			    tp != NULL && --off > 1; tp = tp->q.tqe_next);
d335 2
a336 2
"Less than %s entries on the tags stack; use :display to see the tags stack.",
				    arg);
d339 4
a342 1
			ntp = tp;
d346 1
a346 1
			    tp != NULL; ntp = tp, tp = tp->q.tqe_next) {
d353 2
a354 1
				if (!strncmp(arg, t, arglen))
d356 1
d364 6
d377 1
a377 1
	tp = ntp->q.tqe_next;
d382 2
a383 1
		MODIFY_RET(sp, ep, F_ISSET(cmdp, E_FORCE));
d394 2
a395 3
	/* Pop entries off the queue up to ntp. */
	for (;;) {
		tp = exp->tagq.tqh_first;
a396 7
		if (tp == ntp)
			break;
	}

	/* If returning to the first tag, the stack is now empty. */
	if (exp->tagq.tqh_first->q.tqe_next == NULL)
		TAILQ_REMOVE(&exp->tagq, exp->tagq.tqh_first, q);
d403 1
a403 1
 */
d411 2
a412 1
	TAG *tp;
d414 1
a414 1
	/* Find oldest saved information. */
d416 7
a422 3
	for (tp = exp->tagq.tqh_first;
	    tp != NULL && tp->q.tqe_next != NULL; tp = tp->q.tqe_next);
	if (tp == NULL) {
d428 3
a430 3
	if (tp->frp == sp->frp) {
		sp->lno = tp->lno;
		sp->cno = tp->cno;
d432 3
a434 2
		MODIFY_RET(sp, sp->ep, F_ISSET(cmdp, E_FORCE));
		if (file_init(sp, tp->frp, NULL, 0))
d437 2
a438 2
		tp->frp->lno = tp->lno;
		tp->frp->cno = tp->cno;
d441 1
a443 4

	/* Empty out the queue. */
	while ((tp = exp->tagq.tqh_first) != NULL)
		FREETAG(tp);
d563 1
a563 2
	if (exp->tlast != NULL)
		free(exp->tlast);
d604 1
a604 1

a622 1
	struct stat sb;
a624 1
	size_t plen, slen, tlen;
d626 1
a626 1
	char *p, pbuf[MAXPATHLEN];
a645 3
		else
			if (p != NULL)
				break;
d647 1
a647 1

d663 2
a664 2
	 * the allocated string, filep points to the file name, and searchp
	 * points to the search string.  All three are nul-terminated.
a679 32

	/*
	 * !!!
	 * 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 do it right.
	 */
	if ((*filep)[0] != '/'
	    && stat(*filep, &sb) && (p = strrchr(tfp->name, '/')) != NULL) {
		*p = '\0';
		plen = snprintf(pbuf, sizeof(pbuf), "%s/%s", tfp->name, *filep);
		*p = '/';
		if (stat(pbuf, &sb) == 0) {
			slen = strlen(*searchp);
			tlen = strlen(*tagp);
			MALLOC(sp, p, char *, plen + slen + tlen + 5);
			if (p != NULL) {
				memmove(p, *tagp, tlen);
				free(*tagp);
				*tagp = p;
				*(p += tlen) = '\0';
				memmove(++p, pbuf, plen);
				*filep = p;
				*(p += plen) = '\0';
				memmove(++p, *searchp, slen);
				*searchp = p;
				*(p += slen) = '\0';
			}
		}
	}
d747 1
a747 1
 *
d751 1
a751 1
 *
d753 1
a753 1
 * 	front points to the beginning of a line at or before the first
d755 2
a756 2
 *
 * 	back points to the beginning of a line at or after the first
d758 1
a758 1
 *
d760 1
a760 1
 * 	front = NULL;
d762 1
a762 1
 *
d764 1
a764 1
 *
d766 2
a767 2
 *
 * 	If the string at "p" is not greater than the string to match,
d769 1
a769 1
 *
d771 2
a772 2
 *
 * 	The definition of the routine allows it return at any point,
d774 4
a777 4
 *
 * 	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
d779 2
a780 2
 *
 * 	Trying to continue with binary search at this point would be
d808 1
a808 1
 *
d810 1
a810 1
 *
d812 2
a813 2
 *
 * 	o front points at the first character in a line.
d824 1
d827 1
d839 2
a840 2
 *
 * 	o Matches up to len(s1) are EQUAL.
d842 1
a842 1
 *
@


1.1.1.3
log
@new public version of nvi
@
text
@d38 1
a38 1
static const char sccsid[] = "@@(#)ex_tag.c	8.44 (Berkeley) 8/17/94";
d99 1
a99 1
	if ((frp = file_add(sp, name)) == NULL)
d128 1
a128 1
			msgq(sp, M_ERR, "%s: search pattern not found", tag);
d198 1
a198 1
			msgq(sp, M_ERR, "No previous tag entered");
d215 2
a216 2
	if ((frp = file_add(sp, name)) == NULL)
		goto err;
d221 1
a221 3
		if (file_m1(sp, sp->ep,
		    F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE))
			goto err;
d259 1
a259 1
err:		free(tag);
d288 1
a288 1
			msgq(sp, M_ERR, "%s: search pattern not found", tag);
d328 1
a328 1
		msgq(sp, M_INFO, "The tags stack is empty");
d346 1
a346 1
"Less than %s entries on the tags stack; use :display to see the tags stack",
d366 1
a366 1
"No file named %s on the tags stack; use :display to see the tags stack",
d382 1
a382 3
		if (file_m1(sp, ep,
		    F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE))
			return (1);
d425 1
a425 1
		msgq(sp, M_INFO, "The tags stack is empty");
d434 1
a434 3
		if (file_m1(sp, sp->ep,
		    F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE))
			return (1);
d659 1
a659 1
		msgq(sp, M_ERR, "%s: tag not found", tag);
d687 1
a687 1
		msgq(sp, M_ERR, "%s: corrupted tag in %s", tag, tfp->name);
@


1.1.1.4
log
@new public version of nvi
@
text
@d38 1
a38 1
static char sccsid[] = "@@(#)ex_tag.c	8.45 (Berkeley) 8/17/94";
@


1.1.1.5
log
@import of nvi 1.66
@
text
@a3 2
 * Copyright (c) 1992, 1993, 1994, 1995, 1996
 *	Keith Bostic.  All rights reserved.
d8 27
a34 1
 * See the LICENSE file for redistribution information.
a36 2
#include "config.h"

d38 1
a38 1
static const char sccsid[] = "@@(#)ex_tag.c	10.32 (Berkeley) 5/16/96";
a41 3
#include <sys/types.h>		/* XXX: param.h may not have included types.h */

#ifdef HAVE_SYS_MMAN_H
a42 2
#endif

d52 1
d57 1
d60 6
a65 2
#include "../common/common.h"
#include "../vi/vi.h"
a69 4
static void	 ctag_file __P((SCR *, TAGF *, char *, char **, size_t *));
static int	 ctag_search __P((SCR *, char *, char *));
static int	 ctag_sfile __P((SCR *, TAGF *, TAGQ *, char *));
static TAGQ	*ctag_slist __P((SCR *, char *));
d71 2
a72 5
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 **));
d75 2
a76 4
 * ex_tag_first --
 *	The tag code can be entered from main, e.g., "vi -t tag".
 *
 * PUBLIC: int ex_tag_first __P((SCR *, char *));
d79 1
a79 1
ex_tag_first(sp, tagarg)
d83 10
a92 2
	ARGS *ap[2], a;
	EXCMD cmd;
d94 9
a102 3
	/* 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));
d105 4
a108 4
	 * 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.
d110 25
a134 2
	if (ex_tag_push(sp, &cmd))
		return (0);
d136 7
a142 3
	/* Display tags in the center of the screen. */
	F_CLR(sp, SC_SCR_TOP);
	F_SET(sp, SC_SCR_CENTER);
d144 11
a154 1
	return (0);
d158 2
a159 2
 * ex_tag_push -- ^]
 *		  :tag[!] [string]
d161 8
a168 3
 * Enter a new TAGQ context based on a ctag string.
 *
 * PUBLIC: int ex_tag_push __P((SCR *, EXCMD *));
d171 1
a171 1
ex_tag_push(sp, cmdp)
d173 2
a174 1
	EXCMD *cmdp;
d176 1
d179 4
a182 4
	TAG *rtp;
	TAGQ *rtqp, *tqp;
	recno_t lno;
	size_t cno;
d184 1
a184 1
	int force, istmp;
d189 3
a191 4
		if (exp->tag_last != NULL)
			free(exp->tag_last);

		if ((exp->tag_last = strdup(cmdp->argv[0]->bp)) == NULL) {
a194 5

		/* 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';
d197 2
a198 2
		if (exp->tag_last == NULL) {
			msgq(sp, M_ERR, "158|No previous tag entered");
d206 4
d211 1
a211 1
	if ((tqp = ctag_slist(sp, exp->tag_last)) == NULL)
d214 11
a224 15
	/*
	 * 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;
d228 6
a233 46
	 * 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->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.
d235 4
a238 8
	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;
d240 4
a243 31
	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);
d245 7
a251 3
	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);
a252 3
	if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
		return (1);
	tqp->current = tp;
d254 8
a261 29
	if (F_ISSET(tqp, TAG_CSCOPE))
		(void)cscope_search(sp, tqp, tp);
	else
		(void)ctag_search(sp, tp->search, 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");
a263 3
	if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
		return (1);
	tqp->current = tp;
d265 42
a306 69
	if (F_ISSET(tqp, TAG_CSCOPE))
		(void)cscope_search(sp, tqp, tp);
	else
		(void)ctag_search(sp, tp->search, 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);
a307 26

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

d312 2
a313 6
 * ex_tag_pop -- ^T
 *		 :tagp[op][!] [number | file]
 *
 *	Pop to a previous TAGQ context.
 *
 * PUBLIC: int ex_tag_pop __P((SCR *, EXCMD *));
d316 1
a316 1
ex_tag_pop(sp, cmdp)
d318 2
a319 1
	EXCMD *cmdp;
d322 2
a323 1
	TAGQ *tqp, *dtqp;
a324 1
	long off;
d329 2
a330 2
	if (exp->tq.cqh_first == (void *)&exp->tq) {
		tag_msg(sp, TAG_EMPTY, NULL);
a333 1
	/* Find the last TAG structure that we're going to DISCARD! */
d336 1
a336 1
		dtqp = exp->tq.cqh_first;
d341 31
a371 38
		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);
a372 2
		if (tqp == exp->tq.cqh_first)
			return (0);
d378 11
a388 2
	return (tag_pop(sp, dtqp, FL_ISSET(cmdp->iflags, E_C_FORCE)));
}
d390 3
a392 12
/*
 * 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;
d394 2
a395 1
	exp = EXP(sp);
d397 6
a402 4
	/* Check for an empty stack. */
	if (exp->tq.cqh_first == (void *)&exp->tq) {
		tag_msg(sp, TAG_EMPTY, NULL);
		return (1);
d405 4
a408 3
	/* Return to the oldest information. */
	return (tag_pop(sp,
	    exp->tq.cqh_last->q.cqe_prev, FL_ISSET(cmdp->iflags, E_C_FORCE)));
d412 2
a413 2
 * tag_pop --
 *	Pop up to and including the specified TAGQ context.
d415 2
a416 2
static int
tag_pop(sp, dtqp, force)
d418 2
a419 2
	TAGQ *dtqp;
	int force;
a422 1
	TAGQ *tqp;
d424 1
d426 6
d433 1
a433 5
	/*
	 * Update the cursor from the saved TAG information of the TAG
	 * structure we're moving to.
	 */
	tp = dtqp->q.cqe_next->current;
d438 4
a441 1
		if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
d446 1
d448 1
a448 4
		if (file_init(sp, tp->frp, NULL, FS_SETALT))
			return (1);

		F_SET(sp, SC_FSWITCH);
d451 3
a453 14
	/* 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);

d458 1
a458 1
 * ex_tag_display --
a459 2
 *
 * PUBLIC: int ex_tag_display __P((SCR *));
d462 1
a462 1
ex_tag_display(sp)
d464 1
d468 1
a468 1
	TAGQ *tqp;
d470 1
a470 2
	size_t len;
	char *p, *sep;
d473 2
a474 2
	if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
		tag_msg(sp, TAG_EMPTY, NULL);
d479 2
a480 8
	 * 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.)
d482 23
a504 19
#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);
d506 3
a508 74
				(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);
d514 2
a515 151
 * 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);
	}
	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 line doesn't exist");
		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 *));
d518 1
a518 1
ex_tagf_alloc(sp, str)
d523 1
a523 1
	TAGF *tfp;
d529 2
a530 2
	while ((tfp = exp->tagfq.tqh_first) != NULL)
		tagf_free(sp, tfp);
d536 4
a539 4
				MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
				MALLOC(sp, tfp->name, char *, len + 1);
				if (tfp->name == NULL) {
					free(tfp);
d542 4
a545 4
				memmove(tfp->name, t, len);
				tfp->name[len] = '\0';
				tfp->flags = 0;
				TAILQ_INSERT_TAIL(&exp->tagfq, tfp, q);
d556 2
a557 4
 * ex_tag_free --
 *	Free the ex tag information.
 *
 * PUBLIC: int ex_tag_free __P((SCR *));
d560 1
a560 1
ex_tag_free(sp)
d564 1
a565 1
	TAGQ *tqp;
d569 2
a570 2
	while ((tqp = exp->tq.cqh_first) != (void *)&exp->tq)
		tagq_free(sp, tqp);
d572 3
a574 3
		tagf_free(sp, tfp);
	if (exp->tag_last != NULL)
		free(exp->tag_last);
d579 2
a580 2
 * ctag_search --
 *	Search a file for a tag.
d582 3
a584 4
static int
ctag_search(sp, search, tag)
	SCR *sp;
	char *search, *tag;
d586 17
a602 2
	MARK m;
	char *p;
d604 10
a613 40
	/*
	 * !!!
	 * 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, NULL, SEARCH_FILE | SEARCH_TAG))
			if ((p = strrchr(search, '(')) != NULL) {
				p[1] = '\0';
				if (f_search(sp, &m, &m,
				    search, NULL, SEARCH_FILE | SEARCH_TAG)) {
					p[1] = '(';
					goto notfound;
				}
				p[1] = '(';
			} 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;
d616 6
a621 7
	/*
	 * !!!
	 * 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);
d626 2
a627 2
 * ctag_slist --
 *	Search the list of tags files for a tag, and return tag queue.
d629 2
a630 2
static TAGQ *
ctag_slist(sp, tag)
d632 1
a632 1
	char *tag;
d634 1
d637 3
a639 12
	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);
d645 22
a666 12
	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)
d669 4
a672 5
				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);
d674 1
a674 2
		free(tqp);
		return (NULL);
d677 19
a695 2
	tqp->current = tqp->tagq.cqh_first;
	return (tqp);
d697 32
a728 2
alloc_err:
	return (NULL);
d731 4
d736 2
a737 2
 * ctag_sfile --
 *	Search a tags file for a tag, adding any found to the tag queue.
d740 1
a740 1
ctag_sfile(sp, tfp, tqp, tname)
d742 1
a742 3
	TAGF *tfp;
	TAGQ *tqp;
	char *tname;
d745 2
a746 4
	TAG *tp;
	size_t dlen, nlen, slen;
	int fd, i, nf1, nf2;
	char *back, *cname, *dname, *front, *map, *name, *p, *search, *t;
d748 1
a748 2
	if ((fd = open(tfp->name, O_RDONLY, 0)) < 0) {
		tfp->errnum = errno;
a749 1
	}
a752 8
	 * Some old BSD systems require MAP_FILE as an argument when mapping
	 * regular files.
	 */
#ifndef MAP_FILE
#define	MAP_FILE	0
#endif
	/*
	 * XXX
d757 2
a758 1
	 * is flatly impossible.  Hope mmap fails if the file is too large.
d760 2
a761 4
	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;
a764 1

d767 1
d770 3
a772 1
	if (front == NULL)
d774 1
d776 5
a780 78
	/*
	 * 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);
d782 3
a785 1
alloc_err:
a793 36
 * 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);
		}
	}
}

/*
a830 4
#define	EQUAL		0
#define	GREATER		1
#define	LESS		(-1)

@


1.1.1.6
log
@import of nvi 1.79
@
text
@d16 1
a16 1
static const char sccsid[] = "@@(#)ex_tag.c	10.36 (Berkeley) 9/15/96";
d48 1
a48 1
static int	 ctag_search __P((SCR *, char *, size_t, char *));
d198 1
a198 2
	(void)ctag_search(sp,
	    tqp->current->search, tqp->current->slen, tqp->tag);
d261 1
a261 1
		(void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
d296 1
a296 1
		(void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
d801 1
a801 7
	/*
	 * !!!
	 * 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);
d820 1
a820 2
		msgq_str(sp, M_ERR, tag,
	    "164|%s: the tag's line number is past the end of the file");
d864 1
a864 1
				memcpy(tfp->name, t, len);
d907 1
a907 1
ctag_search(sp, search, slen, tag)
a909 1
	size_t slen;
d934 1
a934 1
		    search, slen, NULL, SEARCH_FILE | SEARCH_TAG))
d936 4
a939 3
				slen = p - search;
				if (f_search(sp, &m, &m, search, slen,
				    NULL, SEARCH_FILE | SEARCH_TAG))
d941 2
@

