tags.c revision 1.6
1/* $OpenBSD: tags.c,v 1.6 2012/10/20 09:05:33 jasper 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 22struct ctag; 23 24static int addctag(char *); 25static int atbow(void); 26void closetags(void); 27static int ctagcmp(struct ctag *, struct ctag *); 28static int loadbuffer(char *); 29static int loadtags(const char *); 30static int pushtag(char *); 31static int searchpat(char *); 32static struct ctag *searchtag(char *); 33static char *strip(char *, size_t); 34static void unloadtags(void); 35 36#define DEFAULTFN "tags" 37 38char *tagsfn = NULL; 39int loaded = FALSE; 40 41/* ctags(1) entries are parsed and maintained in a tree. */ 42struct ctag { 43 RB_ENTRY(ctag) entry; 44 char *tag; 45 char *fname; 46 char *pat; 47}; 48RB_HEAD(tagtree, ctag) tags = RB_INITIALIZER(&tags); 49RB_GENERATE(tagtree, ctag, entry, ctagcmp); 50 51struct tagpos { 52 SLIST_ENTRY(tagpos) entry; 53 int doto; 54 int dotline; 55 char *bname; 56}; 57SLIST_HEAD(tagstack, tagpos) shead = SLIST_HEAD_INITIALIZER(shead); 58 59int 60ctagcmp(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 */ 71int 72tagsvisit(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 */ 134int 135findtag(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 */ 176void 177unloadtags(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 */ 196int 197pushtag(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 free(s); 232 return (FALSE); 233 } 234 s->doto = doto; 235 s->dotline = dotline; 236 SLIST_INSERT_HEAD(&shead, s, entry); 237 return (TRUE); 238 } else { 239 ewprintf("%s: pattern not found", res->tag); 240 return (FALSE); 241 } 242 /* NOTREACHED */ 243 return (FALSE); 244} 245 246/* 247 * If tag stack is not empty pop stack and jump to recorded buffer, dot. 248 */ 249/* ARGSUSED */ 250int 251poptag(int f, int n) 252{ 253 struct line *dotp; 254 struct tagpos *s; 255 256 if (SLIST_EMPTY(&shead)) { 257 ewprintf("No previous location for find-tag invocation"); 258 return (FALSE); 259 } 260 s = SLIST_FIRST(&shead); 261 SLIST_REMOVE_HEAD(&shead, entry); 262 if (loadbuffer(s->bname) == FALSE) 263 return (FALSE); 264 curwp->w_dotline = s->dotline; 265 curwp->w_doto = s->doto; 266 267 /* storing of dotp in tagpos wouldn't work out in cases when 268 * that buffer is killed by user(dangling pointer). Explicitly 269 * traverse till dotline for correct handling. 270 */ 271 dotp = curwp->w_bufp->b_headp; 272 while (s->dotline--) 273 dotp = dotp->l_fp; 274 275 curwp->w_dotp = dotp; 276 free(s->bname); 277 free(s); 278 return (TRUE); 279} 280 281/* 282 * Parse the tags file and construct the tags tree. Remove escape 283 * characters while parsing the file. 284 */ 285int 286loadtags(const char *fn) 287{ 288 char *l; 289 FILE *fd; 290 291 if ((fd = fopen(fn, "r")) == NULL) { 292 ewprintf("Unable to open tags file: %s", fn); 293 return (FALSE); 294 } 295 while ((l = fparseln(fd, NULL, NULL, "\\\\\0", 296 FPARSELN_UNESCCONT | FPARSELN_UNESCREST)) != NULL) { 297 if (addctag(l) == FALSE) { 298 fclose(fd); 299 return (FALSE); 300 } 301 } 302 fclose(fd); 303 return (TRUE); 304} 305 306/* 307 * Cleanup and destroy tree and stack. 308 */ 309void 310closetags(void) 311{ 312 struct tagpos *s; 313 314 while (!SLIST_EMPTY(&shead)) { 315 s = SLIST_FIRST(&shead); 316 SLIST_REMOVE_HEAD(&shead, entry); 317 free(s->bname); 318 free(s); 319 } 320 unloadtags(); 321 free(tagsfn); 322} 323 324/* 325 * Strip away any special characters in pattern. 326 * The pattern in ctags isn't a true regular expression. Its of the form 327 * /^xxx$/ or ?^xxx$? and in some cases the "$" would be missing. Strip 328 * the leading and trailing special characters so the pattern matching 329 * would be a simple string compare. Escape character is taken care by 330 * fparseln. 331 */ 332char * 333strip(char *s, size_t len) 334{ 335 /* first strip trailing special chars */ 336 s[len - 1] = '\0'; 337 if (s[len - 2] == '$') 338 s[len - 2] = '\0'; 339 340 /* then strip leading special chars */ 341 s++; 342 if (*s == '^') 343 s++; 344 345 return s; 346} 347 348/* 349 * tags line is of the format "<tag>\t<filename>\t<pattern>". Split them 350 * by replacing '\t' with '\0'. This wouldn't alter the size of malloc'ed 351 * l, and can be freed during cleanup. 352 */ 353int 354addctag(char *l) 355{ 356 struct ctag *t; 357 358 if ((t = malloc(sizeof(struct ctag))) == NULL) { 359 ewprintf("Out of memory"); 360 return (FALSE); 361 } 362 t->tag = l; 363 if ((l = strchr(l, '\t')) == NULL) 364 goto cleanup; 365 *l++ = '\0'; 366 t->fname = l; 367 if ((l = strchr(l, '\t')) == NULL) 368 goto cleanup; 369 *l++ = '\0'; 370 if (*l == '\0') 371 goto cleanup; 372 t->pat = strip(l, strlen(l)); 373 RB_INSERT(tagtree, &tags, t); 374 return (TRUE); 375cleanup: 376 free(t); 377 free(l); 378 return (TRUE); 379} 380 381/* 382 * Search through each line of buffer for pattern. 383 */ 384int 385searchpat(char *pat) 386{ 387 struct line *lp; 388 int dotline; 389 size_t plen; 390 391 plen = strlen(pat); 392 dotline = 1; 393 lp = lforw(curbp->b_headp); 394 while (lp != curbp->b_headp) { 395 if (ltext(lp) != NULL && plen <= llength(lp) && 396 (strncmp(pat, ltext(lp), plen) == 0)) { 397 curwp->w_doto = 0; 398 curwp->w_dotp = lp; 399 curwp->w_dotline = dotline; 400 return (TRUE); 401 } else { 402 lp = lforw(lp); 403 dotline++; 404 } 405 } 406 return (FALSE); 407} 408 409/* 410 * Return TRUE if dot is at beginning of a word or at beginning 411 * of line, else FALSE. 412 */ 413int 414atbow(void) 415{ 416 if (curwp->w_doto == 0) 417 return (TRUE); 418 if (ISWORD(curwp->w_dotp->l_text[curwp->w_doto]) && 419 !ISWORD(curwp->w_dotp->l_text[curwp->w_doto - 1])) 420 return (TRUE); 421 return (FALSE); 422} 423 424/* 425 * Extract the word at dot without changing dot position. 426 */ 427int 428curtoken(int f, int n, char *token) 429{ 430 struct line *odotp; 431 int odoto, tdoto, odotline, size, r; 432 char c; 433 434 /* Underscore character is to be treated as "inword" while 435 * processing tokens unlike mg's default word traversal. Save 436 * and restore it's cinfo value so that tag matching works for 437 * identifier with underscore. 438 */ 439 c = cinfo['_']; 440 cinfo['_'] = _MG_W; 441 442 odotp = curwp->w_dotp; 443 odoto = curwp->w_doto; 444 odotline = curwp->w_dotline; 445 446 /* Move backword unless we are at the beginning of a word or at 447 * beginning of line. 448 */ 449 if (!atbow()) 450 if ((r = backword(f, n)) == FALSE) 451 goto cleanup; 452 453 tdoto = curwp->w_doto; 454 455 if ((r = forwword(f, n)) == FALSE) 456 goto cleanup; 457 458 /* strip away leading whitespace if any like emacs. */ 459 while (ltext(curwp->w_dotp) && 460 isspace(curwp->w_dotp->l_text[tdoto])) 461 tdoto++; 462 463 size = curwp->w_doto - tdoto; 464 if (size <= 0 || size >= MAX_TOKEN || 465 ltext(curwp->w_dotp) == NULL) { 466 r = FALSE; 467 goto cleanup; 468 } 469 strncpy(token, ltext(curwp->w_dotp) + tdoto, size); 470 token[size] = '\0'; 471 r = TRUE; 472 473cleanup: 474 cinfo['_'] = c; 475 curwp->w_dotp = odotp; 476 curwp->w_doto = odoto; 477 curwp->w_dotline = odotline; 478 return (r); 479} 480 481/* 482 * Search tagstree for a given token. 483 */ 484struct ctag * 485searchtag(char *tok) 486{ 487 struct ctag t, *res; 488 489 t.tag = tok; 490 if ((res = RB_FIND(tagtree, &tags, &t)) == NULL) { 491 ewprintf("No tag containing %s", tok); 492 return (NULL); 493 } 494 return res; 495} 496 497/* 498 * This is equivalent to filevisit from file.c. 499 * Look around to see if we can find the file in another buffer; if we 500 * can't find it, create a new buffer, read in the text, and switch to 501 * the new buffer. *scratch*, *grep*, *compile* needs to be handled 502 * differently from other buffers which have "filenames". 503 */ 504int 505loadbuffer(char *bname) 506{ 507 struct buffer *bufp; 508 char *adjf; 509 510 /* check for special buffers which begin with '*' */ 511 if (bname[0] == '*') { 512 if ((bufp = bfind(bname, FALSE)) != NULL) { 513 curbp = bufp; 514 return (showbuffer(bufp, curwp, WFFULL)); 515 } else { 516 return (FALSE); 517 } 518 } else { 519 if ((adjf = adjustname(bname, TRUE)) == NULL) 520 return (FALSE); 521 if ((bufp = findbuffer(adjf)) == NULL) 522 return (FALSE); 523 } 524 curbp = bufp; 525 if (showbuffer(bufp, curwp, WFFULL) != TRUE) 526 return (FALSE); 527 if (bufp->b_fname[0] == '\0') { 528 if (readin(adjf) != TRUE) { 529 killbuffer(bufp); 530 return (FALSE); 531 } 532 } 533 return (TRUE); 534} 535