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