Commit | Line | Data |
---|---|---|
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 | |
38 | static 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 | ||
67 | static char *binary_search __P((char *, char *, char *)); | |
68 | static int compare __P((char *, char *, char *)); | |
69 | static char *linear_search __P((char *, char *, char *)); | |
70 | static int search __P((SCR *, char *, char *, char **)); | |
71 | static 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 | */ | |
77 | int | |
78 | ex_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 | */ | |
169 | int | |
170 | ex_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); | |
258 | modify_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 | */ | |
312 | int | |
313 | ex_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 | */ | |
410 | int | |
411 | ex_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 | */ | |
454 | int | |
455 | ex_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 | */ | |
510 | int | |
511 | ex_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 | */ | |
552 | int | |
553 | ex_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 | */ | |
575 | int | |
576 | ex_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) { | |
612 | nomem: 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 | */ | |
622 | static int | |
623 | tag_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') { | |
685 | malformed: 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 | */ | |
732 | static int | |
733 | search(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 | ||
779 | done: 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 | ||
826 | static char * | |
827 | binary_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 | */ | |
857 | static char * | |
858 | linear_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 | */ | |
889 | static int | |
890 | compare(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 | } |