From 932dc71fe2edc0c58b15aface319d38855c5e612 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sun, 10 Jan 2016 12:41:59 +0100 Subject: [PATCH] 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 7a6148b2f..b82e5d7e3 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 0785a0a1f..da0963025 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 a6bf0d0c7..bf2e6b2ff 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); -- 2.11.4.GIT