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