Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New tagmanager query module #1187

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion doc/Doxyfile.in
Expand Up @@ -262,6 +262,7 @@ ALIASES += "optional=\noop \xmlonly <simplesect kind=\"geany:optio
ALIASES += "cb=\noop \xmlonly <simplesect kind=\"geany:scope\">notified</simplesect>\endxmlonly"
ALIASES += "cbdata=\noop \xmlonly <simplesect kind=\"geany:closure\"></simplesect>\endxmlonly"
ALIASES += "cbfree=\noop \xmlonly <simplesect kind=\"geany:destroy\"></simplesect>\endxmlonly"
ALIASES += "array{1}=\noop \xmlonly <simplesect kind=\"geany:array\">\1</simplesect>\endxmlonly"


# This tag can be used to specify a number of word-keyword mappings (TCL only).
Expand Down Expand Up @@ -799,7 +800,9 @@ INPUT = @top_srcdir@/src/ \
@top_srcdir@/src/tagmanager/tm_workspace.c \
@top_srcdir@/src/tagmanager/tm_workspace.h \
@top_srcdir@/src/tagmanager/tm_tag.h \
@top_srcdir@/src/tagmanager/tm_parser.h
@top_srcdir@/src/tagmanager/tm_parser.h \
@top_srcdir@/src/tagmanager/tm_query.c \
@top_srcdir@/src/tagmanager/tm_query.h

# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
Expand Down
3 changes: 2 additions & 1 deletion scripts/gen-api-gtkdoc.py
Expand Up @@ -77,7 +77,8 @@ def cb(self, type, str):
self.annot.append(type.split(":")[1])
elif type in ("geany:transfer",
"geany:element-type",
"geany:scope"):
"geany:scope",
"geany:array"):
type = type.split(":")[1]
self.annot.append("%s %s" % (type, str))
elif (type == "see"):
Expand Down
6 changes: 5 additions & 1 deletion src/tagmanager/Makefile.am
@@ -1,8 +1,9 @@
AM_CPPFLAGS = \
-I$(srcdir) \
-I$(top_srcdir)/src \
-I$(top_srcdir)/ctags/main \
-DGEANY_PRIVATE \
-DG_LOG_DOMAIN=\"Tagmanager\"

AM_CFLAGS = \
$(GTK_CFLAGS) \
@LIBGEANY_CFLAGS@
Expand All @@ -11,6 +12,7 @@ noinst_LTLIBRARIES = libtagmanager.la

tagmanager_includedir = $(includedir)/geany/tagmanager
tagmanager_include_HEADERS = \
tm_query.h \
tm_source_file.h \
tm_tag.h \
tm_workspace.h \
Expand All @@ -20,6 +22,8 @@ tagmanager_include_HEADERS = \
libtagmanager_la_SOURCES =\
tm_parser.h \
tm_parser.c \
tm_query.c \
tm_query.h \
tm_source_file.h \
tm_source_file.c \
tm_tag.h \
Expand Down
299 changes: 299 additions & 0 deletions src/tagmanager/tm_query.c
@@ -0,0 +1,299 @@
/*
*
* Copyright (c) 2016 Thomas Martitz <kugel@rockbox.org>
*
* This source code is released for free distribution under the terms of the
* GNU General Public License.
*
*/


/**
* @file tm_query.h
* The TMQuery structure and methods to query tags from the global workspace.
*/

#include <glib.h>
#include <glib-object.h>

#include "utils.h"

#include "tm_workspace.h"
#include "tm_query.h"
#include "tm_tag.h"
#include "tm_parser.h"
#include "tm_source_file.h"


struct TMQuery
{
const TMWorkspace *workspace;
gint data_sources;
GPtrArray *names;
GPtrArray *scopes;
gint type;
GArray *langs;
gint refcount;
};


static TMQuery *tm_query_dup(TMQuery *q)
{
g_atomic_int_inc(&q->refcount);

g_return_val_if_fail(NULL != q, NULL);

return q;
}


/** Gets the GBoxed-derived GType for TMQuery
*
* @return TMQuery type. */
GEANY_API_SYMBOL
GType tm_query_get_type(void);

G_DEFINE_BOXED_TYPE(TMQuery, tm_query, tm_query_dup, tm_query_free);

static void free_g_string(gpointer data)
{
g_string_free((GString *) data, TRUE);
}


/** Create a tag query
*
* The query can be used to retrieve tags from the tagmanager. You can
* optionally add filters and execute the query with tm_query_exec().
* Finally it must be freed with tm_query_free().
*
* @param workspace The workspace where the tags are stored
* @param data_sources Bitmask of data sources. @see TMQuerySource
*
* @return The opaque query pointer.
*/
GEANY_API_SYMBOL
TMQuery *tm_query_new(const TMWorkspace *workspace, gint data_sources)
{
TMQuery *q = g_slice_new(TMQuery);

q->workspace = workspace;
q->data_sources = data_sources;
q->names = g_ptr_array_new_with_free_func(free_g_string);
q->langs = g_array_new(FALSE, FALSE, sizeof(TMParserType));
q->scopes = g_ptr_array_new_with_free_func(free_g_string);
q->type = -1;
q->refcount = 1;

return q;
}


/** Decrements the reference count of @a q
*
* If the reference point drops to 0, then @a q is freed.
*
* @param q the query to free.
*/
GEANY_API_SYMBOL
void tm_query_free(TMQuery *q)
{
if (NULL != q && g_atomic_int_dec_and_test(&q->refcount))
{
g_ptr_array_free(q->names, TRUE);
g_ptr_array_free(q->scopes, TRUE);
g_array_free(q->langs, TRUE);
g_slice_free(TMQuery, q);
}
}


/** Add name filter to a query
*
* The query results will be restricted to tags matching the name. The
* matching is a simple prefix matching. The length to match can be
* limited to allow multiple tags to match a prefix.
*
* @param q The query to operate on.
* @param name The name to filter.
* @param name_len The number of characters to use (from the beginning).
*
* @return 0 on succcess, < 0 on error.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like a better job for a boolean

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It never returns an error either, for example if q or name is NULL (the latter causing an empty string in the array as opposed to a crash).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ability to return error codes is more to make it extensible. Since I want them in the plugin API the return type should be determined now (even if it always returns the same value now).

While gboolean would work, I've almost always regretted using in the past. gint is so much more flexible. I can change to boolean if you truly want it, though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want it to be a nice API for non-C languages, go all the way and use a GError, that will be translated to a proper exception, contains a code and even a message. Not saying it's really worth it, but it seems more like the canonical way of doing it in GLib-using stuff.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…and it still allows the boolean-based calls of if (call()) in C

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a serious suggestion?

If you dislike gint return so much I will change it. Just saying I regularly regret this later on (but not always).

*/
GEANY_API_SYMBOL
gint tm_query_add_name(TMQuery *q, const gchar *name, gssize name_len)
{
GString *s;

if (name_len < 0)
s = g_string_new(name);
else
s = g_string_new_len(name, name_len);

g_ptr_array_add(q->names, s);

return 0;
}


/** Add scope filter to a query
*
* The query results will be restricted to tags that are subordenates of
* a container scope matching @a scope, for example a C++ class.
*
* @param q The query to operate on.
* @param scope The name of the container or parent scope.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is scope just a single name or is it some kind of delimited string (ex. mynamespace/myclass/myinnerclass) ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Full, delimited string:

namespace Foo {
    namespace Bar {
        class Baz
        {
        public:
            static int getA() { return 42; }
        };
    };
};

tag->name -> getA
tag->scope -> Foo::Bar::Baz

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, that's a good thing. It might be useful to document that here even if it's not unique to this function. Also s/subordenates/subordinates/ or could reword it like "...tags which are scoped within the given 'scope'".

*
* @return 0 on succcess, < 0 on error.
*/
GEANY_API_SYMBOL
gint tm_query_add_scope(TMQuery *q, const gchar *scope, gssize scope_len)
{
GString *s;

if (scope_len < 0)
s = g_string_new(scope);
else
s = g_string_new_len(scope, scope_len);

g_ptr_array_add(q->scopes, s);

return 0;
}


/** Add language filter to a query
*
* The query results will be restricted to tags whose corresponding
* @ref TMSourceFile is written in a specific programming language.
*
* @param q The query to operate on.
* @param lang The programming language, see @a TMParserType.
*
* @return 0 on succcess, < 0 on error.
*/
GEANY_API_SYMBOL
gint tm_query_add_lang(TMQuery *q, TMParserType lang)
{
g_array_append_val(q->langs, lang);

return 0;
}


/** Set the tag type filter of a query.
*
* The query results will be restricted to tags match any of the given types.
* You can only set this filter because the @a type is a bitmask containing
* multiple values.
*
* @param q The query to operate on.
* @param type Bitmask of the tag type to filter, see @a TMTagType.
*
* @return 0 on succcess, < 0 on error.
*/
GEANY_API_SYMBOL
gint tm_query_set_tag_type(TMQuery *q, TMTagType type)
{
if (type > tm_tag_max_t)
return -1;

q->type = type;

return 0;
}


/** Execute a query
*
* The query will be executed according to the applied filters. Filters
* of the same type are combined using logical OR. Then, filters of different
* types are combined using logcal AND.
*
* The execution is stateless. Filters can be added after execution and the
* query can be re-run. Previous runs don't add side-effects.
*
* The results are generally sorted by tag name, even if @a sort_attr is NULL.
* supplied. The results can be
* sorted by @a sort_attr, and deduplicated by @a dedup_attr. Deduplication
* happens after sorting. There is an additional limitation for deduplication:
* Only adjacent tags can be deduplicated so they must be suitably ordered.
* This can be achieved by using the same attributes for @a sort_attr and
* @a dedup_attr.
*
* The results are stored in a @c GPtrArray. The individual tags are still
* owned by the workspace but the container array must be freed with
* g_ptr_array_free().
*
* @param q The query to execute
* @param sort_attr @nullable @array{zero-terminated=1} Tag attribute to use
* as sort key, or @c NULL to not sort.
* @param dedup_attr @nullable @array{zero-terminated=1} Tag attribute to use
* as deduplicaiton key, or @c NULL to not perform deduplicaiton.
* @return @transfer{container} @elementtype{TMTag} Resulting tag array.
*/
GEANY_API_SYMBOL
GPtrArray *tm_query_exec(TMQuery *q, TMTagAttrType *sort_attr, TMTagAttrType *dedup_attr)
{
TMTag *tag;
guint i, ntags;
GString *s;
TMParserType *lang;
GPtrArray *ret;
TMTagAttrType def_sort_attr[] = { tm_tag_attr_name_t, 0 };

ret = g_ptr_array_new();

foreach_ptr_array(s, i, q->names)
{
TMTag **tags;
if (q->data_sources & TM_QUERY_SOURCE_GLOBAL_TAGS)
{
tags = tm_tags_find(q->workspace->global_tags, s->str, s->len, &ntags);
if (ntags)
{
g_ptr_array_set_size(ret, ret->len + ntags);
memcpy(&ret->pdata[ret->len], *tags, ntags * sizeof(gpointer));
}
}
if (q->data_sources & TM_QUERY_SOURCE_SESSION_TAGS)
{
tags = tm_tags_find(q->workspace->global_tags, s->str, s->len, &ntags);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is q->workspace->global_tags correct here? You do the same as in the TM_QUERY_SOURCE_GLOBAL_TAGS case above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, copy&paste error. Was correct in the initial commit.

if (ntags)
{
g_ptr_array_set_size(ret, ret->len + ntags);
memcpy(&ret->pdata[ret->len], *tags, ntags * sizeof(gpointer));
}
}
}

/* Filter tags according to scope, type and lang. */
foreach_ptr_array(tag, i, ret)
{
gboolean match = TRUE;

foreach_ptr_array(s, i, q->scopes)
match = match && (strncmp(FALLBACK(tag->scope, ""), s->str, s->len) == 0);

foreach_array(gint, lang, q->langs)
match = match && tm_tag_langs_compatible(*lang, tag->lang);

if (match && q->type >= 0)
match = tag->type & q->type;

/* Remove tag from the results. Can use the fast variant since order
* doesn't matter until after this loop. */
if (!match)
g_ptr_array_remove_index_fast(ret, i);
}

/* Always sort by name, unless wanted otherwise by the caller */
if (!sort_attr)
sort_attr = def_sort_attr;
tm_tags_sort(ret, sort_attr, FALSE, FALSE);

if (dedup_attr)
tm_tags_dedup(ret, dedup_attr, FALSE);

return ret;
}
37 changes: 37 additions & 0 deletions src/tagmanager/tm_query.h
@@ -0,0 +1,37 @@
/*
*
* Copyright (c) 2016 Thomas Martitz <kugel@rockbox.org>
*
* This source code is released for free distribution under the terms of the
* GNU General Public License.
*
*/

#ifndef TM_QUERY_H
#define TM_QUERY_H

#include "tm_workspace.h"
#include "tm_parser.h"
#include "tm_tag.h"

/** Opaque data type representing a tagmanager query */
typedef struct TMQuery TMQuery;

/** Possible data sources for a tagmanager query */
typedef enum {
TM_QUERY_SOURCE_GLOBAL_TAGS,
TM_QUERY_SOURCE_SESSION_TAGS,
} TMQuerySource;


TMQuery *tm_query_new(const TMWorkspace *workspace, gint data_sources);
void tm_query_free(TMQuery *q);

gint tm_query_add_name(TMQuery *q, const gchar *name, gssize name_len);
gint tm_query_add_scope(TMQuery *q, const gchar *scope, gssize scope_len);
gint tm_query_add_lang(TMQuery *q, TMParserType lang);
gint tm_query_set_tag_type(TMQuery *q, TMTagType type);

GPtrArray *tm_query_exec(TMQuery *q, TMTagAttrType *sort_attr, TMTagAttrType *dedup_attr);

#endif