HAMMER Utilities: Remove time/transaction-id conversion directives.
[dragonfly.git] / contrib / nvi / ex / ex_tag.c
blob8f5e7b9dc962cc507fe532f95a8ebfac776d9f4e
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.
7 * This code is derived from software contributed to Berkeley by
8 * David Hitz of Auspex Systems, Inc.
10 * See the LICENSE file for redistribution information.
13 #include "config.h"
15 #ifndef lint
16 static const char sccsid[] = "@(#)ex_tag.c 10.36 (Berkeley) 9/15/96";
17 #endif /* not lint */
19 #include <sys/param.h>
20 #include <sys/types.h> /* XXX: param.h may not have included types.h */
22 #ifdef HAVE_SYS_MMAN_H
23 #include <sys/mman.h>
24 #endif
26 #include <sys/queue.h>
27 #include <sys/stat.h>
28 #include <sys/time.h>
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>
41 #include "../common/common.h"
42 #include "../vi/vi.h"
43 #include "tag.h"
45 static char *binary_search __P((char *, char *, char *));
46 static int compare __P((char *, char *, char *));
47 static void ctag_file __P((SCR *, TAGF *, char *, char **, size_t *));
48 static int ctag_search __P((SCR *, char *, size_t, char *));
49 #ifdef GTAGS
50 static int getentry __P((char *, char **, char **, char **));
51 static TAGQ *gtag_slist __P((SCR *, char *, int));
52 #endif
53 static int ctag_sfile __P((SCR *, TAGF *, TAGQ *, char *));
54 static TAGQ *ctag_slist __P((SCR *, char *));
55 static char *linear_search __P((char *, char *, char *));
56 static int tag_copy __P((SCR *, TAG *, TAG **));
57 static int tag_pop __P((SCR *, TAGQ *, int));
58 static int tagf_copy __P((SCR *, TAGF *, TAGF **));
59 static int tagf_free __P((SCR *, TAGF *));
60 static int tagq_copy __P((SCR *, TAGQ *, TAGQ **));
63 * ex_tag_first --
64 * The tag code can be entered from main, e.g., "vi -t tag".
66 * PUBLIC: int ex_tag_first __P((SCR *, char *));
68 int
69 ex_tag_first(sp, tagarg)
70 SCR *sp;
71 char *tagarg;
73 ARGS *ap[2], a;
74 EXCMD cmd;
76 /* Build an argument for the ex :tag command. */
77 ex_cinit(&cmd, C_TAG, 0, OOBLNO, OOBLNO, 0, ap);
78 ex_cadd(&cmd, &a, tagarg, strlen(tagarg));
81 * XXX
82 * Historic vi went ahead and created a temporary file when it failed
83 * to find the tag. We match historic practice, but don't distinguish
84 * between real error and failure to find the tag.
86 if (ex_tag_push(sp, &cmd))
87 return (0);
89 /* Display tags in the center of the screen. */
90 F_CLR(sp, SC_SCR_TOP);
91 F_SET(sp, SC_SCR_CENTER);
93 return (0);
96 #ifdef GTAGS
98 * ex_rtag_push -- ^]
99 * :rtag[!] [string]
101 * Enter a new TAGQ context based on a ctag string.
103 * PUBLIC: int ex_rtag_push __P((SCR *, EXCMD *));
106 ex_rtag_push(sp, cmdp)
107 SCR *sp;
108 EXCMD *cmdp;
110 F_SET(cmdp, E_REFERENCE);
111 return ex_tag_push(sp, cmdp);
113 #endif
116 * ex_tag_push -- ^]
117 * :tag[!] [string]
119 * Enter a new TAGQ context based on a ctag string.
121 * PUBLIC: int ex_tag_push __P((SCR *, EXCMD *));
124 ex_tag_push(sp, cmdp)
125 SCR *sp;
126 EXCMD *cmdp;
128 EX_PRIVATE *exp;
129 FREF *frp;
130 TAG *rtp;
131 TAGQ *rtqp, *tqp;
132 recno_t lno;
133 size_t cno;
134 long tl;
135 int force, istmp;
137 exp = EXP(sp);
138 switch (cmdp->argc) {
139 case 1:
140 if (exp->tag_last != NULL)
141 free(exp->tag_last);
143 if ((exp->tag_last = strdup(cmdp->argv[0]->bp)) == NULL) {
144 msgq(sp, M_SYSERR, NULL);
145 return (1);
148 /* Taglength may limit the number of characters. */
149 if ((tl =
150 O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(exp->tag_last) > tl)
151 exp->tag_last[tl] = '\0';
152 break;
153 case 0:
154 if (exp->tag_last == NULL) {
155 msgq(sp, M_ERR, "158|No previous tag entered");
156 return (1);
158 break;
159 default:
160 abort();
163 /* Get the tag information. */
164 #ifdef GTAGS
165 if (O_ISSET(sp, O_GTAGSMODE)) {
166 if ((tqp = gtag_slist(sp, exp->tag_last, F_ISSET(cmdp, E_REFERENCE))) == NULL)
167 return (1);
168 } else
169 #endif
170 if ((tqp = ctag_slist(sp, exp->tag_last)) == NULL)
171 return (1);
174 * Allocate all necessary memory before swapping screens. Initialize
175 * flags so we know what to free.
177 rtp = NULL;
178 rtqp = NULL;
179 if (exp->tq.cqh_first == (void *)&exp->tq) {
180 /* Initialize the `local context' tag queue structure. */
181 CALLOC_GOTO(sp, rtqp, TAGQ *, 1, sizeof(TAGQ));
182 CIRCLEQ_INIT(&rtqp->tagq);
184 /* Initialize and link in its tag structure. */
185 CALLOC_GOTO(sp, rtp, TAG *, 1, sizeof(TAG));
186 CIRCLEQ_INSERT_HEAD(&rtqp->tagq, rtp, q);
187 rtqp->current = rtp;
191 * Stick the current context information in a convenient place, we're
192 * about to lose it. Note, if we're called on editor startup, there
193 * will be no FREF structure.
195 frp = sp->frp;
196 lno = sp->lno;
197 cno = sp->cno;
198 istmp = frp == NULL ||
199 F_ISSET(frp, FR_TMPFILE) && !F_ISSET(cmdp, E_NEWSCREEN);
201 /* Try to switch to the tag. */
202 force = FL_ISSET(cmdp->iflags, E_C_FORCE);
203 if (F_ISSET(cmdp, E_NEWSCREEN)) {
204 if (ex_tag_Nswitch(sp, tqp->tagq.cqh_first, force))
205 goto err;
207 /* Everything else gets done in the new screen. */
208 sp = sp->nextdisp;
209 exp = EXP(sp);
210 } else
211 if (ex_tag_nswitch(sp, tqp->tagq.cqh_first, force))
212 goto err;
215 * If this is the first tag, put a `current location' queue entry
216 * in place, so we can pop all the way back to the current mark.
217 * Note, it doesn't point to much of anything, it's a placeholder.
219 if (exp->tq.cqh_first == (void *)&exp->tq) {
220 CIRCLEQ_INSERT_HEAD(&exp->tq, rtqp, q);
221 } else
222 rtqp = exp->tq.cqh_first;
224 /* Link the new TAGQ structure into place. */
225 CIRCLEQ_INSERT_HEAD(&exp->tq, tqp, q);
227 (void)ctag_search(sp,
228 tqp->current->search, tqp->current->slen, tqp->tag);
231 * Move the current context from the temporary save area into the
232 * right structure.
234 * If we were in a temporary file, we don't have a context to which
235 * we can return, so just make it be the same as what we're moving
236 * to. It will be a little odd that ^T doesn't change anything, but
237 * I don't think it's a big deal.
239 if (istmp) {
240 rtqp->current->frp = sp->frp;
241 rtqp->current->lno = sp->lno;
242 rtqp->current->cno = sp->cno;
243 } else {
244 rtqp->current->frp = frp;
245 rtqp->current->lno = lno;
246 rtqp->current->cno = cno;
248 return (0);
250 err:
251 alloc_err:
252 if (rtqp != NULL)
253 free(rtqp);
254 if (rtp != NULL)
255 free(rtp);
256 tagq_free(sp, tqp);
257 return (1);
261 * ex_tag_next --
262 * Switch context to the next TAG.
264 * PUBLIC: int ex_tag_next __P((SCR *, EXCMD *));
267 ex_tag_next(sp, cmdp)
268 SCR *sp;
269 EXCMD *cmdp;
271 EX_PRIVATE *exp;
272 TAG *tp;
273 TAGQ *tqp;
275 exp = EXP(sp);
276 if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
277 tag_msg(sp, TAG_EMPTY, NULL);
278 return (1);
280 if ((tp = tqp->current->q.cqe_next) == (void *)&tqp->tagq) {
281 msgq(sp, M_ERR, "282|Already at the last tag of this group");
282 return (1);
284 if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
285 return (1);
286 tqp->current = tp;
288 if (F_ISSET(tqp, TAG_CSCOPE))
289 (void)cscope_search(sp, tqp, tp);
290 else
291 (void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
292 return (0);
296 * ex_tag_prev --
297 * Switch context to the next TAG.
299 * PUBLIC: int ex_tag_prev __P((SCR *, EXCMD *));
302 ex_tag_prev(sp, cmdp)
303 SCR *sp;
304 EXCMD *cmdp;
306 EX_PRIVATE *exp;
307 TAG *tp;
308 TAGQ *tqp;
310 exp = EXP(sp);
311 if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
312 tag_msg(sp, TAG_EMPTY, NULL);
313 return (0);
315 if ((tp = tqp->current->q.cqe_prev) == (void *)&tqp->tagq) {
316 msgq(sp, M_ERR, "255|Already at the first tag of this group");
317 return (1);
319 if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
320 return (1);
321 tqp->current = tp;
323 if (F_ISSET(tqp, TAG_CSCOPE))
324 (void)cscope_search(sp, tqp, tp);
325 else
326 (void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
327 return (0);
331 * ex_tag_nswitch --
332 * Switch context to the specified TAG.
334 * PUBLIC: int ex_tag_nswitch __P((SCR *, TAG *, int));
337 ex_tag_nswitch(sp, tp, force)
338 SCR *sp;
339 TAG *tp;
340 int force;
342 /* Get a file structure. */
343 if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)
344 return (1);
346 /* If not changing files, return, we're done. */
347 if (tp->frp == sp->frp)
348 return (0);
350 /* Check for permission to leave. */
351 if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
352 return (1);
354 /* Initialize the new file. */
355 if (file_init(sp, tp->frp, NULL, FS_SETALT))
356 return (1);
358 /* Display tags in the center of the screen. */
359 F_CLR(sp, SC_SCR_TOP);
360 F_SET(sp, SC_SCR_CENTER);
362 /* Switch. */
363 F_SET(sp, SC_FSWITCH);
364 return (0);
368 * ex_tag_Nswitch --
369 * Switch context to the specified TAG in a new screen.
371 * PUBLIC: int ex_tag_Nswitch __P((SCR *, TAG *, int));
374 ex_tag_Nswitch(sp, tp, force)
375 SCR *sp;
376 TAG *tp;
377 int force;
379 SCR *new;
381 /* Get a file structure. */
382 if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)
383 return (1);
385 /* Get a new screen. */
386 if (screen_init(sp->gp, sp, &new))
387 return (1);
388 if (vs_split(sp, new, 0)) {
389 (void)file_end(new, new->ep, 1);
390 (void)screen_end(new);
391 return (1);
394 /* Get a backing file. */
395 if (tp->frp == sp->frp) {
396 /* Copy file state. */
397 new->ep = sp->ep;
398 ++new->ep->refcnt;
400 new->frp = tp->frp;
401 new->frp->flags = sp->frp->flags;
402 } else if (file_init(new, tp->frp, NULL, force)) {
403 (void)vs_discard(new, NULL);
404 (void)screen_end(new);
405 return (1);
408 /* Create the argument list. */
409 new->cargv = new->argv = ex_buildargv(sp, NULL, tp->frp->name);
411 /* Display tags in the center of the screen. */
412 F_CLR(new, SC_SCR_TOP);
413 F_SET(new, SC_SCR_CENTER);
415 /* Switch. */
416 sp->nextdisp = new;
417 F_SET(sp, SC_SSWITCH);
419 return (0);
423 * ex_tag_pop -- ^T
424 * :tagp[op][!] [number | file]
426 * Pop to a previous TAGQ context.
428 * PUBLIC: int ex_tag_pop __P((SCR *, EXCMD *));
431 ex_tag_pop(sp, cmdp)
432 SCR *sp;
433 EXCMD *cmdp;
435 EX_PRIVATE *exp;
436 TAGQ *tqp, *dtqp;
437 size_t arglen;
438 long off;
439 char *arg, *p, *t;
441 /* Check for an empty stack. */
442 exp = EXP(sp);
443 if (exp->tq.cqh_first == (void *)&exp->tq) {
444 tag_msg(sp, TAG_EMPTY, NULL);
445 return (1);
448 /* Find the last TAG structure that we're going to DISCARD! */
449 switch (cmdp->argc) {
450 case 0: /* Pop one tag. */
451 dtqp = exp->tq.cqh_first;
452 break;
453 case 1: /* Name or number. */
454 arg = cmdp->argv[0]->bp;
455 off = strtol(arg, &p, 10);
456 if (*p != '\0')
457 goto filearg;
459 /* Number: pop that many queue entries. */
460 if (off < 1)
461 return (0);
462 for (tqp = exp->tq.cqh_first;
463 tqp != (void *)&exp->tq && --off > 1;
464 tqp = tqp->q.cqe_next);
465 if (tqp == (void *)&exp->tq) {
466 msgq(sp, M_ERR,
467 "159|Less than %s entries on the tags stack; use :display t[ags]",
468 arg);
469 return (1);
471 dtqp = tqp;
472 break;
474 /* File argument: pop to that queue entry. */
475 filearg: arglen = strlen(arg);
476 for (tqp = exp->tq.cqh_first;
477 tqp != (void *)&exp->tq;
478 dtqp = tqp, tqp = tqp->q.cqe_next) {
479 /* Don't pop to the current file. */
480 if (tqp == exp->tq.cqh_first)
481 continue;
482 p = tqp->current->frp->name;
483 if ((t = strrchr(p, '/')) == NULL)
484 t = p;
485 else
486 ++t;
487 if (!strncmp(arg, t, arglen))
488 break;
490 if (tqp == (void *)&exp->tq) {
491 msgq_str(sp, M_ERR, arg,
492 "160|No file %s on the tags stack to return to; use :display t[ags]");
493 return (1);
495 if (tqp == exp->tq.cqh_first)
496 return (0);
497 break;
498 default:
499 abort();
502 return (tag_pop(sp, dtqp, FL_ISSET(cmdp->iflags, E_C_FORCE)));
506 * ex_tag_top -- :tagt[op][!]
507 * Clear the tag stack.
509 * PUBLIC: int ex_tag_top __P((SCR *, EXCMD *));
512 ex_tag_top(sp, cmdp)
513 SCR *sp;
514 EXCMD *cmdp;
516 EX_PRIVATE *exp;
518 exp = EXP(sp);
520 /* Check for an empty stack. */
521 if (exp->tq.cqh_first == (void *)&exp->tq) {
522 tag_msg(sp, TAG_EMPTY, NULL);
523 return (1);
526 /* Return to the oldest information. */
527 return (tag_pop(sp,
528 exp->tq.cqh_last->q.cqe_prev, FL_ISSET(cmdp->iflags, E_C_FORCE)));
532 * tag_pop --
533 * Pop up to and including the specified TAGQ context.
535 static int
536 tag_pop(sp, dtqp, force)
537 SCR *sp;
538 TAGQ *dtqp;
539 int force;
541 EX_PRIVATE *exp;
542 TAG *tp;
543 TAGQ *tqp;
545 exp = EXP(sp);
548 * Update the cursor from the saved TAG information of the TAG
549 * structure we're moving to.
551 tp = dtqp->q.cqe_next->current;
552 if (tp->frp == sp->frp) {
553 sp->lno = tp->lno;
554 sp->cno = tp->cno;
555 } else {
556 if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
557 return (1);
559 tp->frp->lno = tp->lno;
560 tp->frp->cno = tp->cno;
561 F_SET(sp->frp, FR_CURSORSET);
562 if (file_init(sp, tp->frp, NULL, FS_SETALT))
563 return (1);
565 F_SET(sp, SC_FSWITCH);
568 /* Pop entries off the queue up to and including dtqp. */
569 do {
570 tqp = exp->tq.cqh_first;
571 if (tagq_free(sp, tqp))
572 return (0);
573 } while (tqp != dtqp);
576 * If only a single tag left, we've returned to the first tag point,
577 * and the stack is now empty.
579 if (exp->tq.cqh_first->q.cqe_next == (void *)&exp->tq)
580 tagq_free(sp, exp->tq.cqh_first);
582 return (0);
586 * ex_tag_display --
587 * Display the list of tags.
589 * PUBLIC: int ex_tag_display __P((SCR *));
592 ex_tag_display(sp)
593 SCR *sp;
595 EX_PRIVATE *exp;
596 TAG *tp;
597 TAGQ *tqp;
598 int cnt;
599 size_t len;
600 char *p, *sep;
602 exp = EXP(sp);
603 if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
604 tag_msg(sp, TAG_EMPTY, NULL);
605 return (0);
609 * We give the file name 20 columns and the search string the rest.
610 * If there's not enough room, we don't do anything special, it's
611 * not worth the effort, it just makes the display more confusing.
613 * We also assume that characters in file names map 1-1 to printing
614 * characters. This might not be true, but I don't think it's worth
615 * fixing. (The obvious fix is to pass the filenames through the
616 * msg_print function.)
618 #define L_NAME 30 /* Name. */
619 #define L_SLOP 4 /* Leading number plus trailing *. */
620 #define L_SPACE 5 /* Spaces after name, before tag. */
621 #define L_TAG 20 /* Tag. */
622 if (sp->cols <= L_NAME + L_SLOP) {
623 msgq(sp, M_ERR, "292|Display too small.");
624 return (0);
628 * Display the list of tags for each queue entry. The first entry
629 * is numbered, and the current tag entry has an asterisk appended.
631 for (cnt = 1, tqp = exp->tq.cqh_first; !INTERRUPTED(sp) &&
632 tqp != (void *)&exp->tq; ++cnt, tqp = tqp->q.cqe_next)
633 for (tp = tqp->tagq.cqh_first;
634 tp != (void *)&tqp->tagq; tp = tp->q.cqe_next) {
635 if (tp == tqp->tagq.cqh_first)
636 (void)ex_printf(sp, "%2d ", cnt);
637 else
638 (void)ex_printf(sp, " ");
639 p = tp->frp == NULL ? tp->fname : tp->frp->name;
640 if ((len = strlen(p)) > L_NAME) {
641 len = len - (L_NAME - 4);
642 (void)ex_printf(sp, " ... %*.*s",
643 L_NAME - 4, L_NAME - 4, p + len);
644 } else
645 (void)ex_printf(sp,
646 " %*.*s", L_NAME, L_NAME, p);
647 if (tqp->current == tp)
648 (void)ex_printf(sp, "*");
650 if (tp == tqp->tagq.cqh_first && tqp->tag != NULL &&
651 (sp->cols - L_NAME) >= L_TAG + L_SPACE) {
652 len = strlen(tqp->tag);
653 if (len > sp->cols - (L_NAME + L_SPACE))
654 len = sp->cols - (L_NAME + L_SPACE);
655 (void)ex_printf(sp, "%s%.*s",
656 tqp->current == tp ? " " : " ",
657 (int)len, tqp->tag);
659 (void)ex_printf(sp, "\n");
661 return (0);
665 * ex_tag_copy --
666 * Copy a screen's tag structures.
668 * PUBLIC: int ex_tag_copy __P((SCR *, SCR *));
671 ex_tag_copy(orig, sp)
672 SCR *orig, *sp;
674 EX_PRIVATE *oexp, *nexp;
675 TAGQ *aqp, *tqp;
676 TAG *ap, *tp;
677 TAGF *atfp, *tfp;
679 oexp = EXP(orig);
680 nexp = EXP(sp);
682 /* Copy tag queue and tags stack. */
683 for (aqp = oexp->tq.cqh_first;
684 aqp != (void *)&oexp->tq; aqp = aqp->q.cqe_next) {
685 if (tagq_copy(sp, aqp, &tqp))
686 return (1);
687 for (ap = aqp->tagq.cqh_first;
688 ap != (void *)&aqp->tagq; ap = ap->q.cqe_next) {
689 if (tag_copy(sp, ap, &tp))
690 return (1);
691 /* Set the current pointer. */
692 if (aqp->current == ap)
693 tqp->current = tp;
694 CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q);
696 CIRCLEQ_INSERT_TAIL(&nexp->tq, tqp, q);
699 /* Copy list of tag files. */
700 for (atfp = oexp->tagfq.tqh_first;
701 atfp != NULL; atfp = atfp->q.tqe_next) {
702 if (tagf_copy(sp, atfp, &tfp))
703 return (1);
704 TAILQ_INSERT_TAIL(&nexp->tagfq, tfp, q);
707 /* Copy the last tag. */
708 if (oexp->tag_last != NULL &&
709 (nexp->tag_last = strdup(oexp->tag_last)) == NULL) {
710 msgq(sp, M_SYSERR, NULL);
711 return (1);
713 return (0);
717 * tagf_copy --
718 * Copy a TAGF structure and return it in new memory.
720 static int
721 tagf_copy(sp, otfp, tfpp)
722 SCR *sp;
723 TAGF *otfp, **tfpp;
725 TAGF *tfp;
727 MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
728 *tfp = *otfp;
730 /* XXX: Allocate as part of the TAGF structure!!! */
731 if ((tfp->name = strdup(otfp->name)) == NULL)
732 return (1);
734 *tfpp = tfp;
735 return (0);
739 * tagq_copy --
740 * Copy a TAGQ structure and return it in new memory.
742 static int
743 tagq_copy(sp, otqp, tqpp)
744 SCR *sp;
745 TAGQ *otqp, **tqpp;
747 TAGQ *tqp;
748 size_t len;
750 len = sizeof(TAGQ);
751 if (otqp->tag != NULL)
752 len += otqp->tlen + 1;
753 MALLOC_RET(sp, tqp, TAGQ *, len);
754 memcpy(tqp, otqp, len);
756 CIRCLEQ_INIT(&tqp->tagq);
757 tqp->current = NULL;
758 if (otqp->tag != NULL)
759 tqp->tag = tqp->buf;
761 *tqpp = tqp;
762 return (0);
766 * tag_copy --
767 * Copy a TAG structure and return it in new memory.
769 static int
770 tag_copy(sp, otp, tpp)
771 SCR *sp;
772 TAG *otp, **tpp;
774 TAG *tp;
775 size_t len;
777 len = sizeof(TAG);
778 if (otp->fname != NULL)
779 len += otp->fnlen + 1;
780 if (otp->search != NULL)
781 len += otp->slen + 1;
782 MALLOC_RET(sp, tp, TAG *, len);
783 memcpy(tp, otp, len);
785 if (otp->fname != NULL)
786 tp->fname = tp->buf;
787 if (otp->search != NULL)
788 tp->search = tp->fname + otp->fnlen + 1;
790 *tpp = tp;
791 return (0);
795 * tagf_free --
796 * Free a TAGF structure.
798 static int
799 tagf_free(sp, tfp)
800 SCR *sp;
801 TAGF *tfp;
803 EX_PRIVATE *exp;
805 exp = EXP(sp);
806 TAILQ_REMOVE(&exp->tagfq, tfp, q);
807 free(tfp->name);
808 free(tfp);
809 return (0);
813 * tagq_free --
814 * Free a TAGQ structure (and associated TAG structures).
816 * PUBLIC: int tagq_free __P((SCR *, TAGQ *));
819 tagq_free(sp, tqp)
820 SCR *sp;
821 TAGQ *tqp;
823 EX_PRIVATE *exp;
824 TAG *tp;
826 exp = EXP(sp);
827 while ((tp = tqp->tagq.cqh_first) != (void *)&tqp->tagq) {
828 CIRCLEQ_REMOVE(&tqp->tagq, tp, q);
829 free(tp);
832 * !!!
833 * If allocated and then the user failed to switch files, the TAGQ
834 * structure was never attached to any list.
836 if (tqp->q.cqe_next != NULL)
837 CIRCLEQ_REMOVE(&exp->tq, tqp, q);
838 free(tqp);
839 return (0);
843 * tag_msg
844 * A few common messages.
846 * PUBLIC: void tag_msg __P((SCR *, tagmsg_t, char *));
848 void
849 tag_msg(sp, msg, tag)
850 SCR *sp;
851 tagmsg_t msg;
852 char *tag;
854 switch (msg) {
855 case TAG_BADLNO:
856 msgq_str(sp, M_ERR, tag,
857 "164|%s: the tag's line number is past the end of the file");
858 break;
859 case TAG_EMPTY:
860 msgq(sp, M_INFO, "165|The tags stack is empty");
861 break;
862 case TAG_SEARCH:
863 msgq_str(sp, M_ERR, tag, "166|%s: search pattern not found");
864 break;
865 default:
866 abort();
871 * ex_tagf_alloc --
872 * Create a new list of ctag files.
874 * PUBLIC: int ex_tagf_alloc __P((SCR *, char *));
877 ex_tagf_alloc(sp, str)
878 SCR *sp;
879 char *str;
881 EX_PRIVATE *exp;
882 TAGF *tfp;
883 size_t len;
884 char *p, *t;
886 /* Free current queue. */
887 exp = EXP(sp);
888 while ((tfp = exp->tagfq.tqh_first) != NULL)
889 tagf_free(sp, tfp);
891 /* Create new queue. */
892 for (p = t = str;; ++p) {
893 if (*p == '\0' || isblank(*p)) {
894 if ((len = p - t) > 1) {
895 MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
896 MALLOC(sp, tfp->name, char *, len + 1);
897 if (tfp->name == NULL) {
898 free(tfp);
899 return (1);
901 memcpy(tfp->name, t, len);
902 tfp->name[len] = '\0';
903 tfp->flags = 0;
904 TAILQ_INSERT_TAIL(&exp->tagfq, tfp, q);
906 t = p + 1;
908 if (*p == '\0')
909 break;
911 return (0);
913 /* Free previous queue. */
915 * ex_tag_free --
916 * Free the ex tag information.
918 * PUBLIC: int ex_tag_free __P((SCR *));
921 ex_tag_free(sp)
922 SCR *sp;
924 EX_PRIVATE *exp;
925 TAGF *tfp;
926 TAGQ *tqp;
928 /* Free up tag information. */
929 exp = EXP(sp);
930 while ((tqp = exp->tq.cqh_first) != (void *)&exp->tq)
931 tagq_free(sp, tqp);
932 while ((tfp = exp->tagfq.tqh_first) != NULL)
933 tagf_free(sp, tfp);
934 if (exp->tag_last != NULL)
935 free(exp->tag_last);
936 return (0);
940 * ctag_search --
941 * Search a file for a tag.
943 static int
944 ctag_search(sp, search, slen, tag)
945 SCR *sp;
946 char *search, *tag;
947 size_t slen;
949 MARK m;
950 char *p;
953 * !!!
954 * The historic tags file format (from a long, long time ago...)
955 * used a line number, not a search string. I got complaints, so
956 * people are still using the format. POSIX 1003.2 permits it.
958 if (isdigit(search[0])) {
959 m.lno = atoi(search);
960 if (!db_exist(sp, m.lno)) {
961 tag_msg(sp, TAG_BADLNO, tag);
962 return (1);
964 } else {
966 * Search for the tag; cheap fallback for C functions
967 * if the name is the same but the arguments have changed.
969 m.lno = 1;
970 m.cno = 0;
971 if (f_search(sp, &m, &m,
972 search, slen, NULL, SEARCH_FILE | SEARCH_TAG))
973 if ((p = strrchr(search, '(')) != NULL) {
974 slen = p - search;
975 if (f_search(sp, &m, &m, search, slen,
976 NULL, SEARCH_FILE | SEARCH_TAG))
977 goto notfound;
978 } else {
979 notfound: tag_msg(sp, TAG_SEARCH, tag);
980 return (1);
983 * !!!
984 * Historically, tags set the search direction if it wasn't
985 * already set.
987 if (sp->searchdir == NOTSET)
988 sp->searchdir = FORWARD;
992 * !!!
993 * Tags move to the first non-blank, NOT the search pattern start.
995 sp->lno = m.lno;
996 sp->cno = 0;
997 (void)nonblank(sp, sp->lno, &sp->cno);
998 return (0);
1001 #ifdef GTAGS
1003 * getentry --
1004 * get tag information from current line.
1006 * gtags temporary file format.
1007 * <tag> <lineno> <file> <image>
1009 * sample.
1010 * +------------------------------------------------
1011 * |main 30 main.c main(argc, argv)
1012 * |func 21 subr.c func(arg)
1014 static int
1015 getentry(buf, tag, file, line)
1016 char *buf, **tag, **file, **line;
1018 char *p = buf;
1020 for (*tag = p; *p && !isspace(*p); p++) /* tag name */
1022 if (*p == 0)
1023 goto err;
1024 *p++ = 0;
1025 for (; *p && isspace(*p); p++) /* (skip blanks) */
1027 if (*p == 0)
1028 goto err;
1029 *line = p; /* line no */
1030 for (*line = p; *p && !isspace(*p); p++)
1032 if (*p == 0)
1033 goto err;
1034 *p++ = 0;
1035 for (; *p && isspace(*p); p++) /* (skip blanks) */
1037 if (*p == 0)
1038 goto err;
1039 *file = p; /* file name */
1040 for (*file = p; *p && !isspace(*p); p++)
1042 if (*p == 0)
1043 goto err;
1044 *p = 0;
1046 /* value check */
1047 if (strlen(*tag) && strlen(*line) && strlen(*file) && atoi(*line) > 0)
1048 return 1; /* OK */
1049 err:
1050 return 0; /* ERROR */
1054 * gtag_slist --
1055 * Search the list of tags files for a tag, and return tag queue.
1057 static TAGQ *
1058 gtag_slist(sp, tag, ref)
1059 SCR *sp;
1060 char *tag;
1061 int ref;
1063 EX_PRIVATE *exp;
1064 TAGF *tfp;
1065 TAGQ *tqp;
1066 size_t len;
1067 int echk;
1068 TAG *tp;
1069 char *name, *file, *line;
1070 char command[BUFSIZ];
1071 char buf[BUFSIZ];
1072 FILE *fp;
1074 /* Allocate and initialize the tag queue structure. */
1075 len = strlen(tag);
1076 CALLOC_GOTO(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + len + 1);
1077 CIRCLEQ_INIT(&tqp->tagq);
1078 tqp->tag = tqp->buf;
1079 memcpy(tqp->tag, tag, (tqp->tlen = len) + 1);
1082 * Find the tag, only display missing file messages once, and
1083 * then only if we didn't find the tag.
1085 snprintf(command, sizeof(command), "global -%s '%s'", ref ? "rx" : "x", tag);
1086 if (fp = popen(command, "r")) {
1087 while (fgets(buf, sizeof(buf), fp)) {
1088 if (buf[strlen(buf)-1] == '\n') /* chop(buf) */
1089 buf[strlen(buf)-1] = 0;
1090 else
1091 while (fgetc(fp) != '\n')
1093 if (getentry(buf, &name, &file, &line) == 0) {
1094 echk = 1;
1095 F_SET(tfp, TAGF_ERR);
1096 break;
1098 CALLOC_GOTO(sp, tp,
1099 TAG *, 1, sizeof(TAG) + strlen(file) + 1 + strlen(line) + 1);
1100 tp->fname = tp->buf;
1101 strcpy(tp->fname, file);
1102 tp->fnlen = strlen(file);
1103 tp->search = tp->fname + tp->fnlen + 1;
1104 strcpy(tp->search, line);
1105 CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q);
1107 pclose(fp);
1110 /* Check to see if we found anything. */
1111 if (tqp->tagq.cqh_first == (void *)&tqp->tagq) {
1112 msgq_str(sp, M_ERR, tag, "162|%s: tag not found");
1113 free(tqp);
1114 return (NULL);
1117 tqp->current = tqp->tagq.cqh_first;
1118 return (tqp);
1120 alloc_err:
1121 return (NULL);
1123 #endif
1125 * ctag_slist --
1126 * Search the list of tags files for a tag, and return tag queue.
1128 static TAGQ *
1129 ctag_slist(sp, tag)
1130 SCR *sp;
1131 char *tag;
1133 EX_PRIVATE *exp;
1134 TAGF *tfp;
1135 TAGQ *tqp;
1136 size_t len;
1137 int echk;
1139 exp = EXP(sp);
1141 /* Allocate and initialize the tag queue structure. */
1142 len = strlen(tag);
1143 CALLOC_GOTO(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + len + 1);
1144 CIRCLEQ_INIT(&tqp->tagq);
1145 tqp->tag = tqp->buf;
1146 memcpy(tqp->tag, tag, (tqp->tlen = len) + 1);
1149 * Find the tag, only display missing file messages once, and
1150 * then only if we didn't find the tag.
1152 for (echk = 0,
1153 tfp = exp->tagfq.tqh_first; tfp != NULL; tfp = tfp->q.tqe_next)
1154 if (ctag_sfile(sp, tfp, tqp, tag)) {
1155 echk = 1;
1156 F_SET(tfp, TAGF_ERR);
1157 } else
1158 F_CLR(tfp, TAGF_ERR | TAGF_ERR_WARN);
1160 /* Check to see if we found anything. */
1161 if (tqp->tagq.cqh_first == (void *)&tqp->tagq) {
1162 msgq_str(sp, M_ERR, tag, "162|%s: tag not found");
1163 if (echk)
1164 for (tfp = exp->tagfq.tqh_first;
1165 tfp != NULL; tfp = tfp->q.tqe_next)
1166 if (F_ISSET(tfp, TAGF_ERR) &&
1167 !F_ISSET(tfp, TAGF_ERR_WARN)) {
1168 errno = tfp->errnum;
1169 msgq_str(sp, M_SYSERR, tfp->name, "%s");
1170 F_SET(tfp, TAGF_ERR_WARN);
1172 free(tqp);
1173 return (NULL);
1176 tqp->current = tqp->tagq.cqh_first;
1177 return (tqp);
1179 alloc_err:
1180 return (NULL);
1184 * ctag_sfile --
1185 * Search a tags file for a tag, adding any found to the tag queue.
1187 static int
1188 ctag_sfile(sp, tfp, tqp, tname)
1189 SCR *sp;
1190 TAGF *tfp;
1191 TAGQ *tqp;
1192 char *tname;
1194 struct stat sb;
1195 TAG *tp;
1196 size_t dlen, nlen, slen;
1197 int fd, i, nf1, nf2;
1198 char *back, *cname, *dname, *front, *map, *name, *p, *search, *t;
1200 if ((fd = open(tfp->name, O_RDONLY, 0)) < 0) {
1201 tfp->errnum = errno;
1202 return (1);
1206 * XXX
1207 * Some old BSD systems require MAP_FILE as an argument when mapping
1208 * regular files.
1210 #ifndef MAP_FILE
1211 #define MAP_FILE 0
1212 #endif
1214 * XXX
1215 * We'd like to test if the file is too big to mmap. Since we don't
1216 * know what size or type off_t's or size_t's are, what the largest
1217 * unsigned integral type is, or what random insanity the local C
1218 * compiler will perpetrate, doing the comparison in a portable way
1219 * is flatly impossible. Hope mmap fails if the file is too large.
1221 if (fstat(fd, &sb) != 0 ||
1222 (map = mmap(NULL, (size_t)sb.st_size, PROT_READ | PROT_WRITE,
1223 MAP_FILE | MAP_PRIVATE, fd, (off_t)0)) == (caddr_t)-1) {
1224 tfp->errnum = errno;
1225 (void)close(fd);
1226 return (1);
1229 front = map;
1230 back = front + sb.st_size;
1231 front = binary_search(tname, front, back);
1232 front = linear_search(tname, front, back);
1233 if (front == NULL)
1234 goto done;
1237 * Initialize and link in the tag structure(s). The historic ctags
1238 * file format only permitted a single tag location per tag. The
1239 * obvious extension to permit multiple tags locations per tag is to
1240 * output multiple records in the standard format. Unfortunately,
1241 * this won't work correctly with historic ex/vi implementations,
1242 * because their binary search assumes that there's only one record
1243 * per tag, and so will use a random tag entry if there si more than
1244 * one. This code handles either format.
1246 * The tags file is in the following format:
1248 * <tag> <filename> <line number> | <pattern>
1250 * Figure out how long everything is so we can allocate in one swell
1251 * foop, but discard anything that looks wrong.
1253 for (;;) {
1254 /* Nul-terminate the end of the line. */
1255 for (p = front; p < back && *p != '\n'; ++p);
1256 if (p == back || *p != '\n')
1257 break;
1258 *p = '\0';
1260 /* Update the pointers for the next time. */
1261 t = p + 1;
1262 p = front;
1263 front = t;
1265 /* Break the line into tokens. */
1266 for (i = 0; i < 2 && (t = strsep(&p, "\t ")) != NULL; ++i)
1267 switch (i) {
1268 case 0: /* Tag. */
1269 cname = t;
1270 break;
1271 case 1: /* Filename. */
1272 name = t;
1273 nlen = strlen(name);
1274 break;
1277 /* Check for corruption. */
1278 if (i != 2 || p == NULL || t == NULL)
1279 goto corrupt;
1281 /* The rest of the string is the search pattern. */
1282 search = p;
1283 if ((slen = strlen(p)) == 0) {
1284 corrupt: p = msg_print(sp, tname, &nf1);
1285 t = msg_print(sp, tfp->name, &nf2);
1286 msgq(sp, M_ERR, "163|%s: corrupted tag in %s", p, t);
1287 if (nf1)
1288 FREE_SPACE(sp, p, 0);
1289 if (nf2)
1290 FREE_SPACE(sp, t, 0);
1291 continue;
1294 /* Check for passing the last entry. */
1295 if (strcmp(tname, cname))
1296 break;
1298 /* Resolve the file name. */
1299 ctag_file(sp, tfp, name, &dname, &dlen);
1301 CALLOC_GOTO(sp, tp,
1302 TAG *, 1, sizeof(TAG) + dlen + 2 + nlen + 1 + slen + 1);
1303 tp->fname = tp->buf;
1304 if (dlen != 0) {
1305 memcpy(tp->fname, dname, dlen);
1306 tp->fname[dlen] = '/';
1307 ++dlen;
1309 memcpy(tp->fname + dlen, name, nlen + 1);
1310 tp->fnlen = dlen + nlen;
1311 tp->search = tp->fname + tp->fnlen + 1;
1312 memcpy(tp->search, search, (tp->slen = slen) + 1);
1313 CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q);
1316 alloc_err:
1317 done: if (munmap(map, (size_t)sb.st_size))
1318 msgq(sp, M_SYSERR, "munmap");
1319 if (close(fd))
1320 msgq(sp, M_SYSERR, "close");
1321 return (0);
1325 * ctag_file --
1326 * Search for the right path to this file.
1328 static void
1329 ctag_file(sp, tfp, name, dirp, dlenp)
1330 SCR *sp;
1331 TAGF *tfp;
1332 char *name, **dirp;
1333 size_t *dlenp;
1335 struct stat sb;
1336 size_t len;
1337 char *p, buf[MAXPATHLEN];
1340 * !!!
1341 * If the tag file path is a relative path, see if it exists. If it
1342 * doesn't, look relative to the tags file path. It's okay for a tag
1343 * file to not exist, and historically, vi simply displayed a "new"
1344 * file. However, if the path exists relative to the tag file, it's
1345 * pretty clear what's happening, so we may as well get it right.
1347 *dlenp = 0;
1348 if (name[0] != '/' &&
1349 stat(name, &sb) && (p = strrchr(tfp->name, '/')) != NULL) {
1350 *p = '\0';
1351 len = snprintf(buf, sizeof(buf), "%s/%s", tfp->name, name);
1352 *p = '/';
1353 if (stat(buf, &sb) == 0) {
1354 *dirp = tfp->name;
1355 *dlenp = strlen(*dirp);
1361 * Binary search for "string" in memory between "front" and "back".
1363 * This routine is expected to return a pointer to the start of a line at
1364 * *or before* the first word matching "string". Relaxing the constraint
1365 * this way simplifies the algorithm.
1367 * Invariants:
1368 * front points to the beginning of a line at or before the first
1369 * matching string.
1371 * back points to the beginning of a line at or after the first
1372 * matching line.
1374 * Base of the Invariants.
1375 * front = NULL;
1376 * back = EOF;
1378 * Advancing the Invariants:
1380 * p = first newline after halfway point from front to back.
1382 * If the string at "p" is not greater than the string to match,
1383 * p is the new front. Otherwise it is the new back.
1385 * Termination:
1387 * The definition of the routine allows it return at any point,
1388 * since front is always at or before the line to print.
1390 * In fact, it returns when the chosen "p" equals "back". This
1391 * implies that there exists a string is least half as long as
1392 * (back - front), which in turn implies that a linear search will
1393 * be no more expensive than the cost of simply printing a string or two.
1395 * Trying to continue with binary search at this point would be
1396 * more trouble than it's worth.
1398 #define EQUAL 0
1399 #define GREATER 1
1400 #define LESS (-1)
1402 #define SKIP_PAST_NEWLINE(p, back) while (p < back && *p++ != '\n');
1404 static char *
1405 binary_search(string, front, back)
1406 register char *string, *front, *back;
1408 register char *p;
1410 p = front + (back - front) / 2;
1411 SKIP_PAST_NEWLINE(p, back);
1413 while (p != back) {
1414 if (compare(string, p, back) == GREATER)
1415 front = p;
1416 else
1417 back = p;
1418 p = front + (back - front) / 2;
1419 SKIP_PAST_NEWLINE(p, back);
1421 return (front);
1425 * Find the first line that starts with string, linearly searching from front
1426 * to back.
1428 * Return NULL for no such line.
1430 * This routine assumes:
1432 * o front points at the first character in a line.
1433 * o front is before or at the first line to be printed.
1435 static char *
1436 linear_search(string, front, back)
1437 char *string, *front, *back;
1439 while (front < back) {
1440 switch (compare(string, front, back)) {
1441 case EQUAL: /* Found it. */
1442 return (front);
1443 case LESS: /* No such string. */
1444 return (NULL);
1445 case GREATER: /* Keep going. */
1446 break;
1448 SKIP_PAST_NEWLINE(front, back);
1450 return (NULL);
1454 * Return LESS, GREATER, or EQUAL depending on how the string1 compares
1455 * with string2 (s1 ??? s2).
1457 * o Matches up to len(s1) are EQUAL.
1458 * o Matches up to len(s2) are GREATER.
1460 * The string "s1" is null terminated. The string s2 is '\t', space, (or
1461 * "back") terminated.
1463 * !!!
1464 * Reasonably modern ctags programs use tabs as separators, not spaces.
1465 * However, historic programs did use spaces, and, I got complaints.
1467 static int
1468 compare(s1, s2, back)
1469 register char *s1, *s2, *back;
1471 for (; *s1 && s2 < back && (*s2 != '\t' && *s2 != ' '); ++s1, ++s2)
1472 if (*s1 != *s2)
1473 return (*s1 < *s2 ? LESS : GREATER);
1474 return (*s1 ? GREATER : s2 < back &&
1475 (*s2 != '\t' && *s2 != ' ') ? LESS : EQUAL);