UNIXworkcode

/******************************************************************************* * * * tags.c -- Nirvana editor tag file handling * * * * Copyright (C) 1999 Mark Edel * * * * This is free software; you can redistribute it and/or modify it under the * * terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. In addition, you may distribute versions of this program linked to * * Motif or Open Motif. See README for details. * * * * This software is distributed in the hope that it will be useful, but WITHOUT * * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * * for more details. * * * * You should have received a copy of the GNU General Public License along with * * software; if not, write to the Free Software Foundation, Inc., 59 Temple * * Place, Suite 330, Boston, MA 02111-1307 USA * * * * Nirvana Text Editor * * July, 1993 * * * * Written by Mark Edel * * * *******************************************************************************/ #ifdef HAVE_CONFIG_H #include "../config.h" #endif #include "tags.h" #include "textBuf.h" #include "text.h" #include "nedit.h" #include "window.h" #include "file.h" #include "preferences.h" #include "search.h" #include "selection.h" #include "calltips.h" #include "../util/DialogF.h" #include "../util/fileUtils.h" #include "../util/misc.h" #include "../util/utils.h" #include "../util/refString.h" #include "../util/nedit_malloc.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <sys/param.h> #include <Xm/PrimitiveP.h> /* For Calltips */ #include <Xm/Xm.h> #include <Xm/SelectioB.h> #include <X11/Xatom.h> #ifdef HAVE_DEBUG_H #include "../debug.h" #endif #define MAXLINE 2048 #define MAX_TAG_LEN 256 #define MAXDUPTAGS 100 #define MAX_TAG_INCLUDE_RECURSION_LEVEL 5 /* Take this many lines when making a tip from a tag. (should probably be a language-dependent option, but...) */ #define TIP_DEFAULT_LINES 4 #define STRSAVE(a) (NEditStrdup(a!=NULL?a:"")) typedef struct _tag { struct _tag *next; const char *path; const char *name; const char *file; const char *searchString; /* see comment below */ int posInf; /* see comment below */ short language; short index; } tag; /* ** contents of tag->searchString | tag->posInf ** ctags, line num specified: "" | line num ** ctags, search expr specfd: ctags search expr | -1 ** etags (emacs tags) etags search string | search start pos */ enum searchDirection {FORWARD, BACKWARD}; static int loadTagsFile(const char *tagSpec, int index, int recLevel); static void findDefCB(Widget widget, XtPointer closure, Atom *sel, Atom *type, XtPointer value, unsigned long *length, int *format); static void setTag(tag *t, const char *name, const char *file, int language, const char *searchString, int posInf, const char * tag); static int fakeRegExSearch(WindowInfo *window, char *buffer, const char *searchString, int *startPos, int *endPos); static void updateMenuItems(void); static int addTag(const char *name, const char *file, int lang, const char *search, int posInf, const char *path, int index); static int delTag(const char *name, const char *file, int lang, const char *search, int posInf, int index); static tag *getTag(const char *name, int search_type); static int findDef(WindowInfo *window, const char *value, int search_type); static int findAllMatches(WindowInfo *window, const char *string); static void findAllCB(Widget parent, XtPointer client_data, XtPointer call_data); static Widget createSelectMenu(Widget parent, char *label, int nArgs, char *args[]); static void editTaggedLocation( Widget parent, int i ); static void showMatchingCalltip( Widget parent, int i ); static int searchLine(char *line, const char *regex); static void rstrip( char *dst, const char *src ); static int nextTFBlock(FILE *fp, char *header, char **tiptext, int *lineAt, int *lineNo); static int loadTipsFile(const char *tipsFile, int index, int recLevel); /* Hash table of tags, implemented as an array. Each bin contains a NULL-terminated linked list of parsed tags */ static tag **Tags = NULL; static unsigned DefTagHashSize = 100000u; /* list of loaded tags files */ tagFile *TagsFileList = NULL; /* Hash table of calltip tags */ static tag **Tips = NULL; tagFile *TipsFileList = NULL; /* These are all transient global variables -- they don't hold any state between tag/tip lookups */ static int searchMode = TAG; static const char *tagName; static char tagFiles[MAXDUPTAGS][MAXPATHLEN]; static char tagSearch[MAXDUPTAGS][MAXPATHLEN]; static int tagPosInf[MAXDUPTAGS]; static Boolean globAnchored; static int globPos; static int globHAlign; static int globVAlign; static int globAlignMode; /* A wrapper for calling TextDShowCalltip */ static int tagsShowCalltip( WindowInfo *window, char *text ) { if (text) return ShowCalltip( window, text, globAnchored, globPos, globHAlign, globVAlign, globAlignMode); else return 0; } /* Set the head of the proper file list (Tags or Tips) to t */ static tagFile *setFileListHead(tagFile *t, int file_type ) { if (file_type == TAG) TagsFileList = t; else TipsFileList = t; return t; } /* Retrieve a tag structure from the hash table */ static tag *getTag(const char *name, int search_type) { static char lastName[MAXLINE]; static tag *t = NULL; tag **table; if (search_type == TIP) table = Tips; else table = Tags; if (table == NULL) return NULL; if (name) { unsigned const addr = StringHashAddr(name) % DefTagHashSize; t = table[addr]; strcpy(lastName,name); } else if (t) { name = lastName; t = t->next; } else return NULL; for (;t; t = t->next) if (!strcmp(name,t->name)) return t; return NULL; } /* ** Searches for `tpl` tag in the list `bkt` */ static tag* matchTagRec(tag const* tpl, tag* bkt) { /* pointers comparison can be used instead of strcmp because strings are made shared by setTag() */ tag *t = bkt; for (; t; t = t->next) if ((tpl->language == t->language) && (tpl->posInf == t->posInf) && (tpl->name == t->name) && (tpl->searchString == t->searchString) && (tpl->file == t->file)) return t; return NULL; } /* Add a tag specification to the hash table ** Return Value: 0 ... tag already existing, spec not added ** 1 ... tag spec is new, added. ** (We don't return boolean as the return value is used as counter increment!) ** */ static int addTag(const char *name, const char *file, int lang, const char *search, int posInf, const char *path, int index) { char newfile[MAXPATHLEN]; tag **table; if (searchMode == TIP) { if (Tips == NULL) Tips = (tag **)NEditCalloc(DefTagHashSize, sizeof(tag*)); table = Tips; } else { if (Tags == NULL) Tags = (tag **)NEditCalloc(DefTagHashSize, sizeof(tag*)); table = Tags; } if (*file == '/') strncpy(newfile, file, MAXPATHLEN); else snprintf(newfile, MAXPATHLEN, "%s%s", path, file); newfile[MAXPATHLEN - 1] = '\0'; NormalizePathname(newfile); { tag tpl; setTag(&tpl, name, newfile, lang, search, posInf, path); { unsigned const addr = StringHashAddr(tpl.name) % DefTagHashSize; tag *t = matchTagRec(&tpl, table[addr]); if (t) return 0; t = NEditNew(tag); *t = tpl; t->index = index; t->next = table[addr]; table[addr] = t; } } return 1; } /* Delete a tag from the cache. * Search is limited to valid matches of 'name','file', 'search', posInf, and 'index'. * EX: delete all tags matching index 2 ==> * delTag(tagname,NULL,-2,NULL,-2,2); * (posInf = -2 is an invalid match, posInf range: -1 .. +MAXINT, lang = -2 is also an invalid match) */ static int delTag(const char *name, const char *file, int lang, const char *search, int posInf, int index) { tag *t, *last; int start,finish,i,del=0; tag **table; if (searchMode == TIP) table = Tips; else table = Tags; if (table == NULL) return FALSE; if (name) start = finish = StringHashAddr(name) % DefTagHashSize; else { start = 0; finish = DefTagHashSize; } for (i = start; i<finish; i++) { for (last = NULL, t = table[i]; t; last = t, t = t?t->next:table[i]) { if (index && index != t->index) continue; if (posInf != t->posInf) continue; if (lang >= PLAIN_LANGUAGE_MODE && lang != t->language) continue; if (name && strcmp(name,t->name)) continue; if (file && strcmp(file,t->file)) continue; if (search && strcmp(search,t->searchString)) continue; if (last) last->next = t->next; else table[i] = t->next; RefStringFree(t->name); RefStringFree(t->file); RefStringFree(t->searchString); RefStringFree(t->path); NEditFree(t); t = NULL; del++; } } return del>0; } /* used in AddRelTagsFile and AddTagsFile */ static int tagFileIndex = 0; /* ** AddRelTagsFile(): Rescan tagSpec for relative tag file specs ** (not starting with [/~]) and extend the tag files list if in ** windowPath a tags file matching the relative spec has been found. */ int AddRelTagsFile(const char *tagSpec, const char *windowPath, int file_type) { tagFile *t; int added=0; struct stat statbuf; char *filename; char pathName[MAXPATHLEN]; char *tmptagSpec; tagFile *FileList; searchMode = file_type; if (searchMode == TAG) FileList = TagsFileList; else FileList = TipsFileList; tmptagSpec = NEditStrdup(tagSpec); for (filename = strtok(tmptagSpec, ":"); filename; filename = strtok(NULL, ":")){ if (*filename == '/' || *filename == '~') continue; if (windowPath && *windowPath) { strcpy(pathName, windowPath); } else { strcpy(pathName, GetCurrentDir()); } strcat(pathName, "/"); strcat(pathName, filename); NormalizePathname(pathName); for (t = FileList; t && strcmp(t->filename, pathName); t = t->next); if (t) { added=1; continue; } if (stat(pathName, &statbuf) != 0) continue; t = (tagFile *) NEditMalloc(sizeof(tagFile)); t->filename = STRSAVE(pathName); t->loaded = 0; t->date = statbuf.st_mtime; t->index = ++tagFileIndex; t->next = FileList; FileList = setFileListHead(t, file_type); added=1; } NEditFree(tmptagSpec); updateMenuItems(); if (added) return TRUE; else return FALSE; } /* ** AddTagsFile(): Set up the the list of tag files to manage from a file spec. ** The file spec comes from the X-Resource Nedit.tags: It can list multiple ** tags files, specified by separating them with colons. The .Xdefaults would ** look like this: ** Nedit.tags: <tagfile1>:<tagfile2> ** Returns True if all files were found in the FileList or loaded successfully, ** FALSE otherwise. */ int AddTagsFile(const char *tagSpec, int file_type) { tagFile *t; int added=1; struct stat statbuf; char *filename; char pathName[MAXPATHLEN]; char *tmptagSpec; tagFile *FileList; /* To prevent any possible segfault */ if (tagSpec == NULL) { fprintf(stderr, "xnedit: Internal Error!\n" " Passed NULL pointer to AddTagsFile!\n"); return FALSE; } searchMode = file_type; if (searchMode == TAG) FileList = TagsFileList; else FileList = TipsFileList; tmptagSpec = NEditStrdup(tagSpec); for (filename = strtok(tmptagSpec,":"); filename; filename = strtok(NULL,":")) { if (*filename != '/') { strcpy(pathName, GetCurrentDir()); strcat(pathName,"/"); strcat(pathName,filename); } else { strcpy(pathName,filename); } NormalizePathname(pathName); for (t = FileList; t && strcmp(t->filename,pathName); t = t->next); if (t) { /* This file is already in the list. It's easiest to just refcount all tag/tip files even though we only actually care about tip files. */ ++(t->refcount); added=1; continue; } if (stat(pathName,&statbuf) != 0) { /* Problem reading this tags file. Return FALSE */ added = 0; continue; } t = (tagFile *) NEditMalloc(sizeof(tagFile)); t->filename = STRSAVE(pathName); t->loaded = 0; t->date = statbuf.st_mtime; t->index = ++tagFileIndex; t->next = FileList; t->refcount = 1; FileList = setFileListHead(t, file_type ); } NEditFree(tmptagSpec); updateMenuItems(); if (added) return TRUE; else return FALSE; } /* Un-manage a colon-delimited set of tags files * Return TRUE if all files were found in the FileList and unloaded, FALSE * if any file was not found in the FileList. * "file_type" is either TAG or TIP * If "force_unload" is true, a calltips file will be deleted even if its * refcount is nonzero. */ int DeleteTagsFile(const char *tagSpec, int file_type, Boolean force_unload) { tagFile *t, *last; tagFile *FileList; char pathName[MAXPATHLEN], *tmptagSpec, *filename; int removed; /* To prevent any possible segfault */ if (tagSpec == NULL) { fprintf(stderr, "xnedit: Internal Error: Passed NULL pointer to DeleteTagsFile!\n"); return FALSE; } searchMode = file_type; if (searchMode == TAG) FileList = TagsFileList; else FileList = TipsFileList; tmptagSpec = NEditStrdup(tagSpec); removed=1; for (filename = strtok(tmptagSpec,":"); filename; filename = strtok(NULL,":")) { if (*filename != '/') { strcpy(pathName, GetCurrentDir()); strcat(pathName,"/"); strcat(pathName,filename); } else { strcpy(pathName,filename); } NormalizePathname(pathName); for (last=NULL,t = FileList; t; last = t,t = t->next) { if (strcmp(t->filename, pathName)) continue; /* Don't unload tips files with nonzero refcounts unless forced */ if (searchMode == TIP && !force_unload && --t->refcount > 0) { break; } if (t->loaded) delTag(NULL,NULL,-2,NULL,-2,t->index); if (last) last->next = t->next; else FileList = setFileListHead(t->next, file_type); NEditFree(t->filename); NEditFree(t); updateMenuItems(); break; } /* If any file can't be removed, return false */ if (!t) removed = 0; } if (removed) return TRUE; else return FALSE; } /* ** Update the "Find Definition", "Unload Tags File", "Show Calltip", ** and "Unload Calltips File" menu items in the existing windows. */ static void updateMenuItems(void) { WindowInfo *w; Boolean tipStat=FALSE, tagStat=FALSE; if (TipsFileList) tipStat=TRUE; if (TagsFileList) tagStat=TRUE; for (w=WindowList; w!=NULL; w=w->next) { if (!IsTopDocument(w)) continue; XtSetSensitive(w->showTipItem, tipStat || tagStat); XtSetSensitive(w->unloadTipsMenuItem, tipStat); XtSetSensitive(w->findDefItem, tagStat); XtSetSensitive(w->unloadTagsMenuItem, tagStat); } } /* ** Scans one <line> from a ctags tags file (<index>) in tagPath. ** Return value: Number of tag specs added. */ static int scanCTagsLine(char *line, const char *tagPath, int index) { char *name = line, *searchString = NULL; char *file = NULL; char *posTagREEnd = NULL, *posTagRENull = NULL; int pos; /* [name]\t[file]\t[searchString]\n */ if ((*name != '!') && (file = strchr(name, '\t')) != NULL) { *file++ = '\0'; if ((searchString = strchr(file, '\t')) != NULL) *searchString++ = '\0'; else return 0; } else return 0; /* ** Guess the end of searchString: ** Try to handle original ctags and exuberant ctags format: */ if(searchString[0] == '/' || searchString[0] == '?') { pos=-1; /* "search expr without pos info" */ /* Situations: /<ANY expr>/\0 ** ?<ANY expr>?\0 --> original ctags ** /<ANY expr>/;" <flags> ** ?<ANY expr>?;" <flags> --> exuberant ctags */ posTagREEnd = strrchr(searchString, ';'); posTagRENull = strchr(searchString, 0); if(!posTagREEnd || (posTagREEnd[1] != '""') || (posTagRENull[-1] == searchString[0])) { /* -> original ctags format = exuberant ctags format 1 */ posTagREEnd = posTagRENull; } else { /* looks like exuberant ctags format 2 */ *posTagREEnd = 0; } /* ** Hide the last delimiter: ** /<expression>/ becomes /<expression> ** ?<expression>? becomes ?<expression> ** This will save a little work in fakeRegExSearch. */ if(posTagREEnd > (searchString+2)) { posTagREEnd--; if(searchString[0] == *posTagREEnd) *posTagREEnd=0; } } else { pos=atoi(searchString); *searchString=0; } /* No ability to read language mode right now */ return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos, tagPath, index); } /* * Scans one <line> from an etags (emacs) tags file (<index>) in tagPath. * recLevel = current recursion level for tags file including * file = destination definition file. possibly modified. len=MAXPATHLEN! * Return value: Number of tag specs added. */ static int scanETagsLine(const char *line, const char * tagPath, int index, char * file, int recLevel) { char name[MAXLINE], searchString[MAXLINE]; char incPath[MAXPATHLEN]; int pos, len; char *posDEL, *posSOH, *posCOM; /* check for destination file separator */ if(line[0]==12) { /* <np> */ *file=0; return 0; } /* check for standard definition line */ posDEL=strchr(line, '\177'); posSOH=strchr(line, '\001'); posCOM=strrchr(line, ','); if(*file && posDEL && (posSOH > posDEL) && (posCOM > posSOH)) { /* exuberant ctags -e style */ len=Min(MAXLINE-1, posDEL - line); strncpy(searchString, line, len); searchString[len]=0; len=Min(MAXLINE-1, (posSOH - posDEL) - 1); strncpy(name, posDEL + 1, len); name[len]=0; pos=atoi(posCOM+1); /* No ability to set language mode for the moment */ return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos, tagPath, index); } if (*file && posDEL && (posCOM > posDEL)) { /* old etags style, part name<soh> is missing here! */ len=Min(MAXLINE-1, posDEL - line); strncpy(searchString, line, len); searchString[len]=0; /* guess name: take the last alnum (plus _) part of searchString */ while(--len >= 0) { if( isalnum((unsigned char)searchString[len]) || (searchString[len] == '_')) break; } if(len<0) return 0; pos=len; while (pos >= 0 && (isalnum((unsigned char)searchString[pos]) || (searchString[pos] == '_'))) pos--; strncpy(name, searchString + pos + 1, len - pos); name[len - pos] = 0; /* name ready */ pos=atoi(posCOM+1); return addTag(name, file, PLAIN_LANGUAGE_MODE, searchString, pos, tagPath, index); } /* check for destination file spec */ if(*line && posCOM) { len=Min(MAXPATHLEN-1, posCOM - line); strncpy(file, line, len); file[len]=0; /* check if that's an include file ... */ if(!(strncmp(posCOM+1, "include", 7))) { if(*file != '/') { if((strlen(tagPath) + strlen(file)) >= MAXPATHLEN) { fprintf(stderr, "tags.c: MAXPATHLEN overflow\n"); *file=0; /* invalidate */ return 0; } strcpy(incPath, tagPath); strcat(incPath, file); CompressPathname(incPath); return(loadTagsFile(incPath, index, recLevel+1)); } else { return(loadTagsFile(file, index, recLevel+1)); } } } return 0; } /* Tag File Type */ typedef enum { TFT_CHECK, TFT_ETAGS, TFT_CTAGS } TFT; /* ** Loads tagsFile into the hash table. ** Returns the number of added tag specifications. */ static int loadTagsFile(const char *tagsFile, int index, int recLevel) { FILE *fp = NULL; char line[MAXLINE]; char file[MAXPATHLEN], tagPath[MAXPATHLEN]; char resolvedTagsFile[MAXPATHLEN+1]; int nTagsAdded=0; int tagFileType = TFT_CHECK; if(recLevel > MAX_TAG_INCLUDE_RECURSION_LEVEL) { return 0; } /* the path of the tags file must be resolved to find the right files: * definition source files are (in most cases) specified relatively inside * the tags file to the tags files directory. */ if(!ResolvePath(tagsFile, resolvedTagsFile)) { return 0; } /* Open the file */ if ((fp = fopen(resolvedTagsFile, "r")) == NULL) { return 0; } ParseFilename(resolvedTagsFile, NULL, tagPath); /* Read the file and store its contents */ while (fgets(line, MAXLINE, fp)) { /* This might take a while if you have a huge tags file (like I do).. keep the windows up to date and post a busy cursor so the user doesn't think we died. */ AllWindowsBusy("Loading tags file..."); /* the first character in the file decides if the file is treat as etags or ctags file. */ if(tagFileType==TFT_CHECK) { if(line[0]==12) /* <np> */ tagFileType=TFT_ETAGS; else tagFileType=TFT_CTAGS; } if(tagFileType==TFT_CTAGS) { nTagsAdded += scanCTagsLine(line, tagPath, index); } else { nTagsAdded += scanETagsLine(line, tagPath, index, file, recLevel); } } fclose(fp); AllWindowsUnbusy(); return nTagsAdded; } /* ** Given a tag name, lookup the file and path of the definition ** and the proper search string. Returned strings are pointers ** to internal storage which are valid until the next loadTagsFile call. ** ** Invocation with name != NULL (containing the searched definition) ** --> returns first definition of name ** Successive invocation with name == NULL ** --> returns further definitions (resulting from multiple tags files) ** ** Return Value: TRUE: tag spec found ** FALSE: no (more) definitions found. */ #define TAG_STS_ERR_FMT "NEdit: Error getting status for tag file %s\n" int LookupTag(const char *name, const char **file, int *language, const char **searchString, int * pos, const char **path, int search_type) { tag *t; tagFile *tf; struct stat statbuf; tagFile *FileList; int load_status; searchMode = search_type; if (searchMode == TIP) FileList = TipsFileList; else FileList = TagsFileList; /* ** Go through the list of all tags Files: ** - load them (if not already loaded) ** - check for update of the tags file and reload it in that case ** - save the modification date of the tags file ** ** Do this only as long as name != NULL, not for sucessive calls ** to find multiple tags specs. ** */ for (tf = FileList; tf && name; tf = tf->next) { if (tf->loaded) { if (stat(tf->filename,&statbuf) != 0) { /* */ fprintf(stderr, TAG_STS_ERR_FMT, tf->filename); } else { if (tf->date == statbuf.st_mtime) { /* current tags file tf is already loaded and up to date */ continue; } } /* tags file has been modified, delete it's entries and reload it */ delTag(NULL,NULL,-2,NULL,-2,tf->index); } /* If we get here we have to try to (re-) load the tags file */ if (FileList == TipsFileList) load_status = loadTipsFile(tf->filename, tf->index, 0); else load_status = loadTagsFile(tf->filename, tf->index, 0); if(load_status) { if (stat(tf->filename,&statbuf) != 0) { if(!tf->loaded) { /* if tf->loaded == 1 we already have seen the error msg */ fprintf(stderr, TAG_STS_ERR_FMT, tf->filename); } } else { tf->date = statbuf.st_mtime; } tf->loaded = 1; } else { tf->loaded = 0; } } t = getTag(name, search_type); if (!t) { return FALSE; } else { *file = t->file; *language = t->language; *searchString = t->searchString; *pos = t->posInf; *path = t->path; return TRUE; } } /* ** This code path is followed if the request came from either ** FindDefinition or FindDefCalltip. This should probably be refactored. */ static int findDef(WindowInfo *window, const char *value, int search_type) { static char tagText[MAX_TAG_LEN + 1]; const char *p; char message[MAX_TAG_LEN+40]; int l, ml, status = 0; searchMode = search_type; l = strlen(value); if (l <= MAX_TAG_LEN) { /* should be of type text??? */ for (p = value; *p && isascii(*p); p++) { } if (!(*p)) { ml = ((l < MAX_TAG_LEN) ? (l) : (MAX_TAG_LEN)); strncpy(tagText, value, ml); tagText[ml] = '\0'; /* See if we can find the tip/tag */ status = findAllMatches(window, tagText); /* If we didn't find a requested calltip, see if we can use a tag */ if (status == 0 && search_type == TIP && TagsFileList != NULL) { searchMode = TIP_FROM_TAG; status = findAllMatches(window, tagText); } if (status == 0) { /* Didn't find any matches */ if (searchMode == TIP_FROM_TAG || searchMode == TIP) { sprintf(message, "No match for \"%s\" in calltips or tags.", tagName); tagsShowCalltip( window, message ); } else { DialogF(DF_WARN, window->textArea, 1, "Tags", "\"%s\" not found in tags file%s", "OK", tagName, (TagsFileList && TagsFileList->next) ? "s" : ""); } } } else { fprintf(stderr, "NEdit: Can''t handle non 8-bit text\n"); XBell(TheDisplay, 0); } } else { fprintf(stderr, "NEdit: Tag Length too long.\n"); XBell(TheDisplay, 0); } return status; } /* ** Lookup the definition for the current primary selection the currently ** loaded tags file and bring up the file and line that the tags file ** indicates. */ static void findDefinitionHelper(WindowInfo *window, Time time, const char *arg, int search_type) { if(arg) { findDef(window, arg, search_type); } else { searchMode = search_type; XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING, findDefCB, window, time); } } /* ** See findDefHelper */ void FindDefinition(WindowInfo *window, Time time, const char *arg) { findDefinitionHelper(window, time, arg, TAG); } /* ** See findDefHelper */ void FindDefCalltip(WindowInfo *window, Time time, const char *arg) { /* Reset calltip parameters to reasonable defaults */ globAnchored = False; globPos = -1; globHAlign = TIP_LEFT; globVAlign = TIP_BELOW; globAlignMode = TIP_SLOPPY; findDefinitionHelper(window, time, arg, TIP); } /* Callback function for FindDefinition */ static void findDefCB(Widget widget, XtPointer closure, Atom *sel, Atom *type, XtPointer v, unsigned long *length, int *format) { WindowInfo *window = closure; char *value = v; /* skip if we can't get the selection data, or it's obviously too long */ if (*type == XT_CONVERT_FAIL || value == NULL) { XBell(TheDisplay, 0); } else { findDef(window, value, searchMode); } NEditFree(value); } /* ** Try to display a calltip ** anchored: If true, tip appears at position pos ** lookup: If true, text is considered a key to be searched for in the ** tip and/or tag database depending on search_type ** search_type: Either TIP or TIP_FROM_TAG */ int ShowTipString(WindowInfo *window, char *text, Boolean anchored, int pos, Boolean lookup, int search_type, int hAlign, int vAlign, int alignMode) { if (search_type == TAG) return 0; /* So we don't have to carry all of the calltip alignment info around */ globAnchored = anchored; globPos = pos; globHAlign = hAlign; globVAlign = vAlign; globAlignMode = alignMode; /* If this isn't a lookup request, just display it. */ if (!lookup) return tagsShowCalltip(window, text); else return findDef(window, text, search_type); } /* store all of the info into a pre-allocated tags struct */ static void setTag(tag *t, const char *name, const char *file, int language, const char *searchString, int posInf, const char *path) { t->name = RefStringDup(name); t->file = RefStringDup(file); t->searchString = RefStringDup(searchString); t->path = RefStringDup(path); t->language = language; t->posInf = posInf; } /* ** ctags search expressions are literal strings with a search direction flag, ** line starting "^" and ending "$" delimiters. This routine translates them ** into NEdit compatible regular expressions and does the search. ** Etags search expressions are plain literals strings, which ** ** If in_buffer is not NULL then it is searched instead of the window buffer. ** In this case in_buffer should be an NEditMalloc allocated buffer and the ** caller is responsible for freeing it. */ static int fakeRegExSearch(WindowInfo *window, char *in_buffer, const char *searchString, int *startPos, int *endPos) { int found, searchStartPos, dir, ctagsMode; char searchSubs[3*MAXLINE+3], *outPtr; const char *fileString, *inPtr; if (in_buffer == NULL) { /* get the entire (sigh) text buffer from the text area widget */ fileString = BufAsString(window->buffer); } else { fileString = in_buffer; } /* determine search direction and start position */ if (*startPos != -1) { /* etags mode! */ dir = SEARCH_FORWARD; searchStartPos = *startPos; ctagsMode=0; } else if (searchString[0] == '/') { dir = SEARCH_FORWARD; searchStartPos = 0; ctagsMode=1; } else if (searchString[0] == '?') { dir = SEARCH_BACKWARD; /* searchStartPos = window->buffer->length; */ searchStartPos = strlen(fileString); ctagsMode=1; } else { fprintf(stderr, "NEdit: Error parsing tag file search string"); return FALSE; } /* Build the search regex. */ outPtr=searchSubs; if(ctagsMode) { inPtr=searchString+1; /* searchString[0] is / or ? --> search dir */ if(*inPtr == '^') { /* If the first char is a caret then it's a RE line start delim */ *outPtr++ = *inPtr++; } } else { /* etags mode, no search dir spec, no leading caret */ inPtr=searchString; } while(*inPtr) { if( (*inPtr=='\\' && inPtr[1]=='/') || (*inPtr=='\r' && inPtr[1]=='$' && !inPtr[2]) ) { /* Remove: - escapes (added by standard and exuberant ctags) from slashes - literal CRs generated by standard ctags for DOSified sources */ inPtr++; } else if(strchr("()-[]<>{}.|^*+?&\\", *inPtr) || (*inPtr == '$' && (inPtr[1]||(!ctagsMode)))){ /* Escape RE Meta Characters to match them literally. Don't escape $ if it's the last charcter of the search expr in ctags mode; always escape $ in etags mode. */ *outPtr++ = '\\'; *outPtr++ = *inPtr++; } else if (isspace((unsigned char)*inPtr)) { /* col. multiple spaces */ *outPtr++ = '\\'; *outPtr++ = 's'; *outPtr++ = '+'; do { inPtr++ ; } while(isspace((unsigned char)*inPtr)); } else { /* simply copy all other characters */ *outPtr++ = *inPtr++; } } *outPtr=0; /* Terminate searchSubs */ found = SearchString(fileString, searchSubs, dir, SEARCH_REGEX, False, searchStartPos, startPos, endPos, NULL, NULL, NULL); if(!found && !ctagsMode) { /* position of the target definition could have been drifted before startPos, if nothing has been found by now try searching backward again from startPos. */ found = SearchString(fileString, searchSubs, SEARCH_BACKWARD, SEARCH_REGEX, False, searchStartPos, startPos, endPos, NULL, NULL, NULL); } /* return the result */ if (found) { /* *startPos and *endPos are set in SearchString*/ return TRUE; } else { /* startPos, endPos left untouched by SearchString if search failed. */ XBell(TheDisplay, 0); return FALSE; } } /* Finds all matches and handles tag "collisions". Prompts user with a list of collided tags in the hash table and allows the user to select the correct one. */ static int findAllMatches(WindowInfo *window, const char *string) { Widget dialogParent = window->textArea; char filename[MAXPATHLEN], pathname[MAXPATHLEN]; char temp[32+2*MAXPATHLEN+MAXLINE]; const char *fileToSearch, *searchString, *tagPath; char **dupTagsList; int startPos, i, pathMatch=0, samePath=0, langMode, nMatches=0; /* verify that the string is reasonable as a tag */ if (*string == '\0' || strlen(string) > MAX_TAG_LEN) { XBell(TheDisplay, 0); return -1; } tagName=string; /* First look up all of the matching tags */ while (LookupTag(string, &fileToSearch, &langMode, &searchString, &startPos, &tagPath, searchMode)) { /* Skip this tag if it has a language mode that doesn't match the current language mode, but don't skip anything if the window is in PLAIN_LANGUAGE_MODE. */ if (window->languageMode != PLAIN_LANGUAGE_MODE && GetPrefSmartTags() && langMode != PLAIN_LANGUAGE_MODE && langMode != window->languageMode) { string=NULL; continue; } if (*fileToSearch == '/') strcpy(tagFiles[nMatches], fileToSearch); else sprintf(tagFiles[nMatches],"%s%s",tagPath,fileToSearch); strcpy(tagSearch[nMatches],searchString); tagPosInf[nMatches]=startPos; ParseFilename(tagFiles[nMatches], filename, pathname); /* Is this match in the current file? If so, use it! */ if (GetPrefSmartTags() && !strcmp(window->filename,filename) && !strcmp(window->path,pathname) ) { if (nMatches) { strcpy(tagFiles[0],tagFiles[nMatches]); strcpy(tagSearch[0],tagSearch[nMatches]); tagPosInf[0]=tagPosInf[nMatches]; } nMatches = 1; break; } /* Is this match in the same dir. as the current file? */ if (!strcmp(window->path,pathname)) { samePath++; pathMatch=nMatches; } if (++nMatches >= MAXDUPTAGS) { DialogF(DF_WARN, dialogParent, 1, "Tags", "Too many duplicate tags, first %d shown", "OK", MAXDUPTAGS); break; } /* Tell LookupTag to look for more definitions of the same tag: */ string = NULL; } /* Did we find any matches? */ if (!nMatches) { return 0; } /* Only one of the matches is in the same dir. as this file. Use it. */ if (GetPrefSmartTags() && samePath == 1 && nMatches > 1) { strcpy(tagFiles[0],tagFiles[pathMatch]); strcpy(tagSearch[0],tagSearch[pathMatch]); tagPosInf[0]=tagPosInf[pathMatch]; nMatches = 1; } /* If all of the tag entries are the same file, just use the first. */ if (GetPrefSmartTags()) { for (i=1; i<nMatches; i++) if (strcmp(tagFiles[i],tagFiles[i-1])) break; if (i==nMatches) nMatches = 1; } if (nMatches>1) { if (!(dupTagsList = (char **) NEditMalloc(sizeof(char *) * nMatches))) { fprintf(stderr, "xnedit: findAllMatches(): out of heap space!\n"); XBell(TheDisplay, 0); return -1; } for (i=0; i<nMatches; i++) { ParseFilename(tagFiles[i], filename, pathname); if ((i<nMatches-1 && !strcmp(tagFiles[i],tagFiles[i+1])) || (i>0 && !strcmp(tagFiles[i],tagFiles[i-1]))) { if(*(tagSearch[i]) && (tagPosInf[i] != -1)) { /* etags */ sprintf(temp,"%2d. %s%s %8i %s", i+1, pathname, filename, tagPosInf[i], tagSearch[i]); } else if (*(tagSearch[i])) { /* ctags search expr */ sprintf(temp,"%2d. %s%s %s", i+1, pathname, filename, tagSearch[i]); } else { /* line number only */ sprintf(temp,"%2d. %s%s %8i", i+1, pathname, filename, tagPosInf[i]); } } else { sprintf(temp,"%2d. %s%s",i+1,pathname,filename); } if (NULL == (dupTagsList[i] = (char*) NEditMalloc(strlen(temp) + 1))) { int j; fprintf(stderr, "xnedit: findAllMatches(): out of heap space!\n"); /* dupTagsList[i] is unallocated, let's free [i - 1] to [0] */ for (j = i - 1; j > -1; j--) { NEditFree(dupTagsList[j]); } NEditFree(dupTagsList); XBell(TheDisplay, 0); return -1; } strcpy(dupTagsList[i],temp); } createSelectMenu(dialogParent, "Duplicate Tags", nMatches, dupTagsList); for (i=0; i<nMatches; i++) NEditFree(dupTagsList[i]); NEditFree(dupTagsList); return 1; } /* ** No need for a dialog list, there is only one tag matching -- ** Go directly to the tag */ if (searchMode == TAG) editTaggedLocation( dialogParent, 0 ); else showMatchingCalltip( dialogParent, 0 ); return 1; } /* Callback function for the FindAll widget. Process the users response. */ static void findAllCB(Widget parent, XtPointer client_data, XtPointer call_data) { int i; char *eptr; XmSelectionBoxCallbackStruct *cbs = (XmSelectionBoxCallbackStruct *) call_data; if (cbs->reason == XmCR_NO_MATCH) return; if (cbs->reason == XmCR_CANCEL) { XtDestroyWidget(XtParent(parent)); return; } XmStringGetLtoR(cbs->value,XmFONTLIST_DEFAULT_TAG,&eptr); if ((i = atoi(eptr)-1) < 0) { XBell(TheDisplay, 0); return; } if (searchMode == TAG) editTaggedLocation( parent, i ); /* Open the file with the definition */ else showMatchingCalltip( parent, i ); if (cbs->reason == XmCR_OK) XtDestroyWidget(XtParent(parent)); } /* Window manager close-box callback for tag-collision dialog */ static void findAllCloseCB(Widget parent, XtPointer client_data, XtPointer call_data) { XtDestroyWidget(parent); } /* * Given a \0 terminated string and a position, advance the position * by n lines, where line separators (for now) are \n. If the end of * string is reached before n lines, return the number of lines advanced, * else normally return -1. */ static int moveAheadNLines( char *str, int *pos, int n ) { int i=n; while (str[*pos] != '\0' && n>0) { if (str[*pos] == '\n') --n; ++(*pos); } if (n==0) return -1; else return i-n; } /* ** Show the calltip specified by tagFiles[i], tagSearch[i], tagPosInf[i] ** This reads from either a source code file (if searchMode == TIP_FROM_TAG) ** or a calltips file (if searchMode == TIP). */ static void showMatchingCalltip( Widget parent, int i ) { int startPos=0, fileLen, readLen, tipLen; int endPos=0; char *fileString; FILE *fp; struct stat statbuf; char *message; /* 1. Open the target file */ NormalizePathname(tagFiles[i]); fp = fopen(tagFiles[i], "r"); if (fp == NULL) { DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s", "OK", tagFiles[i]); return; } if (fstat(fileno(fp), &statbuf) != 0) { fclose(fp); DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s", "OK", tagFiles[i]); return; } /* 2. Read the target file */ /* Allocate space for the whole contents of the file (unfortunately) */ fileLen = statbuf.st_size; fileString = (char*)NEditMalloc(fileLen+1); /* +1 = space for null */ if (fileString == NULL) { fclose(fp); DialogF(DF_ERR, parent, 1, "File too large", "File is too large to load", "OK"); return; } /* Read the file into fileString and terminate with a null */ readLen = fread(fileString, sizeof(char), fileLen, fp); if (ferror(fp)) { fclose(fp); DialogF(DF_ERR, parent, 1, "Error reading File", "Error reading %s", "OK", tagFiles[i]); NEditFree(fileString); return; } fileString[readLen] = 0; /* Close the file */ if (fclose(fp) != 0) { /* unlikely error */ DialogF(DF_WARN, parent, 1, "Error closing File", "Unable to close file", "OK"); /* we read it successfully, so continue */ } /* 3. Search for the tagged location (set startPos) */ if (!*(tagSearch[i])) { /* It's a line number, just go for it */ if ((moveAheadNLines( fileString, &startPos, tagPosInf[i]-1 )) >= 0) { DialogF(DF_ERR, parent, 1, "Tags Error", "%s\n not long enough for definition to be on line %d", "OK", tagFiles[i], tagPosInf[i]); NEditFree(fileString); return; } } else { startPos = tagPosInf[i]; if(!fakeRegExSearch(WidgetToWindow(parent), fileString, tagSearch[i], &startPos, &endPos)){ DialogF(DF_WARN, parent, 1, "Tag not found", "Definition for %s\nnot found in %s", "OK", tagName, tagFiles[i]); NEditFree(fileString); return; } } if (searchMode == TIP) { int dummy, found; /* 4. Find the end of the calltip (delimited by an empty line) */ endPos = startPos; found = SearchString(fileString, "\\n\\s*\\n", SEARCH_FORWARD, SEARCH_REGEX, False, startPos, &endPos, &dummy, NULL, NULL, NULL); if (!found) { /* Just take 4 lines */ moveAheadNLines( fileString, &endPos, TIP_DEFAULT_LINES ); --endPos; /* Lose the last \n */ } } else { /* Mode = TIP_FROM_TAG */ /* 4. Copy TIP_DEFAULT_LINES lines of text to the calltip string */ endPos = startPos; moveAheadNLines( fileString, &endPos, TIP_DEFAULT_LINES ); /* Make sure not to overrun the fileString with ". . ." */ if (((size_t) endPos) <= (strlen(fileString)-5)) { sprintf( &fileString[endPos], ". . ." ); endPos += 5; } } /* 5. Copy the calltip to a string */ tipLen = endPos - startPos; message = (char*)malloc(tipLen+1); /* +1 = space for null */ if (message == NULL) { DialogF(DF_ERR, parent, 1, "Out of Memory", "Can''t allocate memory for calltip message", "OK"); NEditFree(fileString); return; } strncpy( message, &fileString[startPos], tipLen ); message[tipLen] = 0; /* 6. Display it */ tagsShowCalltip( WidgetToWindow(parent), message ); NEditFree(message); NEditFree(fileString); } /* Open a new (or existing) editor window to the location specified in tagFiles[i], tagSearch[i], tagPosInf[i] */ static void editTaggedLocation( Widget parent, int i ) { /* Globals: tagSearch, tagPosInf, tagFiles, tagName, textNrows, WindowList */ int startPos, endPos, lineNum, rows; char filename[MAXPATHLEN], pathname[MAXPATHLEN]; WindowInfo *windowToSearch; WindowInfo *parentWindow = WidgetToWindow(parent); ParseFilename(tagFiles[i],filename,pathname); /* open the file containing the definition */ EditExistingFile(parentWindow, filename, pathname, NULL, NULL, 0, NULL, False, NULL, GetPrefOpenInTab(), False); windowToSearch = FindWindowWithFile(filename, pathname); if (windowToSearch == NULL) { DialogF(DF_WARN, parent, 1, "File not found", "File %s not found", "OK", tagFiles[i]); return; } startPos=tagPosInf[i]; if(!*(tagSearch[i])) { /* if the search string is empty, select the numbered line */ SelectNumberedLine(windowToSearch, startPos); return; } /* search for the tags file search string in the newly opened file */ if(!fakeRegExSearch(windowToSearch, NULL, tagSearch[i], &startPos, &endPos)){ DialogF(DF_WARN, windowToSearch->shell, 1, "Tag Error", "Definition for %s\nnot found in %s", "OK", tagName, tagFiles[i]); return; } /* select the matched string */ BufSelect(windowToSearch->buffer, startPos, endPos); RaiseFocusDocumentWindow(windowToSearch, True); /* Position it nicely in the window, about 1/4 of the way down from the top */ lineNum = BufCountLines(windowToSearch->buffer, 0, startPos); XtVaGetValues(windowToSearch->lastFocus, textNrows, &rows, NULL); TextSetScroll(windowToSearch->lastFocus, lineNum - rows/4, 0); TextSetCursorPos(windowToSearch->lastFocus, endPos); } /* Create a Menu for user to select from the collided tags */ static Widget createSelectMenu(Widget parent, char *label, int nArgs, char *args[]) { int i; char tmpStr[100]; Widget menu; XmStringTable list; XmString popupTitle; int ac; Arg csdargs[20]; list = (XmStringTable) NEditMalloc(nArgs * sizeof(XmString *)); for (i=0; i<nArgs; i++) list[i] = XmStringCreateSimple(args[i]); sprintf(tmpStr,"Select File With TAG: %s",tagName); popupTitle = XmStringCreateSimple(tmpStr); ac = 0; XtSetArg(csdargs[ac], XmNlistLabelString, popupTitle); ac++; XtSetArg(csdargs[ac], XmNlistItems, list); ac++; XtSetArg(csdargs[ac], XmNlistItemCount, nArgs); ac++; XtSetArg(csdargs[ac], XmNvisibleItemCount, 12); ac++; XtSetArg(csdargs[ac], XmNautoUnmanage, False); ac++; menu = CreateSelectionDialog(parent,label,csdargs,ac); XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_TEXT)); XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_HELP_BUTTON)); XtUnmanageChild(XmSelectionBoxGetChild(menu, XmDIALOG_SELECTION_LABEL)); XtAddCallback(menu, XmNokCallback, (XtCallbackProc)findAllCB, menu); XtAddCallback(menu, XmNapplyCallback, (XtCallbackProc)findAllCB, menu); XtAddCallback(menu, XmNcancelCallback, (XtCallbackProc)findAllCB, menu); AddMotifCloseCallback(XtParent(menu), findAllCloseCB, NULL); for (i=0; i<nArgs; i++) XmStringFree(list[i]); NEditFree(list); XmStringFree(popupTitle); ManageDialogCenteredOnPointer(menu); return menu; } /******************************************************************** * Functions for loading Calltips files * ********************************************************************/ enum tftoken_types { TF_EOF, TF_BLOCK, TF_VERSION, TF_INCLUDE, TF_LANGUAGE, TF_ALIAS, TF_ERROR, TF_ERROR_EOF }; /* A wrapper for SearchString */ static int searchLine(char *line, const char *regex) { int dummy1, dummy2; return SearchString(line, regex, SEARCH_FORWARD, SEARCH_REGEX, False, 0, &dummy1, &dummy2, NULL, NULL, NULL); } /* Check if a line has non-ws characters */ static Boolean lineEmpty(const char *line) { while (*line && *line != '\n') { if (*line != ' ' && *line != '\t') return False; ++line; } return True; } /* Remove trailing whitespace from a line */ static void rstrip( char *dst, const char *src ) { int wsStart, dummy2; /* Strip trailing whitespace */ if(SearchString(src, "\\s*\\n", SEARCH_FORWARD, SEARCH_REGEX, False, 0, &wsStart, &dummy2, NULL, NULL, NULL)) { if(dst != src) memcpy(dst, src, wsStart); dst[wsStart] = 0; } else if(dst != src) strcpy(dst, src); } /* ** Get the next block from a tips file. A block is a \n\n+ delimited set of ** lines in a calltips file. All of the parameters except <fp> are return ** values, and most have different roles depending on the type of block ** that is found. ** header: Depends on the block type ** body: Depends on the block type. Used to return a new ** dynamically allocated string. ** blkLine: Returns the line number of the first line of the block ** after the "* xxxx *" line. ** currLine: Used to keep track of the current line in the file. */ static int nextTFBlock(FILE *fp, char *header, char **body, int *blkLine, int *currLine) { /* These are the different kinds of tokens */ const char *commenTF_regex = "^\\s*\\* comment \\*\\s*$"; const char *version_regex = "^\\s*\\* version \\*\\s*$"; const char *include_regex = "^\\s*\\* include \\*\\s*$"; const char *language_regex = "^\\s*\\* language \\*\\s*$"; const char *alias_regex = "^\\s*\\* alias \\*\\s*$"; char line[MAXLINE], *status; int dummy1; int code; /* Skip blank lines and comments */ while(1) { /* Skip blank lines */ while((status=fgets(line, MAXLINE, fp))) { ++(*currLine); if(!lineEmpty( line )) break; } /* Check for error or EOF */ if(!status) return TF_EOF; /* We've got a non-blank line -- is it a comment block? */ if( !searchLine(line, commenTF_regex) ) break; /* Skip the comment (non-blank lines) */ while((status=fgets(line, MAXLINE, fp))) { ++(*currLine); if(lineEmpty( line )) break; } if(!status) return TF_EOF; } /* Now we know it's a meaningful block */ dummy1 = searchLine(line, include_regex); if( dummy1 || searchLine(line, alias_regex) ) { /* INCLUDE or ALIAS block */ int incLen, incPos, i, incLines; /* fprintf(stderr, "Starting include/alias at line %i\n", *currLine); */ if(dummy1) code = TF_INCLUDE; else { code = TF_ALIAS; /* Need to read the header line for an alias */ status=fgets(line, MAXLINE, fp); ++(*currLine); if (!status) return TF_ERROR_EOF; if (lineEmpty( line )) { fprintf( stderr, "xnedit: Warning: empty ''* alias *'' " "block in calltips file.\n" ); return TF_ERROR; } rstrip(header, line); } incPos = ftell(fp); *blkLine = *currLine + 1; /* Line of first actual filename/alias */ if (incPos < 0) return TF_ERROR; /* Figure out how long the block is */ while((status=fgets(line, MAXLINE, fp)) || feof(fp)) { ++(*currLine); if(feof(fp) || lineEmpty( line )) break; } incLen = ftell(fp) - incPos; incLines = *currLine - *blkLine; /* Correct currLine for the empty line it read at the end */ --(*currLine); if (incLines == 0) { fprintf( stderr, "xnedit: Warning: empty ''* include *'' or" " ''* alias *'' block in calltips file.\n" ); return TF_ERROR; } /* Make space for the filenames/alias sources */ *body = (char *)NEditMalloc(incLen+1); if (!*body) return TF_ERROR; *body[0]=0; if (fseek(fp, incPos, SEEK_SET) != 0) { NEditFree(*body); return TF_ERROR; } /* Read all the lines in the block */ /* fprintf(stderr, "Copying lines\n"); */ for (i=0; i<incLines; i++) { status = fgets(line, MAXLINE, fp); if (!status) { NEditFree(*body); return TF_ERROR_EOF; } rstrip(line,line); if(i) strcat(*body, ":"); strcat(*body, line); } /* fprintf(stderr, "Finished include/alias at line %i\n", *currLine); */ } else if( searchLine(line, language_regex) ) { /* LANGUAGE block */ status=fgets(line, MAXLINE, fp); ++(*currLine); if (!status) return TF_ERROR_EOF; if (lineEmpty( line )) { fprintf( stderr, "xnedit: Warning: empty ''* language *'' block in calltips file.\n" ); return TF_ERROR; } *blkLine = *currLine; rstrip(header, line); code = TF_LANGUAGE; } else if( searchLine(line, version_regex) ) { /* VERSION block */ status=fgets(line, MAXLINE, fp); ++(*currLine); if (!status) return TF_ERROR_EOF; if (lineEmpty( line )) { fprintf( stderr, "xnedit: Warning: empty ''* version *'' block in calltips file.\n" ); return TF_ERROR; } *blkLine = *currLine; rstrip(header, line); code = TF_VERSION; } else { /* Calltip block */ /* The first line is the key, the rest is the tip. Strip trailing whitespace. */ rstrip(header, line); status=fgets(line, MAXLINE, fp); ++(*currLine); if (!status) return TF_ERROR_EOF; if (lineEmpty( line )) { fprintf( stderr, "xnedit: Warning: empty calltip block:\n" " \"%s\"\n", header); return TF_ERROR; } *blkLine = *currLine; *body = strdup(line); code = TF_BLOCK; } /* Skip the rest of the block */ dummy1 = *currLine; while(fgets(line, MAXLINE, fp)) { ++(*currLine); if (lineEmpty( line )) break; } /* Warn about any unneeded extra lines (which are ignored). */ if (dummy1+1 < *currLine && code != TF_BLOCK) { fprintf( stderr, "xnedit: Warning: extra lines in language or version block ignored.\n" ); } return code; } /* A struct for describing a calltip alias */ typedef struct _alias { char *dest; char *sources; struct _alias *next; } tf_alias; /* ** Allocate a new alias, copying dest and stealing sources. This may ** seem strange but that's the way it's called */ static tf_alias *new_alias(const char *dest, char *sources) { tf_alias *alias; /* fprintf(stderr, "new_alias: %s <- %s\n", dest, sources); */ /* Allocate the alias */ alias = (tf_alias *)NEditMalloc( sizeof(tf_alias) ); if(!alias) return NULL; /* Fill it in */ alias->dest = (char*)NEditMalloc( strlen(dest)+1 ); if(!(alias->dest)) return NULL; strcpy( alias->dest, dest ); alias->sources = sources; return alias; } /* Deallocate a linked-list of aliases */ static void free_alias_list(tf_alias *alias) { tf_alias *tmp_alias; while(alias) { tmp_alias = alias->next; NEditFree(alias->dest); NEditFree(alias->sources); NEditFree(alias); alias = tmp_alias; } } /* ** Load a calltips file and insert all of the entries into the global tips ** database. Each tip is essentially stored as its filename and the line ** at which it appears--the exact same way ctags indexes source-code. That's ** why calltips and tags share so much code. */ static int loadTipsFile(const char *tipsFile, int index, int recLevel) { FILE *fp = NULL; char header[MAXLINE]; char *body, *tipIncFile; char tipPath[MAXPATHLEN]; char resolvedTipsFile[MAXPATHLEN+1]; int nTipsAdded=0, langMode = PLAIN_LANGUAGE_MODE, oldLangMode; int currLine=0, code, blkLine; tf_alias *aliases=NULL, *tmp_alias; if(recLevel > MAX_TAG_INCLUDE_RECURSION_LEVEL) { fprintf(stderr, "xnedit: Warning: Reached recursion limit before loading calltips file:\n\t%s\n", tipsFile); return 0; } /* find the tips file */ #ifndef VMS /* Allow ~ in Unix filenames */ strncpy(tipPath, tipsFile, MAXPATHLEN); /* ExpandTilde is destructive */ tipPath[MAXPATHLEN - 1] = '\0'; ExpandTilde(tipPath); if(!ResolvePath(tipPath, resolvedTipsFile)) return 0; #else if(!ResolvePath(tipsFile, resolvedTipsFile)) return 0; #endif /* Get the path to the tips file */ ParseFilename(resolvedTipsFile, NULL, tipPath); /* Open the file */ if ((fp = fopen(resolvedTipsFile, "r")) == NULL) return 0; while( 1 ) { code = nextTFBlock(fp, header, &body, &blkLine, &currLine); if( code == TF_ERROR_EOF ) { fprintf(stderr,"xnedit: Warning: unexpected EOF in calltips file.\n"); break; } if( code == TF_EOF ) break; switch (code) { case TF_BLOCK: /* Add the calltip to the global hash table. For the moment I'm just using line numbers because I don't want to have to deal with adding escape characters for regex metacharacters that might appear in the string */ nTipsAdded += addTag(header, resolvedTipsFile, langMode, "", blkLine, tipPath, index); NEditFree( body ); break; case TF_INCLUDE: /* nextTFBlock returns a colon-separated list of tips files in body */ for(tipIncFile=strtok(body,":"); tipIncFile; tipIncFile=strtok(NULL,":")) { /* fprintf(stderr, "xnedit: DEBUG: including tips file '%s'\n", tipIncFile); */ nTipsAdded += loadTipsFile( tipIncFile, index, recLevel+1); } NEditFree( body ); break; case TF_LANGUAGE: /* Switch to the new language mode if it's valid, else ignore it. */ oldLangMode = langMode; langMode = FindLanguageMode( header ); if (langMode == PLAIN_LANGUAGE_MODE && strcmp(header, "Plain")) { fprintf(stderr, "xnedit: Error reading calltips file:\n\t%s\n" "Unknown language mode: \"%s\"\n", tipsFile, header); langMode = oldLangMode; } break; case TF_ERROR: fprintf(stderr,"xnedit: Warning: Recoverable error while " "reading calltips file:\n \"%s\"\n", resolvedTipsFile); break; case TF_ALIAS: /* Allocate a new alias struct */ tmp_alias = aliases; aliases = new_alias(header, body); if( !aliases ) { fprintf(stderr,"xnedit: Can''t allocate memory for tipfile " "alias in calltips file:\n \"%s\"\n", resolvedTipsFile); /* Deallocate any allocated aliases */ free_alias_list(tmp_alias); fclose(fp); return 0; } /* Add it to the list */ aliases->next = tmp_alias; break; default: ;/* Ignore TF_VERSION for now */ } } /* Now resolve any aliases */ tmp_alias = aliases; while (tmp_alias) { tag *t; char *src; t = getTag(tmp_alias->dest, TIP); if (!t) { fprintf(stderr, "xnedit: Can''t find destination of alias \"%s\"\n" " in calltips file:\n \"%s\"\n", tmp_alias->dest, resolvedTipsFile); } else { for(src=strtok(tmp_alias->sources,":"); src; src=strtok(NULL,":")) addTag(src, resolvedTipsFile, t->language, "", t->posInf, tipPath, index); } tmp_alias = tmp_alias->next; } free_alias_list(aliases); fclose(fp); return nTipsAdded; }