geany/src/symbols.c

1103 lines
29 KiB
C
Raw Normal View History

/*
* symbols.c - this file is part of Geany, a fast and lightweight IDE
*
* Copyright 2006-2007 Enrico Tröger <enrico.troeger@uvena.de>
* Copyright 2006-2007 Nick Treleaven <nick.treleaven@btinternet.com>
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id$
*/
/*
* Tagmanager related convenience functions.
* Tagmanager parses tags in the current documents, known as the workspace, plus global tags,
* which are lists of tags for each filetype. Global tags are loaded when a document with a
* matching filetype is first loaded.
*/
#include "geany.h"
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "prefix.h"
#include "symbols.h"
#include "utils.h"
#include "filetypes.h"
#include "encodings.h"
#include "document.h"
#include "support.h"
#include "msgwindow.h"
#include "treeviews.h"
#include "main.h"
#include "navqueue.h"
#include "ui_utils.h"
const guint TM_GLOBAL_TYPE_MASK =
tm_tag_class_t | tm_tag_enum_t | tm_tag_interface_t |
tm_tag_struct_t | tm_tag_typedef_t | tm_tag_union_t;
static gchar **html_entities = NULL;
typedef struct
{
gboolean tags_loaded;
const gchar *tag_file;
} TagFileInfo;
enum // Geany tag files
{
GTF_C,
GTF_PASCAL,
GTF_PHP,
GTF_HTML_ENTITIES,
GTF_LATEX,
GTF_MAX
};
static TagFileInfo tag_file_info[GTF_MAX] =
{
{FALSE, "global.tags"},
{FALSE, "pascal.tags"},
{FALSE, "php.tags"},
{FALSE, "html_entities.tags"},
{FALSE, "latex.tags"}
};
static gchar *user_tags_dir;
static void html_tags_loaded();
static void load_user_tags(filetype_id ft_id);
// Ensure that the global tags file for the file_type_idx filetype is loaded.
void symbols_global_tags_loaded(gint file_type_idx)
{
TagFileInfo *tfi;
gint tag_type;
if (cl_options.ignore_global_tags || app->tm_workspace == NULL)
return;
load_user_tags(file_type_idx);
switch (file_type_idx)
{
case GEANY_FILETYPES_PHP:
case GEANY_FILETYPES_HTML:
html_tags_loaded();
}
switch (file_type_idx)
{
case GEANY_FILETYPES_CPP:
symbols_global_tags_loaded(GEANY_FILETYPES_C); // load C global tags
// no C++ tagfile yet
return;
case GEANY_FILETYPES_C: tag_type = GTF_C; break;
case GEANY_FILETYPES_PASCAL:tag_type = GTF_PASCAL; break;
case GEANY_FILETYPES_PHP: tag_type = GTF_PHP; break;
case GEANY_FILETYPES_LATEX: tag_type = GTF_LATEX; break;
default:
return;
}
tfi = &tag_file_info[tag_type];
if (! tfi->tags_loaded)
{
gchar *fname = g_strconcat(app->datadir, G_DIR_SEPARATOR_S, tfi->tag_file, NULL);
gint tm_lang;
tm_lang = filetypes[file_type_idx]->lang;
tm_workspace_load_global_tags(fname, tm_lang);
tfi->tags_loaded = TRUE;
g_free(fname);
}
}
// HTML tagfile is just a list of entities for autocompletion (e.g. '&amp;')
static void html_tags_loaded()
{
TagFileInfo *tfi;
if (cl_options.ignore_global_tags) return;
tfi = &tag_file_info[GTF_HTML_ENTITIES];
if (! tfi->tags_loaded)
{
gchar *file = g_strconcat(app->datadir, G_DIR_SEPARATOR_S, tfi->tag_file, NULL);
html_entities = utils_read_file_in_array(file);
tfi->tags_loaded = TRUE;
g_free(file);
}
}
GString *symbols_find_tags_as_string(GPtrArray *tags_array, guint tag_types, gint lang)
{
guint j;
TMTag *tag;
GString *s = NULL;
GPtrArray *typedefs;
g_return_val_if_fail(tags_array != NULL, NULL);
typedefs = tm_tags_extract(tags_array, tag_types);
if ((typedefs) && (typedefs->len > 0))
{
s = g_string_sized_new(typedefs->len * 10);
for (j = 0; j < typedefs->len; ++j)
{
tag = TM_TAG(typedefs->pdata[j]);
if (!(tag->atts.entry.scope))
{
// tag->atts.file.lang contains (for some reason) the line of the tag if
// tag->atts.entry.file is not NULL
gint tag_lang =
(tag->atts.entry.file) ? tag->atts.entry.file->lang : tag->atts.file.lang;
// the check for tag_lang == lang is necessary to avoid wrong type colouring of
// e.g. PHP classes in C++ files
// lang = -2 disables the check
if (tag->name && (tag_lang == lang || lang == -2))
{
if (j != 0)
g_string_append_c(s, ' ');
g_string_append(s, tag->name);
}
}
}
}
g_ptr_array_free(typedefs, TRUE);
return s;
}
const gchar *symbols_get_context_separator(gint ft_id)
{
gchar *cosep;
switch (ft_id)
{
case GEANY_FILETYPES_C: // for C++ .h headers or C structs
case GEANY_FILETYPES_CPP:
//~ case GEANY_FILETYPES_RUBY: // not sure what to use atm
{
static gchar cc[] = "::";
cosep = cc;
}
break;
default:
{
static gchar def[] = ".";
cosep = def;
}
}
return cosep; // return ptr to static string
}
GString *symbols_get_macro_list()
{
guint j, i;
const GPtrArray *tags;
GPtrArray *ftags;
GString *words;
if (app->tm_workspace->work_objects == NULL)
return NULL;
ftags = g_ptr_array_sized_new(50);
words = g_string_sized_new(200);
for (j = 0; j < app->tm_workspace->work_objects->len; j++)
{
tags = tm_tags_extract(TM_WORK_OBJECT(app->tm_workspace->work_objects->pdata[j])->tags_array,
tm_tag_enum_t | tm_tag_variable_t | tm_tag_macro_t | tm_tag_macro_with_arg_t);
if (NULL != tags)
{
for (i = 0; ((i < tags->len) && (i < GEANY_MAX_AUTOCOMPLETE_WORDS)); ++i)
{
g_ptr_array_add(ftags, (gpointer) tags->pdata[i]);
}
}
}
tm_tags_sort(ftags, NULL, FALSE);
for (j = 0; j < ftags->len; j++)
{
if (j > 0) g_string_append_c(words, ' ');
g_string_append(words, TM_TAG(ftags->pdata[j])->name);
}
g_ptr_array_free(ftags, TRUE);
return words;
}
static TMTag *
symbols_find_tm_tag(const GPtrArray *tags, const gchar *tag_name)
{
guint i;
g_return_val_if_fail(tags != NULL, NULL);
for (i = 0; i < tags->len; ++i)
{
if (utils_str_equal(TM_TAG(tags->pdata[i])->name, tag_name))
return TM_TAG(tags->pdata[i]);
}
return NULL;
}
static TMTag *find_workspace_tag(const gchar *tag_name, gint type)
{
guint j;
const GPtrArray *tags;
TMTag *tmtag;
if (app->tm_workspace != NULL && app->tm_workspace->work_objects != NULL)
{
for (j = 0; j < app->tm_workspace->work_objects->len; j++)
{
tags = tm_tags_extract(
TM_WORK_OBJECT(app->tm_workspace->work_objects->pdata[j])->tags_array,
type);
if (tags == NULL) continue;
tmtag = symbols_find_tm_tag(tags, tag_name);
if (tmtag != NULL)
return tmtag;
}
}
return NULL; // not found
}
const gchar **symbols_get_html_entities()
{
if (html_entities == NULL)
html_tags_loaded(); // if not yet created, force creation of the array but shouldn't occur
return (const gchar **) html_entities;
}
void symbols_finalize()
{
g_strfreev(html_entities);
}
// small struct to track tag name and type together
typedef struct GeanySymbol
{
gchar *str;
gint type;
gint line;
} GeanySymbol;
/* sort by name, line */
static gint compare_symbol(const GeanySymbol *a, const GeanySymbol *b)
{
gint ret;
if (a == NULL || b == NULL) return 0;
ret = strcmp(a->str, b->str);
if (ret == 0)
{
return a->line - b->line;
}
return ret;
}
/* sort by line only */
static gint compare_symbol_lines(const GeanySymbol *a, const GeanySymbol *b)
{
if (a == NULL || b == NULL) return 0;
return a->line - b->line;
}
static const GList *get_tag_list(gint idx, guint tag_types, gboolean sort_by_name)
{
static GList *tag_names = NULL;
if (idx >= 0 && doc_list[idx].is_valid && doc_list[idx].tm_file &&
doc_list[idx].tm_file->tags_array)
{
TMTag *tag;
guint i;
GeanySymbol *symbol;
gboolean doc_is_utf8 = FALSE;
gchar *utf8_name;
const gchar *cosep =
symbols_get_context_separator(FILETYPE_ID(doc_list[idx].file_type));
if (tag_names)
{
GList *tmp;
for (tmp = tag_names; tmp; tmp = g_list_next(tmp))
{
g_free(((GeanySymbol*)tmp->data)->str);
g_free(tmp->data);
}
g_list_free(tag_names);
tag_names = NULL;
}
// encodings_convert_to_utf8_from_charset() fails with charset "None", so skip conversion
// for None at this point completely
if (utils_str_equal(doc_list[idx].encoding, "UTF-8") ||
utils_str_equal(doc_list[idx].encoding, "None"))
doc_is_utf8 = TRUE;
for (i = 0; i < (doc_list[idx].tm_file)->tags_array->len; ++i)
{
tag = TM_TAG((doc_list[idx].tm_file)->tags_array->pdata[i]);
if (tag == NULL)
return NULL;
if (tag->type & tag_types)
{
if (! doc_is_utf8) utf8_name = encodings_convert_to_utf8_from_charset(tag->name,
-1, doc_list[idx].encoding, TRUE);
else utf8_name = tag->name;
if (utf8_name == NULL)
continue;
symbol = g_new0(GeanySymbol, 1);
if ((tag->atts.entry.scope != NULL) && isalpha(tag->atts.entry.scope[0]))
{
symbol->str = g_strconcat(tag->atts.entry.scope, cosep, utf8_name, NULL);
}
else
{
symbol->str = g_strdup(utf8_name);
}
symbol->type = tag->type;
symbol->line = tag->atts.entry.line;
tag_names = g_list_prepend(tag_names, symbol);
if (! doc_is_utf8) g_free(utf8_name);
}
}
if (sort_by_name)
tag_names = g_list_sort(tag_names, (GCompareFunc) compare_symbol);
else
tag_names = g_list_sort(tag_names, (GCompareFunc) compare_symbol_lines);
return tag_names;
}
else
return NULL;
}
// amount of types in the symbol list (currently max. 8 are used)
#define MAX_SYMBOL_TYPES (sizeof(tv_iters) / sizeof(GtkTreeIter))
struct TreeviewSymbols
{
GtkTreeIter tag_function;
GtkTreeIter tag_class;
GtkTreeIter tag_macro;
GtkTreeIter tag_member;
GtkTreeIter tag_variable;
GtkTreeIter tag_namespace;
GtkTreeIter tag_struct;
GtkTreeIter tag_type;
GtkTreeIter tag_other;
} tv_iters;
static void init_tag_iters()
{
// init all GtkTreeIters with -1 to make them invalid to avoid crashes when switching between
// filetypes(e.g. config file to Python crashes Geany without this)
tv_iters.tag_function.stamp = -1;
tv_iters.tag_class.stamp = -1;
tv_iters.tag_member.stamp = -1;
tv_iters.tag_macro.stamp = -1;
tv_iters.tag_variable.stamp = -1;
tv_iters.tag_namespace.stamp = -1;
tv_iters.tag_struct.stamp = -1;
tv_iters.tag_type.stamp = -1;
tv_iters.tag_other.stamp = -1;
}
/* Adds symbol list groups in (iter*, title) pairs.
* The list must be ended with NULL. */
static void G_GNUC_NULL_TERMINATED
tag_list_add_groups(GtkTreeStore *tree_store, ...)
{
va_list args;
GtkTreeIter *iter;
static GtkIconTheme *icon_theme = NULL;
static gint x, y;
if (icon_theme == NULL)
{
#ifndef G_OS_WIN32
gchar *path = g_strconcat(GEANY_DATADIR, "/icons", NULL);
#endif
gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &x, &y);
icon_theme = gtk_icon_theme_get_default();
#ifdef G_OS_WIN32
gtk_icon_theme_append_search_path(icon_theme, "share\\icons");
#else
gtk_icon_theme_append_search_path(icon_theme, path);
g_free(path);
#endif
}
va_start(args, tree_store);
for (; iter = va_arg(args, GtkTreeIter*), iter != NULL;)
{
gchar *title = va_arg(args, gchar*);
gchar *icon_name = va_arg(args, gchar *);
GdkPixbuf *icon = NULL;
if (icon_name)
{
icon = gtk_icon_theme_load_icon(icon_theme, icon_name, x, 0, NULL);
}
g_assert(title != NULL);
gtk_tree_store_append(tree_store, iter, NULL);
if (G_IS_OBJECT(icon))
{
gtk_tree_store_set(tree_store, iter, SYMBOLS_COLUMN_ICON, icon,
SYMBOLS_COLUMN_NAME, title, -1);
g_object_unref(icon);
}
else
gtk_tree_store_set(tree_store, iter, SYMBOLS_COLUMN_NAME, title, -1);
}
va_end(args);
}
static void init_tag_list(gint idx)
{
filetype_id ft_id = doc_list[idx].file_type->id;
GtkTreeStore *tag_store = doc_list[idx].tag_store;
init_tag_iters();
switch (ft_id)
{
case GEANY_FILETYPES_DIFF:
{
tag_list_add_groups(tag_store,
&(tv_iters.tag_function), _("Files"), NULL, NULL);
break;
}
case GEANY_FILETYPES_DOCBOOK:
{
tag_list_add_groups(tag_store,
&(tv_iters.tag_function), _("Chapter"), NULL,
&(tv_iters.tag_class), _("Section"), NULL,
&(tv_iters.tag_member), _("Sect1"), NULL,
&(tv_iters.tag_macro), _("Sect2"), NULL,
&(tv_iters.tag_variable), _("Sect3"), NULL,
&(tv_iters.tag_struct), _("Appendix"), NULL,
&(tv_iters.tag_other), _("Other"), NULL,
NULL);
// &(tv_iters.tag_namespace), _("Other"), NULL, NULL);
break;
}
case GEANY_FILETYPES_HASKELL:
tag_list_add_groups(tag_store,
&tv_iters.tag_namespace, _("Module"), NULL,
&tv_iters.tag_struct, _("Types"), NULL,
&tv_iters.tag_macro, _("Type constructors"), NULL,
&tv_iters.tag_function, _("Functions"), "classviewer-method",
NULL);
break;
case GEANY_FILETYPES_CONF:
tag_list_add_groups(tag_store,
&tv_iters.tag_namespace, _("Sections"), NULL,
&tv_iters.tag_macro, _("Keys"), NULL,
NULL);
break;
case GEANY_FILETYPES_LATEX:
{
tag_list_add_groups(tag_store,
&(tv_iters.tag_function), _("Command"), NULL,
&(tv_iters.tag_class), _("Environment"), NULL,
&(tv_iters.tag_member), _("Section"), NULL,
&(tv_iters.tag_macro), _("Subsection"), NULL,
&(tv_iters.tag_variable), _("Subsubsection"), NULL,
&(tv_iters.tag_struct), _("Label"), NULL,
&(tv_iters.tag_namespace), _("Chapter"), NULL,
&(tv_iters.tag_other), _("Other"), NULL,
NULL);
break;
}
case GEANY_FILETYPES_PERL:
{
tag_list_add_groups(tag_store,
&(tv_iters.tag_class), _("Package"), NULL,
&(tv_iters.tag_function), _("Functions"), "classviewer-method",
&(tv_iters.tag_member), _("My"), NULL,
&(tv_iters.tag_macro), _("Local"), NULL,
&(tv_iters.tag_variable), _("Our"), NULL,
NULL);
//&(tv_iters.tag_struct), _("Label"), NULL,
//&(tv_iters.tag_namespace), _("Begin"), NULL,
//&(tv_iters.tag_other), _("Other"), NULL, NULL);
break;
}
case GEANY_FILETYPES_PHP:
{
tag_list_add_groups(tag_store,
&(tv_iters.tag_struct), _("Interfaces"), "classviewer-struct",
&(tv_iters.tag_class), _("Classes"), "classviewer-class",
&(tv_iters.tag_function), _("Functions"), "classviewer-method",
&(tv_iters.tag_macro), _("Constants"), "classviewer-macro",
&(tv_iters.tag_variable), _("Variables"), "classviewer-var",
NULL);
//&(tv_iters.tag_struct), _("Label"),
//&(tv_iters.tag_namespace), _("Begin"),
//&(tv_iters.tag_other), _("Other"), NULL);
break;
}
case GEANY_FILETYPES_REST:
{
tag_list_add_groups(tag_store,
&(tv_iters.tag_namespace), _("Chapter"), NULL,
&(tv_iters.tag_member), _("Section"), NULL,
&(tv_iters.tag_macro), _("Subsection"), NULL,
&(tv_iters.tag_variable), _("Subsubsection"), NULL,
NULL);
break;
}
case GEANY_FILETYPES_RUBY:
{
tag_list_add_groups(tag_store,
&(tv_iters.tag_namespace), _("Modules"), NULL,
&(tv_iters.tag_class), _("Classes"), "classviewer-class",
&(tv_iters.tag_member), _("Singletons"), "classviewer-struct",
&(tv_iters.tag_function), _("Methods"), "classviewer-method",
NULL);
//&(tv_iters.tag_namespace), _("Begin"),
//&(tv_iters.tag_other), _("Other"), NULL);
break;
}
case GEANY_FILETYPES_PYTHON:
{
tag_list_add_groups(tag_store,
&(tv_iters.tag_class), _("Classes"), "classviewer-class",
&(tv_iters.tag_member), _("Methods"), "classviewer-member",
&(tv_iters.tag_function), _("Functions"), "classviewer-method",
&(tv_iters.tag_variable), _("Variables"), "classviewer-var",
NULL);
//&(tv_iters.tag_macro), _("Mixin"),
//&(tv_iters.tag_variable), _("Variables"),
//&(tv_iters.tag_struct), _("Members"),
//&(tv_iters.tag_namespace), _("Begin"),
//&(tv_iters.tag_other), _("Other"), NULL);
break;
}
case GEANY_FILETYPES_VHDL:
{
tag_list_add_groups(tag_store,
&(tv_iters.tag_function), _("Functions"), "classviewer-method",
//&(tv_iters.tag_class), _("Constants"),
//&(tv_iters.tag_member), _("Members"),
//&(tv_iters.tag_macro), _("Macros"),
&(tv_iters.tag_variable), _("Variables"), "classviewer-var",
NULL);
//&(tv_iters.tag_namespace), _("Namespaces"),
//&(tv_iters.tag_struct), _("Signals"),
//&(tv_iters.tag_other), _("Other"), NULL);
break;
}
case GEANY_FILETYPES_JAVA:
{
tag_list_add_groups(tag_store,
&(tv_iters.tag_namespace), _("Package"), NULL,
&(tv_iters.tag_struct), _("Interfaces"), "classviewer-struct",
&(tv_iters.tag_class), _("Classes"), "classviewer-class",
&(tv_iters.tag_function), _("Methods"), "classviewer-method",
&(tv_iters.tag_member), _("Members"), "classviewer-member",
// &(tv_iters.tag_macro), _("Macros"),
// &(tv_iters.tag_variable), _("Variables"),
&(tv_iters.tag_other), _("Other"), "classviewer-other",
NULL);
break;
}
case GEANY_FILETYPES_HAXE:
{
tag_list_add_groups(tag_store,
&(tv_iters.tag_struct), _("Interfaces"), "classviewer-struct",
&(tv_iters.tag_class), _("Classes"), "classviewer-class",
&(tv_iters.tag_function), _("Methods"), "classviewer-method",
&(tv_iters.tag_type), _("Types"), "classviewer-macro",
&(tv_iters.tag_variable), _("Variables"), "classviewer-var",
&(tv_iters.tag_other), _("Other"), "classviewer-other",
NULL);
break;
}
case GEANY_FILETYPES_BASIC:
{
tag_list_add_groups(tag_store,
&(tv_iters.tag_function), _("Functions"), "classviewer-method",
&(tv_iters.tag_variable), _("Variables"), "classviewer-var",
&(tv_iters.tag_macro), _("Constants"), "classviewer-macro",
&(tv_iters.tag_struct), _("Types"), "classviewer-namespace",
&(tv_iters.tag_namespace), _("Labels"), "classviewer-member",
&(tv_iters.tag_other), _("Other"), "classviewer-other",
NULL);
break;
}
case GEANY_FILETYPES_D:
default:
{
if (ft_id == GEANY_FILETYPES_D)
tag_list_add_groups(tag_store,
&(tv_iters.tag_namespace), _("Module"), NULL, NULL);
else
tag_list_add_groups(tag_store,
&(tv_iters.tag_namespace), _("Namespaces"), "classviewer-namespace", NULL);
tag_list_add_groups(tag_store,
&(tv_iters.tag_class), _("Classes"), "classviewer-class",
&(tv_iters.tag_function), _("Functions"), "classviewer-method",
&(tv_iters.tag_member), _("Members"), "classviewer-member",
&(tv_iters.tag_struct), _("Structs / Typedefs"), "classviewer-struct",
NULL);
if (ft_id != GEANY_FILETYPES_D)
{
tag_list_add_groups(tag_store,
&(tv_iters.tag_macro), _("Macros"), "classviewer-macro", NULL);
}
tag_list_add_groups(tag_store,
&(tv_iters.tag_variable), _("Variables"), "classviewer-var",
&(tv_iters.tag_other), _("Other"), "classviewer-other", NULL);
}
}
}
/* the following code surely can be improved, at the moment it collects some iters
* for removal and after that the actual removal is done. I didn't find a way to find and remove
* an empty row in one loop (next iter fails then) */
static void hide_empty_rows(GtkTreeModel *model, GtkTreeStore *store)
{
GtkTreeIter iter, *iters[MAX_SYMBOL_TYPES] = { NULL };
guint i = 0;
if (! gtk_tree_model_get_iter_first(model, &iter))
return; // stop when first iter is invalid, i.e. no elements
do // first collect the iters we need to delete empty rows
{
if (! gtk_tree_model_iter_has_child(model, &iter))
iters[i++] = gtk_tree_iter_copy(&iter);
} while (gtk_tree_model_iter_next(model, &iter));
// now actually delete the collected iters
for (i = 0; i < MAX_SYMBOL_TYPES; i++)
{
if (iters[i] == NULL)
break;
gtk_tree_store_remove(store, iters[i]);
gtk_tree_iter_free(iters[i]);
}
}
gboolean symbols_recreate_tag_list(gint idx, gboolean sort_by_name)
{
GList *tmp;
const GList *tags;
GtkTreeIter iter;
GtkTreeModel *model;
filetype_id ft_id = FILETYPE_ID(doc_list[idx].file_type);
g_return_val_if_fail(DOC_IDX_VALID(idx), FALSE);
tags = get_tag_list(idx, tm_tag_max_t, sort_by_name);
if (doc_list[idx].tm_file == NULL || tags == NULL)
return FALSE;
gtk_tree_store_clear(doc_list[idx].tag_store);
// unref the store to speed up the filling(from TreeView Tutorial)
model = gtk_tree_view_get_model(GTK_TREE_VIEW(doc_list[idx].tag_tree));
g_object_ref(model); // Make sure the model stays with us after the tree view unrefs it
gtk_tree_view_set_model(GTK_TREE_VIEW(doc_list[idx].tag_tree), NULL); // Detach model from view
init_tag_list(idx);
for (tmp = (GList*)tags; tmp; tmp = g_list_next(tmp))
{
gchar buf[100];
const GeanySymbol *symbol = (GeanySymbol*)tmp->data;
GtkTreeIter *parent = NULL;
GdkPixbuf *icon = NULL;
g_snprintf(buf, sizeof(buf), "%s [%d]", symbol->str, symbol->line);
switch (symbol->type)
{
case tm_tag_prototype_t:
case tm_tag_method_t:
case tm_tag_function_t:
{
if (tv_iters.tag_function.stamp == -1) break;
parent = &(tv_iters.tag_function);
break;
}
case tm_tag_macro_t:
case tm_tag_macro_with_arg_t:
{
if (tv_iters.tag_macro.stamp == -1) break;
parent = &(tv_iters.tag_macro);
break;
}
case tm_tag_class_t:
{
if (tv_iters.tag_class.stamp == -1) break;
parent = &(tv_iters.tag_class);
break;
}
case tm_tag_member_t:
case tm_tag_field_t:
{
if (tv_iters.tag_member.stamp == -1) break;
parent = &(tv_iters.tag_member);
break;
}
case tm_tag_typedef_t:
case tm_tag_enum_t:
{
// TODO: separate C-like types here also
if (ft_id == GEANY_FILETYPES_HAXE)
{
if (tv_iters.tag_type.stamp == -1) break;
parent = &(tv_iters.tag_type);
break;
}
}
case tm_tag_union_t:
case tm_tag_struct_t:
case tm_tag_interface_t:
{
if (tv_iters.tag_struct.stamp == -1) break;
parent = &(tv_iters.tag_struct);
break;
}
case tm_tag_variable_t:
{
if (tv_iters.tag_variable.stamp == -1) break;
parent = &(tv_iters.tag_variable);
break;
}
case tm_tag_namespace_t:
case tm_tag_package_t:
{
if (tv_iters.tag_namespace.stamp == -1) break;
parent = &(tv_iters.tag_namespace);
break;
}
default:
{
if (tv_iters.tag_other.stamp == -1) break;
parent = &(tv_iters.tag_other);
}
}
if (parent)
{
gtk_tree_model_get(GTK_TREE_MODEL(doc_list[idx].tag_store), parent,
SYMBOLS_COLUMN_ICON, &icon, -1);
gtk_tree_store_append(doc_list[idx].tag_store, &iter, parent);
gtk_tree_store_set(doc_list[idx].tag_store, &iter,
SYMBOLS_COLUMN_ICON, icon,
SYMBOLS_COLUMN_NAME, buf,
SYMBOLS_COLUMN_LINE, symbol->line, -1);
if (G_LIKELY(G_IS_OBJECT(icon)))
g_object_unref(icon);
}
}
hide_empty_rows(model, doc_list[idx].tag_store);
gtk_tree_view_set_model(GTK_TREE_VIEW(doc_list[idx].tag_tree), model); // Re-attach model to view
g_object_unref(model);
gtk_tree_view_expand_all(GTK_TREE_VIEW(doc_list[idx].tag_tree));
return TRUE;
}
/* Detects a global tags filetype from the *.lang.* language extension.
* Returns NULL if there was no matching TM language. */
static filetype *detect_global_tags_filetype(const gchar *utf8_filename)
{
gchar *tags_ext;
gchar *shortname = g_strdup(utf8_filename);
filetype *ft = NULL;
tags_ext = strstr(shortname, ".tags");
if (tags_ext)
{
*tags_ext = '\0'; // remove .tags extension
ft = filetypes_detect_from_filename(shortname);
}
g_free(shortname);
if (ft == NULL || ! filetype_has_tags(ft))
return NULL;
return ft;
}
/* Adapted from anjuta-2.0.2/global-tags/tm_global_tags.c, thanks.
* Needs full paths for filenames, except for C/C++ tag files, when CFLAGS includes
* the relevant path.
* Example:
* CFLAGS=-I/home/user/libname-1.x geany -g libname.d.tags libname.h */
int symbols_generate_global_tags(int argc, char **argv, gboolean want_preprocess)
{
/* -E pre-process, -dD output user macros, -p prof info (?),
* -undef remove builtin macros (seems to be needed with FC5 gcc 4.1.1 */
const char pre_process[] = "gcc -E -dD -p -undef";
if (argc > 2)
{
/* Create global taglist */
int status;
char *command;
const char *tags_file = argv[1];
char *utf8_fname;
filetype *ft;
utf8_fname = utils_get_utf8_from_locale(tags_file);
ft = detect_global_tags_filetype(utf8_fname);
g_free(utf8_fname);
if (ft == NULL)
{
g_printerr(_("Unknown filetype extension for \"%s\".\n"), tags_file);
return 1;
}
if (want_preprocess && (ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP))
command = g_strdup_printf("%s %s", pre_process, NVL(getenv("CFLAGS"), ""));
else
command = NULL; // don't preprocess
geany_debug("Generating %s tags file.", ft->name);
tm_get_workspace();
status = tm_workspace_create_global_tags(app->configdir, command,
(const char **) (argv + 2),
argc - 2, tags_file, ft->lang);
g_free(command);
if (! status)
{
g_printerr(_("Failed to create tags file, perhaps because no tags "
"were found.\n"));
return 1;
}
}
else
{
g_printerr(_("Usage: %s -g <Tag File> <File list>\n\n"), argv[0]);
g_printerr(_("Example:\n"
"CFLAGS=`pkg-config gtk+-2.0 --cflags` %s -g gtk2.c.tags"
" /usr/include/gtk-2.0/gtk/gtk.h\n"), argv[0]);
return 1;
}
return 0;
}
void symbols_show_load_tags_dialog()
{
GtkWidget *dialog;
GtkFileFilter *filter;
dialog = gtk_file_chooser_dialog_new(_("Load Tags"), GTK_WINDOW(app->window),
GTK_FILE_CHOOSER_ACTION_OPEN,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OPEN, GTK_RESPONSE_OK,
NULL);
gtk_widget_set_name(dialog, "GeanyDialog");
filter = gtk_file_filter_new();
gtk_file_filter_set_name(filter, _("Geany tag files (*.tags)"));
gtk_file_filter_add_pattern(filter, "*.tags");
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK)
{
GSList *flist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
GSList *item;
for (item = flist; item != NULL; item = g_slist_next(item))
{
gchar *fname = item->data;
gchar *utf8_fname;
filetype *ft;
utf8_fname = utils_get_utf8_from_locale(fname);
ft = detect_global_tags_filetype(utf8_fname);
if (ft != NULL && tm_workspace_load_global_tags(fname, ft->lang))
ui_set_statusbar(TRUE, _("Loaded %s tags file '%s'."), ft->name, utf8_fname);
else
ui_set_statusbar(TRUE, _("Could not load tags file '%s'."), utf8_fname);
g_free(utf8_fname);
g_free(fname);
}
g_slist_free(flist);
}
gtk_widget_destroy(dialog);
}
/* Fills a hash table with filetype keys that hold a linked list of filenames. */
static GHashTable *get_tagfile_hash(const GSList *file_list)
{
const GSList *node;
GHashTable *hash = g_hash_table_new(NULL, NULL);
for (node = file_list; node != NULL; node = g_slist_next(node))
{
GList *fnames;
gchar *fname = node->data;
gchar *utf8_fname = utils_get_utf8_from_locale(fname);
filetype *ft = detect_global_tags_filetype(utf8_fname);
g_free(utf8_fname);
if (FILETYPE_ID(ft) < GEANY_FILETYPES_ALL)
{
fnames = g_hash_table_lookup(hash, ft); // may be NULL
fnames = g_list_append(fnames, fname);
g_hash_table_insert(hash, ft, fnames);
}
else
geany_debug("Unknown filetype for file '%s'.", fname);
}
return hash;
}
static GHashTable *init_user_tags()
{
GSList *file_list;
GHashTable *lang_hash;
user_tags_dir = g_strconcat(app->configdir, G_DIR_SEPARATOR_S, "tags", NULL);
file_list = utils_get_file_list(user_tags_dir, NULL, NULL);
lang_hash = get_tagfile_hash(file_list);
// don't need to delete list contents because they are now used for hash contents
g_slist_free(file_list);
// create the tags dir for next time if it doesn't exist
if (! g_file_test(user_tags_dir, G_FILE_TEST_IS_DIR))
{
utils_mkdir(user_tags_dir, FALSE);
}
return lang_hash;
}
static void load_user_tags(filetype_id ft_id)
{
static guchar tags_loaded[GEANY_FILETYPES_ALL] = {0};
static GHashTable *lang_hash = NULL;
GList *fnames;
const GList *node;
const filetype *ft = filetypes[ft_id];
g_return_if_fail(ft_id < GEANY_FILETYPES_ALL);
if (tags_loaded[ft_id])
return;
tags_loaded[ft_id] = TRUE; // prevent reloading
if (lang_hash == NULL)
lang_hash = init_user_tags();
fnames = g_hash_table_lookup(lang_hash, ft);
for (node = fnames; node != NULL; node = g_list_next(node))
{
const gint tm_lang = ft->lang;
gchar *fname;
fname = g_strconcat(user_tags_dir, G_DIR_SEPARATOR_S, node->data, NULL);
tm_workspace_load_global_tags(fname, tm_lang);
geany_debug("Loaded %s (%s).", fname, ft->name);
g_free(fname);
}
g_list_foreach(fnames, (GFunc) g_free, NULL);
g_list_free(fnames);
g_hash_table_remove(lang_hash, (gpointer) ft);
}
gboolean symbols_goto_tag(const gchar *name, gboolean definition)
{
const gint forward_types = tm_tag_prototype_t | tm_tag_externvar_t;
gint type;
TMTag *tmtag;
// goto tag definition: all except prototypes / forward declarations / externs
type = (definition) ? tm_tag_max_t - forward_types : forward_types;
tmtag = find_workspace_tag(name, type);
if (tmtag != NULL)
{
gint new_idx = document_find_by_filename(
tmtag->atts.entry.file->work_object.file_name, TRUE);
// not found in opened document, should open
if (new_idx == -1)
{
new_idx = document_open_file(tmtag->atts.entry.file->work_object.file_name, FALSE, NULL, NULL);
}
if (navqueue_goto_line(new_idx, tmtag->atts.entry.line))
return TRUE;
}
// if we are here, there was no match and we are beeping ;-)
utils_beep();
if (type == forward_types)
ui_set_statusbar(FALSE, _("Forward declaration \"%s\" not found."), name);
else
ui_set_statusbar(FALSE, _("Definition of \"%s\" not found."), name);
return FALSE;
}