1 static const char CVSID
[] = "$Id: tags.c,v 1.59 2004/08/01 10:06:11 yooden Exp $";
2 /*******************************************************************************
4 * tags.c -- Nirvana editor tag file handling *
6 * Copyright (C) 1999 Mark Edel *
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. *
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 *
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 *
23 * Nirvana Text Editor *
26 * Written by Mark Edel *
28 *******************************************************************************/
31 #include "../config.h"
40 #include "preferences.h"
42 #include "selection.h"
44 #include "../util/DialogF.h"
45 #include "../util/fileUtils.h"
46 #include "../util/misc.h"
47 #include "../util/utils.h"
53 #include <sys/types.h>
57 #include "../util/VMSparam.h"
60 #include <sys/param.h>
64 #include <Xm/PrimitiveP.h> /* For Calltips */
66 #include <Xm/SelectioB.h>
67 #include <X11/Xatom.h>
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),""))
90 const char *searchString
; /* see comment below */
91 int posInf
; /* see comment below */
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
,
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
,
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
,
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
,
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
;
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
) {
164 return ShowCalltip( window
, text
, globAnchored
, globPos
, globHAlign
,
165 globVAlign
, globAlignMode
);
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
)
180 /* Compute hash address from a string key */
181 static unsigned hashAddr(const char *key
)
183 unsigned s
=strlen(key
);
186 for (i
=0; (i
+3)<s
; i
+= 4) {
187 strncpy((char*)&a
,&key
[i
],4);
191 for (a
=1; i
<(s
+1); i
++, a
*= 256)
197 /* Retrieve a tag structure from the hash table */
198 static tag
*getTag(const char *name
, int search_type
)
200 static char lastName
[MAXLINE
];
205 if (search_type
== TIP
)
210 if (table
== NULL
) return NULL
;
213 addr
= hashAddr(name
) % DefTagHashSize
;
215 strcpy(lastName
,name
);
223 for (;t
; t
= t
->next
)
224 if (!strcmp(name
,t
->name
)) return t
;
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
;
239 char newfile
[MAXPATHLEN
];
242 if (searchMode
== TIP
) {
244 Tips
= (tag
**)calloc(DefTagHashSize
, sizeof(tag
*));
248 Tags
= (tag
**)calloc(DefTagHashSize
, sizeof(tag
*));
253 strcpy(newfile
,file
);
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;
276 t
= (tag
*) malloc(sizeof(tag
));
277 setTag(t
, name
, file
, lang
, search
, posInf
, path
);
279 t
->next
= table
[addr
];
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
)
295 int start
,finish
,i
,del
=0;
298 if (searchMode
== TIP
)
303 if (table
== NULL
) return FALSE
;
305 start
= finish
= hashAddr(name
) % DefTagHashSize
;
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;
319 last
->next
= t
->next
;
324 rcs_free(t
->searchString
);
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
)
348 char pathName
[MAXPATHLEN
];
352 searchMode
= file_type
;
353 if (searchMode
== TAG
)
354 FileList
= TagsFileList
;
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
== '~')
363 if (windowPath
&& *windowPath
) {
364 strcpy(pathName
, windowPath
);
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
);
378 if (stat(pathName
, &statbuf
) != 0)
380 t
= (tagFile
*) malloc(sizeof(tagFile
));
381 t
->filename
= STRSAVE(pathName
);
383 t
->date
= statbuf
.st_mtime
;
384 t
->index
= ++tagFileIndex
;
386 FileList
= setFileListHead(t
, file_type
);
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
402 ** Nedit.tags: <tagfile1>:<tagfile2>
403 ** Returns True if all files were found in the FileList or loaded successfully,
406 int AddTagsFile(const char *tagSpec
, int file_type
)
412 char pathName
[MAXPATHLEN
];
416 /* To prevent any possible segfault */
417 if (tagSpec
== NULL
) {
418 fprintf(stderr
, "nedit: Internal Error!\n"
419 " Passed NULL pointer to AddTagsFile!\n");
423 searchMode
= file_type
;
424 if (searchMode
== TAG
)
425 FileList
= TagsFileList
;
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
);
437 strcpy(pathName
,filename
);
439 NormalizePathname(pathName
);
440 CompressPathname(pathName
);
442 for (t
= FileList
; t
&& strcmp(t
->filename
,pathName
); t
= t
->next
);
447 if (stat(pathName
,&statbuf
) != 0) {
448 /* Problem reading this tags file. Return FALSE */
452 t
= (tagFile
*) malloc(sizeof(tagFile
));
453 t
->filename
= STRSAVE(pathName
);
455 t
->date
= statbuf
.st_mtime
;
456 t
->index
= ++tagFileIndex
;
458 FileList
= setFileListHead(t
, file_type
);
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
)
476 char pathName
[MAXPATHLEN
], *tmptagSpec
, *filename
;
479 /* To prevent any possible segfault */
480 if (tagSpec
== NULL
) {
481 fprintf(stderr
, "nedit: Internal Error!\n"
482 " Passed NULL pointer to DeleteTagsFile!\n");
486 searchMode
= file_type
;
487 if (searchMode
== TAG
)
488 FileList
= TagsFileList
;
490 FileList
= TipsFileList
;
492 tmptagSpec
= (char *) malloc(strlen(tagSpec
)+1);
493 strcpy(tmptagSpec
, tagSpec
);
495 for (filename
= strtok(tmptagSpec
,":"); filename
;
496 filename
= strtok(NULL
,":")) {
497 if (*filename
!= '/') {
498 strcpy(pathName
, GetCurrentDir());
499 strcat(pathName
,"/");
500 strcat(pathName
,filename
);
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
))
511 delTag(NULL
,NULL
,-2,NULL
,-2,t
->index
);
512 if (last
) last
->next
= t
->next
;
513 else FileList
= setFileListHead(t
->next
, file_type
);
519 /* If any file can't be removed, 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()
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
))
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
;
562 nRead
= sscanf(line
, "%s\t%s\t%[^\n]", name
, file
, searchString
);
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
;
588 /* looks like exuberant ctags format 2 */
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)) {
600 if(searchString
[0] == *posTagREEnd
)
604 pos
=atoi(searchString
);
607 /* No ability to read language mode right now */
608 return addTag(name
, file
, PLAIN_LANGUAGE_MODE
, searchString
, pos
, tagPath
,
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
];
624 char *posDEL
, *posSOH
, *posCOM
;
626 /* check for destination file separator */
627 if(line
[0]==12) { /* <np> */
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
);
641 len
=Min(MAXLINE
-1, (posSOH
- posDEL
) - 1);
642 strncpy(name
, posDEL
+ 1, len
);
645 /* No ability to set language mode for the moment */
646 return addTag(name
, file
, PLAIN_LANGUAGE_MODE
, searchString
, pos
,
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
);
654 /* guess name: take the last alnum (plus _) part of searchString */
656 if( isalnum((unsigned char)searchString
[len
]) ||
657 (searchString
[len
] == '_'))
663 while (pos
>= 0 && (isalnum((unsigned char)searchString
[pos
]) ||
664 (searchString
[pos
] == '_')))
666 strncpy(name
, searchString
+ pos
+ 1, len
- pos
);
667 name
[len
- pos
] = 0; /* name ready */
669 return addTag(name
, file
, PLAIN_LANGUAGE_MODE
, searchString
, pos
,
672 /* check for destination file spec */
673 if(*line
&& posCOM
) {
674 len
=Min(MAXPATHLEN
-1, posCOM
- line
);
675 strncpy(file
, line
, len
);
677 /* check if that's an include file ... */
678 if(!(strncmp(posCOM
+1, "include", 7))) {
680 if((strlen(tagPath
) + strlen(file
)) >= MAXPATHLEN
) {
681 fprintf(stderr
, "tags.c: MAXPATHLEN overflow\n");
682 *file
=0; /* invalidate */
685 strcpy(incPath
, tagPath
);
686 strcat(incPath
, file
);
687 CompressPathname(incPath
);
688 return(loadTagsFile(incPath
, index
, recLevel
+1));
690 return(loadTagsFile(file
, index
, recLevel
+1));
699 TFT_CHECK
, TFT_ETAGS
, TFT_CTAGS
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
)
710 char file
[MAXPATHLEN
], tagPath
[MAXPATHLEN
];
711 char resolvedTagsFile
[MAXPATHLEN
+1];
713 int tagFileType
= TFT_CHECK
;
715 if(recLevel
> MAX_TAG_INCLUDE_RECURSION_LEVEL
) {
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
)) {
727 if ((fp
= fopen(resolvedTagsFile
, "r")) == NULL
) {
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
745 if(tagFileType
==TFT_CHECK
) {
746 if(line
[0]==12) /* <np> */
747 tagFileType
=TFT_ETAGS
;
749 tagFileType
=TFT_CTAGS
;
751 if(tagFileType
==TFT_CTAGS
) {
752 nTagsAdded
+= scanCTagsLine(line
, tagPath
, index
);
754 nTagsAdded
+= scanETagsLine(line
, tagPath
, index
, file
, recLevel
);
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
,
787 searchMode
= search_type
;
788 if (searchMode
== TIP
)
789 FileList
= TipsFileList
;
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
) {
805 if (stat(tf
->filename
,&statbuf
) != 0) { /* */
806 fprintf(stderr
, TAG_STS_ERR_FMT
, tf
->filename
);
808 if (tf
->date
== statbuf
.st_mtime
) {
809 /* current tags file tf is already loaded and up to date */
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);
820 load_status
= loadTagsFile(tf
->filename
, tf
->index
, 0);
822 if (stat(tf
->filename
,&statbuf
) != 0) {
824 /* if tf->loaded == 1 we already have seen the error msg */
825 fprintf(stderr
, TAG_STS_ERR_FMT
, tf
->filename
);
828 tf
->date
= statbuf
.st_mtime
;
836 t
= getTag(name
, search_type
);
842 *language
= t
->language
;
843 *searchString
= t
->searchString
;
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];
857 char message
[MAX_TAG_LEN
+40];
858 int l
, ml
, status
= 0;
860 searchMode
= search_type
;
862 if (l
<= MAX_TAG_LEN
) {
863 /* should be of type text??? */
864 for (p
= value
; *p
&& isascii(*p
); p
++) {
867 ml
= ((l
< MAX_TAG_LEN
) ? (l
) : (MAX_TAG_LEN
));
868 strncpy(tagText
, value
, ml
);
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
);
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.",
882 tagsShowCalltip( window
, message
);
885 DialogF(DF_WARN
, window
->textArea
, 1, "Tags",
886 "\"%s\" not found in tags file%s", "OK", tagName
,
887 (TagsFileList
&& TagsFileList
->next
) ? "s" : "");
892 fprintf(stderr
, "NEdit: Can't handle non 8-bit text\n");
893 XBell(TheDisplay
, 0);
897 fprintf(stderr
, "NEdit: Tag Length too long.\n");
898 XBell(TheDisplay
, 0);
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
908 void findDefinitionHelper(WindowInfo
*window
, Time time
, const char *arg
,
913 findDef(window
, arg
, search_type
);
917 searchMode
= search_type
;
918 XtGetSelectionValue(window
->textArea
, XA_PRIMARY
, XA_STRING
,
919 (XtSelectionCallbackProc
)findDefCB
, window
, time
);
926 void FindDefinition(WindowInfo
*window
, Time time
, const char *arg
)
928 findDefinitionHelper(window
, time
, arg
, TAG
);
934 void FindDefCalltip(WindowInfo
*window
, Time time
, const char *arg
)
936 /* Reset calltip parameters to reasonable defaults */
937 globAnchored
= False
;
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);
954 findDef(window
, value
, searchMode
);
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
,
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
;
977 globAlignMode
= alignMode
;
979 /* If this isn't a lookup request, just display it. */
981 return tagsShowCalltip(window
, text
);
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
,
991 t
->name
= rcs_strdup(name
);
992 t
->file
= rcs_strdup(file
);
993 t
->language
= language
;
994 t
->searchString
= rcs_strdup(searchString
);
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
;
1016 if (in_buffer
== NULL
) {
1017 /* get the entire (sigh) text buffer from the text area widget */
1018 fileString
= BufGetAll(window
->buffer
);
1020 fileString
= in_buffer
;
1023 /* determine search direction and start position */
1024 if (*startPos
!= -1) { /* etags mode! */
1025 dir
= SEARCH_FORWARD
;
1026 searchStartPos
= *startPos
;
1028 } else if (searchString
[0] == '/') {
1029 dir
= SEARCH_FORWARD
;
1032 } else if (searchString
[0] == '?') {
1033 dir
= SEARCH_BACKWARD
;
1034 /* searchStartPos = window->buffer->length; */
1035 searchStartPos
= strlen(fileString
);
1038 fprintf(stderr
, "NEdit: Error parsing tag file search string");
1039 if(in_buffer
== NULL
)
1044 /* Build the search regex. */
1047 inPtr
=searchString
+1; /* searchString[0] is / or ? --> search dir */
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 */
1056 if( (*inPtr
=='\\' && inPtr
[1]=='/') ||
1057 (*inPtr
=='\r' && inPtr
[1]=='$' && !inPtr
[2])
1060 - escapes (added by standard and exuberant ctags) from slashes
1061 - literal CRs generated by standard ctags for DOSified sources
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.
1071 *outPtr
++ = *inPtr
++;
1072 } else if (isspace((unsigned char)*inPtr
)) { /* col. multiple spaces */
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
,
1096 /* free the text buffer copy returned from XmTextGetString */
1097 if(in_buffer
== NULL
)
1100 /* return the result */
1102 /* *startPos and *endPos are set in SearchString*/
1105 /* startPos, endPos left untouched by SearchString if search failed. */
1106 XBell(TheDisplay
, 0);
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
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
;
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);
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
) {
1142 if (*fileToSearch
== '/')
1143 strcpy(tagFiles
[nMatches
], fileToSearch
);
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
) ) {
1153 strcpy(tagFiles
[0],tagFiles
[nMatches
]);
1154 strcpy(tagSearch
[0],tagSearch
[nMatches
]);
1155 tagPosInf
[0]=tagPosInf
[nMatches
];
1160 /* Is this match in the same dir. as the current file? */
1161 if (!strcmp(window
->path
,pathname
)) {
1165 if (++nMatches
>= MAXDUPTAGS
) {
1166 DialogF(DF_WARN
, dialogParent
, 1, "Tags",
1167 "Too many duplicate tags, first %d shown", "OK", MAXDUPTAGS
);
1170 /* Tell LookupTag to look for more definitions of the same tag: */
1174 /* Did we find any matches? */
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
];
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]))
1198 if (!(dupTagsList
= (char **) malloc(sizeof(char *) * nMatches
))) {
1199 fprintf(stderr
, "NEdit: findDef(): out of heap space!\n");
1200 XBell(TheDisplay
, 0);
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
,
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);
1224 strcpy(dupTagsList
[i
],temp
);
1226 createSelectMenu(dialogParent
, "Duplicate Tags", nMatches
, dupTagsList
);
1227 for (i
=0; i
<nMatches
; i
++)
1228 free(dupTagsList
[i
]);
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 );
1239 showMatchingCalltip( dialogParent
, 0 );
1243 /* Callback function for the FindAll widget. Process the users response. */
1244 static void findAllCB(Widget parent
, XtPointer client_data
, XtPointer call_data
)
1249 XmSelectionBoxCallbackStruct
*cbs
=
1250 (XmSelectionBoxCallbackStruct
*) call_data
;
1251 if (cbs
->reason
== XmCR_NO_MATCH
)
1253 if (cbs
->reason
== XmCR_CANCEL
) {
1254 XtDestroyWidget(XtParent(parent
));
1258 XmStringGetLtoR(cbs
->value
,XmFONTLIST_DEFAULT_TAG
,&eptr
);
1259 if ((i
= atoi(eptr
)-1) < 0) {
1260 XBell(TheDisplay
, 0);
1264 if (searchMode
== TAG
)
1265 editTaggedLocation( parent
, i
); /* Open the file with the definition */
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
) {
1288 while (str
[*pos
] != '\0' && n
>0) {
1289 if (str
[*pos
] == '\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
;
1310 struct stat statbuf
;
1313 /* 1. Open the target file */
1314 NormalizePathname(tagFiles
[i
]);
1315 fp
= fopen(tagFiles
[i
], "r");
1317 DialogF(DF_ERR
, parent
, 1, "Error opening File", "Error opening %s",
1321 if (fstat(fileno(fp
), &statbuf
) != 0) {
1323 DialogF(DF_ERR
, parent
, 1, "Error opening File", "Error opening %s",
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
) {
1334 DialogF(DF_ERR
, parent
, 1, "File too large",
1335 "File is too large to load", "OK");
1339 /* Read the file into fileString and terminate with a null */
1340 readLen
= fread(fileString
, sizeof(char), fileLen
, fp
);
1343 DialogF(DF_ERR
, parent
, 1, "Error reading File", "Error reading %s",
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
]);
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
,
1380 if (searchMode
== TIP
) {
1383 /* 4. Find the end of the calltip (delimited by an empty line) */
1385 found
= SearchString(fileString
, "\\n\\s*\\n", SEARCH_FORWARD
,
1386 SEARCH_REGEX
, False
, startPos
, &endPos
, &dummy
, NULL
,
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 */
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
], ". . ." );
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");
1413 strncpy( message
, &fileString
[startPos
], tipLen
);
1414 message
[tipLen
] = 0;
1417 tagsShowCalltip( WidgetToWindow(parent
), message
);
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,
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",
1444 startPos
=tagPosInf
[i
];
1446 if(!*(tagSearch
[i
])) {
1447 /* if the search string is empty, select the numbered line */
1448 SelectNumberedLine(windowToSearch
, startPos
);
1452 /* search for the tags file search string in the newly opened file */
1453 if(!fakeRegExSearch(windowToSearch
, NULL
, tagSearch
[i
], &startPos
,
1455 DialogF(DF_WARN
, windowToSearch
->shell
, 1, "Tag Error",
1456 "Definition for %s\nnot found in %s", "OK", tagName
,
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
,
1480 XmString popupTitle
;
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
);
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
);
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
1529 int talloc
, tshar
, tgiveup
, tbytes
, tbyteshared
;
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
)
1554 struct rcs
*prev
= NULL
;
1556 char *newstr
= NULL
;
1561 bucket
= hashAddr(str
) % RCS_SIZE
;
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) */
1580 /* Find it in hash */
1581 for (rp
= Rcs
[bucket
]; rp
; rp
= rp
->next
)
1583 if (!strcmp(str
, rp
->string
))
1588 if (rp
) /* It exists, return it and bump ref ct */
1591 newstr
= rp
->string
;
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
);
1602 newrcs
->next
= NULL
;
1605 prev
->next
= newrcs
;
1607 Rcs
[bucket
] = newrcs
;
1609 newstr
= newrcs
->string
;
1612 RcsStats
.tbytes
+= len
;
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
)
1625 struct rcs
*prev
= NULL
;
1627 if (rcs_str
== NULL
)
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
)
1640 if (rp
) /* It's a shared string, decrease ref count */
1644 if (rp
->usage
< 0) /* D'OH! */
1646 fprintf(stderr
, "NEdit: internal error deallocating shared string.");
1650 if (rp
->usage
== 0) /* Last one- free the storage */
1654 prev
->next
= rp
->next
;
1656 Rcs
[bucket
] = rp
->next
;
1660 else /* Doesn't appear to be a shared string */
1662 fprintf(stderr
, "NEdit: attempt to free a non-shared string.");
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
) {
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')
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
)) {
1698 memcpy(dst
, src
, wsStart
);
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
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
,
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
;
1730 /* Skip blank lines and comments */
1732 /* Skip blank lines */
1733 while((status
=fgets(line
, MAXLINE
, fp
))) {
1735 if(!lineEmpty( line
))
1739 /* Check for error or EOF */
1743 /* We've got a non-blank line -- is it a comment block? */
1744 if( !searchLine(line
, commenTF_regex
) )
1747 /* Skip the comment (non-blank lines) */
1748 while((status
=fgets(line
, MAXLINE
, fp
))) {
1750 if(lineEmpty( line
))
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); */
1769 /* Need to read the header line for an alias */
1770 status
=fgets(line
, MAXLINE
, fp
);
1773 return TF_ERROR_EOF
;
1774 if (lineEmpty( line
)) {
1775 fprintf( stderr
, "nedit: Warning: empty '* alias *' "
1776 "block in calltips file.\n" );
1779 rstrip(header
, line
);
1782 *blkLine
= *currLine
+ 1; /* Line of first actual filename/alias */
1785 /* Figure out how long the block is */
1786 while((status
=fgets(line
, MAXLINE
, fp
))) {
1788 if(lineEmpty( line
))
1791 incLen
= ftell(fp
) - incPos
;
1792 incLines
= *currLine
- *blkLine
;
1793 /* Correct currLine for the empty line it read at the end */
1795 if (incLines
== 0) {
1796 fprintf( stderr
, "nedit: Warning: empty '* include *' or"
1797 " '* alias *' block in calltips file.\n" );
1800 /* Make space for the filenames/alias sources */
1801 *body
= (char *)malloc(incLen
+1);
1805 if (fseek(fp
, incPos
, SEEK_SET
) != 0) {
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
);
1815 return TF_ERROR_EOF
;
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
);
1830 return TF_ERROR_EOF
;
1831 if (lineEmpty( line
)) {
1832 fprintf( stderr
, "nedit: Warning: empty '* language *' block in calltips file.\n" );
1835 *blkLine
= *currLine
;
1836 rstrip(header
, line
);
1840 else if( searchLine(line
, version_regex
) ) {
1842 status
=fgets(line
, MAXLINE
, fp
);
1845 return TF_ERROR_EOF
;
1846 if (lineEmpty( line
)) {
1847 fprintf( stderr
, "nedit: Warning: empty '* version *' block in calltips file.\n" );
1850 *blkLine
= *currLine
;
1851 rstrip(header
, line
);
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
);
1864 return TF_ERROR_EOF
;
1865 if (lineEmpty( line
)) {
1866 fprintf( stderr
, "nedit: Warning: empty calltip block:\n"
1867 " \"%s\"\n", header
);
1870 *blkLine
= *currLine
;
1871 *body
= strdup(line
);
1875 /* Skip the rest of the block */
1877 while(fgets(line
, MAXLINE
, fp
)) {
1879 if (lineEmpty( line
))
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" );
1891 /* A struct for describing a calltip alias */
1892 typedef struct _alias
{
1895 struct _alias
*next
;
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
) {
1905 /* fprintf(stderr, "new_alias: %s <- %s\n", dest, sources); */
1906 /* Allocate the alias */
1907 alias
= (tf_alias
*)malloc( sizeof(tf_alias
) );
1912 alias
->dest
= (char*)malloc( strlen(dest
)+1 );
1915 strcpy( alias
->dest
, dest
);
1916 alias
->sources
= sources
;
1920 /* Deallocate a linked-list of aliases */
1921 static void free_alias_list(tf_alias
*alias
) {
1922 tf_alias
*tmp_alias
;
1924 tmp_alias
= alias
->next
;
1926 free(alias
->sources
);
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
)
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
);
1954 /* find the tips file */
1956 /* Allow ~ in Unix filenames */
1957 strncpy(tipPath
, tipsFile
, MAXPATHLEN
); /* ExpandTilde is destructive */
1958 ExpandTilde(tipPath
);
1959 if(!ResolvePath(tipPath
, resolvedTipsFile
))
1962 if(!ResolvePath(tipsFile
, resolvedTipsFile
))
1966 /* Get the path to the tips file */
1967 ParseFilename(resolvedTipsFile
, NULL
, tipPath
);
1970 if ((fp
= fopen(resolvedTipsFile
, "r")) == NULL
)
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");
1979 if( code
== TF_EOF
)
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
);
1993 /* nextTFBlock returns a colon-separated list of tips files
1995 for(tipIncFile
=strtok(body
,":"); tipIncFile
;
1996 tipIncFile
=strtok(NULL
,":")) {
1998 "nedit: DEBUG: including tips file '%s'\n",
2000 nTipsAdded
+= loadTipsFile( tipIncFile
, index
, recLevel
+1);
2005 /* Switch to the new language mode if it's valid, else ignore
2007 oldLangMode
= langMode
;
2008 langMode
= FindLanguageMode( header
);
2009 if (langMode
== PLAIN_LANGUAGE_MODE
&&
2010 strcmp(header
, "Plain")) {
2012 "nedit: Error reading calltips file:\n\t%s\n"
2013 "Unknown language mode: \"%s\"\n",
2015 langMode
= oldLangMode
;
2019 fprintf(stderr
,"nedit: Warning: Recoverable error while "
2020 "reading calltips file:\n \"%s\"\n",
2024 /* Allocate a new alias struct */
2025 tmp_alias
= aliases
;
2026 aliases
= new_alias(header
, body
);
2028 fprintf(stderr
,"nedit: Can't allocate memory for tipfile "
2029 "alias in calltips file:\n \"%s\"\n",
2031 /* Deallocate any allocated aliases */
2032 free_alias_list(tmp_alias
);
2035 /* Add it to the list */
2036 aliases
->next
= tmp_alias
;
2039 ;/* Ignore TF_VERSION for now */
2043 /* Now resolve any aliases */
2044 tmp_alias
= aliases
;
2048 t
= getTag(tmp_alias
->dest
, TIP
);
2050 fprintf(stderr
, "nedit: Can't find destination of alias \"%s\"\n"
2051 " in calltips file:\n \"%s\"\n",
2052 tmp_alias
->dest
, resolvedTipsFile
);
2054 for(src
=strtok(tmp_alias
->sources
,":"); src
; src
=strtok(NULL
,":"))
2055 addTag(src
, resolvedTipsFile
, t
->language
, "", t
->posInf
,
2058 tmp_alias
= tmp_alias
->next
;
2060 free_alias_list(aliases
);