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