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