Fix for SF bug #1122813: Tab-related crash on posting unload calltips file
[nedit.git] / source / tags.c
blob4826decf012001d3d722028945e008f691e4f30f
1 static const char CVSID[] = "$Id: tags.c,v 1.64 2005/02/15 01:10:16 n8gray 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!\n"
486 " Passed NULL pointer to DeleteTagsFile!\n");
487 return FALSE;
490 searchMode = file_type;
491 if (searchMode == TAG)
492 FileList = TagsFileList;
493 else
494 FileList = TipsFileList;
496 tmptagSpec = (char *) malloc(strlen(tagSpec)+1);
497 strcpy(tmptagSpec, tagSpec);
498 removed=1;
499 for (filename = strtok(tmptagSpec,":"); filename;
500 filename = strtok(NULL,":")) {
501 if (*filename != '/') {
502 strcpy(pathName, GetCurrentDir());
503 strcat(pathName,"/");
504 strcat(pathName,filename);
505 } else {
506 strcpy(pathName,filename);
508 NormalizePathname(pathName);
510 for (last=NULL,t = FileList; t; last = t,t = t->next) {
511 if (strcmp(t->filename, pathName))
512 continue;
513 /* Don't unload tips files with nonzero refcounts unless forced */
514 if (searchMode == TIP && !force_unload && --t->refcount > 0) {
515 break;
517 if (t->loaded)
518 delTag(NULL,NULL,-2,NULL,-2,t->index);
519 if (last) last->next = t->next;
520 else FileList = setFileListHead(t->next, file_type);
521 free(t->filename);
522 free(t);
523 updateMenuItems();
524 break;
526 /* If any file can't be removed, return false */
527 if (!t)
528 removed = 0;
530 if (removed)
531 return TRUE;
532 else
533 return FALSE;
537 ** Update the "Find Definition", "Unload Tags File", "Show Calltip",
538 ** and "Unload Calltips File" menu items in the existing windows.
540 static void updateMenuItems(void)
542 WindowInfo *w;
543 Boolean tipStat=FALSE, tagStat=FALSE;
545 if (TipsFileList) tipStat=TRUE;
546 if (TagsFileList) tagStat=TRUE;
548 for (w=WindowList; w!=NULL; w=w->next) {
549 if (!IsTopDocument(w))
550 continue;
551 XtSetSensitive(w->showTipItem, tipStat || tagStat);
552 XtSetSensitive(w->unloadTipsMenuItem, tipStat);
553 XtSetSensitive(w->findDefItem, tagStat);
554 XtSetSensitive(w->unloadTagsMenuItem, tagStat);
559 ** Scans one <line> from a ctags tags file (<index>) in tagPath.
560 ** Return value: Number of tag specs added.
562 static int scanCTagsLine(const char *line, const char *tagPath, int index)
564 char name[MAXLINE], searchString[MAXLINE];
565 char file[MAXPATHLEN];
566 char *posTagREEnd, *posTagRENull;
567 int nRead, pos;
569 nRead = sscanf(line, "%s\t%s\t%[^\n]", name, file, searchString);
570 if (nRead != 3)
571 return 0;
572 if ( *name == '!' )
573 return 0;
576 ** Guess the end of searchString:
577 ** Try to handle original ctags and exuberant ctags format:
579 if(searchString[0] == '/' || searchString[0] == '?') {
581 pos=-1; /* "search expr without pos info" */
583 /* Situations: /<ANY expr>/\0
584 ** ?<ANY expr>?\0 --> original ctags
585 ** /<ANY expr>/;" <flags>
586 ** ?<ANY expr>?;" <flags> --> exuberant ctags
588 posTagREEnd = strrchr(searchString, ';');
589 posTagRENull = strchr(searchString, 0);
590 if(!posTagREEnd || (posTagREEnd[1] != '"') ||
591 (posTagRENull[-1] == searchString[0])) {
592 /* -> original ctags format = exuberant ctags format 1 */
593 posTagREEnd = posTagRENull;
594 } else {
595 /* looks like exuberant ctags format 2 */
596 *posTagREEnd = 0;
600 ** Hide the last delimiter:
601 ** /<expression>/ becomes /<expression>
602 ** ?<expression>? becomes ?<expression>
603 ** This will save a little work in fakeRegExSearch.
605 if(posTagREEnd > (searchString+2)) {
606 posTagREEnd--;
607 if(searchString[0] == *posTagREEnd)
608 *posTagREEnd=0;
610 } else {
611 pos=atoi(searchString);
612 *searchString=0;
614 /* No ability to read language mode right now */
615 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos, tagPath,
616 index);
620 * Scans one <line> from an etags (emacs) tags file (<index>) in tagPath.
621 * recLevel = current recursion level for tags file including
622 * file = destination definition file. possibly modified. len=MAXPATHLEN!
623 * Return value: Number of tag specs added.
625 static int scanETagsLine(const char *line, const char * tagPath, int index,
626 char * file, int recLevel)
628 char name[MAXLINE], searchString[MAXLINE];
629 char incPath[MAXPATHLEN];
630 int pos, len;
631 char *posDEL, *posSOH, *posCOM;
633 /* check for destination file separator */
634 if(line[0]==12) { /* <np> */
635 *file=0;
636 return 0;
639 /* check for standard definition line */
640 posDEL=strchr(line, '\177');
641 posSOH=strchr(line, '\001');
642 posCOM=strrchr(line, ',');
643 if(*file && posDEL && (posSOH > posDEL) && (posCOM > posSOH)) {
644 /* exuberant ctags -e style */
645 len=Min(MAXLINE-1, posDEL - line);
646 strncpy(searchString, line, len);
647 searchString[len]=0;
648 len=Min(MAXLINE-1, (posSOH - posDEL) - 1);
649 strncpy(name, posDEL + 1, len);
650 name[len]=0;
651 pos=atoi(posCOM+1);
652 /* No ability to set language mode for the moment */
653 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos,
654 tagPath, index);
656 if (*file && posDEL && (posCOM > posDEL)) {
657 /* old etags style, part name<soh> is missing here! */
658 len=Min(MAXLINE-1, posDEL - line);
659 strncpy(searchString, line, len);
660 searchString[len]=0;
661 /* guess name: take the last alnum (plus _) part of searchString */
662 while(--len >= 0) {
663 if( isalnum((unsigned char)searchString[len]) ||
664 (searchString[len] == '_'))
665 break;
667 if(len<0)
668 return 0;
669 pos=len;
670 while (pos >= 0 && (isalnum((unsigned char)searchString[pos]) ||
671 (searchString[pos] == '_')))
672 pos--;
673 strncpy(name, searchString + pos + 1, len - pos);
674 name[len - pos] = 0; /* name ready */
675 pos=atoi(posCOM+1);
676 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos,
677 tagPath, index);
679 /* check for destination file spec */
680 if(*line && posCOM) {
681 len=Min(MAXPATHLEN-1, posCOM - line);
682 strncpy(file, line, len);
683 file[len]=0;
684 /* check if that's an include file ... */
685 if(!(strncmp(posCOM+1, "include", 7))) {
686 if(*file != '/') {
687 if((strlen(tagPath) + strlen(file)) >= MAXPATHLEN) {
688 fprintf(stderr, "tags.c: MAXPATHLEN overflow\n");
689 *file=0; /* invalidate */
690 return 0;
692 strcpy(incPath, tagPath);
693 strcat(incPath, file);
694 CompressPathname(incPath);
695 return(loadTagsFile(incPath, index, recLevel+1));
696 } else {
697 return(loadTagsFile(file, index, recLevel+1));
701 return 0;
704 /* Tag File Type */
705 typedef enum {
706 TFT_CHECK, TFT_ETAGS, TFT_CTAGS
707 } TFT;
710 ** Loads tagsFile into the hash table.
711 ** Returns the number of added tag specifications.
713 static int loadTagsFile(const char *tagsFile, int index, int recLevel)
715 FILE *fp = NULL;
716 char line[MAXLINE];
717 char file[MAXPATHLEN], tagPath[MAXPATHLEN];
718 char resolvedTagsFile[MAXPATHLEN+1];
719 int nTagsAdded=0;
720 int tagFileType = TFT_CHECK;
722 if(recLevel > MAX_TAG_INCLUDE_RECURSION_LEVEL) {
723 return 0;
725 /* the path of the tags file must be resolved to find the right files:
726 * definition source files are (in most cases) specified relatively inside
727 * the tags file to the tags files directory.
729 if(!ResolvePath(tagsFile, resolvedTagsFile)) {
730 return 0;
733 /* Open the file */
734 if ((fp = fopen(resolvedTagsFile, "r")) == NULL) {
735 return 0;
738 ParseFilename(resolvedTagsFile, NULL, tagPath);
740 /* Read the file and store its contents */
741 while (fgets(line, MAXLINE, fp)) {
743 /* This might take a while if you have a huge tags file (like I do)..
744 keep the windows up to date and post a busy cursor so the user
745 doesn't think we died. */
747 AllWindowsBusy("Loading tags file...");
749 /* the first character in the file decides if the file is treat as
750 etags or ctags file.
752 if(tagFileType==TFT_CHECK) {
753 if(line[0]==12) /* <np> */
754 tagFileType=TFT_ETAGS;
755 else
756 tagFileType=TFT_CTAGS;
758 if(tagFileType==TFT_CTAGS) {
759 nTagsAdded += scanCTagsLine(line, tagPath, index);
760 } else {
761 nTagsAdded += scanETagsLine(line, tagPath, index, file, recLevel);
764 fclose(fp);
766 AllWindowsUnbusy();
767 return nTagsAdded;
771 ** Given a tag name, lookup the file and path of the definition
772 ** and the proper search string. Returned strings are pointers
773 ** to internal storage which are valid until the next loadTagsFile call.
775 ** Invocation with name != NULL (containing the searched definition)
776 ** --> returns first definition of name
777 ** Successive invocation with name == NULL
778 ** --> returns further definitions (resulting from multiple tags files)
780 ** Return Value: TRUE: tag spec found
781 ** FALSE: no (more) definitions found.
783 #define TAG_STS_ERR_FMT "NEdit: Error getting status for tag file %s\n"
784 int LookupTag(const char *name, const char **file, int *language,
785 const char **searchString, int * pos, const char **path,
786 int search_type)
788 tag *t;
789 tagFile *tf;
790 struct stat statbuf;
791 tagFile *FileList;
792 int load_status;
794 searchMode = search_type;
795 if (searchMode == TIP)
796 FileList = TipsFileList;
797 else
798 FileList = TagsFileList;
801 ** Go through the list of all tags Files:
802 ** - load them (if not already loaded)
803 ** - check for update of the tags file and reload it in that case
804 ** - save the modification date of the tags file
806 ** Do this only as long as name != NULL, not for sucessive calls
807 ** to find multiple tags specs.
810 for (tf = FileList; tf && name; tf = tf->next) {
811 if (tf->loaded) {
812 if (stat(tf->filename,&statbuf) != 0) { /* */
813 fprintf(stderr, TAG_STS_ERR_FMT, tf->filename);
814 } else {
815 if (tf->date == statbuf.st_mtime) {
816 /* current tags file tf is already loaded and up to date */
817 continue;
820 /* tags file has been modified, delete it's entries and reload it */
821 delTag(NULL,NULL,-2,NULL,-2,tf->index);
823 /* If we get here we have to try to (re-) load the tags file */
824 if (FileList == TipsFileList)
825 load_status = loadTipsFile(tf->filename, tf->index, 0);
826 else
827 load_status = loadTagsFile(tf->filename, tf->index, 0);
828 if(load_status) {
829 if (stat(tf->filename,&statbuf) != 0) {
830 if(!tf->loaded) {
831 /* if tf->loaded == 1 we already have seen the error msg */
832 fprintf(stderr, TAG_STS_ERR_FMT, tf->filename);
834 } else {
835 tf->date = statbuf.st_mtime;
837 tf->loaded = 1;
838 } else {
839 tf->loaded = 0;
843 t = getTag(name, search_type);
845 if (!t) {
846 return FALSE;
847 } else {
848 *file = t->file;
849 *language = t->language;
850 *searchString = t->searchString;
851 *pos = t->posInf;
852 *path = t->path;
853 return TRUE;
858 ** This code path is followed if the request came from either
859 ** FindDefinition or FindDefCalltip. This should probably be refactored.
861 static int findDef(WindowInfo *window, const char *value, int search_type) {
862 static char tagText[MAX_TAG_LEN + 1];
863 const char *p;
864 char message[MAX_TAG_LEN+40];
865 int l, ml, status = 0;
867 searchMode = search_type;
868 l = strlen(value);
869 if (l <= MAX_TAG_LEN) {
870 /* should be of type text??? */
871 for (p = value; *p && isascii(*p); p++) {
873 if (!(*p)) {
874 ml = ((l < MAX_TAG_LEN) ? (l) : (MAX_TAG_LEN));
875 strncpy(tagText, value, ml);
876 tagText[ml] = '\0';
877 /* See if we can find the tip/tag */
878 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);
884 if (status == 0) {
885 /* Didn't find any matches */
886 if (searchMode == TIP_FROM_TAG || searchMode == TIP) {
887 sprintf(message, "No match for \"%s\" in calltips or tags.",
888 tagName);
889 tagsShowCalltip( window, message );
890 } else
892 DialogF(DF_WARN, window->textArea, 1, "Tags",
893 "\"%s\" not found in tags file%s", "OK", tagName,
894 (TagsFileList && TagsFileList->next) ? "s" : "");
898 else {
899 fprintf(stderr, "NEdit: Can't handle non 8-bit text\n");
900 XBell(TheDisplay, 0);
903 else {
904 fprintf(stderr, "NEdit: Tag Length too long.\n");
905 XBell(TheDisplay, 0);
907 return status;
911 ** Lookup the definition for the current primary selection the currently
912 ** loaded tags file and bring up the file and line that the tags file
913 ** indicates.
915 static void findDefinitionHelper(WindowInfo *window, Time time, const char *arg,
916 int search_type)
918 if(arg)
920 findDef(window, arg, search_type);
922 else
924 searchMode = search_type;
925 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING,
926 (XtSelectionCallbackProc)findDefCB, window, time);
931 ** See findDefHelper
933 void FindDefinition(WindowInfo *window, Time time, const char *arg)
935 findDefinitionHelper(window, time, arg, TAG);
939 ** See findDefHelper
941 void FindDefCalltip(WindowInfo *window, Time time, const char *arg)
943 /* Reset calltip parameters to reasonable defaults */
944 globAnchored = False;
945 globPos = -1;
946 globHAlign = TIP_LEFT;
947 globVAlign = TIP_BELOW;
948 globAlignMode = TIP_SLOPPY;
950 findDefinitionHelper(window, time, arg, TIP);
953 /* Callback function for FindDefinition */
954 static void findDefCB(Widget widget, WindowInfo *window, Atom *sel,
955 Atom *type, char *value, int *length, int *format)
957 /* skip if we can't get the selection data, or it's obviously too long */
958 if (*type == XT_CONVERT_FAIL || value == NULL) {
959 XBell(TheDisplay, 0);
960 } else {
961 findDef(window, value, searchMode);
963 XtFree(value);
967 ** Try to display a calltip
968 ** anchored: If true, tip appears at position pos
969 ** lookup: If true, text is considered a key to be searched for in the
970 ** tip and/or tag database depending on search_type
971 ** search_type: Either TIP or TIP_FROM_TAG
973 int ShowTipString(WindowInfo *window, char *text, Boolean anchored,
974 int pos, Boolean lookup, int search_type, int hAlign, int vAlign,
975 int alignMode) {
977 if (search_type == TAG) return 0;
979 /* So we don't have to carry all of the calltip alignment info around */
980 globAnchored = anchored;
981 globPos = pos;
982 globHAlign = hAlign;
983 globVAlign = vAlign;
984 globAlignMode = alignMode;
986 /* If this isn't a lookup request, just display it. */
987 if (!lookup)
988 return tagsShowCalltip(window, text);
989 else
990 return findDef(window, text, search_type);
993 /* store all of the info into a pre-allocated tags struct */
994 static void setTag(tag *t, const char *name, const char *file,
995 int language, const char *searchString, int posInf,
996 const char *path)
998 t->name = rcs_strdup(name);
999 t->file = rcs_strdup(file);
1000 t->language = language;
1001 t->searchString = rcs_strdup(searchString);
1002 t->posInf = posInf;
1003 t->path = rcs_strdup(path);
1007 ** ctags search expressions are literal strings with a search direction flag,
1008 ** line starting "^" and ending "$" delimiters. This routine translates them
1009 ** into NEdit compatible regular expressions and does the search.
1010 ** Etags search expressions are plain literals strings, which
1012 ** If in_buffer is not NULL then it is searched instead of the window buffer.
1013 ** In this case in_buffer should be an XtMalloc allocated buffer and the
1014 ** caller is responsible for freeing it.
1016 static int fakeRegExSearch(WindowInfo *window, char *in_buffer,
1017 const char *searchString, int *startPos, int *endPos)
1019 int found, searchStartPos, dir, ctagsMode;
1020 char *fileString, searchSubs[3*MAXLINE+3], *outPtr;
1021 const char *inPtr;
1023 if (in_buffer == NULL) {
1024 /* get the entire (sigh) text buffer from the text area widget */
1025 fileString = BufGetAll(window->buffer);
1026 } else {
1027 fileString = in_buffer;
1030 /* determine search direction and start position */
1031 if (*startPos != -1) { /* etags mode! */
1032 dir = SEARCH_FORWARD;
1033 searchStartPos = *startPos;
1034 ctagsMode=0;
1035 } else if (searchString[0] == '/') {
1036 dir = SEARCH_FORWARD;
1037 searchStartPos = 0;
1038 ctagsMode=1;
1039 } else if (searchString[0] == '?') {
1040 dir = SEARCH_BACKWARD;
1041 /* searchStartPos = window->buffer->length; */
1042 searchStartPos = strlen(fileString);
1043 ctagsMode=1;
1044 } else {
1045 fprintf(stderr, "NEdit: Error parsing tag file search string");
1046 if(in_buffer == NULL)
1047 XtFree(fileString);
1048 return FALSE;
1051 /* Build the search regex. */
1052 outPtr=searchSubs;
1053 if(ctagsMode) {
1054 inPtr=searchString+1; /* searchString[0] is / or ? --> search dir */
1055 if(*inPtr == '^') {
1056 /* If the first char is a caret then it's a RE line start delim */
1057 *outPtr++ = *inPtr++;
1059 } else { /* etags mode, no search dir spec, no leading caret */
1060 inPtr=searchString;
1062 while(*inPtr) {
1063 if( (*inPtr=='\\' && inPtr[1]=='/') ||
1064 (*inPtr=='\r' && inPtr[1]=='$' && !inPtr[2])
1066 /* Remove:
1067 - escapes (added by standard and exuberant ctags) from slashes
1068 - literal CRs generated by standard ctags for DOSified sources
1070 inPtr++;
1071 } else if(strchr("()-[]<>{}.|^*+?&\\", *inPtr)
1072 || (*inPtr == '$' && (inPtr[1]||(!ctagsMode)))){
1073 /* Escape RE Meta Characters to match them literally.
1074 Don't escape $ if it's the last charcter of the search expr
1075 in ctags mode; always escape $ in etags mode.
1077 *outPtr++ = '\\';
1078 *outPtr++ = *inPtr++;
1079 } else if (isspace((unsigned char)*inPtr)) { /* col. multiple spaces */
1080 *outPtr++ = '\\';
1081 *outPtr++ = 's';
1082 *outPtr++ = '+';
1083 do { inPtr++ ; } while(isspace((unsigned char)*inPtr));
1084 } else { /* simply copy all other characters */
1085 *outPtr++ = *inPtr++;
1088 *outPtr=0; /* Terminate searchSubs */
1090 found = SearchString(fileString, searchSubs, dir, SEARCH_REGEX,
1091 False, searchStartPos, startPos, endPos, NULL, NULL, NULL);
1093 if(!found && !ctagsMode) {
1094 /* position of the target definition could have been drifted before
1095 startPos, if nothing has been found by now try searching backward
1096 again from startPos.
1098 found = SearchString(fileString, searchSubs, SEARCH_BACKWARD,
1099 SEARCH_REGEX, False, searchStartPos, startPos, endPos, NULL,
1100 NULL, NULL);
1103 /* free the text buffer copy returned from XmTextGetString */
1104 if(in_buffer == NULL)
1105 XtFree(fileString);
1107 /* return the result */
1108 if (found) {
1109 /* *startPos and *endPos are set in SearchString*/
1110 return TRUE;
1111 } else {
1112 /* startPos, endPos left untouched by SearchString if search failed. */
1113 XBell(TheDisplay, 0);
1114 return FALSE;
1118 /* Finds all matches and handles tag "collisions". Prompts user with a
1119 list of collided tags in the hash table and allows the user to select
1120 the correct one. */
1121 static int findAllMatches(WindowInfo *window, const char *string)
1123 Widget dialogParent = window->textArea;
1124 char filename[MAXPATHLEN], pathname[MAXPATHLEN];
1125 char temp[32+2*MAXPATHLEN+MAXLINE];
1126 const char *fileToSearch, *searchString, *tagPath;
1127 char **dupTagsList;
1128 int startPos, i, pathMatch=0, samePath=0, langMode, nMatches=0;
1130 /* verify that the string is reasonable as a tag */
1131 if (*string == '\0' || strlen(string) > MAX_TAG_LEN) {
1132 XBell(TheDisplay, 0);
1133 return -1;
1135 tagName=string;
1137 /* First look up all of the matching tags */
1138 while (LookupTag(string, &fileToSearch, &langMode, &searchString, &startPos,
1139 &tagPath, searchMode)) {
1140 /* Skip this tag if it has a language mode that doesn't match the
1141 current language mode, but don't skip anything if the window is in
1142 PLAIN_LANGUAGE_MODE. */
1143 if (window->languageMode != PLAIN_LANGUAGE_MODE &&
1144 GetPrefSmartTags() && langMode != PLAIN_LANGUAGE_MODE &&
1145 langMode != window->languageMode) {
1146 string=NULL;
1147 continue;
1149 if (*fileToSearch == '/')
1150 strcpy(tagFiles[nMatches], fileToSearch);
1151 else
1152 sprintf(tagFiles[nMatches],"%s%s",tagPath,fileToSearch);
1153 strcpy(tagSearch[nMatches],searchString);
1154 tagPosInf[nMatches]=startPos;
1155 ParseFilename(tagFiles[nMatches], filename, pathname);
1156 /* Is this match in the current file? If so, use it! */
1157 if (GetPrefSmartTags() && !strcmp(window->filename,filename)
1158 && !strcmp(window->path,pathname) ) {
1159 if (nMatches) {
1160 strcpy(tagFiles[0],tagFiles[nMatches]);
1161 strcpy(tagSearch[0],tagSearch[nMatches]);
1162 tagPosInf[0]=tagPosInf[nMatches];
1164 nMatches = 1;
1165 break;
1167 /* Is this match in the same dir. as the current file? */
1168 if (!strcmp(window->path,pathname)) {
1169 samePath++;
1170 pathMatch=nMatches;
1172 if (++nMatches >= MAXDUPTAGS) {
1173 DialogF(DF_WARN, dialogParent, 1, "Tags",
1174 "Too many duplicate tags, first %d shown", "OK", MAXDUPTAGS);
1175 break;
1177 /* Tell LookupTag to look for more definitions of the same tag: */
1178 string = NULL;
1181 /* Did we find any matches? */
1182 if (!nMatches) {
1183 return 0;
1186 /* Only one of the matches is in the same dir. as this file. Use it. */
1187 if (GetPrefSmartTags() && samePath == 1 && nMatches > 1) {
1188 strcpy(tagFiles[0],tagFiles[pathMatch]);
1189 strcpy(tagSearch[0],tagSearch[pathMatch]);
1190 tagPosInf[0]=tagPosInf[pathMatch];
1191 nMatches = 1;
1194 /* If all of the tag entries are the same file, just use the first.
1196 if (GetPrefSmartTags()) {
1197 for (i=1; i<nMatches; i++)
1198 if (strcmp(tagFiles[i],tagFiles[i-1]))
1199 break;
1200 if (i==nMatches)
1201 nMatches = 1;
1204 if (nMatches>1) {
1205 if (!(dupTagsList = (char **) malloc(sizeof(char *) * nMatches))) {
1206 fprintf(stderr, "NEdit: findDef(): out of heap space!\n");
1207 XBell(TheDisplay, 0);
1208 return -1;
1210 for (i=0; i<nMatches; i++) {
1211 ParseFilename(tagFiles[i], filename, pathname);
1212 if ((i<nMatches-1 && !strcmp(tagFiles[i],tagFiles[i+1])) ||
1213 (i>0 && !strcmp(tagFiles[i],tagFiles[i-1]))) {
1214 if(*(tagSearch[i]) && (tagPosInf[i] != -1)) { /* etags */
1215 sprintf(temp,"%2d. %s%s %8i %s", i+1, pathname,
1216 filename, tagPosInf[i], tagSearch[i]);
1217 } else if (*(tagSearch[i])) { /* ctags search expr */
1218 sprintf(temp,"%2d. %s%s %s", i+1, pathname,
1219 filename, tagSearch[i]);
1220 } else { /* line number only */
1221 sprintf(temp,"%2d. %s%s %8i", i+1, pathname, filename,
1222 tagPosInf[i]);
1224 } else
1225 sprintf(temp,"%2d. %s%s",i+1,pathname,filename);
1226 if (!(dupTagsList[i] = (char *) malloc(strlen(temp) + 1))) {
1227 fprintf(stderr, "NEdit: findDef(): out of heap space!\n");
1228 XBell(TheDisplay, 0);
1229 return -1;
1231 strcpy(dupTagsList[i],temp);
1233 createSelectMenu(dialogParent, "Duplicate Tags", nMatches, dupTagsList);
1234 for (i=0; i<nMatches; i++)
1235 free(dupTagsList[i]);
1236 free(dupTagsList);
1237 return 1;
1240 ** No need for a dialog list, there is only one tag matching --
1241 ** Go directly to the tag
1243 if (searchMode == TAG)
1244 editTaggedLocation( dialogParent, 0 );
1245 else
1246 showMatchingCalltip( dialogParent, 0 );
1247 return 1;
1250 /* Callback function for the FindAll widget. Process the users response. */
1251 static void findAllCB(Widget parent, XtPointer client_data, XtPointer call_data)
1253 int i;
1254 char *eptr;
1256 XmSelectionBoxCallbackStruct *cbs =
1257 (XmSelectionBoxCallbackStruct *) call_data;
1258 if (cbs->reason == XmCR_NO_MATCH)
1259 return;
1260 if (cbs->reason == XmCR_CANCEL) {
1261 XtDestroyWidget(XtParent(parent));
1262 return;
1265 XmStringGetLtoR(cbs->value,XmFONTLIST_DEFAULT_TAG,&eptr);
1266 if ((i = atoi(eptr)-1) < 0) {
1267 XBell(TheDisplay, 0);
1268 return;
1271 if (searchMode == TAG)
1272 editTaggedLocation( parent, i ); /* Open the file with the definition */
1273 else
1274 showMatchingCalltip( parent, i );
1276 if (cbs->reason == XmCR_OK)
1277 XtDestroyWidget(XtParent(parent));
1280 /* Window manager close-box callback for tag-collision dialog */
1281 static void findAllCloseCB(Widget parent, XtPointer client_data,
1282 XtPointer call_data)
1284 XtDestroyWidget(parent);
1288 * Given a \0 terminated string and a position, advance the position
1289 * by n lines, where line separators (for now) are \n. If the end of
1290 * string is reached before n lines, return the number of lines advanced,
1291 * else normally return -1.
1293 static int moveAheadNLines( char *str, int *pos, int n ) {
1294 int i=n;
1295 while (str[*pos] != '\0' && n>0) {
1296 if (str[*pos] == '\n')
1297 --n;
1298 ++(*pos);
1300 if (n==0)
1301 return -1;
1302 else
1303 return i-n;
1307 ** Show the calltip specified by tagFiles[i], tagSearch[i], tagPosInf[i]
1308 ** This reads from either a source code file (if searchMode == TIP_FROM_TAG)
1309 ** or a calltips file (if searchMode == TIP).
1311 static void showMatchingCalltip( Widget parent, int i )
1313 int startPos=0, fileLen, readLen, tipLen;
1314 int endPos=0;
1315 char *fileString;
1316 FILE *fp;
1317 struct stat statbuf;
1318 char *message;
1320 /* 1. Open the target file */
1321 NormalizePathname(tagFiles[i]);
1322 fp = fopen(tagFiles[i], "r");
1323 if (fp == NULL) {
1324 DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s",
1325 "OK", tagFiles[i]);
1326 return;
1328 if (fstat(fileno(fp), &statbuf) != 0) {
1329 fclose(fp);
1330 DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s",
1331 "OK", tagFiles[i]);
1332 return;
1335 /* 2. Read the target file */
1336 /* Allocate space for the whole contents of the file (unfortunately) */
1337 fileLen = statbuf.st_size;
1338 fileString = XtMalloc(fileLen+1); /* +1 = space for null */
1339 if (fileString == NULL) {
1340 fclose(fp);
1341 DialogF(DF_ERR, parent, 1, "File too large",
1342 "File is too large to load", "OK");
1343 return;
1346 /* Read the file into fileString and terminate with a null */
1347 readLen = fread(fileString, sizeof(char), fileLen, fp);
1348 if (ferror(fp)) {
1349 fclose(fp);
1350 DialogF(DF_ERR, parent, 1, "Error reading File", "Error reading %s",
1351 "OK", tagFiles[i]);
1352 XtFree(fileString);
1353 return;
1355 fileString[readLen] = 0;
1357 /* Close the file */
1358 if (fclose(fp) != 0) {
1359 /* unlikely error */
1360 DialogF(DF_WARN, parent, 1, "Error closing File",
1361 "Unable to close file", "OK");
1362 /* we read it successfully, so continue */
1365 /* 3. Search for the tagged location (set startPos) */
1366 if (!*(tagSearch[i])) {
1367 /* It's a line number, just go for it */
1368 if ((moveAheadNLines( fileString, &startPos, tagPosInf[i]-1 )) >= 0) {
1369 DialogF(DF_ERR, parent, 1, "Tags Error",
1370 "%s\n not long enough for definition to be on line %d",
1371 "OK", tagFiles[i], tagPosInf[i]);
1372 XtFree(fileString);
1373 return;
1375 } else {
1376 startPos = tagPosInf[i];
1377 if(!fakeRegExSearch(WidgetToWindow(parent), fileString, tagSearch[i],
1378 &startPos, &endPos)){
1379 DialogF(DF_WARN, parent, 1, "Tag not found",
1380 "Definition for %s\nnot found in %s", "OK", tagName,
1381 tagFiles[i]);
1382 XtFree(fileString);
1383 return;
1387 if (searchMode == TIP) {
1388 int dummy, found;
1390 /* 4. Find the end of the calltip (delimited by an empty line) */
1391 endPos = startPos;
1392 found = SearchString(fileString, "\\n\\s*\\n", SEARCH_FORWARD,
1393 SEARCH_REGEX, False, startPos, &endPos, &dummy, NULL,
1394 NULL, NULL);
1395 if (!found) {
1396 /* Just take 4 lines */
1397 moveAheadNLines( fileString, &endPos, TIP_DEFAULT_LINES );
1398 --endPos; /* Lose the last \n */
1400 } else { /* Mode = TIP_FROM_TAG */
1401 /* 4. Copy TIP_DEFAULT_LINES lines of text to the calltip string */
1402 endPos = startPos;
1403 moveAheadNLines( fileString, &endPos, TIP_DEFAULT_LINES );
1404 /* Make sure not to overrun the fileString with ". . ." */
1405 if (((size_t) endPos) <= (strlen(fileString)-5)) {
1406 sprintf( &fileString[endPos], ". . ." );
1407 endPos += 5;
1410 /* 5. Copy the calltip to a string */
1411 tipLen = endPos - startPos;
1412 message = XtMalloc(tipLen+1); /* +1 = space for null */
1413 if (message == NULL)
1415 DialogF(DF_ERR, parent, 1, "Out of Memory",
1416 "Can't allocate memory for calltip message", "OK");
1417 XtFree(fileString);
1418 return;
1420 strncpy( message, &fileString[startPos], tipLen );
1421 message[tipLen] = 0;
1423 /* 6. Display it */
1424 tagsShowCalltip( WidgetToWindow(parent), message );
1425 XtFree(message);
1426 XtFree(fileString);
1429 /* Open a new (or existing) editor window to the location specified in
1430 tagFiles[i], tagSearch[i], tagPosInf[i] */
1431 static void editTaggedLocation( Widget parent, int i )
1433 /* Globals: tagSearch, tagPosInf, tagFiles, tagName, textNrows,
1434 WindowList */
1435 int startPos, endPos, lineNum, rows;
1436 char filename[MAXPATHLEN], pathname[MAXPATHLEN];
1437 WindowInfo *windowToSearch;
1438 WindowInfo *parentWindow = WidgetToWindow(parent);
1440 ParseFilename(tagFiles[i],filename,pathname);
1441 /* open the file containing the definition */
1442 EditExistingFile(parentWindow, filename, pathname, 0, NULL, False,
1443 NULL, GetPrefOpenInTab(), False);
1444 windowToSearch = FindWindowWithFile(filename, pathname);
1445 if (windowToSearch == NULL) {
1446 DialogF(DF_WARN, parent, 1, "File not found", "File %s not found", "OK",
1447 tagFiles[i]);
1448 return;
1451 startPos=tagPosInf[i];
1453 if(!*(tagSearch[i])) {
1454 /* if the search string is empty, select the numbered line */
1455 SelectNumberedLine(windowToSearch, startPos);
1456 return;
1459 /* search for the tags file search string in the newly opened file */
1460 if(!fakeRegExSearch(windowToSearch, NULL, tagSearch[i], &startPos,
1461 &endPos)){
1462 DialogF(DF_WARN, windowToSearch->shell, 1, "Tag Error",
1463 "Definition for %s\nnot found in %s", "OK", tagName,
1464 tagFiles[i]);
1465 return;
1468 /* select the matched string */
1469 BufSelect(windowToSearch->buffer, startPos, endPos);
1470 RaiseFocusDocumentWindow(windowToSearch, True);
1472 /* Position it nicely in the window,
1473 about 1/4 of the way down from the top */
1474 lineNum = BufCountLines(windowToSearch->buffer, 0, startPos);
1475 XtVaGetValues(windowToSearch->lastFocus, textNrows, &rows, NULL);
1476 TextSetScroll(windowToSearch->lastFocus, lineNum - rows/4, 0);
1477 TextSetCursorPos(windowToSearch->lastFocus, endPos);
1480 /* Create a Menu for user to select from the collided tags */
1481 static Widget createSelectMenu(Widget parent, char *label, int nArgs,
1482 char *args[])
1484 int i;
1485 char tmpStr[100];
1486 Widget menu;
1487 XmStringTable list;
1488 XmString popupTitle;
1489 int ac;
1490 Arg csdargs[20];
1492 list = (XmStringTable) XtMalloc(nArgs * sizeof(XmString *));
1493 for (i=0; i<nArgs; i++)
1494 list[i] = XmStringCreateSimple(args[i]);
1495 sprintf(tmpStr,"Select File With TAG: %s",tagName);
1496 popupTitle = XmStringCreateSimple(tmpStr);
1497 ac = 0;
1498 XtSetArg(csdargs[ac], XmNlistLabelString, popupTitle); ac++;
1499 XtSetArg(csdargs[ac], XmNlistItems, list); ac++;
1500 XtSetArg(csdargs[ac], XmNlistItemCount, nArgs); ac++;
1501 XtSetArg(csdargs[ac], XmNvisibleItemCount, 12); ac++;
1502 XtSetArg(csdargs[ac], XmNautoUnmanage, False); ac++;
1503 menu = CreateSelectionDialog(parent,label,csdargs,ac);
1504 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_TEXT));
1505 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_HELP_BUTTON));
1506 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_SELECTION_LABEL));
1507 XtAddCallback(menu, XmNokCallback, (XtCallbackProc)findAllCB, menu);
1508 XtAddCallback(menu, XmNapplyCallback, (XtCallbackProc)findAllCB, menu);
1509 XtAddCallback(menu, XmNcancelCallback, (XtCallbackProc)findAllCB, menu);
1510 AddMotifCloseCallback(XtParent(menu), findAllCloseCB, NULL);
1511 for (i=0; i<nArgs; i++)
1512 XmStringFree(list[i]);
1513 XtFree((char *)list);
1514 XmStringFree(popupTitle);
1515 ManageDialogCenteredOnPointer(menu);
1516 return menu;
1521 /*--------------------------------------------------------------------------
1523 Reference-counted string hack; SJT 4/2000
1525 This stuff isn't specific to tags, so it should be in it's own file.
1526 However, I'm leaving it in here for now to reduce the diffs.
1528 This could really benefit from using a real hash table.
1531 #define RCS_SIZE 10000
1533 struct rcs;
1535 struct rcs_stats
1537 int talloc, tshar, tgiveup, tbytes, tbyteshared;
1540 struct rcs
1542 struct rcs *next;
1543 char *string;
1544 int usage;
1547 static struct rcs *Rcs[RCS_SIZE];
1548 static struct rcs_stats RcsStats;
1551 ** Take a normal string, create a shared string from it if need be,
1552 ** and return pointer to that shared string.
1554 ** Returned strings are const because they are shared. Do not modify them!
1557 static const char *rcs_strdup(const char *str)
1559 int bucket;
1560 size_t len;
1561 struct rcs *rp;
1562 struct rcs *prev = NULL;
1564 char *newstr = NULL;
1566 if (str == NULL)
1567 return NULL;
1569 bucket = hashAddr(str) % RCS_SIZE;
1570 len = strlen(str);
1572 RcsStats.talloc++;
1574 #if 0
1575 /* Don't share if it won't save space.
1577 Doesn't save anything - if we have lots of small-size objects,
1578 it's beneifical to share them. We don't know until we make a full
1579 count. My tests show that it's better to leave this out. */
1580 if (len <= sizeof(struct rcs))
1582 new_str = strdup(str); /* GET RID OF strdup() IF EVER ENABLED (not ANSI) */
1583 RcsStats.tgiveup++;
1584 return;
1586 #endif
1588 /* Find it in hash */
1589 for (rp = Rcs[bucket]; rp; rp = rp->next)
1591 if (!strcmp(str, rp->string))
1592 break;
1593 prev = rp;
1596 if (rp) /* It exists, return it and bump ref ct */
1598 rp->usage++;
1599 newstr = rp->string;
1601 RcsStats.tshar++;
1602 RcsStats.tbyteshared += len;
1604 else /* Doesn't exist, conjure up a new one. */
1606 struct rcs *newrcs = malloc(sizeof(struct rcs));
1607 newrcs->string = malloc(len+1);
1608 strcpy(newrcs->string, str);
1609 newrcs->usage = 1;
1610 newrcs->next = NULL;
1612 if (Rcs[bucket])
1613 prev->next = newrcs;
1614 else
1615 Rcs[bucket] = newrcs;
1617 newstr = newrcs->string;
1620 RcsStats.tbytes += len;
1621 return newstr;
1625 ** Decrease the reference count on a shared string. When the reference
1626 ** count reaches zero, free the master string.
1629 static void rcs_free(const char *rcs_str)
1631 int bucket;
1632 struct rcs *rp;
1633 struct rcs *prev = NULL;
1635 if (rcs_str == NULL)
1636 return;
1638 bucket = hashAddr(rcs_str) % RCS_SIZE;
1640 /* find it in hash */
1641 for (rp = Rcs[bucket]; rp; rp = rp->next)
1643 if (rcs_str == rp->string)
1644 break;
1645 prev = rp;
1648 if (rp) /* It's a shared string, decrease ref count */
1650 rp->usage--;
1652 if (rp->usage < 0) /* D'OH! */
1654 fprintf(stderr, "NEdit: internal error deallocating shared string.");
1655 return;
1658 if (rp->usage == 0) /* Last one- free the storage */
1660 free(rp->string);
1661 if (prev)
1662 prev->next = rp->next;
1663 else
1664 Rcs[bucket] = rp->next;
1665 free(rp);
1668 else /* Doesn't appear to be a shared string */
1670 fprintf(stderr, "NEdit: attempt to free a non-shared string.");
1671 return;
1675 /********************************************************************
1676 * Functions for loading Calltips files *
1677 ********************************************************************/
1679 enum tftoken_types { TF_EOF, TF_BLOCK, TF_VERSION, TF_INCLUDE, TF_LANGUAGE,
1680 TF_ALIAS, TF_ERROR, TF_ERROR_EOF };
1682 /* A wrapper for SearchString */
1683 static int searchLine(char *line, const char *regex) {
1684 int dummy1, dummy2;
1685 return SearchString(line, regex, SEARCH_FORWARD, SEARCH_REGEX,
1686 False, 0, &dummy1, &dummy2, NULL, NULL, NULL);
1689 /* Check if a line has non-ws characters */
1690 static Boolean lineEmpty(const char *line) {
1691 while (*line && *line != '\n') {
1692 if (*line != ' ' && *line != '\t')
1693 return False;
1694 ++line;
1696 return True;
1699 /* Remove trailing whitespace from a line */
1700 static void rstrip( char *dst, const char *src ) {
1701 int wsStart, dummy2;
1702 /* Strip trailing whitespace */
1703 if(SearchString(src, "\\s*\\n", SEARCH_FORWARD, SEARCH_REGEX,
1704 False, 0, &wsStart, &dummy2, NULL, NULL, NULL)) {
1705 if(dst != src)
1706 memcpy(dst, src, wsStart);
1707 dst[wsStart] = 0;
1708 } else
1709 if(dst != src)
1710 strcpy(dst, src);
1714 ** Get the next block from a tips file. A block is a \n\n+ delimited set of
1715 ** lines in a calltips file. All of the parameters except <fp> are return
1716 ** values, and most have different roles depending on the type of block
1717 ** that is found.
1718 ** header: Depends on the block type
1719 ** body: Depends on the block type. Used to return a new
1720 ** dynamically allocated string.
1721 ** blkLine: Returns the line number of the first line of the block
1722 ** after the "* xxxx *" line.
1723 ** currLine: Used to keep track of the current line in the file.
1725 static int nextTFBlock(FILE *fp, char *header, char **body, int *blkLine,
1726 int *currLine)
1728 /* These are the different kinds of tokens */
1729 const char *commenTF_regex = "^\\s*\\* comment \\*\\s*$";
1730 const char *version_regex = "^\\s*\\* version \\*\\s*$";
1731 const char *include_regex = "^\\s*\\* include \\*\\s*$";
1732 const char *language_regex = "^\\s*\\* language \\*\\s*$";
1733 const char *alias_regex = "^\\s*\\* alias \\*\\s*$";
1734 char line[MAXLINE], *status;
1735 int dummy1;
1736 int code;
1738 /* Skip blank lines and comments */
1739 while(1) {
1740 /* Skip blank lines */
1741 while((status=fgets(line, MAXLINE, fp))) {
1742 ++(*currLine);
1743 if(!lineEmpty( line ))
1744 break;
1747 /* Check for error or EOF */
1748 if(!status)
1749 return TF_EOF;
1751 /* We've got a non-blank line -- is it a comment block? */
1752 if( !searchLine(line, commenTF_regex) )
1753 break;
1755 /* Skip the comment (non-blank lines) */
1756 while((status=fgets(line, MAXLINE, fp))) {
1757 ++(*currLine);
1758 if(lineEmpty( line ))
1759 break;
1762 if(!status)
1763 return TF_EOF;
1766 /* Now we know it's a meaningful block */
1767 dummy1 = searchLine(line, include_regex);
1768 if( dummy1 || searchLine(line, alias_regex) ) {
1769 /* INCLUDE or ALIAS block */
1770 int incLen, incPos, i, incLines;
1772 /* fprintf(stderr, "Starting include/alias at line %i\n", *currLine); */
1773 if(dummy1)
1774 code = TF_INCLUDE;
1775 else {
1776 code = TF_ALIAS;
1777 /* Need to read the header line for an alias */
1778 status=fgets(line, MAXLINE, fp);
1779 ++(*currLine);
1780 if (!status)
1781 return TF_ERROR_EOF;
1782 if (lineEmpty( line )) {
1783 fprintf( stderr, "nedit: Warning: empty '* alias *' "
1784 "block in calltips file.\n" );
1785 return TF_ERROR;
1787 rstrip(header, line);
1789 incPos = ftell(fp);
1790 *blkLine = *currLine + 1; /* Line of first actual filename/alias */
1791 if (incPos < 0)
1792 return TF_ERROR;
1793 /* Figure out how long the block is */
1794 while((status=fgets(line, MAXLINE, fp))) {
1795 ++(*currLine);
1796 if(lineEmpty( line ))
1797 break;
1799 incLen = ftell(fp) - incPos;
1800 incLines = *currLine - *blkLine;
1801 /* Correct currLine for the empty line it read at the end */
1802 --(*currLine);
1803 if (incLines == 0) {
1804 fprintf( stderr, "nedit: Warning: empty '* include *' or"
1805 " '* alias *' block in calltips file.\n" );
1806 return TF_ERROR;
1808 /* Make space for the filenames/alias sources */
1809 *body = (char *)malloc(incLen+1);
1810 if (!*body)
1811 return TF_ERROR;
1812 *body[0]=0;
1813 if (fseek(fp, incPos, SEEK_SET) != 0) {
1814 free (*body);
1815 return TF_ERROR;
1817 /* Read all the lines in the block */
1818 /* fprintf(stderr, "Copying lines\n"); */
1819 for (i=0; i<incLines; i++) {
1820 status = fgets(line, MAXLINE, fp);
1821 if (!status) {
1822 free (*body);
1823 return TF_ERROR_EOF;
1825 rstrip(line,line);
1826 if(i)
1827 strcat(*body, ":");
1828 strcat(*body, line);
1830 /* fprintf(stderr, "Finished include/alias at line %i\n", *currLine); */
1833 else if( searchLine(line, language_regex) ) {
1834 /* LANGUAGE block */
1835 status=fgets(line, MAXLINE, fp);
1836 ++(*currLine);
1837 if (!status)
1838 return TF_ERROR_EOF;
1839 if (lineEmpty( line )) {
1840 fprintf( stderr, "nedit: Warning: empty '* language *' block in calltips file.\n" );
1841 return TF_ERROR;
1843 *blkLine = *currLine;
1844 rstrip(header, line);
1845 code = TF_LANGUAGE;
1848 else if( searchLine(line, version_regex) ) {
1849 /* VERSION block */
1850 status=fgets(line, MAXLINE, fp);
1851 ++(*currLine);
1852 if (!status)
1853 return TF_ERROR_EOF;
1854 if (lineEmpty( line )) {
1855 fprintf( stderr, "nedit: Warning: empty '* version *' block in calltips file.\n" );
1856 return TF_ERROR;
1858 *blkLine = *currLine;
1859 rstrip(header, line);
1860 code = TF_VERSION;
1863 else {
1864 /* Calltip block */
1865 /* The first line is the key, the rest is the tip.
1866 Strip trailing whitespace. */
1867 rstrip(header, line);
1869 status=fgets(line, MAXLINE, fp);
1870 ++(*currLine);
1871 if (!status)
1872 return TF_ERROR_EOF;
1873 if (lineEmpty( line )) {
1874 fprintf( stderr, "nedit: Warning: empty calltip block:\n"
1875 " \"%s\"\n", header);
1876 return TF_ERROR;
1878 *blkLine = *currLine;
1879 *body = strdup(line);
1880 code = TF_BLOCK;
1883 /* Skip the rest of the block */
1884 dummy1 = *currLine;
1885 while(fgets(line, MAXLINE, fp)) {
1886 ++(*currLine);
1887 if (lineEmpty( line ))
1888 break;
1891 /* Warn about any unneeded extra lines (which are ignored). */
1892 if (dummy1+1 < *currLine && code != TF_BLOCK) {
1893 fprintf( stderr, "nedit: Warning: extra lines in language or version block ignored.\n" );
1896 return code;
1899 /* A struct for describing a calltip alias */
1900 typedef struct _alias {
1901 char *dest;
1902 char *sources;
1903 struct _alias *next;
1904 } tf_alias;
1907 ** Allocate a new alias, copying dest and stealing sources. This may
1908 ** seem strange but that's the way it's called
1910 static tf_alias *new_alias(const char *dest, char *sources) {
1911 tf_alias *alias;
1913 /* fprintf(stderr, "new_alias: %s <- %s\n", dest, sources); */
1914 /* Allocate the alias */
1915 alias = (tf_alias *)malloc( sizeof(tf_alias) );
1916 if(!alias)
1917 return NULL;
1919 /* Fill it in */
1920 alias->dest = (char*)malloc( strlen(dest)+1 );
1921 if(!(alias->dest))
1922 return NULL;
1923 strcpy( alias->dest, dest );
1924 alias->sources = sources;
1925 return alias;
1928 /* Deallocate a linked-list of aliases */
1929 static void free_alias_list(tf_alias *alias) {
1930 tf_alias *tmp_alias;
1931 while(alias) {
1932 tmp_alias = alias->next;
1933 free(alias->dest);
1934 free(alias->sources);
1935 free(alias);
1936 alias = tmp_alias;
1941 ** Load a calltips file and insert all of the entries into the global tips
1942 ** database. Each tip is essentially stored as its filename and the line
1943 ** at which it appears--the exact same way ctags indexes source-code. That's
1944 ** why calltips and tags share so much code.
1946 static int loadTipsFile(const char *tipsFile, int index, int recLevel)
1948 FILE *fp = NULL;
1949 char header[MAXLINE];
1950 char *body, *tipIncFile;
1951 char tipPath[MAXPATHLEN];
1952 char resolvedTipsFile[MAXPATHLEN+1];
1953 int nTipsAdded=0, langMode = PLAIN_LANGUAGE_MODE, oldLangMode;
1954 int currLine=0, code, blkLine;
1955 tf_alias *aliases=NULL, *tmp_alias;
1957 if(recLevel > MAX_TAG_INCLUDE_RECURSION_LEVEL) {
1958 fprintf(stderr, "nedit: Warning: Reached recursion limit before loading calltips file:\n\t%s\n", tipsFile);
1959 return 0;
1962 /* find the tips file */
1963 #ifndef VMS
1964 /* Allow ~ in Unix filenames */
1965 strncpy(tipPath, tipsFile, MAXPATHLEN); /* ExpandTilde is destructive */
1966 ExpandTilde(tipPath);
1967 if(!ResolvePath(tipPath, resolvedTipsFile))
1968 return 0;
1969 #else
1970 if(!ResolvePath(tipsFile, resolvedTipsFile))
1971 return 0;
1972 #endif
1974 /* Get the path to the tips file */
1975 ParseFilename(resolvedTipsFile, NULL, tipPath);
1977 /* Open the file */
1978 if ((fp = fopen(resolvedTipsFile, "r")) == NULL)
1979 return 0;
1981 while( 1 ) {
1982 code = nextTFBlock(fp, header, &body, &blkLine, &currLine);
1983 if( code == TF_ERROR_EOF ) {
1984 fprintf(stderr,"nedit: Warning: unexpected EOF in calltips file.\n");
1985 break;
1987 if( code == TF_EOF )
1988 break;
1990 switch (code) {
1991 case TF_BLOCK:
1992 /* Add the calltip to the global hash table.
1993 For the moment I'm just using line numbers because I don't
1994 want to have to deal with adding escape characters for
1995 regex metacharacters that might appear in the string */
1996 nTipsAdded += addTag(header, resolvedTipsFile, langMode, "",
1997 blkLine, tipPath, index);
1998 free( body );
1999 break;
2000 case TF_INCLUDE:
2001 /* nextTFBlock returns a colon-separated list of tips files
2002 in body */
2003 for(tipIncFile=strtok(body,":"); tipIncFile;
2004 tipIncFile=strtok(NULL,":")) {
2005 /* fprintf(stderr,
2006 "nedit: DEBUG: including tips file '%s'\n",
2007 tipIncFile); */
2008 nTipsAdded += loadTipsFile( tipIncFile, index, recLevel+1);
2010 free( body );
2011 break;
2012 case TF_LANGUAGE:
2013 /* Switch to the new language mode if it's valid, else ignore
2014 it. */
2015 oldLangMode = langMode;
2016 langMode = FindLanguageMode( header );
2017 if (langMode == PLAIN_LANGUAGE_MODE &&
2018 strcmp(header, "Plain")) {
2019 fprintf(stderr,
2020 "nedit: Error reading calltips file:\n\t%s\n"
2021 "Unknown language mode: \"%s\"\n",
2022 tipsFile, header);
2023 langMode = oldLangMode;
2025 break;
2026 case TF_ERROR:
2027 fprintf(stderr,"nedit: Warning: Recoverable error while "
2028 "reading calltips file:\n \"%s\"\n",
2029 resolvedTipsFile);
2030 break;
2031 case TF_ALIAS:
2032 /* Allocate a new alias struct */
2033 tmp_alias = aliases;
2034 aliases = new_alias(header, body);
2035 if( !aliases ) {
2036 fprintf(stderr,"nedit: Can't allocate memory for tipfile "
2037 "alias in calltips file:\n \"%s\"\n",
2038 resolvedTipsFile);
2039 /* Deallocate any allocated aliases */
2040 free_alias_list(tmp_alias);
2041 return 0;
2043 /* Add it to the list */
2044 aliases->next = tmp_alias;
2045 break;
2046 default:
2047 ;/* Ignore TF_VERSION for now */
2051 /* Now resolve any aliases */
2052 tmp_alias = aliases;
2053 while (tmp_alias) {
2054 tag *t;
2055 char *src;
2056 t = getTag(tmp_alias->dest, TIP);
2057 if (!t) {
2058 fprintf(stderr, "nedit: Can't find destination of alias \"%s\"\n"
2059 " in calltips file:\n \"%s\"\n",
2060 tmp_alias->dest, resolvedTipsFile);
2061 } else {
2062 for(src=strtok(tmp_alias->sources,":"); src; src=strtok(NULL,":"))
2063 addTag(src, resolvedTipsFile, t->language, "", t->posInf,
2064 tipPath, index);
2066 tmp_alias = tmp_alias->next;
2068 free_alias_list(aliases);
2069 return nTipsAdded;