Hi,

I started working on support for reading Exuberant Ctags files. The commit message sums up most of the details I hope. If anyone wants to try it out and/or review the code, that would be much appreciated.

The relevant commit is here on my 'ctags-support' branch:
https://github.com/codebrainz/geany/commit/a642077c47ea444e9b13b9d2ad244be0866b562c

Or the attached patch has the same information.

I haven't tested a whole lot but it seems to be working well, allowing colourizing of the tags and auto-completion. It doesn't (yet) get the arguments list for functions (haven't found a clean way yet), so the calltips for function tags are pretty useless.

There is still much to be done, but it's a good start.

Cheers,
Matthew Brush
>From a642077c47ea444e9b13b9d2ad244be0866b562c Mon Sep 17 00:00:00 2001
From: Matthew Brush <matthewbr...@gmail.com>
Date: Tue, 24 May 2011 21:10:56 -0700
Subject: [PATCH] Add basic support for CTags tag file format.

Add new files readtags.c/h (public domain) from the CTags project[1]
for parsing CTags tags from tag files and incorporate those files
into the Autotools build system (todo: Waf).

Add a new enum to allow specifying between the (now) 3 supported
tag file formats, and adjust function signatures and usage
accordingly. (todo: is it a bad name, does it belong elsewhere)

Add new function to initialize tags from a CTags file, which
needs the filename and the language/mode.  This function isn't
re-entrant (or thread safe) since it holds the context in a
static var and doesn't re-init it unless all tags have been parsed
or an error occurs.  The '#if 0' will be used later to get more
info from the extension fields in the CTags format.

Adjust tm_tag_new_from_file() to allow passing in a filename to the
tag file (can be NULL for non-CTags formats).

Add lookup table to allow finding TMTagType based on the 'kind' that
is specified for tags in the CTags file.  The table can be search
by single character kind or string kind depending on what's in the
CTags file. (todo: notes in comments).  Also add functions for
performing lookings.

Add few experimental functions (inside #ifdef USE_HACKS) to play
with getting more information from the tags file.  This code
should not be used for real (yet, if ever).

Add detection of the CTags format marker (!_TAG) at the beginning
of the file and auto-detection if that's missing.  The idea of using
2 or more TAB characters to detect the format might be flawed since
this might occur in the other format(s).

[1] http://ctags.sourceforge.net/tool_support.html
---
 tagmanager/Makefile.am              |    1 +
 tagmanager/include/Makefile.am      |    3 +-
 tagmanager/include/readtags.h       |  252 +++++++++
 tagmanager/include/tm_source_file.h |    8 +
 tagmanager/include/tm_tag.h         |    8 +-
 tagmanager/include/tm_workspace.h   |    1 -
 tagmanager/readtags.c               |  964 +++++++++++++++++++++++++++++++++++
 tagmanager/tm_project.c             |    2 +-
 tagmanager/tm_tag.c                 |  440 ++++++++++++++++-
 tagmanager/tm_workspace.c           |   28 +-
 10 files changed, 1690 insertions(+), 17 deletions(-)
 create mode 100644 tagmanager/include/readtags.h
 create mode 100644 tagmanager/readtags.c

diff --git a/tagmanager/Makefile.am b/tagmanager/Makefile.am
index 754412c..720a320 100644
--- a/tagmanager/Makefile.am
+++ b/tagmanager/Makefile.am
@@ -77,6 +77,7 @@ libtagmanager_a_SOURCES =\
 	options.c\
 	parse.c\
 	read.c\
+	readtags.c \
 	sort.c\
 	strlist.c\
 	vstring.c\
diff --git a/tagmanager/include/Makefile.am b/tagmanager/include/Makefile.am
index 9fc9638..65ab9d7 100644
--- a/tagmanager/include/Makefile.am
+++ b/tagmanager/include/Makefile.am
@@ -1,5 +1,6 @@
 noinst_HEADERS = \
-	gnuregex.h
+	gnuregex.h \
+	readtags.h
 
 tagmanager_includedir = $(includedir)/geany/tagmanager
 tagmanager_include_HEADERS = \
diff --git a/tagmanager/include/readtags.h b/tagmanager/include/readtags.h
new file mode 100644
index 0000000..3a55210
--- /dev/null
+++ b/tagmanager/include/readtags.h
@@ -0,0 +1,252 @@
+/*
+*   $Id: readtags.h 443 2006-05-30 04:37:13Z darren $
+*
+*   Copyright (c) 1996-2003, Darren Hiebert
+*
+*   This source code is released for the public domain.
+*
+*   This file defines the public interface for looking up tag entries in tag
+*   files.
+*
+*   The functions defined in this interface are intended to provide tag file
+*   support to a software tool. The tag lookups provided are sufficiently fast
+*   enough to permit opening a sorted tag file, searching for a matching tag,
+*   then closing the tag file each time a tag is looked up (search times are
+*   on the order of hundreths of a second, even for huge tag files). This is
+*   the recommended use of this library for most tool applications. Adhering
+*   to this approach permits a user to regenerate a tag file at will without
+*   the tool needing to detect and resynchronize with changes to the tag file.
+*   Even for an unsorted 24MB tag file, tag searches take about one second.
+*/
+#ifndef READTAGS_H
+#define READTAGS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+*  MACROS
+*/
+
+/* Options for tagsSetSortType() */
+typedef enum {
+	TAG_UNSORTED, TAG_SORTED, TAG_FOLDSORTED
+} sortType ;
+
+/* Options for tagsFind() */
+#define TAG_FULLMATCH     0x0
+#define TAG_PARTIALMATCH  0x1
+
+#define TAG_OBSERVECASE   0x0
+#define TAG_IGNORECASE    0x2
+
+/*
+*  DATA DECLARATIONS
+*/
+
+typedef enum { TagFailure = 0, TagSuccess = 1 } tagResult;
+
+struct sTagFile;
+
+typedef struct sTagFile ctagFile;
+
+/* This structure contains information about the tag file. */
+typedef struct {
+
+	struct {
+			/* was the tag file successfully opened? */
+		int opened;
+
+			/* errno value when 'opened' is false */
+		int error_number;
+	} status;
+
+		/* information about the structure of the tag file */
+	struct {
+				/* format of tag file (1 = original, 2 = extended) */
+			short format;
+
+				/* how is the tag file sorted? */
+			sortType sort;
+	} file;
+
+
+		/* information about the program which created this tag file */
+	struct {
+			/* name of author of generating program (may be null) */
+		const char *author;
+
+			/* name of program (may be null) */
+		const char *name;
+
+			/* URL of distribution (may be null) */
+		const char *url;
+
+			/* program version (may be null) */
+		const char *version;
+	} program;
+
+} ctagFileInfo;
+
+/* This structure contains information about an extension field for a tag.
+ * These exist at the end of the tag in the form "key:value").
+ */
+typedef struct {
+
+		/* the key of the extension field */
+	const char *key;
+
+		/* the value of the extension field (may be an empty string) */
+	const char *value;
+
+} tagExtensionField;
+
+/* This structure contains information about a specific tag. */
+typedef struct {
+
+		/* name of tag */
+	const char *name;
+
+		/* path of source file containing definition of tag */
+	const char *file;
+
+		/* address for locating tag in source file */
+	struct {
+			/* pattern for locating source line
+			 * (may be NULL if not present) */
+		const char *pattern;
+
+			/* line number in source file of tag definition
+			 * (may be zero if not known) */
+		unsigned long lineNumber;
+	} address;
+
+		/* kind of tag (may by name, character, or NULL if not known) */
+	const char *kind;
+
+		/* is tag of file-limited scope? */
+	short fileScope;
+
+		/* miscellaneous extension fields */
+	struct {
+			/* number of entries in `list' */
+		unsigned short count;
+
+			/* list of key value pairs */
+		tagExtensionField *list;
+	} fields;
+
+} tagEntry;
+
+
+/*
+*  FUNCTION PROTOTYPES
+*/
+
+/*
+*  This function must be called before calling other functions in this
+*  library. It is passed the path to the tag file to read and a (possibly
+*  null) pointer to a structure which, if not null, will be populated with
+*  information about the tag file. If successful, the function will return a
+*  handle which must be supplied to other calls to read information from the
+*  tag file, and info.status.opened will be set to true. If unsuccessful,
+*  info.status.opened will be set to false and info.status.error_number will
+*  be set to the errno value representing the system error preventing the tag
+*  file from being successfully opened.
+*/
+extern ctagFile *tagsOpen (const char *const filePath, ctagFileInfo *const info);
+
+/*
+*  This function allows the client to override the normal automatic detection
+*  of how a tag file is sorted. Permissible values for `type' are
+*  TAG_UNSORTED, TAG_SORTED, TAG_FOLDSORTED. Tag files in the new extended
+*  format contain a key indicating whether or not they are sorted. However,
+*  tag files in the original format do not contain such a key even when
+*  sorted, preventing this library from taking advantage of fast binary
+*  lookups. If the client knows that such an unmarked tag file is indeed
+*  sorted (or not), it can override the automatic detection. Note that
+*  incorrect lookup results will result if a tag file is marked as sorted when
+*  it actually is not. The function will return TagSuccess if called on an
+*  open tag file or TagFailure if not.
+*/
+extern tagResult tagsSetSortType (ctagFile *const file, const sortType type);
+
+/*
+*  Reads the first tag in the file, if any. It is passed the handle to an
+*  opened tag file and a (possibly null) pointer to a structure which, if not
+*  null, will be populated with information about the first tag file entry.
+*  The function will return TagSuccess another tag entry is found, or
+*  TagFailure if not (i.e. it reached end of file).
+*/
+extern tagResult tagsFirst (ctagFile *const file, tagEntry *const entry);
+
+/*
+*  Step to the next tag in the file, if any. It is passed the handle to an
+*  opened tag file and a (possibly null) pointer to a structure which, if not
+*  null, will be populated with information about the next tag file entry. The
+*  function will return TagSuccess another tag entry is found, or TagFailure
+*  if not (i.e. it reached end of file). It will always read the first tag in
+*  the file immediately after calling tagsOpen().
+*/
+extern tagResult tagsNext (ctagFile *const file, tagEntry *const entry);
+
+/*
+*  Retrieve the value associated with the extension field for a specified key.
+*  It is passed a pointer to a structure already populated with values by a
+*  previous call to tagsNext(), tagsFind(), or tagsFindNext(), and a string
+*  containing the key of the desired extension field. If no such field of the
+*  specified key exists, the function will return null.
+*/
+extern const char *tagsField (const tagEntry *const entry, const char *const key);
+
+/*
+*  Find the first tag matching `name'. The structure pointed to by `entry'
+*  will be populated with information about the tag file entry. If a tag file
+*  is sorted using the C locale, a binary search algorithm is used to search
+*  the tag file, resulting in very fast tag lookups, even in huge tag files.
+*  Various options controlling the matches can be combined by bit-wise or-ing
+*  certain values together. The available values are:
+*
+*    TAG_PARTIALMATCH
+*        Tags whose leading characters match `name' will qualify.
+*
+*    TAG_FULLMATCH
+*        Only tags whose full lengths match `name' will qualify.
+*
+*    TAG_IGNORECASE
+*        Matching will be performed in a case-insenstive manner. Note that
+*        this disables binary searches of the tag file.
+*
+*    TAG_OBSERVECASE
+*        Matching will be performed in a case-senstive manner. Note that
+*        this enables binary searches of the tag file.
+*
+*  The function will return TagSuccess if a tag matching the name is found, or
+*  TagFailure if not.
+*/
+extern tagResult tagsFind (ctagFile *const file, tagEntry *const entry, const char *const name, const int options);
+
+/*
+*  Find the next tag matching the name and options supplied to the most recent
+*  call to tagsFind() for the same tag file. The structure pointed to by
+*  `entry' will be populated with information about the tag file entry. The
+*  function will return TagSuccess if another tag matching the name is found,
+*  or TagFailure if not.
+*/
+extern tagResult tagsFindNext (ctagFile *const file, tagEntry *const entry);
+
+/*
+*  Call tagsTerminate() at completion of reading the tag file, which will
+*  close the file and free any internal memory allocated. The function will
+*  return TagFailure is no file is currently open, TagSuccess otherwise.
+*/
+extern tagResult tagsClose (ctagFile *const file);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/tagmanager/include/tm_source_file.h b/tagmanager/include/tm_source_file.h
index 4120e78..6fffba7 100644
--- a/tagmanager/include/tm_source_file.h
+++ b/tagmanager/include/tm_source_file.h
@@ -45,6 +45,14 @@ typedef struct
 } TMSourceFile;
 
 
+typedef enum
+{
+	TM_SOURCE_FILE_FORMAT_TAG_MANAGER,
+	TM_SOURCE_FILE_FORMAT_PIPE,
+	TM_SOURCE_FILE_FORMAT_CTAGS
+} TMSourceFileFormat;
+
+
 /* Initializes a TMSourceFile structure from a file name. */
 gboolean tm_source_file_init(TMSourceFile *source_file, const char *file_name,
 							 gboolean update, const char *name);
diff --git a/tagmanager/include/tm_tag.h b/tagmanager/include/tm_tag.h
index 0107e99..04fbf55 100644
--- a/tagmanager/include/tm_tag.h
+++ b/tagmanager/include/tm_tag.h
@@ -195,6 +195,12 @@ gboolean tm_tag_init_from_file(TMTag *tag, TMSourceFile *file, FILE *fp);
 gboolean tm_tag_init_from_file_alt(TMTag *tag, TMSourceFile *file, FILE *fp);
 
 /*!
+ Same as tm_tag_init_from_file(), but reads tags from an exuberant CTags
+ file.  Also a filename is required.
+*/
+gboolean tm_tag_init_from_file_ctags(TMTag *tag, TMSourceFile *file, FILE *fp, const gchar *ctags_file, gint mode);
+
+/*!
  Creates a new tag structure from a tagEntryInfo pointer and a TMSOurceFile pointer
  and returns a pointer to it.
  \param file - Pointer to the TMSourceFile structure containing the tag
@@ -207,7 +213,7 @@ TMTag *tm_tag_new(TMSourceFile *file, const tagEntryInfo *tag_entry);
  Same as tm_tag_new() except that the tag attributes are read from file.
  \param mode langType to use for the tag.
 */
-TMTag *tm_tag_new_from_file(TMSourceFile *file, FILE *fp, gint mode, gboolean format_pipe);
+TMTag *tm_tag_new_from_file(TMSourceFile *file, FILE *fp, gint mode, TMSourceFileFormat format, const gchar *tags_file);
 
 /*!
  Writes tag information to the given FILE *.
diff --git a/tagmanager/include/tm_workspace.h b/tagmanager/include/tm_workspace.h
index da6d37c..b6da281 100644
--- a/tagmanager/include/tm_workspace.h
+++ b/tagmanager/include/tm_workspace.h
@@ -21,7 +21,6 @@ extern "C"
 #endif
 
 
-
 /*! The Tag Manager Workspace. This is a singleton work object containing a list
  of work objects. These can be either individual files or project containing
  multiple files. There is also a global tag list which can be loaded or
diff --git a/tagmanager/readtags.c b/tagmanager/readtags.c
new file mode 100644
index 0000000..60de268
--- /dev/null
+++ b/tagmanager/readtags.c
@@ -0,0 +1,964 @@
+/*
+*   $Id: readtags.c 592 2007-07-31 03:30:41Z dhiebert $
+*
+*   Copyright (c) 1996-2003, Darren Hiebert
+*
+*   This source code is released into the public domain.
+*
+*   This module contains functions for reading tag files.
+*/
+
+/*
+*   INCLUDE FILES
+*/
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>  /* to declare off_t */
+
+#include "readtags.h"
+
+/*
+*   MACROS
+*/
+#define TAB '\t'
+
+
+/*
+*   DATA DECLARATIONS
+*/
+typedef struct {
+	size_t size;
+	char *buffer;
+} vstring;
+
+/* Information about current tag file */
+struct sTagFile {
+		/* has the file been opened and this structure initialized? */
+	short initialized;
+		/* format of tag file */
+	short format;
+		/* how is the tag file sorted? */
+	sortType sortMethod;
+		/* pointer to file structure */
+	FILE* fp;
+		/* file position of first character of `line' */
+	off_t pos;
+		/* size of tag file in seekable positions */
+	off_t size;
+		/* last line read */
+	vstring line;
+		/* name of tag in last line read */
+	vstring name;
+		/* defines tag search state */
+	struct {
+				/* file position of last match for tag */
+			off_t pos;
+				/* name of tag last searched for */
+			char *name;
+				/* length of name for partial matches */
+			size_t nameLength;
+				/* peforming partial match */
+			short partial;
+				/* ignoring case */
+			short ignorecase;
+	} search;
+		/* miscellaneous extension fields */
+	struct {
+				/* number of entries in `list' */
+			unsigned short max;
+				/* list of key value pairs */
+			tagExtensionField *list;
+	} fields;
+		/* buffers to be freed at close */
+	struct {
+			/* name of program author */
+		char *author;
+			/* name of program */
+		char *name;
+			/* URL of distribution */
+		char *url;
+			/* program version */
+		char *version;
+	} program;
+};
+
+/*
+*   DATA DEFINITIONS
+*/
+const char *const EmptyString = "";
+const char *const PseudoTagPrefix = "!_";
+
+/*
+*   FUNCTION DEFINITIONS
+*/
+
+/*
+ * Compare two strings, ignoring case.
+ * Return 0 for match, < 0 for smaller, > 0 for bigger
+ * Make sure case is folded to uppercase in comparison (like for 'sort -f')
+ * This makes a difference when one of the chars lies between upper and lower
+ * ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !)
+ */
+static int struppercmp (const char *s1, const char *s2)
+{
+	int result;
+	do
+	{
+		result = toupper ((int) *s1) - toupper ((int) *s2);
+	} while (result == 0  &&  *s1++ != '\0'  &&  *s2++ != '\0');
+	return result;
+}
+
+static int strnuppercmp (const char *s1, const char *s2, size_t n)
+{
+	int result;
+	do
+	{
+		result = toupper ((int) *s1) - toupper ((int) *s2);
+	} while (result == 0  &&  --n > 0  &&  *s1++ != '\0'  &&  *s2++ != '\0');
+	return result;
+}
+
+static int growString (vstring *s)
+{
+	int result = 0;
+	size_t newLength;
+	char *newLine;
+	if (s->size == 0)
+	{
+		newLength = 128;
+		newLine = (char*) malloc (newLength);
+		*newLine = '\0';
+	}
+	else
+	{
+		newLength = 2 * s->size;
+		newLine = (char*) realloc (s->buffer, newLength);
+	}
+	if (newLine == NULL)
+		perror ("string too large");
+	else
+	{
+		s->buffer = newLine;
+		s->size = newLength;
+		result = 1;
+	}
+	return result;
+}
+
+/* Copy name of tag out of tag line */
+static void copyName (ctagFile *const file)
+{
+	size_t length;
+	const char *end = strchr (file->line.buffer, '\t');
+	if (end == NULL)
+	{
+		end = strchr (file->line.buffer, '\n');
+		if (end == NULL)
+			end = strchr (file->line.buffer, '\r');
+	}
+	if (end != NULL)
+		length = end - file->line.buffer;
+	else
+		length = strlen (file->line.buffer);
+	while (length >= file->name.size)
+		growString (&file->name);
+	strncpy (file->name.buffer, file->line.buffer, length);
+	file->name.buffer [length] = '\0';
+}
+
+static int readTagLineRaw (ctagFile *const file)
+{
+	int result = 1;
+	int reReadLine;
+
+	/*  If reading the line places any character other than a null or a
+	 *  newline at the last character position in the buffer (one less than
+	 *  the buffer size), then we must resize the buffer and reattempt to read
+	 *  the line.
+	 */
+	do
+	{
+		char *const pLastChar = file->line.buffer + file->line.size - 2;
+		char *line;
+
+		file->pos = ftell (file->fp);
+		reReadLine = 0;
+		*pLastChar = '\0';
+		line = fgets (file->line.buffer, (int) file->line.size, file->fp);
+		if (line == NULL)
+		{
+			/* read error */
+			if (! feof (file->fp))
+				perror ("readTagLine");
+			result = 0;
+		}
+		else if (*pLastChar != '\0'  &&
+					*pLastChar != '\n'  &&  *pLastChar != '\r')
+		{
+			/*  buffer overflow */
+			growString (&file->line);
+			fseek (file->fp, file->pos, SEEK_SET);
+			reReadLine = 1;
+		}
+		else
+		{
+			size_t i = strlen (file->line.buffer);
+			while (i > 0  &&
+				   (file->line.buffer [i - 1] == '\n' || file->line.buffer [i - 1] == '\r'))
+			{
+				file->line.buffer [i - 1] = '\0';
+				--i;
+			}
+		}
+	} while (reReadLine  &&  result);
+	if (result)
+		copyName (file);
+	return result;
+}
+
+static int readTagLine (ctagFile *const file)
+{
+	int result;
+	do
+	{
+		result = readTagLineRaw (file);
+	} while (result && *file->name.buffer == '\0');
+	return result;
+}
+
+static tagResult growFields (ctagFile *const file)
+{
+	tagResult result = TagFailure;
+	unsigned short newCount = (unsigned short) 2 * file->fields.max;
+	tagExtensionField *newFields = (tagExtensionField*)
+			realloc (file->fields.list, newCount * sizeof (tagExtensionField));
+	if (newFields == NULL)
+		perror ("too many extension fields");
+	else
+	{
+		file->fields.list = newFields;
+		file->fields.max = newCount;
+		result = TagSuccess;
+	}
+	return result;
+}
+
+static void parseExtensionFields (ctagFile *const file, tagEntry *const entry,
+								  char *const string)
+{
+	char *p = string;
+	while (p != NULL  &&  *p != '\0')
+	{
+		while (*p == TAB)
+			*p++ = '\0';
+		if (*p != '\0')
+		{
+			char *colon;
+			char *field = p;
+			p = strchr (p, TAB);
+			if (p != NULL)
+				*p++ = '\0';
+			colon = strchr (field, ':');
+			if (colon == NULL)
+				entry->kind = field;
+			else
+			{
+				const char *key = field;
+				const char *value = colon + 1;
+				*colon = '\0';
+				if (strcmp (key, "kind") == 0)
+					entry->kind = value;
+				else if (strcmp (key, "file") == 0)
+					entry->fileScope = 1;
+				else if (strcmp (key, "line") == 0)
+					entry->address.lineNumber = atol (value);
+				else
+				{
+					if (entry->fields.count == file->fields.max)
+						growFields (file);
+					file->fields.list [entry->fields.count].key = key;
+					file->fields.list [entry->fields.count].value = value;
+					++entry->fields.count;
+				}
+			}
+		}
+	}
+}
+
+static void parseTagLine (ctagFile *file, tagEntry *const entry)
+{
+	int i;
+	char *p = file->line.buffer;
+	char *tab = strchr (p, TAB);
+
+	entry->fields.list = NULL;
+	entry->fields.count = 0;
+	entry->kind = NULL;
+	entry->fileScope = 0;
+
+	entry->name = p;
+	if (tab != NULL)
+	{
+		*tab = '\0';
+		p = tab + 1;
+		entry->file = p;
+		tab = strchr (p, TAB);
+		if (tab != NULL)
+		{
+			int fieldsPresent;
+			*tab = '\0';
+			p = tab + 1;
+			if (*p == '/'  ||  *p == '?')
+			{
+				/* parse pattern */
+				int delimiter = *(unsigned char*) p;
+				entry->address.lineNumber = 0;
+				entry->address.pattern = p;
+				do
+				{
+					p = strchr (p + 1, delimiter);
+				} while (p != NULL  &&  *(p - 1) == '\\');
+				if (p == NULL)
+				{
+					/* invalid pattern */
+				}
+				else
+					++p;
+			}
+			else if (isdigit ((int) *(unsigned char*) p))
+			{
+				/* parse line number */
+				entry->address.pattern = p;
+				entry->address.lineNumber = atol (p);
+				while (isdigit ((int) *(unsigned char*) p))
+					++p;
+			}
+			else
+			{
+				/* invalid pattern */
+			}
+			fieldsPresent = (strncmp (p, ";\"", 2) == 0);
+			*p = '\0';
+			if (fieldsPresent)
+				parseExtensionFields (file, entry, p + 2);
+		}
+	}
+	if (entry->fields.count > 0)
+		entry->fields.list = file->fields.list;
+	for (i = entry->fields.count  ;  i < file->fields.max  ;  ++i)
+	{
+		file->fields.list [i].key = NULL;
+		file->fields.list [i].value = NULL;
+	}
+}
+
+static char *duplicate (const char *str)
+{
+	char *result = NULL;
+	if (str != NULL)
+	{
+		result = strdup (str);
+		if (result == NULL)
+			perror (NULL);
+	}
+	return result;
+}
+
+static void readPseudoTags (ctagFile *const file, ctagFileInfo *const info)
+{
+	fpos_t startOfLine;
+	const size_t prefixLength = strlen (PseudoTagPrefix);
+	if (info != NULL)
+	{
+		info->file.format     = 1;
+		info->file.sort       = TAG_UNSORTED;
+		info->program.author  = NULL;
+		info->program.name    = NULL;
+		info->program.url     = NULL;
+		info->program.version = NULL;
+	}
+	while (1)
+	{
+		fgetpos (file->fp, &startOfLine);
+		if (! readTagLine (file))
+			break;
+		if (strncmp (file->line.buffer, PseudoTagPrefix, prefixLength) != 0)
+			break;
+		else
+		{
+			tagEntry entry;
+			const char *key, *value;
+			parseTagLine (file, &entry);
+			key = entry.name + prefixLength;
+			value = entry.file;
+			if (strcmp (key, "TAG_FILE_SORTED") == 0)
+				file->sortMethod = (sortType) atoi (value);
+			else if (strcmp (key, "TAG_FILE_FORMAT") == 0)
+				file->format = (short) atoi (value);
+			else if (strcmp (key, "TAG_PROGRAM_AUTHOR") == 0)
+				file->program.author = duplicate (value);
+			else if (strcmp (key, "TAG_PROGRAM_NAME") == 0)
+				file->program.name = duplicate (value);
+			else if (strcmp (key, "TAG_PROGRAM_URL") == 0)
+				file->program.url = duplicate (value);
+			else if (strcmp (key, "TAG_PROGRAM_VERSION") == 0)
+				file->program.version = duplicate (value);
+			if (info != NULL)
+			{
+				info->file.format     = file->format;
+				info->file.sort       = file->sortMethod;
+				info->program.author  = file->program.author;
+				info->program.name    = file->program.name;
+				info->program.url     = file->program.url;
+				info->program.version = file->program.version;
+			}
+		}
+	}
+	fsetpos (file->fp, &startOfLine);
+}
+
+static void gotoFirstLogicalTag (ctagFile *const file)
+{
+	fpos_t startOfLine;
+	const size_t prefixLength = strlen (PseudoTagPrefix);
+	rewind (file->fp);
+	while (1)
+	{
+		fgetpos (file->fp, &startOfLine);
+		if (! readTagLine (file))
+			break;
+		if (strncmp (file->line.buffer, PseudoTagPrefix, prefixLength) != 0)
+			break;
+	}
+	fsetpos (file->fp, &startOfLine);
+}
+
+static ctagFile *initialize (const char *const filePath, ctagFileInfo *const info)
+{
+	ctagFile *result = (ctagFile*) calloc ((size_t) 1, sizeof (ctagFile));
+	if (result != NULL)
+	{
+		growString (&result->line);
+		growString (&result->name);
+		result->fields.max = 20;
+		result->fields.list = (tagExtensionField*) calloc (
+			result->fields.max, sizeof (tagExtensionField));
+		result->fp = fopen (filePath, "r");
+		if (result->fp == NULL)
+		{
+			free (result);
+			result = NULL;
+			info->status.error_number = errno;
+		}
+		else
+		{
+			fseek (result->fp, 0, SEEK_END);
+			result->size = ftell (result->fp);
+			rewind (result->fp);
+			readPseudoTags (result, info);
+			info->status.opened = 1;
+			result->initialized = 1;
+		}
+	}
+	return result;
+}
+
+static void terminate (ctagFile *const file)
+{
+	fclose (file->fp);
+
+	free (file->line.buffer);
+	free (file->name.buffer);
+	free (file->fields.list);
+
+	if (file->program.author != NULL)
+		free (file->program.author);
+	if (file->program.name != NULL)
+		free (file->program.name);
+	if (file->program.url != NULL)
+		free (file->program.url);
+	if (file->program.version != NULL)
+		free (file->program.version);
+	if (file->search.name != NULL)
+		free (file->search.name);
+
+	memset (file, 0, sizeof (ctagFile));
+
+	free (file);
+}
+
+static tagResult readNext (ctagFile *const file, tagEntry *const entry)
+{
+	tagResult result;
+	if (file == NULL  ||  ! file->initialized)
+		result = TagFailure;
+	else if (! readTagLine (file))
+		result = TagFailure;
+	else
+	{
+		if (entry != NULL)
+			parseTagLine (file, entry);
+		result = TagSuccess;
+	}
+	return result;
+}
+
+static const char *readFieldValue (
+	const tagEntry *const entry, const char *const key)
+{
+	const char *result = NULL;
+	int i;
+	if (strcmp (key, "kind") == 0)
+		result = entry->kind;
+	else if (strcmp (key, "file") == 0)
+		result = EmptyString;
+	else for (i = 0  ;  i < entry->fields.count  &&  result == NULL  ;  ++i)
+		if (strcmp (entry->fields.list [i].key, key) == 0)
+			result = entry->fields.list [i].value;
+	return result;
+}
+
+static int readTagLineSeek (ctagFile *const file, const off_t pos)
+{
+	int result = 0;
+	if (fseek (file->fp, pos, SEEK_SET) == 0)
+	{
+		result = readTagLine (file);  /* read probable partial line */
+		if (pos > 0  &&  result)
+			result = readTagLine (file);  /* read complete line */
+	}
+	return result;
+}
+
+static int nameComparison (ctagFile *const file)
+{
+	int result;
+	if (file->search.ignorecase)
+	{
+		if (file->search.partial)
+			result = strnuppercmp (file->search.name, file->name.buffer,
+					file->search.nameLength);
+		else
+			result = struppercmp (file->search.name, file->name.buffer);
+	}
+	else
+	{
+		if (file->search.partial)
+			result = strncmp (file->search.name, file->name.buffer,
+					file->search.nameLength);
+		else
+		{
+		    if (file->search.name != NULL && file->name.buffer != NULL)
+			    result = strcmp (file->search.name, file->name.buffer);
+			else
+			    result = -1;
+		}
+	}
+	return result;
+}
+
+static void findFirstNonMatchBefore (ctagFile *const file)
+{
+#define JUMP_BACK 512
+	int more_lines;
+	int comp;
+	off_t start = file->pos;
+	off_t pos = start;
+	do
+	{
+		if (pos < (off_t) JUMP_BACK)
+			pos = 0;
+		else
+			pos = pos - JUMP_BACK;
+		more_lines = readTagLineSeek (file, pos);
+		comp = nameComparison (file);
+	} while (more_lines  &&  comp == 0  &&  pos > 0  &&  pos < start);
+}
+
+static tagResult findFirstMatchBefore (ctagFile *const file)
+{
+	tagResult result = TagFailure;
+	int more_lines;
+	off_t start = file->pos;
+	findFirstNonMatchBefore (file);
+	do
+	{
+		more_lines = readTagLine (file);
+		if (nameComparison (file) == 0)
+			result = TagSuccess;
+	} while (more_lines  &&  result != TagSuccess  &&  file->pos < start);
+	return result;
+}
+
+static tagResult findBinary (ctagFile *const file)
+{
+	tagResult result = TagFailure;
+	off_t lower_limit = 0;
+	off_t upper_limit = file->size;
+	off_t last_pos = 0;
+	off_t pos = upper_limit / 2;
+	while (result != TagSuccess)
+	{
+		if (! readTagLineSeek (file, pos))
+		{
+			/* in case we fell off end of file */
+			result = findFirstMatchBefore (file);
+			break;
+		}
+		else if (pos == last_pos)
+		{
+			/* prevent infinite loop if we backed up to beginning of file */
+			break;
+		}
+		else
+		{
+			const int comp = nameComparison (file);
+			last_pos = pos;
+			if (comp < 0)
+			{
+				upper_limit = pos;
+				pos = lower_limit + ((upper_limit - lower_limit) / 2);
+			}
+			else if (comp > 0)
+			{
+				lower_limit = pos;
+				pos = lower_limit + ((upper_limit - lower_limit) / 2);
+			}
+			else if (pos == 0)
+				result = TagSuccess;
+			else
+				result = findFirstMatchBefore (file);
+		}
+	}
+	return result;
+}
+
+static tagResult findSequential (ctagFile *const file)
+{
+	tagResult result = TagFailure;
+	if (file->initialized)
+	{
+		while (result == TagFailure  &&  readTagLine (file))
+		{
+			if (nameComparison (file) == 0)
+				result = TagSuccess;
+		}
+	}
+	return result;
+}
+
+static tagResult find (ctagFile *const file, tagEntry *const entry,
+					   const char *const name, const int options)
+{
+	tagResult result;
+	if (file->search.name != NULL)
+		free (file->search.name);
+	file->search.name = duplicate (name);
+	file->search.nameLength = strlen (name);
+	file->search.partial = (options & TAG_PARTIALMATCH) != 0;
+	file->search.ignorecase = (options & TAG_IGNORECASE) != 0;
+	fseek (file->fp, 0, SEEK_END);
+	file->size = ftell (file->fp);
+	rewind (file->fp);
+	if ((file->sortMethod == TAG_SORTED      && !file->search.ignorecase) ||
+		(file->sortMethod == TAG_FOLDSORTED  &&  file->search.ignorecase))
+	{
+#ifdef DEBUG
+		printf ("<performing binary search>\n");
+#endif
+		result = findBinary (file);
+	}
+	else
+	{
+#ifdef DEBUG
+		printf ("<performing sequential search>\n");
+#endif
+		result = findSequential (file);
+	}
+
+	if (result != TagSuccess)
+		file->search.pos = file->size;
+	else
+	{
+		file->search.pos = file->pos;
+		if (entry != NULL)
+			parseTagLine (file, entry);
+	}
+	return result;
+}
+
+static tagResult findNext (ctagFile *const file, tagEntry *const entry)
+{
+	tagResult result;
+	if ((file->sortMethod == TAG_SORTED      && !file->search.ignorecase) ||
+		(file->sortMethod == TAG_FOLDSORTED  &&  file->search.ignorecase))
+	{
+		result = tagsNext (file, entry);
+		if (result == TagSuccess  && nameComparison (file) != 0)
+			result = TagFailure;
+	}
+	else
+	{
+		result = findSequential (file);
+		if (result == TagSuccess  &&  entry != NULL)
+			parseTagLine (file, entry);
+	}
+	return result;
+}
+
+/*
+*  EXTERNAL INTERFACE
+*/
+
+extern ctagFile *tagsOpen (const char *const filePath, ctagFileInfo *const info)
+{
+	return initialize (filePath, info);
+}
+
+extern tagResult tagsSetSortType (ctagFile *const file, const sortType type)
+{
+	tagResult result = TagFailure;
+	if (file != NULL  &&  file->initialized)
+	{
+		file->sortMethod = type;
+		result = TagSuccess;
+	}
+	return result;
+}
+
+extern tagResult tagsFirst (ctagFile *const file, tagEntry *const entry)
+{
+	tagResult result = TagFailure;
+	if (file != NULL  &&  file->initialized)
+	{
+		gotoFirstLogicalTag (file);
+		result = readNext (file, entry);
+	}
+	return result;
+}
+
+extern tagResult tagsNext (ctagFile *const file, tagEntry *const entry)
+{
+	tagResult result = TagFailure;
+	if (file != NULL  &&  file->initialized)
+		result = readNext (file, entry);
+	return result;
+}
+
+extern const char *tagsField (const tagEntry *const entry, const char *const key)
+{
+	const char *result = NULL;
+	if (entry != NULL)
+		result = readFieldValue (entry, key);
+	return result;
+}
+
+extern tagResult tagsFind (ctagFile *const file, tagEntry *const entry,
+						   const char *const name, const int options)
+{
+	tagResult result = TagFailure;
+	if (file != NULL  &&  file->initialized)
+		result = find (file, entry, name, options);
+	return result;
+}
+
+extern tagResult tagsFindNext (ctagFile *const file, tagEntry *const entry)
+{
+	tagResult result = TagFailure;
+	if (file != NULL  &&  file->initialized)
+		result = findNext (file, entry);
+	return result;
+}
+
+extern tagResult tagsClose (ctagFile *const file)
+{
+	tagResult result = TagFailure;
+	if (file != NULL  &&  file->initialized)
+	{
+		terminate (file);
+		result = TagSuccess;
+	}
+	return result;
+}
+
+/*
+*  TEST FRAMEWORK
+*/
+
+#ifdef READTAGS_MAIN
+
+static const char *TagFileName = "tags";
+static const char *ProgramName;
+static int extensionFields;
+static int SortOverride;
+static sortType SortMethod;
+
+static void printTag (const tagEntry *entry)
+{
+	int i;
+	int first = 1;
+	const char* separator = ";\"";
+	const char* const empty = "";
+/* "sep" returns a value only the first time it is evaluated */
+#define sep (first ? (first = 0, separator) : empty)
+	printf ("%s\t%s\t%s",
+		entry->name, entry->file, entry->address.pattern);
+	if (extensionFields)
+	{
+		if (entry->kind != NULL  &&  entry->kind [0] != '\0')
+			printf ("%s\tkind:%s", sep, entry->kind);
+		if (entry->fileScope)
+			printf ("%s\tfile:", sep);
+#if 0
+		if (entry->address.lineNumber > 0)
+			printf ("%s\tline:%lu", sep, entry->address.lineNumber);
+#endif
+		for (i = 0  ;  i < entry->fields.count  ;  ++i)
+			printf ("%s\t%s:%s", sep, entry->fields.list [i].key,
+				entry->fields.list [i].value);
+	}
+	putchar ('\n');
+#undef sep
+}
+
+static void findTag (const char *const name, const int options)
+{
+	ctagFileInfo info;
+	tagEntry entry;
+	ctagFile *const file = tagsOpen (TagFileName, &info);
+	if (file == NULL)
+	{
+		fprintf (stderr, "%s: cannot open tag file: %s: %s\n",
+				ProgramName, strerror (info.status.error_number), name);
+		exit (1);
+	}
+	else
+	{
+		if (SortOverride)
+			tagsSetSortType (file, SortMethod);
+		if (tagsFind (file, &entry, name, options) == TagSuccess)
+		{
+			do
+			{
+				printTag (&entry);
+			} while (tagsFindNext (file, &entry) == TagSuccess);
+		}
+		tagsClose (file);
+	}
+}
+
+static void listTags (void)
+{
+	ctagFileInfo info;
+	tagEntry entry;
+	ctagFile *const file = tagsOpen (TagFileName, &info);
+	if (file == NULL)
+	{
+		fprintf (stderr, "%s: cannot open tag file: %s: %s\n",
+				ProgramName, strerror (info.status.error_number), TagFileName);
+		exit (1);
+	}
+	else
+	{
+		while (tagsNext (file, &entry) == TagSuccess)
+			printTag (&entry);
+		tagsClose (file);
+	}
+}
+
+const char *const Usage =
+	"Find tag file entries matching specified names.\n\n"
+	"Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n"
+	"Options:\n"
+	"    -e           Include extension fields in output.\n"
+	"    -i           Perform case-insensitive matching.\n"
+	"    -l           List all tags.\n"
+	"    -p           Perform partial matching.\n"
+	"    -s[0|1|2]    Override sort detection of tag file.\n"
+	"    -t file      Use specified tag file (default: \"tags\").\n"
+	"Note that options are acted upon as encountered, so order is significant.\n";
+
+extern int main (int argc, char **argv)
+{
+	int options = 0;
+	int actionSupplied = 0;
+	int i;
+	ProgramName = argv [0];
+	if (argc == 1)
+	{
+		fprintf (stderr, Usage, ProgramName);
+		exit (1);
+	}
+	for (i = 1  ;  i < argc  ;  ++i)
+	{
+		const char *const arg = argv [i];
+		if (arg [0] != '-')
+		{
+			findTag (arg, options);
+			actionSupplied = 1;
+		}
+		else
+		{
+			size_t j;
+			for (j = 1  ;  arg [j] != '\0'  ;  ++j)
+			{
+				switch (arg [j])
+				{
+					case 'e': extensionFields = 1;         break;
+					case 'i': options |= TAG_IGNORECASE;   break;
+					case 'p': options |= TAG_PARTIALMATCH; break;
+					case 'l': listTags (); actionSupplied = 1; break;
+
+					case 't':
+						if (arg [j+1] != '\0')
+						{
+							TagFileName = arg + j + 1;
+							j += strlen (TagFileName);
+						}
+						else if (i + 1 < argc)
+							TagFileName = argv [++i];
+						else
+						{
+							fprintf (stderr, Usage, ProgramName);
+							exit (1);
+						}
+						break;
+					case 's':
+						SortOverride = 1;
+						++j;
+						if (arg [j] == '\0')
+							SortMethod = TAG_SORTED;
+						else if (strchr ("012", arg[j]) != NULL)
+							SortMethod = (sortType) (arg[j] - '0');
+						else
+						{
+							fprintf (stderr, Usage, ProgramName);
+							exit (1);
+						}
+						break;
+					default:
+						fprintf (stderr, "%s: unknown option: %c\n",
+									ProgramName, arg[j]);
+						exit (1);
+						break;
+				}
+			}
+		}
+	}
+	if (! actionSupplied)
+	{
+		fprintf (stderr,
+			"%s: no action specified: specify tag name(s) or -l option\n",
+			ProgramName);
+		exit (1);
+	}
+	return 0;
+}
+
+#endif
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/tagmanager/tm_project.c b/tagmanager/tm_project.c
index 5800df8..bc8e893 100644
--- a/tagmanager/tm_project.c
+++ b/tagmanager/tm_project.c
@@ -384,7 +384,7 @@ gboolean tm_project_open(TMProject *project, gboolean force)
 	tm_project_set_ignorelist(project);
 	if (NULL == (fp = g_fopen(project->work_object.file_name, "r")))
 		return FALSE;
-	while (NULL != (tag = tm_tag_new_from_file(source_file, fp, 0, FALSE)))
+	while (NULL != (tag = tm_tag_new_from_file(source_file, fp, 0, TM_SOURCE_FILE_FORMAT_TAG_MANAGER, NULL)))
 	{
 		if (tm_tag_file_t == tag->type)
 		{
diff --git a/tagmanager/tm_tag.c b/tagmanager/tm_tag.c
index 708f5dc..d67b8f0 100644
--- a/tagmanager/tm_tag.c
+++ b/tagmanager/tm_tag.c
@@ -15,6 +15,7 @@
 #include "entry.h"
 #include "parse.h"
 #include "read.h"
+#include "readtags.h"
 #define LIBCTAGS_DEFINED
 #include "tm_tag.h"
 
@@ -377,17 +378,446 @@ gboolean tm_tag_init_from_file_alt(TMTag *tag, TMSourceFile *file, FILE *fp)
 	return TRUE;
 }
 
-TMTag *tm_tag_new_from_file(TMSourceFile *file, FILE *fp, gint mode, gboolean format_pipe)
+
+/*
+ * Lookup table to map CTags 'kinds' to 'TMTagTypes'.
+ *
+ * TODO:
+ *   - Double check the mappings/types
+ *   - Locate and add missing languages which can be supported
+ *   - Fix some like HTML without a kinds var but show in --list-kinds=all
+ *   - Re-order list with "most used" languages first.
+ *   - Figure out where the langType's are declared and don't use hardcoded value
+ */
+
+typedef struct {
+	int lang;				/* langType/mode */
+	int tag_type;			/* tm tag kind (TMTagType) */
+	int letter;				/* CTags kind letter */
+	const char* name;		/* CTags kind name */
+} ctagKinds;
+
+static ctagKinds allKinds[] = {
+
+	/* ASM */
+	{ 9, tm_tag_variable_t,		'd', "define" },
+	{ 9, tm_tag_undef_t,		'l', "label" },
+	{ 9, tm_tag_macro_t,		'm', "macro" },
+	{ 9, tm_tag_undef_t,		't', "type" },
+
+	/* FreeBasic */
+	{ 26, tm_tag_variable_t,	'c', "constant" },
+	{ 26, tm_tag_function_t,	'f', "function" },
+	{ 26, tm_tag_undef_t,		'l', "label" },
+	{ 26, tm_tag_undef_t,		't', "type" },
+	{ 26, tm_tag_variable_t,	'v', "variable" },
+	{ 26, tm_tag_enum_t,		'g', "enum" },
+
+	/* C */
+	{ 0, tm_tag_class_t,		'c', "class" },
+	{ 0, tm_tag_macro_t,		'd', "macro" },
+	{ 0, tm_tag_enumerator_t,	'e', "enumerator" },
+	{ 0, tm_tag_function_t,		'f', "function" },
+	{ 0, tm_tag_enum_t,			'g', "enum" },
+	{ 0, tm_tag_undef_t,		'l', "local" },
+	{ 0, tm_tag_member_t,		'm', "member" },
+	{ 0, tm_tag_namespace_t,	'n', "namespace" },
+	{ 0, tm_tag_prototype_t,	'p', "prototype" },
+	{ 0, tm_tag_struct_t,		's', "struct" },
+	{ 0, tm_tag_typedef_t,		't', "typedef" },
+	{ 0, tm_tag_union_t,		'u', "union" },
+	{ 0, tm_tag_variable_t,		'v', "variable" },
+	{ 0, tm_tag_externvar_t,	'x', "externvar" },
+
+	/* C++ */
+	{ 1, tm_tag_class_t,		'c', "class" },
+	{ 1, tm_tag_macro_t,		'd', "macro" },
+	{ 1, tm_tag_enumerator_t,	'e', "enumerator" },
+	{ 1, tm_tag_function_t,		'f', "function" },
+	{ 1, tm_tag_enum_t,			'g', "enum" },
+	{ 1, tm_tag_undef_t,		'l', "local" },
+	{ 1, tm_tag_member_t,		'm', "member" },
+	{ 1, tm_tag_namespace_t,	'n', "namespace" },
+	{ 1, tm_tag_prototype_t,	'p', "prototype" },
+	{ 1, tm_tag_struct_t,		's', "struct" },
+	{ 1, tm_tag_typedef_t,		't', "typedef" },
+	{ 1, tm_tag_union_t,		'u', "union" },
+	{ 1, tm_tag_variable_t,		'v', "variable" },
+	{ 1, tm_tag_externvar_t,	'x', "externvar" },
+
+	/* C# */
+	{ 25, tm_tag_class_t,		'c', "class" },
+	{ 25, tm_tag_macro_t,		'd', "macro" },
+	{ 25, tm_tag_enumerator_t,	'e', "enumerator" },
+	{ 25, tm_tag_undef_t,		'E', "event" },
+	{ 25, tm_tag_field_t,		'f', "field" },
+	{ 25, tm_tag_enum_t,		'g', "enum" },
+	{ 25, tm_tag_interface_t,	'i', "interface" },
+	{ 25, tm_tag_undef_t,		'l', "local" },
+	{ 25, tm_tag_method_t,		'm', "method" },
+	{ 25, tm_tag_namespace_t,	'n', "namespace" },
+	{ 25, tm_tag_member_t,		'p', "property" },
+	{ 25, tm_tag_struct_t,		's', "struct" },
+	{ 25, tm_tag_typedef_t,		't', "typedef" },
+
+	/* Java */
+	{ 2, tm_tag_class_t,		'c', "class" },
+	{ 2, tm_tag_undef_t,		'e', "enum constant" },
+	{ 2, tm_tag_field_t,		'f', "field" },
+	{ 2, tm_tag_enum_t,			'g', "enum" },
+	{ 2, tm_tag_interface_t,	'i', "interface" },
+	{ 2, tm_tag_undef_t,		'l', "local" },
+	{ 2, tm_tag_method_t,		'm', "method" },
+	{ 2, tm_tag_package_t,		'p', "package" },
+
+	/* Fortan */
+	{ 18, tm_tag_undef_t,		'b', "block data" },
+	{ 18, tm_tag_undef_t,		'c', "common" },
+	{ 18, tm_tag_undef_t,		'e', "entry" },
+	{ 18, tm_tag_function_t,	'f', "function" },
+	{ 18, tm_tag_interface_t,	'i', "interface" },
+	{ 18, tm_tag_undef_t,		'k', "component" },
+	{ 18, tm_tag_undef_t,		'l', "label" },
+	{ 18, tm_tag_undef_t,		'L', "local" },
+	{ 18, tm_tag_undef_t,		'm', "module" },
+	{ 18, tm_tag_undef_t,		'n', "namelist" },
+	{ 18, tm_tag_undef_t,		'p', "program" },
+	{ 18, tm_tag_function_t,	's', "subroutine" },
+	{ 18, tm_tag_undef_t,		't', "type" },
+	{ 18, tm_tag_variable_t,	'v', "variable" },
+
+	/* JavaScript */
+	{ 23, tm_tag_function_t,	'f', "function" },
+	{ 23, tm_tag_class_t,		'c', "class" },
+	{ 23, tm_tag_method_t,		'm', "method" },
+	{ 23, tm_tag_member_t,		'p', "property" },
+	{ 23, tm_tag_variable_t,	'v', "variable" },
+
+	/* Lua */
+	{ 22, tm_tag_function_t,	'f', "function" },
+
+	/* Make */
+	{ 3, tm_tag_macro_t,		'm', "macro" },
+
+	/* Pascal */
+	{ 4, tm_tag_function_t,		'f', "function" },
+	{ 4, tm_tag_function_t,		'p', "procedure" },
+
+	/* Perl */
+	{ 5, tm_tag_variable_t,		'c', "constant" },
+	{ 5, tm_tag_undef_t,		'f', "format" },
+	{ 5, tm_tag_undef_t,		'l', "label" },
+	{ 5, tm_tag_package_t,		'p', "package" },
+	{ 5, tm_tag_function_t,		's', "subroutine" },
+	{ 5, tm_tag_prototype_t,	'd', "subroutine declaration" },
+
+	/* PHP */
+	{ 6, tm_tag_class_t,		'c', "class" },
+	{ 6, tm_tag_undef_t,		'd', "define" },
+	{ 6, tm_tag_function_t,		'f', "function" },
+	{ 6, tm_tag_variable_t,		'v', "variable" },
+
+	/* Python */
+	{ 7, tm_tag_class_t,		'c', "class" },
+	{ 7, tm_tag_function_t,		'f', "function" },
+	{ 7, tm_tag_member_t,		'm', "member" },
+	{ 7, tm_tag_variable_t,		'v', "variable" },
+	{ 7, tm_tag_namespace_t,	'i', "namespace" },
+
+	/* Ruby */
+	{ 14, tm_tag_class_t,		'c', "class" },
+	{ 14, tm_tag_method_t,		'f', "method" },
+	{ 14, tm_tag_undef_t,		'm', "module" },
+	{ 14, tm_tag_undef_t,		'F', "singleton method" },
+
+	/* Shell */
+	{ 16, tm_tag_function_t,	'f', "function" },
+
+	/* SQL */
+	{ 11, tm_tag_undef_t,		'c', "cursor" },
+	{ 11, tm_tag_prototype_t,	'd', "prototype" },
+	{ 11, tm_tag_function_t,	'f', "function" },
+	{ 11, tm_tag_field_t,		'F', "field" },
+	{ 11, tm_tag_undef_t,		'l', "local" },
+	{ 11, tm_tag_undef_t,		'L', "label" },
+	{ 11, tm_tag_package_t,		'P', "package" },
+	{ 11, tm_tag_function_t,	'p', "procedure" },
+	{ 11, tm_tag_undef_t,		'r', "record" },
+	{ 11, tm_tag_undef_t,		's', "subtype" },
+	{ 11, tm_tag_undef_t,		't', "table" },
+	{ 11, tm_tag_undef_t,		'T', "trigger" },
+	{ 11, tm_tag_variable_t,	'v', "variable" },
+	{ 11, tm_tag_undef_t,		'i', "index" },
+	{ 11, tm_tag_undef_t,		'e', "event" },
+	{ 11, tm_tag_undef_t,		'U', "publication" },
+	{ 11, tm_tag_undef_t,		'R', "service" },
+	{ 11, tm_tag_undef_t,		'D', "domain" },
+	{ 11, tm_tag_undef_t,		'V', "view" },
+	{ 11, tm_tag_undef_t,		'n', "synonym" },
+	{ 11, tm_tag_undef_t,		'x', "mltable" },
+	{ 11, tm_tag_undef_t,		'y', "mlconn" },
+
+	/* Tcl */
+	{ 15, tm_tag_class_t,		'c', "class" },
+	{ 15, tm_tag_method_t,		'm', "method" },
+	{ 15, tm_tag_function_t,	'p', "procedure" },
+
+	/* Tex */
+	{ 8, tm_tag_undef_t,		'c', "chapter" },
+	{ 8, tm_tag_undef_t,		's', "section" },
+	{ 8, tm_tag_undef_t,		'u', "subsection" },
+	{ 8, tm_tag_undef_t,		'b', "subsubsection" },
+	{ 8, tm_tag_undef_t,		'p', "part" },
+	{ 8, tm_tag_undef_t,		'P', "paragraph" },
+	{ 8, tm_tag_undef_t,		'G', "subparagraph" },
+
+	/* Verilog */
+	{ 39, tm_tag_variable_t,	'c', "constant" },
+	{ 39, tm_tag_undef_t,		'e', "event" },
+	{ 39, tm_tag_function_t,	'f', "function" },
+	{ 39, tm_tag_undef_t,		'm', "module" },
+	{ 39, tm_tag_undef_t,		'n', "net" },
+	{ 39, tm_tag_undef_t,		'p', "port" },
+	{ 39, tm_tag_undef_t,		'r', "register" },
+	{ 39, tm_tag_undef_t,		't', "task" },
+
+	/* VHDL */
+	{ 21, tm_tag_variable_t,	'c', "constant" },
+	{ 21, tm_tag_undef_t,		't', "type" },
+	{ 21, tm_tag_undef_t,		'T', "subtype" },
+	{ 21, tm_tag_undef_t,		'r', "record" },
+	{ 21, tm_tag_undef_t,		'e', "entity" },
+	{ 21, tm_tag_undef_t,		'C', "component" },
+	{ 21, tm_tag_prototype_t,	'd', "prototype" },
+	{ 21, tm_tag_function_t,	'f', "function" },
+	{ 21, tm_tag_function_t,	'p', "procedure" },
+	{ 21, tm_tag_package_t,		'P', "package" },
+	{ 21, tm_tag_undef_t,		'l', "local" }
+
+};
+
+
+static TMTagType map_ctags_kind_char(int lang, char kind)
+{
+	int i, arr_len = sizeof(allKinds) / sizeof(ctagKinds);
+
+	for (i = 0; i < arr_len; i++)
+	{
+		if (allKinds[i].lang == lang && allKinds[i].letter == kind)
+			return allKinds[i].tag_type;
+	}
+
+	return tm_tag_undef_t;
+}
+
+
+static TMTagType map_ctags_kind_string(int lang, const char *kind)
+{
+	int i, arr_len = sizeof(allKinds) / sizeof(ctagKinds);
+
+	for (i = 0; i < arr_len; i++)
+	{
+		if (allKinds[i].lang == lang && strcmp(allKinds[i].name, kind) == 0)
+			return allKinds[i].tag_type;
+	}
+
+	return tm_tag_undef_t;
+}
+
+
+#ifdef USE_HACKS
+/* TODO: Use lang param instead of assuming C-style syntax */
+static gchar *get_func_arglist(const gchar *func, gint lang)
+{
+	gchar *arglist = NULL;
+	const gchar *ptr, *start_ptr = NULL, *end_ptr = NULL;
+
+	if (func == NULL || func[0] == '\0')
+		return NULL;
+
+	for (ptr = func; *ptr; ptr++)
+	{
+		if (*ptr == '(' && start_ptr == NULL)
+		{
+			if (*(ptr + 1) /* not on last char */)
+				start_ptr = ptr + 1;
+			continue;
+		}
+		else if (*ptr == ')' && start_ptr != NULL)
+		{
+			end_ptr = ptr;
+			break;
+		}
+	}
+
+	if (start_ptr != NULL && end_ptr != NULL && end_ptr > start_ptr)
+		arglist = g_strstrip(g_strndup(start_ptr, end_ptr - start_ptr));
+
+	return arglist;
+}
+
+/* TODO: Use lang param instead of assuming C-style syntax */
+static gchar *get_func_ret_type(const gchar *func, gint lang)
+{
+	gchar *ret = NULL;
+	const gchar *ptr, *start_ptr, *end_ptr = NULL;
+	gboolean ident_start;
+
+	if (func == NULL || func[0] == '\0')
+		return;
+
+	while (!isalnum(*func))
+		func++;
+
+	end_ptr = strchr(func, '(');
+	if (end_ptr == NULL)
+		return NULL;
+
+	ident_start = FALSE;
+	for (ptr = end_ptr; ptr != func; ptr--)
+	{
+		if (!isspace(*ptr) && ptr != end_ptr)
+		{
+			ident_start = TRUE;
+			continue;
+		}
+		if (isspace(*ptr) && ident_start)
+		{
+			end_ptr = ptr;
+			break;
+		}
+	}
+
+	if (end_ptr != NULL && end_ptr > func)
+		ret = g_strstrip(g_strndup(func, end_ptr - func));
+
+	return ret;
+}
+
+/* TODO: Use lang param instead of assuming C-style syntax */
+static gchar *get_var_type(const gchar *pattern, gint lang)
+{
+	gchar *var = NULL;
+	const gchar *ptr;
+	gchar *end_ptr = NULL;
+	gboolean ident_start = FALSE;
+
+	end_ptr = strchr(pattern, '=');
+	if (end_ptr == NULL)
+	{
+		end_ptr = strchr(pattern, ';');
+		if (end_ptr == NULL)
+			return NULL; /* won't be able to find end point */
+	}
+
+	for (ptr = end_ptr; ptr != pattern; ptr--)
+	{
+		if (!isspace(*ptr) && *ptr != '[' && *ptr != '[' && ptr != end_ptr)
+		{
+			ident_start = TRUE;
+			continue;
+		}
+		if (isspace(*ptr) && ident_start)
+		{
+			end_ptr = (gchar *)ptr;
+			break;
+		}
+	}
+
+	if (end_ptr != NULL && end_ptr > pattern)
+		var = g_strstrip(g_strndup(pattern, end_ptr - pattern));
+
+	return var;
+}
+#endif
+
+/* Read ctags format */
+gboolean tm_tag_init_from_file_ctags(TMTag *tag, TMSourceFile *file, FILE *fp, const gchar *ctags_file, gint mode)
+{
+	gint i;
+	static cnt=0;
+	static ctagFileInfo info;
+	static tagEntry entry;
+	static ctagFile *cfile = NULL;
+
+	tag->refcount = 1;
+	if (cfile == NULL)
+	{
+		cnt = 0;
+	    cfile = tagsOpen (ctags_file, &info);
+	    if (cfile == NULL)
+	    	return FALSE;
+	}
+
+	if (tagsNext(cfile, &entry) == TagSuccess)
+	{
+		tag->name = g_strdup(entry.name);
+
+		if (entry.address.pattern == NULL) /* lineNumber should be valid */
+			tag->atts.entry.line = entry.address.lineNumber;
+
+		 tag->atts.entry.var_type = NULL;
+		 tag->atts.entry.arglist = NULL;
+
+		 if (entry.kind != NULL || strlen(entry.kind) == 1)
+		 	tag->type = map_ctags_kind_char(mode, entry.kind[0]);
+		 else
+		 	tag->type = map_ctags_kind_string(mode, entry.kind);
+
+#ifdef USE_HACKS
+		 if ((tag->type == tm_tag_function_t || tag->type == tm_tag_prototype_t) &&
+		 		entry.address.pattern != NULL)
+		 {
+		 	tag->atts.entry.arglist = get_func_arglist(entry.address.pattern, mode);
+		 	tag->atts.entry.var_type = get_func_ret_type(entry.address.pattern, mode);
+		 }
+		 else if (tag->type == tm_tag_variable_t && entry.address.pattern != NULL)
+			tag->atts.entry.var_type = get_var_type(entry.address.pattern, mode);
+#endif
+
+#if 0
+		for (i = 0; i < entry.fields.count; i++)
+		{
+			/* use optional extensions in newer CTags format */
+		}
+#endif
+
+	}
+	else
+	{
+		tagsClose(cfile);
+		cfile = NULL;
+	}
+
+	if (NULL == tag->name)
+		return FALSE;
+	if (tm_tag_file_t != tag->type)
+		tag->atts.entry.file = file;
+	return TRUE;
+}
+
+TMTag *tm_tag_new_from_file(TMSourceFile *file, FILE *fp, gint mode, TMSourceFileFormat format, const gchar *tags_file)
 {
 	TMTag *tag;
 	gboolean result;
 
 	TAG_NEW(tag);
 
-	if (format_pipe)
-		result = tm_tag_init_from_file_alt(tag, file, fp);
-	else
-		result = tm_tag_init_from_file(tag, file, fp);
+	switch (format)
+	{
+		case TM_SOURCE_FILE_FORMAT_TAG_MANAGER:
+			result = tm_tag_init_from_file(tag, file, fp);
+			break;
+		case TM_SOURCE_FILE_FORMAT_CTAGS:
+			result = tm_tag_init_from_file_ctags(tag, file, fp, tags_file, mode);
+			break;
+		case TM_SOURCE_FILE_FORMAT_PIPE:
+			result = tm_tag_init_from_file_alt(tag, file, fp);
+			break;
+		default:
+			TAG_FREE(tag);
+			return NULL;
+	}
 
 	if (! result)
 	{
diff --git a/tagmanager/tm_workspace.c b/tagmanager/tm_workspace.c
index d9584c0..37d82a9 100644
--- a/tagmanager/tm_workspace.c
+++ b/tagmanager/tm_workspace.c
@@ -145,7 +145,8 @@ gboolean tm_workspace_load_global_tags(const char *tags_file, gint mode)
 	guchar buf[BUFSIZ];
 	FILE *fp;
 	TMTag *tag;
-	gboolean format_pipe = FALSE;
+	TMSourceFile *sfile;
+	TMSourceFileFormat format = TM_SOURCE_FILE_FORMAT_TAG_MANAGER;
 
 	if (NULL == theWorkspace)
 		return FALSE;
@@ -160,25 +161,36 @@ gboolean tm_workspace_load_global_tags(const char *tags_file, gint mode)
 	}
 	else
 	{	/* We read the first line for the format specification. */
-		if (buf[0] == '#' && strstr((gchar*) buf, "format=pipe") != NULL)
-			format_pipe = TRUE;
+		if (buf[0] == '!' && strstr((gchar *) buf, "!_TAG") != NULL)
+			format = TM_SOURCE_FILE_FORMAT_CTAGS;
+		else if (buf[0] == '#' && strstr((gchar*) buf, "format=pipe") != NULL)
+			format = TM_SOURCE_FILE_FORMAT_PIPE;
 		else if (buf[0] == '#' && strstr((gchar*) buf, "format=tagmanager") != NULL)
-			format_pipe = FALSE;
+			format = TM_SOURCE_FILE_FORMAT_TAG_MANAGER;
 		else
 		{	/* We didn't find a valid format specification, so we try to auto-detect the format
 			 * by counting the pipe characters on the first line and asumme pipe format when
-			 * we find more than one pipe on the line. */
-			guint i, pipe_cnt = 0;
+			 * we find more than one pipe on the line.  Also count the tab characters
+			 * to detect if it's a Ctags file (more than 2). */
+			guint i, pipe_cnt = 0, tab_cnt = 0;
 			for (i = 0; i < BUFSIZ && buf[i] != '\0' && pipe_cnt < 2; i++)
 			{
 				if (buf[i] == '|')
 					pipe_cnt++;
+				if (buf[i] == '\t')
+					tab_cnt++;
 			}
-			format_pipe = (pipe_cnt > 1);
+
+			if (pipe_cnt > 1)
+				format = TM_SOURCE_FILE_FORMAT_PIPE;
+			else if (tab_cnt > 2)
+				format = TM_SOURCE_FILE_FORMAT_CTAGS;
+			else
+				format = TM_SOURCE_FILE_FORMAT_TAG_MANAGER;
 		}
 		rewind(fp); /* reset the file pointer, to start reading again from the beginning */
 	}
-	while (NULL != (tag = tm_tag_new_from_file(NULL, fp, mode, format_pipe)))
+	while (NULL != (tag = tm_tag_new_from_file(NULL, fp, mode, format, tags_file)))
 		g_ptr_array_add(theWorkspace->global_tags, tag);
 	fclose(fp);
 
-- 
1.7.1

_______________________________________________
Geany-devel mailing list
Geany-devel@uvena.de
https://lists.uvena.de/cgi-bin/mailman/listinfo/geany-devel

Reply via email to