Restored the "invalid default virtual key binding" workaround that had
[nedit.git] / source / tags.c
blob376f4595539c4940096b2151d42476c003c6354d
1 static const char CVSID[] = "$Id: tags.c,v 1.59 2004/08/01 10:06:11 yooden Exp $";
2 /*******************************************************************************
3 * *
4 * tags.c -- Nirvana editor tag file handling *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. In addition, you may distribute version 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();
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);
258 CompressPathname(newfile);
260 for (t = table[addr]; t; t = t->next) {
261 if (strcmp(name,t->name)) continue;
262 if (lang != t->language) continue;
263 if (strcmp(search,t->searchString)) continue;
264 if (posInf != t->posInf) continue;
265 if (*t->file == '/' && strcmp(newfile,t->file)) continue;
266 if (*t->file != '/') {
267 char tmpfile[MAXPATHLEN];
268 sprintf(tmpfile, "%s%s", t->path, t->file);
269 NormalizePathname(tmpfile);
270 CompressPathname(tmpfile);
271 if (strcmp(newfile, tmpfile)) continue;
273 return 0;
276 t = (tag *) malloc(sizeof(tag));
277 setTag(t, name, file, lang, search, posInf, path);
278 t->index = index;
279 t->next = table[addr];
280 table[addr] = t;
281 return 1;
284 /* Delete a tag from the cache.
285 * Search is limited to valid matches of 'name','file', 'search', posInf, and 'index'.
286 * EX: delete all tags matching index 2 ==>
287 * delTag(tagname,NULL,-2,NULL,-2,2);
288 * (posInf = -2 is an invalid match, posInf range: -1 .. +MAXINT,
289 lang = -2 is also an invalid match)
291 static int delTag(const char *name, const char *file, int lang,
292 const char *search, int posInf, int index)
294 tag *t, *last;
295 int start,finish,i,del=0;
296 tag **table;
298 if (searchMode == TIP)
299 table = Tips;
300 else
301 table = Tags;
303 if (table == NULL) return FALSE;
304 if (name)
305 start = finish = hashAddr(name) % DefTagHashSize;
306 else {
307 start = 0;
308 finish = DefTagHashSize;
310 for (i = start; i<finish; i++) {
311 for (last = NULL, t = table[i]; t; last = t, t = t?t->next:table[i]) {
312 if (name && strcmp(name,t->name)) continue;
313 if (index && index != t->index) continue;
314 if (file && strcmp(file,t->file)) continue;
315 if (lang >= PLAIN_LANGUAGE_MODE && lang != t->language) continue;
316 if (search && strcmp(search,t->searchString)) continue;
317 if (posInf == t->posInf) continue;
318 if (last)
319 last->next = t->next;
320 else
321 table[i] = t->next;
322 rcs_free(t->name);
323 rcs_free(t->file);
324 rcs_free(t->searchString);
325 rcs_free(t->path);
326 free(t);
327 t = NULL;
328 del++;
331 return del>0;
334 /* used in AddRelTagsFile and AddTagsFile */
335 static int tagFileIndex = 0;
338 ** AddRelTagsFile(): Rescan tagSpec for relative tag file specs
339 ** (not starting with [/~]) and extend the tag files list if in
340 ** windowPath a tags file matching the relative spec has been found.
342 int AddRelTagsFile(const char *tagSpec, const char *windowPath, int file_type)
344 tagFile *t;
345 int added=0;
346 struct stat statbuf;
347 char *filename;
348 char pathName[MAXPATHLEN];
349 char *tmptagSpec;
350 tagFile *FileList;
352 searchMode = file_type;
353 if (searchMode == TAG)
354 FileList = TagsFileList;
355 else
356 FileList = TipsFileList;
358 tmptagSpec = (char *) malloc(strlen(tagSpec)+1);
359 strcpy(tmptagSpec, tagSpec);
360 for (filename = strtok(tmptagSpec, ":"); filename; filename = strtok(NULL, ":")){
361 if (*filename == '/' || *filename == '~')
362 continue;
363 if (windowPath && *windowPath) {
364 strcpy(pathName, windowPath);
365 } else {
366 strcpy(pathName, GetCurrentDir());
368 strcat(pathName, "/");
369 strcat(pathName, filename);
370 NormalizePathname(pathName);
371 CompressPathname(pathName);
373 for (t = FileList; t && strcmp(t->filename, pathName); t = t->next);
374 if (t) {
375 added=1;
376 continue;
378 if (stat(pathName, &statbuf) != 0)
379 continue;
380 t = (tagFile *) malloc(sizeof(tagFile));
381 t->filename = STRSAVE(pathName);
382 t->loaded = 0;
383 t->date = statbuf.st_mtime;
384 t->index = ++tagFileIndex;
385 t->next = FileList;
386 FileList = setFileListHead(t, file_type);
387 added=1;
389 free(tmptagSpec);
390 updateMenuItems();
391 if (added)
392 return TRUE;
393 else
394 return FALSE;
398 ** AddTagsFile(): Set up the the list of tag files to manage from a file spec.
399 ** The file spec comes from the X-Resource Nedit.tags: It can list multiple
400 ** tags files, specified by separating them with colons. The .Xdefaults would
401 ** look like this:
402 ** Nedit.tags: <tagfile1>:<tagfile2>
403 ** Returns True if all files were found in the FileList or loaded successfully,
404 ** FALSE otherwise.
406 int AddTagsFile(const char *tagSpec, int file_type)
408 tagFile *t;
409 int added=1;
410 struct stat statbuf;
411 char *filename;
412 char pathName[MAXPATHLEN];
413 char *tmptagSpec;
414 tagFile *FileList;
416 /* To prevent any possible segfault */
417 if (tagSpec == NULL) {
418 fprintf(stderr, "nedit: Internal Error!\n"
419 " Passed NULL pointer to AddTagsFile!\n");
420 return FALSE;
423 searchMode = file_type;
424 if (searchMode == TAG)
425 FileList = TagsFileList;
426 else
427 FileList = TipsFileList;
429 tmptagSpec = (char *) malloc(strlen(tagSpec)+1);
430 strcpy(tmptagSpec, tagSpec);
431 for (filename = strtok(tmptagSpec,":"); filename; filename = strtok(NULL,":")) {
432 if (*filename != '/') {
433 strcpy(pathName, GetCurrentDir());
434 strcat(pathName,"/");
435 strcat(pathName,filename);
436 } else {
437 strcpy(pathName,filename);
439 NormalizePathname(pathName);
440 CompressPathname(pathName);
442 for (t = FileList; t && strcmp(t->filename,pathName); t = t->next);
443 if (t) {
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 FileList = setFileListHead(t, file_type );
460 free(tmptagSpec);
461 updateMenuItems();
462 if (added)
463 return TRUE;
464 else
465 return FALSE;
468 /* Un-manage a colon-delimited set of tags files
469 * Return TRUE if all files were found in the FileList and unloaded, FALSE
470 * if any file was not found in the FileList.
472 int DeleteTagsFile(const char *tagSpec, int file_type)
474 tagFile *t, *last;
475 tagFile *FileList;
476 char pathName[MAXPATHLEN], *tmptagSpec, *filename;
477 int removed;
479 /* To prevent any possible segfault */
480 if (tagSpec == NULL) {
481 fprintf(stderr, "nedit: Internal Error!\n"
482 " Passed NULL pointer to DeleteTagsFile!\n");
483 return FALSE;
486 searchMode = file_type;
487 if (searchMode == TAG)
488 FileList = TagsFileList;
489 else
490 FileList = TipsFileList;
492 tmptagSpec = (char *) malloc(strlen(tagSpec)+1);
493 strcpy(tmptagSpec, tagSpec);
494 removed=1;
495 for (filename = strtok(tmptagSpec,":"); filename;
496 filename = strtok(NULL,":")) {
497 if (*filename != '/') {
498 strcpy(pathName, GetCurrentDir());
499 strcat(pathName,"/");
500 strcat(pathName,filename);
501 } else {
502 strcpy(pathName,filename);
504 NormalizePathname(pathName);
505 CompressPathname(pathName);
507 for (last=NULL,t = FileList; t; last = t,t = t->next) {
508 if (strcmp(t->filename, pathName))
509 continue;
510 if (t->loaded)
511 delTag(NULL,NULL,-2,NULL,-2,t->index);
512 if (last) last->next = t->next;
513 else FileList = setFileListHead(t->next, file_type);
514 free(t->filename);
515 free(t);
516 updateMenuItems();
517 break;
519 /* If any file can't be removed, return false */
520 if (!t)
521 removed = 0;
523 if (removed)
524 return TRUE;
525 else
526 return FALSE;
530 ** Update the "Find Definition", "Unload Tags File", "Show Calltip",
531 ** and "Unload Calltips File" menu items in the existing windows.
533 static void updateMenuItems()
535 WindowInfo *w;
536 Boolean tipStat=FALSE, tagStat=FALSE;
538 if (TipsFileList) tipStat=TRUE;
539 if (TagsFileList) tagStat=TRUE;
541 for (w=WindowList; w!=NULL; w=w->next) {
542 if (!IsTopDocument(w))
543 continue;
544 XtSetSensitive(w->showTipItem, tipStat || tagStat);
545 XtSetSensitive(w->unloadTipsMenuItem, tipStat);
546 XtSetSensitive(w->findDefItem, tagStat);
547 XtSetSensitive(w->unloadTagsMenuItem, tagStat);
552 ** Scans one <line> from a ctags tags file (<index>) in tagPath.
553 ** Return value: Number of tag specs added.
555 static int scanCTagsLine(const char *line, const char *tagPath, int index)
557 char name[MAXLINE], searchString[MAXLINE];
558 char file[MAXPATHLEN];
559 char *posTagREEnd, *posTagRENull;
560 int nRead, pos;
562 nRead = sscanf(line, "%s\t%s\t%[^\n]", name, file, searchString);
563 if (nRead != 3)
564 return 0;
565 if ( *name == '!' )
566 return 0;
569 ** Guess the end of searchString:
570 ** Try to handle original ctags and exuberant ctags format:
572 if(searchString[0] == '/' || searchString[0] == '?') {
574 pos=-1; /* "search expr without pos info" */
576 /* Situations: /<ANY expr>/\0
577 ** ?<ANY expr>?\0 --> original ctags
578 ** /<ANY expr>/;" <flags>
579 ** ?<ANY expr>?;" <flags> --> exuberant ctags
581 posTagREEnd = strrchr(searchString, ';');
582 posTagRENull = strchr(searchString, 0);
583 if(!posTagREEnd || (posTagREEnd[1] != '"') ||
584 (posTagRENull[-1] == searchString[0])) {
585 /* -> original ctags format = exuberant ctags format 1 */
586 posTagREEnd = posTagRENull;
587 } else {
588 /* looks like exuberant ctags format 2 */
589 *posTagREEnd = 0;
593 ** Hide the last delimiter:
594 ** /<expression>/ becomes /<expression>
595 ** ?<expression>? becomes ?<expression>
596 ** This will save a little work in fakeRegExSearch.
598 if(posTagREEnd > (searchString+2)) {
599 posTagREEnd--;
600 if(searchString[0] == *posTagREEnd)
601 *posTagREEnd=0;
603 } else {
604 pos=atoi(searchString);
605 *searchString=0;
607 /* No ability to read language mode right now */
608 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos, tagPath,
609 index);
613 * Scans one <line> from an etags (emacs) tags file (<index>) in tagPath.
614 * recLevel = current recursion level for tags file including
615 * file = destination definition file. possibly modified. len=MAXPATHLEN!
616 * Return value: Number of tag specs added.
618 static int scanETagsLine(const char *line, const char * tagPath, int index,
619 char * file, int recLevel)
621 char name[MAXLINE], searchString[MAXLINE];
622 char incPath[MAXPATHLEN];
623 int pos, len;
624 char *posDEL, *posSOH, *posCOM;
626 /* check for destination file separator */
627 if(line[0]==12) { /* <np> */
628 *file=0;
629 return 0;
632 /* check for standard definition line */
633 posDEL=strchr(line, '\177');
634 posSOH=strchr(line, '\001');
635 posCOM=strrchr(line, ',');
636 if(*file && posDEL && (posSOH > posDEL) && (posCOM > posSOH)) {
637 /* exuberant ctags -e style */
638 len=Min(MAXLINE-1, posDEL - line);
639 strncpy(searchString, line, len);
640 searchString[len]=0;
641 len=Min(MAXLINE-1, (posSOH - posDEL) - 1);
642 strncpy(name, posDEL + 1, len);
643 name[len]=0;
644 pos=atoi(posCOM+1);
645 /* No ability to set language mode for the moment */
646 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos,
647 tagPath, index);
649 if (*file && posDEL && (posCOM > posDEL)) {
650 /* old etags style, part name<soh> is missing here! */
651 len=Min(MAXLINE-1, posDEL - line);
652 strncpy(searchString, line, len);
653 searchString[len]=0;
654 /* guess name: take the last alnum (plus _) part of searchString */
655 while(--len >= 0) {
656 if( isalnum((unsigned char)searchString[len]) ||
657 (searchString[len] == '_'))
658 break;
660 if(len<0)
661 return 0;
662 pos=len;
663 while (pos >= 0 && (isalnum((unsigned char)searchString[pos]) ||
664 (searchString[pos] == '_')))
665 pos--;
666 strncpy(name, searchString + pos + 1, len - pos);
667 name[len - pos] = 0; /* name ready */
668 pos=atoi(posCOM+1);
669 return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos,
670 tagPath, index);
672 /* check for destination file spec */
673 if(*line && posCOM) {
674 len=Min(MAXPATHLEN-1, posCOM - line);
675 strncpy(file, line, len);
676 file[len]=0;
677 /* check if that's an include file ... */
678 if(!(strncmp(posCOM+1, "include", 7))) {
679 if(*file != '/') {
680 if((strlen(tagPath) + strlen(file)) >= MAXPATHLEN) {
681 fprintf(stderr, "tags.c: MAXPATHLEN overflow\n");
682 *file=0; /* invalidate */
683 return 0;
685 strcpy(incPath, tagPath);
686 strcat(incPath, file);
687 CompressPathname(incPath);
688 return(loadTagsFile(incPath, index, recLevel+1));
689 } else {
690 return(loadTagsFile(file, index, recLevel+1));
694 return 0;
697 /* Tag File Type */
698 typedef enum {
699 TFT_CHECK, TFT_ETAGS, TFT_CTAGS
700 } TFT;
703 ** Loads tagsFile into the hash table.
704 ** Returns the number of added tag specifications.
706 static int loadTagsFile(const char *tagsFile, int index, int recLevel)
708 FILE *fp = NULL;
709 char line[MAXLINE];
710 char file[MAXPATHLEN], tagPath[MAXPATHLEN];
711 char resolvedTagsFile[MAXPATHLEN+1];
712 int nTagsAdded=0;
713 int tagFileType = TFT_CHECK;
715 if(recLevel > MAX_TAG_INCLUDE_RECURSION_LEVEL) {
716 return 0;
718 /* the path of the tags file must be resolved to find the right files:
719 * definition source files are (in most cases) specified relatively inside
720 * the tags file to the tags files directory.
722 if(!ResolvePath(tagsFile, resolvedTagsFile)) {
723 return 0;
726 /* Open the file */
727 if ((fp = fopen(resolvedTagsFile, "r")) == NULL) {
728 return 0;
731 ParseFilename(resolvedTagsFile, NULL, tagPath);
733 /* Read the file and store its contents */
734 while (fgets(line, MAXLINE, fp)) {
736 /* This might take a while if you have a huge tags file (like I do)..
737 keep the windows up to date and post a busy cursor so the user
738 doesn't think we died. */
740 AllWindowsBusy("Loading tags file...");
742 /* the first character in the file decides if the file is treat as
743 etags or ctags file.
745 if(tagFileType==TFT_CHECK) {
746 if(line[0]==12) /* <np> */
747 tagFileType=TFT_ETAGS;
748 else
749 tagFileType=TFT_CTAGS;
751 if(tagFileType==TFT_CTAGS) {
752 nTagsAdded += scanCTagsLine(line, tagPath, index);
753 } else {
754 nTagsAdded += scanETagsLine(line, tagPath, index, file, recLevel);
757 fclose(fp);
759 AllWindowsUnbusy();
760 return nTagsAdded;
764 ** Given a tag name, lookup the file and path of the definition
765 ** and the proper search string. Returned strings are pointers
766 ** to internal storage which are valid until the next loadTagsFile call.
768 ** Invocation with name != NULL (containing the searched definition)
769 ** --> returns first definition of name
770 ** Successive invocation with name == NULL
771 ** --> returns further definitions (resulting from multiple tags files)
773 ** Return Value: TRUE: tag spec found
774 ** FALSE: no (more) definitions found.
776 #define TAG_STS_ERR_FMT "NEdit: Error getting status for tag file %s\n"
777 int LookupTag(const char *name, const char **file, int *language,
778 const char **searchString, int * pos, const char **path,
779 int search_type)
781 tag *t;
782 tagFile *tf;
783 struct stat statbuf;
784 tagFile *FileList;
785 int load_status;
787 searchMode = search_type;
788 if (searchMode == TIP)
789 FileList = TipsFileList;
790 else
791 FileList = TagsFileList;
794 ** Go through the list of all tags Files:
795 ** - load them (if not already loaded)
796 ** - check for update of the tags file and reload it in that case
797 ** - save the modification date of the tags file
799 ** Do this only as long as name != NULL, not for sucessive calls
800 ** to find multiple tags specs.
803 for (tf = FileList; tf && name; tf = tf->next) {
804 if (tf->loaded) {
805 if (stat(tf->filename,&statbuf) != 0) { /* */
806 fprintf(stderr, TAG_STS_ERR_FMT, tf->filename);
807 } else {
808 if (tf->date == statbuf.st_mtime) {
809 /* current tags file tf is already loaded and up to date */
810 continue;
813 /* tags file has been modified, delete it's entries and reload it */
814 delTag(NULL,NULL,-2,NULL,-2,tf->index);
816 /* If we get here we have to try to (re-) load the tags file */
817 if (FileList == TipsFileList)
818 load_status = loadTipsFile(tf->filename, tf->index, 0);
819 else
820 load_status = loadTagsFile(tf->filename, tf->index, 0);
821 if(load_status) {
822 if (stat(tf->filename,&statbuf) != 0) {
823 if(!tf->loaded) {
824 /* if tf->loaded == 1 we already have seen the error msg */
825 fprintf(stderr, TAG_STS_ERR_FMT, tf->filename);
827 } else {
828 tf->date = statbuf.st_mtime;
830 tf->loaded = 1;
831 } else {
832 tf->loaded = 0;
836 t = getTag(name, search_type);
838 if (!t) {
839 return FALSE;
840 } else {
841 *file = t->file;
842 *language = t->language;
843 *searchString = t->searchString;
844 *pos = t->posInf;
845 *path = t->path;
846 return TRUE;
851 ** This code path is followed if the request came from either
852 ** FindDefinition or FindDefCalltip. This should probably be refactored.
854 static int findDef(WindowInfo *window, const char *value, int search_type) {
855 static char tagText[MAX_TAG_LEN + 1];
856 const char *p;
857 char message[MAX_TAG_LEN+40];
858 int l, ml, status = 0;
860 searchMode = search_type;
861 l = strlen(value);
862 if (l <= MAX_TAG_LEN) {
863 /* should be of type text??? */
864 for (p = value; *p && isascii(*p); p++) {
866 if (!(*p)) {
867 ml = ((l < MAX_TAG_LEN) ? (l) : (MAX_TAG_LEN));
868 strncpy(tagText, value, ml);
869 tagText[ml] = '\0';
870 /* See if we can find the tip/tag */
871 status = findAllMatches(window, tagText);
872 /* If we didn't find a requested calltip, see if we can use a tag */
873 if (status == 0 && search_type == TIP && TagsFileList != NULL) {
874 searchMode = TIP_FROM_TAG;
875 status = findAllMatches(window, tagText);
877 if (status == 0) {
878 /* Didn't find any matches */
879 if (searchMode == TIP_FROM_TAG || searchMode == TIP) {
880 sprintf(message, "No match for \"%s\" in calltips or tags.",
881 tagName);
882 tagsShowCalltip( window, message );
883 } else
885 DialogF(DF_WARN, window->textArea, 1, "Tags",
886 "\"%s\" not found in tags file%s", "OK", tagName,
887 (TagsFileList && TagsFileList->next) ? "s" : "");
891 else {
892 fprintf(stderr, "NEdit: Can't handle non 8-bit text\n");
893 XBell(TheDisplay, 0);
896 else {
897 fprintf(stderr, "NEdit: Tag Length too long.\n");
898 XBell(TheDisplay, 0);
900 return status;
904 ** Lookup the definition for the current primary selection the currently
905 ** loaded tags file and bring up the file and line that the tags file
906 ** indicates.
908 void findDefinitionHelper(WindowInfo *window, Time time, const char *arg,
909 int search_type)
911 if(arg)
913 findDef(window, arg, search_type);
915 else
917 searchMode = search_type;
918 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING,
919 (XtSelectionCallbackProc)findDefCB, window, time);
924 ** See findDefHelper
926 void FindDefinition(WindowInfo *window, Time time, const char *arg)
928 findDefinitionHelper(window, time, arg, TAG);
932 ** See findDefHelper
934 void FindDefCalltip(WindowInfo *window, Time time, const char *arg)
936 /* Reset calltip parameters to reasonable defaults */
937 globAnchored = False;
938 globPos = -1;
939 globHAlign = TIP_LEFT;
940 globVAlign = TIP_BELOW;
941 globAlignMode = TIP_SLOPPY;
943 findDefinitionHelper(window, time, arg, TIP);
946 /* Callback function for FindDefinition */
947 static void findDefCB(Widget widget, WindowInfo *window, Atom *sel,
948 Atom *type, char *value, int *length, int *format)
950 /* skip if we can't get the selection data, or it's obviously too long */
951 if (*type == XT_CONVERT_FAIL || value == NULL) {
952 XBell(TheDisplay, 0);
953 } else {
954 findDef(window, value, searchMode);
956 XtFree(value);
960 ** Try to display a calltip
961 ** anchored: If true, tip appears at position pos
962 ** lookup: If true, text is considered a key to be searched for in the
963 ** tip and/or tag database depending on search_type
964 ** search_type: Either TIP or TIP_FROM_TAG
966 int ShowTipString(WindowInfo *window, char *text, Boolean anchored,
967 int pos, Boolean lookup, int search_type, int hAlign, int vAlign,
968 int alignMode) {
970 if (search_type == TAG) return 0;
972 /* So we don't have to carry all of the calltip alignment info around */
973 globAnchored = anchored;
974 globPos = pos;
975 globHAlign = hAlign;
976 globVAlign = vAlign;
977 globAlignMode = alignMode;
979 /* If this isn't a lookup request, just display it. */
980 if (!lookup)
981 return tagsShowCalltip(window, text);
982 else
983 return findDef(window, text, search_type);
986 /* store all of the info into a pre-allocated tags struct */
987 static void setTag(tag *t, const char *name, const char *file,
988 int language, const char *searchString, int posInf,
989 const char *path)
991 t->name = rcs_strdup(name);
992 t->file = rcs_strdup(file);
993 t->language = language;
994 t->searchString = rcs_strdup(searchString);
995 t->posInf = posInf;
996 t->path = rcs_strdup(path);
1000 ** ctags search expressions are literal strings with a search direction flag,
1001 ** line starting "^" and ending "$" delimiters. This routine translates them
1002 ** into NEdit compatible regular expressions and does the search.
1003 ** Etags search expressions are plain literals strings, which
1005 ** If in_buffer is not NULL then it is searched instead of the window buffer.
1006 ** In this case in_buffer should be an XtMalloc allocated buffer and the
1007 ** caller is responsible for freeing it.
1009 static int fakeRegExSearch(WindowInfo *window, char *in_buffer,
1010 const char *searchString, int *startPos, int *endPos)
1012 int found, searchStartPos, dir, ctagsMode;
1013 char *fileString, searchSubs[3*MAXLINE+3], *outPtr;
1014 const char *inPtr;
1016 if (in_buffer == NULL) {
1017 /* get the entire (sigh) text buffer from the text area widget */
1018 fileString = BufGetAll(window->buffer);
1019 } else {
1020 fileString = in_buffer;
1023 /* determine search direction and start position */
1024 if (*startPos != -1) { /* etags mode! */
1025 dir = SEARCH_FORWARD;
1026 searchStartPos = *startPos;
1027 ctagsMode=0;
1028 } else if (searchString[0] == '/') {
1029 dir = SEARCH_FORWARD;
1030 searchStartPos = 0;
1031 ctagsMode=1;
1032 } else if (searchString[0] == '?') {
1033 dir = SEARCH_BACKWARD;
1034 /* searchStartPos = window->buffer->length; */
1035 searchStartPos = strlen(fileString);
1036 ctagsMode=1;
1037 } else {
1038 fprintf(stderr, "NEdit: Error parsing tag file search string");
1039 if(in_buffer == NULL)
1040 XtFree(fileString);
1041 return FALSE;
1044 /* Build the search regex. */
1045 outPtr=searchSubs;
1046 if(ctagsMode) {
1047 inPtr=searchString+1; /* searchString[0] is / or ? --> search dir */
1048 if(*inPtr == '^') {
1049 /* If the first char is a caret then it's a RE line start delim */
1050 *outPtr++ = *inPtr++;
1052 } else { /* etags mode, no search dir spec, no leading caret */
1053 inPtr=searchString;
1055 while(*inPtr) {
1056 if( (*inPtr=='\\' && inPtr[1]=='/') ||
1057 (*inPtr=='\r' && inPtr[1]=='$' && !inPtr[2])
1059 /* Remove:
1060 - escapes (added by standard and exuberant ctags) from slashes
1061 - literal CRs generated by standard ctags for DOSified sources
1063 inPtr++;
1064 } else if(strchr("()-[]<>{}.|^*+?&\\", *inPtr)
1065 || (*inPtr == '$' && (inPtr[1]||(!ctagsMode)))){
1066 /* Escape RE Meta Characters to match them literally.
1067 Don't escape $ if it's the last charcter of the search expr
1068 in ctags mode; always escape $ in etags mode.
1070 *outPtr++ = '\\';
1071 *outPtr++ = *inPtr++;
1072 } else if (isspace((unsigned char)*inPtr)) { /* col. multiple spaces */
1073 *outPtr++ = '\\';
1074 *outPtr++ = 's';
1075 *outPtr++ = '+';
1076 do { inPtr++ ; } while(isspace((unsigned char)*inPtr));
1077 } else { /* simply copy all other characters */
1078 *outPtr++ = *inPtr++;
1081 *outPtr=0; /* Terminate searchSubs */
1083 found = SearchString(fileString, searchSubs, dir, SEARCH_REGEX,
1084 False, searchStartPos, startPos, endPos, NULL, NULL, NULL);
1086 if(!found && !ctagsMode) {
1087 /* position of the target definition could have been drifted before
1088 startPos, if nothing has been found by now try searching backward
1089 again from startPos.
1091 found = SearchString(fileString, searchSubs, SEARCH_BACKWARD,
1092 SEARCH_REGEX, False, searchStartPos, startPos, endPos, NULL,
1093 NULL, NULL);
1096 /* free the text buffer copy returned from XmTextGetString */
1097 if(in_buffer == NULL)
1098 XtFree(fileString);
1100 /* return the result */
1101 if (found) {
1102 /* *startPos and *endPos are set in SearchString*/
1103 return TRUE;
1104 } else {
1105 /* startPos, endPos left untouched by SearchString if search failed. */
1106 XBell(TheDisplay, 0);
1107 return FALSE;
1111 /* Finds all matches and handles tag "collisions". Prompts user with a
1112 list of collided tags in the hash table and allows the user to select
1113 the correct one. */
1114 static int findAllMatches(WindowInfo *window, const char *string)
1116 Widget dialogParent = window->textArea;
1117 char filename[MAXPATHLEN], pathname[MAXPATHLEN];
1118 char temp[32+2*MAXPATHLEN+MAXLINE];
1119 const char *fileToSearch, *searchString, *tagPath;
1120 char **dupTagsList;
1121 int startPos, i, pathMatch=0, samePath=0, langMode, nMatches=0;
1123 /* verify that the string is reasonable as a tag */
1124 if (*string == '\0' || strlen(string) > MAX_TAG_LEN) {
1125 XBell(TheDisplay, 0);
1126 return -1;
1128 tagName=string;
1130 /* First look up all of the matching tags */
1131 while (LookupTag(string, &fileToSearch, &langMode, &searchString, &startPos,
1132 &tagPath, searchMode)) {
1133 /* Skip this tag if it has a language mode that doesn't match the
1134 current language mode, but don't skip anything if the window is in
1135 PLAIN_LANGUAGE_MODE. */
1136 if (window->languageMode != PLAIN_LANGUAGE_MODE &&
1137 GetPrefSmartTags() && langMode != PLAIN_LANGUAGE_MODE &&
1138 langMode != window->languageMode) {
1139 string=NULL;
1140 continue;
1142 if (*fileToSearch == '/')
1143 strcpy(tagFiles[nMatches], fileToSearch);
1144 else
1145 sprintf(tagFiles[nMatches],"%s%s",tagPath,fileToSearch);
1146 strcpy(tagSearch[nMatches],searchString);
1147 tagPosInf[nMatches]=startPos;
1148 ParseFilename(tagFiles[nMatches], filename, pathname);
1149 /* Is this match in the current file? If so, use it! */
1150 if (GetPrefSmartTags() && !strcmp(window->filename,filename)
1151 && !strcmp(window->path,pathname) ) {
1152 if (nMatches) {
1153 strcpy(tagFiles[0],tagFiles[nMatches]);
1154 strcpy(tagSearch[0],tagSearch[nMatches]);
1155 tagPosInf[0]=tagPosInf[nMatches];
1157 nMatches = 1;
1158 break;
1160 /* Is this match in the same dir. as the current file? */
1161 if (!strcmp(window->path,pathname)) {
1162 samePath++;
1163 pathMatch=nMatches;
1165 if (++nMatches >= MAXDUPTAGS) {
1166 DialogF(DF_WARN, dialogParent, 1, "Tags",
1167 "Too many duplicate tags, first %d shown", "OK", MAXDUPTAGS);
1168 break;
1170 /* Tell LookupTag to look for more definitions of the same tag: */
1171 string = NULL;
1174 /* Did we find any matches? */
1175 if (!nMatches) {
1176 return 0;
1179 /* Only one of the matches is in the same dir. as this file. Use it. */
1180 if (GetPrefSmartTags() && samePath == 1 && nMatches > 1) {
1181 strcpy(tagFiles[0],tagFiles[pathMatch]);
1182 strcpy(tagSearch[0],tagSearch[pathMatch]);
1183 tagPosInf[0]=tagPosInf[pathMatch];
1184 nMatches = 1;
1187 /* If all of the tag entries are the same file, just use the first.
1189 if (GetPrefSmartTags()) {
1190 for (i=1; i<nMatches; i++)
1191 if (strcmp(tagFiles[i],tagFiles[i-1]))
1192 break;
1193 if (i==nMatches)
1194 nMatches = 1;
1197 if (nMatches>1) {
1198 if (!(dupTagsList = (char **) malloc(sizeof(char *) * nMatches))) {
1199 fprintf(stderr, "NEdit: findDef(): out of heap space!\n");
1200 XBell(TheDisplay, 0);
1201 return -1;
1203 for (i=0; i<nMatches; i++) {
1204 ParseFilename(tagFiles[i], filename, pathname);
1205 if ((i<nMatches-1 && !strcmp(tagFiles[i],tagFiles[i+1])) ||
1206 (i>0 && !strcmp(tagFiles[i],tagFiles[i-1]))) {
1207 if(*(tagSearch[i]) && (tagPosInf[i] != -1)) { /* etags */
1208 sprintf(temp,"%2d. %s%s %8i %s", i+1, pathname,
1209 filename, tagPosInf[i], tagSearch[i]);
1210 } else if (*(tagSearch[i])) { /* ctags search expr */
1211 sprintf(temp,"%2d. %s%s %s", i+1, pathname,
1212 filename, tagSearch[i]);
1213 } else { /* line number only */
1214 sprintf(temp,"%2d. %s%s %8i", i+1, pathname, filename,
1215 tagPosInf[i]);
1217 } else
1218 sprintf(temp,"%2d. %s%s",i+1,pathname,filename);
1219 if (!(dupTagsList[i] = (char *) malloc(strlen(temp) + 1))) {
1220 fprintf(stderr, "NEdit: findDef(): out of heap space!\n");
1221 XBell(TheDisplay, 0);
1222 return -1;
1224 strcpy(dupTagsList[i],temp);
1226 createSelectMenu(dialogParent, "Duplicate Tags", nMatches, dupTagsList);
1227 for (i=0; i<nMatches; i++)
1228 free(dupTagsList[i]);
1229 free(dupTagsList);
1230 return 1;
1233 ** No need for a dialog list, there is only one tag matching --
1234 ** Go directly to the tag
1236 if (searchMode == TAG)
1237 editTaggedLocation( dialogParent, 0 );
1238 else
1239 showMatchingCalltip( dialogParent, 0 );
1240 return 1;
1243 /* Callback function for the FindAll widget. Process the users response. */
1244 static void findAllCB(Widget parent, XtPointer client_data, XtPointer call_data)
1246 int i;
1247 char *eptr;
1249 XmSelectionBoxCallbackStruct *cbs =
1250 (XmSelectionBoxCallbackStruct *) call_data;
1251 if (cbs->reason == XmCR_NO_MATCH)
1252 return;
1253 if (cbs->reason == XmCR_CANCEL) {
1254 XtDestroyWidget(XtParent(parent));
1255 return;
1258 XmStringGetLtoR(cbs->value,XmFONTLIST_DEFAULT_TAG,&eptr);
1259 if ((i = atoi(eptr)-1) < 0) {
1260 XBell(TheDisplay, 0);
1261 return;
1264 if (searchMode == TAG)
1265 editTaggedLocation( parent, i ); /* Open the file with the definition */
1266 else
1267 showMatchingCalltip( parent, i );
1269 if (cbs->reason == XmCR_OK)
1270 XtDestroyWidget(XtParent(parent));
1273 /* Window manager close-box callback for tag-collision dialog */
1274 static void findAllCloseCB(Widget parent, XtPointer client_data,
1275 XtPointer call_data)
1277 XtDestroyWidget(parent);
1281 * Given a \0 terminated string and a position, advance the position
1282 * by n lines, where line separators (for now) are \n. If the end of
1283 * string is reached before n lines, return the number of lines advanced,
1284 * else normally return -1.
1286 static int moveAheadNLines( char *str, int *pos, int n ) {
1287 int i=n;
1288 while (str[*pos] != '\0' && n>0) {
1289 if (str[*pos] == '\n')
1290 --n;
1291 ++(*pos);
1293 if (n==0)
1294 return -1;
1295 else
1296 return i-n;
1300 ** Show the calltip specified by tagFiles[i], tagSearch[i], tagPosInf[i]
1301 ** This reads from either a source code file (if searchMode == TIP_FROM_TAG)
1302 ** or a calltips file (if searchMode == TIP).
1304 static void showMatchingCalltip( Widget parent, int i )
1306 int startPos=0, fileLen, readLen, tipLen;
1307 int endPos=0;
1308 char *fileString;
1309 FILE *fp;
1310 struct stat statbuf;
1311 char *message;
1313 /* 1. Open the target file */
1314 NormalizePathname(tagFiles[i]);
1315 fp = fopen(tagFiles[i], "r");
1316 if (fp == NULL) {
1317 DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s",
1318 "OK", tagFiles[i]);
1319 return;
1321 if (fstat(fileno(fp), &statbuf) != 0) {
1322 fclose(fp);
1323 DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s",
1324 "OK", tagFiles[i]);
1325 return;
1328 /* 2. Read the target file */
1329 /* Allocate space for the whole contents of the file (unfortunately) */
1330 fileLen = statbuf.st_size;
1331 fileString = XtMalloc(fileLen+1); /* +1 = space for null */
1332 if (fileString == NULL) {
1333 fclose(fp);
1334 DialogF(DF_ERR, parent, 1, "File too large",
1335 "File is too large to load", "OK");
1336 return;
1339 /* Read the file into fileString and terminate with a null */
1340 readLen = fread(fileString, sizeof(char), fileLen, fp);
1341 if (ferror(fp)) {
1342 fclose(fp);
1343 DialogF(DF_ERR, parent, 1, "Error reading File", "Error reading %s",
1344 "OK", tagFiles[i]);
1345 XtFree(fileString);
1346 return;
1348 fileString[readLen] = 0;
1350 /* Close the file */
1351 if (fclose(fp) != 0) {
1352 /* unlikely error */
1353 DialogF(DF_WARN, parent, 1, "Error closing File",
1354 "Unable to close file", "OK");
1355 /* we read it successfully, so continue */
1358 /* 3. Search for the tagged location (set startPos) */
1359 if (!*(tagSearch[i])) {
1360 /* It's a line number, just go for it */
1361 if ((moveAheadNLines( fileString, &startPos, tagPosInf[i]-1 )) >= 0) {
1362 DialogF(DF_ERR, parent, 1, "Tags Error",
1363 "%s\n not long enough for definition to be on line %d",
1364 "OK", tagFiles[i], tagPosInf[i]);
1365 XtFree(fileString);
1366 return;
1368 } else {
1369 startPos = tagPosInf[i];
1370 if(!fakeRegExSearch(WidgetToWindow(parent), fileString, tagSearch[i],
1371 &startPos, &endPos)){
1372 DialogF(DF_WARN, parent, 1, "Tag not found",
1373 "Definition for %s\nnot found in %s", "OK", tagName,
1374 tagFiles[i]);
1375 XtFree(fileString);
1376 return;
1380 if (searchMode == TIP) {
1381 int dummy, found;
1383 /* 4. Find the end of the calltip (delimited by an empty line) */
1384 endPos = startPos;
1385 found = SearchString(fileString, "\\n\\s*\\n", SEARCH_FORWARD,
1386 SEARCH_REGEX, False, startPos, &endPos, &dummy, NULL,
1387 NULL, NULL);
1388 if (!found) {
1389 /* Just take 4 lines */
1390 moveAheadNLines( fileString, &endPos, TIP_DEFAULT_LINES );
1391 --endPos; /* Lose the last \n */
1393 } else { /* Mode = TIP_FROM_TAG */
1394 /* 4. Copy TIP_DEFAULT_LINES lines of text to the calltip string */
1395 endPos = startPos;
1396 moveAheadNLines( fileString, &endPos, TIP_DEFAULT_LINES );
1397 /* Make sure not to overrun the fileString with ". . ." */
1398 if (((size_t) endPos) <= (strlen(fileString)-5)) {
1399 sprintf( &fileString[endPos], ". . ." );
1400 endPos += 5;
1403 /* 5. Copy the calltip to a string */
1404 tipLen = endPos - startPos;
1405 message = XtMalloc(tipLen+1); /* +1 = space for null */
1406 if (message == NULL)
1408 DialogF(DF_ERR, parent, 1, "Out of Memory",
1409 "Can't allocate memory for calltip message", "OK");
1410 XtFree(fileString);
1411 return;
1413 strncpy( message, &fileString[startPos], tipLen );
1414 message[tipLen] = 0;
1416 /* 6. Display it */
1417 tagsShowCalltip( WidgetToWindow(parent), message );
1418 XtFree(message);
1419 XtFree(fileString);
1422 /* Open a new (or existing) editor window to the location specified in
1423 tagFiles[i], tagSearch[i], tagPosInf[i] */
1424 static void editTaggedLocation( Widget parent, int i )
1426 /* Globals: tagSearch, tagPosInf, tagFiles, tagName, textNrows,
1427 WindowList */
1428 int startPos, endPos, lineNum, rows;
1429 char filename[MAXPATHLEN], pathname[MAXPATHLEN];
1430 WindowInfo *windowToSearch;
1431 WindowInfo *parentWindow = WidgetToWindow(parent);
1433 ParseFilename(tagFiles[i],filename,pathname);
1434 /* open the file containing the definition */
1435 EditExistingFile(parentWindow, filename, pathname, 0, NULL, False,
1436 NULL, GetPrefOpenInTab(), False);
1437 windowToSearch = FindWindowWithFile(filename, pathname);
1438 if (windowToSearch == NULL) {
1439 DialogF(DF_WARN, parent, 1, "File not found", "File %s not found", "OK",
1440 tagFiles[i]);
1441 return;
1444 startPos=tagPosInf[i];
1446 if(!*(tagSearch[i])) {
1447 /* if the search string is empty, select the numbered line */
1448 SelectNumberedLine(windowToSearch, startPos);
1449 return;
1452 /* search for the tags file search string in the newly opened file */
1453 if(!fakeRegExSearch(windowToSearch, NULL, tagSearch[i], &startPos,
1454 &endPos)){
1455 DialogF(DF_WARN, windowToSearch->shell, 1, "Tag Error",
1456 "Definition for %s\nnot found in %s", "OK", tagName,
1457 tagFiles[i]);
1458 return;
1461 /* select the matched string */
1462 BufSelect(windowToSearch->buffer, startPos, endPos);
1464 /* Position it nicely in the window,
1465 about 1/4 of the way down from the top */
1466 lineNum = BufCountLines(windowToSearch->buffer, 0, startPos);
1467 XtVaGetValues(windowToSearch->lastFocus, textNrows, &rows, NULL);
1468 TextSetScroll(windowToSearch->lastFocus, lineNum - rows/4, 0);
1469 TextSetCursorPos(windowToSearch->lastFocus, endPos);
1472 /* Create a Menu for user to select from the collided tags */
1473 static Widget createSelectMenu(Widget parent, char *label, int nArgs,
1474 char *args[])
1476 int i;
1477 char tmpStr[100];
1478 Widget menu;
1479 XmStringTable list;
1480 XmString popupTitle;
1481 int ac;
1482 Arg csdargs[20];
1484 list = (XmStringTable) XtMalloc(nArgs * sizeof(XmString *));
1485 for (i=0; i<nArgs; i++)
1486 list[i] = XmStringCreateSimple(args[i]);
1487 sprintf(tmpStr,"Select File With TAG: %s",tagName);
1488 popupTitle = XmStringCreateSimple(tmpStr);
1489 ac = 0;
1490 XtSetArg(csdargs[ac], XmNlistLabelString, popupTitle); ac++;
1491 XtSetArg(csdargs[ac], XmNlistItems, list); ac++;
1492 XtSetArg(csdargs[ac], XmNlistItemCount, nArgs); ac++;
1493 XtSetArg(csdargs[ac], XmNvisibleItemCount, 12); ac++;
1494 XtSetArg(csdargs[ac], XmNautoUnmanage, False); ac++;
1495 menu = CreateSelectionDialog(parent,label,csdargs,ac);
1496 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_TEXT));
1497 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_HELP_BUTTON));
1498 XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_SELECTION_LABEL));
1499 XtAddCallback(menu, XmNokCallback, (XtCallbackProc)findAllCB, menu);
1500 XtAddCallback(menu, XmNapplyCallback, (XtCallbackProc)findAllCB, menu);
1501 XtAddCallback(menu, XmNcancelCallback, (XtCallbackProc)findAllCB, menu);
1502 AddMotifCloseCallback(XtParent(menu), findAllCloseCB, NULL);
1503 for (i=0; i<nArgs; i++)
1504 XmStringFree(list[i]);
1505 XtFree((char *)list);
1506 XmStringFree(popupTitle);
1507 ManageDialogCenteredOnPointer(menu);
1508 return menu;
1513 /*--------------------------------------------------------------------------
1515 Reference-counted string hack; SJT 4/2000
1517 This stuff isn't specific to tags, so it should be in it's own file.
1518 However, I'm leaving it in here for now to reduce the diffs.
1520 This could really benefit from using a real hash table.
1523 #define RCS_SIZE 10000
1525 struct rcs;
1527 struct rcs_stats
1529 int talloc, tshar, tgiveup, tbytes, tbyteshared;
1532 struct rcs
1534 struct rcs *next;
1535 char *string;
1536 int usage;
1539 static struct rcs *Rcs[RCS_SIZE];
1540 static struct rcs_stats RcsStats;
1543 ** Take a normal string, create a shared string from it if need be,
1544 ** and return pointer to that shared string.
1546 ** Returned strings are const because they are shared. Do not modify them!
1549 static const char *rcs_strdup(const char *str)
1551 int bucket;
1552 size_t len;
1553 struct rcs *rp;
1554 struct rcs *prev = NULL;
1556 char *newstr = NULL;
1558 if (str == NULL)
1559 return NULL;
1561 bucket = hashAddr(str) % RCS_SIZE;
1562 len = strlen(str);
1564 RcsStats.talloc++;
1566 #if 0
1567 /* Don't share if it won't save space.
1569 Doesn't save anything - if we have lots of small-size objects,
1570 it's beneifical to share them. We don't know until we make a full
1571 count. My tests show that it's better to leave this out. */
1572 if (len <= sizeof(struct rcs))
1574 new_str = strdup(str); /* GET RID OF strdup() IF EVER ENABLED (not ANSI) */
1575 RcsStats.tgiveup++;
1576 return;
1578 #endif
1580 /* Find it in hash */
1581 for (rp = Rcs[bucket]; rp; rp = rp->next)
1583 if (!strcmp(str, rp->string))
1584 break;
1585 prev = rp;
1588 if (rp) /* It exists, return it and bump ref ct */
1590 rp->usage++;
1591 newstr = rp->string;
1593 RcsStats.tshar++;
1594 RcsStats.tbyteshared += len;
1596 else /* Doesn't exist, conjure up a new one. */
1598 struct rcs *newrcs = malloc(sizeof(struct rcs));
1599 newrcs->string = malloc(len+1);
1600 strcpy(newrcs->string, str);
1601 newrcs->usage = 1;
1602 newrcs->next = NULL;
1604 if (Rcs[bucket])
1605 prev->next = newrcs;
1606 else
1607 Rcs[bucket] = newrcs;
1609 newstr = newrcs->string;
1612 RcsStats.tbytes += len;
1613 return newstr;
1617 ** Decrease the reference count on a shared string. When the reference
1618 ** count reaches zero, free the master string.
1621 static void rcs_free(const char *rcs_str)
1623 int bucket;
1624 struct rcs *rp;
1625 struct rcs *prev = NULL;
1627 if (rcs_str == NULL)
1628 return;
1630 bucket = hashAddr(rcs_str) % RCS_SIZE;
1632 /* find it in hash */
1633 for (rp = Rcs[bucket]; rp; rp = rp->next)
1635 if (rcs_str == rp->string)
1636 break;
1637 prev = rp;
1640 if (rp) /* It's a shared string, decrease ref count */
1642 rp->usage--;
1644 if (rp->usage < 0) /* D'OH! */
1646 fprintf(stderr, "NEdit: internal error deallocating shared string.");
1647 return;
1650 if (rp->usage == 0) /* Last one- free the storage */
1652 free(rp->string);
1653 if (prev)
1654 prev->next = rp->next;
1655 else
1656 Rcs[bucket] = rp->next;
1657 free(rp);
1660 else /* Doesn't appear to be a shared string */
1662 fprintf(stderr, "NEdit: attempt to free a non-shared string.");
1663 return;
1667 /********************************************************************
1668 * Functions for loading Calltips files *
1669 ********************************************************************/
1671 enum tftoken_types { TF_EOF, TF_BLOCK, TF_VERSION, TF_INCLUDE, TF_LANGUAGE,
1672 TF_ALIAS, TF_ERROR, TF_ERROR_EOF };
1674 /* A wrapper for SearchString */
1675 static int searchLine(char *line, const char *regex) {
1676 int dummy1, dummy2;
1677 return SearchString(line, regex, SEARCH_FORWARD, SEARCH_REGEX,
1678 False, 0, &dummy1, &dummy2, NULL, NULL, NULL);
1681 /* Check if a line has non-ws characters */
1682 static Boolean lineEmpty(const char *line) {
1683 while (*line && *line != '\n') {
1684 if (*line != ' ' && *line != '\t')
1685 return False;
1686 ++line;
1688 return True;
1691 /* Remove trailing whitespace from a line */
1692 static void rstrip( char *dst, const char *src ) {
1693 int wsStart, dummy2;
1694 /* Strip trailing whitespace */
1695 if(SearchString(src, "\\s*\\n", SEARCH_FORWARD, SEARCH_REGEX,
1696 False, 0, &wsStart, &dummy2, NULL, NULL, NULL)) {
1697 if(dst != src)
1698 memcpy(dst, src, wsStart);
1699 dst[wsStart] = 0;
1700 } else
1701 if(dst != src)
1702 strcpy(dst, src);
1706 ** Get the next block from a tips file. A block is a \n\n+ delimited set of
1707 ** lines in a calltips file. All of the parameters except <fp> are return
1708 ** values, and most have different roles depending on the type of block
1709 ** that is found.
1710 ** header: Depends on the block type
1711 ** body: Depends on the block type. Used to return a new
1712 ** dynamically allocated string.
1713 ** blkLine: Returns the line number of the first line of the block
1714 ** after the "* xxxx *" line.
1715 ** currLine: Used to keep track of the current line in the file.
1717 static int nextTFBlock(FILE *fp, char *header, char **body, int *blkLine,
1718 int *currLine)
1720 /* These are the different kinds of tokens */
1721 const char *commenTF_regex = "^\\s*\\* comment \\*\\s*$";
1722 const char *version_regex = "^\\s*\\* version \\*\\s*$";
1723 const char *include_regex = "^\\s*\\* include \\*\\s*$";
1724 const char *language_regex = "^\\s*\\* language \\*\\s*$";
1725 const char *alias_regex = "^\\s*\\* alias \\*\\s*$";
1726 char line[MAXLINE], *status;
1727 int dummy1;
1728 int code;
1730 /* Skip blank lines and comments */
1731 while(1) {
1732 /* Skip blank lines */
1733 while((status=fgets(line, MAXLINE, fp))) {
1734 ++(*currLine);
1735 if(!lineEmpty( line ))
1736 break;
1739 /* Check for error or EOF */
1740 if(!status)
1741 return TF_EOF;
1743 /* We've got a non-blank line -- is it a comment block? */
1744 if( !searchLine(line, commenTF_regex) )
1745 break;
1747 /* Skip the comment (non-blank lines) */
1748 while((status=fgets(line, MAXLINE, fp))) {
1749 ++(*currLine);
1750 if(lineEmpty( line ))
1751 break;
1754 if(!status)
1755 return TF_EOF;
1758 /* Now we know it's a meaningful block */
1759 dummy1 = searchLine(line, include_regex);
1760 if( dummy1 || searchLine(line, alias_regex) ) {
1761 /* INCLUDE or ALIAS block */
1762 int incLen, incPos, i, incLines;
1764 /* fprintf(stderr, "Starting include/alias at line %i\n", *currLine); */
1765 if(dummy1)
1766 code = TF_INCLUDE;
1767 else {
1768 code = TF_ALIAS;
1769 /* Need to read the header line for an alias */
1770 status=fgets(line, MAXLINE, fp);
1771 ++(*currLine);
1772 if (!status)
1773 return TF_ERROR_EOF;
1774 if (lineEmpty( line )) {
1775 fprintf( stderr, "nedit: Warning: empty '* alias *' "
1776 "block in calltips file.\n" );
1777 return TF_ERROR;
1779 rstrip(header, line);
1781 incPos = ftell(fp);
1782 *blkLine = *currLine + 1; /* Line of first actual filename/alias */
1783 if (incPos < 0)
1784 return TF_ERROR;
1785 /* Figure out how long the block is */
1786 while((status=fgets(line, MAXLINE, fp))) {
1787 ++(*currLine);
1788 if(lineEmpty( line ))
1789 break;
1791 incLen = ftell(fp) - incPos;
1792 incLines = *currLine - *blkLine;
1793 /* Correct currLine for the empty line it read at the end */
1794 --(*currLine);
1795 if (incLines == 0) {
1796 fprintf( stderr, "nedit: Warning: empty '* include *' or"
1797 " '* alias *' block in calltips file.\n" );
1798 return TF_ERROR;
1800 /* Make space for the filenames/alias sources */
1801 *body = (char *)malloc(incLen+1);
1802 if (!*body)
1803 return TF_ERROR;
1804 *body[0]=0;
1805 if (fseek(fp, incPos, SEEK_SET) != 0) {
1806 free (*body);
1807 return TF_ERROR;
1809 /* Read all the lines in the block */
1810 /* fprintf(stderr, "Copying lines\n"); */
1811 for (i=0; i<incLines; i++) {
1812 status = fgets(line, MAXLINE, fp);
1813 if (!status) {
1814 free (*body);
1815 return TF_ERROR_EOF;
1817 rstrip(line,line);
1818 if(i)
1819 strcat(*body, ":");
1820 strcat(*body, line);
1822 /* fprintf(stderr, "Finished include/alias at line %i\n", *currLine); */
1825 else if( searchLine(line, language_regex) ) {
1826 /* LANGUAGE block */
1827 status=fgets(line, MAXLINE, fp);
1828 ++(*currLine);
1829 if (!status)
1830 return TF_ERROR_EOF;
1831 if (lineEmpty( line )) {
1832 fprintf( stderr, "nedit: Warning: empty '* language *' block in calltips file.\n" );
1833 return TF_ERROR;
1835 *blkLine = *currLine;
1836 rstrip(header, line);
1837 code = TF_LANGUAGE;
1840 else if( searchLine(line, version_regex) ) {
1841 /* VERSION block */
1842 status=fgets(line, MAXLINE, fp);
1843 ++(*currLine);
1844 if (!status)
1845 return TF_ERROR_EOF;
1846 if (lineEmpty( line )) {
1847 fprintf( stderr, "nedit: Warning: empty '* version *' block in calltips file.\n" );
1848 return TF_ERROR;
1850 *blkLine = *currLine;
1851 rstrip(header, line);
1852 code = TF_VERSION;
1855 else {
1856 /* Calltip block */
1857 /* The first line is the key, the rest is the tip.
1858 Strip trailing whitespace. */
1859 rstrip(header, line);
1861 status=fgets(line, MAXLINE, fp);
1862 ++(*currLine);
1863 if (!status)
1864 return TF_ERROR_EOF;
1865 if (lineEmpty( line )) {
1866 fprintf( stderr, "nedit: Warning: empty calltip block:\n"
1867 " \"%s\"\n", header);
1868 return TF_ERROR;
1870 *blkLine = *currLine;
1871 *body = strdup(line);
1872 code = TF_BLOCK;
1875 /* Skip the rest of the block */
1876 dummy1 = *currLine;
1877 while(fgets(line, MAXLINE, fp)) {
1878 ++(*currLine);
1879 if (lineEmpty( line ))
1880 break;
1883 /* Warn about any unneeded extra lines (which are ignored). */
1884 if (dummy1+1 < *currLine && code != TF_BLOCK) {
1885 fprintf( stderr, "nedit: Warning: extra lines in language or version block ignored.\n" );
1888 return code;
1891 /* A struct for describing a calltip alias */
1892 typedef struct _alias {
1893 char *dest;
1894 char *sources;
1895 struct _alias *next;
1896 } tf_alias;
1899 ** Allocate a new alias, copying dest and stealing sources. This may
1900 ** seem strange but that's the way it's called
1902 static tf_alias *new_alias(const char *dest, char *sources) {
1903 tf_alias *alias;
1905 /* fprintf(stderr, "new_alias: %s <- %s\n", dest, sources); */
1906 /* Allocate the alias */
1907 alias = (tf_alias *)malloc( sizeof(tf_alias) );
1908 if(!alias)
1909 return NULL;
1911 /* Fill it in */
1912 alias->dest = (char*)malloc( strlen(dest)+1 );
1913 if(!(alias->dest))
1914 return NULL;
1915 strcpy( alias->dest, dest );
1916 alias->sources = sources;
1917 return alias;
1920 /* Deallocate a linked-list of aliases */
1921 static void free_alias_list(tf_alias *alias) {
1922 tf_alias *tmp_alias;
1923 while(alias) {
1924 tmp_alias = alias->next;
1925 free(alias->dest);
1926 free(alias->sources);
1927 free(alias);
1928 alias = tmp_alias;
1933 ** Load a calltips file and insert all of the entries into the global tips
1934 ** database. Each tip is essentially stored as its filename and the line
1935 ** at which it appears--the exact same way ctags indexes source-code. That's
1936 ** why calltips and tags share so much code.
1938 static int loadTipsFile(const char *tipsFile, int index, int recLevel)
1940 FILE *fp = NULL;
1941 char header[MAXLINE];
1942 char *body, *tipIncFile;
1943 char tipPath[MAXPATHLEN];
1944 char resolvedTipsFile[MAXPATHLEN+1];
1945 int nTipsAdded=0, langMode = PLAIN_LANGUAGE_MODE, oldLangMode;
1946 int currLine=0, code, blkLine;
1947 tf_alias *aliases=NULL, *tmp_alias;
1949 if(recLevel > MAX_TAG_INCLUDE_RECURSION_LEVEL) {
1950 fprintf(stderr, "nedit: Warning: Reached recursion limit before loading calltips file:\n\t%s\n", tipsFile);
1951 return 0;
1954 /* find the tips file */
1955 #ifndef VMS
1956 /* Allow ~ in Unix filenames */
1957 strncpy(tipPath, tipsFile, MAXPATHLEN); /* ExpandTilde is destructive */
1958 ExpandTilde(tipPath);
1959 if(!ResolvePath(tipPath, resolvedTipsFile))
1960 return 0;
1961 #else
1962 if(!ResolvePath(tipsFile, resolvedTipsFile))
1963 return 0;
1964 #endif
1966 /* Get the path to the tips file */
1967 ParseFilename(resolvedTipsFile, NULL, tipPath);
1969 /* Open the file */
1970 if ((fp = fopen(resolvedTipsFile, "r")) == NULL)
1971 return 0;
1973 while( 1 ) {
1974 code = nextTFBlock(fp, header, &body, &blkLine, &currLine);
1975 if( code == TF_ERROR_EOF ) {
1976 fprintf(stderr,"nedit: Warning: unexpected EOF in calltips file.\n");
1977 break;
1979 if( code == TF_EOF )
1980 break;
1982 switch (code) {
1983 case TF_BLOCK:
1984 /* Add the calltip to the global hash table.
1985 For the moment I'm just using line numbers because I don't
1986 want to have to deal with adding escape characters for
1987 regex metacharacters that might appear in the string */
1988 nTipsAdded += addTag(header, resolvedTipsFile, langMode, "",
1989 blkLine, tipPath, index);
1990 free( body );
1991 break;
1992 case TF_INCLUDE:
1993 /* nextTFBlock returns a colon-separated list of tips files
1994 in body */
1995 for(tipIncFile=strtok(body,":"); tipIncFile;
1996 tipIncFile=strtok(NULL,":")) {
1997 /* fprintf(stderr,
1998 "nedit: DEBUG: including tips file '%s'\n",
1999 tipIncFile); */
2000 nTipsAdded += loadTipsFile( tipIncFile, index, recLevel+1);
2002 free( body );
2003 break;
2004 case TF_LANGUAGE:
2005 /* Switch to the new language mode if it's valid, else ignore
2006 it. */
2007 oldLangMode = langMode;
2008 langMode = FindLanguageMode( header );
2009 if (langMode == PLAIN_LANGUAGE_MODE &&
2010 strcmp(header, "Plain")) {
2011 fprintf(stderr,
2012 "nedit: Error reading calltips file:\n\t%s\n"
2013 "Unknown language mode: \"%s\"\n",
2014 tipsFile, header);
2015 langMode = oldLangMode;
2017 break;
2018 case TF_ERROR:
2019 fprintf(stderr,"nedit: Warning: Recoverable error while "
2020 "reading calltips file:\n \"%s\"\n",
2021 resolvedTipsFile);
2022 break;
2023 case TF_ALIAS:
2024 /* Allocate a new alias struct */
2025 tmp_alias = aliases;
2026 aliases = new_alias(header, body);
2027 if( !aliases ) {
2028 fprintf(stderr,"nedit: Can't allocate memory for tipfile "
2029 "alias in calltips file:\n \"%s\"\n",
2030 resolvedTipsFile);
2031 /* Deallocate any allocated aliases */
2032 free_alias_list(tmp_alias);
2033 return 0;
2035 /* Add it to the list */
2036 aliases->next = tmp_alias;
2037 break;
2038 default:
2039 ;/* Ignore TF_VERSION for now */
2043 /* Now resolve any aliases */
2044 tmp_alias = aliases;
2045 while (tmp_alias) {
2046 tag *t;
2047 char *src;
2048 t = getTag(tmp_alias->dest, TIP);
2049 if (!t) {
2050 fprintf(stderr, "nedit: Can't find destination of alias \"%s\"\n"
2051 " in calltips file:\n \"%s\"\n",
2052 tmp_alias->dest, resolvedTipsFile);
2053 } else {
2054 for(src=strtok(tmp_alias->sources,":"); src; src=strtok(NULL,":"))
2055 addTag(src, resolvedTipsFile, t->language, "", t->posInf,
2056 tipPath, index);
2058 tmp_alias = tmp_alias->next;
2060 free_alias_list(aliases);
2061 return nTipsAdded;