1 static const char CVSID
[] = "$Id: tags.c,v 1.65 2006/10/13 07:26:02 ajbj 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!\n"
486 " Passed NULL pointer to DeleteTagsFile!\n");
490 searchMode
= file_type
;
491 if (searchMode
== TAG
)
492 FileList
= TagsFileList
;
494 FileList
= TipsFileList
;
496 tmptagSpec
= (char *) malloc(strlen(tagSpec
)+1);
497 strcpy(tmptagSpec
, tagSpec
);
499 for (filename
= strtok(tmptagSpec
,":"); filename
;
500 filename
= strtok(NULL
,":")) {
501 if (*filename
!= '/') {
502 strcpy(pathName
, GetCurrentDir());
503 strcat(pathName
,"/");
504 strcat(pathName
,filename
);
506 strcpy(pathName
,filename
);
508 NormalizePathname(pathName
);
510 for (last
=NULL
,t
= FileList
; t
; last
= t
,t
= t
->next
) {
511 if (strcmp(t
->filename
, pathName
))
513 /* Don't unload tips files with nonzero refcounts unless forced */
514 if (searchMode
== TIP
&& !force_unload
&& --t
->refcount
> 0) {
518 delTag(NULL
,NULL
,-2,NULL
,-2,t
->index
);
519 if (last
) last
->next
= t
->next
;
520 else FileList
= setFileListHead(t
->next
, file_type
);
526 /* If any file can't be removed, return false */
537 ** Update the "Find Definition", "Unload Tags File", "Show Calltip",
538 ** and "Unload Calltips File" menu items in the existing windows.
540 static void updateMenuItems(void)
543 Boolean tipStat
=FALSE
, tagStat
=FALSE
;
545 if (TipsFileList
) tipStat
=TRUE
;
546 if (TagsFileList
) tagStat
=TRUE
;
548 for (w
=WindowList
; w
!=NULL
; w
=w
->next
) {
549 if (!IsTopDocument(w
))
551 XtSetSensitive(w
->showTipItem
, tipStat
|| tagStat
);
552 XtSetSensitive(w
->unloadTipsMenuItem
, tipStat
);
553 XtSetSensitive(w
->findDefItem
, tagStat
);
554 XtSetSensitive(w
->unloadTagsMenuItem
, tagStat
);
559 ** Scans one <line> from a ctags tags file (<index>) in tagPath.
560 ** Return value: Number of tag specs added.
562 static int scanCTagsLine(const char *line
, const char *tagPath
, int index
)
564 char name
[MAXLINE
], searchString
[MAXLINE
];
565 char file
[MAXPATHLEN
];
566 char *posTagREEnd
, *posTagRENull
;
569 nRead
= sscanf(line
, "%s\t%s\t%[^\n]", name
, file
, searchString
);
576 ** Guess the end of searchString:
577 ** Try to handle original ctags and exuberant ctags format:
579 if(searchString
[0] == '/' || searchString
[0] == '?') {
581 pos
=-1; /* "search expr without pos info" */
583 /* Situations: /<ANY expr>/\0
584 ** ?<ANY expr>?\0 --> original ctags
585 ** /<ANY expr>/;" <flags>
586 ** ?<ANY expr>?;" <flags> --> exuberant ctags
588 posTagREEnd
= strrchr(searchString
, ';');
589 posTagRENull
= strchr(searchString
, 0);
590 if(!posTagREEnd
|| (posTagREEnd
[1] != '"') ||
591 (posTagRENull
[-1] == searchString
[0])) {
592 /* -> original ctags format = exuberant ctags format 1 */
593 posTagREEnd
= posTagRENull
;
595 /* looks like exuberant ctags format 2 */
600 ** Hide the last delimiter:
601 ** /<expression>/ becomes /<expression>
602 ** ?<expression>? becomes ?<expression>
603 ** This will save a little work in fakeRegExSearch.
605 if(posTagREEnd
> (searchString
+2)) {
607 if(searchString
[0] == *posTagREEnd
)
611 pos
=atoi(searchString
);
614 /* No ability to read language mode right now */
615 return addTag(name
, file
, PLAIN_LANGUAGE_MODE
, searchString
, pos
, tagPath
,
620 * Scans one <line> from an etags (emacs) tags file (<index>) in tagPath.
621 * recLevel = current recursion level for tags file including
622 * file = destination definition file. possibly modified. len=MAXPATHLEN!
623 * Return value: Number of tag specs added.
625 static int scanETagsLine(const char *line
, const char * tagPath
, int index
,
626 char * file
, int recLevel
)
628 char name
[MAXLINE
], searchString
[MAXLINE
];
629 char incPath
[MAXPATHLEN
];
631 char *posDEL
, *posSOH
, *posCOM
;
633 /* check for destination file separator */
634 if(line
[0]==12) { /* <np> */
639 /* check for standard definition line */
640 posDEL
=strchr(line
, '\177');
641 posSOH
=strchr(line
, '\001');
642 posCOM
=strrchr(line
, ',');
643 if(*file
&& posDEL
&& (posSOH
> posDEL
) && (posCOM
> posSOH
)) {
644 /* exuberant ctags -e style */
645 len
=Min(MAXLINE
-1, posDEL
- line
);
646 strncpy(searchString
, line
, len
);
648 len
=Min(MAXLINE
-1, (posSOH
- posDEL
) - 1);
649 strncpy(name
, posDEL
+ 1, len
);
652 /* No ability to set language mode for the moment */
653 return addTag(name
, file
, PLAIN_LANGUAGE_MODE
, searchString
, pos
,
656 if (*file
&& posDEL
&& (posCOM
> posDEL
)) {
657 /* old etags style, part name<soh> is missing here! */
658 len
=Min(MAXLINE
-1, posDEL
- line
);
659 strncpy(searchString
, line
, len
);
661 /* guess name: take the last alnum (plus _) part of searchString */
663 if( isalnum((unsigned char)searchString
[len
]) ||
664 (searchString
[len
] == '_'))
670 while (pos
>= 0 && (isalnum((unsigned char)searchString
[pos
]) ||
671 (searchString
[pos
] == '_')))
673 strncpy(name
, searchString
+ pos
+ 1, len
- pos
);
674 name
[len
- pos
] = 0; /* name ready */
676 return addTag(name
, file
, PLAIN_LANGUAGE_MODE
, searchString
, pos
,
679 /* check for destination file spec */
680 if(*line
&& posCOM
) {
681 len
=Min(MAXPATHLEN
-1, posCOM
- line
);
682 strncpy(file
, line
, len
);
684 /* check if that's an include file ... */
685 if(!(strncmp(posCOM
+1, "include", 7))) {
687 if((strlen(tagPath
) + strlen(file
)) >= MAXPATHLEN
) {
688 fprintf(stderr
, "tags.c: MAXPATHLEN overflow\n");
689 *file
=0; /* invalidate */
692 strcpy(incPath
, tagPath
);
693 strcat(incPath
, file
);
694 CompressPathname(incPath
);
695 return(loadTagsFile(incPath
, index
, recLevel
+1));
697 return(loadTagsFile(file
, index
, recLevel
+1));
706 TFT_CHECK
, TFT_ETAGS
, TFT_CTAGS
710 ** Loads tagsFile into the hash table.
711 ** Returns the number of added tag specifications.
713 static int loadTagsFile(const char *tagsFile
, int index
, int recLevel
)
717 char file
[MAXPATHLEN
], tagPath
[MAXPATHLEN
];
718 char resolvedTagsFile
[MAXPATHLEN
+1];
720 int tagFileType
= TFT_CHECK
;
722 if(recLevel
> MAX_TAG_INCLUDE_RECURSION_LEVEL
) {
725 /* the path of the tags file must be resolved to find the right files:
726 * definition source files are (in most cases) specified relatively inside
727 * the tags file to the tags files directory.
729 if(!ResolvePath(tagsFile
, resolvedTagsFile
)) {
734 if ((fp
= fopen(resolvedTagsFile
, "r")) == NULL
) {
738 ParseFilename(resolvedTagsFile
, NULL
, tagPath
);
740 /* Read the file and store its contents */
741 while (fgets(line
, MAXLINE
, fp
)) {
743 /* This might take a while if you have a huge tags file (like I do)..
744 keep the windows up to date and post a busy cursor so the user
745 doesn't think we died. */
747 AllWindowsBusy("Loading tags file...");
749 /* the first character in the file decides if the file is treat as
752 if(tagFileType
==TFT_CHECK
) {
753 if(line
[0]==12) /* <np> */
754 tagFileType
=TFT_ETAGS
;
756 tagFileType
=TFT_CTAGS
;
758 if(tagFileType
==TFT_CTAGS
) {
759 nTagsAdded
+= scanCTagsLine(line
, tagPath
, index
);
761 nTagsAdded
+= scanETagsLine(line
, tagPath
, index
, file
, recLevel
);
771 ** Given a tag name, lookup the file and path of the definition
772 ** and the proper search string. Returned strings are pointers
773 ** to internal storage which are valid until the next loadTagsFile call.
775 ** Invocation with name != NULL (containing the searched definition)
776 ** --> returns first definition of name
777 ** Successive invocation with name == NULL
778 ** --> returns further definitions (resulting from multiple tags files)
780 ** Return Value: TRUE: tag spec found
781 ** FALSE: no (more) definitions found.
783 #define TAG_STS_ERR_FMT "NEdit: Error getting status for tag file %s\n"
784 int LookupTag(const char *name
, const char **file
, int *language
,
785 const char **searchString
, int * pos
, const char **path
,
794 searchMode
= search_type
;
795 if (searchMode
== TIP
)
796 FileList
= TipsFileList
;
798 FileList
= TagsFileList
;
801 ** Go through the list of all tags Files:
802 ** - load them (if not already loaded)
803 ** - check for update of the tags file and reload it in that case
804 ** - save the modification date of the tags file
806 ** Do this only as long as name != NULL, not for sucessive calls
807 ** to find multiple tags specs.
810 for (tf
= FileList
; tf
&& name
; tf
= tf
->next
) {
812 if (stat(tf
->filename
,&statbuf
) != 0) { /* */
813 fprintf(stderr
, TAG_STS_ERR_FMT
, tf
->filename
);
815 if (tf
->date
== statbuf
.st_mtime
) {
816 /* current tags file tf is already loaded and up to date */
820 /* tags file has been modified, delete it's entries and reload it */
821 delTag(NULL
,NULL
,-2,NULL
,-2,tf
->index
);
823 /* If we get here we have to try to (re-) load the tags file */
824 if (FileList
== TipsFileList
)
825 load_status
= loadTipsFile(tf
->filename
, tf
->index
, 0);
827 load_status
= loadTagsFile(tf
->filename
, tf
->index
, 0);
829 if (stat(tf
->filename
,&statbuf
) != 0) {
831 /* if tf->loaded == 1 we already have seen the error msg */
832 fprintf(stderr
, TAG_STS_ERR_FMT
, tf
->filename
);
835 tf
->date
= statbuf
.st_mtime
;
843 t
= getTag(name
, search_type
);
849 *language
= t
->language
;
850 *searchString
= t
->searchString
;
858 ** This code path is followed if the request came from either
859 ** FindDefinition or FindDefCalltip. This should probably be refactored.
861 static int findDef(WindowInfo
*window
, const char *value
, int search_type
) {
862 static char tagText
[MAX_TAG_LEN
+ 1];
864 char message
[MAX_TAG_LEN
+40];
865 int l
, ml
, status
= 0;
867 searchMode
= search_type
;
869 if (l
<= MAX_TAG_LEN
) {
870 /* should be of type text??? */
871 for (p
= value
; *p
&& isascii(*p
); p
++) {
874 ml
= ((l
< MAX_TAG_LEN
) ? (l
) : (MAX_TAG_LEN
));
875 strncpy(tagText
, value
, ml
);
877 /* See if we can find the tip/tag */
878 status
= findAllMatches(window
, tagText
);
879 /* If we didn't find a requested calltip, see if we can use a tag */
880 if (status
== 0 && search_type
== TIP
&& TagsFileList
!= NULL
) {
881 searchMode
= TIP_FROM_TAG
;
882 status
= findAllMatches(window
, tagText
);
885 /* Didn't find any matches */
886 if (searchMode
== TIP_FROM_TAG
|| searchMode
== TIP
) {
887 sprintf(message
, "No match for \"%s\" in calltips or tags.",
889 tagsShowCalltip( window
, message
);
892 DialogF(DF_WARN
, window
->textArea
, 1, "Tags",
893 "\"%s\" not found in tags file%s", "OK", tagName
,
894 (TagsFileList
&& TagsFileList
->next
) ? "s" : "");
899 fprintf(stderr
, "NEdit: Can't handle non 8-bit text\n");
900 XBell(TheDisplay
, 0);
904 fprintf(stderr
, "NEdit: Tag Length too long.\n");
905 XBell(TheDisplay
, 0);
911 ** Lookup the definition for the current primary selection the currently
912 ** loaded tags file and bring up the file and line that the tags file
915 static void findDefinitionHelper(WindowInfo
*window
, Time time
, const char *arg
,
920 findDef(window
, arg
, search_type
);
924 searchMode
= search_type
;
925 XtGetSelectionValue(window
->textArea
, XA_PRIMARY
, XA_STRING
,
926 (XtSelectionCallbackProc
)findDefCB
, window
, time
);
933 void FindDefinition(WindowInfo
*window
, Time time
, const char *arg
)
935 findDefinitionHelper(window
, time
, arg
, TAG
);
941 void FindDefCalltip(WindowInfo
*window
, Time time
, const char *arg
)
943 /* Reset calltip parameters to reasonable defaults */
944 globAnchored
= False
;
946 globHAlign
= TIP_LEFT
;
947 globVAlign
= TIP_BELOW
;
948 globAlignMode
= TIP_SLOPPY
;
950 findDefinitionHelper(window
, time
, arg
, TIP
);
953 /* Callback function for FindDefinition */
954 static void findDefCB(Widget widget
, WindowInfo
*window
, Atom
*sel
,
955 Atom
*type
, char *value
, int *length
, int *format
)
957 /* skip if we can't get the selection data, or it's obviously too long */
958 if (*type
== XT_CONVERT_FAIL
|| value
== NULL
) {
959 XBell(TheDisplay
, 0);
961 findDef(window
, value
, searchMode
);
967 ** Try to display a calltip
968 ** anchored: If true, tip appears at position pos
969 ** lookup: If true, text is considered a key to be searched for in the
970 ** tip and/or tag database depending on search_type
971 ** search_type: Either TIP or TIP_FROM_TAG
973 int ShowTipString(WindowInfo
*window
, char *text
, Boolean anchored
,
974 int pos
, Boolean lookup
, int search_type
, int hAlign
, int vAlign
,
977 if (search_type
== TAG
) return 0;
979 /* So we don't have to carry all of the calltip alignment info around */
980 globAnchored
= anchored
;
984 globAlignMode
= alignMode
;
986 /* If this isn't a lookup request, just display it. */
988 return tagsShowCalltip(window
, text
);
990 return findDef(window
, text
, search_type
);
993 /* store all of the info into a pre-allocated tags struct */
994 static void setTag(tag
*t
, const char *name
, const char *file
,
995 int language
, const char *searchString
, int posInf
,
998 t
->name
= rcs_strdup(name
);
999 t
->file
= rcs_strdup(file
);
1000 t
->language
= language
;
1001 t
->searchString
= rcs_strdup(searchString
);
1003 t
->path
= rcs_strdup(path
);
1007 ** ctags search expressions are literal strings with a search direction flag,
1008 ** line starting "^" and ending "$" delimiters. This routine translates them
1009 ** into NEdit compatible regular expressions and does the search.
1010 ** Etags search expressions are plain literals strings, which
1012 ** If in_buffer is not NULL then it is searched instead of the window buffer.
1013 ** In this case in_buffer should be an XtMalloc allocated buffer and the
1014 ** caller is responsible for freeing it.
1016 static int fakeRegExSearch(WindowInfo
*window
, char *in_buffer
,
1017 const char *searchString
, int *startPos
, int *endPos
)
1019 int found
, searchStartPos
, dir
, ctagsMode
;
1020 char searchSubs
[3*MAXLINE
+3], *outPtr
;
1021 const char *fileString
, *inPtr
;
1023 if (in_buffer
== NULL
) {
1024 /* get the entire (sigh) text buffer from the text area widget */
1025 fileString
= BufAsString(window
->buffer
);
1027 fileString
= in_buffer
;
1030 /* determine search direction and start position */
1031 if (*startPos
!= -1) { /* etags mode! */
1032 dir
= SEARCH_FORWARD
;
1033 searchStartPos
= *startPos
;
1035 } else if (searchString
[0] == '/') {
1036 dir
= SEARCH_FORWARD
;
1039 } else if (searchString
[0] == '?') {
1040 dir
= SEARCH_BACKWARD
;
1041 /* searchStartPos = window->buffer->length; */
1042 searchStartPos
= strlen(fileString
);
1045 fprintf(stderr
, "NEdit: Error parsing tag file search string");
1049 /* Build the search regex. */
1052 inPtr
=searchString
+1; /* searchString[0] is / or ? --> search dir */
1054 /* If the first char is a caret then it's a RE line start delim */
1055 *outPtr
++ = *inPtr
++;
1057 } else { /* etags mode, no search dir spec, no leading caret */
1061 if( (*inPtr
=='\\' && inPtr
[1]=='/') ||
1062 (*inPtr
=='\r' && inPtr
[1]=='$' && !inPtr
[2])
1065 - escapes (added by standard and exuberant ctags) from slashes
1066 - literal CRs generated by standard ctags for DOSified sources
1069 } else if(strchr("()-[]<>{}.|^*+?&\\", *inPtr
)
1070 || (*inPtr
== '$' && (inPtr
[1]||(!ctagsMode
)))){
1071 /* Escape RE Meta Characters to match them literally.
1072 Don't escape $ if it's the last charcter of the search expr
1073 in ctags mode; always escape $ in etags mode.
1076 *outPtr
++ = *inPtr
++;
1077 } else if (isspace((unsigned char)*inPtr
)) { /* col. multiple spaces */
1081 do { inPtr
++ ; } while(isspace((unsigned char)*inPtr
));
1082 } else { /* simply copy all other characters */
1083 *outPtr
++ = *inPtr
++;
1086 *outPtr
=0; /* Terminate searchSubs */
1088 found
= SearchString(fileString
, searchSubs
, dir
, SEARCH_REGEX
,
1089 False
, searchStartPos
, startPos
, endPos
, NULL
, NULL
, NULL
);
1091 if(!found
&& !ctagsMode
) {
1092 /* position of the target definition could have been drifted before
1093 startPos, if nothing has been found by now try searching backward
1094 again from startPos.
1096 found
= SearchString(fileString
, searchSubs
, SEARCH_BACKWARD
,
1097 SEARCH_REGEX
, False
, searchStartPos
, startPos
, endPos
, NULL
,
1101 /* return the result */
1103 /* *startPos and *endPos are set in SearchString*/
1106 /* startPos, endPos left untouched by SearchString if search failed. */
1107 XBell(TheDisplay
, 0);
1112 /* Finds all matches and handles tag "collisions". Prompts user with a
1113 list of collided tags in the hash table and allows the user to select
1115 static int findAllMatches(WindowInfo
*window
, const char *string
)
1117 Widget dialogParent
= window
->textArea
;
1118 char filename
[MAXPATHLEN
], pathname
[MAXPATHLEN
];
1119 char temp
[32+2*MAXPATHLEN
+MAXLINE
];
1120 const char *fileToSearch
, *searchString
, *tagPath
;
1122 int startPos
, i
, pathMatch
=0, samePath
=0, langMode
, nMatches
=0;
1124 /* verify that the string is reasonable as a tag */
1125 if (*string
== '\0' || strlen(string
) > MAX_TAG_LEN
) {
1126 XBell(TheDisplay
, 0);
1131 /* First look up all of the matching tags */
1132 while (LookupTag(string
, &fileToSearch
, &langMode
, &searchString
, &startPos
,
1133 &tagPath
, searchMode
)) {
1134 /* Skip this tag if it has a language mode that doesn't match the
1135 current language mode, but don't skip anything if the window is in
1136 PLAIN_LANGUAGE_MODE. */
1137 if (window
->languageMode
!= PLAIN_LANGUAGE_MODE
&&
1138 GetPrefSmartTags() && langMode
!= PLAIN_LANGUAGE_MODE
&&
1139 langMode
!= window
->languageMode
) {
1143 if (*fileToSearch
== '/')
1144 strcpy(tagFiles
[nMatches
], fileToSearch
);
1146 sprintf(tagFiles
[nMatches
],"%s%s",tagPath
,fileToSearch
);
1147 strcpy(tagSearch
[nMatches
],searchString
);
1148 tagPosInf
[nMatches
]=startPos
;
1149 ParseFilename(tagFiles
[nMatches
], filename
, pathname
);
1150 /* Is this match in the current file? If so, use it! */
1151 if (GetPrefSmartTags() && !strcmp(window
->filename
,filename
)
1152 && !strcmp(window
->path
,pathname
) ) {
1154 strcpy(tagFiles
[0],tagFiles
[nMatches
]);
1155 strcpy(tagSearch
[0],tagSearch
[nMatches
]);
1156 tagPosInf
[0]=tagPosInf
[nMatches
];
1161 /* Is this match in the same dir. as the current file? */
1162 if (!strcmp(window
->path
,pathname
)) {
1166 if (++nMatches
>= MAXDUPTAGS
) {
1167 DialogF(DF_WARN
, dialogParent
, 1, "Tags",
1168 "Too many duplicate tags, first %d shown", "OK", MAXDUPTAGS
);
1171 /* Tell LookupTag to look for more definitions of the same tag: */
1175 /* Did we find any matches? */
1180 /* Only one of the matches is in the same dir. as this file. Use it. */
1181 if (GetPrefSmartTags() && samePath
== 1 && nMatches
> 1) {
1182 strcpy(tagFiles
[0],tagFiles
[pathMatch
]);
1183 strcpy(tagSearch
[0],tagSearch
[pathMatch
]);
1184 tagPosInf
[0]=tagPosInf
[pathMatch
];
1188 /* If all of the tag entries are the same file, just use the first.
1190 if (GetPrefSmartTags()) {
1191 for (i
=1; i
<nMatches
; i
++)
1192 if (strcmp(tagFiles
[i
],tagFiles
[i
-1]))
1199 if (!(dupTagsList
= (char **) malloc(sizeof(char *) * nMatches
))) {
1200 fprintf(stderr
, "NEdit: findDef(): out of heap space!\n");
1201 XBell(TheDisplay
, 0);
1204 for (i
=0; i
<nMatches
; i
++) {
1205 ParseFilename(tagFiles
[i
], filename
, pathname
);
1206 if ((i
<nMatches
-1 && !strcmp(tagFiles
[i
],tagFiles
[i
+1])) ||
1207 (i
>0 && !strcmp(tagFiles
[i
],tagFiles
[i
-1]))) {
1208 if(*(tagSearch
[i
]) && (tagPosInf
[i
] != -1)) { /* etags */
1209 sprintf(temp
,"%2d. %s%s %8i %s", i
+1, pathname
,
1210 filename
, tagPosInf
[i
], tagSearch
[i
]);
1211 } else if (*(tagSearch
[i
])) { /* ctags search expr */
1212 sprintf(temp
,"%2d. %s%s %s", i
+1, pathname
,
1213 filename
, tagSearch
[i
]);
1214 } else { /* line number only */
1215 sprintf(temp
,"%2d. %s%s %8i", i
+1, pathname
, filename
,
1219 sprintf(temp
,"%2d. %s%s",i
+1,pathname
,filename
);
1220 if (!(dupTagsList
[i
] = (char *) malloc(strlen(temp
) + 1))) {
1221 fprintf(stderr
, "NEdit: findDef(): out of heap space!\n");
1222 XBell(TheDisplay
, 0);
1225 strcpy(dupTagsList
[i
],temp
);
1227 createSelectMenu(dialogParent
, "Duplicate Tags", nMatches
, dupTagsList
);
1228 for (i
=0; i
<nMatches
; i
++)
1229 free(dupTagsList
[i
]);
1234 ** No need for a dialog list, there is only one tag matching --
1235 ** Go directly to the tag
1237 if (searchMode
== TAG
)
1238 editTaggedLocation( dialogParent
, 0 );
1240 showMatchingCalltip( dialogParent
, 0 );
1244 /* Callback function for the FindAll widget. Process the users response. */
1245 static void findAllCB(Widget parent
, XtPointer client_data
, XtPointer call_data
)
1250 XmSelectionBoxCallbackStruct
*cbs
=
1251 (XmSelectionBoxCallbackStruct
*) call_data
;
1252 if (cbs
->reason
== XmCR_NO_MATCH
)
1254 if (cbs
->reason
== XmCR_CANCEL
) {
1255 XtDestroyWidget(XtParent(parent
));
1259 XmStringGetLtoR(cbs
->value
,XmFONTLIST_DEFAULT_TAG
,&eptr
);
1260 if ((i
= atoi(eptr
)-1) < 0) {
1261 XBell(TheDisplay
, 0);
1265 if (searchMode
== TAG
)
1266 editTaggedLocation( parent
, i
); /* Open the file with the definition */
1268 showMatchingCalltip( parent
, i
);
1270 if (cbs
->reason
== XmCR_OK
)
1271 XtDestroyWidget(XtParent(parent
));
1274 /* Window manager close-box callback for tag-collision dialog */
1275 static void findAllCloseCB(Widget parent
, XtPointer client_data
,
1276 XtPointer call_data
)
1278 XtDestroyWidget(parent
);
1282 * Given a \0 terminated string and a position, advance the position
1283 * by n lines, where line separators (for now) are \n. If the end of
1284 * string is reached before n lines, return the number of lines advanced,
1285 * else normally return -1.
1287 static int moveAheadNLines( char *str
, int *pos
, int n
) {
1289 while (str
[*pos
] != '\0' && n
>0) {
1290 if (str
[*pos
] == '\n')
1301 ** Show the calltip specified by tagFiles[i], tagSearch[i], tagPosInf[i]
1302 ** This reads from either a source code file (if searchMode == TIP_FROM_TAG)
1303 ** or a calltips file (if searchMode == TIP).
1305 static void showMatchingCalltip( Widget parent
, int i
)
1307 int startPos
=0, fileLen
, readLen
, tipLen
;
1311 struct stat statbuf
;
1314 /* 1. Open the target file */
1315 NormalizePathname(tagFiles
[i
]);
1316 fp
= fopen(tagFiles
[i
], "r");
1318 DialogF(DF_ERR
, parent
, 1, "Error opening File", "Error opening %s",
1322 if (fstat(fileno(fp
), &statbuf
) != 0) {
1324 DialogF(DF_ERR
, parent
, 1, "Error opening File", "Error opening %s",
1329 /* 2. Read the target file */
1330 /* Allocate space for the whole contents of the file (unfortunately) */
1331 fileLen
= statbuf
.st_size
;
1332 fileString
= XtMalloc(fileLen
+1); /* +1 = space for null */
1333 if (fileString
== NULL
) {
1335 DialogF(DF_ERR
, parent
, 1, "File too large",
1336 "File is too large to load", "OK");
1340 /* Read the file into fileString and terminate with a null */
1341 readLen
= fread(fileString
, sizeof(char), fileLen
, fp
);
1344 DialogF(DF_ERR
, parent
, 1, "Error reading File", "Error reading %s",
1349 fileString
[readLen
] = 0;
1351 /* Close the file */
1352 if (fclose(fp
) != 0) {
1353 /* unlikely error */
1354 DialogF(DF_WARN
, parent
, 1, "Error closing File",
1355 "Unable to close file", "OK");
1356 /* we read it successfully, so continue */
1359 /* 3. Search for the tagged location (set startPos) */
1360 if (!*(tagSearch
[i
])) {
1361 /* It's a line number, just go for it */
1362 if ((moveAheadNLines( fileString
, &startPos
, tagPosInf
[i
]-1 )) >= 0) {
1363 DialogF(DF_ERR
, parent
, 1, "Tags Error",
1364 "%s\n not long enough for definition to be on line %d",
1365 "OK", tagFiles
[i
], tagPosInf
[i
]);
1370 startPos
= tagPosInf
[i
];
1371 if(!fakeRegExSearch(WidgetToWindow(parent
), fileString
, tagSearch
[i
],
1372 &startPos
, &endPos
)){
1373 DialogF(DF_WARN
, parent
, 1, "Tag not found",
1374 "Definition for %s\nnot found in %s", "OK", tagName
,
1381 if (searchMode
== TIP
) {
1384 /* 4. Find the end of the calltip (delimited by an empty line) */
1386 found
= SearchString(fileString
, "\\n\\s*\\n", SEARCH_FORWARD
,
1387 SEARCH_REGEX
, False
, startPos
, &endPos
, &dummy
, NULL
,
1390 /* Just take 4 lines */
1391 moveAheadNLines( fileString
, &endPos
, TIP_DEFAULT_LINES
);
1392 --endPos
; /* Lose the last \n */
1394 } else { /* Mode = TIP_FROM_TAG */
1395 /* 4. Copy TIP_DEFAULT_LINES lines of text to the calltip string */
1397 moveAheadNLines( fileString
, &endPos
, TIP_DEFAULT_LINES
);
1398 /* Make sure not to overrun the fileString with ". . ." */
1399 if (((size_t) endPos
) <= (strlen(fileString
)-5)) {
1400 sprintf( &fileString
[endPos
], ". . ." );
1404 /* 5. Copy the calltip to a string */
1405 tipLen
= endPos
- startPos
;
1406 message
= XtMalloc(tipLen
+1); /* +1 = space for null */
1407 if (message
== NULL
)
1409 DialogF(DF_ERR
, parent
, 1, "Out of Memory",
1410 "Can't allocate memory for calltip message", "OK");
1414 strncpy( message
, &fileString
[startPos
], tipLen
);
1415 message
[tipLen
] = 0;
1418 tagsShowCalltip( WidgetToWindow(parent
), message
);
1423 /* Open a new (or existing) editor window to the location specified in
1424 tagFiles[i], tagSearch[i], tagPosInf[i] */
1425 static void editTaggedLocation( Widget parent
, int i
)
1427 /* Globals: tagSearch, tagPosInf, tagFiles, tagName, textNrows,
1429 int startPos
, endPos
, lineNum
, rows
;
1430 char filename
[MAXPATHLEN
], pathname
[MAXPATHLEN
];
1431 WindowInfo
*windowToSearch
;
1432 WindowInfo
*parentWindow
= WidgetToWindow(parent
);
1434 ParseFilename(tagFiles
[i
],filename
,pathname
);
1435 /* open the file containing the definition */
1436 EditExistingFile(parentWindow
, filename
, pathname
, 0, NULL
, False
,
1437 NULL
, GetPrefOpenInTab(), False
);
1438 windowToSearch
= FindWindowWithFile(filename
, pathname
);
1439 if (windowToSearch
== NULL
) {
1440 DialogF(DF_WARN
, parent
, 1, "File not found", "File %s not found", "OK",
1445 startPos
=tagPosInf
[i
];
1447 if(!*(tagSearch
[i
])) {
1448 /* if the search string is empty, select the numbered line */
1449 SelectNumberedLine(windowToSearch
, startPos
);
1453 /* search for the tags file search string in the newly opened file */
1454 if(!fakeRegExSearch(windowToSearch
, NULL
, tagSearch
[i
], &startPos
,
1456 DialogF(DF_WARN
, windowToSearch
->shell
, 1, "Tag Error",
1457 "Definition for %s\nnot found in %s", "OK", tagName
,
1462 /* select the matched string */
1463 BufSelect(windowToSearch
->buffer
, startPos
, endPos
);
1464 RaiseFocusDocumentWindow(windowToSearch
, True
);
1466 /* Position it nicely in the window,
1467 about 1/4 of the way down from the top */
1468 lineNum
= BufCountLines(windowToSearch
->buffer
, 0, startPos
);
1469 XtVaGetValues(windowToSearch
->lastFocus
, textNrows
, &rows
, NULL
);
1470 TextSetScroll(windowToSearch
->lastFocus
, lineNum
- rows
/4, 0);
1471 TextSetCursorPos(windowToSearch
->lastFocus
, endPos
);
1474 /* Create a Menu for user to select from the collided tags */
1475 static Widget
createSelectMenu(Widget parent
, char *label
, int nArgs
,
1482 XmString popupTitle
;
1486 list
= (XmStringTable
) XtMalloc(nArgs
* sizeof(XmString
*));
1487 for (i
=0; i
<nArgs
; i
++)
1488 list
[i
] = XmStringCreateSimple(args
[i
]);
1489 sprintf(tmpStr
,"Select File With TAG: %s",tagName
);
1490 popupTitle
= XmStringCreateSimple(tmpStr
);
1492 XtSetArg(csdargs
[ac
], XmNlistLabelString
, popupTitle
); ac
++;
1493 XtSetArg(csdargs
[ac
], XmNlistItems
, list
); ac
++;
1494 XtSetArg(csdargs
[ac
], XmNlistItemCount
, nArgs
); ac
++;
1495 XtSetArg(csdargs
[ac
], XmNvisibleItemCount
, 12); ac
++;
1496 XtSetArg(csdargs
[ac
], XmNautoUnmanage
, False
); ac
++;
1497 menu
= CreateSelectionDialog(parent
,label
,csdargs
,ac
);
1498 XtUnmanageChild(XmSelectionBoxGetChild(menu
, XmDIALOG_TEXT
));
1499 XtUnmanageChild(XmSelectionBoxGetChild(menu
, XmDIALOG_HELP_BUTTON
));
1500 XtUnmanageChild(XmSelectionBoxGetChild(menu
, XmDIALOG_SELECTION_LABEL
));
1501 XtAddCallback(menu
, XmNokCallback
, (XtCallbackProc
)findAllCB
, menu
);
1502 XtAddCallback(menu
, XmNapplyCallback
, (XtCallbackProc
)findAllCB
, menu
);
1503 XtAddCallback(menu
, XmNcancelCallback
, (XtCallbackProc
)findAllCB
, menu
);
1504 AddMotifCloseCallback(XtParent(menu
), findAllCloseCB
, NULL
);
1505 for (i
=0; i
<nArgs
; i
++)
1506 XmStringFree(list
[i
]);
1507 XtFree((char *)list
);
1508 XmStringFree(popupTitle
);
1509 ManageDialogCenteredOnPointer(menu
);
1515 /*--------------------------------------------------------------------------
1517 Reference-counted string hack; SJT 4/2000
1519 This stuff isn't specific to tags, so it should be in it's own file.
1520 However, I'm leaving it in here for now to reduce the diffs.
1522 This could really benefit from using a real hash table.
1525 #define RCS_SIZE 10000
1531 int talloc
, tshar
, tgiveup
, tbytes
, tbyteshared
;
1541 static struct rcs
*Rcs
[RCS_SIZE
];
1542 static struct rcs_stats RcsStats
;
1545 ** Take a normal string, create a shared string from it if need be,
1546 ** and return pointer to that shared string.
1548 ** Returned strings are const because they are shared. Do not modify them!
1551 static const char *rcs_strdup(const char *str
)
1556 struct rcs
*prev
= NULL
;
1558 char *newstr
= NULL
;
1563 bucket
= hashAddr(str
) % RCS_SIZE
;
1569 /* Don't share if it won't save space.
1571 Doesn't save anything - if we have lots of small-size objects,
1572 it's beneifical to share them. We don't know until we make a full
1573 count. My tests show that it's better to leave this out. */
1574 if (len
<= sizeof(struct rcs
))
1576 new_str
= strdup(str
); /* GET RID OF strdup() IF EVER ENABLED (not ANSI) */
1582 /* Find it in hash */
1583 for (rp
= Rcs
[bucket
]; rp
; rp
= rp
->next
)
1585 if (!strcmp(str
, rp
->string
))
1590 if (rp
) /* It exists, return it and bump ref ct */
1593 newstr
= rp
->string
;
1596 RcsStats
.tbyteshared
+= len
;
1598 else /* Doesn't exist, conjure up a new one. */
1600 struct rcs
*newrcs
= malloc(sizeof(struct rcs
));
1601 newrcs
->string
= malloc(len
+1);
1602 strcpy(newrcs
->string
, str
);
1604 newrcs
->next
= NULL
;
1607 prev
->next
= newrcs
;
1609 Rcs
[bucket
] = newrcs
;
1611 newstr
= newrcs
->string
;
1614 RcsStats
.tbytes
+= len
;
1619 ** Decrease the reference count on a shared string. When the reference
1620 ** count reaches zero, free the master string.
1623 static void rcs_free(const char *rcs_str
)
1627 struct rcs
*prev
= NULL
;
1629 if (rcs_str
== NULL
)
1632 bucket
= hashAddr(rcs_str
) % RCS_SIZE
;
1634 /* find it in hash */
1635 for (rp
= Rcs
[bucket
]; rp
; rp
= rp
->next
)
1637 if (rcs_str
== rp
->string
)
1642 if (rp
) /* It's a shared string, decrease ref count */
1646 if (rp
->usage
< 0) /* D'OH! */
1648 fprintf(stderr
, "NEdit: internal error deallocating shared string.");
1652 if (rp
->usage
== 0) /* Last one- free the storage */
1656 prev
->next
= rp
->next
;
1658 Rcs
[bucket
] = rp
->next
;
1662 else /* Doesn't appear to be a shared string */
1664 fprintf(stderr
, "NEdit: attempt to free a non-shared string.");
1669 /********************************************************************
1670 * Functions for loading Calltips files *
1671 ********************************************************************/
1673 enum tftoken_types
{ TF_EOF
, TF_BLOCK
, TF_VERSION
, TF_INCLUDE
, TF_LANGUAGE
,
1674 TF_ALIAS
, TF_ERROR
, TF_ERROR_EOF
};
1676 /* A wrapper for SearchString */
1677 static int searchLine(char *line
, const char *regex
) {
1679 return SearchString(line
, regex
, SEARCH_FORWARD
, SEARCH_REGEX
,
1680 False
, 0, &dummy1
, &dummy2
, NULL
, NULL
, NULL
);
1683 /* Check if a line has non-ws characters */
1684 static Boolean
lineEmpty(const char *line
) {
1685 while (*line
&& *line
!= '\n') {
1686 if (*line
!= ' ' && *line
!= '\t')
1693 /* Remove trailing whitespace from a line */
1694 static void rstrip( char *dst
, const char *src
) {
1695 int wsStart
, dummy2
;
1696 /* Strip trailing whitespace */
1697 if(SearchString(src
, "\\s*\\n", SEARCH_FORWARD
, SEARCH_REGEX
,
1698 False
, 0, &wsStart
, &dummy2
, NULL
, NULL
, NULL
)) {
1700 memcpy(dst
, src
, wsStart
);
1708 ** Get the next block from a tips file. A block is a \n\n+ delimited set of
1709 ** lines in a calltips file. All of the parameters except <fp> are return
1710 ** values, and most have different roles depending on the type of block
1712 ** header: Depends on the block type
1713 ** body: Depends on the block type. Used to return a new
1714 ** dynamically allocated string.
1715 ** blkLine: Returns the line number of the first line of the block
1716 ** after the "* xxxx *" line.
1717 ** currLine: Used to keep track of the current line in the file.
1719 static int nextTFBlock(FILE *fp
, char *header
, char **body
, int *blkLine
,
1722 /* These are the different kinds of tokens */
1723 const char *commenTF_regex
= "^\\s*\\* comment \\*\\s*$";
1724 const char *version_regex
= "^\\s*\\* version \\*\\s*$";
1725 const char *include_regex
= "^\\s*\\* include \\*\\s*$";
1726 const char *language_regex
= "^\\s*\\* language \\*\\s*$";
1727 const char *alias_regex
= "^\\s*\\* alias \\*\\s*$";
1728 char line
[MAXLINE
], *status
;
1732 /* Skip blank lines and comments */
1734 /* Skip blank lines */
1735 while((status
=fgets(line
, MAXLINE
, fp
))) {
1737 if(!lineEmpty( line
))
1741 /* Check for error or EOF */
1745 /* We've got a non-blank line -- is it a comment block? */
1746 if( !searchLine(line
, commenTF_regex
) )
1749 /* Skip the comment (non-blank lines) */
1750 while((status
=fgets(line
, MAXLINE
, fp
))) {
1752 if(lineEmpty( line
))
1760 /* Now we know it's a meaningful block */
1761 dummy1
= searchLine(line
, include_regex
);
1762 if( dummy1
|| searchLine(line
, alias_regex
) ) {
1763 /* INCLUDE or ALIAS block */
1764 int incLen
, incPos
, i
, incLines
;
1766 /* fprintf(stderr, "Starting include/alias at line %i\n", *currLine); */
1771 /* Need to read the header line for an alias */
1772 status
=fgets(line
, MAXLINE
, fp
);
1775 return TF_ERROR_EOF
;
1776 if (lineEmpty( line
)) {
1777 fprintf( stderr
, "nedit: Warning: empty '* alias *' "
1778 "block in calltips file.\n" );
1781 rstrip(header
, line
);
1784 *blkLine
= *currLine
+ 1; /* Line of first actual filename/alias */
1787 /* Figure out how long the block is */
1788 while((status
=fgets(line
, MAXLINE
, fp
))) {
1790 if(lineEmpty( line
))
1793 incLen
= ftell(fp
) - incPos
;
1794 incLines
= *currLine
- *blkLine
;
1795 /* Correct currLine for the empty line it read at the end */
1797 if (incLines
== 0) {
1798 fprintf( stderr
, "nedit: Warning: empty '* include *' or"
1799 " '* alias *' block in calltips file.\n" );
1802 /* Make space for the filenames/alias sources */
1803 *body
= (char *)malloc(incLen
+1);
1807 if (fseek(fp
, incPos
, SEEK_SET
) != 0) {
1811 /* Read all the lines in the block */
1812 /* fprintf(stderr, "Copying lines\n"); */
1813 for (i
=0; i
<incLines
; i
++) {
1814 status
= fgets(line
, MAXLINE
, fp
);
1817 return TF_ERROR_EOF
;
1822 strcat(*body
, line
);
1824 /* fprintf(stderr, "Finished include/alias at line %i\n", *currLine); */
1827 else if( searchLine(line
, language_regex
) ) {
1828 /* LANGUAGE block */
1829 status
=fgets(line
, MAXLINE
, fp
);
1832 return TF_ERROR_EOF
;
1833 if (lineEmpty( line
)) {
1834 fprintf( stderr
, "nedit: Warning: empty '* language *' block in calltips file.\n" );
1837 *blkLine
= *currLine
;
1838 rstrip(header
, line
);
1842 else if( searchLine(line
, version_regex
) ) {
1844 status
=fgets(line
, MAXLINE
, fp
);
1847 return TF_ERROR_EOF
;
1848 if (lineEmpty( line
)) {
1849 fprintf( stderr
, "nedit: Warning: empty '* version *' block in calltips file.\n" );
1852 *blkLine
= *currLine
;
1853 rstrip(header
, line
);
1859 /* The first line is the key, the rest is the tip.
1860 Strip trailing whitespace. */
1861 rstrip(header
, line
);
1863 status
=fgets(line
, MAXLINE
, fp
);
1866 return TF_ERROR_EOF
;
1867 if (lineEmpty( line
)) {
1868 fprintf( stderr
, "nedit: Warning: empty calltip block:\n"
1869 " \"%s\"\n", header
);
1872 *blkLine
= *currLine
;
1873 *body
= strdup(line
);
1877 /* Skip the rest of the block */
1879 while(fgets(line
, MAXLINE
, fp
)) {
1881 if (lineEmpty( line
))
1885 /* Warn about any unneeded extra lines (which are ignored). */
1886 if (dummy1
+1 < *currLine
&& code
!= TF_BLOCK
) {
1887 fprintf( stderr
, "nedit: Warning: extra lines in language or version block ignored.\n" );
1893 /* A struct for describing a calltip alias */
1894 typedef struct _alias
{
1897 struct _alias
*next
;
1901 ** Allocate a new alias, copying dest and stealing sources. This may
1902 ** seem strange but that's the way it's called
1904 static tf_alias
*new_alias(const char *dest
, char *sources
) {
1907 /* fprintf(stderr, "new_alias: %s <- %s\n", dest, sources); */
1908 /* Allocate the alias */
1909 alias
= (tf_alias
*)malloc( sizeof(tf_alias
) );
1914 alias
->dest
= (char*)malloc( strlen(dest
)+1 );
1917 strcpy( alias
->dest
, dest
);
1918 alias
->sources
= sources
;
1922 /* Deallocate a linked-list of aliases */
1923 static void free_alias_list(tf_alias
*alias
) {
1924 tf_alias
*tmp_alias
;
1926 tmp_alias
= alias
->next
;
1928 free(alias
->sources
);
1935 ** Load a calltips file and insert all of the entries into the global tips
1936 ** database. Each tip is essentially stored as its filename and the line
1937 ** at which it appears--the exact same way ctags indexes source-code. That's
1938 ** why calltips and tags share so much code.
1940 static int loadTipsFile(const char *tipsFile
, int index
, int recLevel
)
1943 char header
[MAXLINE
];
1944 char *body
, *tipIncFile
;
1945 char tipPath
[MAXPATHLEN
];
1946 char resolvedTipsFile
[MAXPATHLEN
+1];
1947 int nTipsAdded
=0, langMode
= PLAIN_LANGUAGE_MODE
, oldLangMode
;
1948 int currLine
=0, code
, blkLine
;
1949 tf_alias
*aliases
=NULL
, *tmp_alias
;
1951 if(recLevel
> MAX_TAG_INCLUDE_RECURSION_LEVEL
) {
1952 fprintf(stderr
, "nedit: Warning: Reached recursion limit before loading calltips file:\n\t%s\n", tipsFile
);
1956 /* find the tips file */
1958 /* Allow ~ in Unix filenames */
1959 strncpy(tipPath
, tipsFile
, MAXPATHLEN
); /* ExpandTilde is destructive */
1960 ExpandTilde(tipPath
);
1961 if(!ResolvePath(tipPath
, resolvedTipsFile
))
1964 if(!ResolvePath(tipsFile
, resolvedTipsFile
))
1968 /* Get the path to the tips file */
1969 ParseFilename(resolvedTipsFile
, NULL
, tipPath
);
1972 if ((fp
= fopen(resolvedTipsFile
, "r")) == NULL
)
1976 code
= nextTFBlock(fp
, header
, &body
, &blkLine
, &currLine
);
1977 if( code
== TF_ERROR_EOF
) {
1978 fprintf(stderr
,"nedit: Warning: unexpected EOF in calltips file.\n");
1981 if( code
== TF_EOF
)
1986 /* Add the calltip to the global hash table.
1987 For the moment I'm just using line numbers because I don't
1988 want to have to deal with adding escape characters for
1989 regex metacharacters that might appear in the string */
1990 nTipsAdded
+= addTag(header
, resolvedTipsFile
, langMode
, "",
1991 blkLine
, tipPath
, index
);
1995 /* nextTFBlock returns a colon-separated list of tips files
1997 for(tipIncFile
=strtok(body
,":"); tipIncFile
;
1998 tipIncFile
=strtok(NULL
,":")) {
2000 "nedit: DEBUG: including tips file '%s'\n",
2002 nTipsAdded
+= loadTipsFile( tipIncFile
, index
, recLevel
+1);
2007 /* Switch to the new language mode if it's valid, else ignore
2009 oldLangMode
= langMode
;
2010 langMode
= FindLanguageMode( header
);
2011 if (langMode
== PLAIN_LANGUAGE_MODE
&&
2012 strcmp(header
, "Plain")) {
2014 "nedit: Error reading calltips file:\n\t%s\n"
2015 "Unknown language mode: \"%s\"\n",
2017 langMode
= oldLangMode
;
2021 fprintf(stderr
,"nedit: Warning: Recoverable error while "
2022 "reading calltips file:\n \"%s\"\n",
2026 /* Allocate a new alias struct */
2027 tmp_alias
= aliases
;
2028 aliases
= new_alias(header
, body
);
2030 fprintf(stderr
,"nedit: Can't allocate memory for tipfile "
2031 "alias in calltips file:\n \"%s\"\n",
2033 /* Deallocate any allocated aliases */
2034 free_alias_list(tmp_alias
);
2037 /* Add it to the list */
2038 aliases
->next
= tmp_alias
;
2041 ;/* Ignore TF_VERSION for now */
2045 /* Now resolve any aliases */
2046 tmp_alias
= aliases
;
2050 t
= getTag(tmp_alias
->dest
, TIP
);
2052 fprintf(stderr
, "nedit: Can't find destination of alias \"%s\"\n"
2053 " in calltips file:\n \"%s\"\n",
2054 tmp_alias
->dest
, resolvedTipsFile
);
2056 for(src
=strtok(tmp_alias
->sources
,":"); src
; src
=strtok(NULL
,":"))
2057 addTag(src
, resolvedTipsFile
, t
->language
, "", t
->posInf
,
2060 tmp_alias
= tmp_alias
->next
;
2062 free_alias_list(aliases
);