2 * Copyright (c) 1992, 1993
3 * The Regents of the University of California. All rights reserved.
5 * This code is derived from software contributed to Berkeley by
6 * David Hitz of Auspex Systems, Inc.
8 * %sccs.include.redist.c%
12 static char sccsid
[] = "$Id: ex_tag.c,v 8.30 1993/12/09 19:42:48 bostic Exp $ (Berkeley) $Date: 1993/12/09 19:42:48 $";
15 #include <sys/types.h>
31 static char *binary_search
__P((char *, char *, char *));
32 static int compare
__P((char *, char *, char *));
33 static char *linear_search
__P((char *, char *, char *));
34 static int search
__P((SCR
*, char *, char *, char **));
35 static int tag_get
__P((SCR
*, char *, char **, char **, char **));
39 * The tag code can be entered from main, i.e. "vi -t tag".
42 ex_tagfirst(sp
, tagarg
)
51 char *p
, *tag
, *name
, *search
;
53 /* Taglength may limit the number of characters. */
54 if ((tl
= O_VAL(sp
, O_TAGLENGTH
)) != 0 && strlen(tagarg
) > tl
)
57 /* Get the tag information. */
58 if (tag_get(sp
, tagarg
, &tag
, &name
, &search
))
61 /* Create the file entry. */
62 if ((frp
= file_add(sp
, NULL
, name
, 0)) == NULL
)
64 if (file_init(sp
, frp
, NULL
, 0))
69 * Historic vi accepted a line number as well as a search
70 * string, and people are apparently still using the format.
72 if (isdigit(search
[0])) {
77 * Search for the tag; cheap fallback for C functions if
78 * the name is the same but the arguments have changed.
82 flags
= SEARCH_FILE
| SEARCH_TAG
| SEARCH_TERM
;
83 sval
= f_search(sp
, sp
->ep
, &m
, &m
, search
, NULL
, &flags
);
84 if (sval
&& (p
= strrchr(search
, '(')) != NULL
) {
86 sval
= f_search(sp
, sp
->ep
,
87 &m
, &m
, search
, NULL
, &flags
);
90 msgq(sp
, M_ERR
, "%s: search pattern not found.", tag
);
93 /* Set up the screen. */
96 F_SET(frp
, FR_CURSORSET
);
98 /* Might as well make this the default tag. */
99 if ((EXP(sp
)->tlast
= strdup(tagarg
)) == NULL
) {
100 msgq(sp
, M_SYSERR
, NULL
);
107 * ex_tagpush -- :tag [file]
111 ex_tagpush(sp
, ep
, cmdp
)
116 enum {TC_CHANGE
, TC_CURRENT
} which
;
124 char *name
, *p
, *search
, *tag
;
127 switch (cmdp
->argc
) {
129 if (exp
->tlast
!= NULL
)
130 FREE(exp
->tlast
, strlen(exp
->tlast
) + 1);
131 if ((exp
->tlast
= strdup(cmdp
->argv
[0]->bp
)) == NULL
) {
132 msgq(sp
, M_SYSERR
, NULL
);
137 if (exp
->tlast
== NULL
) {
138 msgq(sp
, M_ERR
, "No previous tag entered.");
146 /* Taglength may limit the number of characters. */
147 if ((tl
= O_VAL(sp
, O_TAGLENGTH
)) != 0 && strlen(exp
->tlast
) > tl
)
148 exp
->tlast
[tl
] = '\0';
150 /* Get the tag information. */
151 if (tag_get(sp
, exp
->tlast
, &tag
, &name
, &search
))
154 /* Get a new FREF structure. */
155 if ((frp
= file_add(sp
, sp
->frp
, name
, 1)) == NULL
) {
156 FREE(tag
, strlen(tag
));
161 * The tags stacks in nvi are a bit tricky. The first record on the
162 * stack is the place where we first did a tag, so it has no search
163 * string. The second record is the first tag, and so on. This means
164 * that the "current" tag is always on the stack. Each tag contains
165 * a file name, search string, and line/column numbers. The search
166 * string is only used for the first access and to display to the user.
167 * we use the saved line/column number when returning to a file.
169 CALLOC(sp
, tp
, TAG
*, 1, sizeof(TAG
));
170 if (exp
->tagq
.tqh_first
== NULL
) {
174 TAILQ_INSERT_HEAD(&exp
->tagq
, tp
, q
);
176 CALLOC(sp
, tp
, TAG
*, 1, sizeof(TAG
));
180 /* Copy the search pattern. */
181 if ((tp
->search
= strdup(search
)) == NULL
)
182 msgq(sp
, M_SYSERR
, NULL
);
184 tp
->slen
= strlen(search
);
190 /* Switch to the new file. */
194 MODIFY_CHECK(sp
, sp
->ep
, F_ISSET(cmdp
, E_FORCE
));
196 if (file_init(sp
, frp
, NULL
, 0)) {
198 FREE(tp
, sizeof(TAG
));
199 FREE(tag
, strlen(tag
));
207 * Historic vi accepted a line number as well as a search
208 * string, and people are apparently still using the format.
210 if (isdigit(search
[0])) {
211 m
.lno
= atoi(search
);
216 * Search for the tag; cheap fallback for C functions
217 * if the name is the same but the arguments have changed.
221 flags
= SEARCH_FILE
| SEARCH_TAG
| SEARCH_TERM
;
222 sval
= f_search(sp
, sp
->ep
, &m
, &m
, search
, NULL
, &flags
);
223 if (sval
&& (p
= strrchr(search
, '(')) != NULL
) {
225 sval
= f_search(sp
, sp
->ep
,
226 &m
, &m
, search
, NULL
, &flags
);
229 msgq(sp
, M_ERR
, "%s: search pattern not found.", tag
);
231 FREE(tag
, strlen(tag
));
237 F_SET(frp
, FR_CURSORSET
);
238 F_SET(sp
, S_FSWITCH
);
248 /* Push the tag onto the stack. */
252 TAILQ_INSERT_HEAD(&exp
->tagq
, tp
, q
);
258 /* Free a tag or tagf structure from a queue. */
259 #define FREETAG(tp) { \
260 TAILQ_REMOVE(&exp->tagq, (tp), q); \
261 if ((tp)->search != NULL) \
262 FREE((tp)->search, (tp)->slen); \
263 FREE((tp), sizeof(TAGF)); \
265 #define FREETAGF(tfp) { \
266 TAILQ_REMOVE(&exp->tagfq, (tfp), q); \
267 FREE((tfp)->name, strlen((tfp)->name) + 1); \
268 FREE((tfp), sizeof(TAGF)); \
272 * ex_tagpop -- :tagp[op][!] [number | file]
276 ex_tagpop(sp
, ep
, cmdp
)
287 /* Check for an empty stack. */
289 if (exp
->tagq
.tqh_first
== NULL
) {
290 msgq(sp
, M_INFO
, "The tags stack is empty.");
294 switch (cmdp
->argc
) {
296 /* Toss the current record. */
297 tp
= exp
->tagq
.tqh_first
;
301 arg
= cmdp
->argv
[0]->bp
;
302 off
= strtol(arg
, &p
, 10);
307 tp
= exp
->tagq
.tqh_first
;
311 arglen
= strlen(arg
);
312 for (tp
= exp
->tagq
.tqh_first
;
313 tp
!= NULL
; tp
= tp
->q
.tqe_next
) {
314 /* Use the user's original file name. */
316 if ((t
= strrchr(p
, '/')) == NULL
)
320 if (!strncmp(arg
, t
, arglen
)) {
327 "No file named %s on the tags stack; use :display to see the tags stack.",
332 tp
= exp
->tagq
.tqh_first
;
343 /* If not switching files, it's easy; else do the work. */
344 tp
= exp
->tagq
.tqh_first
;
345 if (tp
->frp
== sp
->frp
) {
349 MODIFY_CHECK(sp
, ep
, F_ISSET(cmdp
, E_FORCE
));
351 if (file_init(sp
, tp
->frp
, NULL
, 0))
354 tp
->frp
->lno
= tp
->lno
;
355 tp
->frp
->cno
= tp
->cno
;
356 F_SET(sp
->frp
, FR_CURSORSET
);
358 F_SET(sp
, S_FSWITCH
);
361 /* If returning to the first tag, the stack is now empty. */
362 if (tp
->q
.tqe_next
== NULL
)
368 * ex_tagtop -- :tagt[op][!]
369 * Clear the tag stack.
372 ex_tagtop(sp
, ep
, cmdp
)
381 /* Pop to oldest saved information. */
383 for (found
= 0; (tp
= exp
->tagq
.tqh_first
) != NULL
; found
= 1) {
384 if (exp
->tagq
.tqh_first
== NULL
)
390 msgq(sp
, M_INFO
, "The tags stack is empty.");
394 /* If not switching files, it's easy; else do the work. */
395 if (tmp
.frp
== sp
->frp
) {
399 MODIFY_CHECK(sp
, sp
->ep
, F_ISSET(cmdp
, E_FORCE
));
401 if (file_init(sp
, tmp
.frp
, NULL
, 0))
404 tmp
.frp
->lno
= tmp
.lno
;
405 tmp
.frp
->cno
= tmp
.cno
;
407 F_SET(sp
->frp
, FR_CURSORSET
);
409 F_SET(sp
, S_FSWITCH
);
416 * Display the list of tags.
419 ex_tagdisplay(sp
, ep
)
430 if ((tp
= exp
->tagq
.tqh_first
) == NULL
) {
431 (void)ex_printf(EXCOOKIE
, "No tags to display.\n");
436 * Figure out the formatting. MNOC is the maximum
437 * number of file name columns before we split the line.
441 tp
= exp
->tagq
.tqh_first
; tp
!= NULL
; tp
= tp
->q
.tqe_next
) {
442 len
= strlen(name
= tp
->frp
->name
); /* The original name. */
443 if (maxlen
< len
&& len
< MNOC
)
448 tp
= exp
->tagq
.tqh_first
; tp
!= NULL
;
449 ++cnt
, tp
= tp
->q
.tqe_next
) {
450 len
= strlen(name
= tp
->frp
->name
); /* The original name. */
451 if (len
> maxlen
|| len
+ tp
->slen
> sp
->cols
)
452 if (tp
== NULL
|| tp
->search
== NULL
)
453 (void)ex_printf(EXCOOKIE
,
454 "%2d %s\n", cnt
, name
);
456 (void)ex_printf(EXCOOKIE
,
457 "%2d %s\n** %*.*s %s\n", cnt
, name
,
458 (int)maxlen
, (int)maxlen
, "", tp
->search
);
460 if (tp
== NULL
|| tp
->search
== NULL
)
461 (void)ex_printf(EXCOOKIE
, "%2d %*.*s\n",
462 cnt
, (int)maxlen
, (int)len
, name
);
464 (void)ex_printf(EXCOOKIE
, "%2d %*.*s %s\n",
465 cnt
, (int)maxlen
, (int)len
, name
,
473 * Create a new list of tag files.
485 /* Free current queue. */
487 while ((tp
= exp
->tagfq
.tqh_first
) != NULL
)
490 /* Create new queue. */
491 for (p
= t
= str
;; ++p
) {
492 if (*p
== '\0' || isblank(*p
)) {
493 if ((len
= p
- t
) > 1) {
494 MALLOC_RET(sp
, tp
, TAGF
*, sizeof(TAGF
));
495 MALLOC(sp
, tp
->name
, char *, len
+ 1);
496 if (tp
->name
== NULL
) {
497 FREE(tp
, sizeof(TAGF
));
500 memmove(tp
->name
, t
, len
);
501 tp
->name
[len
] = '\0';
503 TAILQ_INSERT_TAIL(&exp
->tagfq
, tp
, q
);
512 /* Free previous queue. */
515 * Free the tags file list.
525 /* Free up tag information. */
527 while ((tp
= exp
->tagq
.tqh_first
) != NULL
)
529 while ((tfp
= exp
->tagfq
.tqh_first
) != NULL
)
531 FREE(exp
->tlast
, strlen(exp
->tlast
) + 1);
537 * Copy a screen's tag structures.
543 EX_PRIVATE
*oexp
, *nexp
;
547 /* Copy tag stack. */
550 for (ap
= oexp
->tagq
.tqh_first
; ap
!= NULL
; ap
= ap
->q
.tqe_next
) {
551 MALLOC(sp
, tp
, TAG
*, sizeof(TAG
));
555 if (ap
->search
!= NULL
&&
556 (tp
->search
= strdup(ap
->search
)) == NULL
)
558 TAILQ_INSERT_TAIL(&nexp
->tagq
, tp
, q
);
561 /* Copy list of tag files. */
562 for (atfp
= oexp
->tagfq
.tqh_first
;
563 atfp
!= NULL
; atfp
= atfp
->q
.tqe_next
) {
564 MALLOC(sp
, tfp
, TAGF
*, sizeof(TAGF
));
568 if ((tfp
->name
= strdup(atfp
->name
)) == NULL
)
570 TAILQ_INSERT_TAIL(&nexp
->tagfq
, tfp
, q
);
573 /* Copy the last tag. */
574 if (oexp
->tlast
!= NULL
&&
575 (nexp
->tlast
= strdup(oexp
->tlast
)) == NULL
) {
576 nomem
: msgq(sp
, M_SYSERR
, NULL
);
584 * Get a tag from the tags files.
587 tag_get(sp
, tag
, tagp
, filep
, searchp
)
589 char *tag
, **tagp
, **filep
, **searchp
;
597 * Find the tag, only display missing file messages once, and
598 * then only if we didn't find the tag.
602 for (p
= NULL
, tfp
= exp
->tagfq
.tqh_first
;
603 tfp
!= NULL
&& p
== NULL
; tfp
= tfp
->q
.tqe_next
) {
605 F_CLR(tfp
, TAGF_DNE
);
606 if (search(sp
, tfp
->name
, tag
, &p
))
607 if (errno
== ENOENT
) {
608 if (!F_ISSET(tfp
, TAGF_DNE_WARN
)) {
610 F_SET(tfp
, TAGF_DNE
);
613 msgq(sp
, M_SYSERR
, tfp
->name
);
617 msgq(sp
, M_ERR
, "%s: tag not found.", tag
);
619 for (tfp
= exp
->tagfq
.tqh_first
;
620 tfp
!= NULL
; tfp
= tfp
->q
.tqe_next
)
621 if (F_ISSET(tfp
, TAGF_DNE
)) {
623 msgq(sp
, M_SYSERR
, tfp
->name
);
624 F_SET(tfp
, TAGF_DNE_WARN
);
630 * Set the return pointers; tagp points to the tag, and, incidentally
631 * the allocated string, filep points to the nul-terminated file name,
632 * searchp points to the nul-terminated search string.
634 for (*tagp
= p
; *p
&& !isblank(*p
); ++p
);
637 for (*p
++ = '\0'; isblank(*p
); ++p
);
638 for (*filep
= p
; *p
&& !isblank(*p
); ++p
);
641 for (*p
++ = '\0'; isblank(*p
); ++p
);
644 malformed
: free(*tagp
);
645 msgq(sp
, M_ERR
, "%s: corrupted tag in %s.", tag
, tfp
->name
);
657 * Search a file for a tag.
660 search(sp
, name
, tname
, tag
)
662 char *name
, *tname
, **tag
;
666 char *endp
, *back
, *front
, *map
, *p
;
668 if ((fd
= open(name
, O_RDONLY
, 0)) < 0)
673 * We'd like to test if the file is too big to mmap. Since we don't
674 * know what size or type off_t's or size_t's are, what the largest
675 * unsigned integral type is, or what random insanity the local C
676 * compiler will perpetrate, doing the comparison in a portable way
677 * is flatly impossible. Hope that malloc fails if the file is too
680 if (fstat(fd
, &sb
) || (map
= mmap(NULL
, (size_t)sb
.st_size
,
681 PROT_READ
, MAP_PRIVATE
, fd
, (off_t
)0)) == (caddr_t
)-1) {
686 back
= front
+ sb
.st_size
;
688 front
= binary_search(tname
, front
, back
);
689 front
= linear_search(tname
, front
, back
);
691 if (front
== NULL
|| (endp
= strchr(front
, '\n')) == NULL
) {
697 MALLOC(sp
, p
, char *, len
+ 1);
702 memmove(p
, front
, len
);
706 done
: if (munmap(map
, (size_t)sb
.st_size
))
707 msgq(sp
, M_SYSERR
, "munmap");
709 msgq(sp
, M_SYSERR
, "close");
714 * Binary search for "string" in memory between "front" and "back".
716 * This routine is expected to return a pointer to the start of a line at
717 * *or before* the first word matching "string". Relaxing the constraint
718 * this way simplifies the algorithm.
721 * front points to the beginning of a line at or before the first
724 * back points to the beginning of a line at or after the first
727 * Base of the Invariants.
731 * Advancing the Invariants:
733 * p = first newline after halfway point from front to back.
735 * If the string at "p" is not greater than the string to match,
736 * p is the new front. Otherwise it is the new back.
740 * The definition of the routine allows it return at any point,
741 * since front is always at or before the line to print.
743 * In fact, it returns when the chosen "p" equals "back". This
744 * implies that there exists a string is least half as long as
745 * (back - front), which in turn implies that a linear search will
746 * be no more expensive than the cost of simply printing a string or two.
748 * Trying to continue with binary search at this point would be
749 * more trouble than it's worth.
751 #define SKIP_PAST_NEWLINE(p, back) while (p < back && *p++ != '\n');
754 binary_search(string
, front
, back
)
755 register char *string
, *front
, *back
;
759 p
= front
+ (back
- front
) / 2;
760 SKIP_PAST_NEWLINE(p
, back
);
763 if (compare(string
, p
, back
) == GREATER
)
767 p
= front
+ (back
- front
) / 2;
768 SKIP_PAST_NEWLINE(p
, back
);
774 * Find the first line that starts with string, linearly searching from front
777 * Return NULL for no such line.
779 * This routine assumes:
781 * o front points at the first character in a line.
782 * o front is before or at the first line to be printed.
785 linear_search(string
, front
, back
)
786 char *string
, *front
, *back
;
788 while (front
< back
) {
789 switch (compare(string
, front
, back
)) {
790 case EQUAL
: /* Found it. */
793 case LESS
: /* No such string. */
796 case GREATER
: /* Keep going. */
799 SKIP_PAST_NEWLINE(front
, back
);
805 * Return LESS, GREATER, or EQUAL depending on how the string1 compares
806 * with string2 (s1 ??? s2).
808 * o Matches up to len(s1) are EQUAL.
809 * o Matches up to len(s2) are GREATER.
811 * The string "s1" is null terminated. The string s2 is '\t', space, (or
812 * "back") terminated.
815 * Reasonably modern ctags programs use tabs as separators, not spaces.
816 * However, historic programs did use spaces, and, I got complaints.
819 compare(s1
, s2
, back
)
820 register char *s1
, *s2
, *back
;
822 for (; *s1
&& s2
< back
&& (*s2
!= '\t' && *s2
!= ' '); ++s1
, ++s2
)
824 return (*s1
< *s2
? LESS
: GREATER
);
825 return (*s1
? GREATER
: s2
< back
&&
826 (*s2
!= '\t' && *s2
!= ' ') ? LESS
: EQUAL
);