4 * Copyright (c) 1996-2002, Darren Hiebert
6 * This source code is released for free distribution under the terms of the
7 * GNU General Public License.
9 * This module contains functions for creating tag entries.
15 #include "general.h" /* must always come first */
18 #include <ctype.h> /* to define isspace () */
21 #if defined (HAVE_SYS_TYPES_H)
22 # include <sys/types.h> /* to declare off_t on some hosts */
24 #if defined (HAVE_TYPES_H)
25 # include <types.h> /* to declare off_t on some hosts */
27 #if defined (HAVE_UNISTD_H)
28 # include <unistd.h> /* to declare close (), ftruncate (), truncate () */
31 /* These header files provide for the functions necessary to do file
54 #define PSEUDO_TAG_PREFIX "!_"
56 #define includeExtensionFlags() (Option.tagFileFormat > 1)
61 #if !defined(HAVE_TRUNCATE) && !defined(HAVE_FTRUNCATE) && !defined(HAVE_CHSIZE)
62 # define USE_REPLACEMENT_TRUNCATE
65 /* Hack for rediculous practice of Microsoft Visual C++.
67 #if defined (WIN32) && defined (_MSC_VER)
68 # define chsize _chsize
71 # define O_RDWR _O_RDWR
79 NULL
, /* tag file name */
80 NULL
, /* tag file directory (absolute) */
81 NULL
, /* file pointer */
82 { 0, 0 }, /* numTags */
83 { 0, 0, 0 }, /* max */
84 { NULL
, NULL
, 0 }, /* etags */
88 static boolean TagsToStdout
= FALSE
;
93 #ifdef NEED_PROTO_TRUNCATE
94 extern int truncate (const char *path
, off_t length
);
97 #ifdef NEED_PROTO_FTRUNCATE
98 extern int ftruncate (int fd
, off_t length
);
102 * FUNCTION DEFINITIONS
105 extern void freeTagFileResources (void)
107 if (TagFile
.directory
!= NULL
)
108 eFree (TagFile
.directory
);
109 vStringDelete (TagFile
.vLine
);
112 extern const char *tagFileName (void)
121 static void rememberMaxLengths (const size_t nameLength
, const size_t lineLength
)
123 if (nameLength
> TagFile
.max
.tag
)
124 TagFile
.max
.tag
= nameLength
;
126 if (lineLength
> TagFile
.max
.line
)
127 TagFile
.max
.line
= lineLength
;
130 static void writePseudoTag (
131 const char *const tagName
,
132 const char *const fileName
,
133 const char *const pattern
)
135 const int length
= fprintf (
136 TagFile
.fp
, "%s%s\t%s\t/%s/\n",
137 PSEUDO_TAG_PREFIX
, tagName
, fileName
, pattern
);
138 ++TagFile
.numTags
.added
;
139 rememberMaxLengths (strlen (tagName
), (size_t) length
);
142 static void addPseudoTags (void)
147 const char *formatComment
= "unknown format";
149 sprintf (format
, "%u", Option
.tagFileFormat
);
151 if (Option
.tagFileFormat
== 1)
152 formatComment
= "original ctags format";
153 else if (Option
.tagFileFormat
== 2)
155 "extended format; --format=1 will not append ;\" to lines";
157 writePseudoTag ("TAG_FILE_FORMAT", format
, formatComment
);
158 writePseudoTag ("TAG_FILE_SORTED",
159 Option
.sorted
== SO_FOLDSORTED
? "2" :
160 (Option
.sorted
== SO_SORTED
? "1" : "0"),
161 "0=unsorted, 1=sorted, 2=foldcase");
162 writePseudoTag ("TAG_PROGRAM_AUTHOR", AUTHOR_NAME
, AUTHOR_EMAIL
);
163 writePseudoTag ("TAG_PROGRAM_NAME", PROGRAM_NAME
, "");
164 writePseudoTag ("TAG_PROGRAM_URL", PROGRAM_URL
, "official site");
165 writePseudoTag ("TAG_PROGRAM_VERSION", PROGRAM_VERSION
, "");
169 static void updateSortedFlag (
170 const char *const line
, FILE *const fp
, fpos_t startOfLine
)
172 const char *const tab
= strchr (line
, '\t');
176 const long boolOffset
= tab
- line
+ 1; /* where it should be */
178 if (line
[boolOffset
] == '0' || line
[boolOffset
] == '1')
182 if (fgetpos (fp
, &nextLine
) == -1 || fsetpos (fp
, &startOfLine
) == -1)
183 error (WARNING
, "Failed to update 'sorted' pseudo-tag");
191 while (c
!= '\t' && c
!= '\n');
192 fgetpos (fp
, &flagLocation
);
194 if (c
== '\t' && (d
== '0' || d
== '1') &&
195 d
!= (int) Option
.sorted
)
197 fsetpos (fp
, &flagLocation
);
198 fputc (Option
.sorted
== SO_FOLDSORTED
? '2' :
199 (Option
.sorted
== SO_SORTED
? '1' : '0'), fp
);
201 fsetpos (fp
, &nextLine
);
207 /* Look through all line beginning with "!_TAG_FILE", and update those which
210 static long unsigned int updatePseudoTags (FILE *const fp
)
212 enum { maxEntryLength
= 20 };
213 char entry
[maxEntryLength
+ 1];
214 unsigned long linesRead
= 0;
219 sprintf (entry
, "%sTAG_FILE", PSEUDO_TAG_PREFIX
);
220 entryLength
= strlen (entry
);
221 Assert (entryLength
< maxEntryLength
);
223 fgetpos (fp
, &startOfLine
);
224 line
= readLine (TagFile
.vLine
, fp
);
225 while (line
!= NULL
&& line
[0] == entry
[0])
228 if (strncmp (line
, entry
, entryLength
) == 0)
230 char tab
, classType
[16];
232 if (sscanf (line
+ entryLength
, "%15s%c", classType
, &tab
) == 2 &&
235 if (strcmp (classType
, "_SORTED") == 0)
236 updateSortedFlag (line
, fp
, startOfLine
);
238 fgetpos (fp
, &startOfLine
);
240 line
= readLine (TagFile
.vLine
, fp
);
242 while (line
!= NULL
) /* skip to end of file */
245 line
= readLine (TagFile
.vLine
, fp
);
251 * Tag file management
254 static boolean
isValidTagAddress (const char *const excmd
)
256 boolean isValid
= FALSE
;
258 if (strchr ("/?", excmd
[0]) != NULL
)
262 char *address
= xMalloc (strlen (excmd
) + 1, char);
263 if (sscanf (excmd
, "%[^;\n]", address
) == 1 &&
264 strspn (address
,"0123456789") == strlen (address
))
271 static boolean
isCtagsLine (const char *const line
)
273 enum fieldList
{ TAG
, TAB1
, SRC_FILE
, TAB2
, EXCMD
, NUM_FIELDS
};
274 boolean ok
= FALSE
; /* we assume not unless confirmed */
275 const size_t fieldLength
= strlen (line
) + 1;
276 char *const fields
= xMalloc (NUM_FIELDS
* fieldLength
, char);
279 error (FATAL
, "Cannot analyze tag file");
282 #define field(x) (fields + ((size_t) (x) * fieldLength))
284 const int numFields
= sscanf (
285 line
, "%[^\t]%[\t]%[^\t]%[\t]%[^\r\n]",
286 field (TAG
), field (TAB1
), field (SRC_FILE
),
287 field (TAB2
), field (EXCMD
));
289 /* There must be exactly five fields: two tab fields containing
290 * exactly one tab each, the tag must not begin with "#", and the
291 * file name should not end with ";", and the excmd must be
294 * These conditions will reject tag-looking lines like:
296 * #define LABEL <C-comment>
298 if (numFields
== NUM_FIELDS
&&
299 strlen (field (TAB1
)) == 1 &&
300 strlen (field (TAB2
)) == 1 &&
301 field (TAG
) [0] != '#' &&
302 field (SRC_FILE
) [strlen (field (SRC_FILE
)) - 1] != ';' &&
303 isValidTagAddress (field (EXCMD
)))
311 static boolean
isEtagsLine (const char *const line
)
313 boolean result
= FALSE
;
314 if (line
[0] == '\f')
315 result
= (boolean
) (line
[1] == '\n' || line
[1] == '\r');
319 static boolean
isTagFile (const char *const filename
)
321 boolean ok
= FALSE
; /* we assume not unless confirmed */
322 FILE *const fp
= fopen (filename
, "rb");
324 if (fp
== NULL
&& errno
== ENOENT
)
328 const char *line
= readLine (TagFile
.vLine
, fp
);
333 ok
= (boolean
) (isCtagsLine (line
) || isEtagsLine (line
));
339 extern void copyBytes (FILE* const fromFp
, FILE* const toFp
, const long size
)
341 enum { BufferSize
= 1000 };
342 long toRead
, numRead
;
343 char* buffer
= xMalloc (BufferSize
, char);
344 long remaining
= size
;
347 toRead
= (0 < remaining
&& remaining
< BufferSize
) ?
348 remaining
: (long) BufferSize
;
349 numRead
= fread (buffer
, (size_t) 1, (size_t) toRead
, fromFp
);
350 if (fwrite (buffer
, (size_t)1, (size_t)numRead
, toFp
) < (size_t)numRead
)
351 error (FATAL
| PERROR
, "cannot complete write");
353 remaining
-= numRead
;
354 } while (numRead
== toRead
&& remaining
!= 0);
358 extern void copyFile (const char *const from
, const char *const to
, const long size
)
360 FILE* const fromFp
= fopen (from
, "rb");
362 error (FATAL
| PERROR
, "cannot open file to copy");
365 FILE* const toFp
= fopen (to
, "wb");
367 error (FATAL
| PERROR
, "cannot open copy destination");
370 copyBytes (fromFp
, toFp
, size
);
377 extern void openTagFile (void)
379 setDefaultTagFileName ();
380 TagsToStdout
= isDestinationStdout ();
382 if (TagFile
.vLine
== NULL
)
383 TagFile
.vLine
= vStringNew ();
385 /* Open the tags file.
388 TagFile
.fp
= tempFile ("w", &TagFile
.name
);
393 setDefaultTagFileName ();
394 TagFile
.name
= eStrdup (Option
.tagFileName
);
395 fileExists
= doesFileExist (TagFile
.name
);
396 if (fileExists
&& ! isTagFile (TagFile
.name
))
398 "\"%s\" doesn't look like a tag file; I refuse to overwrite it.",
403 if (Option
.append
&& fileExists
)
404 TagFile
.fp
= fopen (TagFile
.name
, "a+b");
406 TagFile
.fp
= fopen (TagFile
.name
, "w+b");
410 if (Option
.append
&& fileExists
)
412 TagFile
.fp
= fopen (TagFile
.name
, "r+");
413 if (TagFile
.fp
!= NULL
)
415 TagFile
.numTags
.prev
= updatePseudoTags (TagFile
.fp
);
417 TagFile
.fp
= fopen (TagFile
.name
, "a+");
422 TagFile
.fp
= fopen (TagFile
.name
, "w");
423 if (TagFile
.fp
!= NULL
)
427 if (TagFile
.fp
== NULL
)
429 error (FATAL
| PERROR
, "cannot open tag file");
434 TagFile
.directory
= eStrdup (CurrentDirectory
);
436 TagFile
.directory
= absoluteDirname (TagFile
.name
);
439 #ifdef USE_REPLACEMENT_TRUNCATE
441 /* Replacement for missing library function.
443 static int replacementTruncate (const char *const name
, const long size
)
445 char *tempName
= NULL
;
446 FILE *fp
= tempFile ("w", &tempName
);
448 copyFile (name
, tempName
, size
);
449 copyFile (tempName
, name
, WHOLE_FILE
);
458 static void sortTagFile (void)
460 if (TagFile
.numTags
.added
> 0L)
462 if (Option
.sorted
!= SO_UNSORTED
)
464 verbose ("sorting tag file\n");
466 externalSortTags (TagsToStdout
);
468 internalSortTags (TagsToStdout
);
471 else if (TagsToStdout
)
472 catFile (tagFileName ());
475 remove (tagFileName ()); /* remove temporary file */
478 static void resizeTagFile (const long newSize
)
482 #ifdef USE_REPLACEMENT_TRUNCATE
483 result
= replacementTruncate (TagFile
.name
, newSize
);
485 # ifdef HAVE_TRUNCATE
486 result
= truncate (TagFile
.name
, (off_t
) newSize
);
488 const int fd
= open (TagFile
.name
, O_RDWR
);
494 # ifdef HAVE_FTRUNCATE
495 result
= ftruncate (fd
, (off_t
) newSize
);
498 result
= chsize (fd
, newSize
);
506 fprintf (errout
, "Cannot shorten tag file: errno = %d\n", errno
);
509 static void writeEtagsIncludes (FILE *const fp
)
511 if (Option
.etagsInclude
)
514 for (i
= 0 ; i
< stringListCount (Option
.etagsInclude
) ; ++i
)
516 vString
*item
= stringListItem (Option
.etagsInclude
, i
);
517 fprintf (fp
, "\f\n%s,include\n", vStringValue (item
));
522 extern void closeTagFile (const boolean resize
)
524 long desiredSize
, size
;
527 writeEtagsIncludes (TagFile
.fp
);
528 desiredSize
= ftell (TagFile
.fp
);
529 fseek (TagFile
.fp
, 0L, SEEK_END
);
530 size
= ftell (TagFile
.fp
);
532 if (resize
&& desiredSize
< size
)
535 debugPrintf (DEBUG_STATUS
, "shrinking %s from %ld to %ld bytes\n",
536 TagFile
.name
, size
, desiredSize
); )
537 resizeTagFile (desiredSize
);
540 eFree (TagFile
.name
);
544 extern void beginEtagsFile (void)
546 TagFile
.etags
.fp
= tempFile ("w+b", &TagFile
.etags
.name
);
547 TagFile
.etags
.byteCount
= 0;
550 extern void endEtagsFile (const char *const name
)
554 fprintf (TagFile
.fp
, "\f\n%s,%ld\n", name
, (long) TagFile
.etags
.byteCount
);
555 if (TagFile
.etags
.fp
!= NULL
)
557 rewind (TagFile
.etags
.fp
);
558 while ((line
= readLine (TagFile
.vLine
, TagFile
.etags
.fp
)) != NULL
)
559 fputs (line
, TagFile
.fp
);
560 fclose (TagFile
.etags
.fp
);
561 remove (TagFile
.etags
.name
);
562 eFree (TagFile
.etags
.name
);
563 TagFile
.etags
.fp
= NULL
;
564 TagFile
.etags
.name
= NULL
;
569 * Tag entry management
572 /* This function copies the current line out to a specified file. It has no
573 * effect on the fileGetc () function. During copying, any '\' characters
574 * are doubled and a leading '^' or trailing '$' is also quoted. End of line
575 * characters (line feed or carriage return) are dropped.
577 static size_t writeSourceLine (FILE *const fp
, const char *const line
)
582 /* Write everything up to, but not including, a line end character.
584 for (p
= line
; *p
!= '\0' ; ++p
)
586 const int next
= *(p
+ 1);
589 if (c
== CRETURN
|| c
== NEWLINE
)
592 /* If character is '\', or a terminal '$', then quote it.
594 if (c
== BACKSLASH
|| c
== (Option
.backward
? '?' : '/') ||
595 (c
== '$' && (next
== NEWLINE
|| next
== CRETURN
)))
597 putc (BACKSLASH
, fp
);
606 /* Writes "line", stripping leading and duplicate white space.
608 static size_t writeCompactSourceLine (FILE *const fp
, const char *const line
)
610 boolean lineStarted
= FALSE
;
615 /* Write everything up to, but not including, the newline.
617 for (p
= line
, c
= *p
; c
!= NEWLINE
&& c
!= '\0' ; c
= *++p
)
619 if (lineStarted
|| ! isspace (c
)) /* ignore leading spaces */
626 /* Consume repeating white space.
628 while (next
= *(p
+1) , isspace (next
) && next
!= NEWLINE
)
630 c
= ' '; /* force space character for any white space */
632 if (c
!= CRETURN
|| *(p
+ 1) != NEWLINE
)
642 static int writeXrefEntry (const tagEntryInfo
*const tag
)
644 const char *const line
=
645 readSourceLine (TagFile
.vLine
, tag
->filePosition
, NULL
);
648 if (Option
.tagFileFormat
== 1)
649 length
= fprintf (TagFile
.fp
, "%-16s %4lu %-16s ", tag
->name
,
650 tag
->lineNumber
, tag
->sourceFileName
);
652 length
= fprintf (TagFile
.fp
, "%-16s %-10s %4lu %-16s ", tag
->name
,
653 tag
->kindName
, tag
->lineNumber
, tag
->sourceFileName
);
655 length
+= writeCompactSourceLine (TagFile
.fp
, line
);
656 putc (NEWLINE
, TagFile
.fp
);
662 /* Truncates the text line containing the tag at the character following the
663 * tag, providing a character which designates the end of the tag.
665 static void truncateTagLine (
666 char *const line
, const char *const token
, const boolean discardNewline
)
668 char *p
= strstr (line
, token
);
673 if (*p
!= '\0' && ! (*p
== '\n' && discardNewline
))
674 ++p
; /* skip past character terminating character */
679 static int writeEtagsEntry (const tagEntryInfo
*const tag
)
683 if (tag
->isFileEntry
)
684 length
= fprintf (TagFile
.etags
.fp
, "\177%s\001%lu,0\n",
685 tag
->name
, tag
->lineNumber
);
690 readSourceLine (TagFile
.vLine
, tag
->filePosition
, &seekValue
);
692 if (tag
->truncateLine
)
693 truncateTagLine (line
, tag
->name
, TRUE
);
695 line
[strlen (line
) - 1] = '\0';
697 length
= fprintf (TagFile
.etags
.fp
, "%s\177%s\001%lu,%ld\n", line
,
698 tag
->name
, tag
->lineNumber
, seekValue
);
700 TagFile
.etags
.byteCount
+= length
;
705 static int addExtensionFields (const tagEntryInfo
*const tag
)
707 const char* const kindKey
= Option
.extensionFields
.kindKey
? "kind:" : "";
708 boolean first
= TRUE
;
709 const char* separator
= ";\"";
710 const char* const empty
= "";
712 /* "sep" returns a value only the first time it is evaluated */
713 #define sep (first ? (first = FALSE, separator) : empty)
715 if (tag
->kindName
!= NULL
&& (Option
.extensionFields
.kindLong
||
716 (Option
.extensionFields
.kind
&& tag
->kind
== '\0')))
717 length
+= fprintf (TagFile
.fp
,"%s\t%s%s", sep
, kindKey
, tag
->kindName
);
718 else if (tag
->kind
!= '\0' && (Option
.extensionFields
.kind
||
719 (Option
.extensionFields
.kindLong
&& tag
->kindName
== NULL
)))
720 length
+= fprintf (TagFile
.fp
, "%s\t%s%c", sep
, kindKey
, tag
->kind
);
722 if (Option
.extensionFields
.lineNumber
)
723 length
+= fprintf (TagFile
.fp
, "%s\tline:%ld", sep
, tag
->lineNumber
);
725 if (Option
.extensionFields
.language
&& tag
->language
!= NULL
)
726 length
+= fprintf (TagFile
.fp
, "%s\tlanguage:%s", sep
, tag
->language
);
728 if (Option
.extensionFields
.scope
&&
729 tag
->extensionFields
.scope
[0] != NULL
&&
730 tag
->extensionFields
.scope
[1] != NULL
)
731 length
+= fprintf (TagFile
.fp
, "%s\t%s:%s", sep
,
732 tag
->extensionFields
.scope
[0],
733 tag
->extensionFields
.scope
[1]);
735 if (Option
.extensionFields
.typeRef
&&
736 tag
->extensionFields
.typeRef
[0] != NULL
&&
737 tag
->extensionFields
.typeRef
[1] != NULL
)
738 length
+= fprintf (TagFile
.fp
, "%s\ttyperef:%s:%s", sep
,
739 tag
->extensionFields
.typeRef
[0],
740 tag
->extensionFields
.typeRef
[1]);
742 if (Option
.extensionFields
.fileScope
&& tag
->isFileScope
)
743 length
+= fprintf (TagFile
.fp
, "%s\tfile:", sep
);
745 if (Option
.extensionFields
.inheritance
&&
746 tag
->extensionFields
.inheritance
!= NULL
)
747 length
+= fprintf (TagFile
.fp
, "%s\tinherits:%s", sep
,
748 tag
->extensionFields
.inheritance
);
750 if (Option
.extensionFields
.access
&& tag
->extensionFields
.access
!= NULL
)
751 length
+= fprintf (TagFile
.fp
, "%s\taccess:%s", sep
,
752 tag
->extensionFields
.access
);
754 if (Option
.extensionFields
.implementation
&&
755 tag
->extensionFields
.implementation
!= NULL
)
756 length
+= fprintf (TagFile
.fp
, "%s\timplementation:%s", sep
,
757 tag
->extensionFields
.implementation
);
759 if (Option
.extensionFields
.signature
&&
760 tag
->extensionFields
.signature
!= NULL
)
761 length
+= fprintf (TagFile
.fp
, "%s\tsignature:%s", sep
,
762 tag
->extensionFields
.signature
);
768 static int writePatternEntry (const tagEntryInfo
*const tag
)
770 char *const line
= readSourceLine (TagFile
.vLine
, tag
->filePosition
, NULL
);
771 const int searchChar
= Option
.backward
? '?' : '/';
772 boolean newlineTerminated
;
775 if (tag
->truncateLine
)
776 truncateTagLine (line
, tag
->name
, FALSE
);
777 newlineTerminated
= (boolean
) (line
[strlen (line
) - 1] == '\n');
779 length
+= fprintf (TagFile
.fp
, "%c^", searchChar
);
780 length
+= writeSourceLine (TagFile
.fp
, line
);
781 length
+= fprintf (TagFile
.fp
, "%s%c", newlineTerminated
? "$":"", searchChar
);
786 static int writeLineNumberEntry (const tagEntryInfo
*const tag
)
788 return fprintf (TagFile
.fp
, "%lu", tag
->lineNumber
);
791 static int writeCtagsEntry (const tagEntryInfo
*const tag
)
794 if (tag
== NULL
|| tag
->name
== NULL
||
795 tag
->sourceFileName
== NULL
) {
796 printf("**warning**: returning -1 from writeCtagsEntry()\n");
800 length
= fprintf (TagFile
.fp
, "%s\t%s\t",
801 tag
->name
, tag
->sourceFileName
);
803 if (tag
->lineNumberEntry
)
804 length
+= writeLineNumberEntry (tag
);
806 length
+= writePatternEntry (tag
);
808 if (includeExtensionFlags ())
809 length
+= addExtensionFields (tag
);
811 length
+= fprintf (TagFile
.fp
, "\n");
816 extern void makeTagEntry (const tagEntryInfo
*const tag
)
818 Assert (tag
->name
!= NULL
);
819 if (tag
->name
[0] == '\0')
820 error (WARNING
, "ignoring null tag in %s", vStringValue (File
.name
));
825 DebugStatement ( debugEntry (tag
); )
827 if (NULL
!= TagEntryFunction
) {
828 length
= TagEntryFunction(tag
);
830 else if (Option
.xref
)
832 if (! tag
->isFileEntry
)
833 length
= writeXrefEntry (tag
);
835 else if (Option
.etags
) {
836 // it should crash here. Infact no TagFile.fp is initialized right now.
837 // We're using ctags within tagmanager.
838 length
= writeEtagsEntry (tag
);
841 length
= writeCtagsEntry (tag
);
844 ++TagFile
.numTags
.added
;
845 rememberMaxLengths (strlen (tag
->name
), (size_t) length
);
846 DebugStatement ( fflush (TagFile
.fp
); )
850 extern void initTagEntry (tagEntryInfo
*const e
, const char *const name
)
852 Assert (File
.source
.name
!= NULL
);
853 memset (e
, 0, sizeof (tagEntryInfo
));
854 e
->lineNumberEntry
= (boolean
) (Option
.locate
== EX_LINENUM
);
855 e
->lineNumber
= getSourceLineNumber ();
856 e
->language
= getSourceLanguageName ();
857 e
->filePosition
= getInputFilePosition ();
858 e
->sourceFileName
= getSourceFileTagPath ();
862 /* vi:set tabstop=4 shiftwidth=4: */