ex_tag.c revision 19304
1/*-
2 * Copyright (c) 1992, 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 * Copyright (c) 1992, 1993, 1994, 1995, 1996
5 *	Keith Bostic.  All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * David Hitz of Auspex Systems, Inc.
9 *
10 * See the LICENSE file for redistribution information.
11 */
12
13#include "config.h"
14
15#ifndef lint
16static const char sccsid[] = "@(#)ex_tag.c	10.36 (Berkeley) 9/15/96";
17#endif /* not lint */
18
19#include <sys/param.h>
20#include <sys/types.h>		/* XXX: param.h may not have included types.h */
21
22#ifdef HAVE_SYS_MMAN_H
23#include <sys/mman.h>
24#endif
25
26#include <sys/queue.h>
27#include <sys/stat.h>
28#include <sys/time.h>
29
30#include <bitstring.h>
31#include <ctype.h>
32#include <errno.h>
33#include <fcntl.h>
34#include <limits.h>
35#include <stddef.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <unistd.h>
40
41#include "../common/common.h"
42#include "../vi/vi.h"
43#include "tag.h"
44
45static char	*binary_search __P((char *, char *, char *));
46static int	 compare __P((char *, char *, char *));
47static void	 ctag_file __P((SCR *, TAGF *, char *, char **, size_t *));
48static int	 ctag_search __P((SCR *, char *, size_t, char *));
49static int	 ctag_sfile __P((SCR *, TAGF *, TAGQ *, char *));
50static TAGQ	*ctag_slist __P((SCR *, char *));
51static char	*linear_search __P((char *, char *, char *));
52static int	 tag_copy __P((SCR *, TAG *, TAG **));
53static int	 tag_pop __P((SCR *, TAGQ *, int));
54static int	 tagf_copy __P((SCR *, TAGF *, TAGF **));
55static int	 tagf_free __P((SCR *, TAGF *));
56static int	 tagq_copy __P((SCR *, TAGQ *, TAGQ **));
57
58/*
59 * ex_tag_first --
60 *	The tag code can be entered from main, e.g., "vi -t tag".
61 *
62 * PUBLIC: int ex_tag_first __P((SCR *, char *));
63 */
64int
65ex_tag_first(sp, tagarg)
66	SCR *sp;
67	char *tagarg;
68{
69	ARGS *ap[2], a;
70	EXCMD cmd;
71
72	/* Build an argument for the ex :tag command. */
73	ex_cinit(&cmd, C_TAG, 0, OOBLNO, OOBLNO, 0, ap);
74	ex_cadd(&cmd, &a, tagarg, strlen(tagarg));
75
76	/*
77	 * XXX
78	 * Historic vi went ahead and created a temporary file when it failed
79	 * to find the tag.  We match historic practice, but don't distinguish
80	 * between real error and failure to find the tag.
81	 */
82	if (ex_tag_push(sp, &cmd))
83		return (0);
84
85	/* Display tags in the center of the screen. */
86	F_CLR(sp, SC_SCR_TOP);
87	F_SET(sp, SC_SCR_CENTER);
88
89	return (0);
90}
91
92/*
93 * ex_tag_push -- ^]
94 *		  :tag[!] [string]
95 *
96 * Enter a new TAGQ context based on a ctag string.
97 *
98 * PUBLIC: int ex_tag_push __P((SCR *, EXCMD *));
99 */
100int
101ex_tag_push(sp, cmdp)
102	SCR *sp;
103	EXCMD *cmdp;
104{
105	EX_PRIVATE *exp;
106	FREF *frp;
107	TAG *rtp;
108	TAGQ *rtqp, *tqp;
109	recno_t lno;
110	size_t cno;
111	long tl;
112	int force, istmp;
113
114	exp = EXP(sp);
115	switch (cmdp->argc) {
116	case 1:
117		if (exp->tag_last != NULL)
118			free(exp->tag_last);
119
120		if ((exp->tag_last = strdup(cmdp->argv[0]->bp)) == NULL) {
121			msgq(sp, M_SYSERR, NULL);
122			return (1);
123		}
124
125		/* Taglength may limit the number of characters. */
126		if ((tl =
127		    O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(exp->tag_last) > tl)
128			exp->tag_last[tl] = '\0';
129		break;
130	case 0:
131		if (exp->tag_last == NULL) {
132			msgq(sp, M_ERR, "158|No previous tag entered");
133			return (1);
134		}
135		break;
136	default:
137		abort();
138	}
139
140	/* Get the tag information. */
141	if ((tqp = ctag_slist(sp, exp->tag_last)) == NULL)
142		return (1);
143
144	/*
145	 * Allocate all necessary memory before swapping screens.  Initialize
146	 * flags so we know what to free.
147	 */
148	rtp = NULL;
149	rtqp = NULL;
150	if (exp->tq.cqh_first == (void *)&exp->tq) {
151		/* Initialize the `local context' tag queue structure. */
152		CALLOC_GOTO(sp, rtqp, TAGQ *, 1, sizeof(TAGQ));
153		CIRCLEQ_INIT(&rtqp->tagq);
154
155		/* Initialize and link in its tag structure. */
156		CALLOC_GOTO(sp, rtp, TAG *, 1, sizeof(TAG));
157		CIRCLEQ_INSERT_HEAD(&rtqp->tagq, rtp, q);
158		rtqp->current = rtp;
159	}
160
161	/*
162	 * Stick the current context information in a convenient place, we're
163	 * about to lose it.  Note, if we're called on editor startup, there
164	 * will be no FREF structure.
165	 */
166	frp = sp->frp;
167	lno = sp->lno;
168	cno = sp->cno;
169	istmp = frp == NULL ||
170	    F_ISSET(frp, FR_TMPFILE) && !F_ISSET(cmdp, E_NEWSCREEN);
171
172	/* Try to switch to the tag. */
173	force = FL_ISSET(cmdp->iflags, E_C_FORCE);
174	if (F_ISSET(cmdp, E_NEWSCREEN)) {
175		if (ex_tag_Nswitch(sp, tqp->tagq.cqh_first, force))
176			goto err;
177
178		/* Everything else gets done in the new screen. */
179		sp = sp->nextdisp;
180		exp = EXP(sp);
181	} else
182		if (ex_tag_nswitch(sp, tqp->tagq.cqh_first, force))
183			goto err;
184
185	/*
186	 * If this is the first tag, put a `current location' queue entry
187	 * in place, so we can pop all the way back to the current mark.
188	 * Note, it doesn't point to much of anything, it's a placeholder.
189	 */
190	if (exp->tq.cqh_first == (void *)&exp->tq) {
191		CIRCLEQ_INSERT_HEAD(&exp->tq, rtqp, q);
192	} else
193		rtqp = exp->tq.cqh_first;
194
195	/* Link the new TAGQ structure into place. */
196	CIRCLEQ_INSERT_HEAD(&exp->tq, tqp, q);
197
198	(void)ctag_search(sp,
199	    tqp->current->search, tqp->current->slen, tqp->tag);
200
201	/*
202	 * Move the current context from the temporary save area into the
203	 * right structure.
204	 *
205	 * If we were in a temporary file, we don't have a context to which
206	 * we can return, so just make it be the same as what we're moving
207	 * to.  It will be a little odd that ^T doesn't change anything, but
208	 * I don't think it's a big deal.
209	 */
210	if (istmp) {
211		rtqp->current->frp = sp->frp;
212		rtqp->current->lno = sp->lno;
213		rtqp->current->cno = sp->cno;
214	} else {
215		rtqp->current->frp = frp;
216		rtqp->current->lno = lno;
217		rtqp->current->cno = cno;
218	}
219	return (0);
220
221err:
222alloc_err:
223	if (rtqp != NULL)
224		free(rtqp);
225	if (rtp != NULL)
226		free(rtp);
227	tagq_free(sp, tqp);
228	return (1);
229}
230
231/*
232 * ex_tag_next --
233 *	Switch context to the next TAG.
234 *
235 * PUBLIC: int ex_tag_next __P((SCR *, EXCMD *));
236 */
237int
238ex_tag_next(sp, cmdp)
239	SCR *sp;
240	EXCMD *cmdp;
241{
242	EX_PRIVATE *exp;
243	TAG *tp;
244	TAGQ *tqp;
245
246	exp = EXP(sp);
247	if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
248		tag_msg(sp, TAG_EMPTY, NULL);
249		return (1);
250	}
251	if ((tp = tqp->current->q.cqe_next) == (void *)&tqp->tagq) {
252		msgq(sp, M_ERR, "282|Already at the last tag of this group");
253		return (1);
254	}
255	if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
256		return (1);
257	tqp->current = tp;
258
259	if (F_ISSET(tqp, TAG_CSCOPE))
260		(void)cscope_search(sp, tqp, tp);
261	else
262		(void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
263	return (0);
264}
265
266/*
267 * ex_tag_prev --
268 *	Switch context to the next TAG.
269 *
270 * PUBLIC: int ex_tag_prev __P((SCR *, EXCMD *));
271 */
272int
273ex_tag_prev(sp, cmdp)
274	SCR *sp;
275	EXCMD *cmdp;
276{
277	EX_PRIVATE *exp;
278	TAG *tp;
279	TAGQ *tqp;
280
281	exp = EXP(sp);
282	if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
283		tag_msg(sp, TAG_EMPTY, NULL);
284		return (0);
285	}
286	if ((tp = tqp->current->q.cqe_prev) == (void *)&tqp->tagq) {
287		msgq(sp, M_ERR, "255|Already at the first tag of this group");
288		return (1);
289	}
290	if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
291		return (1);
292	tqp->current = tp;
293
294	if (F_ISSET(tqp, TAG_CSCOPE))
295		(void)cscope_search(sp, tqp, tp);
296	else
297		(void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
298	return (0);
299}
300
301/*
302 * ex_tag_nswitch --
303 *	Switch context to the specified TAG.
304 *
305 * PUBLIC: int ex_tag_nswitch __P((SCR *, TAG *, int));
306 */
307int
308ex_tag_nswitch(sp, tp, force)
309	SCR *sp;
310	TAG *tp;
311	int force;
312{
313	/* Get a file structure. */
314	if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)
315		return (1);
316
317	/* If not changing files, return, we're done. */
318	if (tp->frp == sp->frp)
319		return (0);
320
321	/* Check for permission to leave. */
322	if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
323		return (1);
324
325	/* Initialize the new file. */
326	if (file_init(sp, tp->frp, NULL, FS_SETALT))
327		return (1);
328
329	/* Display tags in the center of the screen. */
330	F_CLR(sp, SC_SCR_TOP);
331	F_SET(sp, SC_SCR_CENTER);
332
333	/* Switch. */
334	F_SET(sp, SC_FSWITCH);
335	return (0);
336}
337
338/*
339 * ex_tag_Nswitch --
340 *	Switch context to the specified TAG in a new screen.
341 *
342 * PUBLIC: int ex_tag_Nswitch __P((SCR *, TAG *, int));
343 */
344int
345ex_tag_Nswitch(sp, tp, force)
346	SCR *sp;
347	TAG *tp;
348	int force;
349{
350	SCR *new;
351
352	/* Get a file structure. */
353	if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)
354		return (1);
355
356	/* Get a new screen. */
357	if (screen_init(sp->gp, sp, &new))
358		return (1);
359	if (vs_split(sp, new, 0)) {
360		(void)file_end(new, new->ep, 1);
361		(void)screen_end(new);
362		return (1);
363	}
364
365	/* Get a backing file. */
366	if (tp->frp == sp->frp) {
367		/* Copy file state. */
368		new->ep = sp->ep;
369		++new->ep->refcnt;
370
371		new->frp = tp->frp;
372		new->frp->flags = sp->frp->flags;
373	} else if (file_init(new, tp->frp, NULL, force)) {
374		(void)vs_discard(new, NULL);
375		(void)screen_end(new);
376		return (1);
377	}
378
379	/* Create the argument list. */
380	new->cargv = new->argv = ex_buildargv(sp, NULL, tp->frp->name);
381
382	/* Display tags in the center of the screen. */
383	F_CLR(new, SC_SCR_TOP);
384	F_SET(new, SC_SCR_CENTER);
385
386	/* Switch. */
387	sp->nextdisp = new;
388	F_SET(sp, SC_SSWITCH);
389
390	return (0);
391}
392
393/*
394 * ex_tag_pop -- ^T
395 *		 :tagp[op][!] [number | file]
396 *
397 *	Pop to a previous TAGQ context.
398 *
399 * PUBLIC: int ex_tag_pop __P((SCR *, EXCMD *));
400 */
401int
402ex_tag_pop(sp, cmdp)
403	SCR *sp;
404	EXCMD *cmdp;
405{
406	EX_PRIVATE *exp;
407	TAGQ *tqp, *dtqp;
408	size_t arglen;
409	long off;
410	char *arg, *p, *t;
411
412	/* Check for an empty stack. */
413	exp = EXP(sp);
414	if (exp->tq.cqh_first == (void *)&exp->tq) {
415		tag_msg(sp, TAG_EMPTY, NULL);
416		return (1);
417	}
418
419	/* Find the last TAG structure that we're going to DISCARD! */
420	switch (cmdp->argc) {
421	case 0:				/* Pop one tag. */
422		dtqp = exp->tq.cqh_first;
423		break;
424	case 1:				/* Name or number. */
425		arg = cmdp->argv[0]->bp;
426		off = strtol(arg, &p, 10);
427		if (*p != '\0')
428			goto filearg;
429
430		/* Number: pop that many queue entries. */
431		if (off < 1)
432			return (0);
433		for (tqp = exp->tq.cqh_first;
434		    tqp != (void *)&exp->tq && --off > 1;
435		    tqp = tqp->q.cqe_next);
436		if (tqp == (void *)&exp->tq) {
437			msgq(sp, M_ERR,
438	"159|Less than %s entries on the tags stack; use :display t[ags]",
439			    arg);
440			return (1);
441		}
442		dtqp = tqp;
443		break;
444
445		/* File argument: pop to that queue entry. */
446filearg:	arglen = strlen(arg);
447		for (tqp = exp->tq.cqh_first;
448		    tqp != (void *)&exp->tq;
449		    dtqp = tqp, tqp = tqp->q.cqe_next) {
450			/* Don't pop to the current file. */
451			if (tqp == exp->tq.cqh_first)
452				continue;
453			p = tqp->current->frp->name;
454			if ((t = strrchr(p, '/')) == NULL)
455				t = p;
456			else
457				++t;
458			if (!strncmp(arg, t, arglen))
459				break;
460		}
461		if (tqp == (void *)&exp->tq) {
462			msgq_str(sp, M_ERR, arg,
463	"160|No file %s on the tags stack to return to; use :display t[ags]");
464			return (1);
465		}
466		if (tqp == exp->tq.cqh_first)
467			return (0);
468		break;
469	default:
470		abort();
471	}
472
473	return (tag_pop(sp, dtqp, FL_ISSET(cmdp->iflags, E_C_FORCE)));
474}
475
476/*
477 * ex_tag_top -- :tagt[op][!]
478 *	Clear the tag stack.
479 *
480 * PUBLIC: int ex_tag_top __P((SCR *, EXCMD *));
481 */
482int
483ex_tag_top(sp, cmdp)
484	SCR *sp;
485	EXCMD *cmdp;
486{
487	EX_PRIVATE *exp;
488
489	exp = EXP(sp);
490
491	/* Check for an empty stack. */
492	if (exp->tq.cqh_first == (void *)&exp->tq) {
493		tag_msg(sp, TAG_EMPTY, NULL);
494		return (1);
495	}
496
497	/* Return to the oldest information. */
498	return (tag_pop(sp,
499	    exp->tq.cqh_last->q.cqe_prev, FL_ISSET(cmdp->iflags, E_C_FORCE)));
500}
501
502/*
503 * tag_pop --
504 *	Pop up to and including the specified TAGQ context.
505 */
506static int
507tag_pop(sp, dtqp, force)
508	SCR *sp;
509	TAGQ *dtqp;
510	int force;
511{
512	EX_PRIVATE *exp;
513	TAG *tp;
514	TAGQ *tqp;
515
516	exp = EXP(sp);
517
518	/*
519	 * Update the cursor from the saved TAG information of the TAG
520	 * structure we're moving to.
521	 */
522	tp = dtqp->q.cqe_next->current;
523	if (tp->frp == sp->frp) {
524		sp->lno = tp->lno;
525		sp->cno = tp->cno;
526	} else {
527		if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
528			return (1);
529
530		tp->frp->lno = tp->lno;
531		tp->frp->cno = tp->cno;
532		F_SET(sp->frp, FR_CURSORSET);
533		if (file_init(sp, tp->frp, NULL, FS_SETALT))
534			return (1);
535
536		F_SET(sp, SC_FSWITCH);
537	}
538
539	/* Pop entries off the queue up to and including dtqp. */
540	do {
541		tqp = exp->tq.cqh_first;
542		if (tagq_free(sp, tqp))
543			return (0);
544	} while (tqp != dtqp);
545
546	/*
547	 * If only a single tag left, we've returned to the first tag point,
548	 * and the stack is now empty.
549	 */
550	if (exp->tq.cqh_first->q.cqe_next == (void *)&exp->tq)
551		tagq_free(sp, exp->tq.cqh_first);
552
553	return (0);
554}
555
556/*
557 * ex_tag_display --
558 *	Display the list of tags.
559 *
560 * PUBLIC: int ex_tag_display __P((SCR *));
561 */
562int
563ex_tag_display(sp)
564	SCR *sp;
565{
566	EX_PRIVATE *exp;
567	TAG *tp;
568	TAGQ *tqp;
569	int cnt;
570	size_t len;
571	char *p, *sep;
572
573	exp = EXP(sp);
574	if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
575		tag_msg(sp, TAG_EMPTY, NULL);
576		return (0);
577	}
578
579	/*
580	 * We give the file name 20 columns and the search string the rest.
581	 * If there's not enough room, we don't do anything special, it's
582	 * not worth the effort, it just makes the display more confusing.
583	 *
584	 * We also assume that characters in file names map 1-1 to printing
585	 * characters.  This might not be true, but I don't think it's worth
586	 * fixing.  (The obvious fix is to pass the filenames through the
587	 * msg_print function.)
588	 */
589#define	L_NAME	30		/* Name. */
590#define	L_SLOP	 4		/* Leading number plus trailing *. */
591#define	L_SPACE	 5		/* Spaces after name, before tag. */
592#define	L_TAG	20		/* Tag. */
593	if (sp->cols <= L_NAME + L_SLOP) {
594		msgq(sp, M_ERR, "292|Display too small.");
595		return (0);
596	}
597
598	/*
599	 * Display the list of tags for each queue entry.  The first entry
600	 * is numbered, and the current tag entry has an asterisk appended.
601	 */
602	for (cnt = 1, tqp = exp->tq.cqh_first; !INTERRUPTED(sp) &&
603	    tqp != (void *)&exp->tq; ++cnt, tqp = tqp->q.cqe_next)
604		for (tp = tqp->tagq.cqh_first;
605		    tp != (void *)&tqp->tagq; tp = tp->q.cqe_next) {
606			if (tp == tqp->tagq.cqh_first)
607				(void)ex_printf(sp, "%2d ", cnt);
608			else
609				(void)ex_printf(sp, "   ");
610			p = tp->frp == NULL ? tp->fname : tp->frp->name;
611			if ((len = strlen(p)) > L_NAME) {
612				len = len - (L_NAME - 4);
613				(void)ex_printf(sp, "   ... %*.*s",
614				    L_NAME - 4, L_NAME - 4, p + len);
615			} else
616				(void)ex_printf(sp,
617				    "   %*.*s", L_NAME, L_NAME, p);
618			if (tqp->current == tp)
619				(void)ex_printf(sp, "*");
620
621			if (tp == tqp->tagq.cqh_first && tqp->tag != NULL &&
622			    (sp->cols - L_NAME) >= L_TAG + L_SPACE) {
623				len = strlen(tqp->tag);
624				if (len > sp->cols - (L_NAME + L_SPACE))
625					len = sp->cols - (L_NAME + L_SPACE);
626				(void)ex_printf(sp, "%s%.*s",
627				    tqp->current == tp ? "    " : "     ",
628				    (int)len, tqp->tag);
629			}
630			(void)ex_printf(sp, "\n");
631		}
632	return (0);
633}
634
635/*
636 * ex_tag_copy --
637 *	Copy a screen's tag structures.
638 *
639 * PUBLIC: int ex_tag_copy __P((SCR *, SCR *));
640 */
641int
642ex_tag_copy(orig, sp)
643	SCR *orig, *sp;
644{
645	EX_PRIVATE *oexp, *nexp;
646	TAGQ *aqp, *tqp;
647	TAG *ap, *tp;
648	TAGF *atfp, *tfp;
649
650	oexp = EXP(orig);
651	nexp = EXP(sp);
652
653	/* Copy tag queue and tags stack. */
654	for (aqp = oexp->tq.cqh_first;
655	    aqp != (void *)&oexp->tq; aqp = aqp->q.cqe_next) {
656		if (tagq_copy(sp, aqp, &tqp))
657			return (1);
658		for (ap = aqp->tagq.cqh_first;
659		    ap != (void *)&aqp->tagq; ap = ap->q.cqe_next) {
660			if (tag_copy(sp, ap, &tp))
661				return (1);
662			/* Set the current pointer. */
663			if (aqp->current == ap)
664				tqp->current = tp;
665			CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q);
666		}
667		CIRCLEQ_INSERT_TAIL(&nexp->tq, tqp, q);
668	}
669
670	/* Copy list of tag files. */
671	for (atfp = oexp->tagfq.tqh_first;
672	    atfp != NULL; atfp = atfp->q.tqe_next) {
673		if (tagf_copy(sp, atfp, &tfp))
674			return (1);
675		TAILQ_INSERT_TAIL(&nexp->tagfq, tfp, q);
676	}
677
678	/* Copy the last tag. */
679	if (oexp->tag_last != NULL &&
680	    (nexp->tag_last = strdup(oexp->tag_last)) == NULL) {
681		msgq(sp, M_SYSERR, NULL);
682		return (1);
683	}
684	return (0);
685}
686
687/*
688 * tagf_copy --
689 *	Copy a TAGF structure and return it in new memory.
690 */
691static int
692tagf_copy(sp, otfp, tfpp)
693	SCR *sp;
694	TAGF *otfp, **tfpp;
695{
696	TAGF *tfp;
697
698	MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
699	*tfp = *otfp;
700
701	/* XXX: Allocate as part of the TAGF structure!!! */
702	if ((tfp->name = strdup(otfp->name)) == NULL)
703		return (1);
704
705	*tfpp = tfp;
706	return (0);
707}
708
709/*
710 * tagq_copy --
711 *	Copy a TAGQ structure and return it in new memory.
712 */
713static int
714tagq_copy(sp, otqp, tqpp)
715	SCR *sp;
716	TAGQ *otqp, **tqpp;
717{
718	TAGQ *tqp;
719	size_t len;
720
721	len = sizeof(TAGQ);
722	if (otqp->tag != NULL)
723		len += otqp->tlen + 1;
724	MALLOC_RET(sp, tqp, TAGQ *, len);
725	memcpy(tqp, otqp, len);
726
727	CIRCLEQ_INIT(&tqp->tagq);
728	tqp->current = NULL;
729	if (otqp->tag != NULL)
730		tqp->tag = tqp->buf;
731
732	*tqpp = tqp;
733	return (0);
734}
735
736/*
737 * tag_copy --
738 *	Copy a TAG structure and return it in new memory.
739 */
740static int
741tag_copy(sp, otp, tpp)
742	SCR *sp;
743	TAG *otp, **tpp;
744{
745	TAG *tp;
746	size_t len;
747
748	len = sizeof(TAG);
749	if (otp->fname != NULL)
750		len += otp->fnlen + 1;
751	if (otp->search != NULL)
752		len += otp->slen + 1;
753	MALLOC_RET(sp, tp, TAG *, len);
754	memcpy(tp, otp, len);
755
756	if (otp->fname != NULL)
757		tp->fname = tp->buf;
758	if (otp->search != NULL)
759		tp->search = tp->fname + otp->fnlen + 1;
760
761	*tpp = tp;
762	return (0);
763}
764
765/*
766 * tagf_free --
767 *	Free a TAGF structure.
768 */
769static int
770tagf_free(sp, tfp)
771	SCR *sp;
772	TAGF *tfp;
773{
774	EX_PRIVATE *exp;
775
776	exp = EXP(sp);
777	TAILQ_REMOVE(&exp->tagfq, tfp, q);
778	free(tfp->name);
779	free(tfp);
780	return (0);
781}
782
783/*
784 * tagq_free --
785 *	Free a TAGQ structure (and associated TAG structures).
786 *
787 * PUBLIC: int tagq_free __P((SCR *, TAGQ *));
788 */
789int
790tagq_free(sp, tqp)
791	SCR *sp;
792	TAGQ *tqp;
793{
794	EX_PRIVATE *exp;
795	TAG *tp;
796
797	exp = EXP(sp);
798	while ((tp = tqp->tagq.cqh_first) != (void *)&tqp->tagq) {
799		CIRCLEQ_REMOVE(&tqp->tagq, tp, q);
800		free(tp);
801	}
802	/*
803	 * !!!
804	 * If allocated and then the user failed to switch files, the TAGQ
805	 * structure was never attached to any list.
806	 */
807	if (tqp->q.cqe_next != NULL)
808		CIRCLEQ_REMOVE(&exp->tq, tqp, q);
809	free(tqp);
810	return (0);
811}
812
813/*
814 * tag_msg
815 *	A few common messages.
816 *
817 * PUBLIC: void tag_msg __P((SCR *, tagmsg_t, char *));
818 */
819void
820tag_msg(sp, msg, tag)
821	SCR *sp;
822	tagmsg_t msg;
823	char *tag;
824{
825	switch (msg) {
826	case TAG_BADLNO:
827		msgq_str(sp, M_ERR, tag,
828	    "164|%s: the tag's line number is past the end of the file");
829		break;
830	case TAG_EMPTY:
831		msgq(sp, M_INFO, "165|The tags stack is empty");
832		break;
833	case TAG_SEARCH:
834		msgq_str(sp, M_ERR, tag, "166|%s: search pattern not found");
835		break;
836	default:
837		abort();
838	}
839}
840
841/*
842 * ex_tagf_alloc --
843 *	Create a new list of ctag files.
844 *
845 * PUBLIC: int ex_tagf_alloc __P((SCR *, char *));
846 */
847int
848ex_tagf_alloc(sp, str)
849	SCR *sp;
850	char *str;
851{
852	EX_PRIVATE *exp;
853	TAGF *tfp;
854	size_t len;
855	char *p, *t;
856
857	/* Free current queue. */
858	exp = EXP(sp);
859	while ((tfp = exp->tagfq.tqh_first) != NULL)
860		tagf_free(sp, tfp);
861
862	/* Create new queue. */
863	for (p = t = str;; ++p) {
864		if (*p == '\0' || isblank(*p)) {
865			if ((len = p - t) > 1) {
866				MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
867				MALLOC(sp, tfp->name, char *, len + 1);
868				if (tfp->name == NULL) {
869					free(tfp);
870					return (1);
871				}
872				memcpy(tfp->name, t, len);
873				tfp->name[len] = '\0';
874				tfp->flags = 0;
875				TAILQ_INSERT_TAIL(&exp->tagfq, tfp, q);
876			}
877			t = p + 1;
878		}
879		if (*p == '\0')
880			 break;
881	}
882	return (0);
883}
884						/* Free previous queue. */
885/*
886 * ex_tag_free --
887 *	Free the ex tag information.
888 *
889 * PUBLIC: int ex_tag_free __P((SCR *));
890 */
891int
892ex_tag_free(sp)
893	SCR *sp;
894{
895	EX_PRIVATE *exp;
896	TAGF *tfp;
897	TAGQ *tqp;
898
899	/* Free up tag information. */
900	exp = EXP(sp);
901	while ((tqp = exp->tq.cqh_first) != (void *)&exp->tq)
902		tagq_free(sp, tqp);
903	while ((tfp = exp->tagfq.tqh_first) != NULL)
904		tagf_free(sp, tfp);
905	if (exp->tag_last != NULL)
906		free(exp->tag_last);
907	return (0);
908}
909
910/*
911 * ctag_search --
912 *	Search a file for a tag.
913 */
914static int
915ctag_search(sp, search, slen, tag)
916	SCR *sp;
917	char *search, *tag;
918	size_t slen;
919{
920	MARK m;
921	char *p;
922
923	/*
924	 * !!!
925	 * The historic tags file format (from a long, long time ago...)
926	 * used a line number, not a search string.  I got complaints, so
927	 * people are still using the format.  POSIX 1003.2 permits it.
928	 */
929	if (isdigit(search[0])) {
930		m.lno = atoi(search);
931		if (!db_exist(sp, m.lno)) {
932			tag_msg(sp, TAG_BADLNO, tag);
933			return (1);
934		}
935	} else {
936		/*
937		 * Search for the tag; cheap fallback for C functions
938		 * if the name is the same but the arguments have changed.
939		 */
940		m.lno = 1;
941		m.cno = 0;
942		if (f_search(sp, &m, &m,
943		    search, slen, NULL, SEARCH_FILE | SEARCH_TAG))
944			if ((p = strrchr(search, '(')) != NULL) {
945				slen = p - search;
946				if (f_search(sp, &m, &m, search, slen,
947				    NULL, SEARCH_FILE | SEARCH_TAG))
948					goto notfound;
949			} else {
950notfound:			tag_msg(sp, TAG_SEARCH, tag);
951				return (1);
952			}
953		/*
954		 * !!!
955		 * Historically, tags set the search direction if it wasn't
956		 * already set.
957		 */
958		if (sp->searchdir == NOTSET)
959			sp->searchdir = FORWARD;
960	}
961
962	/*
963	 * !!!
964	 * Tags move to the first non-blank, NOT the search pattern start.
965	 */
966	sp->lno = m.lno;
967	sp->cno = 0;
968	(void)nonblank(sp, sp->lno, &sp->cno);
969	return (0);
970}
971
972/*
973 * ctag_slist --
974 *	Search the list of tags files for a tag, and return tag queue.
975 */
976static TAGQ *
977ctag_slist(sp, tag)
978	SCR *sp;
979	char *tag;
980{
981	EX_PRIVATE *exp;
982	TAGF *tfp;
983	TAGQ *tqp;
984	size_t len;
985	int echk;
986
987	exp = EXP(sp);
988
989	/* Allocate and initialize the tag queue structure. */
990	len = strlen(tag);
991	CALLOC_GOTO(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + len + 1);
992	CIRCLEQ_INIT(&tqp->tagq);
993	tqp->tag = tqp->buf;
994	memcpy(tqp->tag, tag, (tqp->tlen = len) + 1);
995
996	/*
997	 * Find the tag, only display missing file messages once, and
998	 * then only if we didn't find the tag.
999	 */
1000	for (echk = 0,
1001	    tfp = exp->tagfq.tqh_first; tfp != NULL; tfp = tfp->q.tqe_next)
1002		if (ctag_sfile(sp, tfp, tqp, tag)) {
1003			echk = 1;
1004			F_SET(tfp, TAGF_ERR);
1005		} else
1006			F_CLR(tfp, TAGF_ERR | TAGF_ERR_WARN);
1007
1008	/* Check to see if we found anything. */
1009	if (tqp->tagq.cqh_first == (void *)&tqp->tagq) {
1010		msgq_str(sp, M_ERR, tag, "162|%s: tag not found");
1011		if (echk)
1012			for (tfp = exp->tagfq.tqh_first;
1013			    tfp != NULL; tfp = tfp->q.tqe_next)
1014				if (F_ISSET(tfp, TAGF_ERR) &&
1015				    !F_ISSET(tfp, TAGF_ERR_WARN)) {
1016					errno = tfp->errnum;
1017					msgq_str(sp, M_SYSERR, tfp->name, "%s");
1018					F_SET(tfp, TAGF_ERR_WARN);
1019				}
1020		free(tqp);
1021		return (NULL);
1022	}
1023
1024	tqp->current = tqp->tagq.cqh_first;
1025	return (tqp);
1026
1027alloc_err:
1028	return (NULL);
1029}
1030
1031/*
1032 * ctag_sfile --
1033 *	Search a tags file for a tag, adding any found to the tag queue.
1034 */
1035static int
1036ctag_sfile(sp, tfp, tqp, tname)
1037	SCR *sp;
1038	TAGF *tfp;
1039	TAGQ *tqp;
1040	char *tname;
1041{
1042	struct stat sb;
1043	TAG *tp;
1044	size_t dlen, nlen, slen;
1045	int fd, i, nf1, nf2;
1046	char *back, *cname, *dname, *front, *map, *name, *p, *search, *t;
1047
1048	if ((fd = open(tfp->name, O_RDONLY, 0)) < 0) {
1049		tfp->errnum = errno;
1050		return (1);
1051	}
1052
1053	/*
1054	 * XXX
1055	 * Some old BSD systems require MAP_FILE as an argument when mapping
1056	 * regular files.
1057	 */
1058#ifndef MAP_FILE
1059#define	MAP_FILE	0
1060#endif
1061	/*
1062	 * XXX
1063	 * We'd like to test if the file is too big to mmap.  Since we don't
1064	 * know what size or type off_t's or size_t's are, what the largest
1065	 * unsigned integral type is, or what random insanity the local C
1066	 * compiler will perpetrate, doing the comparison in a portable way
1067	 * is flatly impossible.  Hope mmap fails if the file is too large.
1068	 */
1069	if (fstat(fd, &sb) != 0 ||
1070	    (map = mmap(NULL, (size_t)sb.st_size, PROT_READ | PROT_WRITE,
1071	    MAP_FILE | MAP_PRIVATE, fd, (off_t)0)) == (caddr_t)-1) {
1072		tfp->errnum = errno;
1073		(void)close(fd);
1074		return (1);
1075	}
1076
1077	front = map;
1078	back = front + sb.st_size;
1079	front = binary_search(tname, front, back);
1080	front = linear_search(tname, front, back);
1081	if (front == NULL)
1082		goto done;
1083
1084	/*
1085	 * Initialize and link in the tag structure(s).  The historic ctags
1086	 * file format only permitted a single tag location per tag.  The
1087	 * obvious extension to permit multiple tags locations per tag is to
1088	 * output multiple records in the standard format.  Unfortunately,
1089	 * this won't work correctly with historic ex/vi implementations,
1090	 * because their binary search assumes that there's only one record
1091	 * per tag, and so will use a random tag entry if there si more than
1092	 * one.  This code handles either format.
1093	 *
1094	 * The tags file is in the following format:
1095	 *
1096	 *	<tag> <filename> <line number> | <pattern>
1097	 *
1098	 * Figure out how long everything is so we can allocate in one swell
1099	 * foop, but discard anything that looks wrong.
1100	 */
1101	for (;;) {
1102		/* Nul-terminate the end of the line. */
1103		for (p = front; p < back && *p != '\n'; ++p);
1104		if (p == back || *p != '\n')
1105			break;
1106		*p = '\0';
1107
1108		/* Update the pointers for the next time. */
1109		t = p + 1;
1110		p = front;
1111		front = t;
1112
1113		/* Break the line into tokens. */
1114		for (i = 0; i < 2 && (t = strsep(&p, "\t ")) != NULL; ++i)
1115			switch (i) {
1116			case 0:			/* Tag. */
1117				cname = t;
1118				break;
1119			case 1:			/* Filename. */
1120				name = t;
1121				nlen = strlen(name);
1122				break;
1123			}
1124
1125		/* Check for corruption. */
1126		if (i != 2 || p == NULL || t == NULL)
1127			goto corrupt;
1128
1129		/* The rest of the string is the search pattern. */
1130		search = p;
1131		if ((slen = strlen(p)) == 0) {
1132corrupt:		p = msg_print(sp, tname, &nf1);
1133			t = msg_print(sp, tfp->name, &nf2);
1134			msgq(sp, M_ERR, "163|%s: corrupted tag in %s", p, t);
1135			if (nf1)
1136				FREE_SPACE(sp, p, 0);
1137			if (nf2)
1138				FREE_SPACE(sp, t, 0);
1139			continue;
1140		}
1141
1142		/* Check for passing the last entry. */
1143		if (strcmp(tname, cname))
1144			break;
1145
1146		/* Resolve the file name. */
1147		ctag_file(sp, tfp, name, &dname, &dlen);
1148
1149		CALLOC_GOTO(sp, tp,
1150		    TAG *, 1, sizeof(TAG) + dlen + 2 + nlen + 1 + slen + 1);
1151		tp->fname = tp->buf;
1152		if (dlen != 0) {
1153			memcpy(tp->fname, dname, dlen);
1154			tp->fname[dlen] = '/';
1155			++dlen;
1156		}
1157		memcpy(tp->fname + dlen, name, nlen + 1);
1158		tp->fnlen = dlen + nlen;
1159		tp->search = tp->fname + tp->fnlen + 1;
1160		memcpy(tp->search, search, (tp->slen = slen) + 1);
1161		CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q);
1162	}
1163
1164alloc_err:
1165done:	if (munmap(map, (size_t)sb.st_size))
1166		msgq(sp, M_SYSERR, "munmap");
1167	if (close(fd))
1168		msgq(sp, M_SYSERR, "close");
1169	return (0);
1170}
1171
1172/*
1173 * ctag_file --
1174 *	Search for the right path to this file.
1175 */
1176static void
1177ctag_file(sp, tfp, name, dirp, dlenp)
1178	SCR *sp;
1179	TAGF *tfp;
1180	char *name, **dirp;
1181	size_t *dlenp;
1182{
1183	struct stat sb;
1184	size_t len;
1185	char *p, buf[MAXPATHLEN];
1186
1187	/*
1188	 * !!!
1189	 * If the tag file path is a relative path, see if it exists.  If it
1190	 * doesn't, look relative to the tags file path.  It's okay for a tag
1191	 * file to not exist, and historically, vi simply displayed a "new"
1192	 * file.  However, if the path exists relative to the tag file, it's
1193	 * pretty clear what's happening, so we may as well get it right.
1194	 */
1195	*dlenp = 0;
1196	if (name[0] != '/' &&
1197	    stat(name, &sb) && (p = strrchr(tfp->name, '/')) != NULL) {
1198		*p = '\0';
1199		len = snprintf(buf, sizeof(buf), "%s/%s", tfp->name, name);
1200		*p = '/';
1201		if (stat(buf, &sb) == 0) {
1202			*dirp = tfp->name;
1203			*dlenp = strlen(*dirp);
1204		}
1205	}
1206}
1207
1208/*
1209 * Binary search for "string" in memory between "front" and "back".
1210 *
1211 * This routine is expected to return a pointer to the start of a line at
1212 * *or before* the first word matching "string".  Relaxing the constraint
1213 * this way simplifies the algorithm.
1214 *
1215 * Invariants:
1216 * 	front points to the beginning of a line at or before the first
1217 *	matching string.
1218 *
1219 * 	back points to the beginning of a line at or after the first
1220 *	matching line.
1221 *
1222 * Base of the Invariants.
1223 * 	front = NULL;
1224 *	back = EOF;
1225 *
1226 * Advancing the Invariants:
1227 *
1228 * 	p = first newline after halfway point from front to back.
1229 *
1230 * 	If the string at "p" is not greater than the string to match,
1231 *	p is the new front.  Otherwise it is the new back.
1232 *
1233 * Termination:
1234 *
1235 * 	The definition of the routine allows it return at any point,
1236 *	since front is always at or before the line to print.
1237 *
1238 * 	In fact, it returns when the chosen "p" equals "back".  This
1239 *	implies that there exists a string is least half as long as
1240 *	(back - front), which in turn implies that a linear search will
1241 *	be no more expensive than the cost of simply printing a string or two.
1242 *
1243 * 	Trying to continue with binary search at this point would be
1244 *	more trouble than it's worth.
1245 */
1246#define	EQUAL		0
1247#define	GREATER		1
1248#define	LESS		(-1)
1249
1250#define	SKIP_PAST_NEWLINE(p, back)	while (p < back && *p++ != '\n');
1251
1252static char *
1253binary_search(string, front, back)
1254	register char *string, *front, *back;
1255{
1256	register char *p;
1257
1258	p = front + (back - front) / 2;
1259	SKIP_PAST_NEWLINE(p, back);
1260
1261	while (p != back) {
1262		if (compare(string, p, back) == GREATER)
1263			front = p;
1264		else
1265			back = p;
1266		p = front + (back - front) / 2;
1267		SKIP_PAST_NEWLINE(p, back);
1268	}
1269	return (front);
1270}
1271
1272/*
1273 * Find the first line that starts with string, linearly searching from front
1274 * to back.
1275 *
1276 * Return NULL for no such line.
1277 *
1278 * This routine assumes:
1279 *
1280 * 	o front points at the first character in a line.
1281 *	o front is before or at the first line to be printed.
1282 */
1283static char *
1284linear_search(string, front, back)
1285	char *string, *front, *back;
1286{
1287	while (front < back) {
1288		switch (compare(string, front, back)) {
1289		case EQUAL:		/* Found it. */
1290			return (front);
1291		case LESS:		/* No such string. */
1292			return (NULL);
1293		case GREATER:		/* Keep going. */
1294			break;
1295		}
1296		SKIP_PAST_NEWLINE(front, back);
1297	}
1298	return (NULL);
1299}
1300
1301/*
1302 * Return LESS, GREATER, or EQUAL depending on how the string1 compares
1303 * with string2 (s1 ??? s2).
1304 *
1305 * 	o Matches up to len(s1) are EQUAL.
1306 *	o Matches up to len(s2) are GREATER.
1307 *
1308 * The string "s1" is null terminated.  The string s2 is '\t', space, (or
1309 * "back") terminated.
1310 *
1311 * !!!
1312 * Reasonably modern ctags programs use tabs as separators, not spaces.
1313 * However, historic programs did use spaces, and, I got complaints.
1314 */
1315static int
1316compare(s1, s2, back)
1317	register char *s1, *s2, *back;
1318{
1319	for (; *s1 && s2 < back && (*s2 != '\t' && *s2 != ' '); ++s1, ++s2)
1320		if (*s1 != *s2)
1321			return (*s1 < *s2 ? LESS : GREATER);
1322	return (*s1 ? GREATER : s2 < back &&
1323	    (*s2 != '\t' && *s2 != ' ') ? LESS : EQUAL);
1324}
1325