[geany/geany] d44f06: Merge pull request #406 from techee/multi_tag_goto

Colomban Wendling git-noreply at xxxxx
Sun Feb 28 12:30:07 UTC 2016


Branch:      refs/heads/master
Author:      Colomban Wendling <ban at herbesfolles.org>
Committer:   Colomban Wendling <ban at herbesfolles.org>
Date:        Sun, 28 Feb 2016 12:30:07 UTC
Commit:      d44f06a8b5bf25dd569b73c6c91fab670e2b0f16
             https://github.com/geany/geany/commit/d44f06a8b5bf25dd569b73c6c91fab670e2b0f16

Log Message:
-----------
Merge pull request #406 from techee/multi_tag_goto

If more tags are found during tag definition/declaration goto, let the
user select which one to use.


Modified Paths:
--------------
    src/symbols.c

Modified: src/symbols.c
361 lines changed, 288 insertions(+), 73 deletions(-)
===================================================================
@@ -326,66 +326,6 @@ const gchar *symbols_get_context_separator(gint ft_id)
 }
 
 
-/* Note: if tags is sorted, we can use bsearch or tm_tags_find() to speed this up. */
-static TMTag *
-symbols_find_tm_tag(const GPtrArray *tags, const gchar *tag_name)
-{
-	guint i;
-	g_return_val_if_fail(tags != NULL, NULL);
-
-	for (i = 0; i < tags->len; ++i)
-	{
-		if (utils_str_equal(TM_TAG(tags->pdata[i])->name, tag_name))
-			return TM_TAG(tags->pdata[i]);
-	}
-	return NULL;
-}
-
-
-static TMTag *find_source_file_tag(GPtrArray *tags_array,
-		const gchar *tag_name, guint type)
-{
-	GPtrArray *tags;
-	TMTag *tmtag;
-
-	tags = tm_tags_extract(tags_array, type);
-	if (tags != NULL)
-	{
-		tmtag = symbols_find_tm_tag(tags, tag_name);
-
-		g_ptr_array_free(tags, TRUE);
-
-		if (tmtag != NULL)
-			return tmtag;
-	}
-	return NULL;	/* not found */
-}
-
-
-static TMTag *find_workspace_tag(const gchar *tag_name, guint type)
-{
-	guint j;
-	const GPtrArray *source_files = NULL;
-
-	if (app->tm_workspace != NULL)
-		source_files = app->tm_workspace->source_files;
-
-	if (source_files != NULL)
-	{
-		for (j = 0; j < source_files->len; j++)
-		{
-			TMSourceFile *srcfile = source_files->pdata[j];
-			TMTag *tmtag;
-
-			tmtag = find_source_file_tag(srcfile->tags_array, tag_name, type);
-			if (tmtag != NULL)
-				return tmtag;
-		}
-	}
-	return NULL;	/* not found */
-}
-
-
 const gchar **symbols_get_html_entities(void)
 {
 	if (html_entities == NULL)
@@ -1888,27 +1828,278 @@ static void load_user_tags(filetype_id ft_id)
 }
 
 
+static void on_goto_popup_item_activate(GtkMenuItem *item, TMTag *tag)
+{
+	GeanyDocument *new_doc, *old_doc;
+
+	g_return_if_fail(tag);
+
+	old_doc = document_get_current();
+	new_doc = document_open_file(tag->file->file_name, FALSE, NULL, NULL);
+
+	if (new_doc)
+		navqueue_goto_line(old_doc, new_doc, tag->line);
+}
+
+
+/* FIXME: use the same icons as in the symbols tree defined in add_top_level_items() */
+static guint get_tag_class(const TMTag *tag)
+{
+	switch (tag->type)
+	{
+		case tm_tag_prototype_t:
+		case tm_tag_method_t:
+		case tm_tag_function_t:
+			return ICON_METHOD;
+		case tm_tag_variable_t:
+		case tm_tag_externvar_t:
+			return ICON_VAR;
+		case tm_tag_macro_t:
+		case tm_tag_macro_with_arg_t:
+			return ICON_MACRO;
+		case tm_tag_class_t:
+			return ICON_CLASS;
+		case tm_tag_member_t:
+		case tm_tag_field_t:
+			return ICON_MEMBER;
+		case tm_tag_typedef_t:
+		case tm_tag_enum_t:
+		case tm_tag_union_t:
+		case tm_tag_struct_t:
+			return ICON_STRUCT;
+		case tm_tag_namespace_t:
+		case tm_tag_package_t:
+			return ICON_NAMESPACE;
+		default:
+			break;
+	}
+	return ICON_STRUCT;
+}
+
+
+/* positions a popup at the caret from the ScintillaObject in @p data */
+static void goto_popup_position_func(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
+{
+	gint line_height;
+	GdkScreen *screen = gtk_widget_get_screen(GTK_WIDGET(menu));
+	gint monitor_num;
+	GdkRectangle monitor;
+	GtkRequisition req;
+	GdkEventButton *event_button = g_object_get_data(G_OBJECT(menu), "geany-button-event");
+
+	if (event_button)
+	{
+		/* if we got a mouse click, popup at that position */
+		*x = (gint) event_button->x_root;
+		*y = (gint) event_button->y_root;
+		line_height = 0; /* we don't want to offset below the line or anything */
+	}
+	else /* keyboard positioning */
+	{
+		ScintillaObject *sci = data;
+		GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(sci));
+		gint pos = sci_get_current_position(sci);
+		gint line = sci_get_line_from_position(sci, pos);
+		gint pos_x = scintilla_send_message(sci, SCI_POINTXFROMPOSITION, 0, pos);
+		gint pos_y = scintilla_send_message(sci, SCI_POINTYFROMPOSITION, 0, pos);
+
+		line_height = scintilla_send_message(sci, SCI_TEXTHEIGHT, line, 0);
+
+		gdk_window_get_origin(window, x, y);
+		*x += pos_x;
+		*y += pos_y;
+	}
+
+	monitor_num = gdk_screen_get_monitor_at_point(screen, *x, *y);
+
+#if GTK_CHECK_VERSION(3, 0, 0)
+	gtk_widget_get_preferred_size(GTK_WIDGET(menu), NULL, &req);
+#else
+	gtk_widget_size_request(GTK_WIDGET(menu), &req);
+#endif
+
+#if GTK_CHECK_VERSION(3, 4, 0)
+	gdk_screen_get_monitor_workarea(screen, monitor_num, &monitor);
+#else
+	gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor);
+#endif
+
+	/* put on one size of the X position, but within the monitor */
+	if (gtk_widget_get_direction(GTK_WIDGET(menu)) == GTK_TEXT_DIR_RTL)
+	{
+		if (*x - req.width - 1 >= monitor.x)
+			*x -= req.width + 1;
+		else if (*x + req.width > monitor.x + monitor.width)
+			*x = monitor.x;
+		else
+			*x += 1;
+	}
+	else
+	{
+		if (*x + req.width + 1 <= monitor.x + monitor.width)
+			*x = MAX(monitor.x, *x + 1);
+		else if (*x - req.width - 1 >= monitor.x)
+			*x -= req.width + 1;
+		else
+			*x = monitor.x + MAX(0, monitor.width - req.width);
+	}
+
+	/* try to put, in order:
+	 * 1. below the Y position, under the line
+	 * 2. above the Y position
+	 * 3. within the monitor */
+	if (*y + line_height + req.height <= monitor.y + monitor.height)
+		*y = MAX(monitor.y, *y + line_height);
+	else if (*y - req.height >= monitor.y)
+		*y = *y - req.height;
+	else
+		*y = monitor.y + MAX(0, monitor.height - req.height);
+
+	*push_in = FALSE;
+}
+
+
+static void show_goto_popup(GeanyDocument *doc, GPtrArray *tags, gboolean have_best)
+{
+	GtkWidget *first = NULL;
+	GtkWidget *menu;
+	GdkEvent *event;
+	GdkEventButton *button_event = NULL;
+	TMTag *tmtag;
+	guint i;
+
+	menu = gtk_menu_new();
+
+	foreach_ptr_array(tmtag, i, tags)
+	{
+		GtkWidget *item;
+		GtkWidget *label;
+		GtkWidget *image;
+		gchar *fname = g_path_get_basename(tmtag->file->file_name);
+		gchar *text;
+
+		if (! first && have_best)
+			/* For translators: it's the filename and line number of a tag in the goto-tag popup menu */
+			text = g_markup_printf_escaped(_("<b>%s: %lu</b>"), fname, tmtag->line);
+		else
+			/* For translators: it's the filename and line number of a tag in the goto-tag popup menu */
+			text = g_markup_printf_escaped(_("%s: %lu"), fname, tmtag->line);
+
+		image = gtk_image_new_from_pixbuf(symbols_icons[get_tag_class(tmtag)].pixbuf);
+		label = g_object_new(GTK_TYPE_LABEL, "label", text, "use-markup", TRUE, "xalign", 0.0, NULL);
+		item = g_object_new(GTK_TYPE_IMAGE_MENU_ITEM, "image", image, "child", label, NULL);
+		g_signal_connect_data(item, "activate", G_CALLBACK(on_goto_popup_item_activate),
+		                      tm_tag_ref(tmtag), (GClosureNotify) tm_tag_unref, 0);
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+		if (! first)
+			first = item;
+
+		g_free(text);
+		g_free(fname);
+	}
+
+	gtk_widget_show_all(menu);
+
+	if (first) /* always select the first item for better keyboard navigation */
+		g_signal_connect(menu, "realize", G_CALLBACK(gtk_menu_shell_select_item), first);
+
+	event = gtk_get_current_event();
+	if (event && event->type == GDK_BUTTON_PRESS)
+		button_event = (GdkEventButton *) event;
+	else
+		gdk_event_free(event);
+
+	g_object_set_data_full(G_OBJECT(menu), "geany-button-event", button_event,
+	                       button_event ? (GDestroyNotify) gdk_event_free : NULL);
+	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, goto_popup_position_func, doc->editor->sci,
+				   button_event ? button_event->button : 0, gtk_get_current_event_time ());
+}
+
+
+static gint compare_tags_by_name_line(gconstpointer ptr1, gconstpointer ptr2)
+{
+	gint res;
+	TMTag *t1 = *((TMTag **) ptr1);
+	TMTag *t2 = *((TMTag **) ptr2);
+
+	res = g_strcmp0(t1->file->short_name, t2->file->short_name);
+	if (res != 0)
+		return res;
+	return t1->line - t2->line;
+}
+
+
+static TMTag *find_best_goto_tag(GeanyDocument *doc, GPtrArray *tags)
+{
+	TMTag *tag;
+	guint i;
+
+	/* first check if we have a tag in the current file */
+	foreach_ptr_array(tag, i, tags)
+	{
+		if (g_strcmp0(doc->real_path, tag->file->file_name) == 0)
+			return tag;
+	}
+
+	/* next check if we have a tag for some of the open documents */
+	foreach_ptr_array(tag, i, tags)
+	{
+		guint j;
+
+		foreach_document(j)
+		{
+			if (g_strcmp0(documents[j]->real_path, tag->file->file_name) == 0)
+				return tag;
+		}
+	}
+
+	/* next check if we have a tag for a file inside the current document's directory */
+	foreach_ptr_array(tag, i, tags)
+	{
+		gchar *dir = g_path_get_dirname(doc->real_path);
+
+		if (g_str_has_prefix(tag->file->file_name, dir))
+		{
+			g_free(dir);
+			return tag;
+		}
+		g_free(dir);
+	}
+
+	return NULL;
+}
+
+
 static gboolean goto_tag(const gchar *name, gboolean definition)
 {
 	const TMTagType forward_types = tm_tag_prototype_t | tm_tag_externvar_t;
 	TMTagType type;
 	TMTag *tmtag = NULL;
 	GeanyDocument *old_doc = document_get_current();
+	gboolean found = FALSE;
+	const GPtrArray *all_tags;
+	GPtrArray *workspace_tags;
+	guint i;
 
 	/* goto tag definition: all except prototypes / forward declarations / externs */
 	type = (definition) ? tm_tag_max_t - forward_types : forward_types;
+	all_tags = tm_workspace_find(name, NULL, type, NULL, old_doc->file_type->lang);
 
-	/* first look in the current document */
-	if (old_doc != NULL && old_doc->tm_file)
-		tmtag = find_source_file_tag(old_doc->tm_file->tags_array, name, type);
-
-	/* if not found, look in the workspace */
-	if (tmtag == NULL)
-		tmtag = find_workspace_tag(name, type);
+	/* get rid of global tags */
+	workspace_tags = g_ptr_array_new();
+	foreach_ptr_array(tmtag, i, all_tags)
+	{
+		if (tmtag->file)
+			g_ptr_array_add(workspace_tags, tmtag);
+	}
 
-	if (tmtag != NULL)
+	if (workspace_tags->len == 1)
 	{
-		GeanyDocument *new_doc = document_find_by_real_path(
+		GeanyDocument *new_doc;
+
+		tmtag = workspace_tags->pdata[0];
+		new_doc = document_find_by_real_path(
 			tmtag->file->file_name);
 
 		if (new_doc)
@@ -1918,7 +2109,7 @@ static gboolean goto_tag(const gchar *name, gboolean definition)
 				tmtag->line == (guint)sci_get_current_line(old_doc->editor->sci) + 1)
 			{
 				if (goto_tag(name, !definition))
-					return TRUE;
+					found = TRUE;
 			}
 		}
 		else
@@ -1927,10 +2118,34 @@ static gboolean goto_tag(const gchar *name, gboolean definition)
 			new_doc = document_open_file(tmtag->file->file_name, FALSE, NULL, NULL);
 		}
 
-		if (navqueue_goto_line(old_doc, new_doc, tmtag->line))
-			return TRUE;
+		if (!found && navqueue_goto_line(old_doc, new_doc, tmtag->line))
+			found = TRUE;
 	}
-	return FALSE;
+	else if (workspace_tags->len > 1)
+	{
+		GPtrArray *tags;
+		TMTag *tag, *best_tag;
+
+		g_ptr_array_sort(workspace_tags, compare_tags_by_name_line);
+		best_tag = find_best_goto_tag(old_doc, workspace_tags);
+
+		tags = g_ptr_array_new();
+		if (best_tag)
+			g_ptr_array_add(tags, best_tag);
+		foreach_ptr_array(tag, i, workspace_tags)
+		{
+			if (tag != best_tag)
+				g_ptr_array_add(tags, tag);
+		}
+		show_goto_popup(old_doc, tags, best_tag != NULL);
+
+		g_ptr_array_free(tags, TRUE);
+		found = TRUE;
+	}
+
+	g_ptr_array_free(workspace_tags, TRUE);
+
+	return found;
 }
 
 



--------------
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