1 /*
2  * Look up vi-style tags in the file "tags".
3  *	Invoked either by ":ta routine-name" or by "^]" while sitting
4  *	on a string.  In the latter case, the tag is the word under
5  *	the cursor.
6  *	written for vile.
7  *
8  * Copyright (c) 1990, 1995-2018,2019 by Paul Fox and Thomas E. Dickey
9  *
10  * $Id: tags.c,v 1.155 2019/12/19 09:37:58 bod Exp $
11  */
12 #include "estruct.h"
13 #include "edef.h"
14 
15 #if OPT_TAGS
16 
17 #if OPT_TAGS_CMPL
18 typedef struct {
19     /* FIXME: we could make next-tag work faster if we also hash the
20      * line-pointers for each key.
21      */
22     char *bi_key;
23 } TAGS_DATA;
24 
25 #define BI_DATA TAGS_DATA
26 #include "btree.h"
27 
28 #endif
29 
30 #define UNTAG struct untag
31 UNTAG {
32     char *u_fname;
33     L_NUM u_lineno;
34     C_NUM u_colno;
35     UNTAG *u_stklink;
36 #if OPT_SHOW_TAGS
37     char *u_templ;
38 #endif
39 };
40 
41 #define TAGHITS struct taghits
42 TAGHITS {
43     TAGHITS *link;
44     LINE *tag;			/* points to tag-buffer line */
45     LINE *hit;			/* points to corresponding line in source-file */
46 };
47 
48 static TAGHITS *tag_hits = NULL;
49 static UNTAG *untaghead = NULL;
50 static char tagname[NFILEN + 2];	/* +2 since we may add a tab later */
51 
52 #if OPT_SHOW_TAGS
53 #  if OPT_UPBUFF
54 static int update_tagstack(BUFFER *bp);
55 #  endif
56 #endif /* OPT_SHOW_TAGS */
57 
58 /*
59  * return (in buf) the Nth whitespace
60  *	separated word in "path", counting from 0
61  */
62 static void
nth_name(char * buf,const char * path,int n)63 nth_name(char *buf, const char *path, int n)
64 {
65     while (n-- > 0) {
66 	path = skip_cblanks(path);
67 	path = skip_ctext(path);
68     }
69     path = skip_cblanks(path);
70     while (*path && !isSpace(*path))
71 	*buf++ = *path++;
72     *buf = EOS;
73 }
74 
75 static BUFFER *
gettagsfile(int n,int * endofpathflagp,int * did_read)76 gettagsfile(int n, int *endofpathflagp, int *did_read)
77 {
78 #ifdef MDCHK_MODTIME
79     time_t current;
80 #endif
81     char *tagsfile;
82     BUFFER *tagbp;
83     char tagbufname[NBUFN];
84     char tagfilename[NFILEN];
85 
86     *endofpathflagp = FALSE;
87     *did_read = FALSE;
88 
89     (void) lsprintf(tagbufname, TAGFILE_BufName, n + 1);
90 
91     /* is the buffer around? */
92     if ((tagbp = find_b_name(tagbufname)) == NULL) {
93 	char *tagf = global_b_val_ptr(VAL_TAGS);
94 
95 	nth_name(tagfilename, tagf, n);
96 	if (!doglob(tagfilename)
97 	    || tagfilename[0] == EOS) {
98 	    *endofpathflagp = TRUE;
99 	    return NULL;
100 	}
101 
102 	/* look up the tags file */
103 	tagsfile = cfg_locate(tagfilename, LOCATE_TAGS);
104 
105 	/* if it isn't around, don't sweat it */
106 	if (tagsfile == NULL) {
107 	    return NULL;
108 	}
109 
110 	/* find the pointer to that buffer */
111 	if ((tagbp = bfind(tagbufname, BFINVS)) == NULL) {
112 	    mlforce("[Can't create tags buffer]");
113 	    return NULL;
114 	}
115 
116 	if (readin(tagsfile, FALSE, tagbp, FALSE) != TRUE) {
117 	    zotbuf(tagbp);
118 	    return NULL;
119 	}
120 	set_tagsmode(tagbp);
121 	*did_read = TRUE;
122     }
123 #ifdef MDCHK_MODTIME
124     /*
125      * Re-read the tags buffer if we are checking modification-times and find
126      * that the tags file's been changed.
127      */
128     if (b_val(tagbp, MDCHK_MODTIME)
129 	&& get_modtime(tagbp, &current)
130 	&& tagbp->b_modtime != current) {
131 	if (!*did_read) {
132 	    if (readin(tagbp->b_fname, FALSE, tagbp, FALSE) != TRUE) {
133 		zotbuf(tagbp);
134 		return NULL;
135 	    }
136 	    set_tagsmode(tagbp);
137 	    *did_read = TRUE;
138 	}
139 	set_modtime(tagbp, tagbp->b_fname);
140     }
141 #endif
142     b_set_invisible(tagbp);
143     return tagbp;
144 }
145 
146 #if OPT_TAGS_CMPL
147 
148 static void
old_tags(BI_NODE * a)149 old_tags(BI_NODE * a)
150 {
151     beginDisplay();
152     FreeIfNeeded(BI_KEY(a));
153     FreeIfNeeded(TYPECAST(char, a));
154     endofDisplay();
155 }
156 
157 static BI_NODE *
new_tags(BI_DATA * a)158 new_tags(BI_DATA * a)
159 {
160     BI_NODE *p;
161 
162     beginDisplay();
163     if ((p = typecalloc(BI_NODE)) != NULL) {
164 	p->value = *a;
165 	if ((BI_KEY(p) = strmalloc(a->bi_key)) == NULL) {
166 	    old_tags(p);
167 	    p = NULL;
168 	}
169     }
170     endofDisplay();
171 
172     return p;
173 }
174 
175 /*ARGSUSED*/
176 static void
dpy_tags(BI_NODE * a GCC_UNUSED,int level GCC_UNUSED)177 dpy_tags(BI_NODE * a GCC_UNUSED, int level GCC_UNUSED)
178 {
179 #if OPT_TRACE
180     while (level-- > 0)
181 	TRACE((". "));
182     TRACE(("%s (%d)\n", BI_KEY(a), a->balance));
183 #endif
184 }
185 
186 static void
xcg_tags(BI_NODE * a,BI_NODE * b)187 xcg_tags(BI_NODE * a, BI_NODE * b)
188 {
189     BI_DATA temp = a->value;
190     a->value = b->value;
191     b->value = temp;
192 }
193 
194 #define BI_DATA0 {{0}, 0, {0}}
195 #define BI_TREE0 0, 0, BI_DATA0
196 static BI_TREE tags_tree =
197 {new_tags, old_tags, dpy_tags, xcg_tags, BI_TREE0};
198 
199 /* Parse the identifier out of the given line and store it in the binary tree */
200 static void
store_tag(LINE * lp)201 store_tag(LINE *lp)
202 {
203     char my_name[sizeof(tagname)];
204     size_t len, got;
205     int c;
206 
207     if (llength(lp) > 0) {
208 	len = (size_t) llength(lp);
209 	for (got = 0; got < len; got++) {
210 	    if (got >= sizeof(tagname) - 2) {
211 		return;		/* ignore super-long identifiers */
212 	    }
213 	    c = lgetc(lp, got);
214 	    if (!isGraph(c))
215 		break;
216 	    my_name[got] = (char) c;
217 	}
218 	my_name[got] = EOS;
219 	if (got) {
220 	    BI_DATA temp;
221 #ifdef MDTAGIGNORECASE
222 	    if (b_val(curbp, MDTAGIGNORECASE))
223 		mklower(my_name);
224 #endif
225 	    temp.bi_key = my_name;
226 	    btree_insert(&tags_tree, &temp);
227 	}
228     }
229 }
230 
231 /* check if the binary-tree is up-to-date.  If not, rebuild it. */
232 static const char **
init_tags_cmpl(char * buf,size_t cpos)233 init_tags_cmpl(char *buf, size_t cpos)
234 {
235     int tf_num;
236     BUFFER *bp;
237     LINE *lp;
238     int done;
239     int flag;
240     int obsolete = (tags_tree.count == 0);
241 
242 #ifdef MDTAGIGNORECASE
243     /* If curbp's b_val(curbp,MDTAGIGNORECASE)) is different from the last
244      * time we built the tree, obsolete the tree, since the keys changed.
245      */
246     {
247 	static int my_tcase = SORTOFTRUE;
248 	if (b_val(curbp, MDTAGIGNORECASE) != my_tcase) {
249 	    my_tcase = b_val(curbp, MDTAGIGNORECASE);
250 	    obsolete = TRUE;
251 	}
252     }
253 #endif
254     /*
255      * Check if we've already loaded all of the tags buffers.  If not, we
256      * know we should build the tree.  Also, if any aren't empty, we may
257      * have loaded the buffer for some other reason than tags processing.
258      */
259     if (!obsolete) {
260 	for (tf_num = 0;; tf_num++) {
261 	    bp = gettagsfile(tf_num, &done, &flag);
262 	    if (!done && bp == NULL)
263 		continue;	/* More tag files to examine */
264 	    if (done || bp == NULL)
265 		break;
266 	    (void) bsizes(bp);
267 	    obsolete = flag || (bp->b_linecount != 0);
268 	    if (obsolete)
269 		break;
270 	}
271     }
272 
273     if (obsolete) {
274 	btree_freeup(&tags_tree);
275 
276 	for (tf_num = 0;; tf_num++) {
277 	    bp = gettagsfile(tf_num, &done, &flag);
278 	    if (!done && bp == NULL)
279 		continue;	/* More tag files to examine */
280 	    if (done || bp == NULL)
281 		break;
282 	    for_each_line(lp, bp)
283 		store_tag(lp);
284 	}
285 
286 	TRACE(("stored %d tags entries\n", tags_tree.count));
287     }
288 
289     return btree_parray(&tags_tree, buf, cpos);
290 }
291 
292 int
tags_completion(DONE_ARGS)293 tags_completion(DONE_ARGS)
294 {
295     size_t cpos = *pos;
296     int status = FALSE;
297     const char **nptr;
298 
299     kbd_init();			/* nothing to erase */
300     buf[cpos] = EOS;		/* terminate it for us */
301 
302     beginDisplay();
303     if ((nptr = init_tags_cmpl(buf, cpos)) != NULL) {
304 	status = kbd_complete(PASS_DONE_ARGS, (const char *) nptr, sizeof(*nptr));
305 	free(TYPECAST(char *, nptr));
306     }
307     endofDisplay();
308     return status;
309 }
310 #else
311 #define tags_completion no_completion
312 #endif
313 
314 /*
315  * Record the places we've been to during a tag-search, so we'll not push the
316  * stack just because there's repetition in the tags files.  Return true if
317  * we've been here before.
318  */
319 static int
mark_tag_hit(LINE * tag,LINE * hit)320 mark_tag_hit(LINE *tag, LINE *hit)
321 {
322     TAGHITS *p;
323 
324     TRACE(("mark_tag_hit %s:%d\n", curbp->b_bname, line_no(curbp, hit)));
325     for (p = tag_hits; p != NULL; p = p->link) {
326 	if (p->hit == hit) {
327 	    TRACE(("... mark_tag_hit TRUE\n"));
328 	    return (p->tag == tag) ? ABORT : TRUE;
329 	}
330     }
331 
332     beginDisplay();
333     if ((p = typecalloc(TAGHITS)) != NULL) {
334 	p->link = tag_hits;
335 	p->tag = tag;
336 	p->hit = hit;
337 	tag_hits = p;
338     }
339     endofDisplay();
340 
341     TRACE(("... mark_tag_hit FALSE\n"));
342     return FALSE;
343 }
344 
345 /*
346  * Discard the list of tag-hits when we're about to begin a new tag-search.
347  */
348 static void
free_tag_hits(void)349 free_tag_hits(void)
350 {
351     TAGHITS *p;
352 
353     beginDisplay();
354     while ((p = tag_hits) != NULL) {
355 	tag_hits = p->link;
356 	free(p);
357     }
358     endofDisplay();
359 }
360 
361 static void
free_untag(UNTAG * utp)362 free_untag(UNTAG * utp)
363 {
364     if (utp != NULL) {
365 	beginDisplay();
366 	FreeIfNeeded(utp->u_fname);
367 #if OPT_SHOW_TAGS
368 	FreeIfNeeded(utp->u_templ);
369 #endif
370 	free(utp);
371 	endofDisplay();
372     }
373 }
374 
375 /* discard without returning anything */
376 static void
tossuntag(void)377 tossuntag(void)
378 {
379     UNTAG *utp;
380 
381     if (untaghead != NULL) {
382 	utp = untaghead;
383 	untaghead = utp->u_stklink;
384 	free_untag(utp);
385 	update_scratch(TAGSTACK_BufName, update_tagstack);
386     }
387 }
388 
389 /*ARGSUSED*/
390 static void
pushuntag(char * fname,L_NUM lineno,C_NUM colno,char * tag)391 pushuntag(char *fname, L_NUM lineno, C_NUM colno, char *tag)
392 {
393     UNTAG *utp;
394 
395     (void) tag;
396 
397     beginDisplay();
398     if ((utp = typecalloc(UNTAG)) != NULL) {
399 
400 	if ((utp->u_fname = strmalloc(fname)) == NULL
401 #if OPT_SHOW_TAGS
402 	    || (utp->u_templ = strmalloc(tag)) == NULL
403 #endif
404 	    ) {
405 	    free_untag(utp);
406 	    return;
407 	}
408 #if OPT_VMS_PATH
409 	strip_version(utp->u_fname);
410 #endif
411 
412 	utp->u_lineno = lineno;
413 	utp->u_colno = colno;
414 	utp->u_stklink = untaghead;
415 	untaghead = utp;
416 	update_scratch(TAGSTACK_BufName, update_tagstack);
417     }
418     endofDisplay();
419 }
420 
421 static int
popuntag(char * fname,L_NUM * linenop,C_NUM * colnop)422 popuntag(char *fname, L_NUM * linenop, C_NUM * colnop)
423 {
424     UNTAG *utp;
425 
426     if (untaghead) {
427 	utp = untaghead;
428 	untaghead = utp->u_stklink;
429 	(void) vl_strncpy(fname, utp->u_fname, NFILEN);
430 	*linenop = utp->u_lineno;
431 	*colnop = utp->u_colno;
432 	free_untag(utp);
433 	update_scratch(TAGSTACK_BufName, update_tagstack);
434 	return TRUE;
435     }
436     fname[0] = EOS;
437     *linenop = 0;
438     return FALSE;
439 }
440 
441 /*
442  * Returns TRUE if:
443  *
444  * a) pin-tagstack mode is enabled, and
445  * b) 2 or more visible windows on screen, and
446  * c) the current tag pop/push operation can be effected (pinned) in curwin.
447  */
448 static int
pinned_tagstack(char * fname)449 pinned_tagstack(char *fname /* target of tag/push op */ )
450 {
451     int pinned = FALSE;
452 
453     if (global_g_val(GMDPIN_TAGSTACK)) {
454 	BUFFER *bp;
455 	int nobufchg,		/* TRUE if a call to swbuffer_lfl() will not
456 				 * change/re-read current buffer.
457 				 */
458 	  wdwcnt;
459 	WINDOW *wp;
460 
461 	/* Don't pin tagstack if less than two visible windows on screen. */
462 	wdwcnt = 0;
463 	for_each_visible_window(wp)
464 	    wdwcnt++;
465 	if (wdwcnt > 1) {
466 	    /*
467 	     * Try to display the results of this tag pop in the current
468 	     * window....
469 	     *
470 	     * Got an existing buffer for this filename?
471 	     */
472 
473 	    if ((bp = find_b_file(fname)) != NULL) {
474 		/* yes, set the current window to this buffer. */
475 
476 		nobufchg = (curbp == bp &&
477 			    DOT.l &&
478 			    curwp &&
479 			    curwp->w_bufp == bp &&
480 			    bp->b_active);
481 
482 		if (swbuffer_lfl(bp, FALSE, TRUE) != FALSE) {
483 		    if (nobufchg) {
484 			/*
485 			 * in this case, DOT is changing to a new location in
486 			 * the same buffer.  if DOT isn't currently within the
487 			 * bounds of its window, updpos() will barf when
488 			 * update() is eventually invoked.  forestall this
489 			 * issue by forcing a window reframe.
490 			 */
491 
492 			curwp->w_flag |= WFFORCE;
493 		    }
494 		    pinned = TRUE;
495 		}
496 	    }
497 	}
498     }
499     return (pinned);
500 }
501 
502 /* Jump back to the given file, line#, and column#. */
503 static int
finish_pop(char * fname,L_NUM lineno,C_NUM colno)504 finish_pop(char *fname, L_NUM lineno, C_NUM colno)
505 {
506     MARK odot;
507     int s;
508 
509     if ((s = pinned_tagstack(fname)) == FALSE) {
510 	/* get a window open on the file */
511 
512 	s = getfile(fname, FALSE);
513     }
514     if (s == TRUE) {
515 	/* it's an absolute move -- remember where we are */
516 	odot = DOT;
517 	s = vl_gotoline(lineno);
518 	/* if we moved, update the "last dot" mark */
519 	if (s == TRUE) {
520 	    gocol(colno);
521 	    if (!sameline(DOT, odot))
522 		curwp->w_flag = (USHORT) (curwp->w_flag & ~WFMOVE);
523 	    else
524 		curwp->w_lastdot = odot;
525 	}
526     }
527     return s;
528 }
529 
530 #ifdef MDTAGIGNORECASE
531 typedef int (*CompareFunc) (const char *a, const char *b, size_t len);
532 
533 static int
my_strncasecmp(const char * a,const char * b,size_t len)534 my_strncasecmp(const char *a, const char *b, size_t len)
535 {
536     int aa = EOS, bb = EOS;
537 
538     while ((len != 0)
539 	   && ((aa = (isUpper(*a) ? toLower(*a) : *a)) != EOS)
540 	   && ((bb = (isUpper(*b) ? toLower(*b) : *b)) != EOS)
541 	   && (aa == bb)) {
542 	len--;
543 	a++;
544 	b++;
545     }
546 
547     return aa - bb;
548 }
549 #endif
550 
551 /*
552  * Do exact/inexact lookup of an anchored string in a buffer.
553  *	if taglen is 0, matches must be exact (i.e.  all
554  *	characters significant).  if the user enters less than 'taglen'
555  *	characters, this match must also be exact.  if the user enters
556  *	'taglen' or more characters, only that many characters will be
557  *	significant in the lookup.
558  */
559 static LINE *
cheap_tag_scan(LINE * oldlp,char * name,size_t taglen)560 cheap_tag_scan(LINE *oldlp, char *name, size_t taglen)
561 {
562     LINE *lp, *retlp;
563     size_t namelen = strlen(name);
564     int exact = (taglen == 0);
565     int added_tab;
566 #ifdef MDTAGIGNORECASE
567     CompareFunc compare = (b_val(curbp, MDTAGIGNORECASE)
568 			   ? my_strncasecmp
569 			   : strncmp);
570 #else
571 #define compare strncmp
572 #endif
573 
574     /* force a match of the tab delimiter if we're supposed to do
575        exact matches or if we're searching for something shorter
576        than the "restricted" length */
577     if (exact || namelen < taglen) {
578 	name[namelen++] = '\t';
579 	name[namelen] = EOS;
580 	added_tab = TRUE;
581     } else {
582 	added_tab = FALSE;
583     }
584 
585     retlp = NULL;
586     lp = lforw(oldlp);
587     while (lp != oldlp) {
588 	if (llength(lp) > (int) namelen) {
589 	    if (!compare(lvalue(lp), name, namelen)) {
590 		retlp = lp;
591 		break;
592 	    }
593 	}
594 	lp = lforw(lp);
595     }
596     if (added_tab)
597 	name[namelen - 1] = EOS;
598     return retlp;
599 }
600 
601 static LINE *
cheap_buffer_scan(BUFFER * bp,char * patrn,int dir)602 cheap_buffer_scan(BUFFER *bp, char *patrn, int dir)
603 {
604     LINE *lp;
605     LINE *result = NULL;
606     regexp *exp;
607     int ic = FALSE;
608 
609     if ((exp = regcomp(patrn, strlen(patrn), FALSE)) != NULL) {
610 #ifdef MDTAGIGNORECASE
611 	ic = b_val(bp, MDTAGIGNORECASE);
612 #endif
613 
614 	TRACE(("cheap_buffer_scan '%s' %s\n",
615 	       patrn,
616 	       dir == FORWARD ? "fwd" : "bak"));
617 
618 	for (lp = dir == FORWARD ? lforw(buf_head(bp)) : lback(buf_head(bp));
619 	     lp != buf_head(bp);
620 	     lp = dir == FORWARD ? lforw(lp) : lback(lp)) {
621 	    if (lregexec(exp, lp, 0, llength(lp), ic)) {
622 		result = lp;
623 		break;
624 	    }
625 	}
626 
627 	beginDisplay();
628 	free(TYPECAST(char, exp));
629 	endofDisplay();
630     }
631     return (result);
632 }
633 
634 static int
tag_search(char * tag,int taglen,int initial)635 tag_search(char *tag, int taglen, int initial)
636 {
637     /* variables for 'initial'=FALSE */
638     static int tf_num;
639     static char last_bname[NBUFN];
640 #ifdef MDCHK_MODTIME
641     static time_t last_modtime;
642 #endif
643 
644     static TBUFF *srchpat;
645 
646     LINE *lp;
647     size_t i;
648     int status;
649     char *tfp, *lplim;
650     char tfname[NFILEN];
651     int flag;
652     L_NUM lineno;
653     C_NUM colno;
654     int changedfile;
655     MARK odot;
656     BUFFER *tagbp;
657     int nomore;
658     int gotafile = FALSE;
659     int retried = FALSE;
660 
661     if (initial)
662 	tf_num = 0;
663 #ifdef MDCHK_MODTIME
664     else {
665 	if ((tagbp = find_b_name(last_bname)) == NULL
666 	    || last_modtime != tagbp->b_modtime) {
667 	    initial = TRUE;
668 	    tf_num = 0;
669 	}
670     }
671 #endif
672 
673     do {
674 	tagbp = gettagsfile(tf_num, &nomore, &flag);
675 	lp = NULL;
676 	if (nomore) {
677 	    if (gotafile) {
678 		if (initial || retried) {
679 		    mlwarn("[No such tag: \"%s\"]", tag);
680 		    return FALSE;
681 		}
682 		retried = TRUE;
683 		tf_num = 0;
684 		continue;
685 	    } else {
686 		mlforce("[No tags file available.]");
687 		return FALSE;
688 	    }
689 	}
690 
691 	if (tagbp) {
692 	    lp = cheap_tag_scan((initial || retried
693 				 ? buf_head(tagbp)
694 				 : tagbp->b_dot.l),
695 				tag, (size_t) taglen);
696 	    gotafile = TRUE;
697 	}
698 
699 	tf_num++;
700 
701     } while (lp == NULL);
702 
703     /* Save the position in the tags-file so "next-tag" will work */
704     tf_num--;
705     (void) vl_strncpy(last_bname, tagbp->b_bname, sizeof(last_bname));
706     tagbp->b_dot.l = lp;
707     tagbp->b_dot.o = 0;
708 #ifdef MDCHK_MODTIME
709     last_modtime = tagbp->b_modtime;
710 #endif
711 
712     /* Parse the line from the tags-file */
713     lplim = &lvalue(lp)[llength(lp)];
714     tfp = lvalue(lp);
715     while (tfp < lplim)
716 	if (*tfp++ == '\t')
717 	    break;
718     if (*tfp == '\t') {		/* then it's a new-fangled NeXT tags file */
719 	tfp++;			/* skip the tab */
720     }
721 
722     i = 0;
723     if (b_val(curbp, MDTAGSRELTIV) && !is_slashc(*tfp)
724 #if OPT_MSDOS_PATH
725 	&& !is_msdos_drive(tfp)
726 #endif
727 	) {
728 	char *first = tagbp->b_fname;
729 	char *lastsl = pathleaf(tagbp->b_fname);
730 	while (lastsl != first)
731 	    tfname[i++] = *first++;
732     }
733     while (i < (sizeof(tfname) - 2) && tfp < lplim && *tfp != '\t') {
734 	tfname[i++] = *tfp++;
735     }
736     tfname[i] = EOS;
737 
738     if (tfp >= lplim) {
739 	mlforce("[Bad line in tags file.]");
740 	return FALSE;
741     }
742 
743     if (curbp) {
744 	lineno = line_no(curbp, DOT.l);
745 	colno = getccol(FALSE);
746 	if (!isInternalName(curbp->b_fname))
747 	    pushuntag(curbp->b_fname, lineno, colno, tag);
748 	else
749 	    pushuntag(curbp->b_bname, lineno, colno, tag);
750     }
751 
752     changedfile = (curbp == NULL || !same_fname(tfname, curbp, TRUE));
753     if (changedfile)
754 	(void) doglob(tfname);
755     if (!pinned_tagstack(tfname)) {
756 	if (changedfile) {
757 	    status = getfile(tfname, TRUE);
758 	    if (status != TRUE) {
759 		tossuntag();
760 		return status;
761 	    }
762 	}
763     }
764 
765     /* it's an absolute move -- remember where we are */
766     odot = DOT;
767 
768     tfp++;			/* skip the tab */
769     if (tfp >= lplim) {
770 	mlforce("[Bad line in tags file.]");
771 	return FALSE;
772     }
773     if (isDigit(*tfp)) {	/* then it's a line number */
774 	lineno = 0;
775 	while (isDigit(*tfp) && (tfp < lplim)) {
776 	    lineno = 10 * lineno + *tfp - '0';
777 	    tfp++;
778 	}
779 	status = gotoline(TRUE, lineno);
780 	if (status != TRUE && !changedfile)
781 	    tossuntag();
782     } else {
783 	int delim = *tfp;
784 	int quoted = FALSE;
785 	char *p;
786 	int dir;
787 
788 	if (delim == '?') {
789 	    dir = REVERSE;
790 	} else {
791 	    dir = FORWARD;
792 	}
793 	p = ++tfp;		/* skip the "/" */
794 
795 	/* we're on the '/', so look for the matching one */
796 	while (p < lplim) {
797 	    if (quoted) {
798 		quoted = FALSE;
799 	    } else if (*p == BACKSLASH) {
800 		quoted = TRUE;
801 	    } else if (*p == delim) {
802 		break;
803 	    }
804 	    p++;
805 	}
806 	if (p >= lplim) {
807 	    mlforce("[Bad pattern in tags file.]");
808 	    return FALSE;
809 	}
810 
811 	if ((srchpat = tb_init(&srchpat, EOS)) == NULL
812 	    || (srchpat = tb_bappend(&srchpat, tfp, (size_t) (p - tfp))) == NULL
813 	    || (srchpat = tb_append(&srchpat, EOS)) == NULL)
814 	    return no_memory("tags");
815 
816 	lp = cheap_buffer_scan(curbp, tb_values(srchpat), dir);
817 	if (lp == NULL) {
818 	    mlwarn("[Tag not present]");
819 	    if (!changedfile)
820 		tossuntag();
821 	    return FALSE;
822 	}
823 	DOT.l = lp;
824 	curwp->w_flag |= WFMOVE;
825 	(void) firstnonwhite(FALSE, 1);
826 	status = TRUE;
827     }
828 
829     if (status == TRUE) {
830 	int s;
831 	if ((s = mark_tag_hit(tagbp->b_dot.l, DOT.l)) != FALSE) {
832 	    if (popuntag(tfname, &lineno, &colno)) {
833 		(void) finish_pop(tfname, lineno, colno);
834 	    }
835 	    return s;
836 	}
837 
838 	/*
839 	 * If we've succeeded on a next-tags, adjust the stack so that
840 	 * it's all on the same level.  A tag-pop will return to the
841 	 * original position.
842 	 */
843 	if (!initial
844 	    && untaghead != NULL
845 	    && untaghead->u_stklink != 0) {
846 	    UNTAG *p;
847 	    p = untaghead;
848 	    untaghead = p->u_stklink;
849 	    free_untag(p);
850 	}
851 
852 	if (!changedfile)
853 	    mlwrite("Tag \"%s\" in current buffer", tag);
854 
855 	/* if we moved, update the "last dot" mark */
856 	if (!sameline(DOT, odot)) {
857 	    curwp->w_lastdot = odot;
858 	}
859     }
860 
861     return status;
862 }
863 
864 KBD_OPTIONS
tags_kbd_options(void)865 tags_kbd_options(void)
866 {
867     KBD_OPTIONS mode = KBD_NORMAL
868 #if OPT_TAGS_CMPL
869     | KBD_MAYBEC
870 #endif
871      ;
872 #ifdef MDTAGIGNORECASE
873     if (b_val(curbp, MDTAGIGNORECASE))
874 	mode |= KBD_LOWERC;
875 #endif
876     return mode;
877 }
878 
879 /* ARGSUSED */
880 int
gototag(int f GCC_UNUSED,int n GCC_UNUSED)881 gototag(int f GCC_UNUSED, int n GCC_UNUSED)
882 {
883     int s;
884     int taglen;
885 
886     if (clexec || isnamedcmd) {
887 	if ((s = kbd_string("Tag name: ",
888 			    tagname, sizeof(tagname),
889 			    '\n', tags_kbd_options(), tags_completion)) != TRUE)
890 	    return (s);
891 	taglen = b_val(curbp, VAL_TAGLEN);
892     } else {
893 	s = screen_to_ident(tagname, sizeof(tagname));
894 	taglen = 0;
895     }
896 
897     if (s == TRUE) {
898 #ifdef MDTAGIGNORECASE
899 	if (b_val(curbp, MDTAGIGNORECASE))
900 	    mklower(tagname);
901 #endif
902 	free_tag_hits();
903 	s = tag_search(tagname, taglen, TRUE);
904     } else
905 	tagname[0] = EOS;
906     return s;
907 }
908 
909 /*
910  * Continue a tag-search by looking for other references in the tags file.
911  */
912 /*ARGSUSED*/
913 int
nexttag(int f GCC_UNUSED,int n GCC_UNUSED)914 nexttag(int f GCC_UNUSED, int n GCC_UNUSED)
915 {
916     int s = FALSE;
917 
918     if (tagname[0] != EOS) {
919 	do {
920 	    s = tag_search(tagname, global_b_val(VAL_TAGLEN), FALSE);
921 	} while (s == SORTOFTRUE);
922 	if (s == ABORT)
923 	    mlwarn("[No more matches]");
924     }
925     return s;
926 }
927 
928 int
untagpop(int f,int n)929 untagpop(int f, int n)
930 {
931     L_NUM lineno = 0;
932     C_NUM colno = 0;
933     char fname[NFILEN];
934     int s;
935 
936     n = need_a_count(f, n, 1);
937 
938     while (n && popuntag(fname, &lineno, &colno))
939 	n--;
940     if (lineno && fname[0]) {
941 	s = finish_pop(fname, lineno, colno);
942     } else {
943 	mlwarn("[No stacked un-tags]");
944 	s = FALSE;
945     }
946     return s;
947 }
948 
949 #if OPT_SHOW_TAGS
950 /*ARGSUSED*/
951 static void
maketagslist(int value GCC_UNUSED,void * dummy GCC_UNUSED)952 maketagslist(int value GCC_UNUSED, void *dummy GCC_UNUSED)
953 {
954     UNTAG *utp;
955     int n;
956     int taglen = global_b_val(VAL_TAGLEN);
957     char temp[NFILEN];
958 
959     if (taglen == 0) {
960 	for (utp = untaghead; utp != NULL; utp = utp->u_stklink) {
961 	    n = (int) strlen(utp->u_templ);
962 	    if (n > taglen)
963 		taglen = n;
964 	}
965     }
966     if (taglen < 10)
967 	taglen = 10;
968 
969     bprintf("    %*s FROM line in file\n", taglen, "TO tag");
970     bprintf("    ");
971     bpadc('-', taglen);
972     bprintf(" --------- ");
973     bpadc('-', 30);
974 
975     for (utp = untaghead, n = 0; utp != NULL; utp = utp->u_stklink)
976 	bprintf("\n %2d %*s %8d  %s",
977 		++n,
978 		taglen, utp->u_templ,
979 		utp->u_lineno,
980 		shorten_path(vl_strncpy(temp, utp->u_fname, sizeof(temp)),
981 			     TRUE));
982 }
983 
984 #if OPT_UPBUFF
985 /* ARGSUSED */
986 static int
update_tagstack(BUFFER * bp GCC_UNUSED)987 update_tagstack(BUFFER *bp GCC_UNUSED)
988 {
989     return showtagstack(FALSE, 1);
990 }
991 #endif
992 
993 /*
994  * Display the contents of the tag-stack
995  */
996 /*ARGSUSED*/
997 int
showtagstack(int f,int n GCC_UNUSED)998 showtagstack(int f, int n GCC_UNUSED)
999 {
1000     return liststuff(TAGSTACK_BufName, FALSE, maketagslist, f, (void *) 0);
1001 }
1002 #endif /* OPT_SHOW_TAGS */
1003 
1004 #if NO_LEAKS
1005 void
tags_leaks(void)1006 tags_leaks(void)
1007 {
1008     L_NUM lineno;
1009     C_NUM colno;
1010     char fname[NFILEN];
1011 
1012     free_tag_hits();
1013     while (popuntag(fname, &lineno, &colno)) ;
1014 #if OPT_TAGS_CMPL
1015     btree_freeup(&tags_tree);
1016 #endif
1017 }
1018 #endif
1019 
1020 #endif /* OPT_TAGS */
1021