Oh GACK! src-clean doesn't quite work that easily since cleandist rebuilds the
[unix-history] / usr.bin / vi / ex / ex_tag.c.orig
CommitLineData
395c4480
AM
1/*-
2 * Copyright (c) 1992, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * David Hitz of Auspex Systems, Inc.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by the University of
19 * California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#ifndef lint
38static char sccsid[] = "@(#)ex_tag.c 8.40 (Berkeley) 3/22/94";
39#endif /* not lint */
40
41#include <sys/param.h>
42#include <sys/mman.h>
43#include <queue.h>
44#include <sys/stat.h>
45#include <sys/time.h>
46
47#include <bitstring.h>
48#include <ctype.h>
49#include <errno.h>
50#include <fcntl.h>
51#include <limits.h>
52#include <signal.h>
53#include <stddef.h>
54#include <stdio.h>
55#include <stdlib.h>
56#include <string.h>
57#include <termios.h>
58#include <unistd.h>
59
60#include <db.h>
61#include <regex.h>
62
63#include "vi.h"
64#include "excmd.h"
65#include "tag.h"
66
67static char *binary_search __P((char *, char *, char *));
68static int compare __P((char *, char *, char *));
69static char *linear_search __P((char *, char *, char *));
70static int search __P((SCR *, char *, char *, char **));
71static int tag_get __P((SCR *, char *, char **, char **, char **));
72
73/*
74 * ex_tagfirst --
75 * The tag code can be entered from main, i.e. "vi -t tag".
76 */
77int
78ex_tagfirst(sp, tagarg)
79 SCR *sp;
80 char *tagarg;
81{
82 FREF *frp;
83 MARK m;
84 long tl;
85 u_int flags;
86 int sval;
87 char *p, *tag, *name, *search;
88
89 /* Taglength may limit the number of characters. */
90 if ((tl = O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(tagarg) > tl)
91 tagarg[tl] = '\0';
92
93 /* Get the tag information. */
94 if (tag_get(sp, tagarg, &tag, &name, &search))
95 return (1);
96
97 /* Create the file entry. */
98 if ((frp = file_add(sp, NULL, name, 0)) == NULL)
99 return (1);
100 if (file_init(sp, frp, NULL, 0))
101 return (1);
102
103 /*
104 * !!!
105 * The historic tags file format (from a long, long time ago...)
106 * used a line number, not a search string. I got complaints, so
107 * people are still using the format.
108 */
109 if (isdigit(search[0])) {
110 m.lno = atoi(search);
111 m.cno = 0;
112 } else {
113 /*
114 * Search for the tag; cheap fallback for C functions if
115 * the name is the same but the arguments have changed.
116 */
117 m.lno = 1;
118 m.cno = 0;
119 flags = SEARCH_FILE | SEARCH_TAG | SEARCH_TERM;
120 sval = f_search(sp, sp->ep, &m, &m, search, NULL, &flags);
121 if (sval && (p = strrchr(search, '(')) != NULL) {
122 p[1] = '\0';
123 sval = f_search(sp, sp->ep,
124 &m, &m, search, NULL, &flags);
125 }
126 if (sval)
127 msgq(sp, M_ERR, "%s: search pattern not found.", tag);
128 }
129
130 /* Set up the screen. */
131 frp->lno = m.lno;
132 frp->cno = m.cno;
133 F_SET(frp, FR_CURSORSET);
134
135 /* Might as well make this the default tag. */
136 if ((EXP(sp)->tlast = strdup(tagarg)) == NULL) {
137 msgq(sp, M_SYSERR, NULL);
138 return (1);
139 }
140 return (0);
141}
142
143/* Free a tag or tagf structure from a queue. */
144#define FREETAG(tp) { \
145 TAILQ_REMOVE(&exp->tagq, (tp), q); \
146 if ((tp)->search != NULL) \
147 free((tp)->search); \
148 FREE((tp), sizeof(TAGF)); \
149}
150#define FREETAGF(tfp) { \
151 TAILQ_REMOVE(&exp->tagfq, (tfp), q); \
152 free((tfp)->name); \
153 FREE((tfp), sizeof(TAGF)); \
154}
155
156/*
157 * ex_tagpush -- :tag [file]
158 * Move to a new tag.
159 *
160 * The tags stacks in nvi are a bit tricky. Each tag contains a file name,
161 * search string, and line/column numbers. The search string is only used
162 * for the first access and for user display. The first record on the stack
163 * is the place where we first did a tag, so it has no search string. The
164 * second record is the first tag, and so on. Note, this means that the
165 * "current" tag is always on the stack. Each tag has a line/column which is
166 * the location from which the user tagged the following TAG entry, and which
167 * is used as the return location.
168 */
169int
170ex_tagpush(sp, ep, cmdp)
171 SCR *sp;
172 EXF *ep;
173 EXCMDARG *cmdp;
174{
175 enum {TC_CHANGE, TC_CURRENT} which;
176 EX_PRIVATE *exp;
177 FREF *frp;
178 MARK m;
179 TAG *tp;
180 u_int flags;
181 int sval;
182 long tl;
183 char *name, *p, *search, *tag;
184
185 exp = EXP(sp);
186 switch (cmdp->argc) {
187 case 1:
188 if (exp->tlast != NULL)
189 FREE(exp->tlast, strlen(exp->tlast) + 1);
190 if ((exp->tlast = strdup(cmdp->argv[0]->bp)) == NULL) {
191 msgq(sp, M_SYSERR, NULL);
192 return (1);
193 }
194 break;
195 case 0:
196 if (exp->tlast == NULL) {
197 msgq(sp, M_ERR, "No previous tag entered.");
198 return (1);
199 }
200 break;
201 default:
202 abort();
203 }
204
205 /* Taglength may limit the number of characters. */
206 if ((tl = O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(exp->tlast) > tl)
207 exp->tlast[tl] = '\0';
208
209 /* Get the tag information. */
210 if (tag_get(sp, exp->tlast, &tag, &name, &search))
211 return (1);
212
213 /* Get the (possibly new) FREF structure. */
214 if ((frp = file_add(sp, sp->frp, name, 1)) == NULL)
215 goto modify_err;
216
217 if (sp->frp == frp)
218 which = TC_CURRENT;
219 else {
220 MODIFY_GOTO(sp, sp->ep, F_ISSET(cmdp, E_FORCE));
221 which = TC_CHANGE;
222 }
223
224 /*
225 * Get a tag structure -- if this is the first tag, push it on the
226 * stack as a placeholder and get another tag structure. Set the
227 * line/column of the most recent element on the stack to be the
228 * current values, including the file pointer. Then push the new
229 * TAG onto the stack with the new file and search string for user
230 * display.
231 */
232 CALLOC(sp, tp, TAG *, 1, sizeof(TAG));
233 if (tp != NULL && exp->tagq.tqh_first == NULL) {
234 TAILQ_INSERT_HEAD(&exp->tagq, tp, q);
235 CALLOC(sp, tp, TAG *, 1, sizeof(TAG));
236 }
237 if (exp->tagq.tqh_first != NULL) {
238 exp->tagq.tqh_first->frp = sp->frp;
239 exp->tagq.tqh_first->lno = sp->lno;
240 exp->tagq.tqh_first->cno = sp->cno;
241 }
242 if (tp != NULL) {
243 if ((tp->search = strdup(search)) == NULL)
244 msgq(sp, M_SYSERR, NULL);
245 else
246 tp->slen = strlen(search);
247 tp->frp = frp;
248 TAILQ_INSERT_HEAD(&exp->tagq, tp, q);
249 }
250
251 /* Switch files. */
252 if (which == TC_CHANGE && file_init(sp, frp, NULL, 0)) {
253 if (tp != NULL)
254 FREETAG(tp);
255 /* Handle special, first-tag case. */
256 if (exp->tagq.tqh_first->q.tqe_next == NULL)
257 TAILQ_REMOVE(&exp->tagq, exp->tagq.tqh_first, q);
258modify_err: free(tag);
259 return (1);
260 }
261
262 /*
263 * !!!
264 * Historic vi accepted a line number as well as a search
265 * string, and people are apparently still using the format.
266 */
267 if (isdigit(search[0])) {
268 m.lno = atoi(search);
269 m.cno = 0;
270 sval = 0;
271 } else {
272 /*
273 * Search for the tag; cheap fallback for C functions
274 * if the name is the same but the arguments have changed.
275 */
276 m.lno = 1;
277 m.cno = 0;
278 flags = SEARCH_FILE | SEARCH_TAG | SEARCH_TERM;
279 sval = f_search(sp, sp->ep, &m, &m, search, NULL, &flags);
280 if (sval && (p = strrchr(search, '(')) != NULL) {
281 p[1] = '\0';
282 sval = f_search(sp, sp->ep,
283 &m, &m, search, NULL, &flags);
284 p[1] = '(';
285 }
286 if (sval)
287 msgq(sp, M_ERR, "%s: search pattern not found.", tag);
288 }
289 free(tag);
290
291 switch (which) {
292 case TC_CHANGE:
293 frp->lno = m.lno;
294 frp->cno = m.cno;
295 F_SET(frp, FR_CURSORSET);
296 F_SET(sp, S_FSWITCH);
297 break;
298 case TC_CURRENT:
299 if (sval)
300 return (1);
301 sp->lno = m.lno;
302 sp->cno = m.cno;
303 break;
304 }
305 return (0);
306}
307
308/*
309 * ex_tagpop -- :tagp[op][!] [number | file]
310 * Pop the tag stack.
311 */
312int
313ex_tagpop(sp, ep, cmdp)
314 SCR *sp;
315 EXF *ep;
316 EXCMDARG *cmdp;
317{
318 EX_PRIVATE *exp;
319 TAG *ntp, *tp;
320 long off;
321 size_t arglen;
322 char *arg, *p, *t;
323
324 /* Check for an empty stack. */
325 exp = EXP(sp);
326 if (exp->tagq.tqh_first == NULL) {
327 msgq(sp, M_INFO, "The tags stack is empty.");
328 return (1);
329 }
330
331 switch (cmdp->argc) {
332 case 0: /* Pop one tag. */
333 ntp = exp->tagq.tqh_first;
334 break;
335 case 1: /* Name or number. */
336 arg = cmdp->argv[0]->bp;
337 off = strtol(arg, &p, 10);
338 if (*p == '\0') {
339 if (off < 1)
340 return (0);
341 for (tp = exp->tagq.tqh_first;
342 tp != NULL && --off > 1; tp = tp->q.tqe_next);
343 if (tp == NULL) {
344 msgq(sp, M_ERR,
345"Less than %s entries on the tags stack; use :display to see the tags stack.",
346 arg);
347 return (1);
348 }
349 ntp = tp;
350 } else {
351 arglen = strlen(arg);
352 for (tp = exp->tagq.tqh_first;
353 tp != NULL; ntp = tp, tp = tp->q.tqe_next) {
354 /* Use the user's original file name. */
355 p = tp->frp->name;
356 if ((t = strrchr(p, '/')) == NULL)
357 t = p;
358 else
359 ++t;
360 if (!strncmp(arg, t, arglen))
361 break;
362 }
363 if (tp == NULL) {
364 msgq(sp, M_ERR,
365"No file named %s on the tags stack; use :display to see the tags stack.",
366 arg);
367 return (1);
368 }
369 }
370 break;
371 default:
372 abort();
373 }
374
375 /* Update the cursor from the saved TAG information. */
376 tp = ntp->q.tqe_next;
377 if (tp->frp == sp->frp) {
378 sp->lno = tp->lno;
379 sp->cno = tp->cno;
380 } else {
381 MODIFY_RET(sp, ep, F_ISSET(cmdp, E_FORCE));
382 if (file_init(sp, tp->frp, NULL, 0))
383 return (1);
384
385 tp->frp->lno = tp->lno;
386 tp->frp->cno = tp->cno;
387 F_SET(sp->frp, FR_CURSORSET);
388
389 F_SET(sp, S_FSWITCH);
390 }
391
392 /* Pop entries off the queue up to ntp. */
393 for (;;) {
394 tp = exp->tagq.tqh_first;
395 FREETAG(tp);
396 if (tp == ntp)
397 break;
398 }
399
400 /* If returning to the first tag, the stack is now empty. */
401 if (exp->tagq.tqh_first->q.tqe_next == NULL)
402 TAILQ_REMOVE(&exp->tagq, exp->tagq.tqh_first, q);
403 return (0);
404}
405
406/*
407 * ex_tagtop -- :tagt[op][!]
408 * Clear the tag stack.
409 */
410int
411ex_tagtop(sp, ep, cmdp)
412 SCR *sp;
413 EXF *ep;
414 EXCMDARG *cmdp;
415{
416 EX_PRIVATE *exp;
417 TAG *tp;
418
419 /* Find oldest saved information. */
420 exp = EXP(sp);
421 for (tp = exp->tagq.tqh_first;
422 tp != NULL && tp->q.tqe_next != NULL; tp = tp->q.tqe_next);
423 if (tp == NULL) {
424 msgq(sp, M_INFO, "The tags stack is empty.");
425 return (1);
426 }
427
428 /* If not switching files, it's easy; else do the work. */
429 if (tp->frp == sp->frp) {
430 sp->lno = tp->lno;
431 sp->cno = tp->cno;
432 } else {
433 MODIFY_RET(sp, sp->ep, F_ISSET(cmdp, E_FORCE));
434 if (file_init(sp, tp->frp, NULL, 0))
435 return (1);
436
437 tp->frp->lno = tp->lno;
438 tp->frp->cno = tp->cno;
439
440 F_SET(sp->frp, FR_CURSORSET);
441 F_SET(sp, S_FSWITCH);
442 }
443
444 /* Empty out the queue. */
445 while ((tp = exp->tagq.tqh_first) != NULL)
446 FREETAG(tp);
447 return (0);
448}
449
450/*
451 * ex_tagdisplay --
452 * Display the list of tags.
453 */
454int
455ex_tagdisplay(sp, ep)
456 SCR *sp;
457 EXF *ep;
458{
459 EX_PRIVATE *exp;
460 TAG *tp;
461 size_t len, maxlen;
462 int cnt;
463 char *name;
464
465 exp = EXP(sp);
466 if ((tp = exp->tagq.tqh_first) == NULL) {
467 (void)ex_printf(EXCOOKIE, "No tags to display.\n");
468 return (0);
469 }
470
471 /*
472 * Figure out the formatting. MNOC is the maximum
473 * number of file name columns before we split the line.
474 */
475#define MNOC 15
476 for (maxlen = 0,
477 tp = exp->tagq.tqh_first; tp != NULL; tp = tp->q.tqe_next) {
478 len = strlen(name = tp->frp->name); /* The original name. */
479 if (maxlen < len && len < MNOC)
480 maxlen = len;
481 }
482
483 for (cnt = 1, tp = exp->tagq.tqh_first; tp != NULL;
484 ++cnt, tp = tp->q.tqe_next) {
485 len = strlen(name = tp->frp->name); /* The original name. */
486 if (len > maxlen || len + tp->slen > sp->cols)
487 if (tp == NULL || tp->search == NULL)
488 (void)ex_printf(EXCOOKIE,
489 "%2d %s\n", cnt, name);
490 else
491 (void)ex_printf(EXCOOKIE,
492 "%2d %s\n** %*.*s %s\n", cnt, name,
493 (int)maxlen, (int)maxlen, "", tp->search);
494 else
495 if (tp == NULL || tp->search == NULL)
496 (void)ex_printf(EXCOOKIE, "%2d %*.*s\n",
497 cnt, (int)maxlen, (int)len, name);
498 else
499 (void)ex_printf(EXCOOKIE, "%2d %*.*s %s\n",
500 cnt, (int)maxlen, (int)len, name,
501 tp->search);
502 }
503 return (0);
504}
505
506/*
507 * ex_tagalloc --
508 * Create a new list of tag files.
509 */
510int
511ex_tagalloc(sp, str)
512 SCR *sp;
513 char *str;
514{
515 EX_PRIVATE *exp;
516 TAGF *tp;
517 size_t len;
518 char *p, *t;
519
520 /* Free current queue. */
521 exp = EXP(sp);
522 while ((tp = exp->tagfq.tqh_first) != NULL)
523 FREETAGF(tp);
524
525 /* Create new queue. */
526 for (p = t = str;; ++p) {
527 if (*p == '\0' || isblank(*p)) {
528 if ((len = p - t) > 1) {
529 MALLOC_RET(sp, tp, TAGF *, sizeof(TAGF));
530 MALLOC(sp, tp->name, char *, len + 1);
531 if (tp->name == NULL) {
532 FREE(tp, sizeof(TAGF));
533 return (1);
534 }
535 memmove(tp->name, t, len);
536 tp->name[len] = '\0';
537 tp->flags = 0;
538 TAILQ_INSERT_TAIL(&exp->tagfq, tp, q);
539 }
540 t = p + 1;
541 }
542 if (*p == '\0')
543 break;
544 }
545 return (0);
546}
547 /* Free previous queue. */
548/*
549 * ex_tagfree --
550 * Free the tags file list.
551 */
552int
553ex_tagfree(sp)
554 SCR *sp;
555{
556 EX_PRIVATE *exp;
557 TAG *tp;
558 TAGF *tfp;
559
560 /* Free up tag information. */
561 exp = EXP(sp);
562 while ((tp = exp->tagq.tqh_first) != NULL)
563 FREETAG(tp);
564 while ((tfp = exp->tagfq.tqh_first) != NULL)
565 FREETAGF(tfp);
566 if (exp->tlast != NULL)
567 free(exp->tlast);
568 return (0);
569}
570
571/*
572 * ex_tagcopy --
573 * Copy a screen's tag structures.
574 */
575int
576ex_tagcopy(orig, sp)
577 SCR *orig, *sp;
578{
579 EX_PRIVATE *oexp, *nexp;
580 TAG *ap, *tp;
581 TAGF *atfp, *tfp;
582
583 /* Copy tag stack. */
584 oexp = EXP(orig);
585 nexp = EXP(sp);
586 for (ap = oexp->tagq.tqh_first; ap != NULL; ap = ap->q.tqe_next) {
587 MALLOC(sp, tp, TAG *, sizeof(TAG));
588 if (tp == NULL)
589 goto nomem;
590 *tp = *ap;
591 if (ap->search != NULL &&
592 (tp->search = strdup(ap->search)) == NULL)
593 goto nomem;
594 TAILQ_INSERT_TAIL(&nexp->tagq, tp, q);
595 }
596
597 /* Copy list of tag files. */
598 for (atfp = oexp->tagfq.tqh_first;
599 atfp != NULL; atfp = atfp->q.tqe_next) {
600 MALLOC(sp, tfp, TAGF *, sizeof(TAGF));
601 if (tfp == NULL)
602 goto nomem;
603 *tfp = *atfp;
604 if ((tfp->name = strdup(atfp->name)) == NULL)
605 goto nomem;
606 TAILQ_INSERT_TAIL(&nexp->tagfq, tfp, q);
607 }
608
609 /* Copy the last tag. */
610 if (oexp->tlast != NULL &&
611 (nexp->tlast = strdup(oexp->tlast)) == NULL) {
612nomem: msgq(sp, M_SYSERR, NULL);
613 return (1);
614 }
615 return (0);
616}
617
618/*
619 * tag_get --
620 * Get a tag from the tags files.
621 */
622static int
623tag_get(sp, tag, tagp, filep, searchp)
624 SCR *sp;
625 char *tag, **tagp, **filep, **searchp;
626{
627 struct stat sb;
628 EX_PRIVATE *exp;
629 TAGF *tfp;
630 size_t plen, slen, tlen;
631 int dne;
632 char *p, pbuf[MAXPATHLEN];
633
634 /*
635 * Find the tag, only display missing file messages once, and
636 * then only if we didn't find the tag.
637 */
638 dne = 0;
639 exp = EXP(sp);
640 for (p = NULL, tfp = exp->tagfq.tqh_first;
641 tfp != NULL && p == NULL; tfp = tfp->q.tqe_next) {
642 errno = 0;
643 F_CLR(tfp, TAGF_DNE);
644 if (search(sp, tfp->name, tag, &p))
645 if (errno == ENOENT) {
646 if (!F_ISSET(tfp, TAGF_DNE_WARN)) {
647 dne = 1;
648 F_SET(tfp, TAGF_DNE);
649 }
650 } else
651 msgq(sp, M_SYSERR, tfp->name);
652 else
653 if (p != NULL)
654 break;
655 }
656
657 if (p == NULL) {
658 msgq(sp, M_ERR, "%s: tag not found.", tag);
659 if (dne)
660 for (tfp = exp->tagfq.tqh_first;
661 tfp != NULL; tfp = tfp->q.tqe_next)
662 if (F_ISSET(tfp, TAGF_DNE)) {
663 errno = ENOENT;
664 msgq(sp, M_SYSERR, tfp->name);
665 F_SET(tfp, TAGF_DNE_WARN);
666 }
667 return (1);
668 }
669
670 /*
671 * Set the return pointers; tagp points to the tag, and, incidentally
672 * the allocated string, filep points to the file name, and searchp
673 * points to the search string. All three are nul-terminated.
674 */
675 for (*tagp = p; *p && !isblank(*p); ++p);
676 if (*p == '\0')
677 goto malformed;
678 for (*p++ = '\0'; isblank(*p); ++p);
679 for (*filep = p; *p && !isblank(*p); ++p);
680 if (*p == '\0')
681 goto malformed;
682 for (*p++ = '\0'; isblank(*p); ++p);
683 *searchp = p;
684 if (*p == '\0') {
685malformed: free(*tagp);
686 msgq(sp, M_ERR, "%s: corrupted tag in %s.", tag, tfp->name);
687 return (1);
688 }
689
690 /*
691 * !!!
692 * If the tag file path is a relative path, see if it exists. If it
693 * doesn't, look relative to the tags file path. It's okay for a tag
694 * file to not exist, and, historically, vi simply displayed a "new"
695 * file. However, if the path exists relative to the tag file, it's
696 * pretty clear what's happening, so we may as well do it right.
697 */
698 if ((*filep)[0] != '/'
699 && stat(*filep, &sb) && (p = strrchr(tfp->name, '/')) != NULL) {
700 *p = '\0';
701 plen = snprintf(pbuf, sizeof(pbuf), "%s/%s", tfp->name, *filep);
702 *p = '/';
703 if (stat(pbuf, &sb) == 0) {
704 slen = strlen(*searchp);
705 tlen = strlen(*tagp);
706 MALLOC(sp, p, char *, plen + slen + tlen + 5);
707 if (p != NULL) {
708 memmove(p, *tagp, tlen);
709 free(*tagp);
710 *tagp = p;
711 *(p += tlen) = '\0';
712 memmove(++p, pbuf, plen);
713 *filep = p;
714 *(p += plen) = '\0';
715 memmove(++p, *searchp, slen);
716 *searchp = p;
717 *(p += slen) = '\0';
718 }
719 }
720 }
721 return (0);
722}
723
724#define EQUAL 0
725#define GREATER 1
726#define LESS (-1)
727
728/*
729 * search --
730 * Search a file for a tag.
731 */
732static int
733search(sp, name, tname, tag)
734 SCR *sp;
735 char *name, *tname, **tag;
736{
737 struct stat sb;
738 int fd, len;
739 char *endp, *back, *front, *map, *p;
740
741 if ((fd = open(name, O_RDONLY, 0)) < 0)
742 return (1);
743
744 /*
745 * XXX
746 * We'd like to test if the file is too big to mmap. Since we don't
747 * know what size or type off_t's or size_t's are, what the largest
748 * unsigned integral type is, or what random insanity the local C
749 * compiler will perpetrate, doing the comparison in a portable way
750 * is flatly impossible. Hope that malloc fails if the file is too
751 * large.
752 */
753 if (fstat(fd, &sb) || (map = mmap(NULL, (size_t)sb.st_size,
754 PROT_READ, MAP_PRIVATE, fd, (off_t)0)) == (caddr_t)-1) {
755 (void)close(fd);
756 return (1);
757 }
758 front = map;
759 back = front + sb.st_size;
760
761 front = binary_search(tname, front, back);
762 front = linear_search(tname, front, back);
763
764 if (front == NULL || (endp = strchr(front, '\n')) == NULL) {
765 *tag = NULL;
766 goto done;
767 }
768
769 len = endp - front;
770 MALLOC(sp, p, char *, len + 1);
771 if (p == NULL) {
772 *tag = NULL;
773 goto done;
774 }
775 memmove(p, front, len);
776 p[len] = '\0';
777 *tag = p;
778
779done: if (munmap(map, (size_t)sb.st_size))
780 msgq(sp, M_SYSERR, "munmap");
781 if (close(fd))
782 msgq(sp, M_SYSERR, "close");
783 return (0);
784}
785
786/*
787 * Binary search for "string" in memory between "front" and "back".
788 *
789 * This routine is expected to return a pointer to the start of a line at
790 * *or before* the first word matching "string". Relaxing the constraint
791 * this way simplifies the algorithm.
792 *
793 * Invariants:
794 * front points to the beginning of a line at or before the first
795 * matching string.
796 *
797 * back points to the beginning of a line at or after the first
798 * matching line.
799 *
800 * Base of the Invariants.
801 * front = NULL;
802 * back = EOF;
803 *
804 * Advancing the Invariants:
805 *
806 * p = first newline after halfway point from front to back.
807 *
808 * If the string at "p" is not greater than the string to match,
809 * p is the new front. Otherwise it is the new back.
810 *
811 * Termination:
812 *
813 * The definition of the routine allows it return at any point,
814 * since front is always at or before the line to print.
815 *
816 * In fact, it returns when the chosen "p" equals "back". This
817 * implies that there exists a string is least half as long as
818 * (back - front), which in turn implies that a linear search will
819 * be no more expensive than the cost of simply printing a string or two.
820 *
821 * Trying to continue with binary search at this point would be
822 * more trouble than it's worth.
823 */
824#define SKIP_PAST_NEWLINE(p, back) while (p < back && *p++ != '\n');
825
826static char *
827binary_search(string, front, back)
828 register char *string, *front, *back;
829{
830 register char *p;
831
832 p = front + (back - front) / 2;
833 SKIP_PAST_NEWLINE(p, back);
834
835 while (p != back) {
836 if (compare(string, p, back) == GREATER)
837 front = p;
838 else
839 back = p;
840 p = front + (back - front) / 2;
841 SKIP_PAST_NEWLINE(p, back);
842 }
843 return (front);
844}
845
846/*
847 * Find the first line that starts with string, linearly searching from front
848 * to back.
849 *
850 * Return NULL for no such line.
851 *
852 * This routine assumes:
853 *
854 * o front points at the first character in a line.
855 * o front is before or at the first line to be printed.
856 */
857static char *
858linear_search(string, front, back)
859 char *string, *front, *back;
860{
861 while (front < back) {
862 switch (compare(string, front, back)) {
863 case EQUAL: /* Found it. */
864 return (front);
865 case LESS: /* No such string. */
866 return (NULL);
867 case GREATER: /* Keep going. */
868 break;
869 }
870 SKIP_PAST_NEWLINE(front, back);
871 }
872 return (NULL);
873}
874
875/*
876 * Return LESS, GREATER, or EQUAL depending on how the string1 compares
877 * with string2 (s1 ??? s2).
878 *
879 * o Matches up to len(s1) are EQUAL.
880 * o Matches up to len(s2) are GREATER.
881 *
882 * The string "s1" is null terminated. The string s2 is '\t', space, (or
883 * "back") terminated.
884 *
885 * !!!
886 * Reasonably modern ctags programs use tabs as separators, not spaces.
887 * However, historic programs did use spaces, and, I got complaints.
888 */
889static int
890compare(s1, s2, back)
891 register char *s1, *s2, *back;
892{
893 for (; *s1 && s2 < back && (*s2 != '\t' && *s2 != ' '); ++s1, ++s2)
894 if (*s1 != *s2)
895 return (*s1 < *s2 ? LESS : GREATER);
896 return (*s1 ? GREATER : s2 < back &&
897 (*s2 != '\t' && *s2 != ' ') ? LESS : EQUAL);
898}