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