From Ivan Skytte Jørgensen: remove duplicate declarations
[nedit.git] / source / tags.c
blobf43e9d6fd0813ff7257844f65ba02b0d1d6e75f0
1 static const char CVSID[] = "$Id: tags.c,v 1.66 2007/03/04 23:26:05 yooden 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. In addition, you may distribute versions of this program linked to *
12 * Motif or Open Motif. See README for details. *
13 * *
14 * This software is distributed in the hope that it will be useful, but WITHOUT *
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
17 * for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License along with *
20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
21 * Place, Suite 330, Boston, MA 02111-1307 USA *
22 * *
23 * Nirvana Text Editor *
24 * July, 1993 *
25 * *
26 * Written by Mark Edel *
27 * *
28 *******************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 #include "../config.h"
32 #endif
34 #include "tags.h"
35 #include "textBuf.h"
36 #include "text.h"
37 #include "nedit.h"
38 #include "window.h"
39 #include "file.h"
40 #include "preferences.h"
41 #include "search.h"
42 #include "selection.h"
43 #include "calltips.h"
44 #include "../util/DialogF.h"
45 #include "../util/fileUtils.h"
46 #include "../util/misc.h"
47 #include "../util/utils.h"
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <ctype.h>
53 #include <sys/types.h>
54 #include <sys/stat.h>
55 #include <unistd.h>
56 #ifdef VMS
57 #include "../util/VMSparam.h"
58 #else
59 #ifndef __MVS__
60 #include <sys/param.h>
61 #endif
62 #endif /*VMS*/
64 #include <Xm/PrimitiveP.h> /* For Calltips */
65 #include <Xm/Xm.h>
66 #include <Xm/SelectioB.h>
67 #include <X11/Xatom.h>
69 #ifdef HAVE_DEBUG_H
70 #include "../debug.h"
71 #endif
73 #define MAXLINE 2048
74 #define MAX_TAG_LEN 256
75 #define MAXDUPTAGS 100
76 #define MAX_TAG_INCLUDE_RECURSION_LEVEL 5
78 /* Take this many lines when making a tip from a tag.
79 (should probably be a language-dependent option, but...) */
80 #define TIP_DEFAULT_LINES 4
82 #define STRSAVE(a) ((a!=NULL)?strcpy(malloc(strlen(a)+1),(a)):strcpy(malloc(1),""))
84 typedef struct _tag {
85 struct _tag *next;
86 const char *path;
87 const char *name;
88 const char *file;
89 int language;
90 const char *searchString; /* see comment below */
91 int posInf; /* see comment below */
92 short index;
93 } tag;
96 ** contents of tag->searchString | tag->posInf
97 ** ctags, line num specified: "" | line num
98 ** ctags, search expr specfd: ctags search expr | -1
99 ** etags (emacs tags) etags search string | search start pos
102 enum searchDirection {FORWARD, BACKWARD};
104 static int loadTagsFile(const char *tagSpec, int index, int recLevel);
105 static void findDefCB(Widget widget, WindowInfo *window, Atom *sel,
106 Atom *type, char *value, int *length, int *format);
107 static void setTag(tag *t, const char *name, const char *file,
108 int language, const char *searchString, int posInf,
109 const char * tag);
110 static int fakeRegExSearch(WindowInfo *window, char *buffer,
111 const char *searchString, int *startPos, int *endPos);
112 static unsigned hashAddr(const char *key);
113 static void updateMenuItems(void);
114 static int addTag(const char *name, const char *file, int lang,
115 const char *search, int posInf, const char *path,
116 int index);
117 static int delTag(const char *name, const char *file, int lang,
118 const char *search, int posInf, int index);
119 static tag *getTag(const char *name, int search_type);
120 static int findDef(WindowInfo *window, const char *value, int search_type);
121 static int findAllMatches(WindowInfo *window, const char *string);
122 static void findAllCB(Widget parent, XtPointer client_data, XtPointer call_data);
123 static Widget createSelectMenu(Widget parent, char *label, int nArgs,
124 char *args[]);
125 static void editTaggedLocation( Widget parent, int i );
126 static void showMatchingCalltip( Widget parent, int i );
128 static const char *rcs_strdup(const char *str);
129 static void rcs_free(const char *str);
130 static int searchLine(char *line, const char *regex);
131 static void rstrip( char *dst, const char *src );
132 static int nextTFBlock(FILE *fp, char *header, char **tiptext, int *lineAt,
133 int *lineNo);
134 static int loadTipsFile(const char *tipsFile, int index, int recLevel);
136 /* Hash table of tags, implemented as an array. Each bin contains a
137 NULL-terminated linked list of parsed tags */
138 static tag **Tags = NULL;
139 static int DefTagHashSize = 10000;
140 /* list of loaded tags files */
141 tagFile *TagsFileList = NULL;
143 /* Hash table of calltip tags */
144 static tag **Tips = NULL;
145 tagFile *TipsFileList = NULL;
148 /* These are all transient global variables -- they don't hold any state
149 between tag/tip lookups */
150 static int searchMode = TAG;
151 static const char *tagName;
152 static char tagFiles[MAXDUPTAGS][MAXPATHLEN];
153 static char tagSearch[MAXDUPTAGS][MAXPATHLEN];
154 static int tagPosInf[MAXDUPTAGS];
155 static Boolean globAnchored;
156 static int globPos;
157 static int globHAlign;
158 static int globVAlign;
159 static int globAlignMode;
161 /* A wrapper for calling TextDShowCalltip */
162 static int tagsShowCalltip( WindowInfo *window, char *text ) {
163 if (text)
164 return ShowCalltip( window, text, globAnchored, globPos, globHAlign,
165 globVAlign, globAlignMode);
166 else
167 return 0;
170 /* Set the head of the proper file list (Tags or Tips) to t */
171 static tagFile *setFileListHead(tagFile *t, int file_type )
173 if (file_type == TAG)
174 TagsFileList = t;
175 else
176 TipsFileList = t;
177 return t;
180 /* Compute hash address from a string key */
181 static unsigned hashAddr(const char *key)
183 unsigned s=strlen(key);
184 unsigned a=0,x=0,i;
186 for (i=0; (i+3)<s; i += 4) {
187 strncpy((char*)&a,&key[i],4);
188 x += a;
191 for (a=1; i<(s+1); i++, a *= 256)
192 x += key[i] * a;
194 return x;
197 /* Retrieve a tag structure from the hash table */
198 static tag *getTag(const char *name, int search_type)
200 static char lastName[MAXLINE];
201 static tag *t;
202 static int addr;
203 tag **table;
205 if (search_type == TIP)
206 table = Tips;
207 else
208 table = Tags;
210 if (table == NULL) return NULL;
212 if (name) {
213 addr = hashAddr(name) % DefTagHashSize;
214 t = table[addr];
215 strcpy(lastName,name);
217 else if (t) {
218 name = lastName;
219 t = t->next;
221 else return NULL;
223 for (;t; t = t->next)
224 if (!strcmp(name,t->name)) return t;
225 return NULL;
228 /* Add a tag specification to the hash table
229 ** Return Value: 0 ... tag already existing, spec not added
230 ** 1 ... tag spec is new, added.
231 ** (We don't return boolean as the return value is used as counter increment!)
234 static int addTag(const char *name, const char *file, int lang,
235 const char *search, int posInf, const char *path, int index)
237 int addr = hashAddr(name) % DefTagHashSize;
238 tag *t;
239 char newfile[MAXPATHLEN];
240 tag **table;
242 if (searchMode == TIP) {
243 if (Tips == NULL)
244 Tips = (tag **)calloc(DefTagHashSize, sizeof(tag*));
245 table = Tips;
246 } else {
247 if (Tags == NULL)
248 Tags = (tag **)calloc(DefTagHashSize, sizeof(tag*));
249 table = Tags;
252 if (*file == '/')
253 strcpy(newfile,file);
254 else
255 sprintf(newfile,"%s%s", path, file);
257 NormalizePathname(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 if (strcmp(newfile, tmpfile)) continue;
271 return 0;
274 t = (tag *) malloc(sizeof(tag));
275 setTag(t, name, file, lang, search, posInf, path);
276 t->index = index;
277 t->next = table[addr];
278 table[addr] = t;
279 return 1;
282 /* Delete a tag from the cache.
283 * Search is limited to valid matches of 'name','file', 'search', posInf, and 'index'.
284 * EX: delete all tags matching index 2 ==>
285 * delTag(tagname,NULL,-2,NULL,-2,2);
286 * (posInf = -2 is an invalid match, posInf range: -1 .. +MAXINT,
287 lang = -2 is also an invalid match)
289 static int delTag(const char *name, const char *file, int lang,
290 const char *search, int posInf, int index)
292 tag *t, *last;
293 int start,finish,i,del=0;
294 tag **table;
296 if (searchMode == TIP)
297 table = Tips;
298 else
299 table = Tags;
301 if (table == NULL) return FALSE;
302 if (name)
303 start = finish = hashAddr(name) % DefTagHashSize;
304 else {
305 start = 0;
306 finish = DefTagHashSize;
308 for (i = start; i<finish; i++) {
309 for (last = NULL, t = table[i]; t; last = t, t = t?t->next:table[i]) {
310 if (name && strcmp(name,t->name)) continue;
311 if (index && index != t->index) continue;
312 if (file && strcmp(file,t->file)) continue;
313 if (lang >= PLAIN_LANGUAGE_MODE && lang != t->language) continue;
314 if (search && strcmp(search,t->searchString)) continue;
315 if (posInf == t->posInf) continue;
316 if (last)
317 last->next = t->next;
318 else
319 table[i] = t->next;
320 rcs_free(t->name);
321 rcs_free(t->file);
322 rcs_free(t->searchString);
323 rcs_free(t->path);
324 free(t);
325 t = NULL;
326 del++;
329 return del>0;
332 /* used in AddRelTagsFile and AddTagsFile */
333 static int tagFileIndex = 0;
336 ** AddRelTagsFile(): Rescan tagSpec for relative tag file specs
337 ** (not starting with [/~]) and extend the tag files list if in
338 ** windowPath a tags file matching the relative spec has been found.
340 int AddRelTagsFile(const char *tagSpec, const char *windowPath, int file_type)
342 tagFile *t;
343 int added=0;
344 struct stat statbuf;
345 char *filename;
346 char pathName[MAXPATHLEN];
347 char *tmptagSpec;
348 tagFile *FileList;
350 searchMode = file_type;
351 if (searchMode == TAG)
352 FileList = TagsFileList;
353 else
354 FileList = TipsFileList;
356 tmptagSpec = (char *) malloc(strlen(tagSpec)+1);
357 strcpy(tmptagSpec, tagSpec);
358 for (filename = strtok(tmptagSpec, ":"); filename; filename = strtok(NULL, ":")){
359 if (*filename == '/' || *filename == '~')
360 continue;
361 if (windowPath && *windowPath) {
362 strcpy(pathName, windowPath);
363 } else {
364 strcpy(pathName, GetCurrentDir());
366 strcat(pathName, "/");
367 strcat(pathName, filename);
368 NormalizePathname(pathName);
370 for (t = FileList; t && strcmp(t->filename, pathName); t = t->next);
371 if (t) {
372 added=1;
373 continue;
375 if (stat(pathName, &statbuf) != 0)
376 continue;
377 t = (tagFile *) malloc(sizeof(tagFile));
378 t->filename = STRSAVE(pathName);
379 t->loaded = 0;
380 t->date = statbuf.st_mtime;
381 t->index = ++tagFileIndex;
382 t->next = FileList;
383 FileList = setFileListHead(t, file_type);
384 added=1;
386 free(tmptagSpec);
387 updateMenuItems();
388 if (added)
389 return TRUE;
390 else
391 return FALSE;
395 ** AddTagsFile(): Set up the the list of tag files to manage from a file spec.
396 ** The file spec comes from the X-Resource Nedit.tags: It can list multiple
397 ** tags files, specified by separating them with colons. The .Xdefaults would
398 ** look like this:
399 ** Nedit.tags: <tagfile1>:<tagfile2>
400 ** Returns True if all files were found in the FileList or loaded successfully,
401 ** FALSE otherwise.
403 int AddTagsFile(const char *tagSpec, int file_type)
405 tagFile *t;
406 int added=1;
407 struct stat statbuf;
408 char *filename;
409 char pathName[MAXPATHLEN];
410 char *tmptagSpec;
411 tagFile *FileList;
413 /* To prevent any possible segfault */
414 if (tagSpec == NULL) {
415 fprintf(stderr, "nedit: Internal Error!\n"
416 " Passed NULL pointer to AddTagsFile!\n");
417 return FALSE;
420 searchMode = file_type;
421 if (searchMode == TAG)
422 FileList = TagsFileList;
423 else
424 FileList = TipsFileList;
426 tmptagSpec = (char *) malloc(strlen(tagSpec)+1);
427 strcpy(tmptagSpec, tagSpec);
428 for (filename = strtok(tmptagSpec,":"); filename; filename = strtok(NULL,":")) {
429 if (*filename != '/') {
430 strcpy(pathName, GetCurrentDir());
431 strcat(pathName,"/");
432 strcat(pathName,filename);
433 } else {
434 strcpy(pathName,filename);
436 NormalizePathname(pathName);
438 for (t = FileList; t && strcmp(t->filename,pathName); t = t->next);
439 if (t) {
440 /* This file is already in the list. It's easiest to just
441 refcount all tag/tip files even though we only actually care
442 about tip files. */
443 ++(t->refcount);
444 added=1;
445 continue;
447 if (stat(pathName,&statbuf) != 0) {
448 /* Problem reading this tags file. Return FALSE */
449 added = 0;
450 continue;
452 t = (tagFile *) malloc(sizeof(tagFile));
453 t->filename = STRSAVE(pathName);
454 t->loaded = 0;
455 t->date = statbuf.st_mtime;
456 t->index = ++tagFileIndex;
457 t->next = FileList;
458 t->refcount = 1;
459 FileList = setFileListHead(t, file_type );
461 free(tmptagSpec);
462 updateMenuItems();
463 if (added)
464 return TRUE;
465 else
466 return FALSE;
469 /* Un-manage a colon-delimited set of tags files
470 * Return TRUE if all files were found in the FileList and unloaded, FALSE
471 * if any file was not found in the FileList.
472 * "file_type" is either TAG or TIP
473 * If "force_unload" is true, a calltips file will be deleted even if its
474 * refcount is nonzero.
476 int DeleteTagsFile(const char *tagSpec, int file_type, Boolean force_unload)
478 tagFile *t, *last;
479 tagFile *FileList;
480 char pathName[MAXPATHLEN], *tmptagSpec, *filename;
481 int removed;
483 /* To prevent any possible segfault */
484 if (tagSpec == NULL) {
485 fprintf(stderr, "nedit: Internal Error: Passed NULL pointer to DeleteTagsFile!\n");
486 return FALSE;
489 searchMode = file_type;
490 if (searchMode == TAG)
491 FileList = TagsFileList;
492 else
493 FileList = TipsFileList;
495 tmptagSpec = (char *) malloc(strlen(tagSpec)+1);
496 strcpy(tmptagSpec, tagSpec);
497 removed=1;
498 for (filename = strtok(tmptagSpec,":"); filename;
499 filename = strtok(NULL,":")) {
500 if (*filename != '/') {
501 strcpy(pathName, GetCurrentDir());
502 strcat(pathName,"/");
503 strcat(pathName,filename);
504 } else {
505 strcpy(pathName,filename);
507 NormalizePathname(pathName);
509 for (last=NULL,t = FileList; t; last = t,t = t->next) {
510 if (strcmp(t->filename, pathName))
511 continue;
512 /* Don't unload tips files with nonzero refcounts unless forced */
513 if (searchMode == TIP && !force_unload && --t->refcount > 0) {
514 break;
516 if (t->loaded)
517 delTag(NULL,NULL,-2,NULL,-2,t->index);
518 if (last) last->next = t->next;
519 else FileList = setFileListHead(t->next, file_type);
520 free(t->filename);
521 free(t);
522 updateMenuItems();
523 break;
525 /* If any file can't be removed, return false */
526 if (!t)
527 removed = 0;
529 if (removed)
530 return TRUE;
531 else
532 return FALSE;
536 ** Update the "Find Definition", "Unload Tags File", "Show Calltip",
537 ** and "Unload Calltips File" menu items in the existing windows.
539 static void updateMenuItems(void)
541 WindowInfo *w;
542 Boolean tipStat=FALSE, tagStat=FALSE;
544 if (TipsFileList) tipStat=TRUE;
545 if (TagsFileList) tagStat=TRUE;
547 for (w=WindowList; w!=NULL; w=w->next) {
548 if (!IsTopDocument(w))
549 continue;
550 XtSetSensitive(w->showTipItem, tipStat || tagStat);
551 XtSetSensitive(w->unloadTipsMenuItem, tipStat);
552 XtSetSensitive(w->findDefItem, tagStat);
553 XtSetSensitive(w->unloadTagsMenuItem, tagStat);
558 ** Scans one <line> from a ctags tags file (<index>) in tagPath.
559 ** Return value: Number of tag specs added.
561 static int scanCTagsLine(const char *line, const char *tagPath, int index)
563 char name[MAXLINE], searchString[MAXLINE];
564 char file[MAXPATHLEN];
565 char *posTagREEnd, *posTagRENull;
566 int nRead, pos;
568 nRead = sscanf(line, "%s\t%s\t%[^\n]", name, file, searchString);
569 if (nRead != 3)
570 return 0;
571 if ( *name == '!' )
572 return 0;
575 ** Guess the end of searchString:
576 ** Try to handle original ctags and exuberant ctags format:
578 if(searchString[0] == '/' || searchString[0] == '?') {
580 pos=-1; /* "search expr without pos info" */
582 /* Situations: /<ANY expr>/\0
583 ** ?<ANY expr>?\0 --> original ctags
584 ** /<ANY expr>/;" <flags>
585 ** ?<ANY expr>?;" <flags> --> exuberant ctags
587 posTagREEnd = strrchr(searchString, ';');
588 posTagRENull = strchr(searchString, 0);
589 if(!posTagREEnd || (posTagREEnd[1] != '"') ||
590 (posTagRENull[-1] == searchString[0])) {
591 /* -> original ctags format = exuberant ctags format 1 */
592 posTagREEnd = posTagRENull;
593 } else {
594 /* looks like exuberant ctags format 2 */
595 *posTagREEnd = 0;
599 ** Hide the last delimiter:
600 ** /<expression>/ becomes /<expression>
601 ** ?<expression>? becomes ?<expression>
602 ** This will save a little work in fakeRegExSearch.
604 if(posTagREEnd > (searchString+2)) {
605 posTagREEnd--;
606 if(searchString[0] == *posTagREEnd)
607 *posTagREEnd=0;
609 } else {
610 pos=atoi(searchString);
611 *searchString=0;
613 /* No ability to read language mode right now */
614 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos, tagPath,
615 index);
619 * Scans one <line> from an etags (emacs) tags file (<index>) in tagPath.
620 * recLevel = current recursion level for tags file including
621 * file = destination definition file. possibly modified. len=MAXPATHLEN!
622 * Return value: Number of tag specs added.
624 static int scanETagsLine(const char *line, const char * tagPath, int index,
625 char * file, int recLevel)
627 char name[MAXLINE], searchString[MAXLINE];
628 char incPath[MAXPATHLEN];
629 int pos, len;
630 char *posDEL, *posSOH, *posCOM;
632 /* check for destination file separator */
633 if(line[0]==12) { /* <np> */
634 *file=0;
635 return 0;
638 /* check for standard definition line */
639 posDEL=strchr(line, '\177');
640 posSOH=strchr(line, '\001');
641 posCOM=strrchr(line, ',');
642 if(*file && posDEL && (posSOH > posDEL) && (posCOM > posSOH)) {
643 /* exuberant ctags -e style */
644 len=Min(MAXLINE-1, posDEL - line);
645 strncpy(searchString, line, len);
646 searchString[len]=0;
647 len=Min(MAXLINE-1, (posSOH - posDEL) - 1);
648 strncpy(name, posDEL + 1, len);
649 name[len]=0;
650 pos=atoi(posCOM+1);
651 /* No ability to set language mode for the moment */
652 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos,
653 tagPath, index);
655 if (*file && posDEL && (posCOM > posDEL)) {
656 /* old etags style, part name<soh> is missing here! */
657 len=Min(MAXLINE-1, posDEL - line);
658 strncpy(searchString, line, len);
659 searchString[len]=0;
660 /* guess name: take the last alnum (plus _) part of searchString */
661 while(--len >= 0) {
662 if( isalnum((unsigned char)searchString[len]) ||
663 (searchString[len] == '_'))
664 break;
666 if(len<0)
667 return 0;
668 pos=len;
669 while (pos >= 0 && (isalnum((unsigned char)searchString[pos]) ||
670 (searchString[pos] == '_')))
671 pos--;
672 strncpy(name, searchString + pos + 1, len - pos);
673 name[len - pos] = 0; /* name ready */
674 pos=atoi(posCOM+1);
675 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos,
676 tagPath, index);
678 /* check for destination file spec */
679 if(*line && posCOM) {
680 len=Min(MAXPATHLEN-1, posCOM - line);
681 strncpy(file, line, len);
682 file[len]=0;
683 /* check if that's an include file ... */
684 if(!(strncmp(posCOM+1, "include", 7))) {
685 if(*file != '/') {
686 if((strlen(tagPath) + strlen(file)) >= MAXPATHLEN) {
687 fprintf(stderr, "tags.c: MAXPATHLEN overflow\n");
688 *file=0; /* invalidate */
689 return 0;
691 strcpy(incPath, tagPath);
692 strcat(incPath, file);
693 CompressPathname(incPath);
694 return(loadTagsFile(incPath, index, recLevel+1));
695 } else {
696 return(loadTagsFile(file, index, recLevel+1));
700 return 0;
703 /* Tag File Type */
704 typedef enum {
705 TFT_CHECK, TFT_ETAGS, TFT_CTAGS
706 } TFT;
709 ** Loads tagsFile into the hash table.
710 ** Returns the number of added tag specifications.
712 static int loadTagsFile(const char *tagsFile, int index, int recLevel)
714 FILE *fp = NULL;
715 char line[MAXLINE];
716 char file[MAXPATHLEN], tagPath[MAXPATHLEN];
717 char resolvedTagsFile[MAXPATHLEN+1];
718 int nTagsAdded=0;
719 int tagFileType = TFT_CHECK;
721 if(recLevel > MAX_TAG_INCLUDE_RECURSION_LEVEL) {
722 return 0;
724 /* the path of the tags file must be resolved to find the right files:
725 * definition source files are (in most cases) specified relatively inside
726 * the tags file to the tags files directory.
728 if(!ResolvePath(tagsFile, resolvedTagsFile)) {
729 return 0;
732 /* Open the file */
733 if ((fp = fopen(resolvedTagsFile, "r")) == NULL) {
734 return 0;
737 ParseFilename(resolvedTagsFile, NULL, tagPath);
739 /* Read the file and store its contents */
740 while (fgets(line, MAXLINE, fp)) {
742 /* This might take a while if you have a huge tags file (like I do)..
743 keep the windows up to date and post a busy cursor so the user
744 doesn't think we died. */
746 AllWindowsBusy("Loading tags file...");
748 /* the first character in the file decides if the file is treat as
749 etags or ctags file.
751 if(tagFileType==TFT_CHECK) {
752 if(line[0]==12) /* <np> */
753 tagFileType=TFT_ETAGS;
754 else
755 tagFileType=TFT_CTAGS;
757 if(tagFileType==TFT_CTAGS) {
758 nTagsAdded += scanCTagsLine(line, tagPath, index);
759 } else {
760 nTagsAdded += scanETagsLine(line, tagPath, index, file, recLevel);
763 fclose(fp);
765 AllWindowsUnbusy();
766 return nTagsAdded;
770 ** Given a tag name, lookup the file and path of the definition
771 ** and the proper search string. Returned strings are pointers
772 ** to internal storage which are valid until the next loadTagsFile call.
774 ** Invocation with name != NULL (containing the searched definition)
775 ** --> returns first definition of name
776 ** Successive invocation with name == NULL
777 ** --> returns further definitions (resulting from multiple tags files)
779 ** Return Value: TRUE: tag spec found
780 ** FALSE: no (more) definitions found.
782 #define TAG_STS_ERR_FMT "NEdit: Error getting status for tag file %s\n"
783 int LookupTag(const char *name, const char **file, int *language,
784 const char **searchString, int * pos, const char **path,
785 int search_type)
787 tag *t;
788 tagFile *tf;
789 struct stat statbuf;
790 tagFile *FileList;
791 int load_status;
793 searchMode = search_type;
794 if (searchMode == TIP)
795 FileList = TipsFileList;
796 else
797 FileList = TagsFileList;
800 ** Go through the list of all tags Files:
801 ** - load them (if not already loaded)
802 ** - check for update of the tags file and reload it in that case
803 ** - save the modification date of the tags file
805 ** Do this only as long as name != NULL, not for sucessive calls
806 ** to find multiple tags specs.
809 for (tf = FileList; tf && name; tf = tf->next) {
810 if (tf->loaded) {
811 if (stat(tf->filename,&statbuf) != 0) { /* */
812 fprintf(stderr, TAG_STS_ERR_FMT, tf->filename);
813 } else {
814 if (tf->date == statbuf.st_mtime) {
815 /* current tags file tf is already loaded and up to date */
816 continue;
819 /* tags file has been modified, delete it's entries and reload it */
820 delTag(NULL,NULL,-2,NULL,-2,tf->index);
822 /* If we get here we have to try to (re-) load the tags file */
823 if (FileList == TipsFileList)
824 load_status = loadTipsFile(tf->filename, tf->index, 0);
825 else
826 load_status = loadTagsFile(tf->filename, tf->index, 0);
827 if(load_status) {
828 if (stat(tf->filename,&statbuf) != 0) {
829 if(!tf->loaded) {
830 /* if tf->loaded == 1 we already have seen the error msg */
831 fprintf(stderr, TAG_STS_ERR_FMT, tf->filename);
833 } else {
834 tf->date = statbuf.st_mtime;
836 tf->loaded = 1;
837 } else {
838 tf->loaded = 0;
842 t = getTag(name, search_type);
844 if (!t) {
845 return FALSE;
846 } else {
847 *file = t->file;
848 *language = t->language;
849 *searchString = t->searchString;
850 *pos = t->posInf;
851 *path = t->path;
852 return TRUE;
857 ** This code path is followed if the request came from either
858 ** FindDefinition or FindDefCalltip. This should probably be refactored.
860 static int findDef(WindowInfo *window, const char *value, int search_type) {
861 static char tagText[MAX_TAG_LEN + 1];
862 const char *p;
863 char message[MAX_TAG_LEN+40];
864 int l, ml, status = 0;
866 searchMode = search_type;
867 l = strlen(value);
868 if (l <= MAX_TAG_LEN) {
869 /* should be of type text??? */
870 for (p = value; *p && isascii(*p); p++) {
872 if (!(*p)) {
873 ml = ((l < MAX_TAG_LEN) ? (l) : (MAX_TAG_LEN));
874 strncpy(tagText, value, ml);
875 tagText[ml] = '\0';
876 /* See if we can find the tip/tag */
877 status = findAllMatches(window, tagText);
878 /* If we didn't find a requested calltip, see if we can use a tag */
879 if (status == 0 && search_type == TIP && TagsFileList != NULL) {
880 searchMode = TIP_FROM_TAG;
881 status = findAllMatches(window, tagText);
883 if (status == 0) {
884 /* Didn't find any matches */
885 if (searchMode == TIP_FROM_TAG || searchMode == TIP) {
886 sprintf(message, "No match for \"%s\" in calltips or tags.",
887 tagName);
888 tagsShowCalltip( window, message );
889 } else
891 DialogF(DF_WARN, window->textArea, 1, "Tags",
892 "\"%s\" not found in tags file%s", "OK", tagName,
893 (TagsFileList && TagsFileList->next) ? "s" : "");
897 else {
898 fprintf(stderr, "NEdit: Can't handle non 8-bit text\n");
899 XBell(TheDisplay, 0);
902 else {
903 fprintf(stderr, "NEdit: Tag Length too long.\n");
904 XBell(TheDisplay, 0);
906 return status;
910 ** Lookup the definition for the current primary selection the currently
911 ** loaded tags file and bring up the file and line that the tags file
912 ** indicates.
914 static void findDefinitionHelper(WindowInfo *window, Time time, const char *arg,
915 int search_type)
917 if(arg)
919 findDef(window, arg, search_type);
921 else
923 searchMode = search_type;
924 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING,
925 (XtSelectionCallbackProc)findDefCB, window, time);
930 ** See findDefHelper
932 void FindDefinition(WindowInfo *window, Time time, const char *arg)
934 findDefinitionHelper(window, time, arg, TAG);
938 ** See findDefHelper
940 void FindDefCalltip(WindowInfo *window, Time time, const char *arg)
942 /* Reset calltip parameters to reasonable defaults */
943 globAnchored = False;
944 globPos = -1;
945 globHAlign = TIP_LEFT;
946 globVAlign = TIP_BELOW;
947 globAlignMode = TIP_SLOPPY;
949 findDefinitionHelper(window, time, arg, TIP);
952 /* Callback function for FindDefinition */
953 static void findDefCB(Widget widget, WindowInfo *window, Atom *sel,
954 Atom *type, char *value, int *length, int *format)
956 /* skip if we can't get the selection data, or it's obviously too long */
957 if (*type == XT_CONVERT_FAIL || value == NULL) {
958 XBell(TheDisplay, 0);
959 } else {
960 findDef(window, value, searchMode);
962 XtFree(value);
966 ** Try to display a calltip
967 ** anchored: If true, tip appears at position pos
968 ** lookup: If true, text is considered a key to be searched for in the
969 ** tip and/or tag database depending on search_type
970 ** search_type: Either TIP or TIP_FROM_TAG
972 int ShowTipString(WindowInfo *window, char *text, Boolean anchored,
973 int pos, Boolean lookup, int search_type, int hAlign, int vAlign,
974 int alignMode) {
976 if (search_type == TAG) return 0;
978 /* So we don't have to carry all of the calltip alignment info around */
979 globAnchored = anchored;
980 globPos = pos;
981 globHAlign = hAlign;
982 globVAlign = vAlign;
983 globAlignMode = alignMode;
985 /* If this isn't a lookup request, just display it. */
986 if (!lookup)
987 return tagsShowCalltip(window, text);
988 else
989 return findDef(window, text, search_type);
992 /* store all of the info into a pre-allocated tags struct */
993 static void setTag(tag *t, const char *name, const char *file,
994 int language, const char *searchString, int posInf,
995 const char *path)
997 t->name = rcs_strdup(name);
998 t->file = rcs_strdup(file);
999 t->language = language;
1000 t->searchString = rcs_strdup(searchString);
1001 t->posInf = posInf;
1002 t->path = rcs_strdup(path);
1006 ** ctags search expressions are literal strings with a search direction flag,
1007 ** line starting "^" and ending "$" delimiters. This routine translates them
1008 ** into NEdit compatible regular expressions and does the search.
1009 ** Etags search expressions are plain literals strings, which
1011 ** If in_buffer is not NULL then it is searched instead of the window buffer.
1012 ** In this case in_buffer should be an XtMalloc allocated buffer and the
1013 ** caller is responsible for freeing it.
1015 static int fakeRegExSearch(WindowInfo *window, char *in_buffer,
1016 const char *searchString, int *startPos, int *endPos)
1018 int found, searchStartPos, dir, ctagsMode;
1019 char searchSubs[3*MAXLINE+3], *outPtr;
1020 const char *fileString, *inPtr;
1022 if (in_buffer == NULL) {
1023 /* get the entire (sigh) text buffer from the text area widget */
1024 fileString = BufAsString(window->buffer);
1025 } else {
1026 fileString = in_buffer;
1029 /* determine search direction and start position */
1030 if (*startPos != -1) { /* etags mode! */
1031 dir = SEARCH_FORWARD;
1032 searchStartPos = *startPos;
1033 ctagsMode=0;
1034 } else if (searchString[0] == '/') {
1035 dir = SEARCH_FORWARD;
1036 searchStartPos = 0;
1037 ctagsMode=1;
1038 } else if (searchString[0] == '?') {
1039 dir = SEARCH_BACKWARD;
1040 /* searchStartPos = window->buffer->length; */
1041 searchStartPos = strlen(fileString);
1042 ctagsMode=1;
1043 } else {
1044 fprintf(stderr, "NEdit: Error parsing tag file search string");
1045 return FALSE;
1048 /* Build the search regex. */
1049 outPtr=searchSubs;
1050 if(ctagsMode) {
1051 inPtr=searchString+1; /* searchString[0] is / or ? --> search dir */
1052 if(*inPtr == '^') {
1053 /* If the first char is a caret then it's a RE line start delim */
1054 *outPtr++ = *inPtr++;
1056 } else { /* etags mode, no search dir spec, no leading caret */
1057 inPtr=searchString;
1059 while(*inPtr) {
1060 if( (*inPtr=='\\' && inPtr[1]=='/') ||
1061 (*inPtr=='\r' && inPtr[1]=='$' && !inPtr[2])
1063 /* Remove:
1064 - escapes (added by standard and exuberant ctags) from slashes
1065 - literal CRs generated by standard ctags for DOSified sources
1067 inPtr++;
1068 } else if(strchr("()-[]<>{}.|^*+?&\\", *inPtr)
1069 || (*inPtr == '$' && (inPtr[1]||(!ctagsMode)))){
1070 /* Escape RE Meta Characters to match them literally.
1071 Don't escape $ if it's the last charcter of the search expr
1072 in ctags mode; always escape $ in etags mode.
1074 *outPtr++ = '\\';
1075 *outPtr++ = *inPtr++;
1076 } else if (isspace((unsigned char)*inPtr)) { /* col. multiple spaces */
1077 *outPtr++ = '\\';
1078 *outPtr++ = 's';
1079 *outPtr++ = '+';
1080 do { inPtr++ ; } while(isspace((unsigned char)*inPtr));
1081 } else { /* simply copy all other characters */
1082 *outPtr++ = *inPtr++;
1085 *outPtr=0; /* Terminate searchSubs */
1087 found = SearchString(fileString, searchSubs, dir, SEARCH_REGEX,
1088 False, searchStartPos, startPos, endPos, NULL, NULL, NULL);
1090 if(!found && !ctagsMode) {
1091 /* position of the target definition could have been drifted before
1092 startPos, if nothing has been found by now try searching backward
1093 again from startPos.
1095 found = SearchString(fileString, searchSubs, SEARCH_BACKWARD,
1096 SEARCH_REGEX, False, searchStartPos, startPos, endPos, NULL,
1097 NULL, NULL);
1100 /* return the result */
1101 if (found) {
1102 /* *startPos and *endPos are set in SearchString*/
1103 return TRUE;
1104 } else {
1105 /* startPos, endPos left untouched by SearchString if search failed. */
1106 XBell(TheDisplay, 0);
1107 return FALSE;
1111 /* Finds all matches and handles tag "collisions". Prompts user with a
1112 list of collided tags in the hash table and allows the user to select
1113 the correct one. */
1114 static int findAllMatches(WindowInfo *window, const char *string)
1116 Widget dialogParent = window->textArea;
1117 char filename[MAXPATHLEN], pathname[MAXPATHLEN];
1118 char temp[32+2*MAXPATHLEN+MAXLINE];
1119 const char *fileToSearch, *searchString, *tagPath;
1120 char **dupTagsList;
1121 int startPos, i, pathMatch=0, samePath=0, langMode, nMatches=0;
1123 /* verify that the string is reasonable as a tag */
1124 if (*string == '\0' || strlen(string) > MAX_TAG_LEN) {
1125 XBell(TheDisplay, 0);
1126 return -1;
1128 tagName=string;
1130 /* First look up all of the matching tags */
1131 while (LookupTag(string, &fileToSearch, &langMode, &searchString, &startPos,
1132 &tagPath, searchMode)) {
1133 /* Skip this tag if it has a language mode that doesn't match the
1134 current language mode, but don't skip anything if the window is in
1135 PLAIN_LANGUAGE_MODE. */
1136 if (window->languageMode != PLAIN_LANGUAGE_MODE &&
1137 GetPrefSmartTags() && langMode != PLAIN_LANGUAGE_MODE &&
1138 langMode != window->languageMode) {
1139 string=NULL;
1140 continue;
1142 if (*fileToSearch == '/')
1143 strcpy(tagFiles[nMatches], fileToSearch);
1144 else
1145 sprintf(tagFiles[nMatches],"%s%s",tagPath,fileToSearch);
1146 strcpy(tagSearch[nMatches],searchString);
1147 tagPosInf[nMatches]=startPos;
1148 ParseFilename(tagFiles[nMatches], filename, pathname);
1149 /* Is this match in the current file? If so, use it! */
1150 if (GetPrefSmartTags() && !strcmp(window->filename,filename)
1151 && !strcmp(window->path,pathname) ) {
1152 if (nMatches) {
1153 strcpy(tagFiles[0],tagFiles[nMatches]);
1154 strcpy(tagSearch[0],tagSearch[nMatches]);
1155 tagPosInf[0]=tagPosInf[nMatches];
1157 nMatches = 1;
1158 break;
1160 /* Is this match in the same dir. as the current file? */
1161 if (!strcmp(window->path,pathname)) {
1162 samePath++;
1163 pathMatch=nMatches;
1165 if (++nMatches >= MAXDUPTAGS) {
1166 DialogF(DF_WARN, dialogParent, 1, "Tags",
1167 "Too many duplicate tags, first %d shown", "OK", MAXDUPTAGS);
1168 break;
1170 /* Tell LookupTag to look for more definitions of the same tag: */
1171 string = NULL;
1174 /* Did we find any matches? */
1175 if (!nMatches) {
1176 return 0;
1179 /* Only one of the matches is in the same dir. as this file. Use it. */
1180 if (GetPrefSmartTags() && samePath == 1 && nMatches > 1) {
1181 strcpy(tagFiles[0],tagFiles[pathMatch]);
1182 strcpy(tagSearch[0],tagSearch[pathMatch]);
1183 tagPosInf[0]=tagPosInf[pathMatch];
1184 nMatches = 1;
1187 /* If all of the tag entries are the same file, just use the first.
1189 if (GetPrefSmartTags()) {
1190 for (i=1; i<nMatches; i++)
1191 if (strcmp(tagFiles[i],tagFiles[i-1]))
1192 break;
1193 if (i==nMatches)
1194 nMatches = 1;
1197 if (nMatches>1) {
1198 if (!(dupTagsList = (char **) malloc(sizeof(char *) * nMatches))) {
1199 fprintf(stderr, "NEdit: findDef(): out of heap space!\n");
1200 XBell(TheDisplay, 0);
1201 return -1;
1203 for (i=0; i<nMatches; i++) {
1204 ParseFilename(tagFiles[i], filename, pathname);
1205 if ((i<nMatches-1 && !strcmp(tagFiles[i],tagFiles[i+1])) ||
1206 (i>0 && !strcmp(tagFiles[i],tagFiles[i-1]))) {
1207 if(*(tagSearch[i]) && (tagPosInf[i] != -1)) { /* etags */
1208 sprintf(temp,"%2d. %s%s %8i %s", i+1, pathname,
1209 filename, tagPosInf[i], tagSearch[i]);
1210 } else if (*(tagSearch[i])) { /* ctags search expr */
1211 sprintf(temp,"%2d. %s%s %s", i+1, pathname,
1212 filename, tagSearch[i]);
1213 } else { /* line number only */
1214 sprintf(temp,"%2d. %s%s %8i", i+1, pathname, filename,
1215 tagPosInf[i]);
1217 } else
1218 sprintf(temp,"%2d. %s%s",i+1,pathname,filename);
1219 if (!(dupTagsList[i] = (char *) malloc(strlen(temp) + 1))) {
1220 fprintf(stderr, "NEdit: findDef(): out of heap space!\n");
1221 XBell(TheDisplay, 0);
1222 return -1;
1224 strcpy(dupTagsList[i],temp);
1226 createSelectMenu(dialogParent, "Duplicate Tags", nMatches, dupTagsList);
1227 for (i=0; i<nMatches; i++)
1228 free(dupTagsList[i]);
1229 free(dupTagsList);
1230 return 1;
1233 ** No need for a dialog list, there is only one tag matching --
1234 ** Go directly to the tag
1236 if (searchMode == TAG)
1237 editTaggedLocation( dialogParent, 0 );
1238 else
1239 showMatchingCalltip( dialogParent, 0 );
1240 return 1;
1243 /* Callback function for the FindAll widget. Process the users response. */
1244 static void findAllCB(Widget parent, XtPointer client_data, XtPointer call_data)
1246 int i;
1247 char *eptr;
1249 XmSelectionBoxCallbackStruct *cbs =
1250 (XmSelectionBoxCallbackStruct *) call_data;
1251 if (cbs->reason == XmCR_NO_MATCH)
1252 return;
1253 if (cbs->reason == XmCR_CANCEL) {
1254 XtDestroyWidget(XtParent(parent));
1255 return;
1258 XmStringGetLtoR(cbs->value,XmFONTLIST_DEFAULT_TAG,&eptr);
1259 if ((i = atoi(eptr)-1) < 0) {
1260 XBell(TheDisplay, 0);
1261 return;
1264 if (searchMode == TAG)
1265 editTaggedLocation( parent, i ); /* Open the file with the definition */
1266 else
1267 showMatchingCalltip( parent, i );
1269 if (cbs->reason == XmCR_OK)
1270 XtDestroyWidget(XtParent(parent));
1273 /* Window manager close-box callback for tag-collision dialog */
1274 static void findAllCloseCB(Widget parent, XtPointer client_data,
1275 XtPointer call_data)
1277 XtDestroyWidget(parent);
1281 * Given a \0 terminated string and a position, advance the position
1282 * by n lines, where line separators (for now) are \n. If the end of
1283 * string is reached before n lines, return the number of lines advanced,
1284 * else normally return -1.
1286 static int moveAheadNLines( char *str, int *pos, int n ) {
1287 int i=n;
1288 while (str[*pos] != '\0' && n>0) {
1289 if (str[*pos] == '\n')
1290 --n;
1291 ++(*pos);
1293 if (n==0)
1294 return -1;
1295 else
1296 return i-n;
1300 ** Show the calltip specified by tagFiles[i], tagSearch[i], tagPosInf[i]
1301 ** This reads from either a source code file (if searchMode == TIP_FROM_TAG)
1302 ** or a calltips file (if searchMode == TIP).
1304 static void showMatchingCalltip( Widget parent, int i )
1306 int startPos=0, fileLen, readLen, tipLen;
1307 int endPos=0;
1308 char *fileString;
1309 FILE *fp;
1310 struct stat statbuf;
1311 char *message;
1313 /* 1. Open the target file */
1314 NormalizePathname(tagFiles[i]);
1315 fp = fopen(tagFiles[i], "r");
1316 if (fp == NULL) {
1317 DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s",
1318 "OK", tagFiles[i]);
1319 return;
1321 if (fstat(fileno(fp), &statbuf) != 0) {
1322 fclose(fp);
1323 DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s",
1324 "OK", tagFiles[i]);
1325 return;
1328 /* 2. Read the target file */
1329 /* Allocate space for the whole contents of the file (unfortunately) */
1330 fileLen = statbuf.st_size;
1331 fileString = XtMalloc(fileLen+1); /* +1 = space for null */
1332 if (fileString == NULL) {
1333 fclose(fp);
1334 DialogF(DF_ERR, parent, 1, "File too large",
1335 "File is too large to load", "OK");
1336 return;
1339 /* Read the file into fileString and terminate with a null */
1340 readLen = fread(fileString, sizeof(char), fileLen, fp);
1341 if (ferror(fp)) {
1342 fclose(fp);
1343 DialogF(DF_ERR, parent, 1, "Error reading File", "Error reading %s",
1344 "OK", tagFiles[i]);
1345 XtFree(fileString);
1346 return;
1348 fileString[readLen] = 0;
1350 /* Close the file */
1351 if (fclose(fp) != 0) {
1352 /* unlikely error */
1353 DialogF(DF_WARN, parent, 1, "Error closing File",
1354 "Unable to close file", "OK");
1355 /* we read it successfully, so continue */
1358 /* 3. Search for the tagged location (set startPos) */
1359 if (!*(tagSearch[i])) {
1360 /* It's a line number, just go for it */
1361 if ((moveAheadNLines( fileString, &startPos, tagPosInf[i]-1 )) >= 0) {
1362 DialogF(DF_ERR, parent, 1, "Tags Error",
1363 "%s\n not long enough for definition to be on line %d",
1364 "OK", tagFiles[i], tagPosInf[i]);
1365 XtFree(fileString);
1366 return;
1368 } else {
1369 startPos = tagPosInf[i];
1370 if(!fakeRegExSearch(WidgetToWindow(parent), fileString, tagSearch[i],
1371 &startPos, &endPos)){
1372 DialogF(DF_WARN, parent, 1, "Tag not found",
1373 "Definition for %s\nnot found in %s", "OK", tagName,
1374 tagFiles[i]);
1375 XtFree(fileString);
1376 return;
1380 if (searchMode == TIP) {
1381 int dummy, found;
1383 /* 4. Find the end of the calltip (delimited by an empty line) */
1384 endPos = startPos;
1385 found = SearchString(fileString, "\\n\\s*\\n", SEARCH_FORWARD,
1386 SEARCH_REGEX, False, startPos, &endPos, &dummy, NULL,
1387 NULL, NULL);
1388 if (!found) {
1389 /* Just take 4 lines */
1390 moveAheadNLines( fileString, &endPos, TIP_DEFAULT_LINES );
1391 --endPos; /* Lose the last \n */
1393 } else { /* Mode = TIP_FROM_TAG */
1394 /* 4. Copy TIP_DEFAULT_LINES lines of text to the calltip string */
1395 endPos = startPos;
1396 moveAheadNLines( fileString, &endPos, TIP_DEFAULT_LINES );
1397 /* Make sure not to overrun the fileString with ". . ." */
1398 if (((size_t) endPos) <= (strlen(fileString)-5)) {
1399 sprintf( &fileString[endPos], ". . ." );
1400 endPos += 5;
1403 /* 5. Copy the calltip to a string */
1404 tipLen = endPos - startPos;
1405 message = XtMalloc(tipLen+1); /* +1 = space for null */
1406 if (message == NULL)
1408 DialogF(DF_ERR, parent, 1, "Out of Memory",
1409 "Can't allocate memory for calltip message", "OK");
1410 XtFree(fileString);
1411 return;
1413 strncpy( message, &fileString[startPos], tipLen );
1414 message[tipLen] = 0;
1416 /* 6. Display it */
1417 tagsShowCalltip( WidgetToWindow(parent), message );
1418 XtFree(message);
1419 XtFree(fileString);
1422 /* Open a new (or existing) editor window to the location specified in
1423 tagFiles[i], tagSearch[i], tagPosInf[i] */
1424 static void editTaggedLocation( Widget parent, int i )
1426 /* Globals: tagSearch, tagPosInf, tagFiles, tagName, textNrows,
1427 WindowList */
1428 int startPos, endPos, lineNum, rows;
1429 char filename[MAXPATHLEN], pathname[MAXPATHLEN];
1430 WindowInfo *windowToSearch;
1431 WindowInfo *parentWindow = WidgetToWindow(parent);
1433 ParseFilename(tagFiles[i],filename,pathname);
1434 /* open the file containing the definition */
1435 EditExistingFile(parentWindow, filename, pathname, 0, NULL, False,
1436 NULL, GetPrefOpenInTab(), False);
1437 windowToSearch = FindWindowWithFile(filename, pathname);
1438 if (windowToSearch == NULL) {
1439 DialogF(DF_WARN, parent, 1, "File not found", "File %s not found", "OK",
1440 tagFiles[i]);
1441 return;
1444 startPos=tagPosInf[i];
1446 if(!*(tagSearch[i])) {
1447 /* if the search string is empty, select the numbered line */
1448 SelectNumberedLine(windowToSearch, startPos);
1449 return;
1452 /* search for the tags file search string in the newly opened file */
1453 if(!fakeRegExSearch(windowToSearch, NULL, tagSearch[i], &startPos,
1454 &endPos)){
1455 DialogF(DF_WARN, windowToSearch->shell, 1, "Tag Error",
1456 "Definition for %s\nnot found in %s", "OK", tagName,
1457 tagFiles[i]);
1458 return;
1461 /* select the matched string */
1462 BufSelect(windowToSearch->buffer, startPos, endPos);
1463 RaiseFocusDocumentWindow(windowToSearch, True);
1465 /* Position it nicely in the window,
1466 about 1/4 of the way down from the top */
1467 lineNum = BufCountLines(windowToSearch->buffer, 0, startPos);
1468 XtVaGetValues(windowToSearch->lastFocus, textNrows, &rows, NULL);
1469 TextSetScroll(windowToSearch->lastFocus, lineNum - rows/4, 0);
1470 TextSetCursorPos(windowToSearch->lastFocus, endPos);
1473 /* Create a Menu for user to select from the collided tags */
1474 static Widget createSelectMenu(Widget parent, char *label, int nArgs,
1475 char *args[])
1477 int i;
1478 char tmpStr[100];
1479 Widget menu;
1480 XmStringTable list;
1481 XmString popupTitle;
1482 int ac;
1483 Arg csdargs[20];
1485 list = (XmStringTable) XtMalloc(nArgs * sizeof(XmString *));
1486 for (i=0; i<nArgs; i++)
1487 list[i] = XmStringCreateSimple(args[i]);
1488 sprintf(tmpStr,"Select File With TAG: %s",tagName);
1489 popupTitle = XmStringCreateSimple(tmpStr);
1490 ac = 0;
1491 XtSetArg(csdargs[ac], XmNlistLabelString, popupTitle); ac++;
1492 XtSetArg(csdargs[ac], XmNlistItems, list); ac++;
1493 XtSetArg(csdargs[ac], XmNlistItemCount, nArgs); ac++;
1494 XtSetArg(csdargs[ac], XmNvisibleItemCount, 12); ac++;
1495 XtSetArg(csdargs[ac], XmNautoUnmanage, False); ac++;
1496 menu = CreateSelectionDialog(parent,label,csdargs,ac);
1497 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_TEXT));
1498 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_HELP_BUTTON));
1499 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_SELECTION_LABEL));
1500 XtAddCallback(menu, XmNokCallback, (XtCallbackProc)findAllCB, menu);
1501 XtAddCallback(menu, XmNapplyCallback, (XtCallbackProc)findAllCB, menu);
1502 XtAddCallback(menu, XmNcancelCallback, (XtCallbackProc)findAllCB, menu);
1503 AddMotifCloseCallback(XtParent(menu), findAllCloseCB, NULL);
1504 for (i=0; i<nArgs; i++)
1505 XmStringFree(list[i]);
1506 XtFree((char *)list);
1507 XmStringFree(popupTitle);
1508 ManageDialogCenteredOnPointer(menu);
1509 return menu;
1514 /*--------------------------------------------------------------------------
1516 Reference-counted string hack; SJT 4/2000
1518 This stuff isn't specific to tags, so it should be in it's own file.
1519 However, I'm leaving it in here for now to reduce the diffs.
1521 This could really benefit from using a real hash table.
1524 #define RCS_SIZE 10000
1526 struct rcs;
1528 struct rcs_stats
1530 int talloc, tshar, tgiveup, tbytes, tbyteshared;
1533 struct rcs
1535 struct rcs *next;
1536 char *string;
1537 int usage;
1540 static struct rcs *Rcs[RCS_SIZE];
1541 static struct rcs_stats RcsStats;
1544 ** Take a normal string, create a shared string from it if need be,
1545 ** and return pointer to that shared string.
1547 ** Returned strings are const because they are shared. Do not modify them!
1550 static const char *rcs_strdup(const char *str)
1552 int bucket;
1553 size_t len;
1554 struct rcs *rp;
1555 struct rcs *prev = NULL;
1557 char *newstr = NULL;
1559 if (str == NULL)
1560 return NULL;
1562 bucket = hashAddr(str) % RCS_SIZE;
1563 len = strlen(str);
1565 RcsStats.talloc++;
1567 #if 0
1568 /* Don't share if it won't save space.
1570 Doesn't save anything - if we have lots of small-size objects,
1571 it's beneifical to share them. We don't know until we make a full
1572 count. My tests show that it's better to leave this out. */
1573 if (len <= sizeof(struct rcs))
1575 new_str = strdup(str); /* GET RID OF strdup() IF EVER ENABLED (not ANSI) */
1576 RcsStats.tgiveup++;
1577 return;
1579 #endif
1581 /* Find it in hash */
1582 for (rp = Rcs[bucket]; rp; rp = rp->next)
1584 if (!strcmp(str, rp->string))
1585 break;
1586 prev = rp;
1589 if (rp) /* It exists, return it and bump ref ct */
1591 rp->usage++;
1592 newstr = rp->string;
1594 RcsStats.tshar++;
1595 RcsStats.tbyteshared += len;
1597 else /* Doesn't exist, conjure up a new one. */
1599 struct rcs *newrcs = malloc(sizeof(struct rcs));
1600 newrcs->string = malloc(len+1);
1601 strcpy(newrcs->string, str);
1602 newrcs->usage = 1;
1603 newrcs->next = NULL;
1605 if (Rcs[bucket])
1606 prev->next = newrcs;
1607 else
1608 Rcs[bucket] = newrcs;
1610 newstr = newrcs->string;
1613 RcsStats.tbytes += len;
1614 return newstr;
1618 ** Decrease the reference count on a shared string. When the reference
1619 ** count reaches zero, free the master string.
1622 static void rcs_free(const char *rcs_str)
1624 int bucket;
1625 struct rcs *rp;
1626 struct rcs *prev = NULL;
1628 if (rcs_str == NULL)
1629 return;
1631 bucket = hashAddr(rcs_str) % RCS_SIZE;
1633 /* find it in hash */
1634 for (rp = Rcs[bucket]; rp; rp = rp->next)
1636 if (rcs_str == rp->string)
1637 break;
1638 prev = rp;
1641 if (rp) /* It's a shared string, decrease ref count */
1643 rp->usage--;
1645 if (rp->usage < 0) /* D'OH! */
1647 fprintf(stderr, "NEdit: internal error deallocating shared string.");
1648 return;
1651 if (rp->usage == 0) /* Last one- free the storage */
1653 free(rp->string);
1654 if (prev)
1655 prev->next = rp->next;
1656 else
1657 Rcs[bucket] = rp->next;
1658 free(rp);
1661 else /* Doesn't appear to be a shared string */
1663 fprintf(stderr, "NEdit: attempt to free a non-shared string.");
1664 return;
1668 /********************************************************************
1669 * Functions for loading Calltips files *
1670 ********************************************************************/
1672 enum tftoken_types { TF_EOF, TF_BLOCK, TF_VERSION, TF_INCLUDE, TF_LANGUAGE,
1673 TF_ALIAS, TF_ERROR, TF_ERROR_EOF };
1675 /* A wrapper for SearchString */
1676 static int searchLine(char *line, const char *regex) {
1677 int dummy1, dummy2;
1678 return SearchString(line, regex, SEARCH_FORWARD, SEARCH_REGEX,
1679 False, 0, &dummy1, &dummy2, NULL, NULL, NULL);
1682 /* Check if a line has non-ws characters */
1683 static Boolean lineEmpty(const char *line) {
1684 while (*line && *line != '\n') {
1685 if (*line != ' ' && *line != '\t')
1686 return False;
1687 ++line;
1689 return True;
1692 /* Remove trailing whitespace from a line */
1693 static void rstrip( char *dst, const char *src ) {
1694 int wsStart, dummy2;
1695 /* Strip trailing whitespace */
1696 if(SearchString(src, "\\s*\\n", SEARCH_FORWARD, SEARCH_REGEX,
1697 False, 0, &wsStart, &dummy2, NULL, NULL, NULL)) {
1698 if(dst != src)
1699 memcpy(dst, src, wsStart);
1700 dst[wsStart] = 0;
1701 } else
1702 if(dst != src)
1703 strcpy(dst, src);
1707 ** Get the next block from a tips file. A block is a \n\n+ delimited set of
1708 ** lines in a calltips file. All of the parameters except <fp> are return
1709 ** values, and most have different roles depending on the type of block
1710 ** that is found.
1711 ** header: Depends on the block type
1712 ** body: Depends on the block type. Used to return a new
1713 ** dynamically allocated string.
1714 ** blkLine: Returns the line number of the first line of the block
1715 ** after the "* xxxx *" line.
1716 ** currLine: Used to keep track of the current line in the file.
1718 static int nextTFBlock(FILE *fp, char *header, char **body, int *blkLine,
1719 int *currLine)
1721 /* These are the different kinds of tokens */
1722 const char *commenTF_regex = "^\\s*\\* comment \\*\\s*$";
1723 const char *version_regex = "^\\s*\\* version \\*\\s*$";
1724 const char *include_regex = "^\\s*\\* include \\*\\s*$";
1725 const char *language_regex = "^\\s*\\* language \\*\\s*$";
1726 const char *alias_regex = "^\\s*\\* alias \\*\\s*$";
1727 char line[MAXLINE], *status;
1728 int dummy1;
1729 int code;
1731 /* Skip blank lines and comments */
1732 while(1) {
1733 /* Skip blank lines */
1734 while((status=fgets(line, MAXLINE, fp))) {
1735 ++(*currLine);
1736 if(!lineEmpty( line ))
1737 break;
1740 /* Check for error or EOF */
1741 if(!status)
1742 return TF_EOF;
1744 /* We've got a non-blank line -- is it a comment block? */
1745 if( !searchLine(line, commenTF_regex) )
1746 break;
1748 /* Skip the comment (non-blank lines) */
1749 while((status=fgets(line, MAXLINE, fp))) {
1750 ++(*currLine);
1751 if(lineEmpty( line ))
1752 break;
1755 if(!status)
1756 return TF_EOF;
1759 /* Now we know it's a meaningful block */
1760 dummy1 = searchLine(line, include_regex);
1761 if( dummy1 || searchLine(line, alias_regex) ) {
1762 /* INCLUDE or ALIAS block */
1763 int incLen, incPos, i, incLines;
1765 /* fprintf(stderr, "Starting include/alias at line %i\n", *currLine); */
1766 if(dummy1)
1767 code = TF_INCLUDE;
1768 else {
1769 code = TF_ALIAS;
1770 /* Need to read the header line for an alias */
1771 status=fgets(line, MAXLINE, fp);
1772 ++(*currLine);
1773 if (!status)
1774 return TF_ERROR_EOF;
1775 if (lineEmpty( line )) {
1776 fprintf( stderr, "nedit: Warning: empty '* alias *' "
1777 "block in calltips file.\n" );
1778 return TF_ERROR;
1780 rstrip(header, line);
1782 incPos = ftell(fp);
1783 *blkLine = *currLine + 1; /* Line of first actual filename/alias */
1784 if (incPos < 0)
1785 return TF_ERROR;
1786 /* Figure out how long the block is */
1787 while((status=fgets(line, MAXLINE, fp))) {
1788 ++(*currLine);
1789 if(lineEmpty( line ))
1790 break;
1792 incLen = ftell(fp) - incPos;
1793 incLines = *currLine - *blkLine;
1794 /* Correct currLine for the empty line it read at the end */
1795 --(*currLine);
1796 if (incLines == 0) {
1797 fprintf( stderr, "nedit: Warning: empty '* include *' or"
1798 " '* alias *' block in calltips file.\n" );
1799 return TF_ERROR;
1801 /* Make space for the filenames/alias sources */
1802 *body = (char *)malloc(incLen+1);
1803 if (!*body)
1804 return TF_ERROR;
1805 *body[0]=0;
1806 if (fseek(fp, incPos, SEEK_SET) != 0) {
1807 free (*body);
1808 return TF_ERROR;
1810 /* Read all the lines in the block */
1811 /* fprintf(stderr, "Copying lines\n"); */
1812 for (i=0; i<incLines; i++) {
1813 status = fgets(line, MAXLINE, fp);
1814 if (!status) {
1815 free (*body);
1816 return TF_ERROR_EOF;
1818 rstrip(line,line);
1819 if(i)
1820 strcat(*body, ":");
1821 strcat(*body, line);
1823 /* fprintf(stderr, "Finished include/alias at line %i\n", *currLine); */
1826 else if( searchLine(line, language_regex) ) {
1827 /* LANGUAGE block */
1828 status=fgets(line, MAXLINE, fp);
1829 ++(*currLine);
1830 if (!status)
1831 return TF_ERROR_EOF;
1832 if (lineEmpty( line )) {
1833 fprintf( stderr, "nedit: Warning: empty '* language *' block in calltips file.\n" );
1834 return TF_ERROR;
1836 *blkLine = *currLine;
1837 rstrip(header, line);
1838 code = TF_LANGUAGE;
1841 else if( searchLine(line, version_regex) ) {
1842 /* VERSION block */
1843 status=fgets(line, MAXLINE, fp);
1844 ++(*currLine);
1845 if (!status)
1846 return TF_ERROR_EOF;
1847 if (lineEmpty( line )) {
1848 fprintf( stderr, "nedit: Warning: empty '* version *' block in calltips file.\n" );
1849 return TF_ERROR;
1851 *blkLine = *currLine;
1852 rstrip(header, line);
1853 code = TF_VERSION;
1856 else {
1857 /* Calltip block */
1858 /* The first line is the key, the rest is the tip.
1859 Strip trailing whitespace. */
1860 rstrip(header, line);
1862 status=fgets(line, MAXLINE, fp);
1863 ++(*currLine);
1864 if (!status)
1865 return TF_ERROR_EOF;
1866 if (lineEmpty( line )) {
1867 fprintf( stderr, "nedit: Warning: empty calltip block:\n"
1868 " \"%s\"\n", header);
1869 return TF_ERROR;
1871 *blkLine = *currLine;
1872 *body = strdup(line);
1873 code = TF_BLOCK;
1876 /* Skip the rest of the block */
1877 dummy1 = *currLine;
1878 while(fgets(line, MAXLINE, fp)) {
1879 ++(*currLine);
1880 if (lineEmpty( line ))
1881 break;
1884 /* Warn about any unneeded extra lines (which are ignored). */
1885 if (dummy1+1 < *currLine && code != TF_BLOCK) {
1886 fprintf( stderr, "nedit: Warning: extra lines in language or version block ignored.\n" );
1889 return code;
1892 /* A struct for describing a calltip alias */
1893 typedef struct _alias {
1894 char *dest;
1895 char *sources;
1896 struct _alias *next;
1897 } tf_alias;
1900 ** Allocate a new alias, copying dest and stealing sources. This may
1901 ** seem strange but that's the way it's called
1903 static tf_alias *new_alias(const char *dest, char *sources) {
1904 tf_alias *alias;
1906 /* fprintf(stderr, "new_alias: %s <- %s\n", dest, sources); */
1907 /* Allocate the alias */
1908 alias = (tf_alias *)malloc( sizeof(tf_alias) );
1909 if(!alias)
1910 return NULL;
1912 /* Fill it in */
1913 alias->dest = (char*)malloc( strlen(dest)+1 );
1914 if(!(alias->dest))
1915 return NULL;
1916 strcpy( alias->dest, dest );
1917 alias->sources = sources;
1918 return alias;
1921 /* Deallocate a linked-list of aliases */
1922 static void free_alias_list(tf_alias *alias) {
1923 tf_alias *tmp_alias;
1924 while(alias) {
1925 tmp_alias = alias->next;
1926 free(alias->dest);
1927 free(alias->sources);
1928 free(alias);
1929 alias = tmp_alias;
1934 ** Load a calltips file and insert all of the entries into the global tips
1935 ** database. Each tip is essentially stored as its filename and the line
1936 ** at which it appears--the exact same way ctags indexes source-code. That's
1937 ** why calltips and tags share so much code.
1939 static int loadTipsFile(const char *tipsFile, int index, int recLevel)
1941 FILE *fp = NULL;
1942 char header[MAXLINE];
1943 char *body, *tipIncFile;
1944 char tipPath[MAXPATHLEN];
1945 char resolvedTipsFile[MAXPATHLEN+1];
1946 int nTipsAdded=0, langMode = PLAIN_LANGUAGE_MODE, oldLangMode;
1947 int currLine=0, code, blkLine;
1948 tf_alias *aliases=NULL, *tmp_alias;
1950 if(recLevel > MAX_TAG_INCLUDE_RECURSION_LEVEL) {
1951 fprintf(stderr, "nedit: Warning: Reached recursion limit before loading calltips file:\n\t%s\n", tipsFile);
1952 return 0;
1955 /* find the tips file */
1956 #ifndef VMS
1957 /* Allow ~ in Unix filenames */
1958 strncpy(tipPath, tipsFile, MAXPATHLEN); /* ExpandTilde is destructive */
1959 ExpandTilde(tipPath);
1960 if(!ResolvePath(tipPath, resolvedTipsFile))
1961 return 0;
1962 #else
1963 if(!ResolvePath(tipsFile, resolvedTipsFile))
1964 return 0;
1965 #endif
1967 /* Get the path to the tips file */
1968 ParseFilename(resolvedTipsFile, NULL, tipPath);
1970 /* Open the file */
1971 if ((fp = fopen(resolvedTipsFile, "r")) == NULL)
1972 return 0;
1974 while( 1 ) {
1975 code = nextTFBlock(fp, header, &body, &blkLine, &currLine);
1976 if( code == TF_ERROR_EOF ) {
1977 fprintf(stderr,"nedit: Warning: unexpected EOF in calltips file.\n");
1978 break;
1980 if( code == TF_EOF )
1981 break;
1983 switch (code) {
1984 case TF_BLOCK:
1985 /* Add the calltip to the global hash table.
1986 For the moment I'm just using line numbers because I don't
1987 want to have to deal with adding escape characters for
1988 regex metacharacters that might appear in the string */
1989 nTipsAdded += addTag(header, resolvedTipsFile, langMode, "",
1990 blkLine, tipPath, index);
1991 free( body );
1992 break;
1993 case TF_INCLUDE:
1994 /* nextTFBlock returns a colon-separated list of tips files
1995 in body */
1996 for(tipIncFile=strtok(body,":"); tipIncFile;
1997 tipIncFile=strtok(NULL,":")) {
1998 /* fprintf(stderr,
1999 "nedit: DEBUG: including tips file '%s'\n",
2000 tipIncFile); */
2001 nTipsAdded += loadTipsFile( tipIncFile, index, recLevel+1);
2003 free( body );
2004 break;
2005 case TF_LANGUAGE:
2006 /* Switch to the new language mode if it's valid, else ignore
2007 it. */
2008 oldLangMode = langMode;
2009 langMode = FindLanguageMode( header );
2010 if (langMode == PLAIN_LANGUAGE_MODE &&
2011 strcmp(header, "Plain")) {
2012 fprintf(stderr,
2013 "nedit: Error reading calltips file:\n\t%s\n"
2014 "Unknown language mode: \"%s\"\n",
2015 tipsFile, header);
2016 langMode = oldLangMode;
2018 break;
2019 case TF_ERROR:
2020 fprintf(stderr,"nedit: Warning: Recoverable error while "
2021 "reading calltips file:\n \"%s\"\n",
2022 resolvedTipsFile);
2023 break;
2024 case TF_ALIAS:
2025 /* Allocate a new alias struct */
2026 tmp_alias = aliases;
2027 aliases = new_alias(header, body);
2028 if( !aliases ) {
2029 fprintf(stderr,"nedit: Can't allocate memory for tipfile "
2030 "alias in calltips file:\n \"%s\"\n",
2031 resolvedTipsFile);
2032 /* Deallocate any allocated aliases */
2033 free_alias_list(tmp_alias);
2034 return 0;
2036 /* Add it to the list */
2037 aliases->next = tmp_alias;
2038 break;
2039 default:
2040 ;/* Ignore TF_VERSION for now */
2044 /* Now resolve any aliases */
2045 tmp_alias = aliases;
2046 while (tmp_alias) {
2047 tag *t;
2048 char *src;
2049 t = getTag(tmp_alias->dest, TIP);
2050 if (!t) {
2051 fprintf(stderr, "nedit: Can't find destination of alias \"%s\"\n"
2052 " in calltips file:\n \"%s\"\n",
2053 tmp_alias->dest, resolvedTipsFile);
2054 } else {
2055 for(src=strtok(tmp_alias->sources,":"); src; src=strtok(NULL,":"))
2056 addTag(src, resolvedTipsFile, t->language, "", t->posInf,
2057 tipPath, index);
2059 tmp_alias = tmp_alias->next;
2061 free_alias_list(aliases);
2062 return nTipsAdded;