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.28 1993/11/28 19:31:36 bostic Exp $ (Berkeley) $Date: 1993/11/28 19:31:36 $";
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((char *)cmdp
->argv
[0])) == 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 if ((tp
= calloc(1, sizeof(TAG
))) == NULL
)
170 msgq(sp
, M_SYSERR
, NULL
);
171 if (exp
->tagq
.tqh_first
== NULL
) {
175 TAILQ_INSERT_HEAD(&exp
->tagq
, tp
, q
);
177 if ((tp
= calloc(1, sizeof(TAG
))) == NULL
)
178 msgq(sp
, M_SYSERR
, NULL
);
182 /* Copy the search pattern. */
183 if ((tp
->search
= strdup(search
)) == NULL
)
184 msgq(sp
, M_SYSERR
, NULL
);
186 tp
->slen
= strlen(search
);
192 /* Switch to the new file. */
196 MODIFY_CHECK(sp
, sp
->ep
, F_ISSET(cmdp
, E_FORCE
));
198 if (file_init(sp
, frp
, NULL
, 0)) {
200 FREE(tp
, sizeof(TAG
));
201 FREE(tag
, strlen(tag
));
209 * Historic vi accepted a line number as well as a search
210 * string, and people are apparently still using the format.
212 if (isdigit(search
[0])) {
213 m
.lno
= atoi(search
);
218 * Search for the tag; cheap fallback for C functions
219 * if the name is the same but the arguments have changed.
223 flags
= SEARCH_FILE
| SEARCH_TAG
| SEARCH_TERM
;
224 sval
= f_search(sp
, sp
->ep
, &m
, &m
, search
, NULL
, &flags
);
225 if (sval
&& (p
= strrchr(search
, '(')) != NULL
) {
227 sval
= f_search(sp
, sp
->ep
,
228 &m
, &m
, search
, NULL
, &flags
);
231 msgq(sp
, M_ERR
, "%s: search pattern not found.", tag
);
233 FREE(tag
, strlen(tag
));
239 F_SET(frp
, FR_CURSORSET
);
240 F_SET(sp
, S_FSWITCH
);
250 /* Push the tag onto the stack. */
254 TAILQ_INSERT_HEAD(&exp
->tagq
, tp
, q
);
260 /* Free a tag or tagf structure from a queue. */
261 #define FREETAG(tp) { \
262 TAILQ_REMOVE(&exp->tagq, (tp), q); \
263 if ((tp)->search != NULL) \
264 FREE((tp)->search, (tp)->slen); \
265 FREE((tp), sizeof(TAGF)); \
267 #define FREETAGF(tfp) { \
268 TAILQ_REMOVE(&exp->tagfq, (tfp), q); \
269 FREE((tfp)->name, strlen((tfp)->name) + 1); \
270 FREE((tfp), sizeof(TAGF)); \
274 * ex_tagpop -- :tagp[op][!] [number | file]
278 ex_tagpop(sp
, ep
, cmdp
)
289 /* Check for an empty stack. */
291 if (exp
->tagq
.tqh_first
== NULL
) {
292 msgq(sp
, M_INFO
, "The tags stack is empty.");
296 switch (cmdp
->argc
) {
298 /* Toss the current record. */
299 tp
= exp
->tagq
.tqh_first
;
304 off
= strtol(arg
, &p
, 10);
309 tp
= exp
->tagq
.tqh_first
;
313 arglen
= strlen(arg
);
314 for (tp
= exp
->tagq
.tqh_first
;
315 tp
!= NULL
; tp
= tp
->q
.tqe_next
) {
316 /* Use the user's original file name. */
318 if ((t
= strrchr(p
, '/')) == NULL
)
322 if (!strncmp(arg
, t
, arglen
)) {
329 "No file named %s on the tags stack; use :display to see the tags stack.",
334 tp
= exp
->tagq
.tqh_first
;
345 /* If not switching files, it's easy; else do the work. */
346 tp
= exp
->tagq
.tqh_first
;
347 if (tp
->frp
== sp
->frp
) {
351 MODIFY_CHECK(sp
, ep
, F_ISSET(cmdp
, E_FORCE
));
353 if (file_init(sp
, tp
->frp
, NULL
, 0))
356 tp
->frp
->lno
= tp
->lno
;
357 tp
->frp
->cno
= tp
->cno
;
358 F_SET(sp
->frp
, FR_CURSORSET
);
360 F_SET(sp
, S_FSWITCH
);
363 /* If returning to the first tag, the stack is now empty. */
364 if (tp
->q
.tqe_next
== NULL
)
370 * ex_tagtop -- :tagt[op][!]
371 * Clear the tag stack.
374 ex_tagtop(sp
, ep
, cmdp
)
383 /* Pop to oldest saved information. */
385 for (found
= 0; (tp
= exp
->tagq
.tqh_first
) != NULL
; found
= 1) {
386 if (exp
->tagq
.tqh_first
== NULL
)
392 msgq(sp
, M_INFO
, "The tags stack is empty.");
396 /* If not switching files, it's easy; else do the work. */
397 if (tmp
.frp
== sp
->frp
) {
401 MODIFY_CHECK(sp
, sp
->ep
, F_ISSET(cmdp
, E_FORCE
));
403 if (file_init(sp
, tmp
.frp
, NULL
, 0))
406 tmp
.frp
->lno
= tmp
.lno
;
407 tmp
.frp
->cno
= tmp
.cno
;
409 F_SET(sp
->frp
, FR_CURSORSET
);
411 F_SET(sp
, S_FSWITCH
);
418 * Display the list of tags.
421 ex_tagdisplay(sp
, ep
)
432 if ((tp
= exp
->tagq
.tqh_first
) == NULL
) {
433 (void)ex_printf(EXCOOKIE
, "No tags to display.\n");
438 * Figure out the formatting. MNOC is the maximum
439 * number of file name columns before we split the line.
443 tp
= exp
->tagq
.tqh_first
; tp
!= NULL
; tp
= tp
->q
.tqe_next
) {
444 len
= tp
->frp
->nlen
; /* The original name. */
445 name
= tp
->frp
->name
;
446 if (maxlen
< len
&& len
< MNOC
)
451 tp
= exp
->tagq
.tqh_first
; tp
!= NULL
;
452 ++cnt
, tp
= tp
->q
.tqe_next
) {
453 len
= tp
->frp
->nlen
; /* The original name. */
454 name
= tp
->frp
->name
;
455 if (len
> maxlen
|| len
+ tp
->slen
> sp
->cols
)
456 if (tp
== NULL
|| tp
->search
== NULL
)
457 (void)ex_printf(EXCOOKIE
,
458 "%2d %s\n", cnt
, name
);
460 (void)ex_printf(EXCOOKIE
,
461 "%2d %s\n** %*.*s %s\n", cnt
, name
,
462 (int)maxlen
, (int)maxlen
, "", tp
->search
);
464 if (tp
== NULL
|| tp
->search
== NULL
)
465 (void)ex_printf(EXCOOKIE
, "%2d %*.*s\n",
466 cnt
, (int)maxlen
, (int)len
, name
);
468 (void)ex_printf(EXCOOKIE
, "%2d %*.*s %s\n",
469 cnt
, (int)maxlen
, (int)len
, name
,
477 * Create a new list of tag files.
489 /* Free current queue. */
491 while ((tp
= exp
->tagfq
.tqh_first
) != NULL
)
494 /* Create new queue. */
495 for (p
= t
= str
;; ++p
) {
496 if (*p
== '\0' || isblank(*p
)) {
497 if ((len
= p
- t
) > 1) {
498 if ((tp
= malloc(sizeof(TAGF
))) == NULL
||
499 (tp
->name
= malloc(len
+ 1)) == NULL
) {
501 FREE(tp
, sizeof(TAGF
));
502 msgq(sp
, M_SYSERR
, NULL
);
505 memmove(tp
->name
, t
, len
);
506 tp
->name
[len
] = '\0';
508 TAILQ_INSERT_TAIL(&exp
->tagfq
, tp
, q
);
517 /* Free previous queue. */
520 * Free the tags file list.
530 /* Free up tag information. */
532 while ((tp
= exp
->tagq
.tqh_first
) != NULL
)
534 while ((tfp
= exp
->tagfq
.tqh_first
) != NULL
)
536 FREE(exp
->tlast
, strlen(exp
->tlast
) + 1);
542 * Copy a screen's tag structures.
548 EX_PRIVATE
*oexp
, *nexp
;
552 /* Copy tag stack. */
555 for (ap
= oexp
->tagq
.tqh_first
; ap
!= NULL
; ap
= ap
->q
.tqe_next
) {
556 if ((tp
= malloc(sizeof(TAG
))) == NULL
)
559 if (ap
->search
!= NULL
&&
560 (tp
->search
= strdup(ap
->search
)) == NULL
)
562 TAILQ_INSERT_TAIL(&nexp
->tagq
, tp
, q
);
565 /* Copy list of tag files. */
566 for (atfp
= oexp
->tagfq
.tqh_first
;
567 atfp
!= NULL
; atfp
= atfp
->q
.tqe_next
) {
568 if ((tfp
= malloc(sizeof(TAGF
))) == NULL
)
571 if ((tfp
->name
= strdup(atfp
->name
)) == NULL
)
573 TAILQ_INSERT_TAIL(&nexp
->tagfq
, tfp
, q
);
576 /* Copy the last tag. */
577 if (oexp
->tlast
!= NULL
&&
578 (nexp
->tlast
= strdup(oexp
->tlast
)) == NULL
) {
579 nomem
: msgq(sp
, M_SYSERR
, NULL
);
587 * Get a tag from the tags files.
590 tag_get(sp
, tag
, tagp
, filep
, searchp
)
592 char *tag
, **tagp
, **filep
, **searchp
;
600 * Find the tag, only display missing file messages once, and
601 * then only if we didn't find the tag.
605 for (p
= NULL
, tfp
= exp
->tagfq
.tqh_first
;
606 tfp
!= NULL
&& p
== NULL
; tfp
= tfp
->q
.tqe_next
) {
608 F_CLR(tfp
, TAGF_DNE
);
609 if (search(sp
, tfp
->name
, tag
, &p
))
610 if (errno
== ENOENT
) {
611 if (!F_ISSET(tfp
, TAGF_DNE_WARN
)) {
613 F_SET(tfp
, TAGF_DNE
);
616 msgq(sp
, M_SYSERR
, tfp
->name
);
620 msgq(sp
, M_ERR
, "%s: tag not found.", tag
);
622 for (tfp
= exp
->tagfq
.tqh_first
;
623 tfp
!= NULL
; tfp
= tfp
->q
.tqe_next
)
624 if (F_ISSET(tfp
, TAGF_DNE
)) {
626 msgq(sp
, M_SYSERR
, tfp
->name
);
627 F_SET(tfp
, TAGF_DNE_WARN
);
633 * Set the return pointers; tagp points to the tag, and, incidentally
634 * the allocated string, filep points to the nul-terminated file name,
635 * searchp points to the nul-terminated search string.
637 for (*tagp
= p
; *p
&& !isblank(*p
); ++p
);
640 for (*p
++ = '\0'; isblank(*p
); ++p
);
641 for (*filep
= p
; *p
&& !isblank(*p
); ++p
);
644 for (*p
++ = '\0'; isblank(*p
); ++p
);
647 malformed
: free(*tagp
);
648 msgq(sp
, M_ERR
, "%s: corrupted tag in %s.", tag
, tfp
->name
);
660 * Search a file for a tag.
663 search(sp
, name
, tname
, tag
)
665 char *name
, *tname
, **tag
;
669 char *endp
, *back
, *front
, *map
, *p
;
671 if ((fd
= open(name
, O_RDONLY
, 0)) < 0)
676 * We'd like to test if the file is too big to mmap. Since we don't
677 * know what size or type off_t's or size_t's are, what the largest
678 * unsigned integral type is, or what random insanity the local C
679 * compiler will perpetrate, doing the comparison in a portable way
680 * is flatly impossible. Hope that malloc fails if the file is too
683 if (fstat(fd
, &sb
) || (map
= mmap(NULL
, (size_t)sb
.st_size
,
684 PROT_READ
, MAP_PRIVATE
, fd
, (off_t
)0)) == (caddr_t
)-1) {
689 back
= front
+ sb
.st_size
;
691 front
= binary_search(tname
, front
, back
);
692 front
= linear_search(tname
, front
, back
);
694 if (front
== NULL
|| (endp
= strchr(front
, '\n')) == NULL
) {
700 if ((p
= malloc(len
+ 1)) == NULL
) {
701 msgq(sp
, M_SYSERR
, NULL
);
705 memmove(p
, front
, len
);
709 done
: if (munmap(map
, (size_t)sb
.st_size
))
710 msgq(sp
, M_SYSERR
, "munmap");
712 msgq(sp
, M_SYSERR
, "close");
717 * Binary search for "string" in memory between "front" and "back".
719 * This routine is expected to return a pointer to the start of a line at
720 * *or before* the first word matching "string". Relaxing the constraint
721 * this way simplifies the algorithm.
724 * front points to the beginning of a line at or before the first
727 * back points to the beginning of a line at or after the first
730 * Base of the Invariants.
734 * Advancing the Invariants:
736 * p = first newline after halfway point from front to back.
738 * If the string at "p" is not greater than the string to match,
739 * p is the new front. Otherwise it is the new back.
743 * The definition of the routine allows it return at any point,
744 * since front is always at or before the line to print.
746 * In fact, it returns when the chosen "p" equals "back". This
747 * implies that there exists a string is least half as long as
748 * (back - front), which in turn implies that a linear search will
749 * be no more expensive than the cost of simply printing a string or two.
751 * Trying to continue with binary search at this point would be
752 * more trouble than it's worth.
754 #define SKIP_PAST_NEWLINE(p, back) while (p < back && *p++ != '\n');
757 binary_search(string
, front
, back
)
758 register char *string
, *front
, *back
;
762 p
= front
+ (back
- front
) / 2;
763 SKIP_PAST_NEWLINE(p
, back
);
766 if (compare(string
, p
, back
) == GREATER
)
770 p
= front
+ (back
- front
) / 2;
771 SKIP_PAST_NEWLINE(p
, back
);
777 * Find the first line that starts with string, linearly searching from front
780 * Return NULL for no such line.
782 * This routine assumes:
784 * o front points at the first character in a line.
785 * o front is before or at the first line to be printed.
788 linear_search(string
, front
, back
)
789 char *string
, *front
, *back
;
791 while (front
< back
) {
792 switch (compare(string
, front
, back
)) {
793 case EQUAL
: /* Found it. */
796 case LESS
: /* No such string. */
799 case GREATER
: /* Keep going. */
802 SKIP_PAST_NEWLINE(front
, back
);
808 * Return LESS, GREATER, or EQUAL depending on how the string1 compares
809 * with string2 (s1 ??? s2).
811 * o Matches up to len(s1) are EQUAL.
812 * o Matches up to len(s2) are GREATER.
814 * The string "s1" is null terminated. The string s2 is '\t', space, (or
815 * "back") terminated.
818 * Reasonably modern ctags programs use tabs as separators, not spaces.
819 * However, historic programs did use spaces, and, I got complaints.
822 compare(s1
, s2
, back
)
823 register char *s1
, *s2
, *back
;
825 for (; *s1
&& s2
< back
&& (*s2
!= '\t' && *s2
!= ' '); ++s1
, ++s2
)
827 return (*s1
< *s2
? LESS
: GREATER
);
828 return (*s1
? GREATER
: s2
< back
&&
829 (*s2
!= '\t' && *s2
!= ' ') ? LESS
: EQUAL
);