Xft support under OpenMotif 2.3.3 - I've been using this for quite a while on
[nedit.git] / source / tags.c
blob6f1ebfb9dc71b8f6702a8c3b47bec9f379ead54c
1 static const char CVSID[] = "$Id: tags.c,v 1.71 2009/06/23 21:30:09 lebert 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);
879 /* If we didn't find a requested calltip, see if we can use a tag */
880 if (status == 0 && search_type == TIP && TagsFileList != NULL) {
881 searchMode = TIP_FROM_TAG;
882 status = findAllMatches(window, tagText);
885 if (status == 0) {
886 /* Didn't find any matches */
887 if (searchMode == TIP_FROM_TAG || searchMode == TIP) {
888 sprintf(message, "No match for \"%s\" in calltips or tags.",
889 tagName);
890 tagsShowCalltip( window, message );
891 } else
893 DialogF(DF_WARN, window->textArea, 1, "Tags",
894 "\"%s\" not found in tags file%s", "OK", tagName,
895 (TagsFileList && TagsFileList->next) ? "s" : "");
899 else {
900 fprintf(stderr, "NEdit: Can't handle non 8-bit text\n");
901 XBell(TheDisplay, 0);
904 else {
905 fprintf(stderr, "NEdit: Tag Length too long.\n");
906 XBell(TheDisplay, 0);
908 return status;
912 ** Lookup the definition for the current primary selection the currently
913 ** loaded tags file and bring up the file and line that the tags file
914 ** indicates.
916 static void findDefinitionHelper(WindowInfo *window, Time time, const char *arg,
917 int search_type)
919 if(arg)
921 findDef(window, arg, search_type);
923 else
925 searchMode = search_type;
926 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING,
927 (XtSelectionCallbackProc)findDefCB, window, time);
932 ** See findDefHelper
934 void FindDefinition(WindowInfo *window, Time time, const char *arg)
936 findDefinitionHelper(window, time, arg, TAG);
940 ** See findDefHelper
942 void FindDefCalltip(WindowInfo *window, Time time, const char *arg)
944 /* Reset calltip parameters to reasonable defaults */
945 globAnchored = False;
946 globPos = -1;
947 globHAlign = TIP_LEFT;
948 globVAlign = TIP_BELOW;
949 globAlignMode = TIP_SLOPPY;
951 findDefinitionHelper(window, time, arg, TIP);
954 /* Callback function for FindDefinition */
955 static void findDefCB(Widget widget, WindowInfo *window, Atom *sel,
956 Atom *type, char *value, int *length, int *format)
958 /* skip if we can't get the selection data, or it's obviously too long */
959 if (*type == XT_CONVERT_FAIL || value == NULL) {
960 XBell(TheDisplay, 0);
961 } else {
962 findDef(window, value, searchMode);
964 XtFree(value);
968 ** Try to display a calltip
969 ** anchored: If true, tip appears at position pos
970 ** lookup: If true, text is considered a key to be searched for in the
971 ** tip and/or tag database depending on search_type
972 ** search_type: Either TIP or TIP_FROM_TAG
974 int ShowTipString(WindowInfo *window, char *text, Boolean anchored,
975 int pos, Boolean lookup, int search_type, int hAlign, int vAlign,
976 int alignMode) {
978 if (search_type == TAG) return 0;
980 /* So we don't have to carry all of the calltip alignment info around */
981 globAnchored = anchored;
982 globPos = pos;
983 globHAlign = hAlign;
984 globVAlign = vAlign;
985 globAlignMode = alignMode;
987 /* If this isn't a lookup request, just display it. */
988 if (!lookup)
989 return tagsShowCalltip(window, text);
990 else
991 return findDef(window, text, search_type);
994 /* store all of the info into a pre-allocated tags struct */
995 static void setTag(tag *t, const char *name, const char *file,
996 int language, const char *searchString, int posInf,
997 const char *path)
999 t->name = rcs_strdup(name);
1000 t->file = rcs_strdup(file);
1001 t->language = language;
1002 t->searchString = rcs_strdup(searchString);
1003 t->posInf = posInf;
1004 t->path = rcs_strdup(path);
1008 ** ctags search expressions are literal strings with a search direction flag,
1009 ** line starting "^" and ending "$" delimiters. This routine translates them
1010 ** into NEdit compatible regular expressions and does the search.
1011 ** Etags search expressions are plain literals strings, which
1013 ** If in_buffer is not NULL then it is searched instead of the window buffer.
1014 ** In this case in_buffer should be an XtMalloc allocated buffer and the
1015 ** caller is responsible for freeing it.
1017 static int fakeRegExSearch(WindowInfo *window, char *in_buffer,
1018 const char *searchString, int *startPos, int *endPos)
1020 int found, searchStartPos, dir, ctagsMode;
1021 char searchSubs[3*MAXLINE+3], *outPtr;
1022 const char *fileString, *inPtr;
1024 if (in_buffer == NULL) {
1025 /* get the entire (sigh) text buffer from the text area widget */
1026 fileString = BufAsString(window->buffer);
1027 } else {
1028 fileString = in_buffer;
1031 /* determine search direction and start position */
1032 if (*startPos != -1) { /* etags mode! */
1033 dir = SEARCH_FORWARD;
1034 searchStartPos = *startPos;
1035 ctagsMode=0;
1036 } else if (searchString[0] == '/') {
1037 dir = SEARCH_FORWARD;
1038 searchStartPos = 0;
1039 ctagsMode=1;
1040 } else if (searchString[0] == '?') {
1041 dir = SEARCH_BACKWARD;
1042 /* searchStartPos = window->buffer->length; */
1043 searchStartPos = strlen(fileString);
1044 ctagsMode=1;
1045 } else {
1046 fprintf(stderr, "NEdit: Error parsing tag file search string");
1047 return FALSE;
1050 /* Build the search regex. */
1051 outPtr=searchSubs;
1052 if(ctagsMode) {
1053 inPtr=searchString+1; /* searchString[0] is / or ? --> search dir */
1054 if(*inPtr == '^') {
1055 /* If the first char is a caret then it's a RE line start delim */
1056 *outPtr++ = *inPtr++;
1058 } else { /* etags mode, no search dir spec, no leading caret */
1059 inPtr=searchString;
1061 while(*inPtr) {
1062 if( (*inPtr=='\\' && inPtr[1]=='/') ||
1063 (*inPtr=='\r' && inPtr[1]=='$' && !inPtr[2])
1065 /* Remove:
1066 - escapes (added by standard and exuberant ctags) from slashes
1067 - literal CRs generated by standard ctags for DOSified sources
1069 inPtr++;
1070 } else if(strchr("()-[]<>{}.|^*+?&\\", *inPtr)
1071 || (*inPtr == '$' && (inPtr[1]||(!ctagsMode)))){
1072 /* Escape RE Meta Characters to match them literally.
1073 Don't escape $ if it's the last charcter of the search expr
1074 in ctags mode; always escape $ in etags mode.
1076 *outPtr++ = '\\';
1077 *outPtr++ = *inPtr++;
1078 } else if (isspace((unsigned char)*inPtr)) { /* col. multiple spaces */
1079 *outPtr++ = '\\';
1080 *outPtr++ = 's';
1081 *outPtr++ = '+';
1082 do { inPtr++ ; } while(isspace((unsigned char)*inPtr));
1083 } else { /* simply copy all other characters */
1084 *outPtr++ = *inPtr++;
1087 *outPtr=0; /* Terminate searchSubs */
1089 found = SearchString(fileString, searchSubs, dir, SEARCH_REGEX,
1090 False, searchStartPos, startPos, endPos, NULL, NULL, NULL);
1092 if(!found && !ctagsMode) {
1093 /* position of the target definition could have been drifted before
1094 startPos, if nothing has been found by now try searching backward
1095 again from startPos.
1097 found = SearchString(fileString, searchSubs, SEARCH_BACKWARD,
1098 SEARCH_REGEX, False, searchStartPos, startPos, endPos, NULL,
1099 NULL, NULL);
1102 /* return the result */
1103 if (found) {
1104 /* *startPos and *endPos are set in SearchString*/
1105 return TRUE;
1106 } else {
1107 /* startPos, endPos left untouched by SearchString if search failed. */
1108 XBell(TheDisplay, 0);
1109 return FALSE;
1113 /* Finds all matches and handles tag "collisions". Prompts user with a
1114 list of collided tags in the hash table and allows the user to select
1115 the correct one. */
1116 static int findAllMatches(WindowInfo *window, const char *string)
1118 Widget dialogParent = window->textArea;
1119 char filename[MAXPATHLEN], pathname[MAXPATHLEN];
1120 char temp[32+2*MAXPATHLEN+MAXLINE];
1121 const char *fileToSearch, *searchString, *tagPath;
1122 char **dupTagsList;
1123 int startPos, i, pathMatch=0, samePath=0, langMode, nMatches=0;
1125 /* verify that the string is reasonable as a tag */
1126 if (*string == '\0' || strlen(string) > MAX_TAG_LEN) {
1127 XBell(TheDisplay, 0);
1128 return -1;
1130 tagName=string;
1132 /* First look up all of the matching tags */
1133 while (LookupTag(string, &fileToSearch, &langMode, &searchString, &startPos,
1134 &tagPath, searchMode)) {
1135 /* Skip this tag if it has a language mode that doesn't match the
1136 current language mode, but don't skip anything if the window is in
1137 PLAIN_LANGUAGE_MODE. */
1138 if (window->languageMode != PLAIN_LANGUAGE_MODE &&
1139 GetPrefSmartTags() && langMode != PLAIN_LANGUAGE_MODE &&
1140 langMode != window->languageMode) {
1141 string=NULL;
1142 continue;
1144 if (*fileToSearch == '/')
1145 strcpy(tagFiles[nMatches], fileToSearch);
1146 else
1147 sprintf(tagFiles[nMatches],"%s%s",tagPath,fileToSearch);
1148 strcpy(tagSearch[nMatches],searchString);
1149 tagPosInf[nMatches]=startPos;
1150 ParseFilename(tagFiles[nMatches], filename, pathname);
1151 /* Is this match in the current file? If so, use it! */
1152 if (GetPrefSmartTags() && !strcmp(window->filename,filename)
1153 && !strcmp(window->path,pathname) ) {
1154 if (nMatches) {
1155 strcpy(tagFiles[0],tagFiles[nMatches]);
1156 strcpy(tagSearch[0],tagSearch[nMatches]);
1157 tagPosInf[0]=tagPosInf[nMatches];
1159 nMatches = 1;
1160 break;
1162 /* Is this match in the same dir. as the current file? */
1163 if (!strcmp(window->path,pathname)) {
1164 samePath++;
1165 pathMatch=nMatches;
1167 if (++nMatches >= MAXDUPTAGS) {
1168 DialogF(DF_WARN, dialogParent, 1, "Tags",
1169 "Too many duplicate tags, first %d shown", "OK", MAXDUPTAGS);
1170 break;
1172 /* Tell LookupTag to look for more definitions of the same tag: */
1173 string = NULL;
1176 /* Did we find any matches? */
1177 if (!nMatches) {
1178 return 0;
1181 /* Only one of the matches is in the same dir. as this file. Use it. */
1182 if (GetPrefSmartTags() && samePath == 1 && nMatches > 1) {
1183 strcpy(tagFiles[0],tagFiles[pathMatch]);
1184 strcpy(tagSearch[0],tagSearch[pathMatch]);
1185 tagPosInf[0]=tagPosInf[pathMatch];
1186 nMatches = 1;
1189 /* If all of the tag entries are the same file, just use the first.
1191 if (GetPrefSmartTags()) {
1192 for (i=1; i<nMatches; i++)
1193 if (strcmp(tagFiles[i],tagFiles[i-1]))
1194 break;
1195 if (i==nMatches)
1196 nMatches = 1;
1199 if (nMatches>1) {
1200 if (!(dupTagsList = (char **) malloc(sizeof(char *) * nMatches))) {
1201 fprintf(stderr, "nedit: findAllMatches(): out of heap space!\n");
1202 XBell(TheDisplay, 0);
1203 return -1;
1206 for (i=0; i<nMatches; i++) {
1207 ParseFilename(tagFiles[i], filename, pathname);
1208 if ((i<nMatches-1 && !strcmp(tagFiles[i],tagFiles[i+1])) ||
1209 (i>0 && !strcmp(tagFiles[i],tagFiles[i-1]))) {
1210 if(*(tagSearch[i]) && (tagPosInf[i] != -1)) { /* etags */
1211 sprintf(temp,"%2d. %s%s %8i %s", i+1, pathname,
1212 filename, tagPosInf[i], tagSearch[i]);
1213 } else if (*(tagSearch[i])) { /* ctags search expr */
1214 sprintf(temp,"%2d. %s%s %s", i+1, pathname,
1215 filename, tagSearch[i]);
1216 } else { /* line number only */
1217 sprintf(temp,"%2d. %s%s %8i", i+1, pathname, filename,
1218 tagPosInf[i]);
1220 } else {
1221 sprintf(temp,"%2d. %s%s",i+1,pathname,filename);
1224 if (NULL == (dupTagsList[i] = (char*) malloc(strlen(temp) + 1))) {
1225 int j;
1226 fprintf(stderr, "nedit: findAllMatches(): out of heap space!\n");
1228 /* dupTagsList[i] is unallocated, let's free [i - 1] to [0] */
1229 for (j = i - 1; j > -1; j--) {
1230 free(dupTagsList[j]);
1232 free(dupTagsList);
1234 XBell(TheDisplay, 0);
1235 return -1;
1238 strcpy(dupTagsList[i],temp);
1240 createSelectMenu(dialogParent, "Duplicate Tags", nMatches, dupTagsList);
1241 for (i=0; i<nMatches; i++)
1242 free(dupTagsList[i]);
1243 free(dupTagsList);
1244 return 1;
1248 ** No need for a dialog list, there is only one tag matching --
1249 ** Go directly to the tag
1251 if (searchMode == TAG)
1252 editTaggedLocation( dialogParent, 0 );
1253 else
1254 showMatchingCalltip( dialogParent, 0 );
1255 return 1;
1258 /* Callback function for the FindAll widget. Process the users response. */
1259 static void findAllCB(Widget parent, XtPointer client_data, XtPointer call_data)
1261 int i;
1262 char *eptr;
1264 XmSelectionBoxCallbackStruct *cbs =
1265 (XmSelectionBoxCallbackStruct *) call_data;
1266 if (cbs->reason == XmCR_NO_MATCH)
1267 return;
1268 if (cbs->reason == XmCR_CANCEL) {
1269 XtDestroyWidget(XtParent(parent));
1270 return;
1273 XmStringGetLtoR(cbs->value,XmFONTLIST_DEFAULT_TAG,&eptr);
1274 if ((i = atoi(eptr)-1) < 0) {
1275 XBell(TheDisplay, 0);
1276 return;
1279 if (searchMode == TAG)
1280 editTaggedLocation( parent, i ); /* Open the file with the definition */
1281 else
1282 showMatchingCalltip( parent, i );
1284 if (cbs->reason == XmCR_OK)
1285 XtDestroyWidget(XtParent(parent));
1288 /* Window manager close-box callback for tag-collision dialog */
1289 static void findAllCloseCB(Widget parent, XtPointer client_data,
1290 XtPointer call_data)
1292 XtDestroyWidget(parent);
1296 * Given a \0 terminated string and a position, advance the position
1297 * by n lines, where line separators (for now) are \n. If the end of
1298 * string is reached before n lines, return the number of lines advanced,
1299 * else normally return -1.
1301 static int moveAheadNLines( char *str, int *pos, int n ) {
1302 int i=n;
1303 while (str[*pos] != '\0' && n>0) {
1304 if (str[*pos] == '\n')
1305 --n;
1306 ++(*pos);
1308 if (n==0)
1309 return -1;
1310 else
1311 return i-n;
1315 ** Show the calltip specified by tagFiles[i], tagSearch[i], tagPosInf[i]
1316 ** This reads from either a source code file (if searchMode == TIP_FROM_TAG)
1317 ** or a calltips file (if searchMode == TIP).
1319 static void showMatchingCalltip( Widget parent, int i )
1321 int startPos=0, fileLen, readLen, tipLen;
1322 int endPos=0;
1323 char *fileString;
1324 FILE *fp;
1325 struct stat statbuf;
1326 char *message;
1328 /* 1. Open the target file */
1329 NormalizePathname(tagFiles[i]);
1330 fp = fopen(tagFiles[i], "r");
1331 if (fp == NULL) {
1332 DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s",
1333 "OK", tagFiles[i]);
1334 return;
1336 if (fstat(fileno(fp), &statbuf) != 0) {
1337 fclose(fp);
1338 DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s",
1339 "OK", tagFiles[i]);
1340 return;
1343 /* 2. Read the target file */
1344 /* Allocate space for the whole contents of the file (unfortunately) */
1345 fileLen = statbuf.st_size;
1346 fileString = XtMalloc(fileLen+1); /* +1 = space for null */
1347 if (fileString == NULL) {
1348 fclose(fp);
1349 DialogF(DF_ERR, parent, 1, "File too large",
1350 "File is too large to load", "OK");
1351 return;
1354 /* Read the file into fileString and terminate with a null */
1355 readLen = fread(fileString, sizeof(char), fileLen, fp);
1356 if (ferror(fp)) {
1357 fclose(fp);
1358 DialogF(DF_ERR, parent, 1, "Error reading File", "Error reading %s",
1359 "OK", tagFiles[i]);
1360 XtFree(fileString);
1361 return;
1363 fileString[readLen] = 0;
1365 /* Close the file */
1366 if (fclose(fp) != 0) {
1367 /* unlikely error */
1368 DialogF(DF_WARN, parent, 1, "Error closing File",
1369 "Unable to close file", "OK");
1370 /* we read it successfully, so continue */
1373 /* 3. Search for the tagged location (set startPos) */
1374 if (!*(tagSearch[i])) {
1375 /* It's a line number, just go for it */
1376 if ((moveAheadNLines( fileString, &startPos, tagPosInf[i]-1 )) >= 0) {
1377 DialogF(DF_ERR, parent, 1, "Tags Error",
1378 "%s\n not long enough for definition to be on line %d",
1379 "OK", tagFiles[i], tagPosInf[i]);
1380 XtFree(fileString);
1381 return;
1383 } else {
1384 startPos = tagPosInf[i];
1385 if(!fakeRegExSearch(WidgetToWindow(parent), fileString, tagSearch[i],
1386 &startPos, &endPos)){
1387 DialogF(DF_WARN, parent, 1, "Tag not found",
1388 "Definition for %s\nnot found in %s", "OK", tagName,
1389 tagFiles[i]);
1390 XtFree(fileString);
1391 return;
1395 if (searchMode == TIP) {
1396 int dummy, found;
1398 /* 4. Find the end of the calltip (delimited by an empty line) */
1399 endPos = startPos;
1400 found = SearchString(fileString, "\\n\\s*\\n", SEARCH_FORWARD,
1401 SEARCH_REGEX, False, startPos, &endPos, &dummy, NULL,
1402 NULL, NULL);
1403 if (!found) {
1404 /* Just take 4 lines */
1405 moveAheadNLines( fileString, &endPos, TIP_DEFAULT_LINES );
1406 --endPos; /* Lose the last \n */
1408 } else { /* Mode = TIP_FROM_TAG */
1409 /* 4. Copy TIP_DEFAULT_LINES lines of text to the calltip string */
1410 endPos = startPos;
1411 moveAheadNLines( fileString, &endPos, TIP_DEFAULT_LINES );
1412 /* Make sure not to overrun the fileString with ". . ." */
1413 if (((size_t) endPos) <= (strlen(fileString)-5)) {
1414 sprintf( &fileString[endPos], ". . ." );
1415 endPos += 5;
1418 /* 5. Copy the calltip to a string */
1419 tipLen = endPos - startPos;
1420 message = XtMalloc(tipLen+1); /* +1 = space for null */
1421 if (message == NULL)
1423 DialogF(DF_ERR, parent, 1, "Out of Memory",
1424 "Can't allocate memory for calltip message", "OK");
1425 XtFree(fileString);
1426 return;
1428 strncpy( message, &fileString[startPos], tipLen );
1429 message[tipLen] = 0;
1431 /* 6. Display it */
1432 tagsShowCalltip( WidgetToWindow(parent), message );
1433 XtFree(message);
1434 XtFree(fileString);
1437 /* Open a new (or existing) editor window to the location specified in
1438 tagFiles[i], tagSearch[i], tagPosInf[i] */
1439 static void editTaggedLocation( Widget parent, int i )
1441 /* Globals: tagSearch, tagPosInf, tagFiles, tagName, textNrows,
1442 WindowList */
1443 int startPos, endPos, lineNum, rows;
1444 char filename[MAXPATHLEN], pathname[MAXPATHLEN];
1445 WindowInfo *windowToSearch;
1446 WindowInfo *parentWindow = WidgetToWindow(parent);
1448 ParseFilename(tagFiles[i],filename,pathname);
1449 /* open the file containing the definition */
1450 EditExistingFile(parentWindow, filename, pathname, 0, NULL, False,
1451 NULL, GetPrefOpenInTab(), False);
1452 windowToSearch = FindWindowWithFile(filename, pathname);
1453 if (windowToSearch == NULL) {
1454 DialogF(DF_WARN, parent, 1, "File not found", "File %s not found", "OK",
1455 tagFiles[i]);
1456 return;
1459 startPos=tagPosInf[i];
1461 if(!*(tagSearch[i])) {
1462 /* if the search string is empty, select the numbered line */
1463 SelectNumberedLine(windowToSearch, startPos);
1464 return;
1467 /* search for the tags file search string in the newly opened file */
1468 if(!fakeRegExSearch(windowToSearch, NULL, tagSearch[i], &startPos,
1469 &endPos)){
1470 DialogF(DF_WARN, windowToSearch->shell, 1, "Tag Error",
1471 "Definition for %s\nnot found in %s", "OK", tagName,
1472 tagFiles[i]);
1473 return;
1476 /* select the matched string */
1477 BufSelect(windowToSearch->buffer, startPos, endPos);
1478 RaiseFocusDocumentWindow(windowToSearch, True);
1480 /* Position it nicely in the window,
1481 about 1/4 of the way down from the top */
1482 lineNum = BufCountLines(windowToSearch->buffer, 0, startPos);
1483 XtVaGetValues(windowToSearch->lastFocus, textNrows, &rows, NULL);
1484 TextSetScroll(windowToSearch->lastFocus, lineNum - rows/4, 0);
1485 TextSetCursorPos(windowToSearch->lastFocus, endPos);
1488 /* Create a Menu for user to select from the collided tags */
1489 static Widget createSelectMenu(Widget parent, char *label, int nArgs,
1490 char *args[])
1492 int i;
1493 char tmpStr[100];
1494 Widget menu;
1495 XmStringTable list;
1496 XmString popupTitle;
1497 int ac;
1498 Arg csdargs[20];
1500 list = (XmStringTable) XtMalloc(nArgs * sizeof(XmString *));
1501 for (i=0; i<nArgs; i++)
1502 list[i] = XmStringCreateSimple(args[i]);
1503 sprintf(tmpStr,"Select File With TAG: %s",tagName);
1504 popupTitle = XmStringCreateSimple(tmpStr);
1505 ac = 0;
1506 XtSetArg(csdargs[ac], XmNlistLabelString, popupTitle); ac++;
1507 XtSetArg(csdargs[ac], XmNlistItems, list); ac++;
1508 XtSetArg(csdargs[ac], XmNlistItemCount, nArgs); ac++;
1509 XtSetArg(csdargs[ac], XmNvisibleItemCount, 12); ac++;
1510 XtSetArg(csdargs[ac], XmNautoUnmanage, False); ac++;
1511 menu = CreateSelectionDialog(parent,label,csdargs,ac);
1512 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_TEXT));
1513 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_HELP_BUTTON));
1514 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_SELECTION_LABEL));
1515 XtAddCallback(menu, XmNokCallback, (XtCallbackProc)findAllCB, menu);
1516 XtAddCallback(menu, XmNapplyCallback, (XtCallbackProc)findAllCB, menu);
1517 XtAddCallback(menu, XmNcancelCallback, (XtCallbackProc)findAllCB, menu);
1518 AddMotifCloseCallback(XtParent(menu), findAllCloseCB, NULL);
1519 for (i=0; i<nArgs; i++)
1520 XmStringFree(list[i]);
1521 XtFree((char *)list);
1522 XmStringFree(popupTitle);
1523 ManageDialogCenteredOnPointer(menu);
1524 return menu;
1529 /*--------------------------------------------------------------------------
1531 Reference-counted string hack; SJT 4/2000
1533 This stuff isn't specific to tags, so it should be in it's own file.
1534 However, I'm leaving it in here for now to reduce the diffs.
1536 This could really benefit from using a real hash table.
1539 #define RCS_SIZE 10000
1541 struct rcs;
1543 struct rcs_stats
1545 int talloc, tshar, tgiveup, tbytes, tbyteshared;
1548 struct rcs
1550 struct rcs *next;
1551 char *string;
1552 int usage;
1555 static struct rcs *Rcs[RCS_SIZE];
1556 static struct rcs_stats RcsStats;
1559 ** Take a normal string, create a shared string from it if need be,
1560 ** and return pointer to that shared string.
1562 ** Returned strings are const because they are shared. Do not modify them!
1565 static const char *rcs_strdup(const char *str)
1567 int bucket;
1568 size_t len;
1569 struct rcs *rp;
1570 struct rcs *prev = NULL;
1572 char *newstr = NULL;
1574 if (str == NULL)
1575 return NULL;
1577 bucket = hashAddr(str) % RCS_SIZE;
1578 len = strlen(str);
1580 RcsStats.talloc++;
1582 #if 0
1583 /* Don't share if it won't save space.
1585 Doesn't save anything - if we have lots of small-size objects,
1586 it's beneifical to share them. We don't know until we make a full
1587 count. My tests show that it's better to leave this out. */
1588 if (len <= sizeof(struct rcs))
1590 new_str = strdup(str); /* GET RID OF strdup() IF EVER ENABLED (not ANSI) */
1591 RcsStats.tgiveup++;
1592 return;
1594 #endif
1596 /* Find it in hash */
1597 for (rp = Rcs[bucket]; rp; rp = rp->next)
1599 if (!strcmp(str, rp->string))
1600 break;
1601 prev = rp;
1604 if (rp) /* It exists, return it and bump ref ct */
1606 rp->usage++;
1607 newstr = rp->string;
1609 RcsStats.tshar++;
1610 RcsStats.tbyteshared += len;
1612 else /* Doesn't exist, conjure up a new one. */
1614 struct rcs* newrcs;
1615 if (NULL == (newrcs = (struct rcs*) malloc(sizeof(struct rcs)))) {
1616 /* Not much to fall back to here. */
1617 fprintf(stderr, "nedit: rcs_strdup(): out of heap space!\n");
1618 XBell(TheDisplay, 0);
1619 exit(1);
1622 if (NULL == (newrcs->string = (char*) malloc(len + 1))) {
1623 /* Not much to fall back to here. */
1624 fprintf(stderr, "nedit: rcs_strdup(): out of heap space!\n");
1625 XBell(TheDisplay, 0);
1626 exit(1);
1628 strcpy(newrcs->string, str);
1629 newrcs->usage = 1;
1630 newrcs->next = NULL;
1632 if (Rcs[bucket])
1633 prev->next = newrcs;
1634 else
1635 Rcs[bucket] = newrcs;
1637 newstr = newrcs->string;
1640 RcsStats.tbytes += len;
1641 return newstr;
1645 ** Decrease the reference count on a shared string. When the reference
1646 ** count reaches zero, free the master string.
1649 static void rcs_free(const char *rcs_str)
1651 int bucket;
1652 struct rcs *rp;
1653 struct rcs *prev = NULL;
1655 if (rcs_str == NULL)
1656 return;
1658 bucket = hashAddr(rcs_str) % RCS_SIZE;
1660 /* find it in hash */
1661 for (rp = Rcs[bucket]; rp; rp = rp->next)
1663 if (rcs_str == rp->string)
1664 break;
1665 prev = rp;
1668 if (rp) /* It's a shared string, decrease ref count */
1670 rp->usage--;
1672 if (rp->usage < 0) /* D'OH! */
1674 fprintf(stderr, "NEdit: internal error deallocating shared string.");
1675 return;
1678 if (rp->usage == 0) /* Last one- free the storage */
1680 free(rp->string);
1681 if (prev)
1682 prev->next = rp->next;
1683 else
1684 Rcs[bucket] = rp->next;
1685 free(rp);
1688 else /* Doesn't appear to be a shared string */
1690 fprintf(stderr, "NEdit: attempt to free a non-shared string.");
1691 return;
1695 /********************************************************************
1696 * Functions for loading Calltips files *
1697 ********************************************************************/
1699 enum tftoken_types { TF_EOF, TF_BLOCK, TF_VERSION, TF_INCLUDE, TF_LANGUAGE,
1700 TF_ALIAS, TF_ERROR, TF_ERROR_EOF };
1702 /* A wrapper for SearchString */
1703 static int searchLine(char *line, const char *regex) {
1704 int dummy1, dummy2;
1705 return SearchString(line, regex, SEARCH_FORWARD, SEARCH_REGEX,
1706 False, 0, &dummy1, &dummy2, NULL, NULL, NULL);
1709 /* Check if a line has non-ws characters */
1710 static Boolean lineEmpty(const char *line) {
1711 while (*line && *line != '\n') {
1712 if (*line != ' ' && *line != '\t')
1713 return False;
1714 ++line;
1716 return True;
1719 /* Remove trailing whitespace from a line */
1720 static void rstrip( char *dst, const char *src ) {
1721 int wsStart, dummy2;
1722 /* Strip trailing whitespace */
1723 if(SearchString(src, "\\s*\\n", SEARCH_FORWARD, SEARCH_REGEX,
1724 False, 0, &wsStart, &dummy2, NULL, NULL, NULL)) {
1725 if(dst != src)
1726 memcpy(dst, src, wsStart);
1727 dst[wsStart] = 0;
1728 } else
1729 if(dst != src)
1730 strcpy(dst, src);
1734 ** Get the next block from a tips file. A block is a \n\n+ delimited set of
1735 ** lines in a calltips file. All of the parameters except <fp> are return
1736 ** values, and most have different roles depending on the type of block
1737 ** that is found.
1738 ** header: Depends on the block type
1739 ** body: Depends on the block type. Used to return a new
1740 ** dynamically allocated string.
1741 ** blkLine: Returns the line number of the first line of the block
1742 ** after the "* xxxx *" line.
1743 ** currLine: Used to keep track of the current line in the file.
1745 static int nextTFBlock(FILE *fp, char *header, char **body, int *blkLine,
1746 int *currLine)
1748 /* These are the different kinds of tokens */
1749 const char *commenTF_regex = "^\\s*\\* comment \\*\\s*$";
1750 const char *version_regex = "^\\s*\\* version \\*\\s*$";
1751 const char *include_regex = "^\\s*\\* include \\*\\s*$";
1752 const char *language_regex = "^\\s*\\* language \\*\\s*$";
1753 const char *alias_regex = "^\\s*\\* alias \\*\\s*$";
1754 char line[MAXLINE], *status;
1755 int dummy1;
1756 int code;
1758 /* Skip blank lines and comments */
1759 while(1) {
1760 /* Skip blank lines */
1761 while((status=fgets(line, MAXLINE, fp))) {
1762 ++(*currLine);
1763 if(!lineEmpty( line ))
1764 break;
1767 /* Check for error or EOF */
1768 if(!status)
1769 return TF_EOF;
1771 /* We've got a non-blank line -- is it a comment block? */
1772 if( !searchLine(line, commenTF_regex) )
1773 break;
1775 /* Skip the comment (non-blank lines) */
1776 while((status=fgets(line, MAXLINE, fp))) {
1777 ++(*currLine);
1778 if(lineEmpty( line ))
1779 break;
1782 if(!status)
1783 return TF_EOF;
1786 /* Now we know it's a meaningful block */
1787 dummy1 = searchLine(line, include_regex);
1788 if( dummy1 || searchLine(line, alias_regex) ) {
1789 /* INCLUDE or ALIAS block */
1790 int incLen, incPos, i, incLines;
1792 /* fprintf(stderr, "Starting include/alias at line %i\n", *currLine); */
1793 if(dummy1)
1794 code = TF_INCLUDE;
1795 else {
1796 code = TF_ALIAS;
1797 /* Need to read the header line for an alias */
1798 status=fgets(line, MAXLINE, fp);
1799 ++(*currLine);
1800 if (!status)
1801 return TF_ERROR_EOF;
1802 if (lineEmpty( line )) {
1803 fprintf( stderr, "nedit: Warning: empty '* alias *' "
1804 "block in calltips file.\n" );
1805 return TF_ERROR;
1807 rstrip(header, line);
1809 incPos = ftell(fp);
1810 *blkLine = *currLine + 1; /* Line of first actual filename/alias */
1811 if (incPos < 0)
1812 return TF_ERROR;
1813 /* Figure out how long the block is */
1814 while((status=fgets(line, MAXLINE, fp)) || feof(fp)) {
1815 ++(*currLine);
1816 if(feof(fp) || lineEmpty( line ))
1817 break;
1819 incLen = ftell(fp) - incPos;
1820 incLines = *currLine - *blkLine;
1821 /* Correct currLine for the empty line it read at the end */
1822 --(*currLine);
1823 if (incLines == 0) {
1824 fprintf( stderr, "nedit: Warning: empty '* include *' or"
1825 " '* alias *' block in calltips file.\n" );
1826 return TF_ERROR;
1828 /* Make space for the filenames/alias sources */
1829 *body = (char *)malloc(incLen+1);
1830 if (!*body)
1831 return TF_ERROR;
1832 *body[0]=0;
1833 if (fseek(fp, incPos, SEEK_SET) != 0) {
1834 free (*body);
1835 return TF_ERROR;
1837 /* Read all the lines in the block */
1838 /* fprintf(stderr, "Copying lines\n"); */
1839 for (i=0; i<incLines; i++) {
1840 status = fgets(line, MAXLINE, fp);
1841 if (!status) {
1842 free (*body);
1843 return TF_ERROR_EOF;
1845 rstrip(line,line);
1846 if(i)
1847 strcat(*body, ":");
1848 strcat(*body, line);
1850 /* fprintf(stderr, "Finished include/alias at line %i\n", *currLine); */
1853 else if( searchLine(line, language_regex) ) {
1854 /* LANGUAGE block */
1855 status=fgets(line, MAXLINE, fp);
1856 ++(*currLine);
1857 if (!status)
1858 return TF_ERROR_EOF;
1859 if (lineEmpty( line )) {
1860 fprintf( stderr, "nedit: Warning: empty '* language *' block in calltips file.\n" );
1861 return TF_ERROR;
1863 *blkLine = *currLine;
1864 rstrip(header, line);
1865 code = TF_LANGUAGE;
1868 else if( searchLine(line, version_regex) ) {
1869 /* VERSION block */
1870 status=fgets(line, MAXLINE, fp);
1871 ++(*currLine);
1872 if (!status)
1873 return TF_ERROR_EOF;
1874 if (lineEmpty( line )) {
1875 fprintf( stderr, "nedit: Warning: empty '* version *' block in calltips file.\n" );
1876 return TF_ERROR;
1878 *blkLine = *currLine;
1879 rstrip(header, line);
1880 code = TF_VERSION;
1883 else {
1884 /* Calltip block */
1885 /* The first line is the key, the rest is the tip.
1886 Strip trailing whitespace. */
1887 rstrip(header, line);
1889 status=fgets(line, MAXLINE, fp);
1890 ++(*currLine);
1891 if (!status)
1892 return TF_ERROR_EOF;
1893 if (lineEmpty( line )) {
1894 fprintf( stderr, "nedit: Warning: empty calltip block:\n"
1895 " \"%s\"\n", header);
1896 return TF_ERROR;
1898 *blkLine = *currLine;
1899 *body = strdup(line);
1900 code = TF_BLOCK;
1903 /* Skip the rest of the block */
1904 dummy1 = *currLine;
1905 while(fgets(line, MAXLINE, fp)) {
1906 ++(*currLine);
1907 if (lineEmpty( line ))
1908 break;
1911 /* Warn about any unneeded extra lines (which are ignored). */
1912 if (dummy1+1 < *currLine && code != TF_BLOCK) {
1913 fprintf( stderr, "nedit: Warning: extra lines in language or version block ignored.\n" );
1916 return code;
1919 /* A struct for describing a calltip alias */
1920 typedef struct _alias {
1921 char *dest;
1922 char *sources;
1923 struct _alias *next;
1924 } tf_alias;
1927 ** Allocate a new alias, copying dest and stealing sources. This may
1928 ** seem strange but that's the way it's called
1930 static tf_alias *new_alias(const char *dest, char *sources) {
1931 tf_alias *alias;
1933 /* fprintf(stderr, "new_alias: %s <- %s\n", dest, sources); */
1934 /* Allocate the alias */
1935 alias = (tf_alias *)malloc( sizeof(tf_alias) );
1936 if(!alias)
1937 return NULL;
1939 /* Fill it in */
1940 alias->dest = (char*)malloc( strlen(dest)+1 );
1941 if(!(alias->dest))
1942 return NULL;
1943 strcpy( alias->dest, dest );
1944 alias->sources = sources;
1945 return alias;
1948 /* Deallocate a linked-list of aliases */
1949 static void free_alias_list(tf_alias *alias) {
1950 tf_alias *tmp_alias;
1951 while(alias) {
1952 tmp_alias = alias->next;
1953 free(alias->dest);
1954 free(alias->sources);
1955 free(alias);
1956 alias = tmp_alias;
1961 ** Load a calltips file and insert all of the entries into the global tips
1962 ** database. Each tip is essentially stored as its filename and the line
1963 ** at which it appears--the exact same way ctags indexes source-code. That's
1964 ** why calltips and tags share so much code.
1966 static int loadTipsFile(const char *tipsFile, int index, int recLevel)
1968 FILE *fp = NULL;
1969 char header[MAXLINE];
1970 char *body, *tipIncFile;
1971 char tipPath[MAXPATHLEN];
1972 char resolvedTipsFile[MAXPATHLEN+1];
1973 int nTipsAdded=0, langMode = PLAIN_LANGUAGE_MODE, oldLangMode;
1974 int currLine=0, code, blkLine;
1975 tf_alias *aliases=NULL, *tmp_alias;
1977 if(recLevel > MAX_TAG_INCLUDE_RECURSION_LEVEL) {
1978 fprintf(stderr, "nedit: Warning: Reached recursion limit before loading calltips file:\n\t%s\n", tipsFile);
1979 return 0;
1982 /* find the tips file */
1983 #ifndef VMS
1984 /* Allow ~ in Unix filenames */
1985 strncpy(tipPath, tipsFile, MAXPATHLEN); /* ExpandTilde is destructive */
1986 ExpandTilde(tipPath);
1987 if(!ResolvePath(tipPath, resolvedTipsFile))
1988 return 0;
1989 #else
1990 if(!ResolvePath(tipsFile, resolvedTipsFile))
1991 return 0;
1992 #endif
1994 /* Get the path to the tips file */
1995 ParseFilename(resolvedTipsFile, NULL, tipPath);
1997 /* Open the file */
1998 if ((fp = fopen(resolvedTipsFile, "r")) == NULL)
1999 return 0;
2001 while( 1 ) {
2002 code = nextTFBlock(fp, header, &body, &blkLine, &currLine);
2003 if( code == TF_ERROR_EOF ) {
2004 fprintf(stderr,"nedit: Warning: unexpected EOF in calltips file.\n");
2005 break;
2007 if( code == TF_EOF )
2008 break;
2010 switch (code) {
2011 case TF_BLOCK:
2012 /* Add the calltip to the global hash table.
2013 For the moment I'm just using line numbers because I don't
2014 want to have to deal with adding escape characters for
2015 regex metacharacters that might appear in the string */
2016 nTipsAdded += addTag(header, resolvedTipsFile, langMode, "",
2017 blkLine, tipPath, index);
2018 free( body );
2019 break;
2020 case TF_INCLUDE:
2021 /* nextTFBlock returns a colon-separated list of tips files
2022 in body */
2023 for(tipIncFile=strtok(body,":"); tipIncFile;
2024 tipIncFile=strtok(NULL,":")) {
2025 /* fprintf(stderr,
2026 "nedit: DEBUG: including tips file '%s'\n",
2027 tipIncFile); */
2028 nTipsAdded += loadTipsFile( tipIncFile, index, recLevel+1);
2030 free( body );
2031 break;
2032 case TF_LANGUAGE:
2033 /* Switch to the new language mode if it's valid, else ignore
2034 it. */
2035 oldLangMode = langMode;
2036 langMode = FindLanguageMode( header );
2037 if (langMode == PLAIN_LANGUAGE_MODE &&
2038 strcmp(header, "Plain")) {
2039 fprintf(stderr,
2040 "nedit: Error reading calltips file:\n\t%s\n"
2041 "Unknown language mode: \"%s\"\n",
2042 tipsFile, header);
2043 langMode = oldLangMode;
2045 break;
2046 case TF_ERROR:
2047 fprintf(stderr,"nedit: Warning: Recoverable error while "
2048 "reading calltips file:\n \"%s\"\n",
2049 resolvedTipsFile);
2050 break;
2051 case TF_ALIAS:
2052 /* Allocate a new alias struct */
2053 tmp_alias = aliases;
2054 aliases = new_alias(header, body);
2055 if( !aliases ) {
2056 fprintf(stderr,"nedit: Can't allocate memory for tipfile "
2057 "alias in calltips file:\n \"%s\"\n",
2058 resolvedTipsFile);
2059 /* Deallocate any allocated aliases */
2060 free_alias_list(tmp_alias);
2061 return 0;
2063 /* Add it to the list */
2064 aliases->next = tmp_alias;
2065 break;
2066 default:
2067 ;/* Ignore TF_VERSION for now */
2071 /* Now resolve any aliases */
2072 tmp_alias = aliases;
2073 while (tmp_alias) {
2074 tag *t;
2075 char *src;
2076 t = getTag(tmp_alias->dest, TIP);
2077 if (!t) {
2078 fprintf(stderr, "nedit: Can't find destination of alias \"%s\"\n"
2079 " in calltips file:\n \"%s\"\n",
2080 tmp_alias->dest, resolvedTipsFile);
2081 } else {
2082 for(src=strtok(tmp_alias->sources,":"); src; src=strtok(NULL,":"))
2083 addTag(src, resolvedTipsFile, t->language, "", t->posInf,
2084 tipPath, index);
2086 tmp_alias = tmp_alias->next;
2088 free_alias_list(aliases);
2089 return nTipsAdded;