From 5c6b423f70a73906bf3a8216c7206ca4a98cdd7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Thu, 7 May 2015 01:02:29 +0200 Subject: [PATCH 01/32] Clean up tm_workspace_find() 1. Factor-out the part common to tags_array and global_tags 2. Get both C/CPP tags when either of the languages is specified (both for global_tags and tags_array) 3. Remove unnecessary strcmp()s (tm_tags_find() should return only tags with the specified name) 4. Various minor cleanups --- tagmanager/src/tm_workspace.c | 102 ++++++++++++---------------------- 1 file changed, 35 insertions(+), 67 deletions(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 76cd108e8a..79f08b2b63 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -32,6 +32,7 @@ #include "tm_workspace.h" #include "tm_tag.h" +#include "tm_parser.h" /* when changing, always keep the three sort criteria below in sync */ @@ -671,6 +672,31 @@ gboolean tm_workspace_create_global_tags(const char *pre_process, const char **i } +static void add_filtered_tags(GPtrArray *tags, TMTag **matches, guint tagCount, + TMTagType type, langType lang) +{ + guint tagIter; + + for (tagIter = 0; tagIter < tagCount; ++tagIter) + { + gint tag_lang = (*matches)->lang; + gint tag_lang_alt = tag_lang; + + /* Accept CPP tags for C lang and vice versa */ + if (tag_lang == TM_PARSER_C) + tag_lang_alt = TM_PARSER_CPP; + else if (tag_lang == TM_PARSER_CPP) + tag_lang_alt = TM_PARSER_C; + + if ((type & (*matches)->type) && + (lang == -1 || tag_lang == lang || tag_lang_alt == lang)) + g_ptr_array_add(tags, *matches); + + matches++; + } +} + + /* Returns all matching tags found in the workspace. @param name The name of the tag to find. @param type The tag types to return (TMTagType). Can be a bitmask. @@ -684,83 +710,25 @@ const GPtrArray *tm_workspace_find(const char *name, TMTagType type, TMTagAttrTy gboolean partial, langType lang) { static GPtrArray *tags = NULL; - TMTag **matches[2]; - size_t len; - guint tagCount[2]={0,0}, tagIter; + TMTag **matches; + guint tagCount; - if (!name) - return NULL; - len = strlen(name); - if (!len) + if (!name || !*name) return NULL; + if (tags) g_ptr_array_set_size(tags, 0); else tags = g_ptr_array_new(); - matches[0] = tm_tags_find(theWorkspace->tags_array, name, partial, TRUE, - &tagCount[0]); - matches[1] = tm_tags_find(theWorkspace->global_tags, name, partial, TRUE, &tagCount[1]); - - /* file tags */ - if (matches[0] && *matches[0]) - { - for (tagIter=0;tagIterlang; - - if ((type & (*matches[0])->type) && (lang == -1 || tag_lang == lang)) - g_ptr_array_add(tags, *matches[0]); - if (partial) - { - if (0 != strncmp((*matches[0])->name, name, len)) - break; - } - else - { - if (0 != strcmp((*matches[0])->name, name)) - break; - } - ++ matches[0]; - } - } - - /* global tags */ - if (matches[1] && *matches[1]) - { - for (tagIter=0;tagIterlang; - gint tag_lang_alt = 0; - - /* tag_lang_alt is used to load C global tags only once for C and C++ - * lang = 1 is C++, lang = 0 is C - * if we have lang 0, than accept also lang 1 for C++ */ - if (tag_lang == 0) /* C or C++ */ - tag_lang_alt = 1; - else - tag_lang_alt = tag_lang; /* otherwise just ignore it */ - - if ((type & (*matches[1])->type) && (lang == -1 || - tag_lang == lang || tag_lang_alt == lang)) - g_ptr_array_add(tags, *matches[1]); - - if (partial) - { - if (0 != strncmp((*matches[1])->name, name, len)) - break; - } - else - { - if (0 != strcmp((*matches[1])->name, name)) - break; - } - ++ matches[1]; - } - } + matches = tm_tags_find(theWorkspace->tags_array, name, partial, TRUE, &tagCount); + add_filtered_tags(tags, matches, tagCount, type, lang); + matches = tm_tags_find(theWorkspace->global_tags, name, partial, TRUE, &tagCount); + add_filtered_tags(tags, matches, tagCount, type, lang); if (attrs) tm_tags_sort(tags, attrs, TRUE, FALSE); + return tags; } From ee3eeeb758ff6519db5d82d4ed01a7e0ea45e9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 23 May 2015 14:25:16 +0200 Subject: [PATCH 02/32] Merge tm_workspace_find() and tm_workspace_find_scoped() They are basically identical except: 1. _scoped() compares scope in addition 2. _scoped() is missing the C/CPP tag compatibility part 3. _scoped() allows returning just single result (unused) 4. _scoped() allows not searching in global tags (unused) Since we now always put lang also under tag->lang, the match_langs() function is not necessary. Extend the add_filtered_tags() (and rename it to fill_find_tags_array()) to perform the tm_tags_find(), compare the scope and add scope as parameter of tm_workspace_find() and eliminate tm_workspace_find_scoped() completely. --- src/editor.c | 10 +-- tagmanager/src/tm_workspace.c | 123 ++++++---------------------------- tagmanager/src/tm_workspace.h | 8 +-- 3 files changed, 27 insertions(+), 114 deletions(-) diff --git a/src/editor.c b/src/editor.c index 50ec48c507..1e8439d7f7 100644 --- a/src/editor.c +++ b/src/editor.c @@ -726,7 +726,7 @@ static void autocomplete_scope(GeanyEditor *editor) if (!name) return; - tags = tm_workspace_find(name, tm_tag_max_t, NULL, FALSE, ft->lang); + tags = tm_workspace_find(name, NULL, tm_tag_max_t, NULL, FALSE, ft->lang); g_free(name); if (!tags || tags->len == 0) return; @@ -1849,7 +1849,7 @@ static gchar *find_calltip(const gchar *word, GeanyFiletype *ft) g_return_val_if_fail(ft && word && *word, NULL); /* use all types in case language uses wrong tag type e.g. python "members" instead of "methods" */ - tags = tm_workspace_find(word, tm_tag_max_t, attrs, FALSE, ft->lang); + tags = tm_workspace_find(word, NULL, tm_tag_max_t, attrs, FALSE, ft->lang); if (tags->len == 0) return NULL; @@ -1859,8 +1859,8 @@ static gchar *find_calltip(const gchar *word, GeanyFiletype *ft) (tag->type == tm_tag_class_t || tag->type == tm_tag_struct_t)) { /* user typed e.g. 'new Classname(' so lookup D constructor Classname::this() */ - tags = tm_workspace_find_scoped("this", tag->name, - arg_types, attrs, FALSE, ft->lang, TRUE); + tags = tm_workspace_find("this", tag->name, + arg_types, attrs, FALSE, ft->lang); if (tags->len == 0) return NULL; } @@ -2041,7 +2041,7 @@ autocomplete_tags(GeanyEditor *editor, const gchar *root, gsize rootlen) doc = editor->document; - tags = tm_workspace_find(root, tm_tag_max_t, attrs, TRUE, doc->file_type->lang); + tags = tm_workspace_find(root, NULL, tm_tag_max_t, attrs, TRUE, doc->file_type->lang); if (tags) { show_tags_list(editor, tags, rootlen); diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 79f08b2b63..6b7655cd97 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -672,11 +672,17 @@ gboolean tm_workspace_create_global_tags(const char *pre_process, const char **i } -static void add_filtered_tags(GPtrArray *tags, TMTag **matches, guint tagCount, - TMTagType type, langType lang) +static guint fill_find_tags_array(GPtrArray *dst, const GPtrArray *src, + const char *name, const char *scope, TMTagType type, gboolean partial, langType lang) { + TMTag **matches; guint tagIter; + guint tagCount; + + if (!src || !dst || !name || !*name) + return 0; + matches = tm_tags_find(src, name, partial, TRUE, &tagCount); for (tagIter = 0; tagIter < tagCount; ++tagIter) { gint tag_lang = (*matches)->lang; @@ -689,16 +695,20 @@ static void add_filtered_tags(GPtrArray *tags, TMTag **matches, guint tagCount, tag_lang_alt = TM_PARSER_C; if ((type & (*matches)->type) && - (lang == -1 || tag_lang == lang || tag_lang_alt == lang)) - g_ptr_array_add(tags, *matches); + (lang == -1 || tag_lang == lang || tag_lang_alt == lang) && + (!scope || g_strcmp0((*matches)->scope, scope) == 0)) + g_ptr_array_add(dst, *matches); matches++; } + + return dst->len; } /* Returns all matching tags found in the workspace. @param name The name of the tag to find. + @param scope The scope name of the tag to find, or NULL. @param type The tag types to return (TMTagType). Can be a bitmask. @param attrs The attributes to sort and dedup on (0 terminated integer array). @param partial Whether partial match is allowed. @@ -706,25 +716,18 @@ static void add_filtered_tags(GPtrArray *tags, TMTag **matches, guint tagCount, -1 for all @return Array of matching tags. Do not free() it since it is a static member. */ -const GPtrArray *tm_workspace_find(const char *name, TMTagType type, TMTagAttrType *attrs, - gboolean partial, langType lang) +const GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type, + TMTagAttrType *attrs, gboolean partial, langType lang) { static GPtrArray *tags = NULL; - TMTag **matches; - guint tagCount; - - if (!name || !*name) - return NULL; if (tags) g_ptr_array_set_size(tags, 0); else tags = g_ptr_array_new(); - matches = tm_tags_find(theWorkspace->tags_array, name, partial, TRUE, &tagCount); - add_filtered_tags(tags, matches, tagCount, type, lang); - matches = tm_tags_find(theWorkspace->global_tags, name, partial, TRUE, &tagCount); - add_filtered_tags(tags, matches, tagCount, type, lang); + fill_find_tags_array(tags, theWorkspace->tags_array, name, scope, type, partial, lang); + fill_find_tags_array(tags, theWorkspace->global_tags, name, scope, type, partial, lang); if (attrs) tm_tags_sort(tags, attrs, TRUE, FALSE); @@ -733,92 +736,6 @@ const GPtrArray *tm_workspace_find(const char *name, TMTagType type, TMTagAttrTy } -static gboolean match_langs(gint lang, const TMTag *tag) -{ - if (tag->file) - { /* workspace tag */ - if (lang == tag->file->lang) - return TRUE; - } - else - { /* global tag */ - if (lang == tag->lang) - return TRUE; - } - return FALSE; -} - - -/* scope can be NULL. - * lang can be -1 */ -static guint -fill_find_tags_array (GPtrArray *dst, const GPtrArray *src, - const char *name, const char *scope, TMTagType type, gboolean partial, - gint lang, gboolean first) -{ - TMTag **match; - guint tagIter, count; - - if ((!src) || (!dst) || (!name) || (!*name)) - return 0; - - match = tm_tags_find (src, name, partial, TRUE, &count); - if (count && match && *match) - { - for (tagIter = 0; tagIter < count; ++tagIter) - { - if (! scope || (match[tagIter]->scope && - 0 == strcmp(match[tagIter]->scope, scope))) - { - if (type & match[tagIter]->type) - if (lang == -1 || match_langs(lang, match[tagIter])) - { - g_ptr_array_add (dst, match[tagIter]); - if (first) - break; - } - } - } - } - return dst->len; -} - - -/* Returns all matching tags found in the workspace. Adapted from tm_workspace_find, Anjuta 2.02 - @param name The name of the tag to find. - @param scope The scope name of the tag to find, or NULL. - @param type The tag types to return (TMTagType). Can be a bitmask. - @param attrs The attributes to sort and dedup on (0 terminated integer array). - @param partial Whether partial match is allowed. - @param lang Specifies the language(see the table in parsers.h) of the tags to be found, - -1 for all - @return Array of matching tags. Do not free() it since it is a static member. -*/ -const GPtrArray * -tm_workspace_find_scoped (const char *name, const char *scope, TMTagType type, - TMTagAttrType *attrs, gboolean partial, langType lang, gboolean global_search) -{ - static GPtrArray *tags = NULL; - - if (tags) - g_ptr_array_set_size (tags, 0); - else - tags = g_ptr_array_new (); - - fill_find_tags_array (tags, theWorkspace->tags_array, - name, scope, type, partial, lang, FALSE); - if (global_search) - { - /* for a scoped tag, I think we always want the same language */ - fill_find_tags_array (tags, theWorkspace->global_tags, - name, scope, type, partial, lang, FALSE); - } - if (attrs) - tm_tags_sort (tags, attrs, TRUE, FALSE); - return tags; -} - - static int find_scope_members_tags (const GPtrArray * all, GPtrArray * tags, const langType langJava, const char *name, @@ -971,7 +888,7 @@ tm_workspace_find_scope_members (const GPtrArray * file_tags, const char *name, { g_ptr_array_set_size (tags, 0); got = fill_find_tags_array (tags, file_tags, - new_name, NULL, types, FALSE, -1, FALSE); + new_name, NULL, types, FALSE, -1); } if (got) { @@ -983,7 +900,7 @@ tm_workspace_find_scope_members (const GPtrArray * file_tags, const char *name, tm_tag_attr_name_t, tm_tag_attr_type_t, tm_tag_attr_none_t }; - tags2 = tm_workspace_find (new_name, types, attrs, FALSE, -1); + tags2 = tm_workspace_find (new_name, NULL, types, attrs, FALSE, -1); } if ((tags2) && (tags2->len == 1) && (tag = TM_TAG (tags2->pdata[0]))) diff --git a/tagmanager/src/tm_workspace.h b/tagmanager/src/tm_workspace.h index 4283804ec0..12f8cbf30f 100644 --- a/tagmanager/src/tm_workspace.h +++ b/tagmanager/src/tm_workspace.h @@ -54,12 +54,8 @@ gboolean tm_workspace_load_global_tags(const char *tags_file, gint mode); gboolean tm_workspace_create_global_tags(const char *pre_process, const char **includes, int includes_count, const char *tags_file, int lang); -const GPtrArray *tm_workspace_find(const char *name, TMTagType type, TMTagAttrType *attrs, - gboolean partial, langType lang); - -const GPtrArray * -tm_workspace_find_scoped (const char *name, const char *scope, TMTagType type, - TMTagAttrType *attrs, gboolean partial, langType lang, gboolean global_search); +const GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type, + TMTagAttrType *attrs, gboolean partial, langType lang); const GPtrArray *tm_workspace_find_scope_members(const GPtrArray *file_tags, const char *scope_name, From 5c18b3d1328b8d3dff923a2a02bc3cc6b41a333c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Fri, 8 May 2015 01:29:08 +0200 Subject: [PATCH 03/32] Improve scoped search This patch contains a bit too many things which are however related. It started by the part in editor.c (where we previously used only the first type we found to perform scoped search) by going through all the possible variable types until the scoped search returns some result (this is useful if variable foo is used once as int and once as struct and if the int is the first type found, we won't get the struct's members). This didn't work. After an hour of debugging, it turned out that because tm_workspace_find_scope_members() calls internally tm_workspace_find() and this function returns static array, this invalidates the array returned by the tm_workspace_find() used previously to get all the possible variable types. Since this is really dangerous and hard to notice, I tried to eliminate the static returns from both tm_workspace_find() and tm_workspace_find_scoped_members(). The tm_workspace_find_scoped_members() function is where I got stuck because as I started to understand what it's doing, I found many problems there. This patch does the following in this function: 1. Eliminates search_global and no_definitions parameters because we always search the whole workspace and this simplifies the slightly strange logic at the end of the function. 2. Returns members from global tags even when something found in workspace tags - previously global tags were skipped when something was found from workspace tags but I don't see a reason why. 3. Adds the lang parameter to restrict tags by language (we do this with normal search and the same should be done here). 4. Previously when searching for types with members the function returned NULL when more than one such type was found (there should have been >=1 instead of ==1 at line 906). This patch improves the logic a bit and if multiple types are found, it tries to use the one which is other than typedef because it probably has some members (the typedef can resolve to e.g. int). 5. Previously the function prevented only direct typedef loops like typedef A B; typedef B A; but a loop like A->B->C->A would lead to an infinite cycle. This patch restricts the number how many times the typedef can be resolved by using for loop with limited number of repetitions and giving up when nothing useful is resolved. 6. Finally the patch tries to simplify the function a bit, make it easier to read and adds some comments to make it clearer what the function does. --- src/editor.c | 60 ++++++--- tagmanager/src/tm_workspace.c | 230 +++++++++++++++------------------- tagmanager/src/tm_workspace.h | 8 +- 3 files changed, 144 insertions(+), 154 deletions(-) diff --git a/src/editor.c b/src/editor.c index 1e8439d7f7..e7567caa17 100644 --- a/src/editor.c +++ b/src/editor.c @@ -703,9 +703,10 @@ static void autocomplete_scope(GeanyEditor *editor) gint pos = sci_get_current_position(editor->sci); gchar typed = sci_get_char_at(sci, pos - 1); gchar *name; - const GPtrArray *tags = NULL; + GPtrArray *tags; const TMTag *tag; GeanyFiletype *ft = editor->document->file_type; + guint i; if (ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP) { @@ -728,20 +729,27 @@ static void autocomplete_scope(GeanyEditor *editor) tags = tm_workspace_find(name, NULL, tm_tag_max_t, NULL, FALSE, ft->lang); g_free(name); - if (!tags || tags->len == 0) - return; - tag = g_ptr_array_index(tags, 0); - name = tag->var_type; - if (name) + foreach_ptr_array(tag, i, tags) { - TMSourceFile *obj = editor->document->tm_file; - - tags = tm_workspace_find_scope_members(obj ? obj->tags_array : NULL, - name, TRUE, FALSE); - if (tags) - show_tags_list(editor, tags, 0); + if (tag->var_type) + { + TMSourceFile *sf = editor->document->tm_file; + GPtrArray *member_tags; + gboolean found; + + member_tags = tm_workspace_find_scope_members(sf ? sf->tags_array : NULL, + tag->var_type, + sf ? sf->lang : -1); + found = member_tags && member_tags->len > 0; + if (found) + show_tags_list(editor, member_tags, 0); + g_ptr_array_free(member_tags, TRUE); + if (found) + break; + } } + g_ptr_array_free(tags, TRUE); } @@ -1838,7 +1846,7 @@ static gboolean append_calltip(GString *str, const TMTag *tag, filetype_id ft_id static gchar *find_calltip(const gchar *word, GeanyFiletype *ft) { - const GPtrArray *tags; + GPtrArray *tags; const TMTagType arg_types = tm_tag_function_t | tm_tag_prototype_t | tm_tag_method_t | tm_tag_macro_with_arg_t; TMTagAttrType *attrs = NULL; @@ -1851,18 +1859,25 @@ static gchar *find_calltip(const gchar *word, GeanyFiletype *ft) /* use all types in case language uses wrong tag type e.g. python "members" instead of "methods" */ tags = tm_workspace_find(word, NULL, tm_tag_max_t, attrs, FALSE, ft->lang); if (tags->len == 0) + { + g_ptr_array_free(tags, TRUE); return NULL; + } tag = TM_TAG(tags->pdata[0]); if (ft->id == GEANY_FILETYPES_D && (tag->type == tm_tag_class_t || tag->type == tm_tag_struct_t)) { + g_ptr_array_free(tags, TRUE); /* user typed e.g. 'new Classname(' so lookup D constructor Classname::this() */ tags = tm_workspace_find("this", tag->name, arg_types, attrs, FALSE, ft->lang); if (tags->len == 0) + { + g_ptr_array_free(tags, TRUE); return NULL; + } } /* remove tags with no argument list */ @@ -1875,7 +1890,10 @@ static gchar *find_calltip(const gchar *word, GeanyFiletype *ft) } tm_tags_prune((GPtrArray *) tags); if (tags->len == 0) + { + g_ptr_array_free(tags, TRUE); return NULL; + } else { /* remove duplicate calltips */ TMTagAttrType sort_attr[] = {tm_tag_attr_name_t, tm_tag_attr_scope_t, @@ -1912,6 +1930,9 @@ static gchar *find_calltip(const gchar *word, GeanyFiletype *ft) break; } } + + g_ptr_array_free(tags, TRUE); + if (str) { gchar *result = str->str; @@ -2034,20 +2055,21 @@ static gboolean autocomplete_tags(GeanyEditor *editor, const gchar *root, gsize rootlen) { TMTagAttrType attrs[] = { tm_tag_attr_name_t, 0 }; - const GPtrArray *tags; + GPtrArray *tags; GeanyDocument *doc; + gboolean found; g_return_val_if_fail(editor, FALSE); doc = editor->document; tags = tm_workspace_find(root, NULL, tm_tag_max_t, attrs, TRUE, doc->file_type->lang); - if (tags) - { + found = tags->len > 0; + if (found) show_tags_list(editor, tags, rootlen); - return tags->len > 0; - } - return FALSE; + g_ptr_array_free(tags, TRUE); + + return found; } diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 6b7655cd97..54a38e99cf 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -672,6 +672,20 @@ gboolean tm_workspace_create_global_tags(const char *pre_process, const char **i } +static gboolean langs_compatible(langType lang, langType other) +{ + if (lang == other || lang == -1 || other == -1) + return TRUE; + /* Accept CPP tags for C lang and vice versa */ + else if (lang == TM_PARSER_C && other == TM_PARSER_CPP) + return TRUE; + else if (lang == TM_PARSER_CPP && other == TM_PARSER_C) + return TRUE; + + return FALSE; +} + + static guint fill_find_tags_array(GPtrArray *dst, const GPtrArray *src, const char *name, const char *scope, TMTagType type, gboolean partial, langType lang) { @@ -685,17 +699,8 @@ static guint fill_find_tags_array(GPtrArray *dst, const GPtrArray *src, matches = tm_tags_find(src, name, partial, TRUE, &tagCount); for (tagIter = 0; tagIter < tagCount; ++tagIter) { - gint tag_lang = (*matches)->lang; - gint tag_lang_alt = tag_lang; - - /* Accept CPP tags for C lang and vice versa */ - if (tag_lang == TM_PARSER_C) - tag_lang_alt = TM_PARSER_CPP; - else if (tag_lang == TM_PARSER_CPP) - tag_lang_alt = TM_PARSER_C; - if ((type & (*matches)->type) && - (lang == -1 || tag_lang == lang || tag_lang_alt == lang) && + langs_compatible(lang, (*matches)->lang) && (!scope || g_strcmp0((*matches)->scope, scope) == 0)) g_ptr_array_add(dst, *matches); @@ -714,17 +719,12 @@ static guint fill_find_tags_array(GPtrArray *dst, const GPtrArray *src, @param partial Whether partial match is allowed. @param lang Specifies the language(see the table in parsers.h) of the tags to be found, -1 for all - @return Array of matching tags. Do not free() it since it is a static member. + @return Array of matching tags. */ -const GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type, +GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type, TMTagAttrType *attrs, gboolean partial, langType lang) { - static GPtrArray *tags = NULL; - - if (tags) - g_ptr_array_set_size(tags, 0); - else - tags = g_ptr_array_new(); + GPtrArray *tags = g_ptr_array_new(); fill_find_tags_array(tags, theWorkspace->tags_array, name, scope, type, partial, lang); fill_find_tags_array(tags, theWorkspace->global_tags, name, scope, type, partial, lang); @@ -738,8 +738,7 @@ const GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagTyp static int find_scope_members_tags (const GPtrArray * all, GPtrArray * tags, - const langType langJava, const char *name, - const char *filename, gboolean no_definitions) + const char *name, langType lang) { GPtrArray *local = g_ptr_array_new (); unsigned int i; @@ -748,13 +747,8 @@ find_scope_members_tags (const GPtrArray * all, GPtrArray * tags, for (i = 0; (i < all->len); ++i) { tag = TM_TAG (all->pdata[i]); - if (no_definitions && filename && tag->file && - 0 != strcmp (filename, - tag->file->short_name)) - { - continue; - } - if (tag && tag->scope && tag->scope[0] != '\0') + if (tag && tag->scope && tag->scope[0] != '\0' && + langs_compatible(tag->lang, lang)) { if (0 == strncmp (name, tag->scope, len)) { @@ -795,7 +789,7 @@ find_scope_members_tags (const GPtrArray * all, GPtrArray * tags, } } if (tag->file - && tag->file->lang == langJava) + && tag->file->lang == TM_PARSER_JAVA) { scope = strrchr (tag->scope, '.'); if (scope) @@ -853,142 +847,118 @@ find_scope_members_tags (const GPtrArray * all, GPtrArray * tags, /* Returns all matching members tags found in given struct/union/class name. - @param name Name of the struct/union/class. @param file_tags A GPtrArray of edited file TMTag pointers (for search speedup, can be NULL). + @param name Name of the struct/union/class. @return A GPtrArray of TMTag pointers to struct/union/class members */ -const GPtrArray * -tm_workspace_find_scope_members (const GPtrArray * file_tags, const char *name, - gboolean search_global, gboolean no_definitions) +GPtrArray * +tm_workspace_find_scope_members (const GPtrArray * file_tags, const char *name, langType lang) { - static GPtrArray *tags = NULL; - GPtrArray *local = NULL; - char *new_name = (char *) name; - char *filename = NULL; - int found = 0, del = 0; - static langType langJava = -1; - TMTag *tag = NULL; - - /* FIXME */ - /* langJava = getNamedLanguage ("Java"); */ + gboolean has_members = FALSE; + GPtrArray *tags; + gchar *type_name; + guint i; - g_return_val_if_fail ((theWorkspace && name && name[0] != '\0'), NULL); + g_return_val_if_fail(name && *name, NULL); - if (!tags) - tags = g_ptr_array_new (); + type_name = g_strdup(name); - while (1) + /* First check if type_name is a type that can possibly contain members. + * Try to resolve intermediate typedefs to get the real type name. Also + * add scope information to the name if applicable. The only result of this + * part is the updated type_name and boolean has_members. + * The loop below loops only when resolving typedefs - avoid possibly infinite + * loop when typedefs create a cycle by adding some limits. */ + for (i = 0; i < 5; i++) { - const GPtrArray *tags2; - guint got = 0; + guint j; + TMTag *tag = NULL; + GPtrArray *type_tags; TMTagType types = (tm_tag_class_t | tm_tag_namespace_t | tm_tag_struct_t | tm_tag_typedef_t | tm_tag_union_t | tm_tag_enum_t); + /* try to get the type from file_tags first */ + type_tags = g_ptr_array_new(); if (file_tags) - { - g_ptr_array_set_size (tags, 0); - got = fill_find_tags_array (tags, file_tags, - new_name, NULL, types, FALSE, -1); - } - if (got) - { - tags2 = tags; - } - else + fill_find_tags_array(type_tags, file_tags, type_name, NULL, types, FALSE, lang); + + /* file not specified or type definition not in the file */ + if (type_tags->len == 0) { TMTagAttrType attrs[] = { tm_tag_attr_name_t, tm_tag_attr_type_t, tm_tag_attr_none_t }; - tags2 = tm_workspace_find (new_name, NULL, types, attrs, FALSE, -1); + + g_ptr_array_free(type_tags, TRUE); + type_tags = tm_workspace_find(type_name, NULL, types, attrs, FALSE, lang); } - if ((tags2) && (tags2->len == 1) && (tag = TM_TAG (tags2->pdata[0]))) + if (type_tags) { - if (tag->type == tm_tag_typedef_t && tag->var_type - && tag->var_type[0] != '\0') + for (j = 0; j < type_tags->len; j++) { - char *tmp_name; - tmp_name = tag->var_type; - if (strcmp(tmp_name, new_name) == 0) { - new_name = NULL; - } - else { - new_name = tmp_name; - } - continue; + tag = TM_TAG(type_tags->pdata[j]); + /* prefer non-typedef tags because we can be sure they contain members */ + if (tag->type != tm_tag_typedef_t) + break; } - filename = (tag->file ? - tag->file->short_name : NULL); - if (tag->scope && tag->scope[0] != '\0') + } + + g_ptr_array_free(type_tags, TRUE); + + if (!tag) /* not a type that can contain members */ + break; + + /* intermediate typedef - resolve to the real type */ + if (tag->type == tm_tag_typedef_t && tag->var_type && tag->var_type[0] != '\0') + { + g_free(type_name); + type_name = g_strdup(tag->var_type); + continue; + } + else /* real type with members */ + { + has_members = TRUE; + if (tag->scope && *(tag->scope)) { - del = 1; - if (tag->file && - tag->file->lang == langJava) - { - new_name = g_strdup_printf ("%s.%s", - tag->scope, - new_name); - } + gchar *tmp_name = type_name; + + if (tag->file && tag->file->lang == TM_PARSER_JAVA) + type_name = g_strdup_printf("%s.%s", tag->scope, type_name); else - { - new_name = g_strdup_printf ("%s::%s", - tag->scope, - new_name); - } + type_name = g_strdup_printf("%s::%s", tag->scope, type_name); + g_free(tmp_name); } break; } - else - { - return NULL; - } } - g_ptr_array_set_size (tags, 0); + tags = g_ptr_array_new(); - if (no_definitions && tag && tag->file) - { - local = tm_tags_extract (tag->file->tags_array, - (tm_tag_function_t | tm_tag_prototype_t | - tm_tag_member_t | tm_tag_field_t | - tm_tag_method_t | tm_tag_enumerator_t)); - } - else - { - local = tm_tags_extract (theWorkspace->tags_array, - (tm_tag_function_t | tm_tag_prototype_t | - tm_tag_member_t | tm_tag_field_t | - tm_tag_method_t | tm_tag_enumerator_t)); - } - if (local) + if (has_members) { - found = find_scope_members_tags (local, tags, langJava, new_name, - filename, no_definitions); - g_ptr_array_free (local, TRUE); - } - if (!found && search_global) - { - GPtrArray *global = tm_tags_extract (theWorkspace->global_tags, - (tm_tag_member_t | - tm_tag_prototype_t | - tm_tag_field_t | - tm_tag_method_t | - tm_tag_function_t | - tm_tag_enumerator_t - |tm_tag_struct_t | tm_tag_typedef_t | - tm_tag_union_t | tm_tag_enum_t)); - if (global) + GPtrArray *extracted; + TMTagType member_types = (tm_tag_function_t | tm_tag_prototype_t | + tm_tag_member_t | tm_tag_field_t | + tm_tag_method_t | tm_tag_enumerator_t); + + /* Now get the tags from tags_array and global_tags corresponding to type_name */ + extracted = tm_tags_extract(theWorkspace->tags_array, member_types); + if (extracted) { - find_scope_members_tags (global, tags, langJava, new_name, - filename, no_definitions); - g_ptr_array_free (global, TRUE); + find_scope_members_tags(extracted, tags, type_name, lang); + g_ptr_array_free(extracted, TRUE); + } + extracted = tm_tags_extract(theWorkspace->global_tags, member_types); + if (extracted) + { + find_scope_members_tags(extracted, tags, type_name, lang); + g_ptr_array_free(extracted, TRUE); } } - if (del) - { - g_free (new_name); - } + + g_free(type_name); return tags; } diff --git a/tagmanager/src/tm_workspace.h b/tagmanager/src/tm_workspace.h index 12f8cbf30f..ccb217813f 100644 --- a/tagmanager/src/tm_workspace.h +++ b/tagmanager/src/tm_workspace.h @@ -54,13 +54,11 @@ gboolean tm_workspace_load_global_tags(const char *tags_file, gint mode); gboolean tm_workspace_create_global_tags(const char *pre_process, const char **includes, int includes_count, const char *tags_file, int lang); -const GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type, +GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type, TMTagAttrType *attrs, gboolean partial, langType lang); -const GPtrArray *tm_workspace_find_scope_members(const GPtrArray *file_tags, - const char *scope_name, - gboolean find_global, - gboolean no_definitions); +GPtrArray *tm_workspace_find_scope_members(const GPtrArray *file_tags, + const char *scope_name, langType lang); void tm_workspace_add_source_file_noupdate(TMSourceFile *source_file); From 99e222ea370933ddb28f4a103b28de48b842ba2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Fri, 8 May 2015 06:29:26 +0200 Subject: [PATCH 04/32] Eliminate calls of slow tm_tags_extract() on big arrays Do the same with struct/class/union... member tags as we do with typenames - extract them from the edited file and merge them with the array containing all of them so while editing, there should be no slowdowns because one file usually doesn't contain so many tags. This eliminates about 2s freeze when typing "." on a linux kernel project with 2300000 tags. Extract typename and member tags also for global tags in case someone creates a giant tags file - this needs to be done just once when loading the tag files. All the remaining tm_tags_extract() in Geany are called on file tag array only so there shouldn't be any performance problems. --- src/symbols.c | 4 +-- tagmanager/src/tm_workspace.c | 56 ++++++++++++++++++++++------------- tagmanager/src/tm_workspace.h | 3 ++ 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/symbols.c b/src/symbols.c index e85a7f6900..c21c3b25d4 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -281,7 +281,7 @@ GString *symbols_find_typenames_as_string(gint lang, gboolean global) gint tag_lang; if (global) - typedefs = tm_tags_extract(app->tm_workspace->global_tags, TM_GLOBAL_TYPE_MASK); + typedefs = app->tm_workspace->global_typename_array; else typedefs = app->tm_workspace->typename_array; @@ -305,8 +305,6 @@ GString *symbols_find_typenames_as_string(gint lang, gboolean global) } } } - if (typedefs && global) - g_ptr_array_free(typedefs, TRUE); return s; } diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 54a38e99cf..4506212a6c 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -56,6 +56,10 @@ static TMTagAttrType global_tags_sort_attrs[] = tm_tag_attr_type_t, tm_tag_attr_scope_t, tm_tag_attr_arglist_t, 0 }; +static TMTagType TM_MEMBER_TYPE_MASK = + tm_tag_function_t | tm_tag_prototype_t | + tm_tag_member_t | tm_tag_field_t | + tm_tag_method_t | tm_tag_enumerator_t; static TMWorkspace *theWorkspace = NULL; @@ -68,6 +72,9 @@ static gboolean tm_create_workspace(void) theWorkspace->global_tags = g_ptr_array_new(); theWorkspace->source_files = g_ptr_array_new(); theWorkspace->typename_array = g_ptr_array_new(); + theWorkspace->global_typename_array = g_ptr_array_new(); + theWorkspace->member_array = g_ptr_array_new(); + theWorkspace->global_member_array = g_ptr_array_new(); return TRUE; } @@ -89,6 +96,9 @@ void tm_workspace_free(void) tm_tags_array_free(theWorkspace->global_tags, TRUE); g_ptr_array_free(theWorkspace->tags_array, TRUE); g_ptr_array_free(theWorkspace->typename_array, TRUE); + g_ptr_array_free(theWorkspace->global_typename_array, TRUE); + g_ptr_array_free(theWorkspace->member_array, TRUE); + g_ptr_array_free(theWorkspace->global_member_array, TRUE); g_free(theWorkspace); theWorkspace = NULL; } @@ -117,6 +127,16 @@ static void tm_workspace_merge_tags(GPtrArray **big_array, GPtrArray *small_arra } +static void merge_extracted_tags(GPtrArray **dest, GPtrArray *src, TMTagType tag_types) +{ + GPtrArray *arr; + + arr = tm_tags_extract(src, tag_types); + tm_workspace_merge_tags(dest, arr); + g_ptr_array_free(arr, TRUE); +} + + static void update_source_file(TMSourceFile *source_file, guchar* text_buf, gsize buf_size, gboolean use_buffer, gboolean update_workspace) { @@ -130,21 +150,19 @@ static void update_source_file(TMSourceFile *source_file, guchar* text_buf, * workspace while they exist and can be scanned */ tm_tags_remove_file_tags(source_file, theWorkspace->tags_array); tm_tags_remove_file_tags(source_file, theWorkspace->typename_array); + tm_tags_remove_file_tags(source_file, theWorkspace->member_array); } tm_source_file_parse(source_file, text_buf, buf_size, use_buffer); tm_tags_sort(source_file->tags_array, file_tags_sort_attrs, FALSE, TRUE); if (update_workspace) { - GPtrArray *sf_typedefs; - #ifdef TM_DEBUG g_message("Updating workspace from source file"); #endif tm_workspace_merge_tags(&theWorkspace->tags_array, source_file->tags_array); - - sf_typedefs = tm_tags_extract(source_file->tags_array, TM_GLOBAL_TYPE_MASK); - tm_workspace_merge_tags(&theWorkspace->typename_array, sf_typedefs); - g_ptr_array_free(sf_typedefs, TRUE); + + merge_extracted_tags(&(theWorkspace->typename_array), source_file->tags_array, TM_GLOBAL_TYPE_MASK); + merge_extracted_tags(&(theWorkspace->member_array), source_file->tags_array, TM_MEMBER_TYPE_MASK); } #ifdef TM_DEBUG else @@ -214,6 +232,7 @@ void tm_workspace_remove_source_file(TMSourceFile *source_file) { tm_tags_remove_file_tags(source_file, theWorkspace->tags_array); tm_tags_remove_file_tags(source_file, theWorkspace->typename_array); + tm_tags_remove_file_tags(source_file, theWorkspace->member_array); g_ptr_array_remove_index_fast(theWorkspace->source_files, i); return; } @@ -262,6 +281,7 @@ static void tm_workspace_update(void) g_ptr_array_free(theWorkspace->typename_array, TRUE); theWorkspace->typename_array = tm_tags_extract(theWorkspace->tags_array, TM_GLOBAL_TYPE_MASK); + theWorkspace->member_array = tm_tags_extract(theWorkspace->tags_array, TM_MEMBER_TYPE_MASK); } @@ -386,6 +406,11 @@ gboolean tm_workspace_load_global_tags(const char *tags_file, gint mode) g_ptr_array_free(file_tags, TRUE); theWorkspace->global_tags = new_tags; + g_ptr_array_free(theWorkspace->global_typename_array, TRUE); + theWorkspace->global_typename_array = tm_tags_extract(new_tags, TM_GLOBAL_TYPE_MASK); + g_ptr_array_free(theWorkspace->global_member_array, TRUE); + theWorkspace->global_member_array = tm_tags_extract(new_tags, TM_MEMBER_TYPE_MASK); + return TRUE; } @@ -939,23 +964,12 @@ tm_workspace_find_scope_members (const GPtrArray * file_tags, const char *name, if (has_members) { GPtrArray *extracted; - TMTagType member_types = (tm_tag_function_t | tm_tag_prototype_t | - tm_tag_member_t | tm_tag_field_t | - tm_tag_method_t | tm_tag_enumerator_t); /* Now get the tags from tags_array and global_tags corresponding to type_name */ - extracted = tm_tags_extract(theWorkspace->tags_array, member_types); - if (extracted) - { - find_scope_members_tags(extracted, tags, type_name, lang); - g_ptr_array_free(extracted, TRUE); - } - extracted = tm_tags_extract(theWorkspace->global_tags, member_types); - if (extracted) - { - find_scope_members_tags(extracted, tags, type_name, lang); - g_ptr_array_free(extracted, TRUE); - } + if (theWorkspace->member_array) + find_scope_members_tags(theWorkspace->member_array, tags, type_name, lang); + if (theWorkspace->global_member_array) + find_scope_members_tags(theWorkspace->global_member_array, tags, type_name, lang); } g_free(type_name); diff --git a/tagmanager/src/tm_workspace.h b/tagmanager/src/tm_workspace.h index ccb217813f..b5d5e2a1dc 100644 --- a/tagmanager/src/tm_workspace.h +++ b/tagmanager/src/tm_workspace.h @@ -33,6 +33,9 @@ typedef struct GPtrArray *tags_array; /**< Sorted tags from all source files (just pointers to source file tags, the tag objects are owned by the source files) */ GPtrArray *typename_array; /* Typename tags for syntax highlighting (pointers owned by source files) */ + GPtrArray *global_typename_array; /* Like above for global tags */ + GPtrArray *member_array; /* Typename tags for syntax highlighting (pointers owned by source files) */ + GPtrArray *global_member_array; /* Like above for global tags */ } TMWorkspace; From 2328f84e37328a8d2e8a5fa58dcf8a836885fa41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 9 May 2015 13:19:07 +0200 Subject: [PATCH 05/32] Disallow the possibility to use tm_tags_find() on unsorted array This function won't work correctly on unsorted array because the second part of the function (after the tags search) expects the array is sorted by name. The only user of this is tm_source_file_set_tag_arglist() in which we can go through the tags manually by ourselves (it needs only a single value so the original behavior of tm_tags_find() wasn't a problem). Eliminate the tags_search() function as it isn't needed any more. Just cleanup, not functional change. --- tagmanager/src/tm_source_file.c | 18 +++++++++------- tagmanager/src/tm_tag.c | 37 ++++++--------------------------- tagmanager/src/tm_tag.h | 2 +- tagmanager/src/tm_workspace.c | 2 +- 4 files changed, 18 insertions(+), 41 deletions(-) diff --git a/tagmanager/src/tm_source_file.c b/tagmanager/src/tm_source_file.c index a68616266d..d5faaf5be7 100644 --- a/tagmanager/src/tm_source_file.c +++ b/tagmanager/src/tm_source_file.c @@ -118,8 +118,7 @@ static int tm_source_file_tags(const tagEntryInfo *tag) /* Set the argument list of tag identified by its name */ static void tm_source_file_set_tag_arglist(const char *tag_name, const char *arglist) { - guint count; - TMTag **tags, *tag; + guint i; if (NULL == arglist || NULL == tag_name || @@ -128,13 +127,16 @@ static void tm_source_file_set_tag_arglist(const char *tag_name, const char *arg return; } - tags = tm_tags_find(current_source_file->tags_array, tag_name, FALSE, FALSE, - &count); - if (tags != NULL && count == 1) + /* going in reverse order because the tag was added recently */ + for (i = current_source_file->tags_array->len; i > 0; i--) { - tag = tags[0]; - g_free(tag->arglist); - tag->arglist = g_strdup(arglist); + TMTag *tag = (TMTag *) current_source_file->tags_array->pdata[i - 1]; + if (g_strcmp0(tag->name, tag_name) == 0) + { + g_free(tag->arglist); + tag->arglist = g_strdup(arglist); + break; + } } } diff --git a/tagmanager/src/tm_tag.c b/tagmanager/src/tm_tag.c index 9afc845f3d..778d052a6e 100644 --- a/tagmanager/src/tm_tag.c +++ b/tagmanager/src/tm_tag.c @@ -862,7 +862,7 @@ void tm_tags_remove_file_tags(TMSourceFile *source_file, GPtrArray *tags_array) TMTag **found; TMTag *tag = source_file->tags_array->pdata[i]; - found = tm_tags_find(tags_array, tag->name, FALSE, TRUE, &tag_count); + found = tm_tags_find(tags_array, tag->name, FALSE, &tag_count); for (j = 0; j < tag_count; j++) { @@ -1083,42 +1083,16 @@ static gpointer binary_search(gpointer key, gpointer base, size_t nmemb, return NULL; } -static TMTag **tags_search(const GPtrArray *tags_array, TMTag *tag, - gboolean tags_array_sorted, TMSortOptions *sort_options) -{ - if (tags_array_sorted) - { /* fast binary search on sorted tags array */ - return (TMTag **) binary_search(&tag, tags_array->pdata, tags_array->len, - tm_tag_compare, sort_options); - } - else - { /* the slow way: linear search (to make it a bit faster, search reverse assuming - * that the tag to search was added recently) */ - guint i; - TMTag **t; - for (i = tags_array->len; i > 0; i--) - { - t = (TMTag **) &tags_array->pdata[i - 1]; - if (0 == tm_tag_compare(&tag, t, sort_options)) - return t; - } - } - return NULL; -} - /* Returns a pointer to the position of the first matching tag in a (sorted) tags array. - The passed array of tags should be already sorted by name for optimal performance. If - \c tags_array_sorted is set to FALSE, it may be unsorted but the lookup will be slower. - @param tags_array Tag array (may be sorted on name) + The passed array of tags must be already sorted by name (searched with binary search). + @param tags_array Tag array (sorted on name) @param name Name of the tag to locate. @param partial If TRUE, matches the first part of the name instead of doing exact match. - @param tags_array_sorted If TRUE, the passed \c tags_array is sorted by name so it can be - searched with binary search. Otherwise it is searched linear which is obviously slower. @param tagCount Return location of the matched tags. */ TMTag **tm_tags_find(const GPtrArray *tags_array, const char *name, - gboolean partial, gboolean tags_array_sorted, guint * tagCount) + gboolean partial, guint * tagCount) { static TMTag *tag = NULL; TMTag **result; @@ -1135,7 +1109,8 @@ TMTag **tm_tags_find(const GPtrArray *tags_array, const char *name, sort_options.sort_attrs = NULL; sort_options.partial = partial; - result = tags_search(tags_array, tag, tags_array_sorted, &sort_options); + result = (TMTag **)binary_search(&tag, tags_array->pdata, tags_array->len, + tm_tag_compare, &sort_options); /* There can be matches on both sides of result */ if (result) { diff --git a/tagmanager/src/tm_tag.h b/tagmanager/src/tm_tag.h index f1c9bdf4d5..15f7388e3c 100644 --- a/tagmanager/src/tm_tag.h +++ b/tagmanager/src/tm_tag.h @@ -179,7 +179,7 @@ gboolean tm_tags_prune(GPtrArray *tags_array); gboolean tm_tags_dedup(GPtrArray *tags_array, TMTagAttrType *sort_attributes, gboolean unref_duplicates); TMTag **tm_tags_find(const GPtrArray *tags_array, const char *name, - gboolean partial, gboolean tags_array_sorted, guint * tagCount); + gboolean partial, guint * tagCount); void tm_tags_array_free(GPtrArray *tags_array, gboolean free_all); diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 4506212a6c..4f012717af 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -721,7 +721,7 @@ static guint fill_find_tags_array(GPtrArray *dst, const GPtrArray *src, if (!src || !dst || !name || !*name) return 0; - matches = tm_tags_find(src, name, partial, TRUE, &tagCount); + matches = tm_tags_find(src, name, partial, &tagCount); for (tagIter = 0; tagIter < tagCount; ++tagIter) { if ((type & (*matches)->type) && From b6b93036f682bbc73399b3372168849d0b31fc4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 9 May 2015 16:10:06 +0200 Subject: [PATCH 06/32] Get scope members only from corresponding tag arrays At the moment it can happen that even though a member is found in the currently edited file, the search at the end of the function finds the type inside another file. This typically happens for anonymous structs so e.g. for anon_struct_0{...} from the current file we get members from anon_struct_0{...} from all open documents plus gloabl tags. Search in an increasing "circle" - start with current file only (trying all possible types of the variable), continue with workspace array and finally, if not found, search in the global tags. --- src/editor.c | 31 +++------- tagmanager/src/tm_workspace.c | 105 +++++++++++++++++++++++----------- tagmanager/src/tm_workspace.h | 4 +- 3 files changed, 81 insertions(+), 59 deletions(-) diff --git a/src/editor.c b/src/editor.c index e7567caa17..37b189f4ff 100644 --- a/src/editor.c +++ b/src/editor.c @@ -703,10 +703,8 @@ static void autocomplete_scope(GeanyEditor *editor) gint pos = sci_get_current_position(editor->sci); gchar typed = sci_get_char_at(sci, pos - 1); gchar *name; - GPtrArray *tags; - const TMTag *tag; GeanyFiletype *ft = editor->document->file_type; - guint i; + GPtrArray *tags; if (ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP) { @@ -727,29 +725,14 @@ static void autocomplete_scope(GeanyEditor *editor) if (!name) return; - tags = tm_workspace_find(name, NULL, tm_tag_max_t, NULL, FALSE, ft->lang); - g_free(name); - - foreach_ptr_array(tag, i, tags) + tags = tm_workspace_find_scope_members(editor->document->tm_file, name); + if (tags) { - if (tag->var_type) - { - TMSourceFile *sf = editor->document->tm_file; - GPtrArray *member_tags; - gboolean found; - - member_tags = tm_workspace_find_scope_members(sf ? sf->tags_array : NULL, - tag->var_type, - sf ? sf->lang : -1); - found = member_tags && member_tags->len > 0; - if (found) - show_tags_list(editor, member_tags, 0); - g_ptr_array_free(member_tags, TRUE); - if (found) - break; - } + show_tags_list(editor, tags, 0); + g_ptr_array_free(tags, TRUE); } - g_ptr_array_free(tags, TRUE); + + g_free(name); } diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 4f012717af..fbcce7927a 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -871,15 +871,12 @@ find_scope_members_tags (const GPtrArray * all, GPtrArray * tags, } -/* Returns all matching members tags found in given struct/union/class name. - @param file_tags A GPtrArray of edited file TMTag pointers (for search speedup, can be NULL). - @param name Name of the struct/union/class. - @return A GPtrArray of TMTag pointers to struct/union/class members */ -GPtrArray * -tm_workspace_find_scope_members (const GPtrArray * file_tags, const char *name, langType lang) +static GPtrArray * +find_scope_members (const GPtrArray *tags_array, GPtrArray *member_array, + const char *name, langType lang) { gboolean has_members = FALSE; - GPtrArray *tags; + GPtrArray *tags = NULL; gchar *type_name; guint i; @@ -895,32 +892,17 @@ tm_workspace_find_scope_members (const GPtrArray * file_tags, const char *name, * loop when typedefs create a cycle by adding some limits. */ for (i = 0; i < 5; i++) { - guint j; TMTag *tag = NULL; GPtrArray *type_tags; TMTagType types = (tm_tag_class_t | tm_tag_namespace_t | tm_tag_struct_t | tm_tag_typedef_t | tm_tag_union_t | tm_tag_enum_t); - /* try to get the type from file_tags first */ type_tags = g_ptr_array_new(); - if (file_tags) - fill_find_tags_array(type_tags, file_tags, type_name, NULL, types, FALSE, lang); - - /* file not specified or type definition not in the file */ - if (type_tags->len == 0) - { - TMTagAttrType attrs[] = { - tm_tag_attr_name_t, tm_tag_attr_type_t, - tm_tag_attr_none_t - }; - - g_ptr_array_free(type_tags, TRUE); - type_tags = tm_workspace_find(type_name, NULL, types, attrs, FALSE, lang); - } - + fill_find_tags_array(type_tags, tags_array, type_name, NULL, types, FALSE, lang); if (type_tags) { + guint j; for (j = 0; j < type_tags->len; j++) { tag = TM_TAG(type_tags->pdata[j]); @@ -959,17 +941,15 @@ tm_workspace_find_scope_members (const GPtrArray * file_tags, const char *name, } } - tags = g_ptr_array_new(); - if (has_members) { - GPtrArray *extracted; - - /* Now get the tags from tags_array and global_tags corresponding to type_name */ - if (theWorkspace->member_array) - find_scope_members_tags(theWorkspace->member_array, tags, type_name, lang); - if (theWorkspace->global_member_array) - find_scope_members_tags(theWorkspace->global_member_array, tags, type_name, lang); + tags = g_ptr_array_new(); + find_scope_members_tags(member_array, tags, type_name, lang); + if (tags->len == 0) + { + g_ptr_array_free(tags, TRUE); + tags = NULL; + } } g_free(type_name); @@ -978,6 +958,65 @@ tm_workspace_find_scope_members (const GPtrArray * file_tags, const char *name, } +static GPtrArray * +find_scope_members_all(const GPtrArray *var_array, const GPtrArray *searched_array, + GPtrArray *member_array, langType lang) +{ + GPtrArray *member_tags = NULL; + guint i; + + /* there may be several variables with the same name - try each of them until + * we find something */ + for (i = 0; i < var_array->len; i++) + { + TMTag *tag = TM_TAG(var_array->pdata[i]); + + if (tag->var_type) + { + member_tags = find_scope_members(searched_array, member_array, + tag->var_type, lang); + if (member_tags) + break; + } + } + + return member_tags; +} + + +/* Returns all matching members tags found in given struct/union/class name. + @param source_file TMSourceFile of the edited source file or NULL if not available + @param name Name of the variable whose members are searched + @return A GPtrArray of TMTag pointers to struct/union/class members or NULL when not found */ +GPtrArray * +tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name) +{ + langType lang = source_file ? source_file->lang : -1; + GPtrArray *tags, *member_tags = NULL; + + tags = tm_workspace_find(name, NULL, tm_tag_max_t, NULL, FALSE, lang); + + if (source_file) + { + GPtrArray *file_members = tm_tags_extract(source_file->tags_array, TM_MEMBER_TYPE_MASK); + + member_tags = find_scope_members_all(tags, source_file->tags_array, + file_members, lang); + g_ptr_array_free(file_members, TRUE); + } + if (!member_tags) + member_tags = find_scope_members_all(tags, theWorkspace->tags_array, + theWorkspace->member_array, lang); + if (!member_tags) + member_tags = find_scope_members_all(tags, theWorkspace->global_tags, + theWorkspace->global_member_array, lang); + + g_ptr_array_free(tags, TRUE); + + return member_tags; +} + + #ifdef TM_DEBUG /* Dumps the workspace tree - useful for debugging */ diff --git a/tagmanager/src/tm_workspace.h b/tagmanager/src/tm_workspace.h index b5d5e2a1dc..240dbbc50c 100644 --- a/tagmanager/src/tm_workspace.h +++ b/tagmanager/src/tm_workspace.h @@ -60,8 +60,8 @@ gboolean tm_workspace_create_global_tags(const char *pre_process, const char **i GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type, TMTagAttrType *attrs, gboolean partial, langType lang); -GPtrArray *tm_workspace_find_scope_members(const GPtrArray *file_tags, - const char *scope_name, langType lang); +GPtrArray *tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name); + void tm_workspace_add_source_file_noupdate(TMSourceFile *source_file); From 140a7b6617e89dfcb31d876364d62b3902af0d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 9 May 2015 16:46:59 +0200 Subject: [PATCH 07/32] When extracting members, get them from single file only The previous commit fixed the situation when e.g. anon_struct_0 was in the current file by checking the current file first. In the case the struct type definition isn't found in the current file, at the moment we get all members from all anon_struct_0 which can be a really long list. This list isn't very helpful to users because all the members from all the structs are mixed. Moreover, not all possible members are in the list because there are e.g. just members from anon_struct_0 but not from anon_struct_1 etc. which from the point of view of this function is a different type. Instead, restrict the returned members to just a single file (anonymous structs have unique name per file so it means there will be just one from the file). Of course the picked file can be wrong and the returned members might be from a different struct the user wanted but at least the list will make more sense to users. --- tagmanager/src/tm_workspace.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index fbcce7927a..321f575d56 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -761,6 +761,25 @@ GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type } +static void add_member(GPtrArray * tags, TMTag *tag) +{ + gboolean add = TRUE; + + if (tags->len > 0) + { + TMTag *last_tag = TM_TAG(tags->pdata[tags->len-1]); + /* add only if it's from the same file - this prevents mixing tags from + * structs with identical name (typically the anonymous ones) so we + * only get members from a single one. For workspace tags it adds + * all members because there's no file associated. */ + add = last_tag->file == tag->file; + } + if (add) + /* the first file name "wins" */ + g_ptr_array_add (tags, tag); +} + + static int find_scope_members_tags (const GPtrArray * all, GPtrArray * tags, const char *name, langType lang) @@ -795,7 +814,7 @@ find_scope_members_tags (const GPtrArray * all, GPtrArray * tags, scope = tag->scope; if (scope && 0 == strcmp (name, scope)) { - g_ptr_array_add (tags, tag); + add_member (tags, tag); continue; } s_backup = NULL; @@ -862,7 +881,7 @@ find_scope_members_tags (const GPtrArray * all, GPtrArray * tags, } if (j == local->len) { - g_ptr_array_add (tags, tag); + add_member (tags, tag); } } } From 8ff8cbc3a33ba920a6113a8a9cb62ea007789641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Fri, 22 May 2015 22:32:19 +0200 Subject: [PATCH 08/32] Sane implementation of find_scope_members_tags() Disclaimer: I have absolutely no idea how the original function works. After gazing into the code for one hour, I just gave up and wrote my own version of it based on what I think the function should do but maybe I'm just missing something what justifies the original implementation's insanity. --- tagmanager/src/tm_workspace.c | 110 ++++------------------------------ 1 file changed, 10 insertions(+), 100 deletions(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 321f575d56..d24d597857 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -780,113 +780,23 @@ static void add_member(GPtrArray * tags, TMTag *tag) } -static int +static void find_scope_members_tags (const GPtrArray * all, GPtrArray * tags, - const char *name, langType lang) + const char *scope, langType lang) { - GPtrArray *local = g_ptr_array_new (); - unsigned int i; - TMTag *tag; - size_t len = strlen (name); - for (i = 0; (i < all->len); ++i) + guint i; + + for (i = 0; i < all->len; ++i) { - tag = TM_TAG (all->pdata[i]); + TMTag *tag = TM_TAG (all->pdata[i]); + if (tag && tag->scope && tag->scope[0] != '\0' && - langs_compatible(tag->lang, lang)) - { - if (0 == strncmp (name, tag->scope, len)) - { - g_ptr_array_add (local, tag); - } - } - } - if (local->len > 0) - { - unsigned int j; - TMTag *tag2; - char backup = 0; - char *s_backup = NULL; - char *var_type = NULL; - char *scope; - for (i = 0; (i < local->len); ++i) + langs_compatible(tag->lang, lang) && + strcmp(scope, tag->scope) == 0) { - tag = TM_TAG (local->pdata[i]); - scope = tag->scope; - if (scope && 0 == strcmp (name, scope)) - { - add_member (tags, tag); - continue; - } - s_backup = NULL; - j = 0; /* someone could write better code :P */ - while (scope) - { - if (s_backup) - { - backup = s_backup[0]; - s_backup[0] = '\0'; - if (0 == strcmp (name, tag->scope)) - { - j = local->len; - s_backup[0] = backup; - break; - } - } - if (tag->file - && tag->file->lang == TM_PARSER_JAVA) - { - scope = strrchr (tag->scope, '.'); - if (scope) - var_type = scope + 1; - } - else - { - scope = strrchr (tag->scope, ':'); - if (scope) - { - var_type = scope + 1; - scope--; - } - } - if (s_backup) - { - s_backup[0] = backup; - } - if (scope) - { - if (s_backup) - { - backup = s_backup[0]; - s_backup[0] = '\0'; - } - for (j = 0; (j < local->len); ++j) - { - if (i == j) - continue; - tag2 = TM_TAG (local->pdata[j]); - if (tag2->var_type && - 0 == strcmp (var_type, tag2->var_type)) - { - break; - } - } - if (s_backup) - s_backup[0] = backup; - } - if (j < local->len) - { - break; - } - s_backup = scope; - } - if (j == local->len) - { - add_member (tags, tag); - } + add_member (tags, tag); } } - g_ptr_array_free (local, TRUE); - return (int) tags->len; } From 809a9a7ea5e65a1500349b38afd415095d00d48e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 9 May 2015 20:22:40 +0200 Subject: [PATCH 09/32] Merge add_member() and find_scope_members_tags() By comparing the file pointer in the loop we can speed it up a bit because we can avoid the strcmp() (this function is the slowest part of the scope completion based on profiling). Also move the pointer array creation to this function and return it which is a bit cleaner. --- tagmanager/src/tm_workspace.c | 55 ++++++++++++++--------------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index d24d597857..e4c830da23 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -761,42 +761,39 @@ GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type } -static void add_member(GPtrArray * tags, TMTag *tag) -{ - gboolean add = TRUE; - - if (tags->len > 0) - { - TMTag *last_tag = TM_TAG(tags->pdata[tags->len-1]); - /* add only if it's from the same file - this prevents mixing tags from - * structs with identical name (typically the anonymous ones) so we - * only get members from a single one. For workspace tags it adds - * all members because there's no file associated. */ - add = last_tag->file == tag->file; - } - if (add) - /* the first file name "wins" */ - g_ptr_array_add (tags, tag); -} - - -static void -find_scope_members_tags (const GPtrArray * all, GPtrArray * tags, - const char *scope, langType lang) +static GPtrArray * +find_scope_members_tags (const GPtrArray * all, const char *scope, langType lang) { + GPtrArray *tags = g_ptr_array_new(); + TMSourceFile *last_file = NULL; guint i; for (i = 0; i < all->len; ++i) { TMTag *tag = TM_TAG (all->pdata[i]); - if (tag && tag->scope && tag->scope[0] != '\0' && + if (tag && (tag->file == last_file || last_file == NULL) && + tag->scope && tag->scope[0] != '\0' && langs_compatible(tag->lang, lang) && strcmp(scope, tag->scope) == 0) { - add_member (tags, tag); + g_ptr_array_add (tags, tag); + /* once set, always the same thanks to the if above + * add only if it's from the same file - this prevents mixing tags from + * structs with identical name (typically the anonymous ones) so we + * only get members from a single one. For workspace tags it adds + * all members because there's no file associated. */ + last_file = tag->file; } } + + if (tags->len == 0) + { + g_ptr_array_free(tags, TRUE); + return NULL; + } + + return tags; } @@ -871,15 +868,7 @@ find_scope_members (const GPtrArray *tags_array, GPtrArray *member_array, } if (has_members) - { - tags = g_ptr_array_new(); - find_scope_members_tags(member_array, tags, type_name, lang); - if (tags->len == 0) - { - g_ptr_array_free(tags, TRUE); - tags = NULL; - } - } + tags = find_scope_members_tags(member_array, type_name, lang); g_free(type_name); From e13aac0deab9533ca5f40c170895dcee0ca807c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 9 May 2015 20:56:15 +0200 Subject: [PATCH 10/32] Remove unused tm_workspace_find_namespace_members() The implementation of this function is almost the same like the original m_workspace_find_scoped_members() and there's nothing interesting here we wouldn't be able to recreate trivially. --- tagmanager/src/tm_workspace.c | 203 ---------------------------------- 1 file changed, 203 deletions(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index e4c830da23..6de172b12d 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -956,209 +956,6 @@ void tm_workspace_dump(void) #if 0 -static int -find_namespace_members_tags (const GPtrArray * all, GPtrArray * tags, - const langType langJava, const char *name, - const char *filename) -{ - GPtrArray *local = g_ptr_array_new (); - unsigned int i; - TMTag *tag; - size_t len = strlen (name); - - g_return_val_if_fail (all != NULL, 0); - - for (i = 0; (i < all->len); ++i) - { - tag = TM_TAG (all->pdata[i]); - if (filename && tag->file && - 0 != strcmp (filename, - tag->file->short_name)) - { - continue; - } - - if (tag && tag->scope && tag->scope[0] != '\0') - { - if (0 == strncmp (name, tag->scope, len)) - { - g_ptr_array_add (local, tag); - } - } - } - - if (local->len > 0) - { - char *scope; - for (i = 0; (i < local->len); ++i) - { - tag = TM_TAG (local->pdata[i]); - scope = tag->scope; - - /* if we wanna complete something like - * namespace1:: - * we'll just return the tags that have "namespace1" - * as their scope. So we won't return classes/members/namespaces - * under, for example, namespace2, where namespace1::namespace2 - */ - if (scope && 0 == strcmp (name, scope)) - { - g_ptr_array_add (tags, tag); - } - } - } - - g_ptr_array_free (local, TRUE); - return (int) tags->len; -} - -static const GPtrArray * -tm_workspace_find_namespace_members (const GPtrArray * file_tags, const char *name, - gboolean search_global) -{ - static GPtrArray *tags = NULL; - GPtrArray *local = NULL; - char *new_name = (char *) name; - char *filename = NULL; - int found = 0, del = 0; - static langType langJava = -1; - TMTag *tag = NULL; - - g_return_val_if_fail (name && name[0] != '\0', NULL); - - if (!tags) - tags = g_ptr_array_new (); - - while (1) - { - const GPtrArray *tags2; - guint got = 0; - TMTagType types = (tm_tag_class_t - tm_tag_struct_t | tm_tag_typedef_t | - tm_tag_union_t | tm_tag_enum_t); - - if (file_tags) - { - g_ptr_array_set_size (tags, 0); - got = fill_find_tags_array (tags, file_tags, - new_name, NULL, types, FALSE, -1, FALSE); - } - - - if (got) - { - tags2 = tags; - } - else - { - TMTagAttrType attrs[] = { - tm_tag_attr_name_t, tm_tag_attr_type_t, - tm_tag_attr_none_t - }; - tags2 = tm_workspace_find (new_name, types, attrs, FALSE, -1); - } - - if ((tags2) && (tags2->len == 1) && (tag = TM_TAG (tags2->pdata[0]))) - { - if (tag->type == tm_tag_typedef_t && tag->var_type - && tag->var_type[0] != '\0') - { - new_name = tag->var_type; - continue; - } - filename = (tag->file ? - tag->file->short_name : NULL); - if (tag->scope && tag->scope[0] != '\0') - { - del = 1; - if (tag->file && - tag->file->lang == langJava) - { - new_name = g_strdup_printf ("%s.%s", - tag->scope, - new_name); - } - else - { - new_name = g_strdup_printf ("%s::%s", - tag->scope, - new_name); - } - } - break; - } - else - { - return NULL; - } - } - - g_ptr_array_set_size (tags, 0); - - if (tag && tag->file) - { - local = tm_tags_extract (tag->file->tags_array, - (tm_tag_function_t | - tm_tag_field_t | tm_tag_enumerator_t | - tm_tag_namespace_t | tm_tag_class_t )); - } - else - { - local = tm_tags_extract (theWorkspace->tags_array, - (tm_tag_function_t | tm_tag_prototype_t | - tm_tag_member_t | - tm_tag_field_t | tm_tag_enumerator_t | - tm_tag_namespace_t | tm_tag_class_t )); - } - - if (local) - { - found = find_namespace_members_tags (local, tags, - langJava, new_name, filename); - g_ptr_array_free (local, TRUE); - } - - - if (!found && search_global) - { - GPtrArray *global = tm_tags_extract (theWorkspace->global_tags, - (tm_tag_member_t | - tm_tag_prototype_t | - tm_tag_field_t | - tm_tag_method_t | - tm_tag_function_t | - tm_tag_enumerator_t | - tm_tag_namespace_t | - tm_tag_class_t )); - - if (global) - { - find_namespace_members_tags (global, tags, langJava, - new_name, filename); -/*/ - DEBUG_PRINT ("returning these"); - gint i; - for (i=0; i < tags->len; i++) { - TMTag *cur_tag; - - cur_tag = (TMTag*)g_ptr_array_index (tags, i); - tm_tag_print (cur_tag, stdout ); - } -/*/ - g_ptr_array_free (global, TRUE); - } - } - - - if (del) - { - g_free (new_name); - } - - return tags; -} - - /* Returns a list of parent classes for the given class name @param name Name of the class @return A GPtrArray of TMTag pointers (includes the TMTag for the class) */ From 13755122f2c3193d6e630970ab3ec38dabe36f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Fri, 22 May 2015 23:31:18 +0200 Subject: [PATCH 11/32] Move symbols_get_context_separator() implementation to TM This way we can use it inside TM. --- src/symbols.c | 26 +------------------------- tagmanager/src/tm_tag.c | 30 ++++++++++++++++++++++++++++++ tagmanager/src/tm_tag.h | 2 ++ tagmanager/src/tm_workspace.c | 5 +---- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/symbols.c b/src/symbols.c index c21c3b25d4..7b81724094 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -322,31 +322,7 @@ GString *symbols_find_typenames_as_string(gint lang, gboolean global) GEANY_API_SYMBOL const gchar *symbols_get_context_separator(gint ft_id) { - switch (ft_id) - { - case GEANY_FILETYPES_C: /* for C++ .h headers or C structs */ - case GEANY_FILETYPES_CPP: - case GEANY_FILETYPES_GLSL: /* for structs */ - /*case GEANY_FILETYPES_RUBY:*/ /* not sure what to use atm*/ - case GEANY_FILETYPES_PHP: - case GEANY_FILETYPES_POWERSHELL: - case GEANY_FILETYPES_RUST: - case GEANY_FILETYPES_ZEPHIR: - return "::"; - - /* avoid confusion with other possible separators in group/section name */ - case GEANY_FILETYPES_CONF: - case GEANY_FILETYPES_REST: - return ":::"; - - /* no context separator */ - case GEANY_FILETYPES_ASCIIDOC: - case GEANY_FILETYPES_TXT2TAGS: - return "\x03"; - - default: - return "."; - } + return tm_tag_context_separator(filetypes[ft_id]->lang); } diff --git a/tagmanager/src/tm_tag.c b/tagmanager/src/tm_tag.c index 778d052a6e..3819afa9b9 100644 --- a/tagmanager/src/tm_tag.c +++ b/tagmanager/src/tm_tag.c @@ -17,6 +17,7 @@ #include "read.h" #define LIBCTAGS_DEFINED #include "tm_tag.h" +#include "tm_parser.h" #define TAG_NEW(T) ((T) = g_slice_new0(TMTag)) @@ -1167,6 +1168,35 @@ tm_get_current_tag (GPtrArray * file_tags, const gulong line, const TMTagType ta return matching_tag; } +const gchar *tm_tag_context_separator(langType lang) +{ + switch (lang) + { + case TM_PARSER_C: /* for C++ .h headers or C structs */ + case TM_PARSER_CPP: + case TM_PARSER_GLSL: /* for structs */ + /*case GEANY_FILETYPES_RUBY:*/ /* not sure what to use atm*/ + case TM_PARSER_PHP: + case TM_PARSER_POWERSHELL: + case TM_PARSER_RUST: + case TM_PARSER_ZEPHIR: + return "::"; + + /* avoid confusion with other possible separators in group/section name */ + case TM_PARSER_CONF: + case TM_PARSER_REST: + return ":::"; + + /* no context separator */ + case TM_PARSER_ASCIIDOC: + case TM_PARSER_TXT2TAGS: + return "\x03"; + + default: + return "."; + } +} + #if 0 /* Returns TMTag to function or method which "own" given line @param line Current line in edited file. diff --git a/tagmanager/src/tm_tag.h b/tagmanager/src/tm_tag.h index 15f7388e3c..b339af89ea 100644 --- a/tagmanager/src/tm_tag.h +++ b/tagmanager/src/tm_tag.h @@ -191,6 +191,8 @@ TMTag *tm_tag_ref(TMTag *tag); gboolean tm_tags_equal(const TMTag *a, const TMTag *b); +const gchar *tm_tag_context_separator(langType lang); + #ifdef TM_DEBUG /* various debugging functions */ const char *tm_tag_type_name(const TMTag *tag); diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 6de172b12d..5018ce3bf5 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -857,10 +857,7 @@ find_scope_members (const GPtrArray *tags_array, GPtrArray *member_array, { gchar *tmp_name = type_name; - if (tag->file && tag->file->lang == TM_PARSER_JAVA) - type_name = g_strdup_printf("%s.%s", tag->scope, type_name); - else - type_name = g_strdup_printf("%s::%s", tag->scope, type_name); + type_name = g_strconcat(tag->scope, tm_tag_context_separator(lang), type_name, NULL); g_free(tmp_name); } break; From bf17c90bd66d17811d1a25c454af3115c14318ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sun, 10 May 2015 11:41:04 +0200 Subject: [PATCH 12/32] Improve tag searching for "typedef struct {...}" cases When resolving typedef, search for the subsequent type in the file where the typedef was defined. For more info see the comment in the patch. --- tagmanager/src/tm_workspace.c | 38 +++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 5018ce3bf5..51464b856c 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -762,10 +762,11 @@ GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type static GPtrArray * -find_scope_members_tags (const GPtrArray * all, const char *scope, langType lang) +find_scope_members_tags (const GPtrArray * all, const char *scope, langType lang, + TMSourceFile *tag_file) { GPtrArray *tags = g_ptr_array_new(); - TMSourceFile *last_file = NULL; + TMSourceFile *last_file = tag_file; guint i; for (i = 0; i < all->len; ++i) @@ -801,6 +802,7 @@ static GPtrArray * find_scope_members (const GPtrArray *tags_array, GPtrArray *member_array, const char *name, langType lang) { + TMSourceFile *typedef_file = NULL; gboolean has_members = FALSE; GPtrArray *tags = NULL; gchar *type_name; @@ -818,6 +820,7 @@ find_scope_members (const GPtrArray *tags_array, GPtrArray *member_array, * loop when typedefs create a cycle by adding some limits. */ for (i = 0; i < 5; i++) { + guint j; TMTag *tag = NULL; GPtrArray *type_tags; TMTagType types = (tm_tag_class_t | tm_tag_namespace_t | @@ -825,17 +828,25 @@ find_scope_members (const GPtrArray *tags_array, GPtrArray *member_array, tm_tag_union_t | tm_tag_enum_t); type_tags = g_ptr_array_new(); - fill_find_tags_array(type_tags, tags_array, type_name, NULL, types, FALSE, lang); - if (type_tags) + if (typedef_file) { - guint j; - for (j = 0; j < type_tags->len; j++) - { - tag = TM_TAG(type_tags->pdata[j]); - /* prefer non-typedef tags because we can be sure they contain members */ - if (tag->type != tm_tag_typedef_t) - break; - } + /* If we have typedef_file, which is the file where the typedef was + * defined, search in the file first. This helps for + * "typedef struct {...}" cases where the typedef resolves to + * anon_struct_* and searching for it in the whole workspace returns + * too many (wrong) results. */ + fill_find_tags_array(type_tags, typedef_file->tags_array, type_name, + NULL, types, FALSE, lang); + } + if (type_tags->len == 0) + fill_find_tags_array(type_tags, tags_array, type_name, NULL, types, FALSE, lang); + + for (j = 0; j < type_tags->len; j++) + { + tag = TM_TAG(type_tags->pdata[j]); + /* prefer non-typedef tags because we can be sure they contain members */ + if (tag->type != tm_tag_typedef_t) + break; } g_ptr_array_free(type_tags, TRUE); @@ -848,6 +859,7 @@ find_scope_members (const GPtrArray *tags_array, GPtrArray *member_array, { g_free(type_name); type_name = g_strdup(tag->var_type); + typedef_file = tag->file; continue; } else /* real type with members */ @@ -865,7 +877,7 @@ find_scope_members (const GPtrArray *tags_array, GPtrArray *member_array, } if (has_members) - tags = find_scope_members_tags(member_array, type_name, lang); + tags = find_scope_members_tags(member_array, type_name, lang, typedef_file); g_free(type_name); From 30fa28bac7d8c5928dd613c34d5d833b3dfead2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sun, 10 May 2015 12:12:55 +0200 Subject: [PATCH 13/32] Don't use enums for scoped search Even though enums contain members, their members are accessed in a different way than members of classes and structs. E.g. consider: typedef enum {A, B, C, D} MyEnum; Variable of this type is declared as MyEnum myVar; myVar can be assigned a value from MyEnum; however, we don't access myVar over the dot operator so we don't need the list of all members after typing myVar. This patch eliminates some false positives after typing . --- tagmanager/src/tm_workspace.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 51464b856c..af2ebdd094 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -59,7 +59,7 @@ static TMTagAttrType global_tags_sort_attrs[] = static TMTagType TM_MEMBER_TYPE_MASK = tm_tag_function_t | tm_tag_prototype_t | tm_tag_member_t | tm_tag_field_t | - tm_tag_method_t | tm_tag_enumerator_t; + tm_tag_method_t; static TMWorkspace *theWorkspace = NULL; @@ -824,8 +824,7 @@ find_scope_members (const GPtrArray *tags_array, GPtrArray *member_array, TMTag *tag = NULL; GPtrArray *type_tags; TMTagType types = (tm_tag_class_t | tm_tag_namespace_t | - tm_tag_struct_t | tm_tag_typedef_t | - tm_tag_union_t | tm_tag_enum_t); + tm_tag_struct_t | tm_tag_union_t | tm_tag_typedef_t); type_tags = g_ptr_array_new(); if (typedef_file) From 1281d0c942322d99da626ad00edd6077425f2c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sun, 10 May 2015 21:04:20 +0200 Subject: [PATCH 14/32] Get members from the same file as the type/struct/union Rethink how to extract members from the struct types. Inspired by the patch using the same file as the typedef to search for structs, we can do the same to extract the members only from the file containing the struct and not the whole workspace. This makes this operation fast enough so we don't have to keep the extracted members in a special array (this will become especially useful for namespace search because for it we would have to extract all tags and then the extracted array would have the same size as the workspace so we'd lose the performance gain). Since the above works only for tags having the file information, that is, not the global tags, we'll lose some performance when searching the global tags. I think people don't create the tag files for complete projects but rather for header files which contain less tags and still the performance should be better than before this patch set because we go through the global tag list only once (was twice before). On the way, clean up the source a bit, add more comments and move some code from find_scope_members() to find_scope_members_tags(). --- tagmanager/src/tm_workspace.c | 107 +++++++++++++--------------------- tagmanager/src/tm_workspace.h | 2 - 2 files changed, 42 insertions(+), 67 deletions(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index af2ebdd094..37b26b2184 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -56,11 +56,6 @@ static TMTagAttrType global_tags_sort_attrs[] = tm_tag_attr_type_t, tm_tag_attr_scope_t, tm_tag_attr_arglist_t, 0 }; -static TMTagType TM_MEMBER_TYPE_MASK = - tm_tag_function_t | tm_tag_prototype_t | - tm_tag_member_t | tm_tag_field_t | - tm_tag_method_t; - static TMWorkspace *theWorkspace = NULL; @@ -73,8 +68,6 @@ static gboolean tm_create_workspace(void) theWorkspace->source_files = g_ptr_array_new(); theWorkspace->typename_array = g_ptr_array_new(); theWorkspace->global_typename_array = g_ptr_array_new(); - theWorkspace->member_array = g_ptr_array_new(); - theWorkspace->global_member_array = g_ptr_array_new(); return TRUE; } @@ -97,8 +90,6 @@ void tm_workspace_free(void) g_ptr_array_free(theWorkspace->tags_array, TRUE); g_ptr_array_free(theWorkspace->typename_array, TRUE); g_ptr_array_free(theWorkspace->global_typename_array, TRUE); - g_ptr_array_free(theWorkspace->member_array, TRUE); - g_ptr_array_free(theWorkspace->global_member_array, TRUE); g_free(theWorkspace); theWorkspace = NULL; } @@ -150,7 +141,6 @@ static void update_source_file(TMSourceFile *source_file, guchar* text_buf, * workspace while they exist and can be scanned */ tm_tags_remove_file_tags(source_file, theWorkspace->tags_array); tm_tags_remove_file_tags(source_file, theWorkspace->typename_array); - tm_tags_remove_file_tags(source_file, theWorkspace->member_array); } tm_source_file_parse(source_file, text_buf, buf_size, use_buffer); tm_tags_sort(source_file->tags_array, file_tags_sort_attrs, FALSE, TRUE); @@ -162,7 +152,6 @@ static void update_source_file(TMSourceFile *source_file, guchar* text_buf, tm_workspace_merge_tags(&theWorkspace->tags_array, source_file->tags_array); merge_extracted_tags(&(theWorkspace->typename_array), source_file->tags_array, TM_GLOBAL_TYPE_MASK); - merge_extracted_tags(&(theWorkspace->member_array), source_file->tags_array, TM_MEMBER_TYPE_MASK); } #ifdef TM_DEBUG else @@ -232,7 +221,6 @@ void tm_workspace_remove_source_file(TMSourceFile *source_file) { tm_tags_remove_file_tags(source_file, theWorkspace->tags_array); tm_tags_remove_file_tags(source_file, theWorkspace->typename_array); - tm_tags_remove_file_tags(source_file, theWorkspace->member_array); g_ptr_array_remove_index_fast(theWorkspace->source_files, i); return; } @@ -281,7 +269,6 @@ static void tm_workspace_update(void) g_ptr_array_free(theWorkspace->typename_array, TRUE); theWorkspace->typename_array = tm_tags_extract(theWorkspace->tags_array, TM_GLOBAL_TYPE_MASK); - theWorkspace->member_array = tm_tags_extract(theWorkspace->tags_array, TM_MEMBER_TYPE_MASK); } @@ -408,8 +395,6 @@ gboolean tm_workspace_load_global_tags(const char *tags_file, gint mode) g_ptr_array_free(theWorkspace->global_typename_array, TRUE); theWorkspace->global_typename_array = tm_tags_extract(new_tags, TM_GLOBAL_TYPE_MASK); - g_ptr_array_free(theWorkspace->global_member_array, TRUE); - theWorkspace->global_member_array = tm_tags_extract(new_tags, TM_MEMBER_TYPE_MASK); return TRUE; } @@ -761,33 +746,38 @@ GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type } +/* Gets all members of type_tag; search them inside the all array */ static GPtrArray * -find_scope_members_tags (const GPtrArray * all, const char *scope, langType lang, - TMSourceFile *tag_file) +find_scope_members_tags (const GPtrArray *all, TMTag *type_tag) { + TMTagType member_types = + tm_tag_function_t | tm_tag_prototype_t | + tm_tag_member_t | tm_tag_field_t | + tm_tag_method_t; GPtrArray *tags = g_ptr_array_new(); - TMSourceFile *last_file = tag_file; + gchar *scope; guint i; + if (type_tag->scope && *(type_tag->scope)) + scope = g_strconcat(type_tag->scope, tm_tag_context_separator(type_tag->lang), type_tag->name, NULL); + else + scope = g_strdup(type_tag->name); + for (i = 0; i < all->len; ++i) { TMTag *tag = TM_TAG (all->pdata[i]); - if (tag && (tag->file == last_file || last_file == NULL) && + if (tag && (tag->type & member_types) && tag->scope && tag->scope[0] != '\0' && - langs_compatible(tag->lang, lang) && + langs_compatible(tag->lang, type_tag->lang) && strcmp(scope, tag->scope) == 0) { g_ptr_array_add (tags, tag); - /* once set, always the same thanks to the if above - * add only if it's from the same file - this prevents mixing tags from - * structs with identical name (typically the anonymous ones) so we - * only get members from a single one. For workspace tags it adds - * all members because there's no file associated. */ - last_file = tag->file; } } + g_free(scope); + if (tags->len == 0) { g_ptr_array_free(tags, TRUE); @@ -798,48 +788,48 @@ find_scope_members_tags (const GPtrArray * all, const char *scope, langType lang } +/* Gets all members of the type with the given name; search them inside tags_array */ static GPtrArray * -find_scope_members (const GPtrArray *tags_array, GPtrArray *member_array, - const char *name, langType lang) +find_scope_members (const GPtrArray *tags_array, const char *name, langType lang) { - TMSourceFile *typedef_file = NULL; gboolean has_members = FALSE; GPtrArray *tags = NULL; - gchar *type_name; + TMTag *tag = NULL; + const gchar *type_name; guint i; g_return_val_if_fail(name && *name, NULL); - type_name = g_strdup(name); + type_name = name; /* First check if type_name is a type that can possibly contain members. * Try to resolve intermediate typedefs to get the real type name. Also * add scope information to the name if applicable. The only result of this - * part is the updated type_name and boolean has_members. + * part is the TMTag tag and boolean has_members. * The loop below loops only when resolving typedefs - avoid possibly infinite * loop when typedefs create a cycle by adding some limits. */ for (i = 0; i < 5; i++) { guint j; - TMTag *tag = NULL; GPtrArray *type_tags; TMTagType types = (tm_tag_class_t | tm_tag_namespace_t | tm_tag_struct_t | tm_tag_union_t | tm_tag_typedef_t); type_tags = g_ptr_array_new(); - if (typedef_file) + if (tag && tag->file) { - /* If we have typedef_file, which is the file where the typedef was - * defined, search in the file first. This helps for + /* If we have tag, it means it contains the typedef from the previous + * iteration; search in its file first. This helps for * "typedef struct {...}" cases where the typedef resolves to * anon_struct_* and searching for it in the whole workspace returns * too many (wrong) results. */ - fill_find_tags_array(type_tags, typedef_file->tags_array, type_name, + fill_find_tags_array(type_tags, tag->file->tags_array, type_name, NULL, types, FALSE, lang); } if (type_tags->len == 0) fill_find_tags_array(type_tags, tags_array, type_name, NULL, types, FALSE, lang); + tag = NULL; for (j = 0; j < type_tags->len; j++) { tag = TM_TAG(type_tags->pdata[j]); @@ -856,37 +846,28 @@ find_scope_members (const GPtrArray *tags_array, GPtrArray *member_array, /* intermediate typedef - resolve to the real type */ if (tag->type == tm_tag_typedef_t && tag->var_type && tag->var_type[0] != '\0') { - g_free(type_name); - type_name = g_strdup(tag->var_type); - typedef_file = tag->file; + type_name = tag->var_type; continue; } else /* real type with members */ { has_members = TRUE; - if (tag->scope && *(tag->scope)) - { - gchar *tmp_name = type_name; - - type_name = g_strconcat(tag->scope, tm_tag_context_separator(lang), type_name, NULL); - g_free(tmp_name); - } break; } } if (has_members) - tags = find_scope_members_tags(member_array, type_name, lang, typedef_file); - - g_free(type_name); + /* use the same file as the composite type if file information available */ + tags = find_scope_members_tags(tag->file ? tag->file->tags_array : tags_array, tag); return tags; } +/* For an array of variable tags var_array, find members inside their types */ static GPtrArray * find_scope_members_all(const GPtrArray *var_array, const GPtrArray *searched_array, - GPtrArray *member_array, langType lang) + langType lang) { GPtrArray *member_tags = NULL; guint i; @@ -899,8 +880,7 @@ find_scope_members_all(const GPtrArray *var_array, const GPtrArray *searched_arr if (tag->var_type) { - member_tags = find_scope_members(searched_array, member_array, - tag->var_type, lang); + member_tags = find_scope_members(searched_array, tag->var_type, lang); if (member_tags) break; } @@ -910,7 +890,8 @@ find_scope_members_all(const GPtrArray *var_array, const GPtrArray *searched_arr } -/* Returns all matching members tags found in given struct/union/class name. +/* Returns all member tags of a struct/union/class if the provided variable name is of such + a type. @param source_file TMSourceFile of the edited source file or NULL if not available @param name Name of the variable whose members are searched @return A GPtrArray of TMTag pointers to struct/union/class members or NULL when not found */ @@ -920,22 +901,18 @@ tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name) langType lang = source_file ? source_file->lang : -1; GPtrArray *tags, *member_tags = NULL; + /* tags corresponding to the variable name */ tags = tm_workspace_find(name, NULL, tm_tag_max_t, NULL, FALSE, lang); + /* Start searching inside the source file, continue with workspace tags and + * end with global tags. This way we find the "closest" tag to the current + * file in case there are more of them. */ if (source_file) - { - GPtrArray *file_members = tm_tags_extract(source_file->tags_array, TM_MEMBER_TYPE_MASK); - - member_tags = find_scope_members_all(tags, source_file->tags_array, - file_members, lang); - g_ptr_array_free(file_members, TRUE); - } + member_tags = find_scope_members_all(tags, source_file->tags_array, lang); if (!member_tags) - member_tags = find_scope_members_all(tags, theWorkspace->tags_array, - theWorkspace->member_array, lang); + member_tags = find_scope_members_all(tags, theWorkspace->tags_array, lang); if (!member_tags) - member_tags = find_scope_members_all(tags, theWorkspace->global_tags, - theWorkspace->global_member_array, lang); + member_tags = find_scope_members_all(tags, theWorkspace->global_tags, lang); g_ptr_array_free(tags, TRUE); diff --git a/tagmanager/src/tm_workspace.h b/tagmanager/src/tm_workspace.h index 240dbbc50c..df76b2fe3c 100644 --- a/tagmanager/src/tm_workspace.h +++ b/tagmanager/src/tm_workspace.h @@ -34,8 +34,6 @@ typedef struct (just pointers to source file tags, the tag objects are owned by the source files) */ GPtrArray *typename_array; /* Typename tags for syntax highlighting (pointers owned by source files) */ GPtrArray *global_typename_array; /* Like above for global tags */ - GPtrArray *member_array; /* Typename tags for syntax highlighting (pointers owned by source files) */ - GPtrArray *global_member_array; /* Like above for global tags */ } TMWorkspace; From c4b1cd49381a777274897ba2613fc89758acbb0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 23 May 2015 15:13:15 +0200 Subject: [PATCH 15/32] Perform "namespace" search (autocomplete for A:: where A is a type) In principle this is very similar to the normal scope search. If the provided name belongs to a type that can contain members (contrary to a variable in scope search), perform the namespace search. With namespace search show all possible members that are at the given scope. Since we perform the scope search at file level, don't perform the namespace search for tags that can span multiple files otherwise we get incomplete results which could be confusing to users. This involves namespaces and packages. --- tagmanager/src/tm_workspace.c | 58 +++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 37b26b2184..6f5843d189 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -746,9 +746,15 @@ GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type } -/* Gets all members of type_tag; search them inside the all array */ +/* Gets all members of type_tag; search them inside the all array. + * The namespace parameter determines whether we are performing the "namespace" + * search (user has typed something like "A::" where A is a type) or "scope" search + * (user has typed "a." where a is a global struct-like variable). With the + * namespace search we return all direct descendants of any type while with the + * scope search we return only those which can be invoked on a variable (member, + * method, etc.). */ static GPtrArray * -find_scope_members_tags (const GPtrArray *all, TMTag *type_tag) +find_scope_members_tags (const GPtrArray *all, TMTag *type_tag, gboolean namespace) { TMTagType member_types = tm_tag_function_t | tm_tag_prototype_t | @@ -758,6 +764,9 @@ find_scope_members_tags (const GPtrArray *all, TMTag *type_tag) gchar *scope; guint i; + if (namespace) + member_types = tm_tag_max_t; + if (type_tag->scope && *(type_tag->scope)) scope = g_strconcat(type_tag->scope, tm_tag_context_separator(type_tag->lang), type_tag->name, NULL); else @@ -790,7 +799,8 @@ find_scope_members_tags (const GPtrArray *all, TMTag *type_tag) /* Gets all members of the type with the given name; search them inside tags_array */ static GPtrArray * -find_scope_members (const GPtrArray *tags_array, const char *name, langType lang) +find_scope_members (const GPtrArray *tags_array, const char *name, langType lang, + gboolean namespace) { gboolean has_members = FALSE; GPtrArray *tags = NULL; @@ -812,8 +822,11 @@ find_scope_members (const GPtrArray *tags_array, const char *name, langType lang { guint j; GPtrArray *type_tags; - TMTagType types = (tm_tag_class_t | tm_tag_namespace_t | - tm_tag_struct_t | tm_tag_union_t | tm_tag_typedef_t); + TMTagType types = (tm_tag_class_t | tm_tag_struct_t | + tm_tag_union_t | tm_tag_typedef_t); + + if (namespace) + types |= tm_tag_enum_t; type_tags = g_ptr_array_new(); if (tag && tag->file) @@ -858,42 +871,41 @@ find_scope_members (const GPtrArray *tags_array, const char *name, langType lang if (has_members) /* use the same file as the composite type if file information available */ - tags = find_scope_members_tags(tag->file ? tag->file->tags_array : tags_array, tag); + tags = find_scope_members_tags(tag->file ? tag->file->tags_array : tags_array, tag, namespace); return tags; } -/* For an array of variable tags var_array, find members inside their types */ +/* For an array of variable/type tags, find members inside the types */ static GPtrArray * -find_scope_members_all(const GPtrArray *var_array, const GPtrArray *searched_array, - langType lang) +find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, langType lang) { GPtrArray *member_tags = NULL; guint i; - /* there may be several variables with the same name - try each of them until + /* there may be several variables/types with the same name - try each of them until * we find something */ - for (i = 0; i < var_array->len; i++) + for (i = 0; i < tags->len && !member_tags; i++) { - TMTag *tag = TM_TAG(var_array->pdata[i]); - - if (tag->var_type) - { - member_tags = find_scope_members(searched_array, tag->var_type, lang); - if (member_tags) - break; - } + TMTag *tag = TM_TAG(tags->pdata[i]); + TMTagType types = (tm_tag_class_t | tm_tag_struct_t | tm_tag_union_t | + tm_tag_enum_t | tm_tag_typedef_t); + + if (tag->type & types) /* type: namespace search */ + member_tags = find_scope_members(searched_array, tag->name, lang, TRUE); + else if (tag->var_type) /* variable: scope search */ + member_tags = find_scope_members(searched_array, tag->var_type, lang, FALSE); } return member_tags; } -/* Returns all member tags of a struct/union/class if the provided variable name is of such - a type. +/* Returns all member tags of a struct/union/class if the provided name is a variable + of such a type or the name of the type. @param source_file TMSourceFile of the edited source file or NULL if not available - @param name Name of the variable whose members are searched + @param name Name of the variable/type whose members are searched @return A GPtrArray of TMTag pointers to struct/union/class members or NULL when not found */ GPtrArray * tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name) @@ -901,7 +913,7 @@ tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name) langType lang = source_file ? source_file->lang : -1; GPtrArray *tags, *member_tags = NULL; - /* tags corresponding to the variable name */ + /* tags corresponding to the variable/type name */ tags = tm_workspace_find(name, NULL, tm_tag_max_t, NULL, FALSE, lang); /* Start searching inside the source file, continue with workspace tags and From 4bc5f4a7e482d577cdbff9badf422479cb19714b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Mon, 11 May 2015 12:12:36 +0200 Subject: [PATCH 16/32] Popup scope autocompletion dialog in more cases * for PHP and Rust scope separator :: * when there's more than one whitespace between the identifier and operator --- src/editor.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/editor.c b/src/editor.c index 37b189f4ff..603e89ef2f 100644 --- a/src/editor.c +++ b/src/editor.c @@ -657,7 +657,9 @@ static gboolean match_last_chars(ScintillaObject *sci, gint pos, const gchar *st gchar *buf; g_return_val_if_fail(len < 100, FALSE); - g_return_val_if_fail((gint)len <= pos, FALSE); + + if ((gint)len > pos) + return FALSE; buf = g_alloca(len + 1); sci_get_text_range(sci, pos - len, pos, buf); @@ -706,11 +708,15 @@ static void autocomplete_scope(GeanyEditor *editor) GeanyFiletype *ft = editor->document->file_type; GPtrArray *tags; - if (ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP) + if (ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP || + ft->id == GEANY_FILETYPES_PHP || ft->id == GEANY_FILETYPES_RUST) { - if (pos >= 2 && (match_last_chars(sci, pos, "->") || match_last_chars(sci, pos, "::"))) + if (match_last_chars(sci, pos, "::")) + pos--; + else if ((ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP) && + match_last_chars(sci, pos, "->")) pos--; - else if (ft->id == GEANY_FILETYPES_CPP && pos >= 3 && match_last_chars(sci, pos, "->*")) + else if (ft->id == GEANY_FILETYPES_CPP && match_last_chars(sci, pos, "->*")) pos-=2; else if (typed != '.') return; @@ -719,7 +725,7 @@ static void autocomplete_scope(GeanyEditor *editor) return; /* allow for a space between word and operator */ - if (isspace(sci_get_char_at(sci, pos - 2))) + while (pos >= 2 && isspace(sci_get_char_at(sci, pos - 2))) pos--; name = editor_get_word_at_pos(editor, pos - 1, NULL); if (!name) From e0122592d95f8cd5347729c3d15c05c40094edcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 23 May 2015 15:56:18 +0200 Subject: [PATCH 17/32] Perform scope autocompletion based on function return types We just need to skip the (...) and perform autocompletion as before. Shift pos by 1 in the whole function so we don't have to look 2 characters back (makes the function easier to read). Functions contain pointers in their return values - remove them before searching for the type. Also restrict the searched variable/function/type tags a bit only to types which make sense for the search. --- src/editor.c | 38 ++++++++++++++++++++++++++--------- tagmanager/src/tm_workspace.c | 22 +++++++++++++++++--- tagmanager/src/tm_workspace.h | 3 ++- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/editor.c b/src/editor.c index 603e89ef2f..7a6148b2fa 100644 --- a/src/editor.c +++ b/src/editor.c @@ -707,31 +707,51 @@ static void autocomplete_scope(GeanyEditor *editor) gchar *name; GeanyFiletype *ft = editor->document->file_type; GPtrArray *tags; + gboolean function = FALSE; - if (ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP || + if (typed == '.') + pos -= 1; + else if (ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP || ft->id == GEANY_FILETYPES_PHP || ft->id == GEANY_FILETYPES_RUST) { if (match_last_chars(sci, pos, "::")) - pos--; + pos-=2; else if ((ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP) && match_last_chars(sci, pos, "->")) - pos--; - else if (ft->id == GEANY_FILETYPES_CPP && match_last_chars(sci, pos, "->*")) pos-=2; - else if (typed != '.') + else if (ft->id == GEANY_FILETYPES_CPP && match_last_chars(sci, pos, "->*")) + pos-=3; + else return; } - else if (typed != '.') + else return; /* allow for a space between word and operator */ - while (pos >= 2 && isspace(sci_get_char_at(sci, pos - 2))) + while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1))) pos--; - name = editor_get_word_at_pos(editor, pos - 1, NULL); + + /* if function, skip to matching brace */ + if (pos > 0 && sci_get_char_at(sci, pos - 1) == ')') + { + gint brace_pos = sci_find_matching_brace(sci, pos - 1); + + if (brace_pos != -1) + { + pos = brace_pos; + function = TRUE; + } + + /* allow for a space between opening brace and function name */ + while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1))) + pos--; + } + + name = editor_get_word_at_pos(editor, pos, NULL); if (!name) return; - tags = tm_workspace_find_scope_members(editor->document->tm_file, name); + tags = tm_workspace_find_scope_members(editor->document->tm_file, name, function); if (tags) { show_tags_list(editor, tags, 0); diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 6f5843d189..0785a0a1f9 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -895,7 +895,15 @@ find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, l if (tag->type & types) /* type: namespace search */ member_tags = find_scope_members(searched_array, tag->name, lang, TRUE); else if (tag->var_type) /* variable: scope search */ - member_tags = find_scope_members(searched_array, tag->var_type, lang, FALSE); + { + gchar *tag_type = g_strdup(tag->var_type); + + /* remove pointers in case the type contains them */ + g_strdelimit(tag_type, "*^", ' '); + g_strstrip(tag_type); + member_tags = find_scope_members(searched_array, tag_type, lang, FALSE); + g_free(tag_type); + } } return member_tags; @@ -906,15 +914,23 @@ find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, l of such a type or the name of the type. @param source_file TMSourceFile of the edited source file or NULL if not available @param name Name of the variable/type whose members are searched + @param function TRUE if the name is a name of a function @return A GPtrArray of TMTag pointers to struct/union/class members or NULL when not found */ GPtrArray * -tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name) +tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, gboolean function) { langType lang = source_file ? source_file->lang : -1; GPtrArray *tags, *member_tags = NULL; + TMTagType tag_type = tm_tag_max_t & + ~(tm_tag_enumerator_t | tm_tag_namespace_t | tm_tag_package_t | + tm_tag_macro_t | tm_tag_macro_with_arg_t | + tm_tag_function_t | tm_tag_method_t); + + if (function) + tag_type = tm_tag_function_t | tm_tag_method_t; /* tags corresponding to the variable/type name */ - tags = tm_workspace_find(name, NULL, tm_tag_max_t, NULL, FALSE, lang); + tags = tm_workspace_find(name, NULL, tag_type, NULL, FALSE, lang); /* Start searching inside the source file, continue with workspace tags and * end with global tags. This way we find the "closest" tag to the current diff --git a/tagmanager/src/tm_workspace.h b/tagmanager/src/tm_workspace.h index df76b2fe3c..a6bf0d0c70 100644 --- a/tagmanager/src/tm_workspace.h +++ b/tagmanager/src/tm_workspace.h @@ -58,7 +58,8 @@ gboolean tm_workspace_create_global_tags(const char *pre_process, const char **i GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type, TMTagAttrType *attrs, gboolean partial, langType lang); -GPtrArray *tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name); +GPtrArray *tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, + gboolean function); void tm_workspace_add_source_file_noupdate(TMSourceFile *source_file); From 932dc71fe2edc0c58b15aface319d38855c5e612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sun, 10 Jan 2016 12:41:59 +0100 Subject: [PATCH 18/32] Don't use struct/class/... member types when the edited text isn't a member For instance, consider class A { int a; int b; } class B { A c; void foo() { c. //<---- (3) } } int main() { c. //<---- (1) foo.c. //<---- (2) } Consider cases (1) and (2) first - in the case (1) scope completion shouldn't be performed because c isn't a global variable; however, in case (2) it should be performed because c is a member. To fix this, we can check whether the typed variable ('c' in this case) is preceeded by another dot - if it is, use member tags for scope completion; otherwise don't use them. There's one exception from this rule - in the case (3) we are accessing a member variable from a member function at the same scope so the function should have access to the variable. For this we can use the scope at the position of the cursor. It should be B::foo in this case, more generally ...::class_name::function_name. We need to check if class_name exists at the given scope and if the member variable we are trying to access is inside ...::class_name - if so, scope completion can be performed using member tags (without explicit invocation on a member). --- src/editor.c | 14 +++++- tagmanager/src/tm_workspace.c | 88 ++++++++++++++++++++++++++++++----- tagmanager/src/tm_workspace.h | 2 +- 3 files changed, 91 insertions(+), 13 deletions(-) diff --git a/src/editor.c b/src/editor.c index 7a6148b2fa..b82e5d7e34 100644 --- a/src/editor.c +++ b/src/editor.c @@ -708,6 +708,8 @@ static void autocomplete_scope(GeanyEditor *editor) GeanyFiletype *ft = editor->document->file_type; GPtrArray *tags; gboolean function = FALSE; + gboolean member; + const gchar *current_scope; if (typed == '.') pos -= 1; @@ -751,7 +753,17 @@ static void autocomplete_scope(GeanyEditor *editor) if (!name) return; - tags = tm_workspace_find_scope_members(editor->document->tm_file, name, function); + /* check if invoked on member */ + pos -= strlen(name); + while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1))) + pos--; + member = match_last_chars(sci, pos, ".") || match_last_chars(sci, pos, "::") || + match_last_chars(sci, pos, "->") || match_last_chars(sci, pos, "->*"); + + if (symbols_get_current_scope(editor->document, ¤t_scope) == -1) + current_scope = ""; + tags = tm_workspace_find_scope_members(editor->document->tm_file, name, function, + member, current_scope); if (tags) { show_tags_list(editor, tags, 0); diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 0785a0a1f9..da09630251 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -877,9 +877,60 @@ find_scope_members (const GPtrArray *tags_array, const char *name, langType lang } +/* Checks whether a member tag is directly accessible from method with method_scope */ +static gboolean member_at_method_scope(const GPtrArray *tags, const gchar *method_scope, TMTag *member_tag, + langType lang) +{ + const gchar *sep = tm_tag_context_separator(lang); + gboolean ret = FALSE; + gchar **comps; + guint len; + + /* method scope is in the form ...::class_name::method_name */ + comps = g_strsplit (method_scope, sep, 0); + len = g_strv_length(comps); + if (len > 1) + { + gchar *method, *member_scope, *cls, *cls_scope; + + /* get method/member scope */ + method = comps[len - 1]; + comps[len - 1] = NULL; + member_scope = g_strjoinv(sep, comps); + comps[len - 1] = method; + + /* get class scope */ + cls = comps[len - 2]; + comps[len - 2] = NULL; + cls_scope = g_strjoinv(sep, comps); + comps[len - 2] = cls; + + /* check whether member inside the class */ + if (g_strcmp0(member_tag->scope, member_scope) == 0) + { + const GPtrArray *src = member_tag->file ? member_tag->file->tags_array : tags; + GPtrArray *cls_tags = g_ptr_array_new(); + + /* check whether the class exists */ + fill_find_tags_array(cls_tags, src, cls, cls_scope, + tm_tag_class_t | tm_tag_struct_t | tm_tag_interface_t, FALSE, lang); + ret = cls_tags->len > 0; + g_ptr_array_free(cls_tags, TRUE); + } + + g_free(cls_scope); + g_free(member_scope); + } + + g_strfreev(comps); + return ret; +} + + /* For an array of variable/type tags, find members inside the types */ static GPtrArray * -find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, langType lang) +find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, langType lang, + gboolean member, const gchar *current_scope) { GPtrArray *member_tags = NULL; guint i; @@ -896,13 +947,22 @@ find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, l member_tags = find_scope_members(searched_array, tag->name, lang, TRUE); else if (tag->var_type) /* variable: scope search */ { - gchar *tag_type = g_strdup(tag->var_type); + /* The question now is whether we should use member tags (such as + * tm_tag_field_t, tm_tag_member_t) or not. We want them if member==TRUE + * (which means user has typed something like foo.bar.) or if we are + * inside a method where foo is a class member, we want scope completion + * for foo. */ + if (!(tag->type & (tm_tag_field_t | tm_tag_member_t)) || member || + member_at_method_scope(tags, current_scope, tag, lang)) + { + gchar *tag_type = g_strdup(tag->var_type); - /* remove pointers in case the type contains them */ - g_strdelimit(tag_type, "*^", ' '); - g_strstrip(tag_type); - member_tags = find_scope_members(searched_array, tag_type, lang, FALSE); - g_free(tag_type); + /* remove pointers in case the type contains them */ + g_strdelimit(tag_type, "*^", ' '); + g_strstrip(tag_type); + member_tags = find_scope_members(searched_array, tag_type, lang, FALSE); + g_free(tag_type); + } } } @@ -915,9 +975,12 @@ find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, l @param source_file TMSourceFile of the edited source file or NULL if not available @param name Name of the variable/type whose members are searched @param function TRUE if the name is a name of a function + @param member TRUE if invoked on class/struct member (e.g. after the last dot in foo.bar.) + @param current_scope The current scope in the editor @return A GPtrArray of TMTag pointers to struct/union/class members or NULL when not found */ GPtrArray * -tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, gboolean function) +tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, + gboolean function, gboolean member, const gchar *current_scope) { langType lang = source_file ? source_file->lang : -1; GPtrArray *tags, *member_tags = NULL; @@ -936,11 +999,14 @@ tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, gb * end with global tags. This way we find the "closest" tag to the current * file in case there are more of them. */ if (source_file) - member_tags = find_scope_members_all(tags, source_file->tags_array, lang); + member_tags = find_scope_members_all(tags, source_file->tags_array, + lang, member, current_scope); if (!member_tags) - member_tags = find_scope_members_all(tags, theWorkspace->tags_array, lang); + member_tags = find_scope_members_all(tags, theWorkspace->tags_array, lang, + member, current_scope); if (!member_tags) - member_tags = find_scope_members_all(tags, theWorkspace->global_tags, lang); + member_tags = find_scope_members_all(tags, theWorkspace->global_tags, lang, + member, current_scope); g_ptr_array_free(tags, TRUE); diff --git a/tagmanager/src/tm_workspace.h b/tagmanager/src/tm_workspace.h index a6bf0d0c70..bf2e6b2ffc 100644 --- a/tagmanager/src/tm_workspace.h +++ b/tagmanager/src/tm_workspace.h @@ -59,7 +59,7 @@ GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type TMTagAttrType *attrs, gboolean partial, langType lang); GPtrArray *tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, - gboolean function); + gboolean function, gboolean member, const gchar *current_scope); void tm_workspace_add_source_file_noupdate(TMSourceFile *source_file); From 7a3d6a4ee1e743aec67c7a27c20ed1714f1250ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 23 May 2015 13:26:37 +0200 Subject: [PATCH 19/32] Filter scope autocompletion list based on user input When scope autocompletion list shows, start filtering it when when the user types some more characters. As long as the list is non-empty, don't switch to normal autocompletion and show only the scope autocompletion results. --- src/editor.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/src/editor.c b/src/editor.c index b82e5d7e34..9b55e8bde2 100644 --- a/src/editor.c +++ b/src/editor.c @@ -75,6 +75,7 @@ static GHashTable *snippet_hash = NULL; static GQueue *snippet_offsets = NULL; static gint snippet_cursor_insert_pos; static GtkAccelGroup *snippet_accel_group = NULL; +static gboolean autocomplete_scope_shown = FALSE; static const gchar geany_cursor_marker[] = "__GEANY_CURSOR_MARKER__"; @@ -699,7 +700,7 @@ static void request_reshowing_calltip(SCNotification *nt) } -static void autocomplete_scope(GeanyEditor *editor) +static gboolean autocomplete_scope(GeanyEditor *editor, const gchar *root, gsize rootlen) { ScintillaObject *sci = editor->sci; gint pos = sci_get_current_position(editor->sci); @@ -709,8 +710,22 @@ static void autocomplete_scope(GeanyEditor *editor) GPtrArray *tags; gboolean function = FALSE; gboolean member; + gboolean ret = FALSE; const gchar *current_scope; + if (autocomplete_scope_shown) + { + /* move at the operator position */ + pos -= rootlen; + + /* allow for a space between word and operator */ + while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1))) + pos--; + + if (pos > 0) + typed = sci_get_char_at(sci, pos - 1); + } + if (typed == '.') pos -= 1; else if (ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP || @@ -724,10 +739,10 @@ static void autocomplete_scope(GeanyEditor *editor) else if (ft->id == GEANY_FILETYPES_CPP && match_last_chars(sci, pos, "->*")) pos-=3; else - return; + return FALSE; } else - return; + return FALSE; /* allow for a space between word and operator */ while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1))) @@ -751,7 +766,7 @@ static void autocomplete_scope(GeanyEditor *editor) name = editor_get_word_at_pos(editor, pos, NULL); if (!name) - return; + return FALSE; /* check if invoked on member */ pos -= strlen(name); @@ -766,11 +781,28 @@ static void autocomplete_scope(GeanyEditor *editor) member, current_scope); if (tags) { - show_tags_list(editor, tags, 0); + GPtrArray *filtered = g_ptr_array_new(); + TMTag *tag; + guint i; + + foreach_ptr_array(tag, i, tags) + { + if (g_str_has_prefix(tag->name, root)) + g_ptr_array_add(filtered, tag); + } + + if (filtered->len > 0) + { + show_tags_list(editor, filtered, rootlen); + ret = TRUE; + } + g_ptr_array_free(tags, TRUE); + g_ptr_array_free(filtered, TRUE); } g_free(name); + return ret; } @@ -1129,6 +1161,7 @@ static gboolean on_editor_notify(G_GNUC_UNUSED GObject *object, GeanyEditor *edi case SCN_AUTOCCANCELLED: /* now that autocomplete is finishing or was cancelled, reshow calltips * if they were showing */ + autocomplete_scope_shown = FALSE; request_reshowing_calltip(nt); break; @@ -2250,7 +2283,6 @@ gboolean editor_start_auto_complete(GeanyEditor *editor, gint pos, gboolean forc if (!force && !highlighting_is_code_style(lexer, style)) return FALSE; - autocomplete_scope(editor); ret = autocomplete_check_html(editor, style, pos); if (ft->id == GEANY_FILETYPES_LATEX) @@ -2264,6 +2296,23 @@ gboolean editor_start_auto_complete(GeanyEditor *editor, gint pos, gboolean forc root = cword; rootlen = strlen(root); + if (ret || force) + { + if (autocomplete_scope_shown) + { + autocomplete_scope_shown = FALSE; + if (!ret) + sci_send_command(sci, SCI_AUTOCCANCEL); + } + } + else + { + ret = autocomplete_scope(editor, root, rootlen); + if (!ret && autocomplete_scope_shown) + sci_send_command(sci, SCI_AUTOCCANCEL); + autocomplete_scope_shown = ret; + } + if (!ret && rootlen > 0) { if (ft->id == GEANY_FILETYPES_PHP && style == SCE_HPHP_DEFAULT && From 2682d7973f0518ccfb3806901170ff8314c311c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Thu, 28 May 2015 16:06:24 +0200 Subject: [PATCH 20/32] Remove anon_* elements when performing namespace search These correspond to anonymous structs, enums, etc. but they don't have any name so the name shouldn't be listed. --- tagmanager/src/tm_tag.c | 14 ++++++++++++++ tagmanager/src/tm_tag.h | 2 ++ tagmanager/src/tm_workspace.c | 3 ++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/tagmanager/src/tm_tag.c b/tagmanager/src/tm_tag.c index 3819afa9b9..576a23ce29 100644 --- a/tagmanager/src/tm_tag.c +++ b/tagmanager/src/tm_tag.c @@ -1197,6 +1197,20 @@ const gchar *tm_tag_context_separator(langType lang) } } +gboolean tm_tag_is_anon(const TMTag *tag) +{ + guint i; + char dummy; + + if (tag->lang == TM_PARSER_C || tag->lang == TM_PARSER_CPP) + return sscanf(tag->name, "anon_%*[a-z]_%u%c", &i, &dummy) == 1; + else if (tag->lang == TM_PARSER_FORTRAN || tag->lang == TM_PARSER_F77) + return sscanf(tag->name, "Structure#%u%c", &i, &dummy) == 1 || + sscanf(tag->name, "Interface#%u%c", &i, &dummy) == 1 || + sscanf(tag->name, "Enum#%u%c", &i, &dummy) == 1; + return FALSE; +} + #if 0 /* Returns TMTag to function or method which "own" given line @param line Current line in edited file. diff --git a/tagmanager/src/tm_tag.h b/tagmanager/src/tm_tag.h index b339af89ea..9441392d3d 100644 --- a/tagmanager/src/tm_tag.h +++ b/tagmanager/src/tm_tag.h @@ -193,6 +193,8 @@ gboolean tm_tags_equal(const TMTag *a, const TMTag *b); const gchar *tm_tag_context_separator(langType lang); +gboolean tm_tag_is_anon(const TMTag *tag); + #ifdef TM_DEBUG /* various debugging functions */ const char *tm_tag_type_name(const TMTag *tag); diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index da09630251..e5cc759e8c 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -779,7 +779,8 @@ find_scope_members_tags (const GPtrArray *all, TMTag *type_tag, gboolean namespa if (tag && (tag->type & member_types) && tag->scope && tag->scope[0] != '\0' && langs_compatible(tag->lang, type_tag->lang) && - strcmp(scope, tag->scope) == 0) + strcmp(scope, tag->scope) == 0 && + (!namespace || !tm_tag_is_anon(tag))) { g_ptr_array_add (tags, tag); } From 67916dc4038918a940f38d08d073a9bbb4aea89b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Fri, 22 May 2015 00:49:03 +0200 Subject: [PATCH 21/32] Use only binary search to find first/last element in a row of equal tags The linear part may become expensive when there are many equal tags which can happen when partial search, used for non-scope completion, is used. --- tagmanager/src/tm_tag.c | 82 ++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/tagmanager/src/tm_tag.c b/tagmanager/src/tm_tag.c index 576a23ce29..8f270efad4 100644 --- a/tagmanager/src/tm_tag.c +++ b/tagmanager/src/tm_tag.c @@ -109,6 +109,8 @@ typedef struct { guint *sort_attrs; gboolean partial; + const GPtrArray *tags_array; + gboolean first; } TMSortOptions; static const char *s_tag_type_names[] = { @@ -1084,6 +1086,31 @@ static gpointer binary_search(gpointer key, gpointer base, size_t nmemb, return NULL; } +static gint tag_search_cmp(gconstpointer ptr1, gconstpointer ptr2, gpointer user_data) +{ + gint res = tm_tag_compare(ptr1, ptr2, user_data); + + if (res == 0) + { + TMSortOptions *sort_options = user_data; + const GPtrArray *tags_array = sort_options->tags_array; + TMTag **tag = (TMTag **) ptr2; + + /* if previous/next (depending on sort options) tag equal, we haven't + * found the first/last tag in a sequence of equal tags yet */ + if (sort_options->first && ptr2 != &tags_array->pdata[0]) { + if (tm_tag_compare(ptr1, tag - 1, user_data) == 0) + return -1; + } + else if (!sort_options->first && ptr2 != &tags_array->pdata[tags_array->len-1]) + { + if (tm_tag_compare(ptr1, tag + 1, user_data) == 0) + return 1; + } + } + return res; +} + /* Returns a pointer to the position of the first matching tag in a (sorted) tags array. The passed array of tags must be already sorted by name (searched with binary search). @@ -1093,51 +1120,40 @@ static gpointer binary_search(gpointer key, gpointer base, size_t nmemb, @param tagCount Return location of the matched tags. */ TMTag **tm_tags_find(const GPtrArray *tags_array, const char *name, - gboolean partial, guint * tagCount) + gboolean partial, guint *tagCount) { - static TMTag *tag = NULL; - TMTag **result; - guint tagMatches=0; + TMTag *tag, **first; TMSortOptions sort_options; *tagCount = 0; - if ((!tags_array) || (!tags_array->len)) + if (!tags_array || !tags_array->len) return NULL; - if (NULL == tag) - tag = g_new0(TMTag, 1); + tag = g_new0(TMTag, 1); tag->name = (char *) name; + sort_options.sort_attrs = NULL; sort_options.partial = partial; + sort_options.tags_array = tags_array; + sort_options.first = TRUE; + first = (TMTag **)binary_search(&tag, tags_array->pdata, tags_array->len, + tag_search_cmp, &sort_options); - result = (TMTag **)binary_search(&tag, tags_array->pdata, tags_array->len, - tm_tag_compare, &sort_options); - /* There can be matches on both sides of result */ - if (result) + if (first) { - TMTag **last = (TMTag **) &tags_array->pdata[tags_array->len - 1]; - TMTag **adv; - - /* First look for any matches after result */ - adv = result; - adv++; - for (; adv <= last && *adv; ++ adv) - { - if (0 != tm_tag_compare(&tag, adv, &sort_options)) - break; - ++tagMatches; - } - /* Now look for matches from result and below */ - for (; result >= (TMTag **) tags_array->pdata; -- result) - { - if (0 != tm_tag_compare(&tag, (TMTag **) result, &sort_options)) - break; - ++tagMatches; - } - *tagCount=tagMatches; - ++ result; /* Correct address for the last successful match */ + TMTag **last; + unsigned first_pos; + + sort_options.first = FALSE; + first_pos = first - (TMTag **)tags_array->pdata; + /* search between the first element and end */ + last = (TMTag **)binary_search(&tag, first, tags_array->len - first_pos, + tag_search_cmp, &sort_options); + *tagCount = last - first + 1; } - return (TMTag **) result; + + g_free(tag); + return (TMTag **) first; } /* Returns TMTag which "own" given line From 02105d77d74da4954dce165bba9ead31cbf5ed72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 23 May 2015 21:29:08 +0200 Subject: [PATCH 22/32] Simplify tag type specifications in scope search Consider types with members to have the same properties everywhere (this might differ language to language but this assumption should behave reasonably for any language). Don't check member type in find_scope_members_tags() - we already check scope which should be sufficient and will work even if some language uses function/variable instead of method/member/field. --- tagmanager/src/tm_workspace.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index e5cc759e8c..e7a0d6ab6d 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -56,6 +56,10 @@ static TMTagAttrType global_tags_sort_attrs[] = tm_tag_attr_type_t, tm_tag_attr_scope_t, tm_tag_attr_arglist_t, 0 }; +static TMTagType TM_TYPE_WITH_MEMBERS = + tm_tag_class_t | tm_tag_struct_t | tm_tag_union_t | + tm_tag_enum_t | tm_tag_interface_t; + static TMWorkspace *theWorkspace = NULL; @@ -756,10 +760,7 @@ GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type static GPtrArray * find_scope_members_tags (const GPtrArray *all, TMTag *type_tag, gboolean namespace) { - TMTagType member_types = - tm_tag_function_t | tm_tag_prototype_t | - tm_tag_member_t | tm_tag_field_t | - tm_tag_method_t; + TMTagType member_types = tm_tag_max_t & ~(TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t); GPtrArray *tags = g_ptr_array_new(); gchar *scope; guint i; @@ -823,11 +824,10 @@ find_scope_members (const GPtrArray *tags_array, const char *name, langType lang { guint j; GPtrArray *type_tags; - TMTagType types = (tm_tag_class_t | tm_tag_struct_t | - tm_tag_union_t | tm_tag_typedef_t); + TMTagType types = TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t; - if (namespace) - types |= tm_tag_enum_t; + if (!namespace) + types &= ~tm_tag_enum_t; type_tags = g_ptr_array_new(); if (tag && tag->file) @@ -913,8 +913,8 @@ static gboolean member_at_method_scope(const GPtrArray *tags, const gchar *metho GPtrArray *cls_tags = g_ptr_array_new(); /* check whether the class exists */ - fill_find_tags_array(cls_tags, src, cls, cls_scope, - tm_tag_class_t | tm_tag_struct_t | tm_tag_interface_t, FALSE, lang); + fill_find_tags_array(cls_tags, src, cls, cls_scope, TM_TYPE_WITH_MEMBERS, + FALSE, lang); ret = cls_tags->len > 0; g_ptr_array_free(cls_tags, TRUE); } @@ -941,8 +941,8 @@ find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, l for (i = 0; i < tags->len && !member_tags; i++) { TMTag *tag = TM_TAG(tags->pdata[i]); - TMTagType types = (tm_tag_class_t | tm_tag_struct_t | tm_tag_union_t | - tm_tag_enum_t | tm_tag_typedef_t); + TMTagType member_types = tm_tag_member_t | tm_tag_field_t | tm_tag_method_t; + TMTagType types = TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t; if (tag->type & types) /* type: namespace search */ member_tags = find_scope_members(searched_array, tag->name, lang, TRUE); @@ -953,7 +953,7 @@ find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, l * (which means user has typed something like foo.bar.) or if we are * inside a method where foo is a class member, we want scope completion * for foo. */ - if (!(tag->type & (tm_tag_field_t | tm_tag_member_t)) || member || + if (!(tag->type & member_types) || member || member_at_method_scope(tags, current_scope, tag, lang)) { gchar *tag_type = g_strdup(tag->var_type); @@ -985,13 +985,13 @@ tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, { langType lang = source_file ? source_file->lang : -1; GPtrArray *tags, *member_tags = NULL; + TMTagType function_types = tm_tag_function_t | tm_tag_method_t | + tm_tag_macro_with_arg_t | tm_tag_prototype_t; TMTagType tag_type = tm_tag_max_t & - ~(tm_tag_enumerator_t | tm_tag_namespace_t | tm_tag_package_t | - tm_tag_macro_t | tm_tag_macro_with_arg_t | - tm_tag_function_t | tm_tag_method_t); + ~(function_types | tm_tag_enumerator_t | tm_tag_namespace_t | tm_tag_package_t); if (function) - tag_type = tm_tag_function_t | tm_tag_method_t; + tag_type = function_types; /* tags corresponding to the variable/type name */ tags = tm_workspace_find(name, NULL, tag_type, NULL, FALSE, lang); From 5b4c6f96b20cd309026e42c02133b28d1e0e64df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sun, 24 May 2015 21:06:55 +0200 Subject: [PATCH 23/32] Don't use anon_struct_* and similar members unless we are sure it's the right one We can only be sure it's the right one if we previously resolved a typedef to it and the typedef was in the same file. --- tagmanager/src/tm_workspace.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index e7a0d6ab6d..8d23e4978a 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -805,6 +805,7 @@ find_scope_members (const GPtrArray *tags_array, const char *name, langType lang gboolean namespace) { gboolean has_members = FALSE; + gboolean typedef_struct = FALSE; GPtrArray *tags = NULL; TMTag *tag = NULL; const gchar *type_name; @@ -839,6 +840,7 @@ find_scope_members (const GPtrArray *tags_array, const char *name, langType lang * too many (wrong) results. */ fill_find_tags_array(type_tags, tag->file->tags_array, type_name, NULL, types, FALSE, lang); + typedef_struct = type_tags->len > 0; } if (type_tags->len == 0) fill_find_tags_array(type_tags, tags_array, type_name, NULL, types, FALSE, lang); @@ -870,7 +872,10 @@ find_scope_members (const GPtrArray *tags_array, const char *name, langType lang } } - if (has_members) + /* ignore anon_struct_* and similar unless we resolved a typedef to it within + * a single file so we can be sure we don't pick a wrong anon_struct_* from + * a different file */ + if (has_members && (typedef_struct || !tm_tag_is_anon(tag))) /* use the same file as the composite type if file information available */ tags = find_scope_members_tags(tag->file ? tag->file->tags_array : tags_array, tag, namespace); From ad77ee15dac78d3cc2a01e16828dfbcad33fa2d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Fri, 15 Jan 2016 15:46:00 +0100 Subject: [PATCH 24/32] Add a "prefix" search for non-scope autocompletion The main reason for separating m_workspace_find() into two parts is the fact that when matching only the prefix, the result may contain too many results and we need to go through all of them, return them and at the end discard most of them. For instance, when considering the linux kernel project with 2300000 tags and when autocompletion is set to be invoked after typing a single character, we get on average something like 100000 results (tag_num/alphabet_size). But from these 100000 results, we get only the first 30 which we display in the popup and discard the rest which means going through the list of the 100000 tags and comparing them for no reason. Thanks to using binary search for the start and the end of the sequence of matching tags (added in a separate patch), we can get the start of the sequence and the length of the sequence very quickly without going through it. For the prefix search we can limit the number of tags we are interested in and go through at most this number of returned tags (to be precise, times two, because we need to go both through the workspace array and global tags array and remove the extras only after sorting the two). It would be possible to combine both tm_workspace_find() and tm_workspace_find_prefix() into a single function but the result is a bit hard to read because some of the logic is used only in tm_workspace_find() and some only in tm_workspace_find_prefix() so even though there is some code duplication, I believe it's easier to understand this way. --- src/editor.c | 9 ++-- tagmanager/src/tm_workspace.c | 96 ++++++++++++++++++++++++++--------- tagmanager/src/tm_workspace.h | 4 +- 3 files changed, 77 insertions(+), 32 deletions(-) diff --git a/src/editor.c b/src/editor.c index 9b55e8bde2..1c479115ab 100644 --- a/src/editor.c +++ b/src/editor.c @@ -1903,7 +1903,6 @@ static gchar *find_calltip(const gchar *word, GeanyFiletype *ft) GPtrArray *tags; const TMTagType arg_types = tm_tag_function_t | tm_tag_prototype_t | tm_tag_method_t | tm_tag_macro_with_arg_t; - TMTagAttrType *attrs = NULL; TMTag *tag; GString *str = NULL; guint i; @@ -1911,7 +1910,7 @@ static gchar *find_calltip(const gchar *word, GeanyFiletype *ft) g_return_val_if_fail(ft && word && *word, NULL); /* use all types in case language uses wrong tag type e.g. python "members" instead of "methods" */ - tags = tm_workspace_find(word, NULL, tm_tag_max_t, attrs, FALSE, ft->lang); + tags = tm_workspace_find(word, NULL, tm_tag_max_t, NULL, ft->lang); if (tags->len == 0) { g_ptr_array_free(tags, TRUE); @@ -1925,8 +1924,7 @@ static gchar *find_calltip(const gchar *word, GeanyFiletype *ft) { g_ptr_array_free(tags, TRUE); /* user typed e.g. 'new Classname(' so lookup D constructor Classname::this() */ - tags = tm_workspace_find("this", tag->name, - arg_types, attrs, FALSE, ft->lang); + tags = tm_workspace_find("this", tag->name, arg_types, NULL, ft->lang); if (tags->len == 0) { g_ptr_array_free(tags, TRUE); @@ -2108,7 +2106,6 @@ autocomplete_html(ScintillaObject *sci, const gchar *root, gsize rootlen) static gboolean autocomplete_tags(GeanyEditor *editor, const gchar *root, gsize rootlen) { - TMTagAttrType attrs[] = { tm_tag_attr_name_t, 0 }; GPtrArray *tags; GeanyDocument *doc; gboolean found; @@ -2117,7 +2114,7 @@ autocomplete_tags(GeanyEditor *editor, const gchar *root, gsize rootlen) doc = editor->document; - tags = tm_workspace_find(root, NULL, tm_tag_max_t, attrs, TRUE, doc->file_type->lang); + tags = tm_workspace_find_prefix(root, doc->file_type->lang, editor_prefs.autocompletion_max_entries); found = tags->len > 0; if (found) show_tags_list(editor, tags, rootlen); diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 8d23e4978a..5cec85d6b1 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -700,28 +700,26 @@ static gboolean langs_compatible(langType lang, langType other) } -static guint fill_find_tags_array(GPtrArray *dst, const GPtrArray *src, - const char *name, const char *scope, TMTagType type, gboolean partial, langType lang) +static void fill_find_tags_array(GPtrArray *dst, const GPtrArray *src, + const char *name, const char *scope, TMTagType type, langType lang) { - TMTag **matches; - guint tagIter; - guint tagCount; + TMTag **tag; + guint i, num; if (!src || !dst || !name || !*name) - return 0; + return; - matches = tm_tags_find(src, name, partial, &tagCount); - for (tagIter = 0; tagIter < tagCount; ++tagIter) + tag = tm_tags_find(src, name, FALSE, &num); + for (i = 0; i < num; ++i) { - if ((type & (*matches)->type) && - langs_compatible(lang, (*matches)->lang) && - (!scope || g_strcmp0((*matches)->scope, scope) == 0)) - g_ptr_array_add(dst, *matches); - - matches++; + if ((type & (*tag)->type) && + langs_compatible(lang, (*tag)->lang) && + (!scope || g_strcmp0((*tag)->scope, scope) == 0)) + { + g_ptr_array_add(dst, *tag); + } + tag++; } - - return dst->len; } @@ -730,18 +728,17 @@ static guint fill_find_tags_array(GPtrArray *dst, const GPtrArray *src, @param scope The scope name of the tag to find, or NULL. @param type The tag types to return (TMTagType). Can be a bitmask. @param attrs The attributes to sort and dedup on (0 terminated integer array). - @param partial Whether partial match is allowed. @param lang Specifies the language(see the table in parsers.h) of the tags to be found, -1 for all @return Array of matching tags. */ GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type, - TMTagAttrType *attrs, gboolean partial, langType lang) + TMTagAttrType *attrs, langType lang) { GPtrArray *tags = g_ptr_array_new(); - fill_find_tags_array(tags, theWorkspace->tags_array, name, scope, type, partial, lang); - fill_find_tags_array(tags, theWorkspace->global_tags, name, scope, type, partial, lang); + fill_find_tags_array(tags, theWorkspace->tags_array, name, scope, type, lang); + fill_find_tags_array(tags, theWorkspace->global_tags, name, scope, type, lang); if (attrs) tm_tags_sort(tags, attrs, TRUE, FALSE); @@ -750,6 +747,56 @@ GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type } +static void fill_find_tags_array_prefix(GPtrArray *dst, const GPtrArray *src, + const char *name, langType lang, guint max_num) +{ + TMTag **tag, *last = NULL; + guint i, count, num; + + if (!src || !dst || !name || !*name) + return; + + num = 0; + tag = tm_tags_find(src, name, TRUE, &count); + for (i = 0; i < count && num < max_num; ++i) + { + if (langs_compatible(lang, (*tag)->lang) && + !tm_tag_is_anon(*tag) && + (!last || g_strcmp0(last->name, (*tag)->name) != 0)) + { + g_ptr_array_add(dst, *tag); + last = *tag; + num++; + } + tag++; + } +} + + +/* Returns tags with the specified prefix sorted by name. If there are several + tags with the same name, only one of them appears in the resulting array. + @param prefix The prefix of the tag to find. + @param lang Specifies the language(see the table in parsers.h) of the tags to be found, + -1 for all. + @param max_num The maximum number of tags to return. + @return Array of matching tags sorted by their name. +*/ +GPtrArray *tm_workspace_find_prefix(const char *prefix, langType lang, guint max_num) +{ + TMTagAttrType attrs[] = { tm_tag_attr_name_t, 0 }; + GPtrArray *tags = g_ptr_array_new(); + + fill_find_tags_array_prefix(tags, theWorkspace->tags_array, prefix, lang, max_num); + fill_find_tags_array_prefix(tags, theWorkspace->global_tags, prefix, lang, max_num); + + tm_tags_sort(tags, attrs, TRUE, FALSE); + if (tags->len > max_num) + tags->len = max_num; + + return tags; +} + + /* Gets all members of type_tag; search them inside the all array. * The namespace parameter determines whether we are performing the "namespace" * search (user has typed something like "A::" where A is a type) or "scope" search @@ -839,11 +886,11 @@ find_scope_members (const GPtrArray *tags_array, const char *name, langType lang * anon_struct_* and searching for it in the whole workspace returns * too many (wrong) results. */ fill_find_tags_array(type_tags, tag->file->tags_array, type_name, - NULL, types, FALSE, lang); + NULL, types, lang); typedef_struct = type_tags->len > 0; } if (type_tags->len == 0) - fill_find_tags_array(type_tags, tags_array, type_name, NULL, types, FALSE, lang); + fill_find_tags_array(type_tags, tags_array, type_name, NULL, types, lang); tag = NULL; for (j = 0; j < type_tags->len; j++) @@ -918,8 +965,7 @@ static gboolean member_at_method_scope(const GPtrArray *tags, const gchar *metho GPtrArray *cls_tags = g_ptr_array_new(); /* check whether the class exists */ - fill_find_tags_array(cls_tags, src, cls, cls_scope, TM_TYPE_WITH_MEMBERS, - FALSE, lang); + fill_find_tags_array(cls_tags, src, cls, cls_scope, TM_TYPE_WITH_MEMBERS, lang); ret = cls_tags->len > 0; g_ptr_array_free(cls_tags, TRUE); } @@ -999,7 +1045,7 @@ tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, tag_type = function_types; /* tags corresponding to the variable/type name */ - tags = tm_workspace_find(name, NULL, tag_type, NULL, FALSE, lang); + tags = tm_workspace_find(name, NULL, tag_type, NULL, lang); /* Start searching inside the source file, continue with workspace tags and * end with global tags. This way we find the "closest" tag to the current diff --git a/tagmanager/src/tm_workspace.h b/tagmanager/src/tm_workspace.h index bf2e6b2ffc..50821224b9 100644 --- a/tagmanager/src/tm_workspace.h +++ b/tagmanager/src/tm_workspace.h @@ -56,7 +56,9 @@ gboolean tm_workspace_create_global_tags(const char *pre_process, const char **i int includes_count, const char *tags_file, int lang); GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type, - TMTagAttrType *attrs, gboolean partial, langType lang); + TMTagAttrType *attrs, langType lang); + +GPtrArray *tm_workspace_find_prefix(const char *prefix, langType lang, guint max_num); GPtrArray *tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, gboolean function, gboolean member, const gchar *current_scope); From 5cd573464227268a9b3c10bca1ee274340ceeaae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Wed, 27 May 2015 21:49:54 +0200 Subject: [PATCH 25/32] Minor cleanup Make the unused code compile and remove unused tm_get_current_function() (we have similar symbols_get_current_function() and there's no reason to keep it) --- tagmanager/src/tm_tag.c | 12 ------------ tagmanager/src/tm_workspace.c | 4 ++-- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/tagmanager/src/tm_tag.c b/tagmanager/src/tm_tag.c index 8f270efad4..a4780b509a 100644 --- a/tagmanager/src/tm_tag.c +++ b/tagmanager/src/tm_tag.c @@ -1227,18 +1227,6 @@ gboolean tm_tag_is_anon(const TMTag *tag) return FALSE; } -#if 0 -/* Returns TMTag to function or method which "own" given line - @param line Current line in edited file. - @param file_tags A GPtrArray of edited file TMTag pointers. - @return TMTag pointers to owner function. */ -static const TMTag * -tm_get_current_function (GPtrArray * file_tags, const gulong line) -{ - return tm_get_current_tag (file_tags, line, tm_tag_function_t | tm_tag_method_t); -} -#endif - #ifdef TM_DEBUG /* various debugging functions */ diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 5cec85d6b1..7ee715acde 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -1107,7 +1107,7 @@ static const GPtrArray *tm_workspace_get_parents(const gchar *name) parents = g_ptr_array_new(); else g_ptr_array_set_size(parents, 0); - matches = tm_workspace_find(name, tm_tag_class_t, type, FALSE, -1); + matches = tm_workspace_find(name, NULL, tm_tag_class_t, type, -1); if ((NULL == matches) || (0 == matches->len)) return NULL; g_ptr_array_add(parents, matches->pdata[0]); @@ -1126,7 +1126,7 @@ static const GPtrArray *tm_workspace_get_parents(const gchar *name) } if (parents->len == j) { - matches = tm_workspace_find(*klass, tm_tag_class_t, type, FALSE, -1); + matches = tm_workspace_find(*klass, NULL, tm_tag_class_t, type, -1); if ((NULL != matches) && (0 < matches->len)) g_ptr_array_add(parents, matches->pdata[0]); } From 981320c3b882ca4aa2e7af152e5513dbf985d079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Wed, 9 Dec 2015 17:25:41 +0100 Subject: [PATCH 26/32] Skip [] when performing scope autocompletion In addition to skipping function parameters, we can also skip indexes so in something like foo[2]. we get autocompletion for foo's type members. --- src/editor.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/editor.c b/src/editor.c index 1c479115ab..b94c7f2475 100644 --- a/src/editor.c +++ b/src/editor.c @@ -705,6 +705,7 @@ static gboolean autocomplete_scope(GeanyEditor *editor, const gchar *root, gsize ScintillaObject *sci = editor->sci; gint pos = sci_get_current_position(editor->sci); gchar typed = sci_get_char_at(sci, pos - 1); + gchar brace_char; gchar *name; GeanyFiletype *ft = editor->document->file_type; GPtrArray *tags; @@ -748,18 +749,19 @@ static gboolean autocomplete_scope(GeanyEditor *editor, const gchar *root, gsize while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1))) pos--; - /* if function, skip to matching brace */ - if (pos > 0 && sci_get_char_at(sci, pos - 1) == ')') + /* if function or array index, skip to matching brace */ + brace_char = sci_get_char_at(sci, pos - 1); + if (pos > 0 && (brace_char == ')' || brace_char == ']')) { gint brace_pos = sci_find_matching_brace(sci, pos - 1); if (brace_pos != -1) { pos = brace_pos; - function = TRUE; + function = brace_char == ')'; } - /* allow for a space between opening brace and function name */ + /* allow for a space between opening brace and name */ while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1))) pos--; } From 5801844d7eb15a8de3b2dab96c50ace1ef89b75c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Wed, 9 Dec 2015 16:02:20 +0100 Subject: [PATCH 27/32] Improve anonymous type handling Make sure the anonymous types are from the same file as the variable of that type (or, when performing typedef resolution, from the same file as the typedef). On the way, simplify find_scope_members() a bit and fix some minor problems. --- tagmanager/src/tm_workspace.c | 78 ++++++++++++++--------------------- 1 file changed, 31 insertions(+), 47 deletions(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 7ee715acde..5da939f51e 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -848,24 +848,16 @@ find_scope_members_tags (const GPtrArray *all, TMTag *type_tag, gboolean namespa /* Gets all members of the type with the given name; search them inside tags_array */ static GPtrArray * -find_scope_members (const GPtrArray *tags_array, const char *name, langType lang, - gboolean namespace) +find_scope_members (const GPtrArray *tags_array, const gchar *type_name, TMSourceFile *file, + langType lang, gboolean namespace) { - gboolean has_members = FALSE; - gboolean typedef_struct = FALSE; - GPtrArray *tags = NULL; - TMTag *tag = NULL; - const gchar *type_name; guint i; - g_return_val_if_fail(name && *name, NULL); + g_return_val_if_fail(type_name && *type_name, NULL); - type_name = name; - - /* First check if type_name is a type that can possibly contain members. + /* Check if type_name is a type that can possibly contain members. * Try to resolve intermediate typedefs to get the real type name. Also - * add scope information to the name if applicable. The only result of this - * part is the TMTag tag and boolean has_members. + * add scope information to the name if applicable. * The loop below loops only when resolving typedefs - avoid possibly infinite * loop when typedefs create a cycle by adding some limits. */ for (i = 0; i < 5; i++) @@ -873,60 +865,52 @@ find_scope_members (const GPtrArray *tags_array, const char *name, langType lang guint j; GPtrArray *type_tags; TMTagType types = TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t; + TMTag *tag = NULL; if (!namespace) types &= ~tm_tag_enum_t; type_tags = g_ptr_array_new(); - if (tag && tag->file) - { - /* If we have tag, it means it contains the typedef from the previous - * iteration; search in its file first. This helps for - * "typedef struct {...}" cases where the typedef resolves to - * anon_struct_* and searching for it in the whole workspace returns - * too many (wrong) results. */ - fill_find_tags_array(type_tags, tag->file->tags_array, type_name, - NULL, types, lang); - typedef_struct = type_tags->len > 0; - } - if (type_tags->len == 0) - fill_find_tags_array(type_tags, tags_array, type_name, NULL, types, lang); + fill_find_tags_array(type_tags, tags_array, type_name, NULL, types, lang); - tag = NULL; for (j = 0; j < type_tags->len; j++) { - tag = TM_TAG(type_tags->pdata[j]); + TMTag *test_tag = TM_TAG(type_tags->pdata[j]); + + /* anonymous type defined in a different file than the variable - + * this isn't the type we are looking for */ + if (tm_tag_is_anon(test_tag) && (file != test_tag->file || test_tag->file == NULL)) + continue; + + tag = test_tag; + /* prefer non-typedef tags because we can be sure they contain members */ - if (tag->type != tm_tag_typedef_t) + if (test_tag->type != tm_tag_typedef_t) break; } g_ptr_array_free(type_tags, TRUE); if (!tag) /* not a type that can contain members */ - break; + return NULL; /* intermediate typedef - resolve to the real type */ - if (tag->type == tm_tag_typedef_t && tag->var_type && tag->var_type[0] != '\0') + if (tag->type == tm_tag_typedef_t) { - type_name = tag->var_type; - continue; + if (tag->var_type && tag->var_type[0] != '\0') + { + type_name = tag->var_type; + file = tag->file; + continue; + } + return NULL; } else /* real type with members */ - { - has_members = TRUE; - break; - } + /* use the same file as the composite type if file information available */ + return find_scope_members_tags(tag->file ? tag->file->tags_array : tags_array, tag, namespace); } - /* ignore anon_struct_* and similar unless we resolved a typedef to it within - * a single file so we can be sure we don't pick a wrong anon_struct_* from - * a different file */ - if (has_members && (typedef_struct || !tm_tag_is_anon(tag))) - /* use the same file as the composite type if file information available */ - tags = find_scope_members_tags(tag->file ? tag->file->tags_array : tags_array, tag, namespace); - - return tags; + return NULL; } @@ -996,7 +980,7 @@ find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, l TMTagType types = TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t; if (tag->type & types) /* type: namespace search */ - member_tags = find_scope_members(searched_array, tag->name, lang, TRUE); + member_tags = find_scope_members(searched_array, tag->name, tag->file, lang, TRUE); else if (tag->var_type) /* variable: scope search */ { /* The question now is whether we should use member tags (such as @@ -1012,7 +996,7 @@ find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, l /* remove pointers in case the type contains them */ g_strdelimit(tag_type, "*^", ' '); g_strstrip(tag_type); - member_tags = find_scope_members(searched_array, tag_type, lang, FALSE); + member_tags = find_scope_members(searched_array, tag_type, tag->file, lang, FALSE); g_free(tag_type); } } From f38068f04e9514269161a884df278b0af3e5c2c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Thu, 10 Dec 2015 16:50:32 +0100 Subject: [PATCH 28/32] Skip typedef resolution in namespace search if not needed When we already have a struct-like type in namespace search, we don't need any extra resolution - we already have the right type. Skip the whole typedef resolution in this case. --- tagmanager/src/tm_workspace.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 5da939f51e..a744d8c4a3 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -980,7 +980,13 @@ find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, l TMTagType types = TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t; if (tag->type & types) /* type: namespace search */ - member_tags = find_scope_members(searched_array, tag->name, tag->file, lang, TRUE); + { + if (tag->type & tm_tag_typedef_t) + member_tags = find_scope_members(searched_array, tag->name, tag->file, lang, TRUE); + else + member_tags = find_scope_members_tags(tag->file ? tag->file->tags_array : searched_array, + tag, TRUE); + } else if (tag->var_type) /* variable: scope search */ { /* The question now is whether we should use member tags (such as From cd1a58f0a503f2d18592ebd77a1a7fb104e863cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sun, 10 Jan 2016 17:42:53 +0100 Subject: [PATCH 29/32] Use language-specific context separator instead of hard-coded "::" This makes it possible to popup scope completion dialog for more languages. --- src/editor.c | 25 +++++++++++-------------- tagmanager/src/tm_tag.c | 3 ++- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/editor.c b/src/editor.c index b94c7f2475..49b23362da 100644 --- a/src/editor.c +++ b/src/editor.c @@ -713,6 +713,7 @@ static gboolean autocomplete_scope(GeanyEditor *editor, const gchar *root, gsize gboolean member; gboolean ret = FALSE; const gchar *current_scope; + const gchar *context_sep = tm_tag_context_separator(ft->lang); if (autocomplete_scope_shown) { @@ -727,21 +728,16 @@ static gboolean autocomplete_scope(GeanyEditor *editor, const gchar *root, gsize typed = sci_get_char_at(sci, pos - 1); } + /* make sure to keep in sync with similar checks below */ if (typed == '.') pos -= 1; - else if (ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP || - ft->id == GEANY_FILETYPES_PHP || ft->id == GEANY_FILETYPES_RUST) - { - if (match_last_chars(sci, pos, "::")) - pos-=2; - else if ((ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP) && - match_last_chars(sci, pos, "->")) - pos-=2; - else if (ft->id == GEANY_FILETYPES_CPP && match_last_chars(sci, pos, "->*")) - pos-=3; - else - return FALSE; - } + else if (match_last_chars(sci, pos, context_sep)) + pos -= strlen(context_sep); + else if ((ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP) && + match_last_chars(sci, pos, "->")) + pos -= 2; + else if (ft->id == GEANY_FILETYPES_CPP && match_last_chars(sci, pos, "->*")) + pos -= 3; else return FALSE; @@ -774,7 +770,8 @@ static gboolean autocomplete_scope(GeanyEditor *editor, const gchar *root, gsize pos -= strlen(name); while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1))) pos--; - member = match_last_chars(sci, pos, ".") || match_last_chars(sci, pos, "::") || + /* make sure to keep in sync with similar checks above */ + member = match_last_chars(sci, pos, ".") || match_last_chars(sci, pos, context_sep) || match_last_chars(sci, pos, "->") || match_last_chars(sci, pos, "->*"); if (symbols_get_current_scope(editor->document, ¤t_scope) == -1) diff --git a/tagmanager/src/tm_tag.c b/tagmanager/src/tm_tag.c index a4780b509a..a5ddeeeb81 100644 --- a/tagmanager/src/tm_tag.c +++ b/tagmanager/src/tm_tag.c @@ -1368,11 +1368,12 @@ void tm_tags_array_print(GPtrArray *tags, FILE *fp) */ gint tm_tag_scope_depth(const TMTag *t) { + const gchar *context_sep = tm_tag_context_separator(t->lang); gint depth; char *s; if(!(t && t->scope)) return 0; - for (s = t->scope, depth = 0; s; s = strstr(s, "::")) + for (s = t->scope, depth = 0; s; s = strstr(s, context_sep)) { ++ depth; ++ s; From f10747ae5ab668931706f864a12776662b294ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Mon, 18 Jan 2016 23:00:19 +0100 Subject: [PATCH 30/32] Remove duplicate names from scope search popup There may be duplicate tag names in the list e.g. when performing namespace search and overloaded methods are found. --- tagmanager/src/tm_workspace.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index a744d8c4a3..19cb6a32d3 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -1030,6 +1030,7 @@ tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, tm_tag_macro_with_arg_t | tm_tag_prototype_t; TMTagType tag_type = tm_tag_max_t & ~(function_types | tm_tag_enumerator_t | tm_tag_namespace_t | tm_tag_package_t); + TMTagAttrType sort_attr[] = {tm_tag_attr_name_t, 0}; if (function) tag_type = function_types; @@ -1052,6 +1053,8 @@ tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, g_ptr_array_free(tags, TRUE); + tm_tags_dedup(member_tags, sort_attr, FALSE); + return member_tags; } From c653741a3ce7ce1daf7da6cb44f3885e9ff151b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Tue, 19 Jan 2016 00:40:06 +0100 Subject: [PATCH 31/32] Fix member scope completion with nested members First, the search for existing type with the given scope should be done also for namespaces. Second, with the string operations we get no scope as empty string "" but the rest of TM functions expect scope to be set to NULL in such case. Fix that. --- tagmanager/src/tm_workspace.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 19cb6a32d3..4ee229ae6f 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -941,6 +941,7 @@ static gboolean member_at_method_scope(const GPtrArray *tags, const gchar *metho comps[len - 2] = NULL; cls_scope = g_strjoinv(sep, comps); comps[len - 2] = cls; + cls_scope = strlen(cls_scope) > 0 ? cls_scope : NULL; /* check whether member inside the class */ if (g_strcmp0(member_tag->scope, member_scope) == 0) @@ -949,7 +950,7 @@ static gboolean member_at_method_scope(const GPtrArray *tags, const gchar *metho GPtrArray *cls_tags = g_ptr_array_new(); /* check whether the class exists */ - fill_find_tags_array(cls_tags, src, cls, cls_scope, TM_TYPE_WITH_MEMBERS, lang); + fill_find_tags_array(cls_tags, src, cls, cls_scope, TM_TYPE_WITH_MEMBERS | tm_tag_namespace_t, lang); ret = cls_tags->len > 0; g_ptr_array_free(cls_tags, TRUE); } From 6b2e99d0670ab349ec1e6f7d4f24cece6cc33720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Wed, 27 Jan 2016 01:06:56 +0100 Subject: [PATCH 32/32] Remove scope prefix of variable types We only perform search based on variable name so if a variable is e.g. of the type std::Foo, we can drop the std:: prefix and search only for the Foo type. --- tagmanager/src/tm_workspace.c | 48 +++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/tagmanager/src/tm_workspace.c b/tagmanager/src/tm_workspace.c index 4ee229ae6f..cc128a8ce6 100644 --- a/tagmanager/src/tm_workspace.c +++ b/tagmanager/src/tm_workspace.c @@ -846,14 +846,37 @@ find_scope_members_tags (const GPtrArray *all, TMTag *type_tag, gboolean namespa } +static gchar *strip_type(const gchar *scoped_name, langType lang) +{ + if (scoped_name != NULL) + { + /* remove scope prefix */ + const gchar *sep = tm_tag_context_separator(lang); + const gchar *base = g_strrstr(scoped_name, sep); + gchar *name = base ? g_strdup(base + strlen(sep)) : g_strdup(scoped_name); + + /* remove pointers */ + g_strdelimit(name, "*^", ' '); + g_strstrip(name); + + return name; + } + return NULL; +} + + /* Gets all members of the type with the given name; search them inside tags_array */ static GPtrArray * -find_scope_members (const GPtrArray *tags_array, const gchar *type_name, TMSourceFile *file, +find_scope_members (const GPtrArray *tags_array, const gchar *name, TMSourceFile *file, langType lang, gboolean namespace) { + GPtrArray *res = NULL; + gchar *type_name; guint i; - g_return_val_if_fail(type_name && *type_name, NULL); + g_return_val_if_fail(name && *name, NULL); + + type_name = g_strdup(name); /* Check if type_name is a type that can possibly contain members. * Try to resolve intermediate typedefs to get the real type name. Also @@ -892,25 +915,31 @@ find_scope_members (const GPtrArray *tags_array, const gchar *type_name, TMSourc g_ptr_array_free(type_tags, TRUE); if (!tag) /* not a type that can contain members */ - return NULL; + break; /* intermediate typedef - resolve to the real type */ if (tag->type == tm_tag_typedef_t) { if (tag->var_type && tag->var_type[0] != '\0') { - type_name = tag->var_type; + g_free(type_name); + type_name = strip_type(tag->var_type, tag->lang); file = tag->file; continue; } - return NULL; + break; } else /* real type with members */ + { /* use the same file as the composite type if file information available */ - return find_scope_members_tags(tag->file ? tag->file->tags_array : tags_array, tag, namespace); + res = find_scope_members_tags(tag->file ? tag->file->tags_array : tags_array, tag, namespace); + break; + } } - return NULL; + g_free(type_name); + + return res; } @@ -998,11 +1027,8 @@ find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, l if (!(tag->type & member_types) || member || member_at_method_scope(tags, current_scope, tag, lang)) { - gchar *tag_type = g_strdup(tag->var_type); + gchar *tag_type = strip_type(tag->var_type, tag->lang); - /* remove pointers in case the type contains them */ - g_strdelimit(tag_type, "*^", ' '); - g_strstrip(tag_type); member_tags = find_scope_members(searched_array, tag_type, tag->file, lang, FALSE); g_free(tag_type); }