UNIXworkcode

1 /******************************************************************************* 2 * * 3 * tags.c -- Nirvana editor tag file handling * 4 * * 5 * Copyright (C) 1999 Mark Edel * 6 * * 7 * This is free software; you can redistribute it and/or modify it under the * 8 * terms of the GNU General Public License as published by the Free Software * 9 * Foundation; either version 2 of the License, or (at your option) any later * 10 * version. In addition, you may distribute versions of this program linked to * 11 * Motif or Open Motif. See README for details. * 12 * * 13 * This software is distributed in the hope that it will be useful, but WITHOUT * 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * 16 * for more details. * 17 * * 18 * You should have received a copy of the GNU General Public License along with * 19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple * 20 * Place, Suite 330, Boston, MA 02111-1307 USA * 21 * * 22 * Nirvana Text Editor * 23 * July, 1993 * 24 * * 25 * Written by Mark Edel * 26 * * 27 *******************************************************************************/ 28 29 #ifdef HAVE_CONFIG_H 30 #include "../config.h" 31 #endif 32 33 #include "tags.h" 34 #include "textBuf.h" 35 #include "text.h" 36 #include "nedit.h" 37 #include "window.h" 38 #include "file.h" 39 #include "preferences.h" 40 #include "search.h" 41 #include "selection.h" 42 #include "calltips.h" 43 #include "../util/DialogF.h" 44 #include "../util/fileUtils.h" 45 #include "../util/misc.h" 46 #include "../util/utils.h" 47 #include "../util/refString.h" 48 #include "../util/nedit_malloc.h" 49 50 #include <stdio.h> 51 #include <stdlib.h> 52 #include <string.h> 53 #include <ctype.h> 54 #include <sys/types.h> 55 #include <sys/stat.h> 56 #include <unistd.h> 57 #include <sys/param.h> 58 59 #include <Xm/PrimitiveP.h> /* For Calltips */ 60 #include <Xm/Xm.h> 61 #include <Xm/SelectioB.h> 62 #include <X11/Xatom.h> 63 64 #ifdef HAVE_DEBUG_H 65 #include "../debug.h" 66 #endif 67 68 #define MAXLINE 2048 69 #define MAX_TAG_LEN 256 70 #define MAXDUPTAGS 100 71 #define MAX_TAG_INCLUDE_RECURSION_LEVEL 5 72 73 /* Take this many lines when making a tip from a tag. 74 (should probably be a language-dependent option, but...) */ 75 #define TIP_DEFAULT_LINES 4 76 77 #define STRSAVE(a) (NEditStrdup(a!=NULL?a:"")) 78 79 typedef struct _tag { 80 struct _tag *next; 81 const char *path; 82 const char *name; 83 const char *file; 84 const char *searchString; /* see comment below */ 85 int posInf; /* see comment below */ 86 short language; 87 short index; 88 } tag; 89 90 /* 91 ** contents of tag->searchString | tag->posInf 92 ** ctags, line num specified: "" | line num 93 ** ctags, search expr specfd: ctags search expr | -1 94 ** etags (emacs tags) etags search string | search start pos 95 */ 96 97 enum searchDirection {FORWARD, BACKWARD}; 98 99 static int loadTagsFile(const char *tagSpec, int index, int recLevel); 100 static void findDefCB(Widget widget, XtPointer closure, Atom *sel, 101 Atom *type, XtPointer value, unsigned long *length, int *format); 102 static void setTag(tag *t, const char *name, const char *file, 103 int language, const char *searchString, int posInf, 104 const char * tag); 105 static int fakeRegExSearch(WindowInfo *window, char *buffer, 106 const char *searchString, int *startPos, int *endPos); 107 static void updateMenuItems(void); 108 static int addTag(const char *name, const char *file, int lang, 109 const char *search, int posInf, const char *path, 110 int index); 111 static int delTag(const char *name, const char *file, int lang, 112 const char *search, int posInf, int index); 113 static tag *getTag(const char *name, int search_type); 114 static int findDef(WindowInfo *window, const char *value, int search_type); 115 static int findAllMatches(WindowInfo *window, const char *string); 116 static void findAllCB(Widget parent, XtPointer client_data, XtPointer call_data); 117 static Widget createSelectMenu(Widget parent, char *label, int nArgs, 118 char *args[]); 119 static void editTaggedLocation( Widget parent, int i ); 120 static void showMatchingCalltip( Widget parent, int i ); 121 122 static int searchLine(char *line, const char *regex); 123 static void rstrip( char *dst, const char *src ); 124 static int nextTFBlock(FILE *fp, char *header, char **tiptext, int *lineAt, 125 int *lineNo); 126 static int loadTipsFile(const char *tipsFile, int index, int recLevel); 127 128 /* Hash table of tags, implemented as an array. Each bin contains a 129 NULL-terminated linked list of parsed tags */ 130 static tag **Tags = NULL; 131 static unsigned DefTagHashSize = 100000u; 132 /* list of loaded tags files */ 133 tagFile *TagsFileList = NULL; 134 135 /* Hash table of calltip tags */ 136 static tag **Tips = NULL; 137 tagFile *TipsFileList = NULL; 138 139 140 /* These are all transient global variables -- they don't hold any state 141 between tag/tip lookups */ 142 static int searchMode = TAG; 143 static const char *tagName; 144 static char tagFiles[MAXDUPTAGS][MAXPATHLEN]; 145 static char tagSearch[MAXDUPTAGS][MAXPATHLEN]; 146 static int tagPosInf[MAXDUPTAGS]; 147 static Boolean globAnchored; 148 static int globPos; 149 static int globHAlign; 150 static int globVAlign; 151 static int globAlignMode; 152 153 /* A wrapper for calling TextDShowCalltip */ 154 static int tagsShowCalltip( WindowInfo *window, char *text ) { 155 if (text) 156 return ShowCalltip( window, text, globAnchored, globPos, globHAlign, 157 globVAlign, globAlignMode); 158 else 159 return 0; 160 } 161 162 /* Set the head of the proper file list (Tags or Tips) to t */ 163 static tagFile *setFileListHead(tagFile *t, int file_type ) 164 { 165 if (file_type == TAG) 166 TagsFileList = t; 167 else 168 TipsFileList = t; 169 return t; 170 } 171 172 /* Retrieve a tag structure from the hash table */ 173 static tag *getTag(const char *name, int search_type) 174 { 175 static char lastName[MAXLINE]; 176 static tag *t = NULL; 177 tag **table; 178 179 if (search_type == TIP) 180 table = Tips; 181 else 182 table = Tags; 183 184 if (table == NULL) return NULL; 185 186 if (name) { 187 unsigned const addr = StringHashAddr(name) % DefTagHashSize; 188 t = table[addr]; 189 strcpy(lastName,name); 190 } 191 else if (t) { 192 name = lastName; 193 t = t->next; 194 } 195 else return NULL; 196 197 for (;t; t = t->next) 198 if (!strcmp(name,t->name)) return t; 199 return NULL; 200 } 201 202 /* 203 ** Searches for `tpl` tag in the list `bkt` 204 */ 205 static tag* matchTagRec(tag const* tpl, tag* bkt) 206 { 207 /* pointers comparison can be used instead of strcmp because strings 208 are made shared by setTag() */ 209 tag *t = bkt; 210 for (; t; t = t->next) 211 if ((tpl->language == t->language) && (tpl->posInf == t->posInf) && 212 (tpl->name == t->name) && (tpl->searchString == t->searchString) && 213 (tpl->file == t->file)) return t; 214 215 return NULL; 216 } 217 218 /* Add a tag specification to the hash table 219 ** Return Value: 0 ... tag already existing, spec not added 220 ** 1 ... tag spec is new, added. 221 ** (We don't return boolean as the return value is used as counter increment!) 222 ** 223 */ 224 static int addTag(const char *name, const char *file, int lang, 225 const char *search, int posInf, const char *path, int index) 226 { 227 char newfile[MAXPATHLEN]; 228 tag **table; 229 230 if (searchMode == TIP) { 231 if (Tips == NULL) 232 Tips = (tag **)NEditCalloc(DefTagHashSize, sizeof(tag*)); 233 table = Tips; 234 } else { 235 if (Tags == NULL) 236 Tags = (tag **)NEditCalloc(DefTagHashSize, sizeof(tag*)); 237 table = Tags; 238 } 239 240 if (*file == '/') 241 strncpy(newfile, file, MAXPATHLEN); 242 else 243 snprintf(newfile, MAXPATHLEN, "%s%s", path, file); 244 newfile[MAXPATHLEN - 1] = '\0'; 245 246 NormalizePathname(newfile); 247 248 { 249 tag tpl; 250 setTag(&tpl, name, newfile, lang, search, posInf, path); 251 252 { 253 unsigned const addr = StringHashAddr(tpl.name) % DefTagHashSize; 254 255 tag *t = matchTagRec(&tpl, table[addr]); 256 if (t) 257 return 0; 258 259 t = NEditNew(tag); 260 *t = tpl; 261 t->index = index; 262 t->next = table[addr]; 263 table[addr] = t; 264 } 265 } 266 267 return 1; 268 } 269 270 /* Delete a tag from the cache. 271 * Search is limited to valid matches of 'name','file', 'search', posInf, and 'index'. 272 * EX: delete all tags matching index 2 ==> 273 * delTag(tagname,NULL,-2,NULL,-2,2); 274 * (posInf = -2 is an invalid match, posInf range: -1 .. +MAXINT, 275 lang = -2 is also an invalid match) 276 */ 277 static int delTag(const char *name, const char *file, int lang, 278 const char *search, int posInf, int index) 279 { 280 tag *t, *last; 281 int start,finish,i,del=0; 282 tag **table; 283 284 if (searchMode == TIP) 285 table = Tips; 286 else 287 table = Tags; 288 289 if (table == NULL) return FALSE; 290 if (name) 291 start = finish = StringHashAddr(name) % DefTagHashSize; 292 else { 293 start = 0; 294 finish = DefTagHashSize; 295 } 296 297 for (i = start; i<finish; i++) { 298 for (last = NULL, t = table[i]; t; last = t, t = t?t->next:table[i]) { 299 if (index && index != t->index) continue; 300 if (posInf != t->posInf) continue; 301 if (lang >= PLAIN_LANGUAGE_MODE && lang != t->language) continue; 302 if (name && strcmp(name,t->name)) continue; 303 if (file && strcmp(file,t->file)) continue; 304 if (search && strcmp(search,t->searchString)) continue; 305 306 if (last) 307 last->next = t->next; 308 else 309 table[i] = t->next; 310 311 RefStringFree(t->name); 312 RefStringFree(t->file); 313 RefStringFree(t->searchString); 314 RefStringFree(t->path); 315 NEditFree(t); 316 t = NULL; 317 del++; 318 } 319 } 320 return del>0; 321 } 322 323 /* used in AddRelTagsFile and AddTagsFile */ 324 static int tagFileIndex = 0; 325 326 /* 327 ** AddRelTagsFile(): Rescan tagSpec for relative tag file specs 328 ** (not starting with [/~]) and extend the tag files list if in 329 ** windowPath a tags file matching the relative spec has been found. 330 */ 331 int AddRelTagsFile(const char *tagSpec, const char *windowPath, int file_type) 332 { 333 tagFile *t; 334 int added=0; 335 struct stat statbuf; 336 char *filename; 337 char pathName[MAXPATHLEN]; 338 char *tmptagSpec; 339 tagFile *FileList; 340 341 searchMode = file_type; 342 if (searchMode == TAG) 343 FileList = TagsFileList; 344 else 345 FileList = TipsFileList; 346 347 tmptagSpec = NEditStrdup(tagSpec); 348 for (filename = strtok(tmptagSpec, ":"); filename; filename = strtok(NULL, ":")){ 349 if (*filename == '/' || *filename == '~') 350 continue; 351 if (windowPath && *windowPath) { 352 strcpy(pathName, windowPath); 353 } else { 354 strcpy(pathName, GetCurrentDir()); 355 } 356 strcat(pathName, "/"); 357 strcat(pathName, filename); 358 NormalizePathname(pathName); 359 360 for (t = FileList; t && strcmp(t->filename, pathName); t = t->next); 361 if (t) { 362 added=1; 363 continue; 364 } 365 if (stat(pathName, &statbuf) != 0) 366 continue; 367 t = (tagFile *) NEditMalloc(sizeof(tagFile)); 368 t->filename = STRSAVE(pathName); 369 t->loaded = 0; 370 t->date = statbuf.st_mtime; 371 t->index = ++tagFileIndex; 372 t->next = FileList; 373 FileList = setFileListHead(t, file_type); 374 added=1; 375 } 376 NEditFree(tmptagSpec); 377 updateMenuItems(); 378 if (added) 379 return TRUE; 380 else 381 return FALSE; 382 } 383 384 /* 385 ** AddTagsFile(): Set up the the list of tag files to manage from a file spec. 386 ** The file spec comes from the X-Resource Nedit.tags: It can list multiple 387 ** tags files, specified by separating them with colons. The .Xdefaults would 388 ** look like this: 389 ** Nedit.tags: <tagfile1>:<tagfile2> 390 ** Returns True if all files were found in the FileList or loaded successfully, 391 ** FALSE otherwise. 392 */ 393 int AddTagsFile(const char *tagSpec, int file_type) 394 { 395 tagFile *t; 396 int added=1; 397 struct stat statbuf; 398 char *filename; 399 char pathName[MAXPATHLEN]; 400 char *tmptagSpec; 401 tagFile *FileList; 402 403 /* To prevent any possible segfault */ 404 if (tagSpec == NULL) { 405 fprintf(stderr, "xnedit: Internal Error!\n" 406 " Passed NULL pointer to AddTagsFile!\n"); 407 return FALSE; 408 } 409 410 searchMode = file_type; 411 if (searchMode == TAG) 412 FileList = TagsFileList; 413 else 414 FileList = TipsFileList; 415 416 tmptagSpec = NEditStrdup(tagSpec); 417 for (filename = strtok(tmptagSpec,":"); filename; filename = strtok(NULL,":")) { 418 if (*filename != '/') { 419 strcpy(pathName, GetCurrentDir()); 420 strcat(pathName,"/"); 421 strcat(pathName,filename); 422 } else { 423 strcpy(pathName,filename); 424 } 425 NormalizePathname(pathName); 426 427 for (t = FileList; t && strcmp(t->filename,pathName); t = t->next); 428 if (t) { 429 /* This file is already in the list. It's easiest to just 430 refcount all tag/tip files even though we only actually care 431 about tip files. */ 432 ++(t->refcount); 433 added=1; 434 continue; 435 } 436 if (stat(pathName,&statbuf) != 0) { 437 /* Problem reading this tags file. Return FALSE */ 438 added = 0; 439 continue; 440 } 441 t = (tagFile *) NEditMalloc(sizeof(tagFile)); 442 t->filename = STRSAVE(pathName); 443 t->loaded = 0; 444 t->date = statbuf.st_mtime; 445 t->index = ++tagFileIndex; 446 t->next = FileList; 447 t->refcount = 1; 448 FileList = setFileListHead(t, file_type ); 449 } 450 NEditFree(tmptagSpec); 451 updateMenuItems(); 452 if (added) 453 return TRUE; 454 else 455 return FALSE; 456 } 457 458 /* Un-manage a colon-delimited set of tags files 459 * Return TRUE if all files were found in the FileList and unloaded, FALSE 460 * if any file was not found in the FileList. 461 * "file_type" is either TAG or TIP 462 * If "force_unload" is true, a calltips file will be deleted even if its 463 * refcount is nonzero. 464 */ 465 int DeleteTagsFile(const char *tagSpec, int file_type, Boolean force_unload) 466 { 467 tagFile *t, *last; 468 tagFile *FileList; 469 char pathName[MAXPATHLEN], *tmptagSpec, *filename; 470 int removed; 471 472 /* To prevent any possible segfault */ 473 if (tagSpec == NULL) { 474 fprintf(stderr, "xnedit: Internal Error: Passed NULL pointer to DeleteTagsFile!\n"); 475 return FALSE; 476 } 477 478 searchMode = file_type; 479 if (searchMode == TAG) 480 FileList = TagsFileList; 481 else 482 FileList = TipsFileList; 483 484 tmptagSpec = NEditStrdup(tagSpec); 485 removed=1; 486 for (filename = strtok(tmptagSpec,":"); filename; 487 filename = strtok(NULL,":")) { 488 if (*filename != '/') { 489 strcpy(pathName, GetCurrentDir()); 490 strcat(pathName,"/"); 491 strcat(pathName,filename); 492 } else { 493 strcpy(pathName,filename); 494 } 495 NormalizePathname(pathName); 496 497 for (last=NULL,t = FileList; t; last = t,t = t->next) { 498 if (strcmp(t->filename, pathName)) 499 continue; 500 /* Don't unload tips files with nonzero refcounts unless forced */ 501 if (searchMode == TIP && !force_unload && --t->refcount > 0) { 502 break; 503 } 504 if (t->loaded) 505 delTag(NULL,NULL,-2,NULL,-2,t->index); 506 if (last) last->next = t->next; 507 else FileList = setFileListHead(t->next, file_type); 508 NEditFree(t->filename); 509 NEditFree(t); 510 updateMenuItems(); 511 break; 512 } 513 /* If any file can't be removed, return false */ 514 if (!t) 515 removed = 0; 516 } 517 if (removed) 518 return TRUE; 519 else 520 return FALSE; 521 } 522 523 /* 524 ** Update the "Find Definition", "Unload Tags File", "Show Calltip", 525 ** and "Unload Calltips File" menu items in the existing windows. 526 */ 527 static void updateMenuItems(void) 528 { 529 WindowInfo *w; 530 Boolean tipStat=FALSE, tagStat=FALSE; 531 532 if (TipsFileList) tipStat=TRUE; 533 if (TagsFileList) tagStat=TRUE; 534 535 for (w=WindowList; w!=NULL; w=w->next) { 536 if (!IsTopDocument(w)) 537 continue; 538 XtSetSensitive(w->showTipItem, tipStat || tagStat); 539 XtSetSensitive(w->unloadTipsMenuItem, tipStat); 540 XtSetSensitive(w->findDefItem, tagStat); 541 XtSetSensitive(w->unloadTagsMenuItem, tagStat); 542 } 543 } 544 545 /* 546 ** Scans one <line> from a ctags tags file (<index>) in tagPath. 547 ** Return value: Number of tag specs added. 548 */ 549 static int scanCTagsLine(char *line, const char *tagPath, int index) 550 { 551 char *name = line, *searchString = NULL; 552 char *file = NULL; 553 char *posTagREEnd = NULL, *posTagRENull = NULL; 554 int pos; 555 556 /* [name]\t[file]\t[searchString]\n */ 557 if ((*name != '!') && (file = strchr(name, '\t')) != NULL) 558 { 559 *file++ = '\0'; 560 if ((searchString = strchr(file, '\t')) != NULL) 561 *searchString++ = '\0'; 562 else return 0; 563 } 564 else return 0; 565 566 /* 567 ** Guess the end of searchString: 568 ** Try to handle original ctags and exuberant ctags format: 569 */ 570 if(searchString[0] == '/' || searchString[0] == '?') { 571 572 pos=-1; /* "search expr without pos info" */ 573 574 /* Situations: /<ANY expr>/\0 575 ** ?<ANY expr>?\0 --> original ctags 576 ** /<ANY expr>/;" <flags> 577 ** ?<ANY expr>?;" <flags> --> exuberant ctags 578 */ 579 posTagREEnd = strrchr(searchString, ';'); 580 posTagRENull = strchr(searchString, 0); 581 if(!posTagREEnd || (posTagREEnd[1] != '""') || 582 (posTagRENull[-1] == searchString[0])) { 583 /* -> original ctags format = exuberant ctags format 1 */ 584 posTagREEnd = posTagRENull; 585 } else { 586 /* looks like exuberant ctags format 2 */ 587 *posTagREEnd = 0; 588 } 589 590 /* 591 ** Hide the last delimiter: 592 ** /<expression>/ becomes /<expression> 593 ** ?<expression>? becomes ?<expression> 594 ** This will save a little work in fakeRegExSearch. 595 */ 596 if(posTagREEnd > (searchString+2)) { 597 posTagREEnd--; 598 if(searchString[0] == *posTagREEnd) 599 *posTagREEnd=0; 600 } 601 } else { 602 pos=atoi(searchString); 603 *searchString=0; 604 } 605 /* No ability to read language mode right now */ 606 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos, tagPath, 607 index); 608 } 609 610 /* 611 * Scans one <line> from an etags (emacs) tags file (<index>) in tagPath. 612 * recLevel = current recursion level for tags file including 613 * file = destination definition file. possibly modified. len=MAXPATHLEN! 614 * Return value: Number of tag specs added. 615 */ 616 static int scanETagsLine(const char *line, const char * tagPath, int index, 617 char * file, int recLevel) 618 { 619 char name[MAXLINE], searchString[MAXLINE]; 620 char incPath[MAXPATHLEN]; 621 int pos, len; 622 char *posDEL, *posSOH, *posCOM; 623 624 /* check for destination file separator */ 625 if(line[0]==12) { /* <np> */ 626 *file=0; 627 return 0; 628 } 629 630 /* check for standard definition line */ 631 posDEL=strchr(line, '\177'); 632 posSOH=strchr(line, '\001'); 633 posCOM=strrchr(line, ','); 634 if(*file && posDEL && (posSOH > posDEL) && (posCOM > posSOH)) { 635 /* exuberant ctags -e style */ 636 len=Min(MAXLINE-1, posDEL - line); 637 strncpy(searchString, line, len); 638 searchString[len]=0; 639 len=Min(MAXLINE-1, (posSOH - posDEL) - 1); 640 strncpy(name, posDEL + 1, len); 641 name[len]=0; 642 pos=atoi(posCOM+1); 643 /* No ability to set language mode for the moment */ 644 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos, 645 tagPath, index); 646 } 647 if (*file && posDEL && (posCOM > posDEL)) { 648 /* old etags style, part name<soh> is missing here! */ 649 len=Min(MAXLINE-1, posDEL - line); 650 strncpy(searchString, line, len); 651 searchString[len]=0; 652 /* guess name: take the last alnum (plus _) part of searchString */ 653 while(--len >= 0) { 654 if( isalnum((unsigned char)searchString[len]) || 655 (searchString[len] == '_')) 656 break; 657 } 658 if(len<0) 659 return 0; 660 pos=len; 661 while (pos >= 0 && (isalnum((unsigned char)searchString[pos]) || 662 (searchString[pos] == '_'))) 663 pos--; 664 strncpy(name, searchString + pos + 1, len - pos); 665 name[len - pos] = 0; /* name ready */ 666 pos=atoi(posCOM+1); 667 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos, 668 tagPath, index); 669 } 670 /* check for destination file spec */ 671 if(*line && posCOM) { 672 len=Min(MAXPATHLEN-1, posCOM - line); 673 strncpy(file, line, len); 674 file[len]=0; 675 /* check if that's an include file ... */ 676 if(!(strncmp(posCOM+1, "include", 7))) { 677 if(*file != '/') { 678 if((strlen(tagPath) + strlen(file)) >= MAXPATHLEN) { 679 fprintf(stderr, "tags.c: MAXPATHLEN overflow\n"); 680 *file=0; /* invalidate */ 681 return 0; 682 } 683 strcpy(incPath, tagPath); 684 strcat(incPath, file); 685 CompressPathname(incPath); 686 return(loadTagsFile(incPath, index, recLevel+1)); 687 } else { 688 return(loadTagsFile(file, index, recLevel+1)); 689 } 690 } 691 } 692 return 0; 693 } 694 695 /* Tag File Type */ 696 typedef enum { 697 TFT_CHECK, TFT_ETAGS, TFT_CTAGS 698 } TFT; 699 700 /* 701 ** Loads tagsFile into the hash table. 702 ** Returns the number of added tag specifications. 703 */ 704 static int loadTagsFile(const char *tagsFile, int index, int recLevel) 705 { 706 FILE *fp = NULL; 707 char line[MAXLINE]; 708 char file[MAXPATHLEN], tagPath[MAXPATHLEN]; 709 char resolvedTagsFile[MAXPATHLEN+1]; 710 int nTagsAdded=0; 711 int tagFileType = TFT_CHECK; 712 713 if(recLevel > MAX_TAG_INCLUDE_RECURSION_LEVEL) { 714 return 0; 715 } 716 /* the path of the tags file must be resolved to find the right files: 717 * definition source files are (in most cases) specified relatively inside 718 * the tags file to the tags files directory. 719 */ 720 if(!ResolvePath(tagsFile, resolvedTagsFile)) { 721 return 0; 722 } 723 724 /* Open the file */ 725 if ((fp = fopen(resolvedTagsFile, "r")) == NULL) { 726 return 0; 727 } 728 729 ParseFilename(resolvedTagsFile, NULL, tagPath); 730 731 /* Read the file and store its contents */ 732 while (fgets(line, MAXLINE, fp)) { 733 734 /* This might take a while if you have a huge tags file (like I do).. 735 keep the windows up to date and post a busy cursor so the user 736 doesn't think we died. */ 737 738 AllWindowsBusy("Loading tags file..."); 739 740 /* the first character in the file decides if the file is treat as 741 etags or ctags file. 742 */ 743 if(tagFileType==TFT_CHECK) { 744 if(line[0]==12) /* <np> */ 745 tagFileType=TFT_ETAGS; 746 else 747 tagFileType=TFT_CTAGS; 748 } 749 if(tagFileType==TFT_CTAGS) { 750 nTagsAdded += scanCTagsLine(line, tagPath, index); 751 } else { 752 nTagsAdded += scanETagsLine(line, tagPath, index, file, recLevel); 753 } 754 } 755 fclose(fp); 756 757 AllWindowsUnbusy(); 758 return nTagsAdded; 759 } 760 761 /* 762 ** Given a tag name, lookup the file and path of the definition 763 ** and the proper search string. Returned strings are pointers 764 ** to internal storage which are valid until the next loadTagsFile call. 765 ** 766 ** Invocation with name != NULL (containing the searched definition) 767 ** --> returns first definition of name 768 ** Successive invocation with name == NULL 769 ** --> returns further definitions (resulting from multiple tags files) 770 ** 771 ** Return Value: TRUE: tag spec found 772 ** FALSE: no (more) definitions found. 773 */ 774 #define TAG_STS_ERR_FMT "NEdit: Error getting status for tag file %s\n" 775 int LookupTag(const char *name, const char **file, int *language, 776 const char **searchString, int * pos, const char **path, 777 int search_type) 778 { 779 tag *t; 780 tagFile *tf; 781 struct stat statbuf; 782 tagFile *FileList; 783 int load_status; 784 785 searchMode = search_type; 786 if (searchMode == TIP) 787 FileList = TipsFileList; 788 else 789 FileList = TagsFileList; 790 791 /* 792 ** Go through the list of all tags Files: 793 ** - load them (if not already loaded) 794 ** - check for update of the tags file and reload it in that case 795 ** - save the modification date of the tags file 796 ** 797 ** Do this only as long as name != NULL, not for sucessive calls 798 ** to find multiple tags specs. 799 ** 800 */ 801 for (tf = FileList; tf && name; tf = tf->next) { 802 if (tf->loaded) { 803 if (stat(tf->filename,&statbuf) != 0) { /* */ 804 fprintf(stderr, TAG_STS_ERR_FMT, tf->filename); 805 } else { 806 if (tf->date == statbuf.st_mtime) { 807 /* current tags file tf is already loaded and up to date */ 808 continue; 809 } 810 } 811 /* tags file has been modified, delete it's entries and reload it */ 812 delTag(NULL,NULL,-2,NULL,-2,tf->index); 813 } 814 /* If we get here we have to try to (re-) load the tags file */ 815 if (FileList == TipsFileList) 816 load_status = loadTipsFile(tf->filename, tf->index, 0); 817 else 818 load_status = loadTagsFile(tf->filename, tf->index, 0); 819 if(load_status) { 820 if (stat(tf->filename,&statbuf) != 0) { 821 if(!tf->loaded) { 822 /* if tf->loaded == 1 we already have seen the error msg */ 823 fprintf(stderr, TAG_STS_ERR_FMT, tf->filename); 824 } 825 } else { 826 tf->date = statbuf.st_mtime; 827 } 828 tf->loaded = 1; 829 } else { 830 tf->loaded = 0; 831 } 832 } 833 834 t = getTag(name, search_type); 835 836 if (!t) { 837 return FALSE; 838 } else { 839 *file = t->file; 840 *language = t->language; 841 *searchString = t->searchString; 842 *pos = t->posInf; 843 *path = t->path; 844 return TRUE; 845 } 846 } 847 848 /* 849 ** This code path is followed if the request came from either 850 ** FindDefinition or FindDefCalltip. This should probably be refactored. 851 */ 852 static int findDef(WindowInfo *window, const char *value, int search_type) { 853 static char tagText[MAX_TAG_LEN + 1]; 854 const char *p; 855 char message[MAX_TAG_LEN+40]; 856 int l, ml, status = 0; 857 858 searchMode = search_type; 859 l = strlen(value); 860 if (l <= MAX_TAG_LEN) { 861 /* should be of type text??? */ 862 for (p = value; *p && isascii(*p); p++) { 863 } 864 if (!(*p)) { 865 ml = ((l < MAX_TAG_LEN) ? (l) : (MAX_TAG_LEN)); 866 strncpy(tagText, value, ml); 867 tagText[ml] = '\0'; 868 /* See if we can find the tip/tag */ 869 status = findAllMatches(window, tagText); 870 871 /* If we didn't find a requested calltip, see if we can use a tag */ 872 if (status == 0 && search_type == TIP && TagsFileList != NULL) { 873 searchMode = TIP_FROM_TAG; 874 status = findAllMatches(window, tagText); 875 } 876 877 if (status == 0) { 878 /* Didn't find any matches */ 879 if (searchMode == TIP_FROM_TAG || searchMode == TIP) { 880 sprintf(message, "No match for \"%s\" in calltips or tags.", 881 tagName); 882 tagsShowCalltip( window, message ); 883 } else 884 { 885 DialogF(DF_WARN, window->textArea, 1, "Tags", 886 "\"%s\" not found in tags file%s", "OK", tagName, 887 (TagsFileList && TagsFileList->next) ? "s" : ""); 888 } 889 } 890 } 891 else { 892 fprintf(stderr, "NEdit: Can''t handle non 8-bit text\n"); 893 XBell(TheDisplay, 0); 894 } 895 } 896 else { 897 fprintf(stderr, "NEdit: Tag Length too long.\n"); 898 XBell(TheDisplay, 0); 899 } 900 return status; 901 } 902 903 /* 904 ** Lookup the definition for the current primary selection the currently 905 ** loaded tags file and bring up the file and line that the tags file 906 ** indicates. 907 */ 908 static void findDefinitionHelper(WindowInfo *window, Time time, const char *arg, 909 int search_type) 910 { 911 if(arg) 912 { 913 findDef(window, arg, search_type); 914 } 915 else 916 { 917 searchMode = search_type; 918 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING, 919 findDefCB, window, time); 920 } 921 } 922 923 /* 924 ** See findDefHelper 925 */ 926 void FindDefinition(WindowInfo *window, Time time, const char *arg) 927 { 928 findDefinitionHelper(window, time, arg, TAG); 929 } 930 931 /* 932 ** See findDefHelper 933 */ 934 void FindDefCalltip(WindowInfo *window, Time time, const char *arg) 935 { 936 /* Reset calltip parameters to reasonable defaults */ 937 globAnchored = False; 938 globPos = -1; 939 globHAlign = TIP_LEFT; 940 globVAlign = TIP_BELOW; 941 globAlignMode = TIP_SLOPPY; 942 943 findDefinitionHelper(window, time, arg, TIP); 944 } 945 946 /* Callback function for FindDefinition */ 947 static void findDefCB(Widget widget, XtPointer closure, Atom *sel, 948 Atom *type, XtPointer v, unsigned long *length, int *format) 949 { 950 WindowInfo *window = closure; 951 char *value = v; 952 953 /* skip if we can't get the selection data, or it's obviously too long */ 954 if (*type == XT_CONVERT_FAIL || value == NULL) { 955 XBell(TheDisplay, 0); 956 } else { 957 findDef(window, value, searchMode); 958 } 959 NEditFree(value); 960 } 961 962 /* 963 ** Try to display a calltip 964 ** anchored: If true, tip appears at position pos 965 ** lookup: If true, text is considered a key to be searched for in the 966 ** tip and/or tag database depending on search_type 967 ** search_type: Either TIP or TIP_FROM_TAG 968 */ 969 int ShowTipString(WindowInfo *window, char *text, Boolean anchored, 970 int pos, Boolean lookup, int search_type, int hAlign, int vAlign, 971 int alignMode) { 972 973 if (search_type == TAG) return 0; 974 975 /* So we don't have to carry all of the calltip alignment info around */ 976 globAnchored = anchored; 977 globPos = pos; 978 globHAlign = hAlign; 979 globVAlign = vAlign; 980 globAlignMode = alignMode; 981 982 /* If this isn't a lookup request, just display it. */ 983 if (!lookup) 984 return tagsShowCalltip(window, text); 985 else 986 return findDef(window, text, search_type); 987 } 988 989 /* store all of the info into a pre-allocated tags struct */ 990 static void setTag(tag *t, const char *name, const char *file, 991 int language, const char *searchString, int posInf, 992 const char *path) 993 { 994 t->name = RefStringDup(name); 995 t->file = RefStringDup(file); 996 t->searchString = RefStringDup(searchString); 997 t->path = RefStringDup(path); 998 t->language = language; 999 t->posInf = posInf; 1000 } 1001 1002 /* 1003 ** ctags search expressions are literal strings with a search direction flag, 1004 ** line starting "^" and ending "$" delimiters. This routine translates them 1005 ** into NEdit compatible regular expressions and does the search. 1006 ** Etags search expressions are plain literals strings, which 1007 ** 1008 ** If in_buffer is not NULL then it is searched instead of the window buffer. 1009 ** In this case in_buffer should be an NEditMalloc allocated buffer and the 1010 ** caller is responsible for freeing it. 1011 */ 1012 static int fakeRegExSearch(WindowInfo *window, char *in_buffer, 1013 const char *searchString, int *startPos, int *endPos) 1014 { 1015 int found, searchStartPos, dir, ctagsMode; 1016 char searchSubs[3*MAXLINE+3], *outPtr; 1017 const char *fileString, *inPtr; 1018 1019 if (in_buffer == NULL) { 1020 /* get the entire (sigh) text buffer from the text area widget */ 1021 fileString = BufAsString(window->buffer); 1022 } else { 1023 fileString = in_buffer; 1024 } 1025 1026 /* determine search direction and start position */ 1027 if (*startPos != -1) { /* etags mode! */ 1028 dir = SEARCH_FORWARD; 1029 searchStartPos = *startPos; 1030 ctagsMode=0; 1031 } else if (searchString[0] == '/') { 1032 dir = SEARCH_FORWARD; 1033 searchStartPos = 0; 1034 ctagsMode=1; 1035 } else if (searchString[0] == '?') { 1036 dir = SEARCH_BACKWARD; 1037 /* searchStartPos = window->buffer->length; */ 1038 searchStartPos = strlen(fileString); 1039 ctagsMode=1; 1040 } else { 1041 fprintf(stderr, "NEdit: Error parsing tag file search string"); 1042 return FALSE; 1043 } 1044 1045 /* Build the search regex. */ 1046 outPtr=searchSubs; 1047 if(ctagsMode) { 1048 inPtr=searchString+1; /* searchString[0] is / or ? --> search dir */ 1049 if(*inPtr == '^') { 1050 /* If the first char is a caret then it's a RE line start delim */ 1051 *outPtr++ = *inPtr++; 1052 } 1053 } else { /* etags mode, no search dir spec, no leading caret */ 1054 inPtr=searchString; 1055 } 1056 while(*inPtr) { 1057 if( (*inPtr=='\\' && inPtr[1]=='/') || 1058 (*inPtr=='\r' && inPtr[1]=='$' && !inPtr[2]) 1059 ) { 1060 /* Remove: 1061 - escapes (added by standard and exuberant ctags) from slashes 1062 - literal CRs generated by standard ctags for DOSified sources 1063 */ 1064 inPtr++; 1065 } else if(strchr("()-[]<>{}.|^*+?&\\", *inPtr) 1066 || (*inPtr == '$' && (inPtr[1]||(!ctagsMode)))){ 1067 /* Escape RE Meta Characters to match them literally. 1068 Don't escape $ if it's the last charcter of the search expr 1069 in ctags mode; always escape $ in etags mode. 1070 */ 1071 *outPtr++ = '\\'; 1072 *outPtr++ = *inPtr++; 1073 } else if (isspace((unsigned char)*inPtr)) { /* col. multiple spaces */ 1074 *outPtr++ = '\\'; 1075 *outPtr++ = 's'; 1076 *outPtr++ = '+'; 1077 do { inPtr++ ; } while(isspace((unsigned char)*inPtr)); 1078 } else { /* simply copy all other characters */ 1079 *outPtr++ = *inPtr++; 1080 } 1081 } 1082 *outPtr=0; /* Terminate searchSubs */ 1083 1084 found = SearchString(fileString, searchSubs, dir, SEARCH_REGEX, 1085 False, searchStartPos, startPos, endPos, NULL, NULL, NULL); 1086 1087 if(!found && !ctagsMode) { 1088 /* position of the target definition could have been drifted before 1089 startPos, if nothing has been found by now try searching backward 1090 again from startPos. 1091 */ 1092 found = SearchString(fileString, searchSubs, SEARCH_BACKWARD, 1093 SEARCH_REGEX, False, searchStartPos, startPos, endPos, NULL, 1094 NULL, NULL); 1095 } 1096 1097 /* return the result */ 1098 if (found) { 1099 /* *startPos and *endPos are set in SearchString*/ 1100 return TRUE; 1101 } else { 1102 /* startPos, endPos left untouched by SearchString if search failed. */ 1103 XBell(TheDisplay, 0); 1104 return FALSE; 1105 } 1106 } 1107 1108 /* Finds all matches and handles tag "collisions". Prompts user with a 1109 list of collided tags in the hash table and allows the user to select 1110 the correct one. */ 1111 static int findAllMatches(WindowInfo *window, const char *string) 1112 { 1113 Widget dialogParent = window->textArea; 1114 char filename[MAXPATHLEN], pathname[MAXPATHLEN]; 1115 char temp[32+2*MAXPATHLEN+MAXLINE]; 1116 const char *fileToSearch, *searchString, *tagPath; 1117 char **dupTagsList; 1118 int startPos, i, pathMatch=0, samePath=0, langMode, nMatches=0; 1119 1120 /* verify that the string is reasonable as a tag */ 1121 if (*string == '\0' || strlen(string) > MAX_TAG_LEN) { 1122 XBell(TheDisplay, 0); 1123 return -1; 1124 } 1125 tagName=string; 1126 1127 /* First look up all of the matching tags */ 1128 while (LookupTag(string, &fileToSearch, &langMode, &searchString, &startPos, 1129 &tagPath, searchMode)) { 1130 /* Skip this tag if it has a language mode that doesn't match the 1131 current language mode, but don't skip anything if the window is in 1132 PLAIN_LANGUAGE_MODE. */ 1133 if (window->languageMode != PLAIN_LANGUAGE_MODE && 1134 GetPrefSmartTags() && langMode != PLAIN_LANGUAGE_MODE && 1135 langMode != window->languageMode) { 1136 string=NULL; 1137 continue; 1138 } 1139 if (*fileToSearch == '/') 1140 strcpy(tagFiles[nMatches], fileToSearch); 1141 else 1142 sprintf(tagFiles[nMatches],"%s%s",tagPath,fileToSearch); 1143 strcpy(tagSearch[nMatches],searchString); 1144 tagPosInf[nMatches]=startPos; 1145 ParseFilename(tagFiles[nMatches], filename, pathname); 1146 /* Is this match in the current file? If so, use it! */ 1147 if (GetPrefSmartTags() && !strcmp(window->filename,filename) 1148 && !strcmp(window->path,pathname) ) { 1149 if (nMatches) { 1150 strcpy(tagFiles[0],tagFiles[nMatches]); 1151 strcpy(tagSearch[0],tagSearch[nMatches]); 1152 tagPosInf[0]=tagPosInf[nMatches]; 1153 } 1154 nMatches = 1; 1155 break; 1156 } 1157 /* Is this match in the same dir. as the current file? */ 1158 if (!strcmp(window->path,pathname)) { 1159 samePath++; 1160 pathMatch=nMatches; 1161 } 1162 if (++nMatches >= MAXDUPTAGS) { 1163 DialogF(DF_WARN, dialogParent, 1, "Tags", 1164 "Too many duplicate tags, first %d shown", "OK", MAXDUPTAGS); 1165 break; 1166 } 1167 /* Tell LookupTag to look for more definitions of the same tag: */ 1168 string = NULL; 1169 } 1170 1171 /* Did we find any matches? */ 1172 if (!nMatches) { 1173 return 0; 1174 } 1175 1176 /* Only one of the matches is in the same dir. as this file. Use it. */ 1177 if (GetPrefSmartTags() && samePath == 1 && nMatches > 1) { 1178 strcpy(tagFiles[0],tagFiles[pathMatch]); 1179 strcpy(tagSearch[0],tagSearch[pathMatch]); 1180 tagPosInf[0]=tagPosInf[pathMatch]; 1181 nMatches = 1; 1182 } 1183 1184 /* If all of the tag entries are the same file, just use the first. 1185 */ 1186 if (GetPrefSmartTags()) { 1187 for (i=1; i<nMatches; i++) 1188 if (strcmp(tagFiles[i],tagFiles[i-1])) 1189 break; 1190 if (i==nMatches) 1191 nMatches = 1; 1192 } 1193 1194 if (nMatches>1) { 1195 if (!(dupTagsList = (char **) NEditMalloc(sizeof(char *) * nMatches))) { 1196 fprintf(stderr, "xnedit: findAllMatches(): out of heap space!\n"); 1197 XBell(TheDisplay, 0); 1198 return -1; 1199 } 1200 1201 for (i=0; i<nMatches; i++) { 1202 ParseFilename(tagFiles[i], filename, pathname); 1203 if ((i<nMatches-1 && !strcmp(tagFiles[i],tagFiles[i+1])) || 1204 (i>0 && !strcmp(tagFiles[i],tagFiles[i-1]))) { 1205 if(*(tagSearch[i]) && (tagPosInf[i] != -1)) { /* etags */ 1206 sprintf(temp,"%2d. %s%s %8i %s", i+1, pathname, 1207 filename, tagPosInf[i], tagSearch[i]); 1208 } else if (*(tagSearch[i])) { /* ctags search expr */ 1209 sprintf(temp,"%2d. %s%s %s", i+1, pathname, 1210 filename, tagSearch[i]); 1211 } else { /* line number only */ 1212 sprintf(temp,"%2d. %s%s %8i", i+1, pathname, filename, 1213 tagPosInf[i]); 1214 } 1215 } else { 1216 sprintf(temp,"%2d. %s%s",i+1,pathname,filename); 1217 } 1218 1219 if (NULL == (dupTagsList[i] = (char*) NEditMalloc(strlen(temp) + 1))) { 1220 int j; 1221 fprintf(stderr, "xnedit: findAllMatches(): out of heap space!\n"); 1222 1223 /* dupTagsList[i] is unallocated, let's free [i - 1] to [0] */ 1224 for (j = i - 1; j > -1; j--) { 1225 NEditFree(dupTagsList[j]); 1226 } 1227 NEditFree(dupTagsList); 1228 1229 XBell(TheDisplay, 0); 1230 return -1; 1231 } 1232 1233 strcpy(dupTagsList[i],temp); 1234 } 1235 createSelectMenu(dialogParent, "Duplicate Tags", nMatches, dupTagsList); 1236 for (i=0; i<nMatches; i++) 1237 NEditFree(dupTagsList[i]); 1238 NEditFree(dupTagsList); 1239 return 1; 1240 } 1241 1242 /* 1243 ** No need for a dialog list, there is only one tag matching -- 1244 ** Go directly to the tag 1245 */ 1246 if (searchMode == TAG) 1247 editTaggedLocation( dialogParent, 0 ); 1248 else 1249 showMatchingCalltip( dialogParent, 0 ); 1250 return 1; 1251 } 1252 1253 /* Callback function for the FindAll widget. Process the users response. */ 1254 static void findAllCB(Widget parent, XtPointer client_data, XtPointer call_data) 1255 { 1256 int i; 1257 char *eptr; 1258 1259 XmSelectionBoxCallbackStruct *cbs = 1260 (XmSelectionBoxCallbackStruct *) call_data; 1261 if (cbs->reason == XmCR_NO_MATCH) 1262 return; 1263 if (cbs->reason == XmCR_CANCEL) { 1264 XtDestroyWidget(XtParent(parent)); 1265 return; 1266 } 1267 1268 XmStringGetLtoR(cbs->value,XmFONTLIST_DEFAULT_TAG,&eptr); 1269 if ((i = atoi(eptr)-1) < 0) { 1270 XBell(TheDisplay, 0); 1271 return; 1272 } 1273 1274 if (searchMode == TAG) 1275 editTaggedLocation( parent, i ); /* Open the file with the definition */ 1276 else 1277 showMatchingCalltip( parent, i ); 1278 1279 if (cbs->reason == XmCR_OK) 1280 XtDestroyWidget(XtParent(parent)); 1281 } 1282 1283 /* Window manager close-box callback for tag-collision dialog */ 1284 static void findAllCloseCB(Widget parent, XtPointer client_data, 1285 XtPointer call_data) 1286 { 1287 XtDestroyWidget(parent); 1288 } 1289 1290 /* 1291 * Given a \0 terminated string and a position, advance the position 1292 * by n lines, where line separators (for now) are \n. If the end of 1293 * string is reached before n lines, return the number of lines advanced, 1294 * else normally return -1. 1295 */ 1296 static int moveAheadNLines( char *str, int *pos, int n ) { 1297 int i=n; 1298 while (str[*pos] != '\0' && n>0) { 1299 if (str[*pos] == '\n') 1300 --n; 1301 ++(*pos); 1302 } 1303 if (n==0) 1304 return -1; 1305 else 1306 return i-n; 1307 } 1308 1309 /* 1310 ** Show the calltip specified by tagFiles[i], tagSearch[i], tagPosInf[i] 1311 ** This reads from either a source code file (if searchMode == TIP_FROM_TAG) 1312 ** or a calltips file (if searchMode == TIP). 1313 */ 1314 static void showMatchingCalltip( Widget parent, int i ) 1315 { 1316 int startPos=0, fileLen, readLen, tipLen; 1317 int endPos=0; 1318 char *fileString; 1319 FILE *fp; 1320 struct stat statbuf; 1321 char *message; 1322 1323 /* 1. Open the target file */ 1324 NormalizePathname(tagFiles[i]); 1325 fp = fopen(tagFiles[i], "r"); 1326 if (fp == NULL) { 1327 DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s", 1328 "OK", tagFiles[i]); 1329 return; 1330 } 1331 if (fstat(fileno(fp), &statbuf) != 0) { 1332 fclose(fp); 1333 DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s", 1334 "OK", tagFiles[i]); 1335 return; 1336 } 1337 1338 /* 2. Read the target file */ 1339 /* Allocate space for the whole contents of the file (unfortunately) */ 1340 fileLen = statbuf.st_size; 1341 fileString = (char*)NEditMalloc(fileLen+1); /* +1 = space for null */ 1342 if (fileString == NULL) { 1343 fclose(fp); 1344 DialogF(DF_ERR, parent, 1, "File too large", 1345 "File is too large to load", "OK"); 1346 return; 1347 } 1348 1349 /* Read the file into fileString and terminate with a null */ 1350 readLen = fread(fileString, sizeof(char), fileLen, fp); 1351 if (ferror(fp)) { 1352 fclose(fp); 1353 DialogF(DF_ERR, parent, 1, "Error reading File", "Error reading %s", 1354 "OK", tagFiles[i]); 1355 NEditFree(fileString); 1356 return; 1357 } 1358 fileString[readLen] = 0; 1359 1360 /* Close the file */ 1361 if (fclose(fp) != 0) { 1362 /* unlikely error */ 1363 DialogF(DF_WARN, parent, 1, "Error closing File", 1364 "Unable to close file", "OK"); 1365 /* we read it successfully, so continue */ 1366 } 1367 1368 /* 3. Search for the tagged location (set startPos) */ 1369 if (!*(tagSearch[i])) { 1370 /* It's a line number, just go for it */ 1371 if ((moveAheadNLines( fileString, &startPos, tagPosInf[i]-1 )) >= 0) { 1372 DialogF(DF_ERR, parent, 1, "Tags Error", 1373 "%s\n not long enough for definition to be on line %d", 1374 "OK", tagFiles[i], tagPosInf[i]); 1375 NEditFree(fileString); 1376 return; 1377 } 1378 } else { 1379 startPos = tagPosInf[i]; 1380 if(!fakeRegExSearch(WidgetToWindow(parent), fileString, tagSearch[i], 1381 &startPos, &endPos)){ 1382 DialogF(DF_WARN, parent, 1, "Tag not found", 1383 "Definition for %s\nnot found in %s", "OK", tagName, 1384 tagFiles[i]); 1385 NEditFree(fileString); 1386 return; 1387 } 1388 } 1389 1390 if (searchMode == TIP) { 1391 int dummy, found; 1392 1393 /* 4. Find the end of the calltip (delimited by an empty line) */ 1394 endPos = startPos; 1395 found = SearchString(fileString, "\\n\\s*\\n", SEARCH_FORWARD, 1396 SEARCH_REGEX, False, startPos, &endPos, &dummy, NULL, 1397 NULL, NULL); 1398 if (!found) { 1399 /* Just take 4 lines */ 1400 moveAheadNLines( fileString, &endPos, TIP_DEFAULT_LINES ); 1401 --endPos; /* Lose the last \n */ 1402 } 1403 } else { /* Mode = TIP_FROM_TAG */ 1404 /* 4. Copy TIP_DEFAULT_LINES lines of text to the calltip string */ 1405 endPos = startPos; 1406 moveAheadNLines( fileString, &endPos, TIP_DEFAULT_LINES ); 1407 /* Make sure not to overrun the fileString with ". . ." */ 1408 if (((size_t) endPos) <= (strlen(fileString)-5)) { 1409 sprintf( &fileString[endPos], ". . ." ); 1410 endPos += 5; 1411 } 1412 } 1413 /* 5. Copy the calltip to a string */ 1414 tipLen = endPos - startPos; 1415 message = (char*)malloc(tipLen+1); /* +1 = space for null */ 1416 if (message == NULL) 1417 { 1418 DialogF(DF_ERR, parent, 1, "Out of Memory", 1419 "Can''t allocate memory for calltip message", "OK"); 1420 NEditFree(fileString); 1421 return; 1422 } 1423 strncpy( message, &fileString[startPos], tipLen ); 1424 message[tipLen] = 0; 1425 1426 /* 6. Display it */ 1427 tagsShowCalltip( WidgetToWindow(parent), message ); 1428 NEditFree(message); 1429 NEditFree(fileString); 1430 } 1431 1432 /* Open a new (or existing) editor window to the location specified in 1433 tagFiles[i], tagSearch[i], tagPosInf[i] */ 1434 static void editTaggedLocation( Widget parent, int i ) 1435 { 1436 /* Globals: tagSearch, tagPosInf, tagFiles, tagName, textNrows, 1437 WindowList */ 1438 int startPos, endPos, lineNum, rows; 1439 char filename[MAXPATHLEN], pathname[MAXPATHLEN]; 1440 WindowInfo *windowToSearch; 1441 WindowInfo *parentWindow = WidgetToWindow(parent); 1442 1443 ParseFilename(tagFiles[i],filename,pathname); 1444 /* open the file containing the definition */ 1445 EditExistingFile(parentWindow, filename, pathname, NULL, NULL, 0, NULL, 1446 False, NULL, GetPrefOpenInTab(), False); 1447 windowToSearch = FindWindowWithFile(filename, pathname); 1448 if (windowToSearch == NULL) { 1449 DialogF(DF_WARN, parent, 1, "File not found", "File %s not found", "OK", 1450 tagFiles[i]); 1451 return; 1452 } 1453 1454 startPos=tagPosInf[i]; 1455 1456 if(!*(tagSearch[i])) { 1457 /* if the search string is empty, select the numbered line */ 1458 SelectNumberedLine(windowToSearch, startPos); 1459 return; 1460 } 1461 1462 /* search for the tags file search string in the newly opened file */ 1463 if(!fakeRegExSearch(windowToSearch, NULL, tagSearch[i], &startPos, 1464 &endPos)){ 1465 DialogF(DF_WARN, windowToSearch->shell, 1, "Tag Error", 1466 "Definition for %s\nnot found in %s", "OK", tagName, 1467 tagFiles[i]); 1468 return; 1469 } 1470 1471 /* select the matched string */ 1472 BufSelect(windowToSearch->buffer, startPos, endPos); 1473 RaiseFocusDocumentWindow(windowToSearch, True); 1474 1475 /* Position it nicely in the window, 1476 about 1/4 of the way down from the top */ 1477 lineNum = BufCountLines(windowToSearch->buffer, 0, startPos); 1478 XtVaGetValues(windowToSearch->lastFocus, textNrows, &rows, NULL); 1479 TextSetScroll(windowToSearch->lastFocus, lineNum - rows/4, 0); 1480 TextSetCursorPos(windowToSearch->lastFocus, endPos); 1481 } 1482 1483 /* Create a Menu for user to select from the collided tags */ 1484 static Widget createSelectMenu(Widget parent, char *label, int nArgs, 1485 char *args[]) 1486 { 1487 int i; 1488 char tmpStr[100]; 1489 Widget menu; 1490 XmStringTable list; 1491 XmString popupTitle; 1492 int ac; 1493 Arg csdargs[20]; 1494 1495 list = (XmStringTable) NEditMalloc(nArgs * sizeof(XmString *)); 1496 for (i=0; i<nArgs; i++) 1497 list[i] = XmStringCreateSimple(args[i]); 1498 sprintf(tmpStr,"Select File With TAG: %s",tagName); 1499 popupTitle = XmStringCreateSimple(tmpStr); 1500 ac = 0; 1501 XtSetArg(csdargs[ac], XmNlistLabelString, popupTitle); ac++; 1502 XtSetArg(csdargs[ac], XmNlistItems, list); ac++; 1503 XtSetArg(csdargs[ac], XmNlistItemCount, nArgs); ac++; 1504 XtSetArg(csdargs[ac], XmNvisibleItemCount, 12); ac++; 1505 XtSetArg(csdargs[ac], XmNautoUnmanage, False); ac++; 1506 menu = CreateSelectionDialog(parent,label,csdargs,ac); 1507 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_TEXT)); 1508 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_HELP_BUTTON)); 1509 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_SELECTION_LABEL)); 1510 XtAddCallback(menu, XmNokCallback, (XtCallbackProc)findAllCB, menu); 1511 XtAddCallback(menu, XmNapplyCallback, (XtCallbackProc)findAllCB, menu); 1512 XtAddCallback(menu, XmNcancelCallback, (XtCallbackProc)findAllCB, menu); 1513 AddMotifCloseCallback(XtParent(menu), findAllCloseCB, NULL); 1514 for (i=0; i<nArgs; i++) 1515 XmStringFree(list[i]); 1516 NEditFree(list); 1517 XmStringFree(popupTitle); 1518 ManageDialogCenteredOnPointer(menu); 1519 return menu; 1520 } 1521 1522 1523 1524 /******************************************************************** 1525 * Functions for loading Calltips files * 1526 ********************************************************************/ 1527 1528 enum tftoken_types { TF_EOF, TF_BLOCK, TF_VERSION, TF_INCLUDE, TF_LANGUAGE, 1529 TF_ALIAS, TF_ERROR, TF_ERROR_EOF }; 1530 1531 /* A wrapper for SearchString */ 1532 static int searchLine(char *line, const char *regex) { 1533 int dummy1, dummy2; 1534 return SearchString(line, regex, SEARCH_FORWARD, SEARCH_REGEX, 1535 False, 0, &dummy1, &dummy2, NULL, NULL, NULL); 1536 } 1537 1538 /* Check if a line has non-ws characters */ 1539 static Boolean lineEmpty(const char *line) { 1540 while (*line && *line != '\n') { 1541 if (*line != ' ' && *line != '\t') 1542 return False; 1543 ++line; 1544 } 1545 return True; 1546 } 1547 1548 /* Remove trailing whitespace from a line */ 1549 static void rstrip( char *dst, const char *src ) { 1550 int wsStart, dummy2; 1551 /* Strip trailing whitespace */ 1552 if(SearchString(src, "\\s*\\n", SEARCH_FORWARD, SEARCH_REGEX, 1553 False, 0, &wsStart, &dummy2, NULL, NULL, NULL)) { 1554 if(dst != src) 1555 memcpy(dst, src, wsStart); 1556 dst[wsStart] = 0; 1557 } else 1558 if(dst != src) 1559 strcpy(dst, src); 1560 } 1561 1562 /* 1563 ** Get the next block from a tips file. A block is a \n\n+ delimited set of 1564 ** lines in a calltips file. All of the parameters except <fp> are return 1565 ** values, and most have different roles depending on the type of block 1566 ** that is found. 1567 ** header: Depends on the block type 1568 ** body: Depends on the block type. Used to return a new 1569 ** dynamically allocated string. 1570 ** blkLine: Returns the line number of the first line of the block 1571 ** after the "* xxxx *" line. 1572 ** currLine: Used to keep track of the current line in the file. 1573 */ 1574 static int nextTFBlock(FILE *fp, char *header, char **body, int *blkLine, 1575 int *currLine) 1576 { 1577 /* These are the different kinds of tokens */ 1578 const char *commenTF_regex = "^\\s*\\* comment \\*\\s*$"; 1579 const char *version_regex = "^\\s*\\* version \\*\\s*$"; 1580 const char *include_regex = "^\\s*\\* include \\*\\s*$"; 1581 const char *language_regex = "^\\s*\\* language \\*\\s*$"; 1582 const char *alias_regex = "^\\s*\\* alias \\*\\s*$"; 1583 char line[MAXLINE], *status; 1584 int dummy1; 1585 int code; 1586 1587 /* Skip blank lines and comments */ 1588 while(1) { 1589 /* Skip blank lines */ 1590 while((status=fgets(line, MAXLINE, fp))) { 1591 ++(*currLine); 1592 if(!lineEmpty( line )) 1593 break; 1594 } 1595 1596 /* Check for error or EOF */ 1597 if(!status) 1598 return TF_EOF; 1599 1600 /* We've got a non-blank line -- is it a comment block? */ 1601 if( !searchLine(line, commenTF_regex) ) 1602 break; 1603 1604 /* Skip the comment (non-blank lines) */ 1605 while((status=fgets(line, MAXLINE, fp))) { 1606 ++(*currLine); 1607 if(lineEmpty( line )) 1608 break; 1609 } 1610 1611 if(!status) 1612 return TF_EOF; 1613 } 1614 1615 /* Now we know it's a meaningful block */ 1616 dummy1 = searchLine(line, include_regex); 1617 if( dummy1 || searchLine(line, alias_regex) ) { 1618 /* INCLUDE or ALIAS block */ 1619 int incLen, incPos, i, incLines; 1620 1621 /* fprintf(stderr, "Starting include/alias at line %i\n", *currLine); */ 1622 if(dummy1) 1623 code = TF_INCLUDE; 1624 else { 1625 code = TF_ALIAS; 1626 /* Need to read the header line for an alias */ 1627 status=fgets(line, MAXLINE, fp); 1628 ++(*currLine); 1629 if (!status) 1630 return TF_ERROR_EOF; 1631 if (lineEmpty( line )) { 1632 fprintf( stderr, "xnedit: Warning: empty ''* alias *'' " 1633 "block in calltips file.\n" ); 1634 return TF_ERROR; 1635 } 1636 rstrip(header, line); 1637 } 1638 incPos = ftell(fp); 1639 *blkLine = *currLine + 1; /* Line of first actual filename/alias */ 1640 if (incPos < 0) 1641 return TF_ERROR; 1642 /* Figure out how long the block is */ 1643 while((status=fgets(line, MAXLINE, fp)) || feof(fp)) { 1644 ++(*currLine); 1645 if(feof(fp) || lineEmpty( line )) 1646 break; 1647 } 1648 incLen = ftell(fp) - incPos; 1649 incLines = *currLine - *blkLine; 1650 /* Correct currLine for the empty line it read at the end */ 1651 --(*currLine); 1652 if (incLines == 0) { 1653 fprintf( stderr, "xnedit: Warning: empty ''* include *'' or" 1654 " ''* alias *'' block in calltips file.\n" ); 1655 return TF_ERROR; 1656 } 1657 /* Make space for the filenames/alias sources */ 1658 *body = (char *)NEditMalloc(incLen+1); 1659 if (!*body) 1660 return TF_ERROR; 1661 *body[0]=0; 1662 if (fseek(fp, incPos, SEEK_SET) != 0) { 1663 NEditFree(*body); 1664 return TF_ERROR; 1665 } 1666 /* Read all the lines in the block */ 1667 /* fprintf(stderr, "Copying lines\n"); */ 1668 for (i=0; i<incLines; i++) { 1669 status = fgets(line, MAXLINE, fp); 1670 if (!status) { 1671 NEditFree(*body); 1672 return TF_ERROR_EOF; 1673 } 1674 rstrip(line,line); 1675 if(i) 1676 strcat(*body, ":"); 1677 strcat(*body, line); 1678 } 1679 /* fprintf(stderr, "Finished include/alias at line %i\n", *currLine); */ 1680 } 1681 1682 else if( searchLine(line, language_regex) ) { 1683 /* LANGUAGE block */ 1684 status=fgets(line, MAXLINE, fp); 1685 ++(*currLine); 1686 if (!status) 1687 return TF_ERROR_EOF; 1688 if (lineEmpty( line )) { 1689 fprintf( stderr, "xnedit: Warning: empty ''* language *'' block in calltips file.\n" ); 1690 return TF_ERROR; 1691 } 1692 *blkLine = *currLine; 1693 rstrip(header, line); 1694 code = TF_LANGUAGE; 1695 } 1696 1697 else if( searchLine(line, version_regex) ) { 1698 /* VERSION block */ 1699 status=fgets(line, MAXLINE, fp); 1700 ++(*currLine); 1701 if (!status) 1702 return TF_ERROR_EOF; 1703 if (lineEmpty( line )) { 1704 fprintf( stderr, "xnedit: Warning: empty ''* version *'' block in calltips file.\n" ); 1705 return TF_ERROR; 1706 } 1707 *blkLine = *currLine; 1708 rstrip(header, line); 1709 code = TF_VERSION; 1710 } 1711 1712 else { 1713 /* Calltip block */ 1714 /* The first line is the key, the rest is the tip. 1715 Strip trailing whitespace. */ 1716 rstrip(header, line); 1717 1718 status=fgets(line, MAXLINE, fp); 1719 ++(*currLine); 1720 if (!status) 1721 return TF_ERROR_EOF; 1722 if (lineEmpty( line )) { 1723 fprintf( stderr, "xnedit: Warning: empty calltip block:\n" 1724 " \"%s\"\n", header); 1725 return TF_ERROR; 1726 } 1727 *blkLine = *currLine; 1728 *body = strdup(line); 1729 code = TF_BLOCK; 1730 } 1731 1732 /* Skip the rest of the block */ 1733 dummy1 = *currLine; 1734 while(fgets(line, MAXLINE, fp)) { 1735 ++(*currLine); 1736 if (lineEmpty( line )) 1737 break; 1738 } 1739 1740 /* Warn about any unneeded extra lines (which are ignored). */ 1741 if (dummy1+1 < *currLine && code != TF_BLOCK) { 1742 fprintf( stderr, "xnedit: Warning: extra lines in language or version block ignored.\n" ); 1743 } 1744 1745 return code; 1746 } 1747 1748 /* A struct for describing a calltip alias */ 1749 typedef struct _alias { 1750 char *dest; 1751 char *sources; 1752 struct _alias *next; 1753 } tf_alias; 1754 1755 /* 1756 ** Allocate a new alias, copying dest and stealing sources. This may 1757 ** seem strange but that's the way it's called 1758 */ 1759 static tf_alias *new_alias(const char *dest, char *sources) { 1760 tf_alias *alias; 1761 1762 /* fprintf(stderr, "new_alias: %s <- %s\n", dest, sources); */ 1763 /* Allocate the alias */ 1764 alias = (tf_alias *)NEditMalloc( sizeof(tf_alias) ); 1765 if(!alias) 1766 return NULL; 1767 1768 /* Fill it in */ 1769 alias->dest = (char*)NEditMalloc( strlen(dest)+1 ); 1770 if(!(alias->dest)) 1771 return NULL; 1772 strcpy( alias->dest, dest ); 1773 alias->sources = sources; 1774 return alias; 1775 } 1776 1777 /* Deallocate a linked-list of aliases */ 1778 static void free_alias_list(tf_alias *alias) { 1779 tf_alias *tmp_alias; 1780 while(alias) { 1781 tmp_alias = alias->next; 1782 NEditFree(alias->dest); 1783 NEditFree(alias->sources); 1784 NEditFree(alias); 1785 alias = tmp_alias; 1786 } 1787 } 1788 1789 /* 1790 ** Load a calltips file and insert all of the entries into the global tips 1791 ** database. Each tip is essentially stored as its filename and the line 1792 ** at which it appears--the exact same way ctags indexes source-code. That's 1793 ** why calltips and tags share so much code. 1794 */ 1795 static int loadTipsFile(const char *tipsFile, int index, int recLevel) 1796 { 1797 FILE *fp = NULL; 1798 char header[MAXLINE]; 1799 char *body, *tipIncFile; 1800 char tipPath[MAXPATHLEN]; 1801 char resolvedTipsFile[MAXPATHLEN+1]; 1802 int nTipsAdded=0, langMode = PLAIN_LANGUAGE_MODE, oldLangMode; 1803 int currLine=0, code, blkLine; 1804 tf_alias *aliases=NULL, *tmp_alias; 1805 1806 if(recLevel > MAX_TAG_INCLUDE_RECURSION_LEVEL) { 1807 fprintf(stderr, "xnedit: Warning: Reached recursion limit before loading calltips file:\n\t%s\n", tipsFile); 1808 return 0; 1809 } 1810 1811 /* find the tips file */ 1812 #ifndef VMS 1813 /* Allow ~ in Unix filenames */ 1814 strncpy(tipPath, tipsFile, MAXPATHLEN); /* ExpandTilde is destructive */ 1815 tipPath[MAXPATHLEN - 1] = '\0'; 1816 ExpandTilde(tipPath); 1817 if(!ResolvePath(tipPath, resolvedTipsFile)) 1818 return 0; 1819 #else 1820 if(!ResolvePath(tipsFile, resolvedTipsFile)) 1821 return 0; 1822 #endif 1823 1824 /* Get the path to the tips file */ 1825 ParseFilename(resolvedTipsFile, NULL, tipPath); 1826 1827 /* Open the file */ 1828 if ((fp = fopen(resolvedTipsFile, "r")) == NULL) 1829 return 0; 1830 1831 while( 1 ) { 1832 code = nextTFBlock(fp, header, &body, &blkLine, &currLine); 1833 if( code == TF_ERROR_EOF ) { 1834 fprintf(stderr,"xnedit: Warning: unexpected EOF in calltips file.\n"); 1835 break; 1836 } 1837 if( code == TF_EOF ) 1838 break; 1839 1840 switch (code) { 1841 case TF_BLOCK: 1842 /* Add the calltip to the global hash table. 1843 For the moment I'm just using line numbers because I don't 1844 want to have to deal with adding escape characters for 1845 regex metacharacters that might appear in the string */ 1846 nTipsAdded += addTag(header, resolvedTipsFile, langMode, "", 1847 blkLine, tipPath, index); 1848 NEditFree( body ); 1849 break; 1850 case TF_INCLUDE: 1851 /* nextTFBlock returns a colon-separated list of tips files 1852 in body */ 1853 for(tipIncFile=strtok(body,":"); tipIncFile; 1854 tipIncFile=strtok(NULL,":")) { 1855 /* fprintf(stderr, 1856 "xnedit: DEBUG: including tips file '%s'\n", 1857 tipIncFile); */ 1858 nTipsAdded += loadTipsFile( tipIncFile, index, recLevel+1); 1859 } 1860 NEditFree( body ); 1861 break; 1862 case TF_LANGUAGE: 1863 /* Switch to the new language mode if it's valid, else ignore 1864 it. */ 1865 oldLangMode = langMode; 1866 langMode = FindLanguageMode( header ); 1867 if (langMode == PLAIN_LANGUAGE_MODE && 1868 strcmp(header, "Plain")) { 1869 fprintf(stderr, 1870 "xnedit: Error reading calltips file:\n\t%s\n" 1871 "Unknown language mode: \"%s\"\n", 1872 tipsFile, header); 1873 langMode = oldLangMode; 1874 } 1875 break; 1876 case TF_ERROR: 1877 fprintf(stderr,"xnedit: Warning: Recoverable error while " 1878 "reading calltips file:\n \"%s\"\n", 1879 resolvedTipsFile); 1880 break; 1881 case TF_ALIAS: 1882 /* Allocate a new alias struct */ 1883 tmp_alias = aliases; 1884 aliases = new_alias(header, body); 1885 if( !aliases ) { 1886 fprintf(stderr,"xnedit: Can''t allocate memory for tipfile " 1887 "alias in calltips file:\n \"%s\"\n", 1888 resolvedTipsFile); 1889 /* Deallocate any allocated aliases */ 1890 free_alias_list(tmp_alias); 1891 fclose(fp); 1892 return 0; 1893 } 1894 /* Add it to the list */ 1895 aliases->next = tmp_alias; 1896 break; 1897 default: 1898 ;/* Ignore TF_VERSION for now */ 1899 } 1900 } 1901 1902 /* Now resolve any aliases */ 1903 tmp_alias = aliases; 1904 while (tmp_alias) { 1905 tag *t; 1906 char *src; 1907 t = getTag(tmp_alias->dest, TIP); 1908 if (!t) { 1909 fprintf(stderr, "xnedit: Can''t find destination of alias \"%s\"\n" 1910 " in calltips file:\n \"%s\"\n", 1911 tmp_alias->dest, resolvedTipsFile); 1912 } else { 1913 for(src=strtok(tmp_alias->sources,":"); src; src=strtok(NULL,":")) 1914 addTag(src, resolvedTipsFile, t->language, "", t->posInf, 1915 tipPath, index); 1916 } 1917 tmp_alias = tmp_alias->next; 1918 } 1919 free_alias_list(aliases); 1920 fclose(fp); 1921 1922 return nTipsAdded; 1923 } 1924