]> pd.if.org Git - pd_readline/blob - mg/tags.c
Added mg from an OpenBSD mirror site. Many thanks to the OpenBSD team and the mg...
[pd_readline] / mg / tags.c
1 /*      $OpenBSD: tags.c,v 1.5 2012/07/02 08:08:31 lum Exp $    */
2
3 /*
4  * This file is in the public domain.
5  *
6  * Author: Sunil Nimmagadda <sunil@sunilnimmagadda.com>
7  */
8
9 #include <sys/queue.h>
10 #include <sys/stat.h>
11 #include <sys/tree.h>
12 #include <sys/types.h>
13
14 #include <ctype.h>
15 #include <err.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <util.h>
19
20 #include "def.h"
21
22 struct ctag;
23
24 static int               addctag(char *);
25 static int               atbow(void);
26 void                     closetags(void);
27 static int               ctagcmp(struct ctag *, struct ctag *);
28 static int               loadbuffer(char *);
29 static int               loadtags(const char *);
30 static int               pushtag(char *);
31 static int               searchpat(char *);
32 static struct ctag       *searchtag(char *);
33 static char              *strip(char *, size_t);
34 static void              unloadtags(void);
35
36 #define DEFAULTFN "tags"
37
38 char *tagsfn = NULL;
39 int  loaded  = FALSE;
40
41 /* ctags(1) entries are parsed and maintained in a tree. */
42 struct ctag {
43         RB_ENTRY(ctag) entry;
44         char *tag;
45         char *fname;
46         char *pat;
47 };
48 RB_HEAD(tagtree, ctag) tags = RB_INITIALIZER(&tags);
49 RB_GENERATE(tagtree, ctag, entry, ctagcmp);
50
51 struct tagpos {
52         SLIST_ENTRY(tagpos) entry;
53         int    doto;
54         int    dotline;
55         char   *bname;
56 };
57 SLIST_HEAD(tagstack, tagpos) shead = SLIST_HEAD_INITIALIZER(shead);
58
59 int
60 ctagcmp(struct ctag *s, struct ctag *t)
61 {
62         return strcmp(s->tag, t->tag);
63 }
64
65 /*
66  * Record the filename that contain tags to be used while loading them
67  * on first use. If a filename is already recorded, ask user to retain
68  * already loaded tags (if any) and unload them if user chooses not to.
69  */
70 /* ARGSUSED */
71 int
72 tagsvisit(int f, int n)
73 {
74         char fname[NFILEN], *bufp, *temp;
75         struct stat sb;
76         
77         if (getbufcwd(fname, sizeof(fname)) == FALSE)
78                 fname[0] = '\0';
79         
80         if (strlcat(fname, DEFAULTFN, sizeof(fname)) >= sizeof(fname)) {
81                 ewprintf("Filename too long");
82                 return (FALSE);
83         }
84         
85         bufp = eread("visit tags table (default %s): ", fname,
86             NFILEN, EFFILE | EFCR | EFNEW | EFDEF, DEFAULTFN);
87
88         if (stat(bufp, &sb) == -1) {
89                 ewprintf("stat: %s", strerror(errno));
90                 return (FALSE);
91         } else if (S_ISREG(sb.st_mode) == 0) {
92                 ewprintf("Not a regular file");
93                 return (FALSE);
94         } else if (access(bufp, R_OK) == -1) {
95                 ewprintf("Cannot access file %s", bufp);
96                 return (FALSE);
97         }
98         
99         if (tagsfn == NULL) {
100                 if (bufp == NULL)
101                         return (ABORT);
102                 else if (bufp[0] == '\0') {
103                         if ((tagsfn = strdup(fname)) == NULL) {
104                                 ewprintf("Out of memory");
105                                 return (FALSE);
106                         }
107                 } else {
108                         /* bufp points to local variable, so duplicate. */
109                         if ((tagsfn = strdup(bufp)) == NULL) {
110                                 ewprintf("Out of memory");
111                                 return (FALSE);
112                         }
113                 }
114         } else {
115                 if ((temp = strdup(bufp)) == NULL) {
116                         ewprintf("Out of memory");
117                         return (FALSE);
118                 }
119                 free(tagsfn);
120                 tagsfn = temp;
121                 if (eyorn("Keep current list of tags table also") == FALSE) {
122                         ewprintf("Starting a new list of tags table");
123                         unloadtags();
124                 }
125                 loaded = FALSE;
126         }
127         return (TRUE);
128 }
129
130 /*
131  * Ask user for a tag while treating word at dot as default. Visit tags
132  * file if not yet done, load tags and jump to definition of the tag.
133  */
134 int
135 findtag(int f, int n)
136 {
137         char utok[MAX_TOKEN], dtok[MAX_TOKEN];
138         char *tok, *bufp;
139         int  ret;
140
141         if (curtoken(f, n, dtok) == FALSE)
142                 return (FALSE);
143         
144         bufp = eread("Find tag (default %s) ", utok, MAX_TOKEN,
145             EFNUL | EFNEW, dtok);
146
147         if (bufp == NULL)
148                 return (ABORT);
149         else if (bufp[0] == '\0')
150                 tok = dtok;
151         else
152                 tok = utok;
153         
154         if (tok[0] == '\0') {
155                 ewprintf("There is no default tag");
156                 return (FALSE);
157         }
158         
159         if (tagsfn == NULL)
160                 if ((ret = tagsvisit(f, n)) != TRUE)
161                         return (ret);
162         if (!loaded) {
163                 if (loadtags(tagsfn) == FALSE) {
164                         free(tagsfn);
165                         tagsfn = NULL;
166                         return (FALSE);
167                 }
168                 loaded = TRUE;
169         }
170         return pushtag(tok);
171 }
172
173 /*
174  * Free tags tree.
175  */
176 void
177 unloadtags(void)
178 {
179         struct ctag *var, *nxt;
180         
181         for (var = RB_MIN(tagtree, &tags); var != NULL; var = nxt) {
182                 nxt = RB_NEXT(tagtree, &tags, var);
183                 RB_REMOVE(tagtree, &tags, var);
184                 /* line parsed with fparseln needs to be freed */
185                 free(var->tag);
186                 free(var);
187         }
188 }
189
190 /*
191  * Lookup tag passed in tree and if found, push current location and 
192  * buffername onto stack, load the file with tag definition into a new
193  * buffer and position dot at the pattern.
194  */
195 /*ARGSUSED */
196 int
197 pushtag(char *tok)
198 {
199         struct ctag *res;
200         struct tagpos *s;
201         char bname[NFILEN];
202         int doto, dotline;
203         
204         if ((res = searchtag(tok)) == NULL)
205                 return (FALSE);
206                 
207         doto = curwp->w_doto;
208         dotline = curwp->w_dotline;
209         /* record absolute filenames. Fixes issues when mg's cwd is not the
210          * same as buffer's directory.
211          */
212         if (strlcpy(bname, curbp->b_cwd, sizeof(bname)) >= sizeof(bname)) {
213                     ewprintf("filename too long");
214                     return (FALSE);
215         }
216         if (strlcat(bname, curbp->b_bname, sizeof(bname)) >= sizeof(bname)) {
217                     ewprintf("filename too long");
218                     return (FALSE);
219         }       
220
221         if (loadbuffer(res->fname) == FALSE)
222                 return (FALSE);
223         
224         if (searchpat(res->pat) == TRUE) {
225                 if ((s = malloc(sizeof(struct tagpos))) == NULL) {
226                         ewprintf("Out of memory");
227                         return (FALSE);
228                 }
229                 if ((s->bname = strdup(bname)) == NULL) {
230                         ewprintf("Out of memory");
231                         return (FALSE);
232                 }
233                 s->doto = doto;
234                 s->dotline = dotline;
235                 SLIST_INSERT_HEAD(&shead, s, entry);
236                 return (TRUE);
237         } else {
238                 ewprintf("%s: pattern not found", res->tag);
239                 return (FALSE);
240         }
241         /* NOTREACHED */
242         return (FALSE);
243 }
244
245 /*
246  * If tag stack is not empty pop stack and jump to recorded buffer, dot.
247  */
248 /* ARGSUSED */
249 int
250 poptag(int f, int n)
251 {
252         struct line *dotp;
253         struct tagpos *s;
254         
255         if (SLIST_EMPTY(&shead)) {
256                 ewprintf("No previous location for find-tag invocation");
257                 return (FALSE);
258         }
259         s = SLIST_FIRST(&shead);
260         SLIST_REMOVE_HEAD(&shead, entry);
261         if (loadbuffer(s->bname) == FALSE)
262                 return (FALSE);
263         curwp->w_dotline = s->dotline;
264         curwp->w_doto = s->doto;
265         
266         /* storing of dotp in tagpos wouldn't work out in cases when
267          * that buffer is killed by user(dangling pointer). Explicitly
268          * traverse till dotline for correct handling. 
269          */
270         dotp = curwp->w_bufp->b_headp;
271         while (s->dotline--)
272                 dotp = dotp->l_fp;
273         
274         curwp->w_dotp = dotp;
275         free(s->bname);
276         free(s);
277         return (TRUE);
278 }
279
280 /*
281  * Parse the tags file and construct the tags tree. Remove escape 
282  * characters while parsing the file.
283  */
284 int
285 loadtags(const char *fn)
286 {
287         char *l;
288         FILE *fd;
289         
290         if ((fd = fopen(fn, "r")) == NULL) {
291                 ewprintf("Unable to open tags file: %s", fn);
292                 return (FALSE);
293         }
294         while ((l = fparseln(fd, NULL, NULL, "\\\\\0",
295             FPARSELN_UNESCCONT | FPARSELN_UNESCREST)) != NULL) {
296                 if (addctag(l) == FALSE) {
297                         fclose(fd);
298                         return (FALSE);
299                 }
300         }
301         fclose(fd);
302         return (TRUE);
303 }
304
305 /*
306  * Cleanup and destroy tree and stack.
307  */
308 void
309 closetags(void)
310 {
311         struct tagpos *s;       
312         
313         while (!SLIST_EMPTY(&shead)) {
314                 s = SLIST_FIRST(&shead);
315                 SLIST_REMOVE_HEAD(&shead, entry);
316                 free(s->bname);
317                 free(s);
318         }
319         unloadtags();
320         free(tagsfn);
321 }
322
323 /*
324  * Strip away any special characters in pattern.
325  * The pattern in ctags isn't a true regular expression. Its of the form
326  * /^xxx$/ or ?^xxx$? and in some cases the "$" would be missing. Strip 
327  * the leading and trailing special characters so the pattern matching
328  * would be a simple string compare. Escape character is taken care by 
329  * fparseln.
330  */
331 char *
332 strip(char *s, size_t len)
333 {
334         /* first strip trailing special chars */        
335         s[len - 1] = '\0';
336         if (s[len - 2] == '$')
337                 s[len - 2] = '\0';
338         
339         /* then strip leading special chars */
340         s++;
341         if (*s == '^')
342                 s++;
343         
344         return s;
345 }
346
347 /*
348  * tags line is of the format "<tag>\t<filename>\t<pattern>". Split them
349  * by replacing '\t' with '\0'. This wouldn't alter the size of malloc'ed
350  * l, and can be freed during cleanup.
351  */
352 int
353 addctag(char *l)
354 {
355         struct ctag *t;
356         
357         if ((t = malloc(sizeof(struct ctag))) == NULL) {
358                 ewprintf("Out of memory");
359                 return (FALSE);
360         }
361         t->tag = l;
362         if ((l = strchr(l, '\t')) == NULL)
363                 goto cleanup;
364         *l++ = '\0';
365         t->fname = l;
366         if ((l = strchr(l, '\t')) == NULL)
367                 goto cleanup;
368         *l++ = '\0';
369         if (*l == '\0')
370                 goto cleanup;
371         t->pat = strip(l, strlen(l));
372         RB_INSERT(tagtree, &tags, t);
373         return (TRUE);
374 cleanup:
375         free(t);
376         free(l);
377         return (TRUE);
378 }
379
380 /*
381  * Search through each line of buffer for pattern.
382  */
383 int
384 searchpat(char *pat)
385 {
386         struct line *lp;
387         int dotline;
388         size_t plen;
389
390         plen = strlen(pat);
391         dotline = 1;
392         lp = lforw(curbp->b_headp);
393         while (lp != curbp->b_headp) {
394                 if (ltext(lp) != NULL && plen <= llength(lp) &&
395                     (strncmp(pat, ltext(lp), plen) == 0)) {
396                         curwp->w_doto = 0;
397                         curwp->w_dotp = lp;
398                         curwp->w_dotline = dotline;
399                         return (TRUE);
400                 } else {
401                         lp = lforw(lp);
402                         dotline++;
403                 }
404         }
405         return (FALSE);
406 }
407
408 /*
409  * Return TRUE if dot is at beginning of a word or at beginning 
410  * of line, else FALSE.
411  */
412 int
413 atbow(void)
414 {
415         if (curwp->w_doto == 0)
416                 return (TRUE);
417         if (ISWORD(curwp->w_dotp->l_text[curwp->w_doto]) &&
418             !ISWORD(curwp->w_dotp->l_text[curwp->w_doto - 1]))
419                 return (TRUE);
420         return (FALSE);
421 }
422
423 /*
424  * Extract the word at dot without changing dot position.
425  */
426 int
427 curtoken(int f, int n, char *token)
428 {
429         struct line *odotp;
430         int odoto, tdoto, odotline, size, r;
431         char c;
432         
433         /* Underscore character is to be treated as "inword" while
434          * processing tokens unlike mg's default word traversal. Save
435          * and restore it's cinfo value so that tag matching works for
436          * identifier with underscore.
437          */
438         c = cinfo['_'];
439         cinfo['_'] = _MG_W;
440         
441         odotp = curwp->w_dotp;
442         odoto = curwp->w_doto;
443         odotline = curwp->w_dotline;
444         
445         /* Move backword unless we are at the beginning of a word or at
446          * beginning of line.
447          */
448         if (!atbow())
449                 if ((r = backword(f, n)) == FALSE)
450                         goto cleanup;
451                 
452         tdoto = curwp->w_doto;
453
454         if ((r = forwword(f, n)) == FALSE)
455                 goto cleanup;
456         
457         /* strip away leading whitespace if any like emacs. */
458         while (ltext(curwp->w_dotp) && 
459             isspace(curwp->w_dotp->l_text[tdoto]))
460                 tdoto++;
461
462         size = curwp->w_doto - tdoto;
463         if (size <= 0 || size >= MAX_TOKEN || 
464             ltext(curwp->w_dotp) == NULL) {
465                 r = FALSE;
466                 goto cleanup;
467         }    
468         strncpy(token, ltext(curwp->w_dotp) + tdoto, size);
469         token[size] = '\0';
470         r = TRUE;
471         
472 cleanup:
473         cinfo['_'] = c;
474         curwp->w_dotp = odotp;
475         curwp->w_doto = odoto;
476         curwp->w_dotline = odotline;
477         return (r);
478 }
479
480 /*
481  * Search tagstree for a given token.
482  */
483 struct ctag *
484 searchtag(char *tok)
485 {
486         struct ctag t, *res;
487
488         t.tag = tok;
489         if ((res = RB_FIND(tagtree, &tags, &t)) == NULL) {
490                 ewprintf("No tag containing %s", tok);
491                 return (NULL);
492         }
493         return res;
494 }
495
496 /*
497  * This is equivalent to filevisit from file.c.
498  * Look around to see if we can find the file in another buffer; if we
499  * can't find it, create a new buffer, read in the text, and switch to
500  * the new buffer. *scratch*, *grep*, *compile* needs to be handled 
501  * differently from other buffers which have "filenames".
502  */
503 int
504 loadbuffer(char *bname)
505 {
506         struct buffer *bufp;
507         char *adjf;
508
509         /* check for special buffers which begin with '*' */
510         if (bname[0] == '*') {
511                 if ((bufp = bfind(bname, FALSE)) != NULL) {
512                         curbp = bufp;
513                         return (showbuffer(bufp, curwp, WFFULL));
514                 } else {
515                         return (FALSE);
516                 }
517         } else {        
518                 if ((adjf = adjustname(bname, TRUE)) == NULL)
519                         return (FALSE);
520                 if ((bufp = findbuffer(adjf)) == NULL)
521                         return (FALSE);
522         }
523         curbp = bufp;
524         if (showbuffer(bufp, curwp, WFFULL) != TRUE)
525                 return (FALSE);
526         if (bufp->b_fname[0] == '\0') {
527                 if (readin(adjf) != TRUE) {
528                         killbuffer(bufp);
529                         return (FALSE);
530                 }
531         }
532         return (TRUE);
533 }