119304Speter/*-
219304Speter * Copyright (c) 1992, 1993, 1994
319304Speter *	The Regents of the University of California.  All rights reserved.
419304Speter * Copyright (c) 1992, 1993, 1994, 1995, 1996
519304Speter *	Keith Bostic.  All rights reserved.
619304Speter *
719304Speter * This code is derived from software contributed to Berkeley by
819304Speter * David Hitz of Auspex Systems, Inc.
919304Speter *
1019304Speter * See the LICENSE file for redistribution information.
1119304Speter */
1219304Speter
1319304Speter#include "config.h"
1419304Speter
1519304Speter#ifndef lint
16254225Speterstatic const char sccsid[] = "$Id: ex_tag.c,v 10.54 2012/04/12 07:17:30 zy Exp $";
1719304Speter#endif /* not lint */
1819304Speter
19254225Speter#include <sys/types.h>
2019304Speter#include <sys/mman.h>
2119304Speter#include <sys/queue.h>
2219304Speter#include <sys/stat.h>
2319304Speter
2419304Speter#include <bitstring.h>
2519304Speter#include <ctype.h>
2619304Speter#include <errno.h>
2719304Speter#include <fcntl.h>
2819304Speter#include <limits.h>
2919304Speter#include <stddef.h>
3019304Speter#include <stdio.h>
3119304Speter#include <stdlib.h>
3219304Speter#include <string.h>
3319304Speter#include <unistd.h>
3419304Speter
3519304Speter#include "../common/common.h"
3619304Speter#include "../vi/vi.h"
3719304Speter#include "tag.h"
3819304Speter
39281373Sbaptstatic char	*binary_search(char *, char *, char *);
40281373Sbaptstatic int	 compare(char *, char *, char *);
41281373Sbaptstatic void	 ctag_file(SCR *, TAGF *, char *, char **, size_t *);
42281373Sbaptstatic int	 ctag_search(SCR *, CHAR_T *, size_t, char *);
43281373Sbaptstatic int	 ctag_sfile(SCR *, TAGF *, TAGQ *, char *);
44281373Sbaptstatic TAGQ	*ctag_slist(SCR *, CHAR_T *);
45281373Sbaptstatic char	*linear_search(char *, char *, char *, long);
46281373Sbaptstatic int	 tag_copy(SCR *, TAG *, TAG **);
47281373Sbaptstatic int	 tag_pop(SCR *, TAGQ *, int);
48281373Sbaptstatic int	 tagf_copy(SCR *, TAGF *, TAGF **);
49281373Sbaptstatic int	 tagf_free(SCR *, TAGF *);
50281373Sbaptstatic int	 tagq_copy(SCR *, TAGQ *, TAGQ **);
5119304Speter
5219304Speter/*
5319304Speter * ex_tag_first --
5419304Speter *	The tag code can be entered from main, e.g., "vi -t tag".
5519304Speter *
56281373Sbapt * PUBLIC: int ex_tag_first(SCR *, CHAR_T *);
5719304Speter */
5819304Speterint
59254225Speterex_tag_first(SCR *sp, CHAR_T *tagarg)
6019304Speter{
6119304Speter	EXCMD cmd;
6219304Speter
6319304Speter	/* Build an argument for the ex :tag command. */
64254225Speter	ex_cinit(sp, &cmd, C_TAG, 0, OOBLNO, OOBLNO, 0);
65254225Speter	argv_exp0(sp, &cmd, tagarg, STRLEN(tagarg));
6619304Speter
6719304Speter	/*
6819304Speter	 * XXX
6919304Speter	 * Historic vi went ahead and created a temporary file when it failed
7019304Speter	 * to find the tag.  We match historic practice, but don't distinguish
7119304Speter	 * between real error and failure to find the tag.
7219304Speter	 */
7319304Speter	if (ex_tag_push(sp, &cmd))
7419304Speter		return (0);
7519304Speter
7619304Speter	/* Display tags in the center of the screen. */
7719304Speter	F_CLR(sp, SC_SCR_TOP);
7819304Speter	F_SET(sp, SC_SCR_CENTER);
7919304Speter
8019304Speter	return (0);
8119304Speter}
8219304Speter
8319304Speter/*
8419304Speter * ex_tag_push -- ^]
8519304Speter *		  :tag[!] [string]
8619304Speter *
8719304Speter * Enter a new TAGQ context based on a ctag string.
8819304Speter *
89281373Sbapt * PUBLIC: int ex_tag_push(SCR *, EXCMD *);
9019304Speter */
9119304Speterint
92254225Speterex_tag_push(SCR *sp, EXCMD *cmdp)
9319304Speter{
9419304Speter	EX_PRIVATE *exp;
95254225Speter	TAGQ *tqp;
9619304Speter	long tl;
9719304Speter
9819304Speter	exp = EXP(sp);
9919304Speter	switch (cmdp->argc) {
10019304Speter	case 1:
10119304Speter		if (exp->tag_last != NULL)
10219304Speter			free(exp->tag_last);
10319304Speter
104254225Speter		if ((exp->tag_last = v_wstrdup(sp, cmdp->argv[0]->bp,
105254225Speter		    cmdp->argv[0]->len)) == NULL) {
10619304Speter			msgq(sp, M_SYSERR, NULL);
10719304Speter			return (1);
10819304Speter		}
10919304Speter
11019304Speter		/* Taglength may limit the number of characters. */
11119304Speter		if ((tl =
112254225Speter		    O_VAL(sp, O_TAGLENGTH)) != 0 && STRLEN(exp->tag_last) > tl)
11319304Speter			exp->tag_last[tl] = '\0';
11419304Speter		break;
11519304Speter	case 0:
11619304Speter		if (exp->tag_last == NULL) {
11719304Speter			msgq(sp, M_ERR, "158|No previous tag entered");
11819304Speter			return (1);
11919304Speter		}
12019304Speter		break;
12119304Speter	default:
12219304Speter		abort();
12319304Speter	}
12419304Speter
12519304Speter	/* Get the tag information. */
12619304Speter	if ((tqp = ctag_slist(sp, exp->tag_last)) == NULL)
12719304Speter		return (1);
12819304Speter
129254225Speter	if (tagq_push(sp, tqp, F_ISSET(cmdp, E_NEWSCREEN),
130254225Speter			       FL_ISSET(cmdp->iflags, E_C_FORCE)))
131254225Speter		return 1;
13219304Speter
133254225Speter	return 0;
13419304Speter}
13519304Speter
13619304Speter/*
13719304Speter * ex_tag_next --
13819304Speter *	Switch context to the next TAG.
13919304Speter *
140281373Sbapt * PUBLIC: int ex_tag_next(SCR *, EXCMD *);
14119304Speter */
14219304Speterint
143254225Speterex_tag_next(SCR *sp, EXCMD *cmdp)
14419304Speter{
14519304Speter	EX_PRIVATE *exp;
14619304Speter	TAG *tp;
14719304Speter	TAGQ *tqp;
148254225Speter	char *np;
149254225Speter	size_t nlen;
15019304Speter
15119304Speter	exp = EXP(sp);
152254225Speter	if ((tqp = TAILQ_FIRST(exp->tq)) == NULL) {
15319304Speter		tag_msg(sp, TAG_EMPTY, NULL);
15419304Speter		return (1);
15519304Speter	}
156254225Speter	if ((tp = TAILQ_NEXT(tqp->current, q)) == NULL) {
15719304Speter		msgq(sp, M_ERR, "282|Already at the last tag of this group");
15819304Speter		return (1);
15919304Speter	}
16019304Speter	if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
16119304Speter		return (1);
16219304Speter	tqp->current = tp;
16319304Speter
16419304Speter	if (F_ISSET(tqp, TAG_CSCOPE))
16519304Speter		(void)cscope_search(sp, tqp, tp);
16619304Speter	else
16719304Speter		(void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
168254225Speter	if (tqp->current->msg) {
169254225Speter	    INT2CHAR(sp, tqp->current->msg, tqp->current->mlen + 1,
170254225Speter		     np, nlen);
171254225Speter	    msgq(sp, M_INFO, "%s", np);
172254225Speter	}
17319304Speter	return (0);
17419304Speter}
17519304Speter
17619304Speter/*
17719304Speter * ex_tag_prev --
17819304Speter *	Switch context to the next TAG.
17919304Speter *
180281373Sbapt * PUBLIC: int ex_tag_prev(SCR *, EXCMD *);
18119304Speter */
18219304Speterint
183254225Speterex_tag_prev(SCR *sp, EXCMD *cmdp)
18419304Speter{
18519304Speter	EX_PRIVATE *exp;
18619304Speter	TAG *tp;
18719304Speter	TAGQ *tqp;
188254225Speter	char *np;
189254225Speter	size_t nlen;
19019304Speter
19119304Speter	exp = EXP(sp);
192254225Speter	if ((tqp = TAILQ_FIRST(exp->tq)) == NULL) {
19319304Speter		tag_msg(sp, TAG_EMPTY, NULL);
19419304Speter		return (0);
19519304Speter	}
196254225Speter	if ((tp = TAILQ_PREV(tqp->current, _tagqh, q)) == NULL) {
19719304Speter		msgq(sp, M_ERR, "255|Already at the first tag of this group");
19819304Speter		return (1);
19919304Speter	}
20019304Speter	if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
20119304Speter		return (1);
20219304Speter	tqp->current = tp;
20319304Speter
20419304Speter	if (F_ISSET(tqp, TAG_CSCOPE))
20519304Speter		(void)cscope_search(sp, tqp, tp);
20619304Speter	else
20719304Speter		(void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
208254225Speter	if (tqp->current->msg) {
209254225Speter	    INT2CHAR(sp, tqp->current->msg, tqp->current->mlen + 1,
210254225Speter		     np, nlen);
211254225Speter	    msgq(sp, M_INFO, "%s", np);
212254225Speter	}
21319304Speter	return (0);
21419304Speter}
21519304Speter
21619304Speter/*
21719304Speter * ex_tag_nswitch --
21819304Speter *	Switch context to the specified TAG.
21919304Speter *
220281373Sbapt * PUBLIC: int ex_tag_nswitch(SCR *, TAG *, int);
22119304Speter */
22219304Speterint
223254225Speterex_tag_nswitch(SCR *sp, TAG *tp, int force)
22419304Speter{
22519304Speter	/* Get a file structure. */
22619304Speter	if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)
22719304Speter		return (1);
22819304Speter
22919304Speter	/* If not changing files, return, we're done. */
23019304Speter	if (tp->frp == sp->frp)
23119304Speter		return (0);
23219304Speter
23319304Speter	/* Check for permission to leave. */
23419304Speter	if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
23519304Speter		return (1);
23619304Speter
23719304Speter	/* Initialize the new file. */
23819304Speter	if (file_init(sp, tp->frp, NULL, FS_SETALT))
23919304Speter		return (1);
24019304Speter
24119304Speter	/* Display tags in the center of the screen. */
24219304Speter	F_CLR(sp, SC_SCR_TOP);
24319304Speter	F_SET(sp, SC_SCR_CENTER);
24419304Speter
24519304Speter	/* Switch. */
24619304Speter	F_SET(sp, SC_FSWITCH);
24719304Speter	return (0);
24819304Speter}
24919304Speter
25019304Speter/*
25119304Speter * ex_tag_Nswitch --
25219304Speter *	Switch context to the specified TAG in a new screen.
25319304Speter *
254281373Sbapt * PUBLIC: int ex_tag_Nswitch(SCR *, TAG *, int);
25519304Speter */
25619304Speterint
257254225Speterex_tag_Nswitch(SCR *sp, TAG *tp, int force)
25819304Speter{
25919304Speter	SCR *new;
26019304Speter
26119304Speter	/* Get a file structure. */
26219304Speter	if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)
26319304Speter		return (1);
26419304Speter
26519304Speter	/* Get a new screen. */
26619304Speter	if (screen_init(sp->gp, sp, &new))
26719304Speter		return (1);
26819304Speter	if (vs_split(sp, new, 0)) {
26919304Speter		(void)file_end(new, new->ep, 1);
27019304Speter		(void)screen_end(new);
27119304Speter		return (1);
27219304Speter	}
27319304Speter
27419304Speter	/* Get a backing file. */
27519304Speter	if (tp->frp == sp->frp) {
27619304Speter		/* Copy file state. */
27719304Speter		new->ep = sp->ep;
27819304Speter		++new->ep->refcnt;
27919304Speter
28019304Speter		new->frp = tp->frp;
28119304Speter		new->frp->flags = sp->frp->flags;
28219304Speter	} else if (file_init(new, tp->frp, NULL, force)) {
28319304Speter		(void)vs_discard(new, NULL);
28419304Speter		(void)screen_end(new);
28519304Speter		return (1);
28619304Speter	}
28719304Speter
28819304Speter	/* Create the argument list. */
28919304Speter	new->cargv = new->argv = ex_buildargv(sp, NULL, tp->frp->name);
29019304Speter
29119304Speter	/* Display tags in the center of the screen. */
29219304Speter	F_CLR(new, SC_SCR_TOP);
29319304Speter	F_SET(new, SC_SCR_CENTER);
29419304Speter
29519304Speter	/* Switch. */
29619304Speter	sp->nextdisp = new;
29719304Speter	F_SET(sp, SC_SSWITCH);
29819304Speter
29919304Speter	return (0);
30019304Speter}
30119304Speter
30219304Speter/*
30319304Speter * ex_tag_pop -- ^T
30419304Speter *		 :tagp[op][!] [number | file]
30519304Speter *
30619304Speter *	Pop to a previous TAGQ context.
30719304Speter *
308281373Sbapt * PUBLIC: int ex_tag_pop(SCR *, EXCMD *);
30919304Speter */
31019304Speterint
311254225Speterex_tag_pop(SCR *sp, EXCMD *cmdp)
31219304Speter{
31319304Speter	EX_PRIVATE *exp;
31419304Speter	TAGQ *tqp, *dtqp;
31519304Speter	size_t arglen;
31619304Speter	long off;
31719304Speter	char *arg, *p, *t;
318254225Speter	size_t nlen;
31919304Speter
32019304Speter	/* Check for an empty stack. */
32119304Speter	exp = EXP(sp);
322254225Speter	if (TAILQ_EMPTY(exp->tq)) {
32319304Speter		tag_msg(sp, TAG_EMPTY, NULL);
32419304Speter		return (1);
32519304Speter	}
32619304Speter
32719304Speter	/* Find the last TAG structure that we're going to DISCARD! */
32819304Speter	switch (cmdp->argc) {
32919304Speter	case 0:				/* Pop one tag. */
330254225Speter		dtqp = TAILQ_FIRST(exp->tq);
33119304Speter		break;
33219304Speter	case 1:				/* Name or number. */
333254225Speter		INT2CHAR(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len+1,
334254225Speter			 arg, nlen);
33519304Speter		off = strtol(arg, &p, 10);
33619304Speter		if (*p != '\0')
33719304Speter			goto filearg;
33819304Speter
33919304Speter		/* Number: pop that many queue entries. */
34019304Speter		if (off < 1)
34119304Speter			return (0);
342254225Speter		TAILQ_FOREACH(tqp, exp->tq, q)
343254225Speter			if (--off <= 1) break;
344254225Speter		if (tqp == NULL) {
34519304Speter			msgq(sp, M_ERR,
34619304Speter	"159|Less than %s entries on the tags stack; use :display t[ags]",
34719304Speter			    arg);
34819304Speter			return (1);
34919304Speter		}
35019304Speter		dtqp = tqp;
35119304Speter		break;
35219304Speter
35319304Speter		/* File argument: pop to that queue entry. */
35419304Speterfilearg:	arglen = strlen(arg);
355254225Speter		for (tqp = TAILQ_FIRST(exp->tq); tqp;
356254225Speter		    dtqp = tqp, tqp = TAILQ_NEXT(tqp, q)) {
35719304Speter			/* Don't pop to the current file. */
358254225Speter			if (tqp == TAILQ_FIRST(exp->tq))
35919304Speter				continue;
36019304Speter			p = tqp->current->frp->name;
36119304Speter			if ((t = strrchr(p, '/')) == NULL)
36219304Speter				t = p;
36319304Speter			else
36419304Speter				++t;
36519304Speter			if (!strncmp(arg, t, arglen))
36619304Speter				break;
36719304Speter		}
368254225Speter		if (tqp == NULL) {
36919304Speter			msgq_str(sp, M_ERR, arg,
37019304Speter	"160|No file %s on the tags stack to return to; use :display t[ags]");
37119304Speter			return (1);
37219304Speter		}
373254225Speter		if (tqp == TAILQ_FIRST(exp->tq))
37419304Speter			return (0);
37519304Speter		break;
37619304Speter	default:
37719304Speter		abort();
37819304Speter	}
37919304Speter
38019304Speter	return (tag_pop(sp, dtqp, FL_ISSET(cmdp->iflags, E_C_FORCE)));
38119304Speter}
38219304Speter
38319304Speter/*
38419304Speter * ex_tag_top -- :tagt[op][!]
38519304Speter *	Clear the tag stack.
38619304Speter *
387281373Sbapt * PUBLIC: int ex_tag_top(SCR *, EXCMD *);
38819304Speter */
38919304Speterint
390254225Speterex_tag_top(SCR *sp, EXCMD *cmdp)
39119304Speter{
39219304Speter	EX_PRIVATE *exp;
39319304Speter
39419304Speter	exp = EXP(sp);
39519304Speter
39619304Speter	/* Check for an empty stack. */
397254225Speter	if (TAILQ_EMPTY(exp->tq)) {
39819304Speter		tag_msg(sp, TAG_EMPTY, NULL);
39919304Speter		return (1);
40019304Speter	}
40119304Speter
40219304Speter	/* Return to the oldest information. */
403254225Speter	return (tag_pop(sp, TAILQ_PREV(TAILQ_LAST(exp->tq, _tqh), _tqh, q),
404254225Speter	    FL_ISSET(cmdp->iflags, E_C_FORCE)));
40519304Speter}
40619304Speter
40719304Speter/*
40819304Speter * tag_pop --
40919304Speter *	Pop up to and including the specified TAGQ context.
41019304Speter */
41119304Speterstatic int
412254225Spetertag_pop(SCR *sp, TAGQ *dtqp, int force)
41319304Speter{
41419304Speter	EX_PRIVATE *exp;
41519304Speter	TAG *tp;
41619304Speter	TAGQ *tqp;
41719304Speter
41819304Speter	exp = EXP(sp);
41919304Speter
42019304Speter	/*
42119304Speter	 * Update the cursor from the saved TAG information of the TAG
42219304Speter	 * structure we're moving to.
42319304Speter	 */
424254225Speter	tp = TAILQ_NEXT(dtqp, q)->current;
42519304Speter	if (tp->frp == sp->frp) {
42619304Speter		sp->lno = tp->lno;
42719304Speter		sp->cno = tp->cno;
42819304Speter	} else {
42919304Speter		if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
43019304Speter			return (1);
43119304Speter
43219304Speter		tp->frp->lno = tp->lno;
43319304Speter		tp->frp->cno = tp->cno;
43419304Speter		F_SET(sp->frp, FR_CURSORSET);
43519304Speter		if (file_init(sp, tp->frp, NULL, FS_SETALT))
43619304Speter			return (1);
43719304Speter
43819304Speter		F_SET(sp, SC_FSWITCH);
43919304Speter	}
44019304Speter
44119304Speter	/* Pop entries off the queue up to and including dtqp. */
44219304Speter	do {
443254225Speter		tqp = TAILQ_FIRST(exp->tq);
44419304Speter		if (tagq_free(sp, tqp))
44519304Speter			return (0);
44619304Speter	} while (tqp != dtqp);
44719304Speter
44819304Speter	/*
44919304Speter	 * If only a single tag left, we've returned to the first tag point,
45019304Speter	 * and the stack is now empty.
45119304Speter	 */
452254225Speter	if (TAILQ_NEXT(TAILQ_FIRST(exp->tq), q) == NULL)
453254225Speter		tagq_free(sp, TAILQ_FIRST(exp->tq));
45419304Speter
45519304Speter	return (0);
45619304Speter}
45719304Speter
45819304Speter/*
45919304Speter * ex_tag_display --
46019304Speter *	Display the list of tags.
46119304Speter *
462281373Sbapt * PUBLIC: int ex_tag_display(SCR *);
46319304Speter */
46419304Speterint
465254225Speterex_tag_display(SCR *sp)
46619304Speter{
46719304Speter	EX_PRIVATE *exp;
46819304Speter	TAG *tp;
46919304Speter	TAGQ *tqp;
47019304Speter	int cnt;
47119304Speter	size_t len;
472254225Speter	char *p;
47319304Speter
47419304Speter	exp = EXP(sp);
475254225Speter	if (TAILQ_EMPTY(exp->tq)) {
47619304Speter		tag_msg(sp, TAG_EMPTY, NULL);
47719304Speter		return (0);
47819304Speter	}
47919304Speter
48019304Speter	/*
48119304Speter	 * We give the file name 20 columns and the search string the rest.
48219304Speter	 * If there's not enough room, we don't do anything special, it's
48319304Speter	 * not worth the effort, it just makes the display more confusing.
48419304Speter	 *
48519304Speter	 * We also assume that characters in file names map 1-1 to printing
48619304Speter	 * characters.  This might not be true, but I don't think it's worth
48719304Speter	 * fixing.  (The obvious fix is to pass the filenames through the
48819304Speter	 * msg_print function.)
48919304Speter	 */
49019304Speter#define	L_NAME	30		/* Name. */
49119304Speter#define	L_SLOP	 4		/* Leading number plus trailing *. */
49219304Speter#define	L_SPACE	 5		/* Spaces after name, before tag. */
49319304Speter#define	L_TAG	20		/* Tag. */
49419304Speter	if (sp->cols <= L_NAME + L_SLOP) {
49519304Speter		msgq(sp, M_ERR, "292|Display too small.");
49619304Speter		return (0);
49719304Speter	}
49819304Speter
49919304Speter	/*
50019304Speter	 * Display the list of tags for each queue entry.  The first entry
50119304Speter	 * is numbered, and the current tag entry has an asterisk appended.
50219304Speter	 */
503254225Speter	for (cnt = 1, tqp = TAILQ_FIRST(exp->tq); !INTERRUPTED(sp) &&
504254225Speter	    tqp != NULL; ++cnt, tqp = TAILQ_NEXT(tqp, q))
505254225Speter		TAILQ_FOREACH(tp, tqp->tagq, q) {
506254225Speter			if (tp == TAILQ_FIRST(tqp->tagq))
50719304Speter				(void)ex_printf(sp, "%2d ", cnt);
50819304Speter			else
50919304Speter				(void)ex_printf(sp, "   ");
51019304Speter			p = tp->frp == NULL ? tp->fname : tp->frp->name;
51119304Speter			if ((len = strlen(p)) > L_NAME) {
51219304Speter				len = len - (L_NAME - 4);
51319304Speter				(void)ex_printf(sp, "   ... %*.*s",
51419304Speter				    L_NAME - 4, L_NAME - 4, p + len);
51519304Speter			} else
51619304Speter				(void)ex_printf(sp,
51719304Speter				    "   %*.*s", L_NAME, L_NAME, p);
51819304Speter			if (tqp->current == tp)
51919304Speter				(void)ex_printf(sp, "*");
52019304Speter
521254225Speter			if (tp == TAILQ_FIRST(tqp->tagq) && tqp->tag != NULL &&
52219304Speter			    (sp->cols - L_NAME) >= L_TAG + L_SPACE) {
52319304Speter				len = strlen(tqp->tag);
52419304Speter				if (len > sp->cols - (L_NAME + L_SPACE))
52519304Speter					len = sp->cols - (L_NAME + L_SPACE);
52619304Speter				(void)ex_printf(sp, "%s%.*s",
52719304Speter				    tqp->current == tp ? "    " : "     ",
52819304Speter				    (int)len, tqp->tag);
52919304Speter			}
53019304Speter			(void)ex_printf(sp, "\n");
53119304Speter		}
53219304Speter	return (0);
53319304Speter}
53419304Speter
53519304Speter/*
53619304Speter * ex_tag_copy --
53719304Speter *	Copy a screen's tag structures.
53819304Speter *
539281373Sbapt * PUBLIC: int ex_tag_copy(SCR *, SCR *);
54019304Speter */
54119304Speterint
542254225Speterex_tag_copy(SCR *orig, SCR *sp)
54319304Speter{
54419304Speter	EX_PRIVATE *oexp, *nexp;
54519304Speter	TAGQ *aqp, *tqp;
54619304Speter	TAG *ap, *tp;
54719304Speter	TAGF *atfp, *tfp;
54819304Speter
54919304Speter	oexp = EXP(orig);
55019304Speter	nexp = EXP(sp);
55119304Speter
55219304Speter	/* Copy tag queue and tags stack. */
553254225Speter	TAILQ_FOREACH(aqp, oexp->tq, q) {
55419304Speter		if (tagq_copy(sp, aqp, &tqp))
55519304Speter			return (1);
556254225Speter		TAILQ_FOREACH(ap, aqp->tagq, q) {
55719304Speter			if (tag_copy(sp, ap, &tp))
55819304Speter				return (1);
55919304Speter			/* Set the current pointer. */
56019304Speter			if (aqp->current == ap)
56119304Speter				tqp->current = tp;
562254225Speter			TAILQ_INSERT_TAIL(tqp->tagq, tp, q);
56319304Speter		}
564254225Speter		TAILQ_INSERT_TAIL(nexp->tq, tqp, q);
56519304Speter	}
56619304Speter
56719304Speter	/* Copy list of tag files. */
568254225Speter	TAILQ_FOREACH(atfp, oexp->tagfq, q) {
56919304Speter		if (tagf_copy(sp, atfp, &tfp))
57019304Speter			return (1);
571254225Speter		TAILQ_INSERT_TAIL(nexp->tagfq, tfp, q);
57219304Speter	}
57319304Speter
57419304Speter	/* Copy the last tag. */
57519304Speter	if (oexp->tag_last != NULL &&
576254225Speter	    (nexp->tag_last = v_wstrdup(sp, oexp->tag_last,
577254225Speter					STRLEN(oexp->tag_last))) == NULL) {
57819304Speter		msgq(sp, M_SYSERR, NULL);
57919304Speter		return (1);
58019304Speter	}
58119304Speter	return (0);
58219304Speter}
58319304Speter
58419304Speter/*
58519304Speter * tagf_copy --
58619304Speter *	Copy a TAGF structure and return it in new memory.
58719304Speter */
58819304Speterstatic int
589254225Spetertagf_copy(SCR *sp, TAGF *otfp, TAGF **tfpp)
59019304Speter{
59119304Speter	TAGF *tfp;
59219304Speter
59319304Speter	MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
59419304Speter	*tfp = *otfp;
59519304Speter
59619304Speter	/* XXX: Allocate as part of the TAGF structure!!! */
59719304Speter	if ((tfp->name = strdup(otfp->name)) == NULL)
59819304Speter		return (1);
59919304Speter
60019304Speter	*tfpp = tfp;
60119304Speter	return (0);
60219304Speter}
60319304Speter
60419304Speter/*
60519304Speter * tagq_copy --
60619304Speter *	Copy a TAGQ structure and return it in new memory.
60719304Speter */
60819304Speterstatic int
609254225Spetertagq_copy(SCR *sp, TAGQ *otqp, TAGQ **tqpp)
61019304Speter{
61119304Speter	TAGQ *tqp;
61219304Speter	size_t len;
61319304Speter
61419304Speter	len = sizeof(TAGQ);
61519304Speter	if (otqp->tag != NULL)
61619304Speter		len += otqp->tlen + 1;
61719304Speter	MALLOC_RET(sp, tqp, TAGQ *, len);
61819304Speter	memcpy(tqp, otqp, len);
61919304Speter
620254225Speter	TAILQ_INIT(tqp->tagq);
62119304Speter	tqp->current = NULL;
62219304Speter	if (otqp->tag != NULL)
62319304Speter		tqp->tag = tqp->buf;
62419304Speter
62519304Speter	*tqpp = tqp;
62619304Speter	return (0);
62719304Speter}
62819304Speter
62919304Speter/*
63019304Speter * tag_copy --
63119304Speter *	Copy a TAG structure and return it in new memory.
63219304Speter */
63319304Speterstatic int
634254225Spetertag_copy(SCR *sp, TAG *otp, TAG **tpp)
63519304Speter{
63619304Speter	TAG *tp;
63719304Speter	size_t len;
63819304Speter
63919304Speter	len = sizeof(TAG);
64019304Speter	if (otp->fname != NULL)
64119304Speter		len += otp->fnlen + 1;
64219304Speter	if (otp->search != NULL)
64319304Speter		len += otp->slen + 1;
644254225Speter	if (otp->msg != NULL)
645254225Speter		len += otp->mlen + 1;
64619304Speter	MALLOC_RET(sp, tp, TAG *, len);
64719304Speter	memcpy(tp, otp, len);
64819304Speter
64919304Speter	if (otp->fname != NULL)
650254225Speter		tp->fname = (char *)tp->buf;
65119304Speter	if (otp->search != NULL)
652254225Speter		tp->search = tp->buf + (otp->search - otp->buf);
653254225Speter	if (otp->msg != NULL)
654254225Speter		tp->msg = tp->buf + (otp->msg - otp->buf);
65519304Speter
65619304Speter	*tpp = tp;
65719304Speter	return (0);
65819304Speter}
65919304Speter
66019304Speter/*
66119304Speter * tagf_free --
66219304Speter *	Free a TAGF structure.
66319304Speter */
66419304Speterstatic int
665254225Spetertagf_free(SCR *sp, TAGF *tfp)
66619304Speter{
66719304Speter	EX_PRIVATE *exp;
66819304Speter
66919304Speter	exp = EXP(sp);
670254225Speter	TAILQ_REMOVE(exp->tagfq, tfp, q);
67119304Speter	free(tfp->name);
67219304Speter	free(tfp);
67319304Speter	return (0);
67419304Speter}
67519304Speter
67619304Speter/*
67719304Speter * tagq_free --
67819304Speter *	Free a TAGQ structure (and associated TAG structures).
67919304Speter *
680281373Sbapt * PUBLIC: int tagq_free(SCR *, TAGQ *);
68119304Speter */
68219304Speterint
683254225Spetertagq_free(SCR *sp, TAGQ *tqp)
68419304Speter{
68519304Speter	EX_PRIVATE *exp;
68619304Speter	TAG *tp;
68719304Speter
68819304Speter	exp = EXP(sp);
689254225Speter	while ((tp = TAILQ_FIRST(tqp->tagq)) != NULL) {
690254225Speter		TAILQ_REMOVE(tqp->tagq, tp, q);
69119304Speter		free(tp);
69219304Speter	}
69319304Speter	/*
69419304Speter	 * !!!
69519304Speter	 * If allocated and then the user failed to switch files, the TAGQ
69619304Speter	 * structure was never attached to any list.
69719304Speter	 */
698254225Speter	if (TAILQ_ENTRY_ISVALID(tqp, q))
699254225Speter		TAILQ_REMOVE(exp->tq, tqp, q);
70019304Speter	free(tqp);
70119304Speter	return (0);
70219304Speter}
70319304Speter
70419304Speter/*
705281373Sbapt * PUBLIC: int tagq_push(SCR*, TAGQ*, int, int );
706254225Speter */
707254225Speterint
708254225Spetertagq_push(SCR *sp, TAGQ *tqp, int new_screen, int force)
709254225Speter{
710254225Speter	EX_PRIVATE *exp;
711254225Speter	FREF *frp;
712254225Speter	TAG *rtp;
713254225Speter	TAGQ *rtqp;
714254225Speter	recno_t lno;
715254225Speter	size_t cno;
716254225Speter	int istmp;
717254225Speter	char *np;
718254225Speter	size_t nlen;
719254225Speter
720254225Speter	exp = EXP(sp);
721254225Speter
722254225Speter	/*
723254225Speter	 * Allocate all necessary memory before swapping screens.  Initialize
724254225Speter	 * flags so we know what to free.
725254225Speter	 */
726254225Speter	rtp = NULL;
727254225Speter	rtqp = NULL;
728254225Speter	if (TAILQ_EMPTY(exp->tq)) {
729254225Speter		/* Initialize the `local context' tag queue structure. */
730254225Speter		CALLOC_GOTO(sp, rtqp, TAGQ *, 1, sizeof(TAGQ));
731254225Speter		TAILQ_INIT(rtqp->tagq);
732254225Speter
733254225Speter		/* Initialize and link in its tag structure. */
734254225Speter		CALLOC_GOTO(sp, rtp, TAG *, 1, sizeof(TAG));
735254225Speter		TAILQ_INSERT_HEAD(rtqp->tagq, rtp, q);
736254225Speter		rtqp->current = rtp;
737254225Speter	}
738254225Speter
739254225Speter	/*
740254225Speter	 * Stick the current context information in a convenient place, we're
741254225Speter	 * about to lose it.  Note, if we're called on editor startup, there
742254225Speter	 * will be no FREF structure.
743254225Speter	 */
744254225Speter	frp = sp->frp;
745254225Speter	lno = sp->lno;
746254225Speter	cno = sp->cno;
747254225Speter	istmp = frp == NULL ||
748254225Speter	    (F_ISSET(frp, FR_TMPFILE) && !new_screen);
749254225Speter
750254225Speter	/* Try to switch to the preset tag. */
751254225Speter	if (new_screen) {
752254225Speter		if (ex_tag_Nswitch(sp, tqp->current, force))
753254225Speter			goto err;
754254225Speter
755254225Speter		/* Everything else gets done in the new screen. */
756254225Speter		sp = sp->nextdisp;
757254225Speter		exp = EXP(sp);
758254225Speter	} else
759254225Speter		if (ex_tag_nswitch(sp, tqp->current, force))
760254225Speter			goto err;
761254225Speter
762254225Speter	/*
763254225Speter	 * If this is the first tag, put a `current location' queue entry
764254225Speter	 * in place, so we can pop all the way back to the current mark.
765254225Speter	 * Note, it doesn't point to much of anything, it's a placeholder.
766254225Speter	 */
767254225Speter	if (TAILQ_EMPTY(exp->tq)) {
768254225Speter		TAILQ_INSERT_HEAD(exp->tq, rtqp, q);
769254225Speter	} else
770254225Speter		rtqp = TAILQ_FIRST(exp->tq);
771254225Speter
772254225Speter	/* Link the new TAGQ structure into place. */
773254225Speter	TAILQ_INSERT_HEAD(exp->tq, tqp, q);
774254225Speter
775254225Speter	(void)ctag_search(sp,
776254225Speter	    tqp->current->search, tqp->current->slen, tqp->tag);
777254225Speter	if (tqp->current->msg) {
778254225Speter	    INT2CHAR(sp, tqp->current->msg, tqp->current->mlen + 1,
779254225Speter		     np, nlen);
780254225Speter	    msgq(sp, M_INFO, "%s", np);
781254225Speter	}
782254225Speter
783254225Speter	/*
784254225Speter	 * Move the current context from the temporary save area into the
785254225Speter	 * right structure.
786254225Speter	 *
787254225Speter	 * If we were in a temporary file, we don't have a context to which
788254225Speter	 * we can return, so just make it be the same as what we're moving
789254225Speter	 * to.  It will be a little odd that ^T doesn't change anything, but
790254225Speter	 * I don't think it's a big deal.
791254225Speter	 */
792254225Speter	if (istmp) {
793254225Speter		rtqp->current->frp = sp->frp;
794254225Speter		rtqp->current->lno = sp->lno;
795254225Speter		rtqp->current->cno = sp->cno;
796254225Speter	} else {
797254225Speter		rtqp->current->frp = frp;
798254225Speter		rtqp->current->lno = lno;
799254225Speter		rtqp->current->cno = cno;
800254225Speter	}
801254225Speter	return (0);
802254225Speter
803254225Spetererr:
804254225Speteralloc_err:
805254225Speter	if (rtqp != NULL)
806254225Speter		free(rtqp);
807254225Speter	if (rtp != NULL)
808254225Speter		free(rtp);
809254225Speter	tagq_free(sp, tqp);
810254225Speter	return (1);
811254225Speter}
812254225Speter
813254225Speter/*
81419304Speter * tag_msg
81519304Speter *	A few common messages.
81619304Speter *
817281373Sbapt * PUBLIC: void tag_msg(SCR *, tagmsg_t, char *);
81819304Speter */
81919304Spetervoid
820254225Spetertag_msg(SCR *sp, tagmsg_t msg, char *tag)
82119304Speter{
82219304Speter	switch (msg) {
82319304Speter	case TAG_BADLNO:
82419304Speter		msgq_str(sp, M_ERR, tag,
82519304Speter	    "164|%s: the tag's line number is past the end of the file");
82619304Speter		break;
82719304Speter	case TAG_EMPTY:
82819304Speter		msgq(sp, M_INFO, "165|The tags stack is empty");
82919304Speter		break;
83019304Speter	case TAG_SEARCH:
83119304Speter		msgq_str(sp, M_ERR, tag, "166|%s: search pattern not found");
83219304Speter		break;
83319304Speter	default:
83419304Speter		abort();
83519304Speter	}
83619304Speter}
83719304Speter
83819304Speter/*
83919304Speter * ex_tagf_alloc --
84019304Speter *	Create a new list of ctag files.
84119304Speter *
842281373Sbapt * PUBLIC: int ex_tagf_alloc(SCR *, char *);
84319304Speter */
84419304Speterint
845254225Speterex_tagf_alloc(SCR *sp, char *str)
84619304Speter{
84719304Speter	EX_PRIVATE *exp;
84819304Speter	TAGF *tfp;
84919304Speter	size_t len;
85019304Speter	char *p, *t;
85119304Speter
85219304Speter	/* Free current queue. */
85319304Speter	exp = EXP(sp);
854254225Speter	while ((tfp = TAILQ_FIRST(exp->tagfq)) != NULL)
85519304Speter		tagf_free(sp, tfp);
85619304Speter
85719304Speter	/* Create new queue. */
85819304Speter	for (p = t = str;; ++p) {
859254225Speter		if (*p == '\0' || cmdskip(*p)) {
860254225Speter			if ((len = p - t)) {
86119304Speter				MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
86219304Speter				MALLOC(sp, tfp->name, char *, len + 1);
86319304Speter				if (tfp->name == NULL) {
86419304Speter					free(tfp);
86519304Speter					return (1);
86619304Speter				}
86719304Speter				memcpy(tfp->name, t, len);
86819304Speter				tfp->name[len] = '\0';
86919304Speter				tfp->flags = 0;
870254225Speter				TAILQ_INSERT_TAIL(exp->tagfq, tfp, q);
87119304Speter			}
87219304Speter			t = p + 1;
87319304Speter		}
87419304Speter		if (*p == '\0')
87519304Speter			 break;
87619304Speter	}
87719304Speter	return (0);
87819304Speter}
87919304Speter						/* Free previous queue. */
88019304Speter/*
88119304Speter * ex_tag_free --
88219304Speter *	Free the ex tag information.
88319304Speter *
884281373Sbapt * PUBLIC: int ex_tag_free(SCR *);
88519304Speter */
88619304Speterint
887254225Speterex_tag_free(SCR *sp)
88819304Speter{
88919304Speter	EX_PRIVATE *exp;
89019304Speter	TAGF *tfp;
89119304Speter	TAGQ *tqp;
89219304Speter
89319304Speter	/* Free up tag information. */
89419304Speter	exp = EXP(sp);
895254225Speter	while ((tqp = TAILQ_FIRST(exp->tq)) != NULL)
89619304Speter		tagq_free(sp, tqp);
897254225Speter	while ((tfp = TAILQ_FIRST(exp->tagfq)) != NULL)
89819304Speter		tagf_free(sp, tfp);
89919304Speter	if (exp->tag_last != NULL)
90019304Speter		free(exp->tag_last);
90119304Speter	return (0);
90219304Speter}
90319304Speter
90419304Speter/*
90519304Speter * ctag_search --
90619304Speter *	Search a file for a tag.
90719304Speter */
90819304Speterstatic int
909254225Speterctag_search(SCR *sp, CHAR_T *search, size_t slen, char *tag)
91019304Speter{
91119304Speter	MARK m;
91219304Speter	char *p;
913254225Speter	char *np;
914254225Speter	size_t nlen;
91519304Speter
91619304Speter	/*
91719304Speter	 * !!!
91819304Speter	 * The historic tags file format (from a long, long time ago...)
91919304Speter	 * used a line number, not a search string.  I got complaints, so
92019304Speter	 * people are still using the format.  POSIX 1003.2 permits it.
92119304Speter	 */
922254225Speter	if (ISDIGIT(search[0])) {
923254225Speter		INT2CHAR(sp, search, slen+1, np, nlen);
924254225Speter		m.lno = atoi(np);
92519304Speter		if (!db_exist(sp, m.lno)) {
92619304Speter			tag_msg(sp, TAG_BADLNO, tag);
92719304Speter			return (1);
92819304Speter		}
92919304Speter	} else {
93019304Speter		/*
93119304Speter		 * Search for the tag; cheap fallback for C functions
93219304Speter		 * if the name is the same but the arguments have changed.
93319304Speter		 */
93419304Speter		m.lno = 1;
93519304Speter		m.cno = 0;
93619304Speter		if (f_search(sp, &m, &m,
937254225Speter		    search, slen, NULL, SEARCH_FILE | SEARCH_TAG)) {
938254225Speter			INT2CHAR(sp, search, slen, np, nlen);
939254225Speter			if ((p = strrchr(np, '(')) != NULL) {
940254225Speter				slen = p - np;
94119304Speter				if (f_search(sp, &m, &m, search, slen,
94219304Speter				    NULL, SEARCH_FILE | SEARCH_TAG))
94319304Speter					goto notfound;
94419304Speter			} else {
94519304Speternotfound:			tag_msg(sp, TAG_SEARCH, tag);
94619304Speter				return (1);
94719304Speter			}
948254225Speter		}
94919304Speter		/*
95019304Speter		 * !!!
95119304Speter		 * Historically, tags set the search direction if it wasn't
95219304Speter		 * already set.
95319304Speter		 */
95419304Speter		if (sp->searchdir == NOTSET)
95519304Speter			sp->searchdir = FORWARD;
95619304Speter	}
95719304Speter
95819304Speter	/*
95919304Speter	 * !!!
96019304Speter	 * Tags move to the first non-blank, NOT the search pattern start.
96119304Speter	 */
96219304Speter	sp->lno = m.lno;
96319304Speter	sp->cno = 0;
96419304Speter	(void)nonblank(sp, sp->lno, &sp->cno);
96519304Speter	return (0);
96619304Speter}
96719304Speter
96819304Speter/*
96919304Speter * ctag_slist --
97019304Speter *	Search the list of tags files for a tag, and return tag queue.
97119304Speter */
97219304Speterstatic TAGQ *
973254225Speterctag_slist(SCR *sp, CHAR_T *tag)
97419304Speter{
97519304Speter	EX_PRIVATE *exp;
97619304Speter	TAGF *tfp;
97719304Speter	TAGQ *tqp;
97819304Speter	size_t len;
979254225Speter	int echk = 0;
980254225Speter	char *np;
981254225Speter	size_t nlen;
98219304Speter
98319304Speter	exp = EXP(sp);
98419304Speter
98519304Speter	/* Allocate and initialize the tag queue structure. */
986254225Speter	INT2CHAR(sp, tag, STRLEN(tag) + 1, np, nlen);
987254225Speter	len = nlen - 1;
98819304Speter	CALLOC_GOTO(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + len + 1);
989254225Speter	TAILQ_INIT(tqp->tagq);
99019304Speter	tqp->tag = tqp->buf;
991254225Speter	memcpy(tqp->tag, np, (tqp->tlen = len) + 1);
99219304Speter
99319304Speter	/*
99419304Speter	 * Find the tag, only display missing file messages once, and
99519304Speter	 * then only if we didn't find the tag.
99619304Speter	 */
997254225Speter	TAILQ_FOREACH(tfp, exp->tagfq, q)
998254225Speter		if (ctag_sfile(sp, tfp, tqp, tqp->tag)) {
99919304Speter			echk = 1;
100019304Speter			F_SET(tfp, TAGF_ERR);
100119304Speter		} else
100219304Speter			F_CLR(tfp, TAGF_ERR | TAGF_ERR_WARN);
100319304Speter
100419304Speter	/* Check to see if we found anything. */
1005254225Speter	if (TAILQ_EMPTY(tqp->tagq)) {
1006254225Speter		msgq_str(sp, M_ERR, tqp->tag, "162|%s: tag not found");
100719304Speter		if (echk)
1008254225Speter			TAILQ_FOREACH(tfp, exp->tagfq, q)
100919304Speter				if (F_ISSET(tfp, TAGF_ERR) &&
101019304Speter				    !F_ISSET(tfp, TAGF_ERR_WARN)) {
101119304Speter					errno = tfp->errnum;
101219304Speter					msgq_str(sp, M_SYSERR, tfp->name, "%s");
101319304Speter					F_SET(tfp, TAGF_ERR_WARN);
101419304Speter				}
101519304Speter		free(tqp);
101619304Speter		return (NULL);
101719304Speter	}
101819304Speter
101919304Speter	return (tqp);
102019304Speter
102119304Speteralloc_err:
102219304Speter	return (NULL);
102319304Speter}
102419304Speter
102519304Speter/*
102619304Speter * ctag_sfile --
102719304Speter *	Search a tags file for a tag, adding any found to the tag queue.
102819304Speter */
102919304Speterstatic int
1030254225Speterctag_sfile(SCR *sp, TAGF *tfp, TAGQ *tqp, char *tname)
103119304Speter{
103219304Speter	struct stat sb;
103319304Speter	TAG *tp;
1034254225Speter	size_t dlen, nlen = 0, slen;
103519304Speter	int fd, i, nf1, nf2;
1036254225Speter	char *back, *front, *map, *p, *search, *t;
1037254225Speter	char *cname = NULL, *dname = NULL, *name = NULL;
1038254225Speter	CHAR_T *wp;
1039254225Speter	size_t wlen;
1040254225Speter	long tl;
104119304Speter
104219304Speter	if ((fd = open(tfp->name, O_RDONLY, 0)) < 0) {
104319304Speter		tfp->errnum = errno;
104419304Speter		return (1);
104519304Speter	}
104619304Speter
104719304Speter	if (fstat(fd, &sb) != 0 ||
1048254225Speter	    (map = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE,
1049254225Speter	    MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
105019304Speter		tfp->errnum = errno;
105119304Speter		(void)close(fd);
105219304Speter		return (1);
105319304Speter	}
105419304Speter
1055254225Speter	tl = O_VAL(sp, O_TAGLENGTH);
105619304Speter	front = map;
105719304Speter	back = front + sb.st_size;
105819304Speter	front = binary_search(tname, front, back);
1059254225Speter	front = linear_search(tname, front, back, tl);
106019304Speter	if (front == NULL)
106119304Speter		goto done;
106219304Speter
106319304Speter	/*
106419304Speter	 * Initialize and link in the tag structure(s).  The historic ctags
106519304Speter	 * file format only permitted a single tag location per tag.  The
106619304Speter	 * obvious extension to permit multiple tags locations per tag is to
106719304Speter	 * output multiple records in the standard format.  Unfortunately,
106819304Speter	 * this won't work correctly with historic ex/vi implementations,
106919304Speter	 * because their binary search assumes that there's only one record
107019304Speter	 * per tag, and so will use a random tag entry if there si more than
107119304Speter	 * one.  This code handles either format.
107219304Speter	 *
107319304Speter	 * The tags file is in the following format:
107419304Speter	 *
107519304Speter	 *	<tag> <filename> <line number> | <pattern>
107619304Speter	 *
107719304Speter	 * Figure out how long everything is so we can allocate in one swell
107819304Speter	 * foop, but discard anything that looks wrong.
107919304Speter	 */
108019304Speter	for (;;) {
108119304Speter		/* Nul-terminate the end of the line. */
108219304Speter		for (p = front; p < back && *p != '\n'; ++p);
108319304Speter		if (p == back || *p != '\n')
108419304Speter			break;
108519304Speter		*p = '\0';
108619304Speter
108719304Speter		/* Update the pointers for the next time. */
108819304Speter		t = p + 1;
108919304Speter		p = front;
109019304Speter		front = t;
109119304Speter
109219304Speter		/* Break the line into tokens. */
109319304Speter		for (i = 0; i < 2 && (t = strsep(&p, "\t ")) != NULL; ++i)
109419304Speter			switch (i) {
109519304Speter			case 0:			/* Tag. */
109619304Speter				cname = t;
109719304Speter				break;
109819304Speter			case 1:			/* Filename. */
109919304Speter				name = t;
110019304Speter				nlen = strlen(name);
110119304Speter				break;
110219304Speter			}
110319304Speter
110419304Speter		/* Check for corruption. */
110519304Speter		if (i != 2 || p == NULL || t == NULL)
110619304Speter			goto corrupt;
110719304Speter
110819304Speter		/* The rest of the string is the search pattern. */
110919304Speter		search = p;
111019304Speter		if ((slen = strlen(p)) == 0) {
111119304Spetercorrupt:		p = msg_print(sp, tname, &nf1);
111219304Speter			t = msg_print(sp, tfp->name, &nf2);
111319304Speter			msgq(sp, M_ERR, "163|%s: corrupted tag in %s", p, t);
111419304Speter			if (nf1)
111519304Speter				FREE_SPACE(sp, p, 0);
111619304Speter			if (nf2)
111719304Speter				FREE_SPACE(sp, t, 0);
111819304Speter			continue;
111919304Speter		}
112019304Speter
112119304Speter		/* Check for passing the last entry. */
1122254225Speter		if (tl ? strncmp(tname, cname, tl) : strcmp(tname, cname))
112319304Speter			break;
112419304Speter
112519304Speter		/* Resolve the file name. */
112619304Speter		ctag_file(sp, tfp, name, &dname, &dlen);
112719304Speter
112819304Speter		CALLOC_GOTO(sp, tp,
1129254225Speter		    TAG *, 1, sizeof(TAG) + dlen + 2 + nlen + 1 +
1130254225Speter		    (slen + 1) * sizeof(CHAR_T));
1131254225Speter		tp->fname = (char *)tp->buf;
1132254225Speter		if (dlen == 1 && *dname == '.')
1133254225Speter			--dlen;
1134254225Speter		else if (dlen != 0) {
113519304Speter			memcpy(tp->fname, dname, dlen);
113619304Speter			tp->fname[dlen] = '/';
113719304Speter			++dlen;
113819304Speter		}
113919304Speter		memcpy(tp->fname + dlen, name, nlen + 1);
114019304Speter		tp->fnlen = dlen + nlen;
1141254225Speter		tp->search = (CHAR_T*)(tp->fname + tp->fnlen + 1);
1142254225Speter		CHAR2INT(sp, search, slen + 1, wp, wlen);
1143254225Speter		MEMCPY(tp->search, wp, (tp->slen = slen) + 1);
1144254225Speter		TAILQ_INSERT_TAIL(tqp->tagq, tp, q);
1145254225Speter
1146254225Speter		/* Try to preset the tag within the current file. */
1147254225Speter		if (sp->frp != NULL && sp->frp->name != NULL &&
1148254225Speter		    tqp->current == NULL && !strcmp(tp->fname, sp->frp->name))
1149254225Speter			tqp->current = tp;
115019304Speter	}
115119304Speter
1152254225Speter	if (tqp->current == NULL)
1153254225Speter		tqp->current = TAILQ_FIRST(tqp->tagq);
1154254225Speter
115519304Speteralloc_err:
1156254225Speterdone:	if (munmap(map, sb.st_size))
115719304Speter		msgq(sp, M_SYSERR, "munmap");
115819304Speter	if (close(fd))
115919304Speter		msgq(sp, M_SYSERR, "close");
116019304Speter	return (0);
116119304Speter}
116219304Speter
116319304Speter/*
116419304Speter * ctag_file --
116519304Speter *	Search for the right path to this file.
116619304Speter */
116719304Speterstatic void
1168254225Speterctag_file(SCR *sp, TAGF *tfp, char *name, char **dirp, size_t *dlenp)
116919304Speter{
117019304Speter	struct stat sb;
1171254225Speter	char *p, *buf;
117219304Speter
117319304Speter	/*
117419304Speter	 * !!!
117519304Speter	 * If the tag file path is a relative path, see if it exists.  If it
117619304Speter	 * doesn't, look relative to the tags file path.  It's okay for a tag
117719304Speter	 * file to not exist, and historically, vi simply displayed a "new"
117819304Speter	 * file.  However, if the path exists relative to the tag file, it's
117919304Speter	 * pretty clear what's happening, so we may as well get it right.
118019304Speter	 */
118119304Speter	*dlenp = 0;
118219304Speter	if (name[0] != '/' &&
118319304Speter	    stat(name, &sb) && (p = strrchr(tfp->name, '/')) != NULL) {
118419304Speter		*p = '\0';
1185254225Speter		if ((buf = join(tfp->name, name)) == NULL) {
1186254225Speter			msgq(sp, M_SYSERR, NULL);
1187254225Speter			return;
1188254225Speter		}
118919304Speter		if (stat(buf, &sb) == 0) {
119019304Speter			*dirp = tfp->name;
119119304Speter			*dlenp = strlen(*dirp);
119219304Speter		}
1193254225Speter		free(buf);
1194254225Speter		*p = '/';
119519304Speter	}
119619304Speter}
119719304Speter
119819304Speter/*
119919304Speter * Binary search for "string" in memory between "front" and "back".
120019304Speter *
120119304Speter * This routine is expected to return a pointer to the start of a line at
120219304Speter * *or before* the first word matching "string".  Relaxing the constraint
120319304Speter * this way simplifies the algorithm.
120419304Speter *
120519304Speter * Invariants:
120619304Speter * 	front points to the beginning of a line at or before the first
120719304Speter *	matching string.
120819304Speter *
120919304Speter * 	back points to the beginning of a line at or after the first
121019304Speter *	matching line.
121119304Speter *
121219304Speter * Base of the Invariants.
121319304Speter * 	front = NULL;
121419304Speter *	back = EOF;
121519304Speter *
121619304Speter * Advancing the Invariants:
121719304Speter *
121819304Speter * 	p = first newline after halfway point from front to back.
121919304Speter *
122019304Speter * 	If the string at "p" is not greater than the string to match,
122119304Speter *	p is the new front.  Otherwise it is the new back.
122219304Speter *
122319304Speter * Termination:
122419304Speter *
122519304Speter * 	The definition of the routine allows it return at any point,
122619304Speter *	since front is always at or before the line to print.
122719304Speter *
122819304Speter * 	In fact, it returns when the chosen "p" equals "back".  This
122919304Speter *	implies that there exists a string is least half as long as
123019304Speter *	(back - front), which in turn implies that a linear search will
123119304Speter *	be no more expensive than the cost of simply printing a string or two.
123219304Speter *
123319304Speter * 	Trying to continue with binary search at this point would be
123419304Speter *	more trouble than it's worth.
123519304Speter */
123619304Speter#define	EQUAL		0
123719304Speter#define	GREATER		1
123819304Speter#define	LESS		(-1)
123919304Speter
1240254225Speter#define	SKIP_PAST_NEWLINE(p, back)					\
1241254225Speter	while (p < back && *p++ != '\n') continue;
124219304Speter
124319304Speterstatic char *
1244281373Sbaptbinary_search(char *string, char *front, char *back)
124519304Speter{
1246281373Sbapt	char *p;
124719304Speter
124819304Speter	p = front + (back - front) / 2;
124919304Speter	SKIP_PAST_NEWLINE(p, back);
125019304Speter
125119304Speter	while (p != back) {
125219304Speter		if (compare(string, p, back) == GREATER)
125319304Speter			front = p;
125419304Speter		else
125519304Speter			back = p;
125619304Speter		p = front + (back - front) / 2;
125719304Speter		SKIP_PAST_NEWLINE(p, back);
125819304Speter	}
125919304Speter	return (front);
126019304Speter}
126119304Speter
126219304Speter/*
126319304Speter * Find the first line that starts with string, linearly searching from front
126419304Speter * to back.
126519304Speter *
126619304Speter * Return NULL for no such line.
126719304Speter *
126819304Speter * This routine assumes:
126919304Speter *
127019304Speter * 	o front points at the first character in a line.
127119304Speter *	o front is before or at the first line to be printed.
127219304Speter */
127319304Speterstatic char *
1274254225Speterlinear_search(char *string, char *front, char *back, long tl)
127519304Speter{
1276254225Speter	char *end;
127719304Speter	while (front < back) {
1278254225Speter		end = tl && back-front > tl ? front+tl : back;
1279254225Speter		switch (compare(string, front, end)) {
128019304Speter		case EQUAL:		/* Found it. */
128119304Speter			return (front);
128219304Speter		case LESS:		/* No such string. */
128319304Speter			return (NULL);
128419304Speter		case GREATER:		/* Keep going. */
128519304Speter			break;
128619304Speter		}
128719304Speter		SKIP_PAST_NEWLINE(front, back);
128819304Speter	}
128919304Speter	return (NULL);
129019304Speter}
129119304Speter
129219304Speter/*
129319304Speter * Return LESS, GREATER, or EQUAL depending on how the string1 compares
129419304Speter * with string2 (s1 ??? s2).
129519304Speter *
129619304Speter * 	o Matches up to len(s1) are EQUAL.
129719304Speter *	o Matches up to len(s2) are GREATER.
129819304Speter *
129919304Speter * The string "s1" is null terminated.  The string s2 is '\t', space, (or
130019304Speter * "back") terminated.
130119304Speter *
130219304Speter * !!!
130319304Speter * Reasonably modern ctags programs use tabs as separators, not spaces.
130419304Speter * However, historic programs did use spaces, and, I got complaints.
130519304Speter */
130619304Speterstatic int
1307281373Sbaptcompare(char *s1, char *s2, char *back)
130819304Speter{
130919304Speter	for (; *s1 && s2 < back && (*s2 != '\t' && *s2 != ' '); ++s1, ++s2)
131019304Speter		if (*s1 != *s2)
131119304Speter			return (*s1 < *s2 ? LESS : GREATER);
131219304Speter	return (*s1 ? GREATER : s2 < back &&
131319304Speter	    (*s2 != '\t' && *s2 != ' ') ? LESS : EQUAL);
131419304Speter}
1315