From d453fe33fe4139a7d90682cc60ac067ddc222b14 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Mon, 1 Apr 2013 00:21:49 +0200 Subject: [PATCH] Add support for loading Vi and CTags tag files This allows to load tag files in the CTags format, which is compatible with Vi format. References: * http://ctags.sourceforge.net/FORMAT * http://ctags.sourceforge.net/ctags.html#TAG%20FILE%20FORMAT --- doc/geany.txt | 23 +++-- tagmanager/src/tm_tag.c | 226 +++++++++++++++++++++++++++++++++++------- tagmanager/src/tm_tag.h | 13 ++- tagmanager/src/tm_workspace.c | 21 ++-- 4 files changed, 233 insertions(+), 50 deletions(-) diff --git a/doc/geany.txt b/doc/geany.txt index d36449bf3..05592f1c2 100644 --- a/doc/geany.txt +++ b/doc/geany.txt @@ -1601,16 +1601,17 @@ corresponding filetype is first used. Currently these are for: Global tags file format ``````````````````````` -Global tags files can have two different formats: +Global tags files can have three different formats: * Tagmanager format * Pipe-separated format +* CTags format The first line of global tags files should be a comment, introduced -by ``#`` followed by a space and a string like ``format=pipe`` -or ``format=tagmanager`` respectively, these are case-sensitive. -This helps Geany to read the file properly. If this line -is missing, Geany tries to auto-detect the used format but this +by ``#`` followed by a space and a string like ``format=pipe``, +``format=ctags`` or ``format=tagmanager`` respectively, these are +case-sensitive. This helps Geany to read the file properly. If this +line is missing, Geany tries to auto-detect the used format but this might fail. @@ -1618,7 +1619,8 @@ The Tagmanager format is a bit more complex and is used for files created by the ``geany -g`` command. There is one tag per line. Different tag attributes like the return value or the argument list are separated with different characters indicating the type of the -following argument. +following argument. This is the more complete and recommended tag +format. Pipe-separated format ********************* @@ -1641,6 +1643,15 @@ You can easily write your own global tag files using this format. Just save them in your tags directory, as described earlier in the section `Global tags`_. +CTags format +************ +This is the format that ctags generates, and that is used by Vim. +This format is compatible with the format historically used by Vi. + +The format is described at http://ctags.sourceforge.net/FORMAT, but +for the full list of existing extensions please refer to ctags. +However, note that Geany may actually only honor a subset of the +existing extensions. Generating a global tags file ````````````````````````````` diff --git a/tagmanager/src/tm_tag.c b/tagmanager/src/tm_tag.c index 9c5466339..a1ed2cb8f 100644 --- a/tagmanager/src/tm_tag.c +++ b/tagmanager/src/tm_tag.c @@ -177,6 +177,37 @@ static int get_tag_type(const char *tag_name) return tm_tag_undef_t; } +static char get_tag_impl(const char *impl) +{ + if ((0 == strcmp("virtual", impl)) + || (0 == strcmp("pure virtual", impl))) + return TAG_IMPL_VIRTUAL; + +#ifdef TM_DEBUG + g_warning("Unknown implementation %s", impl); +#endif + return TAG_IMPL_UNKNOWN; +} + +static char get_tag_access(const char *access) +{ + if (0 == strcmp("public", access)) + return TAG_ACCESS_PUBLIC; + else if (0 == strcmp("protected", access)) + return TAG_ACCESS_PROTECTED; + else if (0 == strcmp("private", access)) + return TAG_ACCESS_PRIVATE; + else if (0 == strcmp("friend", access)) + return TAG_ACCESS_FRIEND; + else if (0 == strcmp("default", access)) + return TAG_ACCESS_DEFAULT; + +#ifdef TM_DEBUG + g_warning("Unknown access type %s", access); +#endif + return TAG_ACCESS_UNKNOWN; +} + gboolean tm_tag_init(TMTag *tag, TMSourceFile *file, const tagEntryInfo *tag_entry) { tag->refcount = 1; @@ -217,38 +248,9 @@ gboolean tm_tag_init(TMTag *tag, TMSourceFile *file, const tagEntryInfo *tag_ent if (tag_entry->extensionFields.varType != NULL) tag->atts.entry.var_type = g_strdup(tag_entry->extensionFields.varType); if (tag_entry->extensionFields.access != NULL) - { - if (0 == strcmp("public", tag_entry->extensionFields.access)) - tag->atts.entry.access = TAG_ACCESS_PUBLIC; - else if (0 == strcmp("protected", tag_entry->extensionFields.access)) - tag->atts.entry.access = TAG_ACCESS_PROTECTED; - else if (0 == strcmp("private", tag_entry->extensionFields.access)) - tag->atts.entry.access = TAG_ACCESS_PRIVATE; - else if (0 == strcmp("friend", tag_entry->extensionFields.access)) - tag->atts.entry.access = TAG_ACCESS_FRIEND; - else if (0 == strcmp("default", tag_entry->extensionFields.access)) - tag->atts.entry.access = TAG_ACCESS_DEFAULT; - else - { -#ifdef TM_DEBUG - g_warning("Unknown access type %s", tag_entry->extensionFields.access); -#endif - tag->atts.entry.access = TAG_ACCESS_UNKNOWN; - } - } + tag->atts.entry.access = get_tag_access(tag_entry->extensionFields.access); if (tag_entry->extensionFields.implementation != NULL) - { - if ((0 == strcmp("virtual", tag_entry->extensionFields.implementation)) - || (0 == strcmp("pure virtual", tag_entry->extensionFields.implementation))) - tag->atts.entry.impl = TAG_IMPL_VIRTUAL; - else - { -#ifdef TM_DEBUG - g_warning("Unknown implementation %s", tag_entry->extensionFields.implementation); -#endif - tag->atts.entry.impl = TAG_IMPL_UNKNOWN; - } - } + tag->atts.entry.impl = get_tag_impl(tag_entry->extensionFields.implementation); if ((tm_tag_macro_t == tag->type) && (NULL != tag->atts.entry.arglist)) tag->type = tm_tag_macro_with_arg_t; tag->atts.entry.file = file; @@ -416,17 +418,167 @@ 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) +/* Reads ctags format (http://ctags.sourceforge.net/FORMAT) */ +gboolean tm_tag_init_from_file_ctags(TMTag *tag, TMSourceFile *file, FILE *fp) +{ + gchar buf[BUFSIZ]; + gchar *p, *tab; + + tag->refcount = 1; + tag->type = tm_tag_function_t; /* default type is function if no kind is specified */ + do + { + if ((NULL == fgets(buf, BUFSIZ, fp)) || ('\0' == *buf)) + return FALSE; + } + while (strncmp(buf, "!_TAG_", 6) == 0); /* skip !_TAG_ lines */ + + p = buf; + + /* tag name */ + if (! (tab = strchr(p, '\t')) || p == tab) + return FALSE; + tag->name = g_strndup(p, (gsize)(tab - p)); + p = tab + 1; + + /* tagfile, unused */ + if (! (tab = strchr(p, '\t'))) + { + g_free(tag->name); + tag->name = NULL; + return FALSE; + } + p = tab + 1; + /* Ex command, unused */ + if (*p == '/' || *p == '?') + { + gchar c = *p; + for (++p; *p && *p != c; p++) + { + if (*p == '\\' && p[1]) + p++; + } + } + else /* assume a line */ + tag->atts.entry.line = atol(p); + tab = strstr(p, ";\""); + /* read extension fields */ + if (tab) + { + p = tab + 2; + while (*p && *p != '\n' && *p != '\r') + { + gchar *end; + const gchar *key, *value = NULL; + + /* skip leading tabulations */ + while (*p && *p == '\t') p++; + /* find the separator (:) and end (\t) */ + key = end = p; + while (*end && *end != '\t' && *end != '\n' && *end != '\r') + { + if (*end == ':' && ! value) + { + *end = 0; /* terminate the key */ + value = end + 1; + } + end++; + } + /* move p paste the so we won't stop parsing by setting *end=0 below */ + p = *end ? end + 1 : end; + *end = 0; /* terminate the value (or key if no value) */ + + if (! value || 0 == strcmp(key, "kind")) /* tag kind */ + { + const gchar *kind = value ? value : key; + + if (kind[0] && kind[1]) + tag->type = get_tag_type(kind); + else + { + switch (*kind) + { + case 'c': tag->type = tm_tag_class_t; break; + case 'd': tag->type = tm_tag_macro_t; break; + case 'e': tag->type = tm_tag_enumerator_t; break; + case 'F': tag->type = tm_tag_file_t; break; + case 'f': tag->type = tm_tag_function_t; break; + case 'g': tag->type = tm_tag_enum_t; break; + case 'I': tag->type = tm_tag_class_t; break; + case 'i': tag->type = tm_tag_interface_t; break; + case 'l': tag->type = tm_tag_variable_t; break; + case 'M': tag->type = tm_tag_macro_t; break; + case 'm': tag->type = tm_tag_member_t; break; + case 'n': tag->type = tm_tag_namespace_t; break; + case 'P': tag->type = tm_tag_package_t; break; + case 'p': tag->type = tm_tag_prototype_t; break; + case 's': tag->type = tm_tag_struct_t; break; + case 't': tag->type = tm_tag_typedef_t; break; + case 'u': tag->type = tm_tag_union_t; break; + case 'v': tag->type = tm_tag_variable_t; break; + case 'x': tag->type = tm_tag_externvar_t; break; + default: +#ifdef TM_DEBUG + g_warning("Unknown tag kind %c", *kind); +#endif + tag->type = tm_tag_other_t; break; + } + } + } + else if (0 == strcmp(key, "inherits")) /* comma-separated list of classes this class inherits from */ + { + g_free(tag->atts.entry.inheritance); + tag->atts.entry.inheritance = g_strdup(value); + } + else if (0 == strcmp(key, "implementation")) /* implementation limit */ + tag->atts.entry.impl = get_tag_impl(value); + else if (0 == strcmp(key, "line")) /* line */ + tag->atts.entry.line = atol(value); + else if (0 == strcmp(key, "access")) /* access */ + tag->atts.entry.access = get_tag_access(value); + else if (0 == strcmp(key, "class") || + 0 == strcmp(key, "enum") || + 0 == strcmp(key, "function") || + 0 == strcmp(key, "struct") || + 0 == strcmp(key, "union")) /* Name of the class/enum/function/struct/union in which this tag is a member */ + { + g_free(tag->atts.entry.scope); + tag->atts.entry.scope = g_strdup(value); + } + else if (0 == strcmp(key, "file")) /* static (local) tag */ + tag->atts.entry.local = TRUE; + else if (0 == strcmp(key, "signature")) /* arglist */ + { + g_free(tag->atts.entry.arglist); + tag->atts.entry.arglist = g_strdup(value); + } + } + } + + 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, TMFileFormat format) { TMTag *tag; - gboolean result; + gboolean result = FALSE; 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_FILE_FORMAT_TAGMANAGER: + result = tm_tag_init_from_file(tag, file, fp); + break; + case TM_FILE_FORMAT_PIPE: + result = tm_tag_init_from_file_alt(tag, file, fp); + break; + case TM_FILE_FORMAT_CTAGS: + result = tm_tag_init_from_file_ctags(tag, file, fp); + break; + } if (! result) { diff --git a/tagmanager/src/tm_tag.h b/tagmanager/src/tm_tag.h index 627237dc1..f4e2b63d9 100644 --- a/tagmanager/src/tm_tag.h +++ b/tagmanager/src/tm_tag.h @@ -151,6 +151,12 @@ typedef struct _TMTag gint refcount; /*!< the reference count of the tag */ } TMTag; +typedef enum { + TM_FILE_FORMAT_TAGMANAGER, + TM_FILE_FORMAT_PIPE, + TM_FILE_FORMAT_CTAGS +} TMFileFormat; + /*! Prototype for user-defined tag comparison function. This is the type of argument that needs to be passed to tm_tags_sort_custom() and @@ -195,6 +201,11 @@ 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 parsing CTags tag file format +*/ +gboolean tm_tag_init_from_file_ctags(TMTag *tag, TMSourceFile *file, FILE *fp); + +/*! 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 +218,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, TMFileFormat format); /*! Writes tag information to the given FILE *. diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 919013241..e20b6785e 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -146,7 +146,7 @@ gboolean tm_workspace_load_global_tags(const char *tags_file, gint mode) guchar buf[BUFSIZ]; FILE *fp; TMTag *tag; - gboolean format_pipe = FALSE; + TMFileFormat format = TM_FILE_FORMAT_TAGMANAGER; if (NULL == theWorkspace) return FALSE; @@ -163,24 +163,33 @@ 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; + format = TM_FILE_FORMAT_PIPE; else if (buf[0] == '#' && strstr((gchar*) buf, "format=tagmanager") != NULL) - format_pipe = FALSE; + format = TM_FILE_FORMAT_TAGMANAGER; + else if (buf[0] == '#' && strstr((gchar*) buf, "format=ctags") != NULL) + format = TM_FILE_FORMAT_CTAGS; + else if (strncmp((gchar*) buf, "!_TAG_", 6) == 0) + format = TM_FILE_FORMAT_CTAGS; 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; + 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++; + else if (buf[i] == '\t') + tab_cnt++; } - format_pipe = (pipe_cnt > 1); + if (pipe_cnt > 1) + format = TM_FILE_FORMAT_PIPE; + else if (tab_cnt > 1) + format = TM_FILE_FORMAT_CTAGS; } 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))) g_ptr_array_add(theWorkspace->global_tags, tag); fclose(fp); -- 2.11.4.GIT