[geany/geany] 5c18b3: Improve scoped search

Jiří Techet git-noreply at xxxxx
Thu Feb 11 14:35:49 UTC 2016


Branch:      refs/heads/master
Author:      Jiří Techet <techet at gmail.com>
Committer:   Jiří Techet <techet at gmail.com>
Date:        Tue, 14 Jul 2015 07:44:33 UTC
Commit:      5c18b3d1328b8d3dff923a2a02bc3cc6b41a333c
             https://github.com/geany/geany/commit/5c18b3d1328b8d3dff923a2a02bc3cc6b41a333c

Log Message:
-----------
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.


Modified Paths:
--------------
    src/editor.c
    tagmanager/src/tm_workspace.c
    tagmanager/src/tm_workspace.h

Modified: src/editor.c
60 lines changed, 41 insertions(+), 19 deletions(-)
===================================================================
@@ -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;
 }
 
 


Modified: tagmanager/src/tm_workspace.c
230 lines changed, 100 insertions(+), 130 deletions(-)
===================================================================
@@ -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;
 }


Modified: tagmanager/src/tm_workspace.h
8 lines changed, 3 insertions(+), 5 deletions(-)
===================================================================
@@ -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);
 



--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).


More information about the Commits mailing list