1 static const char CVSID
[] = "$Id: tags.c,v 1.70 2008/03/01 22:18:06 lebert 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 versions 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(void);
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
);
259 for (t
= table
[addr
]; t
; t
= t
->next
) {
260 if (strcmp(name
,t
->name
)) continue;
261 if (lang
!= t
->language
) continue;
262 if (strcmp(search
,t
->searchString
)) continue;
263 if (posInf
!= t
->posInf
) continue;
264 if (*t
->file
== '/' && strcmp(newfile
,t
->file
)) continue;
265 if (*t
->file
!= '/') {
266 char tmpfile
[MAXPATHLEN
];
267 sprintf(tmpfile
, "%s%s", t
->path
, t
->file
);
268 NormalizePathname(tmpfile
);
269 if (strcmp(newfile
, tmpfile
)) continue;
274 t
= (tag
*) malloc(sizeof(tag
));
275 setTag(t
, name
, file
, lang
, search
, posInf
, path
);
277 t
->next
= table
[addr
];
282 /* Delete a tag from the cache.
283 * Search is limited to valid matches of 'name','file', 'search', posInf, and 'index'.
284 * EX: delete all tags matching index 2 ==>
285 * delTag(tagname,NULL,-2,NULL,-2,2);
286 * (posInf = -2 is an invalid match, posInf range: -1 .. +MAXINT,
287 lang = -2 is also an invalid match)
289 static int delTag(const char *name
, const char *file
, int lang
,
290 const char *search
, int posInf
, int index
)
293 int start
,finish
,i
,del
=0;
296 if (searchMode
== TIP
)
301 if (table
== NULL
) return FALSE
;
303 start
= finish
= hashAddr(name
) % DefTagHashSize
;
306 finish
= DefTagHashSize
;
308 for (i
= start
; i
<finish
; i
++) {
309 for (last
= NULL
, t
= table
[i
]; t
; last
= t
, t
= t
?t
->next
:table
[i
]) {
310 if (name
&& strcmp(name
,t
->name
)) continue;
311 if (index
&& index
!= t
->index
) continue;
312 if (file
&& strcmp(file
,t
->file
)) continue;
313 if (lang
>= PLAIN_LANGUAGE_MODE
&& lang
!= t
->language
) continue;
314 if (search
&& strcmp(search
,t
->searchString
)) continue;
315 if (posInf
== t
->posInf
) continue;
317 last
->next
= t
->next
;
322 rcs_free(t
->searchString
);
332 /* used in AddRelTagsFile and AddTagsFile */
333 static int tagFileIndex
= 0;
336 ** AddRelTagsFile(): Rescan tagSpec for relative tag file specs
337 ** (not starting with [/~]) and extend the tag files list if in
338 ** windowPath a tags file matching the relative spec has been found.
340 int AddRelTagsFile(const char *tagSpec
, const char *windowPath
, int file_type
)
346 char pathName
[MAXPATHLEN
];
350 searchMode
= file_type
;
351 if (searchMode
== TAG
)
352 FileList
= TagsFileList
;
354 FileList
= TipsFileList
;
356 tmptagSpec
= (char *) malloc(strlen(tagSpec
)+1);
357 strcpy(tmptagSpec
, tagSpec
);
358 for (filename
= strtok(tmptagSpec
, ":"); filename
; filename
= strtok(NULL
, ":")){
359 if (*filename
== '/' || *filename
== '~')
361 if (windowPath
&& *windowPath
) {
362 strcpy(pathName
, windowPath
);
364 strcpy(pathName
, GetCurrentDir());
366 strcat(pathName
, "/");
367 strcat(pathName
, filename
);
368 NormalizePathname(pathName
);
370 for (t
= FileList
; t
&& strcmp(t
->filename
, pathName
); t
= t
->next
);
375 if (stat(pathName
, &statbuf
) != 0)
377 t
= (tagFile
*) malloc(sizeof(tagFile
));
378 t
->filename
= STRSAVE(pathName
);
380 t
->date
= statbuf
.st_mtime
;
381 t
->index
= ++tagFileIndex
;
383 FileList
= setFileListHead(t
, file_type
);
395 ** AddTagsFile(): Set up the the list of tag files to manage from a file spec.
396 ** The file spec comes from the X-Resource Nedit.tags: It can list multiple
397 ** tags files, specified by separating them with colons. The .Xdefaults would
399 ** Nedit.tags: <tagfile1>:<tagfile2>
400 ** Returns True if all files were found in the FileList or loaded successfully,
403 int AddTagsFile(const char *tagSpec
, int file_type
)
409 char pathName
[MAXPATHLEN
];
413 /* To prevent any possible segfault */
414 if (tagSpec
== NULL
) {
415 fprintf(stderr
, "nedit: Internal Error!\n"
416 " Passed NULL pointer to AddTagsFile!\n");
420 searchMode
= file_type
;
421 if (searchMode
== TAG
)
422 FileList
= TagsFileList
;
424 FileList
= TipsFileList
;
426 tmptagSpec
= (char *) malloc(strlen(tagSpec
)+1);
427 strcpy(tmptagSpec
, tagSpec
);
428 for (filename
= strtok(tmptagSpec
,":"); filename
; filename
= strtok(NULL
,":")) {
429 if (*filename
!= '/') {
430 strcpy(pathName
, GetCurrentDir());
431 strcat(pathName
,"/");
432 strcat(pathName
,filename
);
434 strcpy(pathName
,filename
);
436 NormalizePathname(pathName
);
438 for (t
= FileList
; t
&& strcmp(t
->filename
,pathName
); t
= t
->next
);
440 /* This file is already in the list. It's easiest to just
441 refcount all tag/tip files even though we only actually care
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
;
459 FileList
= setFileListHead(t
, file_type
);
469 /* Un-manage a colon-delimited set of tags files
470 * Return TRUE if all files were found in the FileList and unloaded, FALSE
471 * if any file was not found in the FileList.
472 * "file_type" is either TAG or TIP
473 * If "force_unload" is true, a calltips file will be deleted even if its
474 * refcount is nonzero.
476 int DeleteTagsFile(const char *tagSpec
, int file_type
, Boolean force_unload
)
480 char pathName
[MAXPATHLEN
], *tmptagSpec
, *filename
;
483 /* To prevent any possible segfault */
484 if (tagSpec
== NULL
) {
485 fprintf(stderr
, "nedit: Internal Error: Passed NULL pointer to DeleteTagsFile!\n");
489 searchMode
= file_type
;
490 if (searchMode
== TAG
)
491 FileList
= TagsFileList
;
493 FileList
= TipsFileList
;
495 tmptagSpec
= (char *) malloc(strlen(tagSpec
)+1);
496 strcpy(tmptagSpec
, tagSpec
);
498 for (filename
= strtok(tmptagSpec
,":"); filename
;
499 filename
= strtok(NULL
,":")) {
500 if (*filename
!= '/') {
501 strcpy(pathName
, GetCurrentDir());
502 strcat(pathName
,"/");
503 strcat(pathName
,filename
);
505 strcpy(pathName
,filename
);
507 NormalizePathname(pathName
);
509 for (last
=NULL
,t
= FileList
; t
; last
= t
,t
= t
->next
) {
510 if (strcmp(t
->filename
, pathName
))
512 /* Don't unload tips files with nonzero refcounts unless forced */
513 if (searchMode
== TIP
&& !force_unload
&& --t
->refcount
> 0) {
517 delTag(NULL
,NULL
,-2,NULL
,-2,t
->index
);
518 if (last
) last
->next
= t
->next
;
519 else FileList
= setFileListHead(t
->next
, file_type
);
525 /* If any file can't be removed, return false */
536 ** Update the "Find Definition", "Unload Tags File", "Show Calltip",
537 ** and "Unload Calltips File" menu items in the existing windows.
539 static void updateMenuItems(void)
542 Boolean tipStat
=FALSE
, tagStat
=FALSE
;
544 if (TipsFileList
) tipStat
=TRUE
;
545 if (TagsFileList
) tagStat
=TRUE
;
547 for (w
=WindowList
; w
!=NULL
; w
=w
->next
) {
548 if (!IsTopDocument(w
))
550 XtSetSensitive(w
->showTipItem
, tipStat
|| tagStat
);
551 XtSetSensitive(w
->unloadTipsMenuItem
, tipStat
);
552 XtSetSensitive(w
->findDefItem
, tagStat
);
553 XtSetSensitive(w
->unloadTagsMenuItem
, tagStat
);
558 ** Scans one <line> from a ctags tags file (<index>) in tagPath.
559 ** Return value: Number of tag specs added.
561 static int scanCTagsLine(const char *line
, const char *tagPath
, int index
)
563 char name
[MAXLINE
], searchString
[MAXLINE
];
564 char file
[MAXPATHLEN
];
565 char *posTagREEnd
, *posTagRENull
;
568 nRead
= sscanf(line
, "%s\t%s\t%[^\n]", name
, file
, searchString
);
575 ** Guess the end of searchString:
576 ** Try to handle original ctags and exuberant ctags format:
578 if(searchString
[0] == '/' || searchString
[0] == '?') {
580 pos
=-1; /* "search expr without pos info" */
582 /* Situations: /<ANY expr>/\0
583 ** ?<ANY expr>?\0 --> original ctags
584 ** /<ANY expr>/;" <flags>
585 ** ?<ANY expr>?;" <flags> --> exuberant ctags
587 posTagREEnd
= strrchr(searchString
, ';');
588 posTagRENull
= strchr(searchString
, 0);
589 if(!posTagREEnd
|| (posTagREEnd
[1] != '"') ||
590 (posTagRENull
[-1] == searchString
[0])) {
591 /* -> original ctags format = exuberant ctags format 1 */
592 posTagREEnd
= posTagRENull
;
594 /* looks like exuberant ctags format 2 */
599 ** Hide the last delimiter:
600 ** /<expression>/ becomes /<expression>
601 ** ?<expression>? becomes ?<expression>
602 ** This will save a little work in fakeRegExSearch.
604 if(posTagREEnd
> (searchString
+2)) {
606 if(searchString
[0] == *posTagREEnd
)
610 pos
=atoi(searchString
);
613 /* No ability to read language mode right now */
614 return addTag(name
, file
, PLAIN_LANGUAGE_MODE
, searchString
, pos
, tagPath
,
619 * Scans one <line> from an etags (emacs) tags file (<index>) in tagPath.
620 * recLevel = current recursion level for tags file including
621 * file = destination definition file. possibly modified. len=MAXPATHLEN!
622 * Return value: Number of tag specs added.
624 static int scanETagsLine(const char *line
, const char * tagPath
, int index
,
625 char * file
, int recLevel
)
627 char name
[MAXLINE
], searchString
[MAXLINE
];
628 char incPath
[MAXPATHLEN
];
630 char *posDEL
, *posSOH
, *posCOM
;
632 /* check for destination file separator */
633 if(line
[0]==12) { /* <np> */
638 /* check for standard definition line */
639 posDEL
=strchr(line
, '\177');
640 posSOH
=strchr(line
, '\001');
641 posCOM
=strrchr(line
, ',');
642 if(*file
&& posDEL
&& (posSOH
> posDEL
) && (posCOM
> posSOH
)) {
643 /* exuberant ctags -e style */
644 len
=Min(MAXLINE
-1, posDEL
- line
);
645 strncpy(searchString
, line
, len
);
647 len
=Min(MAXLINE
-1, (posSOH
- posDEL
) - 1);
648 strncpy(name
, posDEL
+ 1, len
);
651 /* No ability to set language mode for the moment */
652 return addTag(name
, file
, PLAIN_LANGUAGE_MODE
, searchString
, pos
,
655 if (*file
&& posDEL
&& (posCOM
> posDEL
)) {
656 /* old etags style, part name<soh> is missing here! */
657 len
=Min(MAXLINE
-1, posDEL
- line
);
658 strncpy(searchString
, line
, len
);
660 /* guess name: take the last alnum (plus _) part of searchString */
662 if( isalnum((unsigned char)searchString
[len
]) ||
663 (searchString
[len
] == '_'))
669 while (pos
>= 0 && (isalnum((unsigned char)searchString
[pos
]) ||
670 (searchString
[pos
] == '_')))
672 strncpy(name
, searchString
+ pos
+ 1, len
- pos
);
673 name
[len
- pos
] = 0; /* name ready */
675 return addTag(name
, file
, PLAIN_LANGUAGE_MODE
, searchString
, pos
,
678 /* check for destination file spec */
679 if(*line
&& posCOM
) {
680 len
=Min(MAXPATHLEN
-1, posCOM
- line
);
681 strncpy(file
, line
, len
);
683 /* check if that's an include file ... */
684 if(!(strncmp(posCOM
+1, "include", 7))) {
686 if((strlen(tagPath
) + strlen(file
)) >= MAXPATHLEN
) {
687 fprintf(stderr
, "tags.c: MAXPATHLEN overflow\n");
688 *file
=0; /* invalidate */
691 strcpy(incPath
, tagPath
);
692 strcat(incPath
, file
);
693 CompressPathname(incPath
);
694 return(loadTagsFile(incPath
, index
, recLevel
+1));
696 return(loadTagsFile(file
, index
, recLevel
+1));
705 TFT_CHECK
, TFT_ETAGS
, TFT_CTAGS
709 ** Loads tagsFile into the hash table.
710 ** Returns the number of added tag specifications.
712 static int loadTagsFile(const char *tagsFile
, int index
, int recLevel
)
716 char file
[MAXPATHLEN
], tagPath
[MAXPATHLEN
];
717 char resolvedTagsFile
[MAXPATHLEN
+1];
719 int tagFileType
= TFT_CHECK
;
721 if(recLevel
> MAX_TAG_INCLUDE_RECURSION_LEVEL
) {
724 /* the path of the tags file must be resolved to find the right files:
725 * definition source files are (in most cases) specified relatively inside
726 * the tags file to the tags files directory.
728 if(!ResolvePath(tagsFile
, resolvedTagsFile
)) {
733 if ((fp
= fopen(resolvedTagsFile
, "r")) == NULL
) {
737 ParseFilename(resolvedTagsFile
, NULL
, tagPath
);
739 /* Read the file and store its contents */
740 while (fgets(line
, MAXLINE
, fp
)) {
742 /* This might take a while if you have a huge tags file (like I do)..
743 keep the windows up to date and post a busy cursor so the user
744 doesn't think we died. */
746 AllWindowsBusy("Loading tags file...");
748 /* the first character in the file decides if the file is treat as
751 if(tagFileType
==TFT_CHECK
) {
752 if(line
[0]==12) /* <np> */
753 tagFileType
=TFT_ETAGS
;
755 tagFileType
=TFT_CTAGS
;
757 if(tagFileType
==TFT_CTAGS
) {
758 nTagsAdded
+= scanCTagsLine(line
, tagPath
, index
);
760 nTagsAdded
+= scanETagsLine(line
, tagPath
, index
, file
, recLevel
);
770 ** Given a tag name, lookup the file and path of the definition
771 ** and the proper search string. Returned strings are pointers
772 ** to internal storage which are valid until the next loadTagsFile call.
774 ** Invocation with name != NULL (containing the searched definition)
775 ** --> returns first definition of name
776 ** Successive invocation with name == NULL
777 ** --> returns further definitions (resulting from multiple tags files)
779 ** Return Value: TRUE: tag spec found
780 ** FALSE: no (more) definitions found.
782 #define TAG_STS_ERR_FMT "NEdit: Error getting status for tag file %s\n"
783 int LookupTag(const char *name
, const char **file
, int *language
,
784 const char **searchString
, int * pos
, const char **path
,
793 searchMode
= search_type
;
794 if (searchMode
== TIP
)
795 FileList
= TipsFileList
;
797 FileList
= TagsFileList
;
800 ** Go through the list of all tags Files:
801 ** - load them (if not already loaded)
802 ** - check for update of the tags file and reload it in that case
803 ** - save the modification date of the tags file
805 ** Do this only as long as name != NULL, not for sucessive calls
806 ** to find multiple tags specs.
809 for (tf
= FileList
; tf
&& name
; tf
= tf
->next
) {
811 if (stat(tf
->filename
,&statbuf
) != 0) { /* */
812 fprintf(stderr
, TAG_STS_ERR_FMT
, tf
->filename
);
814 if (tf
->date
== statbuf
.st_mtime
) {
815 /* current tags file tf is already loaded and up to date */
819 /* tags file has been modified, delete it's entries and reload it */
820 delTag(NULL
,NULL
,-2,NULL
,-2,tf
->index
);
822 /* If we get here we have to try to (re-) load the tags file */
823 if (FileList
== TipsFileList
)
824 load_status
= loadTipsFile(tf
->filename
, tf
->index
, 0);
826 load_status
= loadTagsFile(tf
->filename
, tf
->index
, 0);
828 if (stat(tf
->filename
,&statbuf
) != 0) {
830 /* if tf->loaded == 1 we already have seen the error msg */
831 fprintf(stderr
, TAG_STS_ERR_FMT
, tf
->filename
);
834 tf
->date
= statbuf
.st_mtime
;
842 t
= getTag(name
, search_type
);
848 *language
= t
->language
;
849 *searchString
= t
->searchString
;
857 ** This code path is followed if the request came from either
858 ** FindDefinition or FindDefCalltip. This should probably be refactored.
860 static int findDef(WindowInfo
*window
, const char *value
, int search_type
) {
861 static char tagText
[MAX_TAG_LEN
+ 1];
863 char message
[MAX_TAG_LEN
+40];
864 int l
, ml
, status
= 0;
866 searchMode
= search_type
;
868 if (l
<= MAX_TAG_LEN
) {
869 /* should be of type text??? */
870 for (p
= value
; *p
&& isascii(*p
); p
++) {
873 ml
= ((l
< MAX_TAG_LEN
) ? (l
) : (MAX_TAG_LEN
));
874 strncpy(tagText
, value
, ml
);
876 /* See if we can find the tip/tag */
877 status
= findAllMatches(window
, tagText
);
879 /* If we didn't find a requested calltip, see if we can use a tag */
880 if (status
== 0 && search_type
== TIP
&& TagsFileList
!= NULL
) {
881 searchMode
= TIP_FROM_TAG
;
882 status
= findAllMatches(window
, tagText
);
886 /* Didn't find any matches */
887 if (searchMode
== TIP_FROM_TAG
|| searchMode
== TIP
) {
888 sprintf(message
, "No match for \"%s\" in calltips or tags.",
890 tagsShowCalltip( window
, message
);
893 DialogF(DF_WARN
, window
->textArea
, 1, "Tags",
894 "\"%s\" not found in tags file%s", "OK", tagName
,
895 (TagsFileList
&& TagsFileList
->next
) ? "s" : "");
900 fprintf(stderr
, "NEdit: Can't handle non 8-bit text\n");
901 XBell(TheDisplay
, 0);
905 fprintf(stderr
, "NEdit: Tag Length too long.\n");
906 XBell(TheDisplay
, 0);
912 ** Lookup the definition for the current primary selection the currently
913 ** loaded tags file and bring up the file and line that the tags file
916 static void findDefinitionHelper(WindowInfo
*window
, Time time
, const char *arg
,
921 findDef(window
, arg
, search_type
);
925 searchMode
= search_type
;
926 XtGetSelectionValue(window
->textArea
, XA_PRIMARY
, XA_STRING
,
927 (XtSelectionCallbackProc
)findDefCB
, window
, time
);
934 void FindDefinition(WindowInfo
*window
, Time time
, const char *arg
)
936 findDefinitionHelper(window
, time
, arg
, TAG
);
942 void FindDefCalltip(WindowInfo
*window
, Time time
, const char *arg
)
944 /* Reset calltip parameters to reasonable defaults */
945 globAnchored
= False
;
947 globHAlign
= TIP_LEFT
;
948 globVAlign
= TIP_BELOW
;
949 globAlignMode
= TIP_SLOPPY
;
951 findDefinitionHelper(window
, time
, arg
, TIP
);
954 /* Callback function for FindDefinition */
955 static void findDefCB(Widget widget
, WindowInfo
*window
, Atom
*sel
,
956 Atom
*type
, char *value
, int *length
, int *format
)
958 /* skip if we can't get the selection data, or it's obviously too long */
959 if (*type
== XT_CONVERT_FAIL
|| value
== NULL
) {
960 XBell(TheDisplay
, 0);
962 findDef(window
, value
, searchMode
);
968 ** Try to display a calltip
969 ** anchored: If true, tip appears at position pos
970 ** lookup: If true, text is considered a key to be searched for in the
971 ** tip and/or tag database depending on search_type
972 ** search_type: Either TIP or TIP_FROM_TAG
974 int ShowTipString(WindowInfo
*window
, char *text
, Boolean anchored
,
975 int pos
, Boolean lookup
, int search_type
, int hAlign
, int vAlign
,
978 if (search_type
== TAG
) return 0;
980 /* So we don't have to carry all of the calltip alignment info around */
981 globAnchored
= anchored
;
985 globAlignMode
= alignMode
;
987 /* If this isn't a lookup request, just display it. */
989 return tagsShowCalltip(window
, text
);
991 return findDef(window
, text
, search_type
);
994 /* store all of the info into a pre-allocated tags struct */
995 static void setTag(tag
*t
, const char *name
, const char *file
,
996 int language
, const char *searchString
, int posInf
,
999 t
->name
= rcs_strdup(name
);
1000 t
->file
= rcs_strdup(file
);
1001 t
->language
= language
;
1002 t
->searchString
= rcs_strdup(searchString
);
1004 t
->path
= rcs_strdup(path
);
1008 ** ctags search expressions are literal strings with a search direction flag,
1009 ** line starting "^" and ending "$" delimiters. This routine translates them
1010 ** into NEdit compatible regular expressions and does the search.
1011 ** Etags search expressions are plain literals strings, which
1013 ** If in_buffer is not NULL then it is searched instead of the window buffer.
1014 ** In this case in_buffer should be an XtMalloc allocated buffer and the
1015 ** caller is responsible for freeing it.
1017 static int fakeRegExSearch(WindowInfo
*window
, char *in_buffer
,
1018 const char *searchString
, int *startPos
, int *endPos
)
1020 int found
, searchStartPos
, dir
, ctagsMode
;
1021 char searchSubs
[3*MAXLINE
+3], *outPtr
;
1022 const char *fileString
, *inPtr
;
1024 if (in_buffer
== NULL
) {
1025 /* get the entire (sigh) text buffer from the text area widget */
1026 fileString
= BufAsString(window
->buffer
);
1028 fileString
= in_buffer
;
1031 /* determine search direction and start position */
1032 if (*startPos
!= -1) { /* etags mode! */
1033 dir
= SEARCH_FORWARD
;
1034 searchStartPos
= *startPos
;
1036 } else if (searchString
[0] == '/') {
1037 dir
= SEARCH_FORWARD
;
1040 } else if (searchString
[0] == '?') {
1041 dir
= SEARCH_BACKWARD
;
1042 /* searchStartPos = window->buffer->length; */
1043 searchStartPos
= strlen(fileString
);
1046 fprintf(stderr
, "NEdit: Error parsing tag file search string");
1050 /* Build the search regex. */
1053 inPtr
=searchString
+1; /* searchString[0] is / or ? --> search dir */
1055 /* If the first char is a caret then it's a RE line start delim */
1056 *outPtr
++ = *inPtr
++;
1058 } else { /* etags mode, no search dir spec, no leading caret */
1062 if( (*inPtr
=='\\' && inPtr
[1]=='/') ||
1063 (*inPtr
=='\r' && inPtr
[1]=='$' && !inPtr
[2])
1066 - escapes (added by standard and exuberant ctags) from slashes
1067 - literal CRs generated by standard ctags for DOSified sources
1070 } else if(strchr("()-[]<>{}.|^*+?&\\", *inPtr
)
1071 || (*inPtr
== '$' && (inPtr
[1]||(!ctagsMode
)))){
1072 /* Escape RE Meta Characters to match them literally.
1073 Don't escape $ if it's the last charcter of the search expr
1074 in ctags mode; always escape $ in etags mode.
1077 *outPtr
++ = *inPtr
++;
1078 } else if (isspace((unsigned char)*inPtr
)) { /* col. multiple spaces */
1082 do { inPtr
++ ; } while(isspace((unsigned char)*inPtr
));
1083 } else { /* simply copy all other characters */
1084 *outPtr
++ = *inPtr
++;
1087 *outPtr
=0; /* Terminate searchSubs */
1089 found
= SearchString(fileString
, searchSubs
, dir
, SEARCH_REGEX
,
1090 False
, searchStartPos
, startPos
, endPos
, NULL
, NULL
, NULL
);
1092 if(!found
&& !ctagsMode
) {
1093 /* position of the target definition could have been drifted before
1094 startPos, if nothing has been found by now try searching backward
1095 again from startPos.
1097 found
= SearchString(fileString
, searchSubs
, SEARCH_BACKWARD
,
1098 SEARCH_REGEX
, False
, searchStartPos
, startPos
, endPos
, NULL
,
1102 /* return the result */
1104 /* *startPos and *endPos are set in SearchString*/
1107 /* startPos, endPos left untouched by SearchString if search failed. */
1108 XBell(TheDisplay
, 0);
1113 /* Finds all matches and handles tag "collisions". Prompts user with a
1114 list of collided tags in the hash table and allows the user to select
1116 static int findAllMatches(WindowInfo
*window
, const char *string
)
1118 Widget dialogParent
= window
->textArea
;
1119 char filename
[MAXPATHLEN
], pathname
[MAXPATHLEN
];
1120 char temp
[32+2*MAXPATHLEN
+MAXLINE
];
1121 const char *fileToSearch
, *searchString
, *tagPath
;
1123 int startPos
, i
, pathMatch
=0, samePath
=0, langMode
, nMatches
=0;
1125 /* verify that the string is reasonable as a tag */
1126 if (*string
== '\0' || strlen(string
) > MAX_TAG_LEN
) {
1127 XBell(TheDisplay
, 0);
1132 /* First look up all of the matching tags */
1133 while (LookupTag(string
, &fileToSearch
, &langMode
, &searchString
, &startPos
,
1134 &tagPath
, searchMode
)) {
1135 /* Skip this tag if it has a language mode that doesn't match the
1136 current language mode, but don't skip anything if the window is in
1137 PLAIN_LANGUAGE_MODE. */
1138 if (window
->languageMode
!= PLAIN_LANGUAGE_MODE
&&
1139 GetPrefSmartTags() && langMode
!= PLAIN_LANGUAGE_MODE
&&
1140 langMode
!= window
->languageMode
) {
1144 if (*fileToSearch
== '/')
1145 strcpy(tagFiles
[nMatches
], fileToSearch
);
1147 sprintf(tagFiles
[nMatches
],"%s%s",tagPath
,fileToSearch
);
1148 strcpy(tagSearch
[nMatches
],searchString
);
1149 tagPosInf
[nMatches
]=startPos
;
1150 ParseFilename(tagFiles
[nMatches
], filename
, pathname
);
1151 /* Is this match in the current file? If so, use it! */
1152 if (GetPrefSmartTags() && !strcmp(window
->filename
,filename
)
1153 && !strcmp(window
->path
,pathname
) ) {
1155 strcpy(tagFiles
[0],tagFiles
[nMatches
]);
1156 strcpy(tagSearch
[0],tagSearch
[nMatches
]);
1157 tagPosInf
[0]=tagPosInf
[nMatches
];
1162 /* Is this match in the same dir. as the current file? */
1163 if (!strcmp(window
->path
,pathname
)) {
1167 if (++nMatches
>= MAXDUPTAGS
) {
1168 DialogF(DF_WARN
, dialogParent
, 1, "Tags",
1169 "Too many duplicate tags, first %d shown", "OK", MAXDUPTAGS
);
1172 /* Tell LookupTag to look for more definitions of the same tag: */
1176 /* Did we find any matches? */
1181 /* Only one of the matches is in the same dir. as this file. Use it. */
1182 if (GetPrefSmartTags() && samePath
== 1 && nMatches
> 1) {
1183 strcpy(tagFiles
[0],tagFiles
[pathMatch
]);
1184 strcpy(tagSearch
[0],tagSearch
[pathMatch
]);
1185 tagPosInf
[0]=tagPosInf
[pathMatch
];
1189 /* If all of the tag entries are the same file, just use the first.
1191 if (GetPrefSmartTags()) {
1192 for (i
=1; i
<nMatches
; i
++)
1193 if (strcmp(tagFiles
[i
],tagFiles
[i
-1]))
1200 if (!(dupTagsList
= (char **) malloc(sizeof(char *) * nMatches
))) {
1201 fprintf(stderr
, "nedit: findAllMatches(): out of heap space!\n");
1202 XBell(TheDisplay
, 0);
1206 for (i
=0; i
<nMatches
; i
++) {
1207 ParseFilename(tagFiles
[i
], filename
, pathname
);
1208 if ((i
<nMatches
-1 && !strcmp(tagFiles
[i
],tagFiles
[i
+1])) ||
1209 (i
>0 && !strcmp(tagFiles
[i
],tagFiles
[i
-1]))) {
1210 if(*(tagSearch
[i
]) && (tagPosInf
[i
] != -1)) { /* etags */
1211 sprintf(temp
,"%2d. %s%s %8i %s", i
+1, pathname
,
1212 filename
, tagPosInf
[i
], tagSearch
[i
]);
1213 } else if (*(tagSearch
[i
])) { /* ctags search expr */
1214 sprintf(temp
,"%2d. %s%s %s", i
+1, pathname
,
1215 filename
, tagSearch
[i
]);
1216 } else { /* line number only */
1217 sprintf(temp
,"%2d. %s%s %8i", i
+1, pathname
, filename
,
1221 sprintf(temp
,"%2d. %s%s",i
+1,pathname
,filename
);
1224 if (NULL
== (dupTagsList
[i
] = (char*) malloc(strlen(temp
) + 1))) {
1226 fprintf(stderr
, "nedit: findAllMatches(): out of heap space!\n");
1228 /* dupTagsList[i] is unallocated, let's free [i - 1] to [0] */
1229 for (j
= i
- 1; j
> -1; j
--) {
1230 free(dupTagsList
[j
]);
1234 XBell(TheDisplay
, 0);
1238 strcpy(dupTagsList
[i
],temp
);
1240 createSelectMenu(dialogParent
, "Duplicate Tags", nMatches
, dupTagsList
);
1241 for (i
=0; i
<nMatches
; i
++)
1242 free(dupTagsList
[i
]);
1248 ** No need for a dialog list, there is only one tag matching --
1249 ** Go directly to the tag
1251 if (searchMode
== TAG
)
1252 editTaggedLocation( dialogParent
, 0 );
1254 showMatchingCalltip( dialogParent
, 0 );
1258 /* Callback function for the FindAll widget. Process the users response. */
1259 static void findAllCB(Widget parent
, XtPointer client_data
, XtPointer call_data
)
1264 XmSelectionBoxCallbackStruct
*cbs
=
1265 (XmSelectionBoxCallbackStruct
*) call_data
;
1266 if (cbs
->reason
== XmCR_NO_MATCH
)
1268 if (cbs
->reason
== XmCR_CANCEL
) {
1269 XtDestroyWidget(XtParent(parent
));
1273 XmStringGetLtoR(cbs
->value
,XmFONTLIST_DEFAULT_TAG
,&eptr
);
1274 if ((i
= atoi(eptr
)-1) < 0) {
1275 XBell(TheDisplay
, 0);
1279 if (searchMode
== TAG
)
1280 editTaggedLocation( parent
, i
); /* Open the file with the definition */
1282 showMatchingCalltip( parent
, i
);
1284 if (cbs
->reason
== XmCR_OK
)
1285 XtDestroyWidget(XtParent(parent
));
1288 /* Window manager close-box callback for tag-collision dialog */
1289 static void findAllCloseCB(Widget parent
, XtPointer client_data
,
1290 XtPointer call_data
)
1292 XtDestroyWidget(parent
);
1296 * Given a \0 terminated string and a position, advance the position
1297 * by n lines, where line separators (for now) are \n. If the end of
1298 * string is reached before n lines, return the number of lines advanced,
1299 * else normally return -1.
1301 static int moveAheadNLines( char *str
, int *pos
, int n
) {
1303 while (str
[*pos
] != '\0' && n
>0) {
1304 if (str
[*pos
] == '\n')
1315 ** Show the calltip specified by tagFiles[i], tagSearch[i], tagPosInf[i]
1316 ** This reads from either a source code file (if searchMode == TIP_FROM_TAG)
1317 ** or a calltips file (if searchMode == TIP).
1319 static void showMatchingCalltip( Widget parent
, int i
)
1321 int startPos
=0, fileLen
, readLen
, tipLen
;
1325 struct stat statbuf
;
1328 /* 1. Open the target file */
1329 NormalizePathname(tagFiles
[i
]);
1330 fp
= fopen(tagFiles
[i
], "r");
1332 DialogF(DF_ERR
, parent
, 1, "Error opening File", "Error opening %s",
1336 if (fstat(fileno(fp
), &statbuf
) != 0) {
1338 DialogF(DF_ERR
, parent
, 1, "Error opening File", "Error opening %s",
1343 /* 2. Read the target file */
1344 /* Allocate space for the whole contents of the file (unfortunately) */
1345 fileLen
= statbuf
.st_size
;
1346 fileString
= XtMalloc(fileLen
+1); /* +1 = space for null */
1347 if (fileString
== NULL
) {
1349 DialogF(DF_ERR
, parent
, 1, "File too large",
1350 "File is too large to load", "OK");
1354 /* Read the file into fileString and terminate with a null */
1355 readLen
= fread(fileString
, sizeof(char), fileLen
, fp
);
1358 DialogF(DF_ERR
, parent
, 1, "Error reading File", "Error reading %s",
1363 fileString
[readLen
] = 0;
1365 /* Close the file */
1366 if (fclose(fp
) != 0) {
1367 /* unlikely error */
1368 DialogF(DF_WARN
, parent
, 1, "Error closing File",
1369 "Unable to close file", "OK");
1370 /* we read it successfully, so continue */
1373 /* 3. Search for the tagged location (set startPos) */
1374 if (!*(tagSearch
[i
])) {
1375 /* It's a line number, just go for it */
1376 if ((moveAheadNLines( fileString
, &startPos
, tagPosInf
[i
]-1 )) >= 0) {
1377 DialogF(DF_ERR
, parent
, 1, "Tags Error",
1378 "%s\n not long enough for definition to be on line %d",
1379 "OK", tagFiles
[i
], tagPosInf
[i
]);
1384 startPos
= tagPosInf
[i
];
1385 if(!fakeRegExSearch(WidgetToWindow(parent
), fileString
, tagSearch
[i
],
1386 &startPos
, &endPos
)){
1387 DialogF(DF_WARN
, parent
, 1, "Tag not found",
1388 "Definition for %s\nnot found in %s", "OK", tagName
,
1395 if (searchMode
== TIP
) {
1398 /* 4. Find the end of the calltip (delimited by an empty line) */
1400 found
= SearchString(fileString
, "\\n\\s*\\n", SEARCH_FORWARD
,
1401 SEARCH_REGEX
, False
, startPos
, &endPos
, &dummy
, NULL
,
1404 /* Just take 4 lines */
1405 moveAheadNLines( fileString
, &endPos
, TIP_DEFAULT_LINES
);
1406 --endPos
; /* Lose the last \n */
1408 } else { /* Mode = TIP_FROM_TAG */
1409 /* 4. Copy TIP_DEFAULT_LINES lines of text to the calltip string */
1411 moveAheadNLines( fileString
, &endPos
, TIP_DEFAULT_LINES
);
1412 /* Make sure not to overrun the fileString with ". . ." */
1413 if (((size_t) endPos
) <= (strlen(fileString
)-5)) {
1414 sprintf( &fileString
[endPos
], ". . ." );
1418 /* 5. Copy the calltip to a string */
1419 tipLen
= endPos
- startPos
;
1420 message
= XtMalloc(tipLen
+1); /* +1 = space for null */
1421 if (message
== NULL
)
1423 DialogF(DF_ERR
, parent
, 1, "Out of Memory",
1424 "Can't allocate memory for calltip message", "OK");
1428 strncpy( message
, &fileString
[startPos
], tipLen
);
1429 message
[tipLen
] = 0;
1432 tagsShowCalltip( WidgetToWindow(parent
), message
);
1437 /* Open a new (or existing) editor window to the location specified in
1438 tagFiles[i], tagSearch[i], tagPosInf[i] */
1439 static void editTaggedLocation( Widget parent
, int i
)
1441 /* Globals: tagSearch, tagPosInf, tagFiles, tagName, textNrows,
1443 int startPos
, endPos
, lineNum
, rows
;
1444 char filename
[MAXPATHLEN
], pathname
[MAXPATHLEN
];
1445 WindowInfo
*windowToSearch
;
1446 WindowInfo
*parentWindow
= WidgetToWindow(parent
);
1448 ParseFilename(tagFiles
[i
],filename
,pathname
);
1449 /* open the file containing the definition */
1450 EditExistingFile(parentWindow
, filename
, pathname
, 0, NULL
, False
,
1451 NULL
, GetPrefOpenInTab(), False
);
1452 windowToSearch
= FindWindowWithFile(filename
, pathname
);
1453 if (windowToSearch
== NULL
) {
1454 DialogF(DF_WARN
, parent
, 1, "File not found", "File %s not found", "OK",
1459 startPos
=tagPosInf
[i
];
1461 if(!*(tagSearch
[i
])) {
1462 /* if the search string is empty, select the numbered line */
1463 SelectNumberedLine(windowToSearch
, startPos
);
1467 /* search for the tags file search string in the newly opened file */
1468 if(!fakeRegExSearch(windowToSearch
, NULL
, tagSearch
[i
], &startPos
,
1470 DialogF(DF_WARN
, windowToSearch
->shell
, 1, "Tag Error",
1471 "Definition for %s\nnot found in %s", "OK", tagName
,
1476 /* select the matched string */
1477 BufSelect(windowToSearch
->buffer
, startPos
, endPos
);
1478 RaiseFocusDocumentWindow(windowToSearch
, True
);
1480 /* Position it nicely in the window,
1481 about 1/4 of the way down from the top */
1482 lineNum
= BufCountLines(windowToSearch
->buffer
, 0, startPos
);
1483 XtVaGetValues(windowToSearch
->lastFocus
, textNrows
, &rows
, NULL
);
1484 TextSetScroll(windowToSearch
->lastFocus
, lineNum
- rows
/4, 0);
1485 TextSetCursorPos(windowToSearch
->lastFocus
, endPos
);
1488 /* Create a Menu for user to select from the collided tags */
1489 static Widget
createSelectMenu(Widget parent
, char *label
, int nArgs
,
1496 XmString popupTitle
;
1500 list
= (XmStringTable
) XtMalloc(nArgs
* sizeof(XmString
*));
1501 for (i
=0; i
<nArgs
; i
++)
1502 list
[i
] = XmStringCreateSimple(args
[i
]);
1503 sprintf(tmpStr
,"Select File With TAG: %s",tagName
);
1504 popupTitle
= XmStringCreateSimple(tmpStr
);
1506 XtSetArg(csdargs
[ac
], XmNlistLabelString
, popupTitle
); ac
++;
1507 XtSetArg(csdargs
[ac
], XmNlistItems
, list
); ac
++;
1508 XtSetArg(csdargs
[ac
], XmNlistItemCount
, nArgs
); ac
++;
1509 XtSetArg(csdargs
[ac
], XmNvisibleItemCount
, 12); ac
++;
1510 XtSetArg(csdargs
[ac
], XmNautoUnmanage
, False
); ac
++;
1511 menu
= CreateSelectionDialog(parent
,label
,csdargs
,ac
);
1512 XtUnmanageChild(XmSelectionBoxGetChild(menu
, XmDIALOG_TEXT
));
1513 XtUnmanageChild(XmSelectionBoxGetChild(menu
, XmDIALOG_HELP_BUTTON
));
1514 XtUnmanageChild(XmSelectionBoxGetChild(menu
, XmDIALOG_SELECTION_LABEL
));
1515 XtAddCallback(menu
, XmNokCallback
, (XtCallbackProc
)findAllCB
, menu
);
1516 XtAddCallback(menu
, XmNapplyCallback
, (XtCallbackProc
)findAllCB
, menu
);
1517 XtAddCallback(menu
, XmNcancelCallback
, (XtCallbackProc
)findAllCB
, menu
);
1518 AddMotifCloseCallback(XtParent(menu
), findAllCloseCB
, NULL
);
1519 for (i
=0; i
<nArgs
; i
++)
1520 XmStringFree(list
[i
]);
1521 XtFree((char *)list
);
1522 XmStringFree(popupTitle
);
1523 ManageDialogCenteredOnPointer(menu
);
1529 /*--------------------------------------------------------------------------
1531 Reference-counted string hack; SJT 4/2000
1533 This stuff isn't specific to tags, so it should be in it's own file.
1534 However, I'm leaving it in here for now to reduce the diffs.
1536 This could really benefit from using a real hash table.
1539 #define RCS_SIZE 10000
1545 int talloc
, tshar
, tgiveup
, tbytes
, tbyteshared
;
1555 static struct rcs
*Rcs
[RCS_SIZE
];
1556 static struct rcs_stats RcsStats
;
1559 ** Take a normal string, create a shared string from it if need be,
1560 ** and return pointer to that shared string.
1562 ** Returned strings are const because they are shared. Do not modify them!
1565 static const char *rcs_strdup(const char *str
)
1570 struct rcs
*prev
= NULL
;
1572 char *newstr
= NULL
;
1577 bucket
= hashAddr(str
) % RCS_SIZE
;
1583 /* Don't share if it won't save space.
1585 Doesn't save anything - if we have lots of small-size objects,
1586 it's beneifical to share them. We don't know until we make a full
1587 count. My tests show that it's better to leave this out. */
1588 if (len
<= sizeof(struct rcs
))
1590 new_str
= strdup(str
); /* GET RID OF strdup() IF EVER ENABLED (not ANSI) */
1596 /* Find it in hash */
1597 for (rp
= Rcs
[bucket
]; rp
; rp
= rp
->next
)
1599 if (!strcmp(str
, rp
->string
))
1604 if (rp
) /* It exists, return it and bump ref ct */
1607 newstr
= rp
->string
;
1610 RcsStats
.tbyteshared
+= len
;
1612 else /* Doesn't exist, conjure up a new one. */
1615 if (NULL
== (newrcs
= (struct rcs
*) malloc(sizeof(struct rcs
)))) {
1616 /* Not much to fall back to here. */
1617 fprintf(stderr
, "nedit: rcs_strdup(): out of heap space!\n");
1618 XBell(TheDisplay
, 0);
1622 if (NULL
== (newrcs
->string
= (char*) malloc(len
+ 1))) {
1623 /* Not much to fall back to here. */
1624 fprintf(stderr
, "nedit: rcs_strdup(): out of heap space!\n");
1625 XBell(TheDisplay
, 0);
1628 strcpy(newrcs
->string
, str
);
1630 newrcs
->next
= NULL
;
1633 prev
->next
= newrcs
;
1635 Rcs
[bucket
] = newrcs
;
1637 newstr
= newrcs
->string
;
1640 RcsStats
.tbytes
+= len
;
1645 ** Decrease the reference count on a shared string. When the reference
1646 ** count reaches zero, free the master string.
1649 static void rcs_free(const char *rcs_str
)
1653 struct rcs
*prev
= NULL
;
1655 if (rcs_str
== NULL
)
1658 bucket
= hashAddr(rcs_str
) % RCS_SIZE
;
1660 /* find it in hash */
1661 for (rp
= Rcs
[bucket
]; rp
; rp
= rp
->next
)
1663 if (rcs_str
== rp
->string
)
1668 if (rp
) /* It's a shared string, decrease ref count */
1672 if (rp
->usage
< 0) /* D'OH! */
1674 fprintf(stderr
, "NEdit: internal error deallocating shared string.");
1678 if (rp
->usage
== 0) /* Last one- free the storage */
1682 prev
->next
= rp
->next
;
1684 Rcs
[bucket
] = rp
->next
;
1688 else /* Doesn't appear to be a shared string */
1690 fprintf(stderr
, "NEdit: attempt to free a non-shared string.");
1695 /********************************************************************
1696 * Functions for loading Calltips files *
1697 ********************************************************************/
1699 enum tftoken_types
{ TF_EOF
, TF_BLOCK
, TF_VERSION
, TF_INCLUDE
, TF_LANGUAGE
,
1700 TF_ALIAS
, TF_ERROR
, TF_ERROR_EOF
};
1702 /* A wrapper for SearchString */
1703 static int searchLine(char *line
, const char *regex
) {
1705 return SearchString(line
, regex
, SEARCH_FORWARD
, SEARCH_REGEX
,
1706 False
, 0, &dummy1
, &dummy2
, NULL
, NULL
, NULL
);
1709 /* Check if a line has non-ws characters */
1710 static Boolean
lineEmpty(const char *line
) {
1711 while (*line
&& *line
!= '\n') {
1712 if (*line
!= ' ' && *line
!= '\t')
1719 /* Remove trailing whitespace from a line */
1720 static void rstrip( char *dst
, const char *src
) {
1721 int wsStart
, dummy2
;
1722 /* Strip trailing whitespace */
1723 if(SearchString(src
, "\\s*\\n", SEARCH_FORWARD
, SEARCH_REGEX
,
1724 False
, 0, &wsStart
, &dummy2
, NULL
, NULL
, NULL
)) {
1726 memcpy(dst
, src
, wsStart
);
1734 ** Get the next block from a tips file. A block is a \n\n+ delimited set of
1735 ** lines in a calltips file. All of the parameters except <fp> are return
1736 ** values, and most have different roles depending on the type of block
1738 ** header: Depends on the block type
1739 ** body: Depends on the block type. Used to return a new
1740 ** dynamically allocated string.
1741 ** blkLine: Returns the line number of the first line of the block
1742 ** after the "* xxxx *" line.
1743 ** currLine: Used to keep track of the current line in the file.
1745 static int nextTFBlock(FILE *fp
, char *header
, char **body
, int *blkLine
,
1748 /* These are the different kinds of tokens */
1749 const char *commenTF_regex
= "^\\s*\\* comment \\*\\s*$";
1750 const char *version_regex
= "^\\s*\\* version \\*\\s*$";
1751 const char *include_regex
= "^\\s*\\* include \\*\\s*$";
1752 const char *language_regex
= "^\\s*\\* language \\*\\s*$";
1753 const char *alias_regex
= "^\\s*\\* alias \\*\\s*$";
1754 char line
[MAXLINE
], *status
;
1758 /* Skip blank lines and comments */
1760 /* Skip blank lines */
1761 while((status
=fgets(line
, MAXLINE
, fp
))) {
1763 if(!lineEmpty( line
))
1767 /* Check for error or EOF */
1771 /* We've got a non-blank line -- is it a comment block? */
1772 if( !searchLine(line
, commenTF_regex
) )
1775 /* Skip the comment (non-blank lines) */
1776 while((status
=fgets(line
, MAXLINE
, fp
))) {
1778 if(lineEmpty( line
))
1786 /* Now we know it's a meaningful block */
1787 dummy1
= searchLine(line
, include_regex
);
1788 if( dummy1
|| searchLine(line
, alias_regex
) ) {
1789 /* INCLUDE or ALIAS block */
1790 int incLen
, incPos
, i
, incLines
;
1792 /* fprintf(stderr, "Starting include/alias at line %i\n", *currLine); */
1797 /* Need to read the header line for an alias */
1798 status
=fgets(line
, MAXLINE
, fp
);
1801 return TF_ERROR_EOF
;
1802 if (lineEmpty( line
)) {
1803 fprintf( stderr
, "nedit: Warning: empty '* alias *' "
1804 "block in calltips file.\n" );
1807 rstrip(header
, line
);
1810 *blkLine
= *currLine
+ 1; /* Line of first actual filename/alias */
1813 /* Figure out how long the block is */
1814 while((status
=fgets(line
, MAXLINE
, fp
))) {
1816 if(lineEmpty( line
))
1819 incLen
= ftell(fp
) - incPos
;
1820 incLines
= *currLine
- *blkLine
;
1821 /* Correct currLine for the empty line it read at the end */
1823 if (incLines
== 0) {
1824 fprintf( stderr
, "nedit: Warning: empty '* include *' or"
1825 " '* alias *' block in calltips file.\n" );
1828 /* Make space for the filenames/alias sources */
1829 *body
= (char *)malloc(incLen
+1);
1833 if (fseek(fp
, incPos
, SEEK_SET
) != 0) {
1837 /* Read all the lines in the block */
1838 /* fprintf(stderr, "Copying lines\n"); */
1839 for (i
=0; i
<incLines
; i
++) {
1840 status
= fgets(line
, MAXLINE
, fp
);
1843 return TF_ERROR_EOF
;
1848 strcat(*body
, line
);
1850 /* fprintf(stderr, "Finished include/alias at line %i\n", *currLine); */
1853 else if( searchLine(line
, language_regex
) ) {
1854 /* LANGUAGE block */
1855 status
=fgets(line
, MAXLINE
, fp
);
1858 return TF_ERROR_EOF
;
1859 if (lineEmpty( line
)) {
1860 fprintf( stderr
, "nedit: Warning: empty '* language *' block in calltips file.\n" );
1863 *blkLine
= *currLine
;
1864 rstrip(header
, line
);
1868 else if( searchLine(line
, version_regex
) ) {
1870 status
=fgets(line
, MAXLINE
, fp
);
1873 return TF_ERROR_EOF
;
1874 if (lineEmpty( line
)) {
1875 fprintf( stderr
, "nedit: Warning: empty '* version *' block in calltips file.\n" );
1878 *blkLine
= *currLine
;
1879 rstrip(header
, line
);
1885 /* The first line is the key, the rest is the tip.
1886 Strip trailing whitespace. */
1887 rstrip(header
, line
);
1889 status
=fgets(line
, MAXLINE
, fp
);
1892 return TF_ERROR_EOF
;
1893 if (lineEmpty( line
)) {
1894 fprintf( stderr
, "nedit: Warning: empty calltip block:\n"
1895 " \"%s\"\n", header
);
1898 *blkLine
= *currLine
;
1899 *body
= strdup(line
);
1903 /* Skip the rest of the block */
1905 while(fgets(line
, MAXLINE
, fp
)) {
1907 if (lineEmpty( line
))
1911 /* Warn about any unneeded extra lines (which are ignored). */
1912 if (dummy1
+1 < *currLine
&& code
!= TF_BLOCK
) {
1913 fprintf( stderr
, "nedit: Warning: extra lines in language or version block ignored.\n" );
1919 /* A struct for describing a calltip alias */
1920 typedef struct _alias
{
1923 struct _alias
*next
;
1927 ** Allocate a new alias, copying dest and stealing sources. This may
1928 ** seem strange but that's the way it's called
1930 static tf_alias
*new_alias(const char *dest
, char *sources
) {
1933 /* fprintf(stderr, "new_alias: %s <- %s\n", dest, sources); */
1934 /* Allocate the alias */
1935 alias
= (tf_alias
*)malloc( sizeof(tf_alias
) );
1940 alias
->dest
= (char*)malloc( strlen(dest
)+1 );
1943 strcpy( alias
->dest
, dest
);
1944 alias
->sources
= sources
;
1948 /* Deallocate a linked-list of aliases */
1949 static void free_alias_list(tf_alias
*alias
) {
1950 tf_alias
*tmp_alias
;
1952 tmp_alias
= alias
->next
;
1954 free(alias
->sources
);
1961 ** Load a calltips file and insert all of the entries into the global tips
1962 ** database. Each tip is essentially stored as its filename and the line
1963 ** at which it appears--the exact same way ctags indexes source-code. That's
1964 ** why calltips and tags share so much code.
1966 static int loadTipsFile(const char *tipsFile
, int index
, int recLevel
)
1969 char header
[MAXLINE
];
1970 char *body
, *tipIncFile
;
1971 char tipPath
[MAXPATHLEN
];
1972 char resolvedTipsFile
[MAXPATHLEN
+1];
1973 int nTipsAdded
=0, langMode
= PLAIN_LANGUAGE_MODE
, oldLangMode
;
1974 int currLine
=0, code
, blkLine
;
1975 tf_alias
*aliases
=NULL
, *tmp_alias
;
1977 if(recLevel
> MAX_TAG_INCLUDE_RECURSION_LEVEL
) {
1978 fprintf(stderr
, "nedit: Warning: Reached recursion limit before loading calltips file:\n\t%s\n", tipsFile
);
1982 /* find the tips file */
1984 /* Allow ~ in Unix filenames */
1985 strncpy(tipPath
, tipsFile
, MAXPATHLEN
); /* ExpandTilde is destructive */
1986 ExpandTilde(tipPath
);
1987 if(!ResolvePath(tipPath
, resolvedTipsFile
))
1990 if(!ResolvePath(tipsFile
, resolvedTipsFile
))
1994 /* Get the path to the tips file */
1995 ParseFilename(resolvedTipsFile
, NULL
, tipPath
);
1998 if ((fp
= fopen(resolvedTipsFile
, "r")) == NULL
)
2002 code
= nextTFBlock(fp
, header
, &body
, &blkLine
, &currLine
);
2003 if( code
== TF_ERROR_EOF
) {
2004 fprintf(stderr
,"nedit: Warning: unexpected EOF in calltips file.\n");
2007 if( code
== TF_EOF
)
2012 /* Add the calltip to the global hash table.
2013 For the moment I'm just using line numbers because I don't
2014 want to have to deal with adding escape characters for
2015 regex metacharacters that might appear in the string */
2016 nTipsAdded
+= addTag(header
, resolvedTipsFile
, langMode
, "",
2017 blkLine
, tipPath
, index
);
2021 /* nextTFBlock returns a colon-separated list of tips files
2023 for(tipIncFile
=strtok(body
,":"); tipIncFile
;
2024 tipIncFile
=strtok(NULL
,":")) {
2026 "nedit: DEBUG: including tips file '%s'\n",
2028 nTipsAdded
+= loadTipsFile( tipIncFile
, index
, recLevel
+1);
2033 /* Switch to the new language mode if it's valid, else ignore
2035 oldLangMode
= langMode
;
2036 langMode
= FindLanguageMode( header
);
2037 if (langMode
== PLAIN_LANGUAGE_MODE
&&
2038 strcmp(header
, "Plain")) {
2040 "nedit: Error reading calltips file:\n\t%s\n"
2041 "Unknown language mode: \"%s\"\n",
2043 langMode
= oldLangMode
;
2047 fprintf(stderr
,"nedit: Warning: Recoverable error while "
2048 "reading calltips file:\n \"%s\"\n",
2052 /* Allocate a new alias struct */
2053 tmp_alias
= aliases
;
2054 aliases
= new_alias(header
, body
);
2056 fprintf(stderr
,"nedit: Can't allocate memory for tipfile "
2057 "alias in calltips file:\n \"%s\"\n",
2059 /* Deallocate any allocated aliases */
2060 free_alias_list(tmp_alias
);
2063 /* Add it to the list */
2064 aliases
->next
= tmp_alias
;
2067 ;/* Ignore TF_VERSION for now */
2071 /* Now resolve any aliases */
2072 tmp_alias
= aliases
;
2076 t
= getTag(tmp_alias
->dest
, TIP
);
2078 fprintf(stderr
, "nedit: Can't find destination of alias \"%s\"\n"
2079 " in calltips file:\n \"%s\"\n",
2080 tmp_alias
->dest
, resolvedTipsFile
);
2082 for(src
=strtok(tmp_alias
->sources
,":"); src
; src
=strtok(NULL
,":"))
2083 addTag(src
, resolvedTipsFile
, t
->language
, "", t
->posInf
,
2086 tmp_alias
= tmp_alias
->next
;
2088 free_alias_list(aliases
);