Merged patch #734202: Warnings Removal (Thorsten).
[nedit.git] / source / tags.c
blob9ced0676b05222c92813c7be79cf4cb86bb4ac5c
1 static const char CVSID[] = "$Id: tags.c,v 1.51 2003/05/09 17:43:48 edg Exp $";
2 /*******************************************************************************
3 * *
4 * tags.c -- Nirvana editor tag file handling *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
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 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
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"
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <ctype.h>
52 #include <sys/types.h>
53 #include <sys/stat.h>
54 #include <unistd.h>
55 #ifdef VMS
56 #include "../util/VMSparam.h"
57 #else
58 #ifndef __MVS__
59 #include <sys/param.h>
60 #endif
61 #endif /*VMS*/
63 #include <Xm/PrimitiveP.h> /* For Calltips */
64 #include <Xm/Xm.h>
65 #include <Xm/SelectioB.h>
66 #include <X11/Xatom.h>
68 #ifdef HAVE_DEBUG_H
69 #include "../debug.h"
70 #endif
72 #define MAXLINE 2048
73 #define MAX_TAG_LEN 256
74 #define MAXDUPTAGS 100
75 #define MAX_TAG_INCLUDE_RECURSION_LEVEL 5
77 /* Take this many lines when making a tip from a tag.
78 (should probably be a language-dependent option, but...) */
79 #define TIP_DEFAULT_LINES 4
81 #define STRSAVE(a) ((a!=NULL)?strcpy(malloc(strlen(a)+1),(a)):strcpy(malloc(1),""))
83 typedef struct _tag {
84 struct _tag *next;
85 const char *path;
86 const char *name;
87 const char *file;
88 int language;
89 const char *searchString; /* see comment below */
90 int posInf; /* see comment below */
91 short index;
92 } tag;
95 ** contents of tag->searchString | tag->posInf
96 ** ctags, line num specified: "" | line num
97 ** ctags, search expr specfd: ctags search expr | -1
98 ** etags (emacs tags) etags search string | search start pos
101 enum searchDirection {FORWARD, BACKWARD};
103 static int loadTagsFile(const char *tagSpec, int index, int recLevel);
104 static void findDefCB(Widget widget, WindowInfo *window, Atom *sel,
105 Atom *type, char *value, int *length, int *format);
106 static void setTag(tag *t, const char *name, const char *file,
107 int language, const char *searchString, int posInf,
108 const char * tag);
109 static int fakeRegExSearch(WindowInfo *window, char *buffer,
110 const char *searchString, int *startPos, int *endPos);
111 static unsigned hashAddr(const char *key);
112 static void updateMenuItems();
113 static int addTag(const char *name, const char *file, int lang,
114 const char *search, int posInf, const char *path,
115 int index);
116 static int delTag(const char *name, const char *file, int lang,
117 const char *search, int posInf, int index);
118 static tag *getTag(const char *name, int search_type);
119 static int findDef(WindowInfo *window, const char *value, int search_type);
120 static int findAllMatches(WindowInfo *window, const char *string);
121 static void findAllCB(Widget parent, XtPointer client_data, XtPointer call_data);
122 static Widget createSelectMenu(Widget parent, char *label, int nArgs,
123 char *args[]);
124 static void editTaggedLocation( Widget parent, int i );
125 static void showMatchingCalltip( Widget parent, int i );
127 static const char *rcs_strdup(const char *str);
128 static void rcs_free(const char *str);
129 static int searchLine(char *line, const char *regex);
130 static void rstrip( char *dst, const char *src );
131 static int nextTFBlock(FILE *fp, char *header, char **tiptext, int *lineAt,
132 int *lineNo);
133 static int loadTipsFile(const char *tipsFile, int index, int recLevel);
135 /* Hash table of tags, implemented as an array. Each bin contains a
136 NULL-terminated linked list of parsed tags */
137 static tag **Tags = NULL;
138 static int DefTagHashSize = 10000;
139 /* list of loaded tags files */
140 tagFile *TagsFileList = NULL;
142 /* Hash table of calltip tags */
143 static tag **Tips = NULL;
144 tagFile *TipsFileList = NULL;
147 /* These are all transient global variables -- they don't hold any state
148 between tag/tip lookups */
149 static int searchMode = TAG;
150 static const char *tagName;
151 static char tagFiles[MAXDUPTAGS][MAXPATHLEN];
152 static char tagSearch[MAXDUPTAGS][MAXPATHLEN];
153 static int tagPosInf[MAXDUPTAGS];
154 static Boolean globAnchored;
155 static int globPos;
156 static int globHAlign;
157 static int globVAlign;
158 static int globAlignMode;
160 /* A wrapper for calling TextDShowCalltip */
161 static int tagsShowCalltip( WindowInfo *window, char *text ) {
162 if (text)
163 return ShowCalltip( window, text, globAnchored, globPos, globHAlign,
164 globVAlign, globAlignMode);
165 else
166 return 0;
169 /* Set the head of the proper file list (Tags or Tips) to t */
170 static tagFile *setFileListHead(tagFile *t, int file_type )
172 if (file_type == TAG)
173 TagsFileList = t;
174 else
175 TipsFileList = t;
176 return t;
179 /* Compute hash address from a string key */
180 static unsigned hashAddr(const char *key)
182 unsigned s=strlen(key);
183 unsigned a=0,x=0,i;
185 for (i=0; (i+3)<s; i += 4) {
186 strncpy((char*)&a,&key[i],4);
187 x += a;
190 for (a=1; i<(s+1); i++, a *= 256)
191 x += key[i] * a;
193 return x;
196 /* Retrieve a tag structure from the hash table */
197 static tag *getTag(const char *name, int search_type)
199 static char lastName[MAXLINE];
200 static tag *t;
201 static int addr;
202 tag **table;
204 if (search_type == TIP)
205 table = Tips;
206 else
207 table = Tags;
209 if (table == NULL) return NULL;
211 if (name) {
212 addr = hashAddr(name) % DefTagHashSize;
213 t = table[addr];
214 strcpy(lastName,name);
216 else if (t) {
217 name = lastName;
218 t = t->next;
220 else return NULL;
222 for (;t; t = t->next)
223 if (!strcmp(name,t->name)) return t;
224 return NULL;
227 /* Add a tag specification to the hash table
228 ** Return Value: 0 ... tag already existing, spec not added
229 ** 1 ... tag spec is new, added.
230 ** (We don't return boolean as the return value is used as counter increment!)
233 static int addTag(const char *name, const char *file, int lang,
234 const char *search, int posInf, const char *path, int index)
236 int addr = hashAddr(name) % DefTagHashSize;
237 tag *t;
238 char newfile[MAXPATHLEN];
239 tag **table;
241 if (searchMode == TIP) {
242 if (Tips == NULL)
243 Tips = (tag **)calloc(DefTagHashSize, sizeof(tag*));
244 table = Tips;
245 } else {
246 if (Tags == NULL)
247 Tags = (tag **)calloc(DefTagHashSize, sizeof(tag*));
248 table = Tags;
251 if (*file == '/')
252 strcpy(newfile,file);
253 else
254 sprintf(newfile,"%s%s", path, file);
256 NormalizePathname(newfile);
257 CompressPathname(newfile);
259 for (t = table[addr]; t; t = t->next) {
260 if (strcmp(name,t->name)) continue;
261 if (lang != t->language) continue;
262 if (strcmp(search,t->searchString)) continue;
263 if (posInf != t->posInf) continue;
264 if (*t->file == '/' && strcmp(newfile,t->file)) continue;
265 if (*t->file != '/') {
266 char tmpfile[MAXPATHLEN];
267 sprintf(tmpfile, "%s%s", t->path, t->file);
268 NormalizePathname(tmpfile);
269 CompressPathname(tmpfile);
270 if (strcmp(newfile, tmpfile)) continue;
272 return 0;
275 t = (tag *) malloc(sizeof(tag));
276 setTag(t, name, file, lang, search, posInf, path);
277 t->index = index;
278 t->next = table[addr];
279 table[addr] = t;
280 return 1;
283 /* Delete a tag from the cache.
284 * Search is limited to valid matches of 'name','file', 'search', posInf, and 'index'.
285 * EX: delete all tags matching index 2 ==>
286 * delTag(tagname,NULL,-2,NULL,-2,2);
287 * (posInf = -2 is an invalid match, posInf range: -1 .. +MAXINT,
288 lang = -2 is also an invalid match)
290 static int delTag(const char *name, const char *file, int lang,
291 const char *search, int posInf, int index)
293 tag *t, *last;
294 int start,finish,i,del=0;
295 tag **table;
297 if (searchMode == TIP)
298 table = Tips;
299 else
300 table = Tags;
302 if (table == NULL) return FALSE;
303 if (name)
304 start = finish = hashAddr(name) % DefTagHashSize;
305 else {
306 start = 0;
307 finish = DefTagHashSize;
309 for (i = start; i<finish; i++) {
310 for (last = NULL, t = table[i]; t; last = t, t = t?t->next:table[i]) {
311 if (name && strcmp(name,t->name)) continue;
312 if (index && index != t->index) continue;
313 if (file && strcmp(file,t->file)) continue;
314 if (lang >= PLAIN_LANGUAGE_MODE && lang != t->language) continue;
315 if (search && strcmp(search,t->searchString)) continue;
316 if (posInf == t->posInf) continue;
317 if (last)
318 last->next = t->next;
319 else
320 table[i] = t->next;
321 rcs_free(t->name);
322 rcs_free(t->file);
323 rcs_free(t->searchString);
324 rcs_free(t->path);
325 free(t);
326 t = NULL;
327 del++;
330 return del>0;
333 /* used in AddRelTagsFile and AddTagsFile */
334 static int tagFileIndex = 0;
337 ** AddRelTagsFile(): Rescan tagSpec for relative tag file specs
338 ** (not starting with [/~]) and extend the tag files list if in
339 ** windowPath a tags file matching the relative spec has been found.
341 int AddRelTagsFile(const char *tagSpec, const char *windowPath, int file_type)
343 tagFile *t;
344 int added=0;
345 struct stat statbuf;
346 char *filename;
347 char pathName[MAXPATHLEN];
348 char *tmptagSpec;
349 tagFile *FileList;
351 searchMode = file_type;
352 if (searchMode == TAG)
353 FileList = TagsFileList;
354 else
355 FileList = TipsFileList;
357 tmptagSpec = (char *) malloc(strlen(tagSpec)+1);
358 strcpy(tmptagSpec, tagSpec);
359 for (filename = strtok(tmptagSpec, ":"); filename; filename = strtok(NULL, ":")){
360 if (*filename == '/' || *filename == '~')
361 continue;
362 if (windowPath && *windowPath) {
363 strcpy(pathName, windowPath);
364 } else {
365 strcpy(pathName, GetCurrentDir());
367 strcat(pathName, "/");
368 strcat(pathName, filename);
369 NormalizePathname(pathName);
370 CompressPathname(pathName);
372 for (t = FileList; t && strcmp(t->filename, pathName); t = t->next);
373 if (t) {
374 added=1;
375 continue;
377 if (stat(pathName, &statbuf) != 0)
378 continue;
379 t = (tagFile *) malloc(sizeof(tagFile));
380 t->filename = STRSAVE(pathName);
381 t->loaded = 0;
382 t->date = statbuf.st_mtime;
383 t->index = ++tagFileIndex;
384 t->next = FileList;
385 FileList = setFileListHead(t, file_type);
386 added=1;
388 free(tmptagSpec);
389 updateMenuItems();
390 if (added)
391 return TRUE;
392 else
393 return FALSE;
397 ** AddTagsFile(): Set up the the list of tag files to manage from a file spec.
398 ** The file spec comes from the X-Resource Nedit.tags: It can list multiple
399 ** tags files, specified by separating them with colons. The .Xdefaults would
400 ** look like this:
401 ** Nedit.tags: <tagfile1>:<tagfile2>
402 ** Returns True if all files were found in the FileList or loaded successfully,
403 ** FALSE otherwise.
405 int AddTagsFile(const char *tagSpec, int file_type)
407 tagFile *t;
408 int added=1;
409 struct stat statbuf;
410 char *filename;
411 char pathName[MAXPATHLEN];
412 char *tmptagSpec;
413 tagFile *FileList;
415 /* To prevent any possible segfault */
416 if (tagSpec == NULL) {
417 fprintf(stderr, "nedit: Internal Error!\n"
418 " Passed NULL pointer to AddTagsFile!\n");
419 return FALSE;
422 searchMode = file_type;
423 if (searchMode == TAG)
424 FileList = TagsFileList;
425 else
426 FileList = TipsFileList;
428 tmptagSpec = (char *) malloc(strlen(tagSpec)+1);
429 strcpy(tmptagSpec, tagSpec);
430 for (filename = strtok(tmptagSpec,":"); filename; filename = strtok(NULL,":")) {
431 if (*filename != '/') {
432 strcpy(pathName, GetCurrentDir());
433 strcat(pathName,"/");
434 strcat(pathName,filename);
435 } else {
436 strcpy(pathName,filename);
438 NormalizePathname(pathName);
439 CompressPathname(pathName);
441 for (t = FileList; t && strcmp(t->filename,pathName); t = t->next);
442 if (t) {
443 added=1;
444 continue;
446 if (stat(pathName,&statbuf) != 0) {
447 /* Problem reading this tags file. Return FALSE */
448 added = 0;
449 continue;
451 t = (tagFile *) malloc(sizeof(tagFile));
452 t->filename = STRSAVE(pathName);
453 t->loaded = 0;
454 t->date = statbuf.st_mtime;
455 t->index = ++tagFileIndex;
456 t->next = FileList;
457 FileList = setFileListHead(t, file_type );
459 free(tmptagSpec);
460 updateMenuItems();
461 if (added)
462 return TRUE;
463 else
464 return FALSE;
467 /* Un-manage a colon-delimited set of tags files
468 * Return TRUE if all files were found in the FileList and unloaded, FALSE
469 * if any file was not found in the FileList.
471 int DeleteTagsFile(const char *tagSpec, int file_type)
473 tagFile *t, *last;
474 tagFile *FileList;
475 char pathName[MAXPATHLEN], *tmptagSpec, *filename;
476 int removed;
478 /* To prevent any possible segfault */
479 if (tagSpec == NULL) {
480 fprintf(stderr, "nedit: Internal Error!\n"
481 " Passed NULL pointer to DeleteTagsFile!\n");
482 return FALSE;
485 searchMode = file_type;
486 if (searchMode == TAG)
487 FileList = TagsFileList;
488 else
489 FileList = TipsFileList;
491 tmptagSpec = (char *) malloc(strlen(tagSpec)+1);
492 strcpy(tmptagSpec, tagSpec);
493 removed=1;
494 for (filename = strtok(tmptagSpec,":"); filename;
495 filename = strtok(NULL,":")) {
496 if (*filename != '/') {
497 strcpy(pathName, GetCurrentDir());
498 strcat(pathName,"/");
499 strcat(pathName,filename);
500 } else {
501 strcpy(pathName,filename);
503 NormalizePathname(pathName);
504 CompressPathname(pathName);
506 for (last=NULL,t = FileList; t; last = t,t = t->next) {
507 if (strcmp(t->filename, pathName))
508 continue;
509 if (t->loaded)
510 delTag(NULL,NULL,-2,NULL,-2,t->index);
511 if (last) last->next = t->next;
512 else FileList = setFileListHead(t->next, file_type);
513 free(t->filename);
514 free(t);
515 updateMenuItems();
516 break;
518 /* If any file can't be removed, return false */
519 if (!t)
520 removed = 0;
522 if (removed)
523 return TRUE;
524 else
525 return FALSE;
529 ** Update the "Find Definition", "Unload Tags File", "Show Calltip",
530 ** and "Unload Calltips File" menu items in the existing windows.
532 static void updateMenuItems()
534 WindowInfo *w;
535 Boolean tipStat=FALSE, tagStat=FALSE;
537 if (TipsFileList) tipStat=TRUE;
538 if (TagsFileList) tagStat=TRUE;
540 for (w=WindowList; w!=NULL; w=w->next) {
541 XtSetSensitive(w->showTipItem, tipStat || tagStat);
542 XtSetSensitive(w->unloadTipsMenuItem, tipStat);
543 XtSetSensitive(w->findDefItem, tagStat);
544 XtSetSensitive(w->unloadTagsMenuItem, tagStat);
549 ** Scans one <line> from a ctags tags file (<index>) in tagPath.
550 ** Return value: Number of tag specs added.
552 static int scanCTagsLine(const char *line, const char *tagPath, int index)
554 char name[MAXLINE], searchString[MAXLINE];
555 char file[MAXPATHLEN];
556 char *posTagREEnd, *posTagRENull;
557 int nRead, pos;
559 nRead = sscanf(line, "%s\t%s\t%[^\n]", name, file, searchString);
560 if (nRead != 3)
561 return 0;
562 if ( *name == '!' )
563 return 0;
566 ** Guess the end of searchString:
567 ** Try to handle original ctags and exuberant ctags format:
569 if(searchString[0] == '/' || searchString[0] == '?') {
571 pos=-1; /* "search expr without pos info" */
573 /* Situations: /<ANY expr>/\0
574 ** ?<ANY expr>?\0 --> original ctags
575 ** /<ANY expr>/;" <flags>
576 ** ?<ANY expr>?;" <flags> --> exuberant ctags
578 posTagREEnd = strrchr(searchString, ';');
579 posTagRENull = strchr(searchString, 0);
580 if(!posTagREEnd || (posTagREEnd[1] != '"') ||
581 (posTagRENull[-1] == searchString[0])) {
582 /* -> original ctags format = exuberant ctags format 1 */
583 posTagREEnd = posTagRENull;
584 } else {
585 /* looks like exuberant ctags format 2 */
586 *posTagREEnd = 0;
590 ** Hide the last delimiter:
591 ** /<expression>/ becomes /<expression>
592 ** ?<expression>? becomes ?<expression>
593 ** This will save a little work in fakeRegExSearch.
595 if(posTagREEnd > (searchString+2)) {
596 posTagREEnd--;
597 if(searchString[0] == *posTagREEnd)
598 *posTagREEnd=0;
600 } else {
601 pos=atoi(searchString);
602 *searchString=0;
604 /* No ability to read language mode right now */
605 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos, tagPath,
606 index);
610 * Scans one <line> from an etags (emacs) tags file (<index>) in tagPath.
611 * recLevel = current recursion level for tags file including
612 * file = destination definition file. possibly modified. len=MAXPATHLEN!
613 * Return value: Number of tag specs added.
615 static int scanETagsLine(const char *line, const char * tagPath, int index,
616 char * file, int recLevel)
618 char name[MAXLINE], searchString[MAXLINE];
619 char incPath[MAXPATHLEN];
620 int pos, len;
621 char *posDEL, *posSOH, *posCOM;
623 /* check for destination file separator */
624 if(line[0]==12) { /* <np> */
625 *file=0;
626 return 0;
629 /* check for standard definition line */
630 posDEL=strchr(line, '\177');
631 posSOH=strchr(line, '\001');
632 posCOM=strrchr(line, ',');
633 if(*file && posDEL && (posSOH > posDEL) && (posCOM > posSOH)) {
634 /* exuberant ctags -e style */
635 len=Min(MAXLINE-1, posDEL - line);
636 strncpy(searchString, line, len);
637 searchString[len]=0;
638 len=Min(MAXLINE-1, (posSOH - posDEL) - 1);
639 strncpy(name, posDEL + 1, len);
640 name[len]=0;
641 pos=atoi(posCOM+1);
642 /* No ability to set language mode for the moment */
643 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos,
644 tagPath, index);
646 if (*file && posDEL && (posCOM > posDEL)) {
647 /* old etags style, part name<soh> is missing here! */
648 len=Min(MAXLINE-1, posDEL - line);
649 strncpy(searchString, line, len);
650 searchString[len]=0;
651 /* guess name: take the last alnum (plus _) part of searchString */
652 while(--len >= 0) {
653 if( isalnum((unsigned char)searchString[len]) ||
654 (searchString[len] == '_'))
655 break;
657 if(len<0)
658 return 0;
659 pos=len;
660 while (pos >= 0 && (isalnum((unsigned char)searchString[pos]) ||
661 (searchString[pos] == '_')))
662 pos--;
663 strncpy(name, searchString + pos + 1, len - pos);
664 name[len - pos] = 0; /* name ready */
665 pos=atoi(posCOM+1);
666 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos,
667 tagPath, index);
669 /* check for destination file spec */
670 if(*line && posCOM) {
671 len=Min(MAXPATHLEN-1, posCOM - line);
672 strncpy(file, line, len);
673 file[len]=0;
674 /* check if that's an include file ... */
675 if(!(strncmp(posCOM+1, "include", 7))) {
676 if(*file != '/') {
677 if((strlen(tagPath) + strlen(file)) >= MAXPATHLEN) {
678 fprintf(stderr, "tags.c: MAXPATHLEN overflow\n");
679 *file=0; /* invalidate */
680 return 0;
682 strcpy(incPath, tagPath);
683 strcat(incPath, file);
684 CompressPathname(incPath);
685 return(loadTagsFile(incPath, index, recLevel+1));
686 } else {
687 return(loadTagsFile(file, index, recLevel+1));
691 return 0;
694 /* Tag File Type */
695 typedef enum {
696 TFT_CHECK, TFT_ETAGS, TFT_CTAGS
697 } TFT;
700 ** Loads tagsFile into the hash table.
701 ** Returns the number of added tag specifications.
703 static int loadTagsFile(const char *tagsFile, int index, int recLevel)
705 FILE *fp = NULL;
706 char line[MAXLINE];
707 char file[MAXPATHLEN], tagPath[MAXPATHLEN];
708 char resolvedTagsFile[MAXPATHLEN+1];
709 int nTagsAdded=0;
710 int tagFileType = TFT_CHECK;
712 if(recLevel > MAX_TAG_INCLUDE_RECURSION_LEVEL) {
713 return 0;
715 /* the path of the tags file must be resolved to find the right files:
716 * definition source files are (in most cases) specified relatively inside
717 * the tags file to the tags files directory.
719 if(!ResolvePath(tagsFile, resolvedTagsFile)) {
720 return 0;
723 /* Open the file */
724 if ((fp = fopen(resolvedTagsFile, "r")) == NULL) {
725 return 0;
728 ParseFilename(resolvedTagsFile, NULL, tagPath);
730 /* Read the file and store its contents */
731 while (fgets(line, MAXLINE, fp)) {
733 /* This might take a while if you have a huge tags file (like I do)..
734 keep the windows up to date and post a busy cursor so the user
735 doesn't think we died. */
737 AllWindowsBusy("Loading tags file...");
739 /* the first character in the file decides if the file is treat as
740 etags or ctags file.
742 if(tagFileType==TFT_CHECK) {
743 if(line[0]==12) /* <np> */
744 tagFileType=TFT_ETAGS;
745 else
746 tagFileType=TFT_CTAGS;
748 if(tagFileType==TFT_CTAGS) {
749 nTagsAdded += scanCTagsLine(line, tagPath, index);
750 } else {
751 nTagsAdded += scanETagsLine(line, tagPath, index, file, recLevel);
754 fclose(fp);
756 AllWindowsUnbusy();
757 return nTagsAdded;
761 ** Given a tag name, lookup the file and path of the definition
762 ** and the proper search string. Returned strings are pointers
763 ** to internal storage which are valid until the next loadTagsFile call.
765 ** Invocation with name != NULL (containing the searched definition)
766 ** --> returns first definition of name
767 ** Successive invocation with name == NULL
768 ** --> returns further definitions (resulting from multiple tags files)
770 ** Return Value: TRUE: tag spec found
771 ** FALSE: no (more) definitions found.
773 #define TAG_STS_ERR_FMT "NEdit: Error getting status for tag file %s\n"
774 int LookupTag(const char *name, const char **file, int *language,
775 const char **searchString, int * pos, const char **path,
776 int search_type)
778 tag *t;
779 tagFile *tf;
780 struct stat statbuf;
781 tagFile *FileList;
782 int load_status;
784 searchMode = search_type;
785 if (searchMode == TIP)
786 FileList = TipsFileList;
787 else
788 FileList = TagsFileList;
791 ** Go through the list of all tags Files:
792 ** - load them (if not already loaded)
793 ** - check for update of the tags file and reload it in that case
794 ** - save the modification date of the tags file
796 ** Do this only as long as name != NULL, not for sucessive calls
797 ** to find multiple tags specs.
800 for (tf = FileList; tf && name; tf = tf->next) {
801 if (tf->loaded) {
802 if (stat(tf->filename,&statbuf) != 0) { /* */
803 fprintf(stderr, TAG_STS_ERR_FMT, tf->filename);
804 } else {
805 if (tf->date == statbuf.st_mtime) {
806 /* current tags file tf is already loaded and up to date */
807 continue;
810 /* tags file has been modified, delete it's entries and reload it */
811 delTag(NULL,NULL,-2,NULL,-2,tf->index);
813 /* If we get here we have to try to (re-) load the tags file */
814 if (FileList == TipsFileList)
815 load_status = loadTipsFile(tf->filename, tf->index, 0);
816 else
817 load_status = loadTagsFile(tf->filename, tf->index, 0);
818 if(load_status) {
819 if (stat(tf->filename,&statbuf) != 0) {
820 if(!tf->loaded) {
821 /* if tf->loaded == 1 we already have seen the error msg */
822 fprintf(stderr, TAG_STS_ERR_FMT, tf->filename);
824 } else {
825 tf->date = statbuf.st_mtime;
827 tf->loaded = 1;
828 } else {
829 tf->loaded = 0;
833 t = getTag(name, search_type);
835 if (!t) {
836 return FALSE;
837 } else {
838 *file = t->file;
839 *language = t->language;
840 *searchString = t->searchString;
841 *pos = t->posInf;
842 *path = t->path;
843 return TRUE;
848 ** This code path is followed if the request came from either
849 ** FindDefinition or FindDefCalltip. This should probably be refactored.
851 static int findDef(WindowInfo *window, const char *value, int search_type) {
852 static char tagText[MAX_TAG_LEN + 1];
853 const char *p;
854 char message[MAX_TAG_LEN+40];
855 int l, ml, status = 0;
857 searchMode = search_type;
858 l = strlen(value);
859 if (l <= MAX_TAG_LEN) {
860 /* should be of type text??? */
861 for (p = value; *p && isascii(*p); p++) {
863 if (!(*p)) {
864 ml = ((l < MAX_TAG_LEN) ? (l) : (MAX_TAG_LEN));
865 strncpy(tagText, value, ml);
866 tagText[ml] = '\0';
867 /* See if we can find the tip/tag */
868 status = findAllMatches(window, tagText);
869 /* If we didn't find a requested calltip, see if we can use a tag */
870 if (status == 0 && search_type == TIP && TagsFileList != NULL) {
871 searchMode = TIP_FROM_TAG;
872 status = findAllMatches(window, tagText);
874 if (status == 0) {
875 /* Didn't find any matches */
876 if (searchMode == TIP_FROM_TAG || searchMode == TIP) {
877 sprintf(message, "No match for \"%s\" in calltips or tags.",
878 tagName);
879 tagsShowCalltip( window, message );
880 } else
882 DialogF(DF_WARN, window->textArea, 1, "Tags",
883 "\"%s\" not found in tags file%s", "OK", tagName,
884 (TagsFileList && TagsFileList->next) ? "s" : "");
888 else {
889 fprintf(stderr, "NEdit: Can't handle non 8-bit text\n");
890 XBell(TheDisplay, 0);
893 else {
894 fprintf(stderr, "NEdit: Tag Length too long.\n");
895 XBell(TheDisplay, 0);
897 return status;
901 ** Lookup the definition for the current primary selection the currently
902 ** loaded tags file and bring up the file and line that the tags file
903 ** indicates.
905 void findDefinitionHelper(WindowInfo *window, Time time, const char *arg,
906 int search_type)
908 if(arg)
910 findDef(window, arg, search_type);
912 else
914 searchMode = search_type;
915 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING,
916 (XtSelectionCallbackProc)findDefCB, window, time);
921 ** See findDefHelper
923 void FindDefinition(WindowInfo *window, Time time, const char *arg)
925 findDefinitionHelper(window, time, arg, TAG);
929 ** See findDefHelper
931 void FindDefCalltip(WindowInfo *window, Time time, const char *arg)
933 /* Reset calltip parameters to reasonable defaults */
934 globAnchored = False;
935 globPos = -1;
936 globHAlign = TIP_LEFT;
937 globVAlign = TIP_BELOW;
938 globAlignMode = TIP_SLOPPY;
940 findDefinitionHelper(window, time, arg, TIP);
943 /* Callback function for FindDefinition */
944 static void findDefCB(Widget widget, WindowInfo *window, Atom *sel,
945 Atom *type, char *value, int *length, int *format)
947 /* skip if we can't get the selection data, or it's obviously too long */
948 if (*type == XT_CONVERT_FAIL || value == NULL) {
949 XBell(TheDisplay, 0);
950 } else {
951 findDef(window, value, searchMode);
953 XtFree(value);
957 ** Try to display a calltip
958 ** anchored: If true, tip appears at position pos
959 ** lookup: If true, text is considered a key to be searched for in the
960 ** tip and/or tag database depending on search_type
961 ** search_type: Either TIP or TIP_FROM_TAG
963 int ShowTipString(WindowInfo *window, char *text, Boolean anchored,
964 int pos, Boolean lookup, int search_type, int hAlign, int vAlign,
965 int alignMode) {
967 if (search_type == TAG) return 0;
969 /* So we don't have to carry all of the calltip alignment info around */
970 globAnchored = anchored;
971 globPos = pos;
972 globHAlign = hAlign;
973 globVAlign = vAlign;
974 globAlignMode = alignMode;
976 /* If this isn't a lookup request, just display it. */
977 if (!lookup)
978 return tagsShowCalltip(window, text);
979 else
980 return findDef(window, text, search_type);
983 /* store all of the info into a pre-allocated tags struct */
984 static void setTag(tag *t, const char *name, const char *file,
985 int language, const char *searchString, int posInf,
986 const char *path)
988 t->name = rcs_strdup(name);
989 t->file = rcs_strdup(file);
990 t->language = language;
991 t->searchString = rcs_strdup(searchString);
992 t->posInf = posInf;
993 t->path = rcs_strdup(path);
997 ** ctags search expressions are literal strings with a search direction flag,
998 ** line starting "^" and ending "$" delimiters. This routine translates them
999 ** into NEdit compatible regular expressions and does the search.
1000 ** Etags search expressions are plain literals strings, which
1002 ** If in_buffer is not NULL then it is searched instead of the window buffer.
1003 ** In this case in_buffer should be an XtMalloc allocated buffer and the
1004 ** caller is responsible for freeing it.
1006 static int fakeRegExSearch(WindowInfo *window, char *in_buffer,
1007 const char *searchString, int *startPos, int *endPos)
1009 int found, searchStartPos, dir, ctagsMode;
1010 char *fileString, searchSubs[3*MAXLINE+3], *outPtr;
1011 const char *inPtr;
1013 if (in_buffer == NULL) {
1014 /* get the entire (sigh) text buffer from the text area widget */
1015 fileString = BufGetAll(window->buffer);
1016 } else {
1017 fileString = in_buffer;
1020 /* determine search direction and start position */
1021 if (*startPos != -1) { /* etags mode! */
1022 dir = SEARCH_FORWARD;
1023 searchStartPos = *startPos;
1024 ctagsMode=0;
1025 } else if (searchString[0] == '/') {
1026 dir = SEARCH_FORWARD;
1027 searchStartPos = 0;
1028 ctagsMode=1;
1029 } else if (searchString[0] == '?') {
1030 dir = SEARCH_BACKWARD;
1031 /* searchStartPos = window->buffer->length; */
1032 searchStartPos = strlen(fileString);
1033 ctagsMode=1;
1034 } else {
1035 fprintf(stderr, "NEdit: Error parsing tag file search string");
1036 if(in_buffer == NULL)
1037 XtFree(fileString);
1038 return FALSE;
1041 /* Build the search regex. */
1042 outPtr=searchSubs;
1043 if(ctagsMode) {
1044 inPtr=searchString+1; /* searchString[0] is / or ? --> search dir */
1045 if(*inPtr == '^') {
1046 /* If the first char is a caret then it's a RE line start delim */
1047 *outPtr++ = *inPtr++;
1049 } else { /* etags mode, no search dir spec, no leading caret */
1050 inPtr=searchString;
1052 while(*inPtr) {
1053 if( (*inPtr=='\\' && inPtr[1]=='/') ||
1054 (*inPtr=='\r' && inPtr[1]=='$' && !inPtr[2])
1056 /* Remove:
1057 - escapes (added by standard and exuberant ctags) from slashes
1058 - literal CRs generated by standard ctags for DOSified sources
1060 inPtr++;
1061 } else if(strchr("()-[]<>{}.|^*+?&\\", *inPtr)
1062 || (*inPtr == '$' && (inPtr[1]||(!ctagsMode)))){
1063 /* Escape RE Meta Characters to match them literally.
1064 Don't escape $ if it's the last charcter of the search expr
1065 in ctags mode; always escape $ in etags mode.
1067 *outPtr++ = '\\';
1068 *outPtr++ = *inPtr++;
1069 } else if (isspace((unsigned char)*inPtr)) { /* col. multiple spaces */
1070 *outPtr++ = '\\';
1071 *outPtr++ = 's';
1072 *outPtr++ = '+';
1073 do { inPtr++ ; } while(isspace((unsigned char)*inPtr));
1074 } else { /* simply copy all other characters */
1075 *outPtr++ = *inPtr++;
1078 *outPtr=0; /* Terminate searchSubs */
1080 found = SearchString(fileString, searchSubs, dir, SEARCH_REGEX,
1081 False, searchStartPos, startPos, endPos, NULL, NULL, NULL);
1083 if(!found && !ctagsMode) {
1084 /* position of the target definition could have been drifted before
1085 startPos, if nothing has been found by now try searching backward
1086 again from startPos.
1088 found = SearchString(fileString, searchSubs, SEARCH_BACKWARD,
1089 SEARCH_REGEX, False, searchStartPos, startPos, endPos, NULL,
1090 NULL, NULL);
1093 /* free the text buffer copy returned from XmTextGetString */
1094 if(in_buffer == NULL)
1095 XtFree(fileString);
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;
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)
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;
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;
1125 tagName=string;
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;
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];
1154 nMatches = 1;
1155 break;
1157 /* Is this match in the same dir. as the current file? */
1158 if (!strcmp(window->path,pathname)) {
1159 samePath++;
1160 pathMatch=nMatches;
1162 if (++nMatches >= MAXDUPTAGS) {
1163 DialogF(DF_WARN, dialogParent, 1, "Tags",
1164 "Too many duplicate tags, first %d shown", "OK", MAXDUPTAGS);
1165 break;
1167 /* Tell LookupTag to look for more definitions of the same tag: */
1168 string = NULL;
1171 /* Did we find any matches? */
1172 if (!nMatches) {
1173 return 0;
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;
1184 /* If all of the tag entries are the same file, just use the first.
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;
1194 if (nMatches>1) {
1195 if (!(dupTagsList = (char **) malloc(sizeof(char *) * nMatches))) {
1196 fprintf(stderr, "NEdit: findDef(): out of heap space!\n");
1197 XBell(TheDisplay, 0);
1198 return -1;
1200 for (i=0; i<nMatches; i++) {
1201 ParseFilename(tagFiles[i], filename, pathname);
1202 if ((i<nMatches-1 && !strcmp(tagFiles[i],tagFiles[i+1])) ||
1203 (i>0 && !strcmp(tagFiles[i],tagFiles[i-1]))) {
1204 if(*(tagSearch[i]) && (tagPosInf[i] != -1)) { /* etags */
1205 sprintf(temp,"%2d. %s%s %8i %s", i+1, pathname,
1206 filename, tagPosInf[i], tagSearch[i]);
1207 } else if (*(tagSearch[i])) { /* ctags search expr */
1208 sprintf(temp,"%2d. %s%s %s", i+1, pathname,
1209 filename, tagSearch[i]);
1210 } else { /* line number only */
1211 sprintf(temp,"%2d. %s%s %8i", i+1, pathname, filename,
1212 tagPosInf[i]);
1214 } else
1215 sprintf(temp,"%2d. %s%s",i+1,pathname,filename);
1216 if (!(dupTagsList[i] = (char *) malloc(strlen(temp) + 1))) {
1217 fprintf(stderr, "NEdit: findDef(): out of heap space!\n");
1218 XBell(TheDisplay, 0);
1219 return -1;
1221 strcpy(dupTagsList[i],temp);
1223 createSelectMenu(dialogParent, "Duplicate Tags", nMatches, dupTagsList);
1224 for (i=0; i<nMatches; i++)
1225 free(dupTagsList[i]);
1226 free(dupTagsList);
1227 return 1;
1230 ** No need for a dialog list, there is only one tag matching --
1231 ** Go directly to the tag
1233 if (searchMode == TAG)
1234 editTaggedLocation( dialogParent, 0 );
1235 else
1236 showMatchingCalltip( dialogParent, 0 );
1237 return 1;
1240 /* Callback function for the FindAll widget. Process the users response. */
1241 static void findAllCB(Widget parent, XtPointer client_data, XtPointer call_data)
1243 int i;
1244 char *eptr;
1246 XmSelectionBoxCallbackStruct *cbs =
1247 (XmSelectionBoxCallbackStruct *) call_data;
1248 if (cbs->reason == XmCR_NO_MATCH)
1249 return;
1250 if (cbs->reason == XmCR_CANCEL) {
1251 XtDestroyWidget(XtParent(parent));
1252 return;
1255 XmStringGetLtoR(cbs->value,XmFONTLIST_DEFAULT_TAG,&eptr);
1256 if ((i = atoi(eptr)-1) < 0) {
1257 XBell(TheDisplay, 0);
1258 return;
1261 if (searchMode == TAG)
1262 editTaggedLocation( parent, i ); /* Open the file with the definition */
1263 else
1264 showMatchingCalltip( parent, i );
1266 if (cbs->reason == XmCR_OK)
1267 XtDestroyWidget(XtParent(parent));
1270 /* Window manager close-box callback for tag-collision dialog */
1271 static void findAllCloseCB(Widget parent, XtPointer client_data,
1272 XtPointer call_data)
1274 XtDestroyWidget(parent);
1278 * Given a \0 terminated string and a position, advance the position
1279 * by n lines, where line separators (for now) are \n. If the end of
1280 * string is reached before n lines, return the number of lines advanced,
1281 * else normally return -1.
1283 static int moveAheadNLines( char *str, int *pos, int n ) {
1284 int i=n;
1285 while (str[*pos] != '\0' && n>0) {
1286 if (str[*pos] == '\n')
1287 --n;
1288 ++(*pos);
1290 if (n==0)
1291 return -1;
1292 else
1293 return i-n;
1297 ** Show the calltip specified by tagFiles[i], tagSearch[i], tagPosInf[i]
1298 ** This reads from either a source code file (if searchMode == TIP_FROM_TAG)
1299 ** or a calltips file (if searchMode == TIP).
1301 static void showMatchingCalltip( Widget parent, int i )
1303 int startPos=0, fileLen, readLen, tipLen;
1304 int endPos=0;
1305 char *fileString;
1306 FILE *fp;
1307 struct stat statbuf;
1308 char *message;
1310 /* 1. Open the target file */
1311 NormalizePathname(tagFiles[i]);
1312 fp = fopen(tagFiles[i], "r");
1313 if (fp == NULL) {
1314 DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s",
1315 "Dismiss", tagFiles[i]);
1316 return;
1318 if (fstat(fileno(fp), &statbuf) != 0) {
1319 fclose(fp);
1320 DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s",
1321 "Dismiss", tagFiles[i]);
1322 return;
1325 /* 2. Read the target file */
1326 /* Allocate space for the whole contents of the file (unfortunately) */
1327 fileLen = statbuf.st_size;
1328 fileString = XtMalloc(fileLen+1); /* +1 = space for null */
1329 if (fileString == NULL) {
1330 fclose(fp);
1331 DialogF(DF_ERR, parent, 1, "File too large",
1332 "File is too large to load", "Dismiss");
1333 return;
1336 /* Read the file into fileString and terminate with a null */
1337 readLen = fread(fileString, sizeof(char), fileLen, fp);
1338 if (ferror(fp)) {
1339 fclose(fp);
1340 DialogF(DF_ERR, parent, 1, "Error reading File", "Error reading %s",
1341 "Dismiss", tagFiles[i]);
1342 XtFree(fileString);
1343 return;
1345 fileString[readLen] = 0;
1347 /* Close the file */
1348 if (fclose(fp) != 0) {
1349 /* unlikely error */
1350 DialogF(DF_WARN, parent, 1, "Error closing File",
1351 "Unable to close file", "Dismiss");
1352 /* we read it successfully, so continue */
1355 /* 3. Search for the tagged location (set startPos) */
1356 if (!*(tagSearch[i])) {
1357 /* It's a line number, just go for it */
1358 if ((moveAheadNLines( fileString, &startPos, tagPosInf[i]-1 )) >= 0) {
1359 DialogF(DF_ERR, parent, 1, "Tags Error",
1360 "%s\n not long enough for definition to be on line %d",
1361 "Dismiss", tagFiles[i], tagPosInf[i]);
1362 XtFree(fileString);
1363 return;
1365 } else {
1366 startPos = tagPosInf[i];
1367 if(!fakeRegExSearch(WidgetToWindow(parent), fileString, tagSearch[i],
1368 &startPos, &endPos)){
1369 DialogF(DF_WARN, parent, 1, "Tag not found",
1370 "Definition for %s\nnot found in %s", "OK", tagName,
1371 tagFiles[i]);
1372 XtFree(fileString);
1373 return;
1377 if (searchMode == TIP) {
1378 int dummy, found;
1380 /* 4. Find the end of the calltip (delimited by an empty line) */
1381 endPos = startPos;
1382 found = SearchString(fileString, "\\n\\s*\\n", SEARCH_FORWARD,
1383 SEARCH_REGEX, False, startPos, &endPos, &dummy, NULL,
1384 NULL, NULL);
1385 if (!found) {
1386 /* Just take 4 lines */
1387 moveAheadNLines( fileString, &endPos, TIP_DEFAULT_LINES );
1388 --endPos; /* Lose the last \n */
1390 } else { /* Mode = TIP_FROM_TAG */
1391 /* 4. Copy TIP_DEFAULT_LINES lines of text to the calltip string */
1392 endPos = startPos;
1393 moveAheadNLines( fileString, &endPos, TIP_DEFAULT_LINES );
1394 /* Make sure not to overrun the fileString with ". . ." */
1395 if (((size_t) endPos) <= (strlen(fileString)-5)) {
1396 sprintf( &fileString[endPos], ". . ." );
1397 endPos += 5;
1400 /* 5. Copy the calltip to a string */
1401 tipLen = endPos - startPos;
1402 message = XtMalloc(tipLen+1); /* +1 = space for null */
1403 if (message == NULL)
1405 DialogF(DF_ERR, parent, 1, "Out of Memory",
1406 "Can't allocate memory for calltip message", "Dismiss");
1407 XtFree(fileString);
1408 return;
1410 strncpy( message, &fileString[startPos], tipLen );
1411 message[tipLen] = 0;
1413 /* 6. Display it */
1414 tagsShowCalltip( WidgetToWindow(parent), message );
1415 XtFree(message);
1416 XtFree(fileString);
1419 /* Open a new (or existing) editor window to the location specified in
1420 tagFiles[i], tagSearch[i], tagPosInf[i] */
1421 static void editTaggedLocation( Widget parent, int i )
1423 /* Globals: tagSearch, tagPosInf, tagFiles, tagName, textNrows,
1424 WindowList */
1425 int startPos, endPos, lineNum, rows;
1426 char filename[MAXPATHLEN], pathname[MAXPATHLEN];
1427 WindowInfo *windowToSearch;
1429 ParseFilename(tagFiles[i],filename,pathname);
1430 /* open the file containing the definition */
1431 EditExistingFile(WindowList, filename, pathname, 0, NULL, False, NULL);
1432 windowToSearch = FindWindowWithFile(filename, pathname);
1433 if (windowToSearch == NULL) {
1434 DialogF(DF_WARN, parent, 1, "File not found", "File %s not found", "OK",
1435 tagFiles[i]);
1436 return;
1439 startPos=tagPosInf[i];
1441 if(!*(tagSearch[i])) {
1442 /* if the search string is empty, select the numbered line */
1443 SelectNumberedLine(windowToSearch, startPos);
1444 return;
1447 /* search for the tags file search string in the newly opened file */
1448 if(!fakeRegExSearch(windowToSearch, NULL, tagSearch[i], &startPos,
1449 &endPos)){
1450 DialogF(DF_WARN, windowToSearch->shell, 1, "Tag Error",
1451 "Definition for %s\nnot found in %s", "OK", tagName,
1452 tagFiles[i]);
1453 return;
1456 /* select the matched string */
1457 BufSelect(windowToSearch->buffer, startPos, endPos);
1459 /* Position it nicely in the window,
1460 about 1/4 of the way down from the top */
1461 lineNum = BufCountLines(windowToSearch->buffer, 0, startPos);
1462 XtVaGetValues(windowToSearch->lastFocus, textNrows, &rows, NULL);
1463 TextSetScroll(windowToSearch->lastFocus, lineNum - rows/4, 0);
1464 TextSetCursorPos(windowToSearch->lastFocus, endPos);
1467 /* Create a Menu for user to select from the collided tags */
1468 static Widget createSelectMenu(Widget parent, char *label, int nArgs,
1469 char *args[])
1471 int i;
1472 char tmpStr[100];
1473 Widget menu;
1474 XmStringTable list;
1475 XmString popupTitle;
1476 int ac;
1477 Arg csdargs[20];
1479 list = (XmStringTable) XtMalloc(nArgs * sizeof(XmString *));
1480 for (i=0; i<nArgs; i++)
1481 list[i] = XmStringCreateSimple(args[i]);
1482 sprintf(tmpStr,"Select File With TAG: %s",tagName);
1483 popupTitle = XmStringCreateSimple(tmpStr);
1484 ac = 0;
1485 XtSetArg(csdargs[ac], XmNlistLabelString, popupTitle); ac++;
1486 XtSetArg(csdargs[ac], XmNlistItems, list); ac++;
1487 XtSetArg(csdargs[ac], XmNlistItemCount, nArgs); ac++;
1488 XtSetArg(csdargs[ac], XmNvisibleItemCount, 12); ac++;
1489 XtSetArg(csdargs[ac], XmNautoUnmanage, False); ac++;
1490 menu = CreateSelectionDialog(parent,label,csdargs,ac);
1491 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_TEXT));
1492 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_HELP_BUTTON));
1493 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_SELECTION_LABEL));
1494 XtAddCallback(menu, XmNokCallback, (XtCallbackProc)findAllCB, menu);
1495 XtAddCallback(menu, XmNapplyCallback, (XtCallbackProc)findAllCB, menu);
1496 XtAddCallback(menu, XmNcancelCallback, (XtCallbackProc)findAllCB, menu);
1497 AddMotifCloseCallback(XtParent(menu), findAllCloseCB, NULL);
1498 for (i=0; i<nArgs; i++)
1499 XmStringFree(list[i]);
1500 XtFree((char *)list);
1501 XmStringFree(popupTitle);
1502 ManageDialogCenteredOnPointer(menu);
1503 return menu;
1508 /*--------------------------------------------------------------------------
1510 Reference-counted string hack; SJT 4/2000
1512 This stuff isn't specific to tags, so it should be in it's own file.
1513 However, I'm leaving it in here for now to reduce the diffs.
1515 This could really benefit from using a real hash table.
1518 #define RCS_SIZE 10000
1520 struct rcs;
1522 struct rcs_stats
1524 int talloc, tshar, tgiveup, tbytes, tbyteshared;
1527 struct rcs
1529 struct rcs *next;
1530 char *string;
1531 int usage;
1534 static struct rcs *Rcs[RCS_SIZE];
1535 static struct rcs_stats RcsStats;
1538 ** Take a normal string, create a shared string from it if need be,
1539 ** and return pointer to that shared string.
1541 ** Returned strings are const because they are shared. Do not modify them!
1544 static const char *rcs_strdup(const char *str)
1546 int bucket;
1547 size_t len;
1548 struct rcs *rp;
1549 struct rcs *prev = NULL;
1551 char *newstr = NULL;
1553 if (str == NULL)
1554 return NULL;
1556 bucket = hashAddr(str) % RCS_SIZE;
1557 len = strlen(str);
1559 RcsStats.talloc++;
1561 #if 0
1562 /* Don't share if it won't save space.
1564 Doesn't save anything - if we have lots of small-size objects,
1565 it's beneifical to share them. We don't know until we make a full
1566 count. My tests show that it's better to leave this out. */
1567 if (len <= sizeof(struct rcs))
1569 new_str = strdup(str); /* GET RID OF strdup() IF EVER ENABLED (not ANSI) */
1570 RcsStats.tgiveup++;
1571 return;
1573 #endif
1575 /* Find it in hash */
1576 for (rp = Rcs[bucket]; rp; rp = rp->next)
1578 if (!strcmp(str, rp->string))
1579 break;
1580 prev = rp;
1583 if (rp) /* It exists, return it and bump ref ct */
1585 rp->usage++;
1586 newstr = rp->string;
1588 RcsStats.tshar++;
1589 RcsStats.tbyteshared += len;
1591 else /* Doesn't exist, conjure up a new one. */
1593 struct rcs *newrcs = malloc(sizeof(struct rcs));
1594 newrcs->string = malloc(len+1);
1595 strcpy(newrcs->string, str);
1596 newrcs->usage = 1;
1597 newrcs->next = NULL;
1599 if (Rcs[bucket])
1600 prev->next = newrcs;
1601 else
1602 Rcs[bucket] = newrcs;
1604 newstr = newrcs->string;
1607 RcsStats.tbytes += len;
1608 return newstr;
1612 ** Decrease the reference count on a shared string. When the reference
1613 ** count reaches zero, free the master string.
1616 static void rcs_free(const char *rcs_str)
1618 int bucket;
1619 struct rcs *rp;
1620 struct rcs *prev = NULL;
1622 if (rcs_str == NULL)
1623 return;
1625 bucket = hashAddr(rcs_str) % RCS_SIZE;
1627 /* find it in hash */
1628 for (rp = Rcs[bucket]; rp; rp = rp->next)
1630 if (rcs_str == rp->string)
1631 break;
1632 prev = rp;
1635 if (rp) /* It's a shared string, decrease ref count */
1637 rp->usage--;
1639 if (rp->usage < 0) /* D'OH! */
1641 fprintf(stderr, "NEdit: internal error deallocating shared string.");
1642 return;
1645 if (rp->usage == 0) /* Last one- free the storage */
1647 free(rp->string);
1648 if (prev)
1649 prev->next = rp->next;
1650 else
1651 Rcs[bucket] = rp->next;
1652 free(rp);
1655 else /* Doesn't appear to be a shared string */
1657 fprintf(stderr, "NEdit: attempt to free a non-shared string.");
1658 return;
1662 /********************************************************************
1663 * Functions for loading Calltips files *
1664 ********************************************************************/
1666 enum tftoken_types { TF_EOF, TF_BLOCK, TF_VERSION, TF_INCLUDE, TF_LANGUAGE,
1667 TF_ALIAS, TF_ERROR, TF_ERROR_EOF };
1669 /* A wrapper for SearchString */
1670 static int searchLine(char *line, const char *regex) {
1671 int dummy1, dummy2;
1672 return SearchString(line, regex, SEARCH_FORWARD, SEARCH_REGEX,
1673 False, 0, &dummy1, &dummy2, NULL, NULL, NULL);
1676 /* Check if a line has non-ws characters */
1677 static Boolean lineEmpty(const char *line) {
1678 while (*line && *line != '\n') {
1679 if (*line != ' ' && *line != '\t')
1680 return False;
1681 ++line;
1683 return True;
1686 /* Remove trailing whitespace from a line */
1687 static void rstrip( char *dst, const char *src ) {
1688 int wsStart, dummy2;
1689 /* Strip trailing whitespace */
1690 if(SearchString(src, "\\s*\\n", SEARCH_FORWARD, SEARCH_REGEX,
1691 False, 0, &wsStart, &dummy2, NULL, NULL, NULL)) {
1692 if(dst != src)
1693 memcpy(dst, src, wsStart);
1694 dst[wsStart] = 0;
1695 } else
1696 if(dst != src)
1697 strcpy(dst, src);
1701 ** Get the next block from a tips file. A block is a \n\n+ delimited set of
1702 ** lines in a calltips file. All of the parameters except <fp> are return
1703 ** values, and most have different roles depending on the type of block
1704 ** that is found.
1705 ** header: Depends on the block type
1706 ** body: Depends on the block type. Used to return a new
1707 ** dynamically allocated string.
1708 ** blkLine: Returns the line number of the first line of the block
1709 ** after the "* xxxx *" line.
1710 ** currLine: Used to keep track of the current line in the file.
1712 static int nextTFBlock(FILE *fp, char *header, char **body, int *blkLine,
1713 int *currLine)
1715 /* These are the different kinds of tokens */
1716 const char *commenTF_regex = "^\\s*\\* comment \\*\\s*$";
1717 const char *version_regex = "^\\s*\\* version \\*\\s*$";
1718 const char *include_regex = "^\\s*\\* include \\*\\s*$";
1719 const char *language_regex = "^\\s*\\* language \\*\\s*$";
1720 const char *alias_regex = "^\\s*\\* alias \\*\\s*$";
1721 char line[MAXLINE], *status;
1722 int dummy1;
1723 int code;
1725 /* Skip blank lines and comments */
1726 while(1) {
1727 /* Skip blank lines */
1728 while((status=fgets(line, MAXLINE, fp))) {
1729 ++(*currLine);
1730 if(!lineEmpty( line ))
1731 break;
1734 /* Check for error or EOF */
1735 if(!status)
1736 return TF_EOF;
1738 /* We've got a non-blank line -- is it a comment block? */
1739 if( !searchLine(line, commenTF_regex) )
1740 break;
1742 /* Skip the comment (non-blank lines) */
1743 while((status=fgets(line, MAXLINE, fp))) {
1744 ++(*currLine);
1745 if(lineEmpty( line ))
1746 break;
1749 if(!status)
1750 return TF_EOF;
1753 /* Now we know it's a meaningful block */
1754 dummy1 = searchLine(line, include_regex);
1755 if( dummy1 || searchLine(line, alias_regex) ) {
1756 /* INCLUDE or ALIAS block */
1757 int incLen, incPos = ftell(fp), i, incLines;
1759 /* fprintf(stderr, "Starting include\n"); */
1760 *blkLine = *currLine + 1; /* Line of first actual filename */
1761 if (incPos < 0)
1762 return TF_ERROR;
1763 /* Figure out how long the block is */
1764 while((status=fgets(line, MAXLINE, fp))) {
1765 ++(*currLine);
1766 if(lineEmpty( line ))
1767 break;
1769 incLen = ftell(fp) - incPos;
1770 incLines = *currLine - *blkLine;
1771 /* Correct currLine for the empty line it read at the end */
1772 --(*currLine);
1773 if (incLines == 0) {
1774 fprintf( stderr, "nedit: Warning: empty '* include *' block in calltips file.\n" );
1775 return TF_ERROR;
1777 /* Make space for the filenames */
1778 *body = (char *)malloc(incLen+1);
1779 if (!*body)
1780 return TF_ERROR;
1781 *body[0]=0;
1782 if (fseek(fp, incPos, SEEK_SET) != 0) {
1783 free (*body);
1784 return TF_ERROR;
1786 /* Read all the lines in the block */
1787 /* fprintf(stderr, "Copying lines\n"); */
1788 for (i=0; i<incLines; i++) {
1789 status = fgets(line, MAXLINE, fp);
1790 if (!status) {
1791 free (*body);
1792 return TF_ERROR_EOF;
1794 rstrip(line,line);
1795 if(i)
1796 strcat(*body, ":");
1797 strcat(*body, line);
1799 /* fprintf(stderr, "Finished include\n"); */
1800 if(dummy1)
1801 code = TF_INCLUDE;
1802 else
1803 code = TF_ALIAS;
1806 else if( searchLine(line, language_regex) ) {
1807 /* LANGUAGE block */
1808 status=fgets(line, MAXLINE, fp);
1809 ++(*currLine);
1810 if (!status)
1811 return TF_ERROR_EOF;
1812 if (lineEmpty( line )) {
1813 fprintf( stderr, "nedit: Warning: empty '* language *' block in calltips file.\n" );
1814 return TF_ERROR;
1816 *blkLine = *currLine;
1817 rstrip(header, line);
1818 code = TF_LANGUAGE;
1821 else if( searchLine(line, version_regex) ) {
1822 /* VERSION block */
1823 status=fgets(line, MAXLINE, fp);
1824 ++(*currLine);
1825 if (!status)
1826 return TF_ERROR_EOF;
1827 if (lineEmpty( line )) {
1828 fprintf( stderr, "nedit: Warning: empty '* version *' block in calltips file.\n" );
1829 return TF_ERROR;
1831 *blkLine = *currLine;
1832 rstrip(header, line);
1833 code = TF_VERSION;
1836 else {
1837 /* Calltip block */
1838 /* The first line is the key, the rest is the tip.
1839 Strip trailing whitespace. */
1840 rstrip(header, line);
1842 status=fgets(line, MAXLINE, fp);
1843 ++(*currLine);
1844 if (!status)
1845 return TF_ERROR_EOF;
1846 if (lineEmpty( line )) {
1847 fprintf( stderr, "nedit: Warning: empty calltip block:\n"
1848 " \"%s\"\n", header);
1849 return TF_ERROR;
1851 *blkLine = *currLine;
1852 *body = strdup(line);
1853 code = TF_BLOCK;
1856 /* Skip the rest of the block */
1857 dummy1 = *currLine;
1858 while(fgets(line, MAXLINE, fp)) {
1859 ++(*currLine);
1860 if (lineEmpty( line ))
1861 break;
1864 /* Warn about any unneeded extra lines (which are ignored). */
1865 if (dummy1+1 < *currLine && code != TF_BLOCK) {
1866 fprintf( stderr, "nedit: Warning: extra lines in language or version block ignored.\n" );
1869 return code;
1872 /* A struct for describing a calltip alias */
1873 typedef struct _alias {
1874 char *dest;
1875 char *sources;
1876 struct _alias *next;
1877 } tf_alias;
1880 ** Allocate a new alias, copying dest and stealing sources. This may
1881 ** seem strange but that's the way it's called
1883 static tf_alias *new_alias(const char *dest, char *sources) {
1884 tf_alias *alias;
1885 /* Allocate the alias */
1886 alias = (tf_alias *)malloc( sizeof(tf_alias) );
1887 if(!alias)
1888 return NULL;
1890 /* Fill it in */
1891 alias->dest = (char*)malloc( strlen(dest)+1 );
1892 if(!(alias->dest))
1893 return NULL;
1894 strcpy( alias->dest, dest );
1895 alias->sources = sources;
1896 return alias;
1899 /* Deallocate a linked-list of aliases */
1900 static void free_alias_list(tf_alias *alias) {
1901 tf_alias *tmp_alias;
1902 while(alias) {
1903 tmp_alias = alias->next;
1904 free(alias->dest);
1905 free(alias->sources);
1906 free(alias);
1907 alias = tmp_alias;
1912 ** Load a calltips file and insert all of the entries into the global tips
1913 ** database. Each tip is essentially stored as its filename and the line
1914 ** at which it appears--the exact same way ctags indexes source-code. That's
1915 ** why calltips and tags share so much code.
1917 static int loadTipsFile(const char *tipsFile, int index, int recLevel)
1919 FILE *fp = NULL;
1920 char header[MAXLINE];
1921 char *body, *tipIncFile;
1922 char tipPath[MAXPATHLEN];
1923 char resolvedTipsFile[MAXPATHLEN+1];
1924 int nTipsAdded=0, langMode = PLAIN_LANGUAGE_MODE, oldLangMode;
1925 int currLine=0, code, blkLine;
1926 tf_alias *aliases=NULL, *tmp_alias;
1928 if(recLevel > MAX_TAG_INCLUDE_RECURSION_LEVEL) {
1929 fprintf(stderr, "nedit: Warning: Reached recursion limit before loading calltips file:\n\t%s\n", tipsFile);
1930 return 0;
1933 /* find the tips file */
1934 #ifndef VMS
1935 /* Allow ~ in Unix filenames */
1936 strncpy(tipPath, tipsFile, MAXPATHLEN); /* ExpandTilde is destructive */
1937 ExpandTilde(tipPath);
1938 if(!ResolvePath(tipPath, resolvedTipsFile))
1939 return 0;
1940 #else
1941 if(!ResolvePath(tipsFile, resolvedTipsFile))
1942 return 0;
1943 #endif
1945 /* Get the path to the tips file */
1946 ParseFilename(resolvedTipsFile, NULL, tipPath);
1948 /* Open the file */
1949 if ((fp = fopen(resolvedTipsFile, "r")) == NULL)
1950 return 0;
1952 while( 1 ) {
1953 code = nextTFBlock(fp, header, &body, &blkLine, &currLine);
1954 if( code == TF_ERROR_EOF ) {
1955 fprintf(stderr,"nedit: Warning: unexpected EOF in calltips file.\n");
1956 break;
1958 if( code == TF_EOF )
1959 break;
1961 switch (code) {
1962 case TF_BLOCK:
1963 /* Add the calltip to the global hash table.
1964 For the moment I'm just using line numbers because I don't
1965 want to have to deal with adding escape characters for
1966 regex metacharacters that might appear in the string */
1967 nTipsAdded += addTag(header, resolvedTipsFile, langMode, "",
1968 blkLine, tipPath, index);
1969 free( body );
1970 break;
1971 case TF_INCLUDE:
1972 /* nextTFBlock returns a colon-separated list of tips files
1973 in body */
1974 for(tipIncFile=strtok(body,":"); tipIncFile;
1975 tipIncFile=strtok(NULL,":")) {
1976 /* fprintf(stderr,
1977 "nedit: DEBUG: including tips file '%s'\n",
1978 tipIncFile); */
1979 nTipsAdded += loadTipsFile( tipIncFile, index, recLevel+1);
1981 free( body );
1982 break;
1983 case TF_LANGUAGE:
1984 /* Switch to the new language mode if it's valid, else ignore
1985 it. */
1986 oldLangMode = langMode;
1987 langMode = FindLanguageMode( header );
1988 if (langMode == PLAIN_LANGUAGE_MODE &&
1989 strcmp(header, "Plain")) {
1990 fprintf(stderr,
1991 "nedit: Error reading calltips file:\n\t%s\n"
1992 "Unknown language mode: \"%s\"\n",
1993 tipsFile, header);
1994 langMode = oldLangMode;
1996 break;
1997 case TF_ERROR:
1998 fprintf(stderr,"nedit: Warning: Recoverable error while "
1999 "reading calltips file:\n \"%s\"\n",
2000 resolvedTipsFile);
2001 break;
2002 case TF_ALIAS:
2003 /* Allocate a new alias struct */
2004 tmp_alias = aliases;
2005 aliases = new_alias(header, body);
2006 if( !aliases ) {
2007 fprintf(stderr,"nedit: Can't allocate memory for tipfile "
2008 "alias in calltips file:\n \"%s\"\n",
2009 resolvedTipsFile);
2010 /* Deallocate any allocated aliases */
2011 free_alias_list(tmp_alias);
2012 return 0;
2014 /* Add it to the list */
2015 aliases->next = tmp_alias;
2016 break;
2017 default:
2018 ;/* Ignore TF_VERSION for now */
2022 /* Now resolve any aliases */
2023 tmp_alias = aliases;
2024 while (tmp_alias) {
2025 tag *t;
2026 char *src;
2027 t = getTag(tmp_alias->dest, TIP);
2028 if (!t) {
2029 fprintf(stderr, "nedit: Can't find destination of alias \"%s\"\n"
2030 " in calltips file:\n \"%s\"\n",
2031 tmp_alias->dest, resolvedTipsFile);
2032 } else {
2033 for(src=strtok(tmp_alias->sources,":"); src; src=strtok(NULL,":"))
2034 addTag(src, resolvedTipsFile, t->language, "", t->posInf,
2035 tipPath, index);
2037 tmp_alias = tmp_alias->next;
2039 free_alias_list(aliases);
2040 return nTipsAdded;