[geany/geany-plugins] c890cb: gproject: Lots of new features and improvements

Jiří Techet git-noreply at xxxxx
Thu Dec 11 20:00:49 UTC 2014


Branch:      refs/heads/master
Author:      Jiří Techet <techet at gmail.com>
Committer:   Jiří Techet <techet at gmail.com>
Date:        Thu, 11 Dec 2014 20:00:49 UTC
Commit:      c890cbc3b9ac81795f95dddc781e4d359cd01af5
             https://github.com/geany/geany-plugins/commit/c890cbc3b9ac81795f95dddc781e4d359cd01af5

Log Message:
-----------
gproject: Lots of new features and improvements

* Added the "external directory" feature
* Added find tag by name
* Added ignored file patterns
* Performance improvements in tag generation using updated Geany API
* Automatic tag generation for projects with less than 500 files (configurable)
* Follow active editor on by default
* Treat empty "file patterns" in project properties as * (everything)
* New toolbar icons
* Fixed missing icons under Windows (thanks to Enrico)
* Lots of minor iprovements, bugfixes (and probably new bug introductions)


Modified Paths:
--------------
    gproject/NEWS
    gproject/README
    gproject/icons/Makefile.am
    gproject/icons/gproject-add-external.png
    gproject/src/Makefile.am
    gproject/src/gproject-main.c
    gproject/src/gproject-menu.c
    gproject/src/gproject-project.c
    gproject/src/gproject-project.h
    gproject/src/gproject-sidebar.c
    gproject/src/gproject-sidebar.h
    gproject/src/gproject-utils.c
    gproject/wscript_build

Modified: gproject/NEWS
11 lines changed, 11 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,11 @@
+November 2014
+* Added the "external directory" feature
+* Added find tag by name
+* Added ignored file patterns
+* Performance improvements in tag generation using updated Geany API
+* Automatic tag generation for projects with less than 500 files (configurable)
+* Follow active editor on by default
+* Treat empty "file patterns" in project properties as * (everything)
+* New toolbar icons
+* Fixed missing icons under Windows (thanks to Enrico)
+* Lots of minor iprovements, bugfixes (and probably new bug introductions)


Modified: gproject/README
127 lines changed, 84 insertions(+), 43 deletions(-)
===================================================================
@@ -8,10 +8,14 @@ About
 =====
 
 GProject is an extension of Geany's project management displaying a tree of files
-belonging to the project in the sidebar. In addition, it enables quick swapping
-between header and source files, searching project files by name and more. The plugin
-was created with big projects in mind so everything works fast enough even with
-projects consisting of hundreds of thousands of files.
+belonging to the project in the sidebar. In addition, it enables complete indexing
+of the project files (and having code completion, syntax highlighting and tag 
+definition/declaration jumps for the whole project) quick swapping between header 
+and source files, improved opening of includes, searching project files by name 
+and more. External directories can be attached to the project to extend the plugin's
+functionality to related directories outside the project tree. The plugin was created
+with big projects in mind so everything works fast enough even with projects consisting
+of tens of thousands of files.
 
 Why was it created?
 -------------------
@@ -27,8 +31,10 @@ for every project is:
 This approach is fine for smaller projects where most of the project files
 are stored in a single directory but doesn't work very well with projects consisting
 of many deeply nested directories containing thousands of files. For such projects
-an expandable tree showing the project files is a better alternative making it much
-easier to to navigate between different project directories.
+an expandable tree showing all the project files is a better alternative making it much
+easier to to navigate among various project directories. Knowing which files
+belong to the project makes it possible to add other useful features like project file
+indexing, header/source swapping or file searching.
 
 Why are files belonging to a project defined by patterns?
 ---------------------------------------------------------
@@ -39,10 +45,11 @@ and moved quite frequently and after each change the project has to be updated m
 Instead, GProject defines a list of files belonging to the project implicitly using:
 
 * project's base directory
-* a list of glob-like patterns
+* a list of glob-like patterns (e.g. \*.c, \*.h, or just simply \* if you want to 
+  see everything)
 
 Every file under the base directory matching the patterns is included into the project
-and updating the file list is as simple as pressing the refresh button.
+and updating the file list is as simple as pressing the refresh button in the sidebar.
 
 What are the differences between GProject and GeanyPrj?
 -------------------------------------------------------
@@ -54,16 +61,14 @@ in several aspects:
   at the same time and vice versa. On the other hand, GeanyPrj project is a separate
   project so if you want to set build properties for a GeanyPrj project, you have to
   set up a second Geany project in parallel.
-* Because GeanyPrj is a separate project management plugin, it can do some things that
-  GProject cannot - in particular, it can manage several projects in parallel.
-  If you need to switch between several projects, GeanyPrj might be a better option
-  for you. Alternatively, you can open several Geany instances for different projects
-  to work on several projects in parallel using GProject.
+* GeanyPrj can display several projects in the sidebar; even though only a single project
+  can be opened with GProject at one time, similar effect can be achieved with the 
+  "external directories" feature.
 * GProject displays full tree in the sidebar while GeanyPrj displays only two-level
   tree (full directory name as a parent and a list of files under the directory).
-* GProject has configurable patterns while patterns in GeanyPrj are hard-coded
+* GProject has configurable file patterns while patterns in GeanyPrj are hard-coded
 * GProject offers header/source swapping
-* GProject offers finding project files by name
+* GProject offers finding project files by name and improved include file opening
 
 Usage
 =====
@@ -74,47 +79,67 @@ Project configuration
 Upon project creation, you should define the list of file patterns under the
 Project->Properties Project tab. For instance, for a typical open source C project,
 use patterns "\*.c \*h \*.am \*.ac" to see the source files together with
-automake and autoconf files. After closing the dialog, the files matching the patterns
+automake and autoconf files. If no patterns are defined (default), GProject treats 
+this as the "\*" pattern in which case all files under the project directory are
+displayed. After closing the dialog, the files matching the patterns
 should appear in the sidebar under the Project tab (unless the GProject plugin
-was loaded after the project - see Known Issues for more details).
+was loaded for the first time and Geany project was already open - see Known Issues 
+for more details).
 
-Additional settings is available from the GProject tab under the Project properties
+Additional settings are available from the GProject tab under the Project properties
 dialog. You can define patterns to distinguish between header and source files for
-C-like languages. This information is used for header/source swapping and also for
-giving different icons to headers and sources in the sidebar (therefore, this settings
-can be also used for other types of languages if you want to give different file
-types different icons).
+C-like languages. This information is used for header/source swapping.
 
-In addition, you can define patterns for directories that should be ignored when
-searching for files belonging to the project. These will typically be various
-VCS or hidden directories.
+In addition, you can define patterns for files and directories that should be ignored when
+searching for files belonging to the project. These will typically be various binary
+files and VCS or hidden directories.
 
 Finally, you can specify whether the tag manager should be used to index all the project
-files or not. This settings is turned off by default because the indexing takes too
-long when too many files are present in the project (several thousands or more).
+files or not. The default settings is Auto which means that if the total number
+of project (and external directory) files is less than 500, tags are generated.
+This is a rather conservative number, at least for an SSD disk - GProject was tested 
+with tens of thousands project files and even though the initial scanning may take 
+some time (for the linux kernel with 35000 files and 2300000 tags it takes about 
+20s with an SSD disk), the work with the project is completely normal afterwards.
+However, with ordinary HDD expect only around 100 scanned files per second because
+of slow random access time.
 
 Sidebar
 -------
 
-The sidebar contains a tree of files belonging to the project. Directories can be expanded
-by double-clicking them; the same action is used to open files. When a sidebar item
-is right-clicked, a context menu appears:
+The sidebar contains a tree of files belonging to the project and external directory
+trees (drawn with gray background to distinguish them from ordinary project files). 
+Directories can be expanded by double-clicking them; the same action is used to open 
+files. When a sidebar item is right-clicked, a context menu appears:
 
 * Expand all - recursively expands all the subdirectories of the given directory
 * Find in files - opens the Find in files dialog and sets the search directory
   to the clicked directory
 * Find file - opens the Find file dialog and sets the search directory to the
   clicked directory
+* Find tag - opens the Find tag dialog and sets the search directory to the
+  clicked directory
+* Remove external directory - removes a previously added external directory from
+  the project
 
 The following actions can be invoked from the sidebar's toolbar:
 
-* Reload all - reloads the project file tree. This is useful when files were added or
-  removed from the project.
+* Reload all - reloads the project file tree and reindexes the files (if tag generation
+  enabled). This is useful when files were added, modified externally or removed from 
+  the project.
+* Add external directory - adds an additional directory related to the project (e.g.
+  it is useful to have the geany project as an external directory for the geany-plugins 
+  project). External directories are indexed using the tag manager, and basically 
+  all GProject features work with external directories too (find file or find in files
+  from the context menu, swap header/source, open selected file, tag definition/declaration
+  jumps, and active editor following). Apart from adding related projects, one of the
+  possible uses is the addition of system header directories, e.g. /usr/include/gtk-2.0,
+  and having them indexed for code completion and syntax highlighting.
 * Expand all - recursively expands all the directories
-* Collapse all - recursively collapses all the directories
+* Collapse to project root - collapses all the directories except for the project root
 * Follow active editor - automatically selects the current file in the sidebar
   when the user switches to another file. It auto-expands the tree to reveal the selected
-  file.
+  file. On by default.
 
 Project menu entries
 --------------------
@@ -123,8 +148,10 @@ GProject adds some extra entries under the Project menu:
 
 * Find in Project Files - opens the Find in files dialog and sets the search directory
   to the base directory of the project
-* Find Project File - opens the Find file dialog and sets the search directory to the
-  base directory of the project
+* Find Project File - opens the Find file dialog which can be used to find files
+  within the project or external directories
+* Find Project Tag - opens the Find tag dialog which can be used to find tags
+  within the project or external directories
 * Swap Header/Source - if the current file matches one of the source patterns from
   the properties, it opens a project file with the same base name (without extension)
   matching header patterns (and vice versa). If the files are already open, it
@@ -145,13 +172,27 @@ The following search properties are configurable:
 * Search in full path - when not checked, the search is performed in the file
   name only (excluding path); when checked, the search is performed in the full path
 
+Find tag dialog
+----------------
+
+The Find tag dialog can be invoked either from the Project menu or from the
+sidebar's context menu. Searches are performed within the "Search inside" directory.
+There are several search types:
+
+* prefix (default) - finds all tags with the specified prefix
+* exact - finds all tags matching the name exactly
+* pattern - finds all tags matching the provided glob pattern
+
+By default, tag definitions are searched; to search tag declarations, select the 
+Declaration option.
+
 Editor context menu
 -------------------
 
 GProject adds an extra entry into the context menu of the editor:
 
 * Open Selected File (GProject) - contrary to the Open Selected File entry present
-  in Geany it also searches for the file in project files
+  in Geany it also searches for the file in project files and external directories.
 
 Known issues
 ============
@@ -159,12 +200,11 @@ Known issues
 * When the plugin is loaded from the Plugin Manager for the first time and a project
   is already open, no files are displayed in the sidebar. The workaround is to
   reload the project in this case. The issue is caused by Geany only passing the project
-  configuration file to a plugin when the project is loaded, which isn't the case when
+  configuration file to a plugin when the project is opened, which isn't the case when
   the plugin is loaded.
 * There might be some issues with UTF8 file names - the testing has been very
   limited.
-* GProject hasn't been tested under Windows so probably not everything works there.
-  Patches are welcome.
+* GProject hasn't been tested under Windows so there might be issues. Patches are welcome.
 
 License
 =======
@@ -192,8 +232,9 @@ Get the code from::
 Ideas, questions, patches and bug reports
 =========================================
 
-If you add something, or fix a bug, please send a patch (in 'diff -u'
-format) to the geany mailing list or to one of the authors listed bellow.
+Please direct all questions, bug reports and patches to the plugin author using the
+email address listed below or to the Geany mailing list to get some help from other
+Geany users.
 
-2010-2011 by Jiří Techet
+2010-2014 by Jiří Techet
 techet(at)gmail(dot)com


Modified: gproject/icons/Makefile.am
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -7,6 +7,7 @@ dist_icon_DATA = \
 	gproject-expand.png	\
 	gproject-collapse.png	\
 	gproject-follow.png	\
+	gproject-add-external.png	\
 	gproject-refresh.png
 
 gtk_update_icon_cache = gtk-update-icon-cache -f -t $(datadir)/icons/hicolor


Modified: gproject/icons/gproject-add-external.png
0 lines changed, 0 insertions(+), 0 deletions(-)
===================================================================
No diff available, check online


Modified: gproject/src/Makefile.am
3 lines changed, 3 insertions(+), 0 deletions(-)
===================================================================
@@ -13,6 +13,9 @@ gproject_la_SOURCES = \
 	gproject-menu.h \
 	gproject-menu.c
 
+gproject_la_CPPFLAGS = $(AM_CPPFLAGS) \
+	-DPLUGIN=\"$(plugin)\" \
+	-DG_LOG_DOMAIN=\"GProject\"
 gproject_la_CFLAGS = $(AM_CFLAGS)
 gproject_la_LIBADD = $(COMMONLIBS)
 


Modified: gproject/src/gproject-main.c
18 lines changed, 10 insertions(+), 8 deletions(-)
===================================================================
@@ -23,17 +23,14 @@
 #include <sys/time.h>
 #include <string.h>
 
-#ifdef HAVE_CONFIG_H
-	#include "config.h"
-#endif
 #include <geanyplugin.h>
 
 #include "gproject-project.h"
 #include "gproject-sidebar.h"
 #include "gproject-menu.h"
 
-PLUGIN_VERSION_CHECK(221)
-PLUGIN_SET_INFO(_("GProject"),
+PLUGIN_VERSION_CHECK(214)
+PLUGIN_SET_INFO("GProject",
 	_("Glob-pattern-based project management plugin for Geany."),
 	VERSION,
 	"Jiri Techet <techet at gmail.com>")
@@ -51,9 +48,8 @@ static void on_doc_open(G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GeanyDocument
 {
 	g_return_if_fail(doc != NULL && doc->file_name != NULL);
 
-	/* tags of open files managed by geany*/
 	if (gprj_project_is_in_project(doc->file_name))
-		gprj_project_remove_file_tag(doc->file_name);
+		gprj_project_remove_single_tm_file(doc->file_name);
 
 	gprj_sidebar_update(FALSE);
 }
@@ -77,7 +73,7 @@ static void on_doc_close(G_GNUC_UNUSED GObject * obj, GeanyDocument * doc,
 	/* tags of open files managed by geany - when the file gets closed, 
 	 * we should take care of it */
 	if (gprj_project_is_in_project(doc->file_name))
-		gprj_project_add_file_tag(doc->file_name);
+		gprj_project_add_single_tm_file(doc->file_name);
 
 	gprj_sidebar_update(FALSE);
 }
@@ -194,3 +190,9 @@ void plugin_cleanup(void)
 	gprj_menu_cleanup();
 	gprj_sidebar_cleanup();
 }
+
+
+void plugin_help (void)
+{
+	utils_open_browser (DOCDIR "/" PLUGIN "/README");
+}


Modified: gproject/src/gproject-menu.c
108 lines changed, 69 insertions(+), 39 deletions(-)
===================================================================
@@ -42,11 +42,12 @@ enum
 	KB_SWAP_HEADER_SOURCE,
 	KB_FIND_IN_PROJECT,
 	KB_FIND_FILE,
+	KB_FIND_TAG,
 	KB_COUNT
 };
 
 
-static GtkWidget *s_fif_item, *s_ff_item, *s_shs_item, *s_sep_item, *s_context_osf_item, *s_context_sep_item;
+static GtkWidget *s_fif_item, *s_ff_item, *s_ft_item, *s_shs_item, *s_sep_item, *s_context_osf_item, *s_context_sep_item;
 
 
 static gboolean try_swap_header_source(gchar *file_name, gboolean is_header, GSList *file_list, GSList *header_patterns, GSList *source_patterns)
@@ -63,7 +64,7 @@ static gboolean try_swap_header_source(gchar *file_name, gboolean is_header, GSL
 	pattern = g_pattern_spec_new(name_pattern);
 	g_free(name_pattern);
 
-	for (elem = file_list; elem != NULL; elem = g_slist_next(elem))
+	foreach_slist (elem, file_list)
 	{
 		gchar *full_name = elem->data;
 		base_name = g_path_get_basename(full_name);
@@ -87,12 +88,6 @@ static gboolean try_swap_header_source(gchar *file_name, gboolean is_header, GSL
 }
 
 
-static void get_project_file_list(char *file_name, G_GNUC_UNUSED gpointer value, GSList **list)
-{
-	*list = g_slist_prepend(*list, file_name);
-}
-
-
 static void on_swap_header_source(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer user_data)
 {
 	GSList *header_patterns, *source_patterns;
@@ -143,7 +138,7 @@ static void on_swap_header_source(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_U
 			SETPTR(doc_dir, utils_get_locale_from_utf8(doc_dir));
 
 			list = utils_get_file_list(doc_dir, NULL, NULL);
-			for (elem = list; elem != NULL; elem = g_slist_next(elem))
+			foreach_list (elem, list)
 			{
 				gchar *full_name;
 
@@ -160,9 +155,21 @@ static void on_swap_header_source(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_U
 
 		if (!swapped)
 		{
-			g_hash_table_foreach(g_prj->file_tag_table, (GHFunc) get_project_file_list, &list);
-			try_swap_header_source(doc->file_name, is_header, list, header_patterns, source_patterns);
-			g_slist_free(list);
+			foreach_slist(elem, g_prj->roots)
+			{
+				GHashTableIter iter;
+				gpointer key, value;
+				GPrjRoot *root = elem->data;
+				
+				list = NULL;
+				g_hash_table_iter_init(&iter, root->file_table);
+				while (g_hash_table_iter_next(&iter, &key, &value))
+					list = g_slist_prepend(list, key);
+				swapped = try_swap_header_source(doc->file_name, is_header, list, header_patterns, source_patterns);
+				g_slist_free(list);
+				if (swapped)
+					break;
+			}
 		}
 	}
 
@@ -184,11 +191,18 @@ static void on_find_in_project(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUS
 
 static void on_find_file(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer user_data)
 {
-        if (geany_data->app->project)
+	if (geany_data->app->project)
 		gprj_sidebar_find_file_in_active();
 }
 
 
+static void on_find_tag(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer user_data)
+{
+	if (geany_data->app->project)
+		gprj_sidebar_find_tag_in_active();
+}
+
+
 static gboolean kb_callback(guint key_id)
 {
 	switch (key_id)
@@ -202,31 +216,14 @@ static gboolean kb_callback(guint key_id)
 		case KB_FIND_FILE:
 			on_find_file(NULL, NULL);
 			return TRUE;
+		case KB_FIND_TAG:
+			on_find_tag(NULL, NULL);
+			return TRUE;
 	}
 	return FALSE;
 }
 
 
-typedef struct
-{
-	gchar *subpath;
-	gchar *found_path;
-} FindData;
-
-
-static void find_name(char *file_name, G_GNUC_UNUSED gpointer value, FindData *data)
-{
-	gchar *pos;
-
-	if (data->found_path)
-		return;
-
-	pos = g_strrstr(file_name, data->subpath);
-	if (pos && (pos - file_name + strlen(data->subpath) == strlen(file_name)))
-		data->found_path = file_name;
-}
-
-
 static void on_open_selected_file(GtkMenuItem *menuitem, gpointer user_data)
 {
 	GeanyDocument *doc = document_get_current();
@@ -291,14 +288,35 @@ static void on_open_selected_file(GtkMenuItem *menuitem, gpointer user_data)
 
 		if (g_strcmp0(path, "") != 0)
 		{
-			FindData data;
+			GSList *elem;
+			gchar *found_path = NULL;
 
-			data.subpath = path;
-			data.found_path = NULL;
-			g_hash_table_foreach(g_prj->file_tag_table, (GHFunc)find_name, &data);
-			if (data.found_path)
+			foreach_slist (elem, g_prj->roots)
 			{
-				filename = g_strdup(data.found_path);
+				GPrjRoot *root = elem->data;
+				gpointer key, value;
+				GHashTableIter iter;
+				
+				g_hash_table_iter_init(&iter, root->file_table);
+				while (g_hash_table_iter_next(&iter, &key, &value))
+				{
+					gchar *file_name = key;
+					gchar *pos = g_strrstr(file_name, path);
+					
+					if (pos && (pos - file_name + strlen(path) == strlen(file_name)))
+					{
+						found_path = file_name;
+						break;
+					}
+				}
+				
+				if (found_path)
+					break;
+			}
+
+			if (found_path)
+			{
+				filename = g_strdup(found_path);
 				SETPTR(filename, utils_get_locale_from_utf8(filename));
 				if (!g_file_test(filename, G_FILE_TEST_EXISTS))
 				{
@@ -369,6 +387,16 @@ void gprj_menu_init(void)
 	keybindings_set_item(key_group, KB_FIND_FILE, NULL,
 		0, 0, "find_file", _("Find project file"), s_ff_item);
 
+	image = gtk_image_new_from_stock(GTK_STOCK_FIND, GTK_ICON_SIZE_MENU);
+	gtk_widget_show(image);
+	s_ft_item = gtk_image_menu_item_new_with_mnemonic(_("Find Project Tag"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(s_ft_item), image);
+	gtk_widget_show(s_ft_item);
+	gtk_container_add(GTK_CONTAINER(geany->main_widgets->project_menu), s_ft_item);
+	g_signal_connect((gpointer) s_ft_item, "activate", G_CALLBACK(on_find_tag), NULL);
+	keybindings_set_item(key_group, KB_FIND_TAG, NULL,
+		0, 0, "find_tag", _("Find project tag"), s_ft_item);
+
 	s_shs_item = gtk_menu_item_new_with_mnemonic(_("Swap Header/Source"));
 	gtk_widget_show(s_shs_item);
 	gtk_container_add(GTK_CONTAINER(geany->main_widgets->project_menu), s_shs_item);
@@ -394,6 +422,7 @@ void gprj_menu_activate_menu_items(gboolean activate)
 	gtk_widget_set_sensitive(s_context_osf_item, activate);
 	gtk_widget_set_sensitive(s_shs_item, activate);
 	gtk_widget_set_sensitive(s_ff_item, activate);
+	gtk_widget_set_sensitive(s_ft_item, activate);
 	gtk_widget_set_sensitive(s_fif_item, activate);
 }
 
@@ -402,6 +431,7 @@ void gprj_menu_cleanup(void)
 {
 	gtk_widget_destroy(s_fif_item);
 	gtk_widget_destroy(s_ff_item);
+	gtk_widget_destroy(s_ft_item);
 	gtk_widget_destroy(s_shs_item);
 	gtk_widget_destroy(s_sep_item);
 


Modified: gproject/src/gproject-project.c
620 lines changed, 405 insertions(+), 215 deletions(-)
===================================================================
@@ -37,122 +37,36 @@ typedef struct
 	GtkWidget *source_patterns;
 	GtkWidget *header_patterns;
 	GtkWidget *ignored_dirs_patterns;
-	GtkWidget *generate_tags;
+	GtkWidget *ignored_file_patterns;
+	GtkWidget *generate_tag_prefs;
 } PropertyDialogElements;
 
 GPrj *g_prj = NULL;
 
 static PropertyDialogElements *e;
 
-typedef enum {DeferredTagOpAdd, DeferredTagOpRemove} DeferredTagOpType;
+static GSList *s_idle_add_funcs;
+static GSList *s_idle_remove_funcs;
 
-typedef struct
-{
-	gchar * filename;
-	DeferredTagOpType type;
-} DeferredTagOp;
-
-static GSList *file_tag_deferred_op_queue = NULL;
-static gboolean flush_queued = FALSE;
-
-
-static void deferred_op_free(DeferredTagOp* op, G_GNUC_UNUSED gpointer user_data)
-{
-	g_free(op->filename);
-	g_free(op);
-}
 
-
-static void deferred_op_queue_clean(void)
+static void clear_idle_queue(GSList **queue)
 {
-	g_slist_foreach(file_tag_deferred_op_queue, (GFunc)deferred_op_free, NULL);
-	g_slist_free(file_tag_deferred_op_queue);
-	file_tag_deferred_op_queue = NULL;
-        flush_queued = FALSE;
-}
-
-
-static void workspace_add_file_tag(gchar *filename)
-{
-	TagObject *obj;
-
-	obj = g_hash_table_lookup(g_prj->file_tag_table, filename);
-	if (obj)
-	{
-		TMSourceFile *tm_obj = NULL;
-		
-		if (!document_find_by_filename(filename))
-		{
-			tm_obj = tm_source_file_new(filename, filetypes_detect_from_file(filename)->name);
-			if (tm_obj)
-				tm_workspace_add_source_file(tm_obj);
-		}
-
-		if (obj->tag)
-		{
-			tm_workspace_remove_source_file(obj->tag);
-			tm_source_file_free(obj->tag);
-		}
-
-		obj->tag = tm_obj;
-	}
-}
-
-
-static void workspace_remove_file_tag(gchar *filename)
-{
-	TagObject *obj;
-
-	obj = g_hash_table_lookup(g_prj->file_tag_table, filename);
-	if (obj && obj->tag)
-	{ 
-		tm_workspace_remove_source_file(obj->tag);
-		tm_source_file_free(obj->tag);
-		obj->tag = NULL;
-	}
+	g_slist_free_full(*queue, g_free);
+	*queue = NULL;
 }
 
 
-static void deferred_op_queue_dispatch(DeferredTagOp* op, G_GNUC_UNUSED gpointer user_data)
+static void collect_source_files(gchar *filename, TMSourceFile *sf, gpointer user_data)
 {
-	if (op->type == DeferredTagOpAdd)
-		workspace_add_file_tag(op->filename);
-	else if (op->type == DeferredTagOpRemove)
-		workspace_remove_file_tag(op->filename);
-}
-
-
-static gboolean deferred_op_queue_flush(G_GNUC_UNUSED gpointer data)
-{
-	g_slist_foreach(file_tag_deferred_op_queue,
-					(GFunc)deferred_op_queue_dispatch, NULL);
-	deferred_op_queue_clean();
-	flush_queued = FALSE;
-
-	return FALSE; /* returning false removes this callback; it is a one-shot */
-}
-
-
-static void deferred_op_queue_enqueue(gchar* filename, DeferredTagOpType type)
-{
-	DeferredTagOp * op;
-
-	op = (DeferredTagOp *) g_new0(DeferredTagOp, 1);
-	op->type = type;
-	op->filename = g_strdup(filename);
-
-	file_tag_deferred_op_queue = g_slist_prepend(file_tag_deferred_op_queue,op);
-
-	if (!flush_queued)
-	{
-		flush_queued = TRUE;
-		plugin_idle_add(geany_plugin, (GSourceFunc)deferred_op_queue_flush, NULL);
-	}
+	GPtrArray *array = user_data;
+	
+	if (sf != NULL)
+		g_ptr_array_add(array, sf);
 }
 
 
 /* path - absolute path in locale, returned list in locale */
-static GSList *get_file_list(const gchar * path, GSList *patterns, GSList *ignored_dirs_patterns)
+static GSList *get_file_list(const gchar * path, GSList *patterns, GSList *ignored_dirs_patterns, GSList *ignored_file_patterns)
 {
 	GSList *list = NULL;
 	GDir *dir;
@@ -182,14 +96,14 @@ static GSList *get_file_list(const gchar * path, GSList *patterns, GSList *ignor
 				continue;
 			}
 
-			lst = get_file_list(filename, patterns, ignored_dirs_patterns);
+			lst = get_file_list(filename, patterns, ignored_dirs_patterns, ignored_file_patterns);
 			if (lst)
 				list = g_slist_concat(list, lst);
 			g_free(filename);
 		}
 		else if (g_file_test(filename, G_FILE_TEST_IS_REGULAR))
 		{
-			if (patterns_match(patterns, name))
+			if (patterns_match(patterns, name) && !patterns_match(ignored_file_patterns, name))
 				list = g_slist_prepend(list, filename);
 			else
 				g_free(filename);
@@ -202,108 +116,164 @@ static GSList *get_file_list(const gchar * path, GSList *patterns, GSList *ignor
 }
 
 
-static void remove_all_tags()
+static gint gprj_project_rescan_root(GPrjRoot *root)
 {
-	GPtrArray *to_update;
-	GHashTableIter iter;
-	gpointer key, value;
+	GPtrArray *source_files;
+	GSList *pattern_list = NULL;
+	GSList *ignored_dirs_list = NULL;
+	GSList *ignored_file_list = NULL;
+	GSList *lst;
+	GSList *elem;
+	gint filenum = 0;
+
+	source_files = g_ptr_array_new();
+	g_hash_table_foreach(root->file_table, (GHFunc)collect_source_files, source_files);
+	tm_workspace_remove_source_files(source_files);
+	g_ptr_array_free(source_files, TRUE);
+	g_hash_table_remove_all(root->file_table);
 
-	g_hash_table_iter_init (&iter, g_prj->file_tag_table);
-	to_update = g_ptr_array_new();
-	while (g_hash_table_iter_next (&iter, &key, &value))
+	if (!geany_data->app->project->file_patterns || !geany_data->app->project->file_patterns[0])
 	{
-		TagObject *obj = value;
-		
-		g_ptr_array_add(to_update, obj->tag);
-		obj->tag = NULL;
+		gchar **all_pattern = g_strsplit ("*", " ", -1);
+		pattern_list = get_precompiled_patterns(all_pattern);
+		g_strfreev(all_pattern);
 	}
-	
-	tm_workspace_remove_source_files(to_update);
-	g_ptr_array_foreach(to_update, (GFunc)tm_source_file_free, NULL);
-	g_ptr_array_free(to_update, TRUE);
-}
-
+	else
+		pattern_list = get_precompiled_patterns(geany_data->app->project->file_patterns);
 
-static void add_all_tags()
-{
-	GPtrArray *to_update;
-	GHashTableIter iter;
-	gpointer key, value;
+	ignored_dirs_list = get_precompiled_patterns(g_prj->ignored_dirs_patterns);
+	ignored_file_list = get_precompiled_patterns(g_prj->ignored_file_patterns);
 
-	g_hash_table_iter_init (&iter, g_prj->file_tag_table);
-	to_update = g_ptr_array_new();
-	while (g_hash_table_iter_next (&iter, &key, &value))
+	lst = get_file_list(root->base_dir, pattern_list, ignored_dirs_list, ignored_file_list);
+	
+	foreach_slist(elem, lst)
 	{
-		TagObject *obj = value;
-		gchar *filename = key;
-		TMSourceFile *tm_obj = NULL;
-		 
-		if (!document_find_by_filename(filename))
-		{
-			tm_obj = tm_source_file_new(filename, filetypes_detect_from_file(filename)->name);
+		char *path;
 
-			if (tm_obj)
-				g_ptr_array_add(to_update, tm_obj);
+		path = tm_get_real_path(elem->data);
+		if (path)
+		{
+			SETPTR(path, utils_get_utf8_from_locale(path));
+			g_hash_table_insert(root->file_table, path, NULL);
+			filenum++;
 		}
-
-		obj->tag = tm_obj;
 	}
+
+	g_slist_foreach(lst, (GFunc) g_free, NULL);
+	g_slist_free(lst);
+
+	g_slist_foreach(pattern_list, (GFunc) g_pattern_spec_free, NULL);
+	g_slist_free(pattern_list);
+
+	g_slist_foreach(ignored_dirs_list, (GFunc) g_pattern_spec_free, NULL);
+	g_slist_free(ignored_dirs_list);
 	
-	tm_workspace_add_source_files(to_update);
-	g_ptr_array_free(to_update, TRUE);
+	return filenum;
 }
 
 
-void gprj_project_rescan(void)
+static gboolean match_basename(gconstpointer pft, gconstpointer user_data)
 {
-	GSList *pattern_list = NULL;
-	GSList *ignored_dirs_list = NULL;
-	GSList *lst;
-	GSList *elem;
+	const GeanyFiletype *ft = pft;
+	const gchar *base_filename = user_data;
+	gint j;
+	gboolean ret = FALSE;
 
-	if (!g_prj)
-		return;
-
-	if (g_prj->generate_tags)
-		remove_all_tags();
-	g_hash_table_destroy(g_prj->file_tag_table);
-	g_prj->file_tag_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+	if (G_UNLIKELY(ft->id == GEANY_FILETYPES_NONE))
+		return FALSE;
 
-	deferred_op_queue_clean();
+	for (j = 0; ft->pattern[j] != NULL; j++)
+	{
+		GPatternSpec *pattern = g_pattern_spec_new(ft->pattern[j]);
 
-	pattern_list = get_precompiled_patterns(geany_data->app->project->file_patterns);
+		if (g_pattern_match_string(pattern, base_filename))
+		{
+			ret = TRUE;
+			g_pattern_spec_free(pattern);
+			break;
+		}
+		g_pattern_spec_free(pattern);
+	}
+	return ret;
+}
 
-	ignored_dirs_list = get_precompiled_patterns(g_prj->ignored_dirs_patterns);
 
-	lst = get_file_list(geany_data->app->project->base_path, pattern_list, ignored_dirs_list);
+/* Stolen and modified version from Geany. The only difference is that Geany
+ * first looks at shebang inside the file and then, if it fails, checks the
+ * file extension. Opening every file is too expensive so instead check just
+ * extension and only if this fails, look at the shebang */
+static GeanyFiletype *filetypes_detect(const gchar *utf8_filename)
+{
+	guint i;
+	gchar *base_filename;
+	GeanyFiletype *ft = NULL;
+
+	/* to match against the basename of the file (because of Makefile*) */
+	base_filename = g_path_get_basename(utf8_filename);
+#ifdef G_OS_WIN32
+	/* use lower case basename */
+	SETPTR(base_filename, g_utf8_strdown(base_filename, -1));
+#endif
 
-	for (elem = lst; elem != NULL; elem = g_slist_next(elem))
+	for (i = 0; i < geany_data->filetypes_array->len; i++)
 	{
-		char *path;
-		TagObject *obj;
+		GeanyFiletype *ftype = filetypes[i];
 
-		obj = g_new0(TagObject, 1);
-		obj->tag = NULL;
-
-		path = tm_get_real_path(elem->data);
-		if (path)
+		if (match_basename(ftype, base_filename))
 		{
-			SETPTR(path, utils_get_utf8_from_locale(path));
-			g_hash_table_insert(g_prj->file_tag_table, path, obj);
+			ft = ftype;
+			break;
 		}
 	}
+	
+	if (ft == NULL)
+		ft = filetypes_detect_from_file(utf8_filename);
 
-	if (g_prj->generate_tags)
-		add_all_tags();
+	g_free(base_filename);
+	return ft;
+}
 
-	g_slist_foreach(lst, (GFunc) g_free, NULL);
-	g_slist_free(lst);
 
-	g_slist_foreach(pattern_list, (GFunc) g_pattern_spec_free, NULL);
-	g_slist_free(pattern_list);
+static void regenerate_tags(GPrjRoot *root, gpointer user_data)
+{
+	GHashTableIter iter;
+	gpointer key, value;
+	GPtrArray *source_files;
 
-	g_slist_foreach(ignored_dirs_list, (GFunc) g_pattern_spec_free, NULL);
-	g_slist_free(ignored_dirs_list);
+	source_files = g_ptr_array_new();
+	g_hash_table_iter_init(&iter, root->file_table);
+	while (g_hash_table_iter_next(&iter, &key, &value))
+	{
+		TMSourceFile *sf;
+		gchar *path = key;
+		
+		sf = tm_source_file_new(path, filetypes_detect(path)->name);
+		if (sf && !document_find_by_filename(path))
+			g_ptr_array_add(source_files, sf);
+		
+		g_hash_table_iter_replace(&iter, sf);
+	}
+	tm_workspace_add_source_files(source_files);
+	g_ptr_array_free(source_files, TRUE);
+}
+
+
+void gprj_project_rescan(void)
+{
+	GSList *elem;
+	gint filenum = 0;
+	
+	if (!g_prj)
+		return;
+
+	clear_idle_queue(&s_idle_add_funcs);
+	clear_idle_queue(&s_idle_remove_funcs);
+	
+	foreach_slist(elem, g_prj->roots)
+		filenum += gprj_project_rescan_root(elem->data);
+	
+	if (g_prj->generate_tag_prefs == GPrjTagYes || (g_prj->generate_tag_prefs == GPrjTagAuto && filenum < 500))
+		g_slist_foreach(g_prj->roots, (GFunc)regenerate_tags, NULL);
 }
 
 
@@ -311,7 +281,8 @@ static void update_project(
 	gchar **source_patterns,
 	gchar **header_patterns,
 	gchar **ignored_dirs_patterns,
-	gboolean generate_tags)
+	gchar **ignored_file_patterns,
+	GPrjTagPrefs generate_tag_prefs)
 {
 	if (g_prj->source_patterns)
 		g_strfreev(g_prj->source_patterns);
@@ -325,7 +296,11 @@ static void update_project(
 		g_strfreev(g_prj->ignored_dirs_patterns);
 	g_prj->ignored_dirs_patterns = g_strdupv(ignored_dirs_patterns);
 
-	g_prj->generate_tags = generate_tags;
+	if (g_prj->ignored_file_patterns)
+		g_strfreev(g_prj->ignored_file_patterns);
+	g_prj->ignored_file_patterns = g_strdupv(ignored_file_patterns);
+
+	g_prj->generate_tag_prefs = generate_tag_prefs;
 
 	gprj_project_rescan();
 }
@@ -333,6 +308,9 @@ static void update_project(
 
 void gprj_project_save(GKeyFile * key_file)
 {
+	GPtrArray *array;
+	GSList *elem, *lst;
+	
 	if (!g_prj)
 		return;
 
@@ -342,14 +320,91 @@ void gprj_project_save(GKeyFile * key_file)
 		(const gchar**) g_prj->header_patterns, g_strv_length(g_prj->header_patterns));
 	g_key_file_set_string_list(key_file, "gproject", "ignored_dirs_patterns",
 		(const gchar**) g_prj->ignored_dirs_patterns, g_strv_length(g_prj->ignored_dirs_patterns));
-	g_key_file_set_boolean(key_file, "gproject", "generate_tags", g_prj->generate_tags);
+	g_key_file_set_string_list(key_file, "gproject", "ignored_file_patterns",
+		(const gchar**) g_prj->ignored_file_patterns, g_strv_length(g_prj->ignored_file_patterns));
+	g_key_file_set_integer(key_file, "gproject", "generate_tag_prefs", g_prj->generate_tag_prefs);
+	
+	array = g_ptr_array_new();
+	lst = g_prj->roots->next;
+	foreach_slist (elem, lst)
+	{
+		GPrjRoot *root = elem->data;
+		g_ptr_array_add(array, root->base_dir);
+	}
+	g_key_file_set_string_list(key_file, "gproject", "external_dirs", (const gchar * const *)array->pdata, array->len);
+	g_ptr_array_free(array, TRUE);
+}
+
+
+static GPrjRoot *create_root(const gchar *base_dir)
+{
+	GPrjRoot *root = (GPrjRoot *) g_new0(GPrjRoot, 1);
+	root->base_dir = g_strdup(base_dir);
+	root->file_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GFreeFunc)tm_source_file_free);
+	return root;
+}
+
+
+static void close_root(GPrjRoot *root, gpointer user_data)
+{
+	GPtrArray *source_files;
+	
+	source_files = g_ptr_array_new();
+	g_hash_table_foreach(root->file_table, (GHFunc)collect_source_files, source_files);
+	tm_workspace_remove_source_files(source_files);
+	g_ptr_array_free(source_files, TRUE);
+	
+	g_hash_table_destroy(root->file_table);
+	g_free(root->base_dir);
+	g_free(root);
+}
+
+
+static gint root_comparator(GPrjRoot *a, GPrjRoot *b)
+{
+	return g_strcmp0(a->base_dir, b->base_dir);
+}
+
+
+void gprj_project_add_external_dir(const gchar *dirname)
+{
+	GPrjRoot *new_root = create_root(dirname);
+	if (g_slist_find_custom (g_prj->roots, new_root, (GCompareFunc)root_comparator) != NULL)
+	{
+		close_root(new_root, NULL);
+		return;
+	}
+	
+	GSList *lst = g_prj->roots->next;
+	lst = g_slist_prepend(lst, new_root);
+	lst = g_slist_sort(lst, (GCompareFunc)root_comparator);
+	g_prj->roots->next = lst;
+	
+	gprj_project_rescan();
+}
+
+
+void gprj_project_remove_external_dir(const gchar *dirname)
+{
+	GPrjRoot *test_root = create_root(dirname);
+	GSList *found = g_slist_find_custom (g_prj->roots, test_root, (GCompareFunc)root_comparator);
+	if (found != NULL)
+	{
+		GPrjRoot *found_root = found->data;
+		
+		g_prj->roots = g_slist_remove(g_prj->roots, found_root);
+		close_root(found_root, NULL);
+		gprj_project_rescan();
+	}
+	close_root(test_root, NULL);
 }
 
 
 void gprj_project_open(GKeyFile * key_file)
 {
-	gchar **source_patterns, **header_patterns, **ignored_dirs_patterns;
-	gboolean generate_tags;
+	gchar **source_patterns, **header_patterns, **ignored_dirs_patterns, **ignored_file_patterns, **external_dirs, **dir_ptr, *last_name;
+	gint generate_tag_prefs;
+	GSList *elem, *ext_list = NULL;
 
 	if (g_prj != NULL)
 		gprj_project_close();
@@ -359,32 +414,50 @@ void gprj_project_open(GKeyFile * key_file)
 	g_prj->source_patterns = NULL;
 	g_prj->header_patterns = NULL;
 	g_prj->ignored_dirs_patterns = NULL;
-	g_prj->generate_tags = FALSE;
-
-	g_prj->file_tag_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
-
-	deferred_op_queue_clean();
+	g_prj->ignored_file_patterns = NULL;
+	g_prj->generate_tag_prefs = GPrjTagAuto;
 
 	source_patterns = g_key_file_get_string_list(key_file, "gproject", "source_patterns", NULL, NULL);
 	if (!source_patterns)
-		source_patterns = g_strsplit("*.c *.C *.cpp *.cxx *.c++ *.cc", " ", -1);
+		source_patterns = g_strsplit("*.c *.C *.cpp *.cxx *.c++ *.cc *.m", " ", -1);
 	header_patterns = g_key_file_get_string_list(key_file, "gproject", "header_patterns", NULL, NULL);
 	if (!header_patterns)
-		header_patterns = g_strsplit("*.h *.H *.hpp *.hxx *.h++ *.hh *.m", " ", -1);
+		header_patterns = g_strsplit("*.h *.H *.hpp *.hxx *.h++ *.hh", " ", -1);
 	ignored_dirs_patterns = g_key_file_get_string_list(key_file, "gproject", "ignored_dirs_patterns", NULL, NULL);
 	if (!ignored_dirs_patterns)
 		ignored_dirs_patterns = g_strsplit(".* CVS", " ", -1);
-	generate_tags = utils_get_setting_boolean(key_file, "gproject", "generate_tags", FALSE);
+	ignored_file_patterns = g_key_file_get_string_list(key_file, "gproject", "ignored_file_patterns", NULL, NULL);
+	if (!ignored_file_patterns)
+		ignored_file_patterns = g_strsplit("*.o *.obj *.a *.lib *.so *.dll *.lo *.la *.class *.jar *.pyc *.mo *.gmo", " ", -1);
+	generate_tag_prefs = utils_get_setting_integer(key_file, "gproject", "generate_tag_prefs", GPrjTagAuto);
+
+	external_dirs = g_key_file_get_string_list(key_file, "gproject", "external_dirs", NULL, NULL);
+	foreach_strv (dir_ptr, external_dirs)
+		ext_list = g_slist_prepend(ext_list, *dir_ptr);
+	ext_list = g_slist_sort(ext_list, (GCompareFunc)g_strcmp0);
+	last_name = NULL;
+	foreach_slist (elem, ext_list)
+	{
+		if (g_strcmp0(last_name, elem->data) != 0)
+			g_prj->roots = g_slist_append(g_prj->roots, create_root(elem->data));
+		last_name = elem->data;
+	}
+	g_slist_free(ext_list);
+	/* the project directory is always first */
+	g_prj->roots = g_slist_prepend(g_prj->roots, create_root(geany_data->app->project->base_path));
 
 	update_project(
 		source_patterns,
 		header_patterns,
 		ignored_dirs_patterns,
-		generate_tags);
+		ignored_file_patterns,
+		generate_tag_prefs);
 
 	g_strfreev(source_patterns);
 	g_strfreev(header_patterns);
 	g_strfreev(ignored_dirs_patterns);
+	g_strfreev(ignored_file_patterns);
+	g_strfreev(external_dirs);
 }
 
 
@@ -408,19 +481,21 @@ static gchar **split_patterns(const gchar *str)
 
 void gprj_project_read_properties_tab(void)
 {
-	gchar **source_patterns, **header_patterns, **ignored_dirs_patterns;
+	gchar **source_patterns, **header_patterns, **ignored_dirs_patterns, **ignored_file_patterns;
 
 	source_patterns = split_patterns(gtk_entry_get_text(GTK_ENTRY(e->source_patterns)));
 	header_patterns = split_patterns(gtk_entry_get_text(GTK_ENTRY(e->header_patterns)));
 	ignored_dirs_patterns = split_patterns(gtk_entry_get_text(GTK_ENTRY(e->ignored_dirs_patterns)));
+	ignored_file_patterns = split_patterns(gtk_entry_get_text(GTK_ENTRY(e->ignored_file_patterns)));
 
 	update_project(
-		source_patterns, header_patterns, ignored_dirs_patterns,
-		gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(e->generate_tags)));
+		source_patterns, header_patterns, ignored_dirs_patterns, ignored_file_patterns,
+		gtk_combo_box_get_active(GTK_COMBO_BOX(e->generate_tag_prefs)));
 
 	g_strfreev(source_patterns);
 	g_strfreev(header_patterns);
 	g_strfreev(ignored_dirs_patterns);
+	g_strfreev(ignored_file_patterns);
 }
 
 
@@ -436,7 +511,7 @@ gint gprj_project_add_properties_tab(GtkWidget *notebook)
 
 	vbox = gtk_vbox_new(FALSE, 0);
 
-	table = gtk_table_new(3, 2, FALSE);
+	table = gtk_table_new(5, 2, FALSE);
 	gtk_table_set_row_spacings(GTK_TABLE(table), 5);
 	gtk_table_set_col_spacings(GTK_TABLE(table), 10);
 
@@ -446,7 +521,8 @@ gint gprj_project_add_properties_tab(GtkWidget *notebook)
 	ui_table_add_row(GTK_TABLE(table), 0, label, e->source_patterns, NULL);
 	ui_entry_add_clear_icon(GTK_ENTRY(e->source_patterns));
 	ui_widget_set_tooltip_text(e->source_patterns,
-		_("Space separated list of patterns that are used to identify source files."));
+		_("Space separated list of patterns that are used to identify source files. "
+		  "Used for header/source swapping."));
 	str = g_strjoinv(" ", g_prj->source_patterns);
 	gtk_entry_set_text(GTK_ENTRY(e->source_patterns), str);
 	g_free(str);
@@ -458,16 +534,28 @@ gint gprj_project_add_properties_tab(GtkWidget *notebook)
 	ui_table_add_row(GTK_TABLE(table), 1, label, e->header_patterns, NULL);
 	ui_widget_set_tooltip_text(e->header_patterns,
 		_("Space separated list of patterns that are used to identify headers. "
-		  "Used mainly for header/source swapping."));
+		  "Used for header/source swapping."));
 	str = g_strjoinv(" ", g_prj->header_patterns);
 	gtk_entry_set_text(GTK_ENTRY(e->header_patterns), str);
 	g_free(str);
 
-	label = gtk_label_new(_("Ignored dirs patterns:"));
+	label = gtk_label_new(_("Ignored file patterns:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+	e->ignored_file_patterns = gtk_entry_new();
+	ui_entry_add_clear_icon(GTK_ENTRY(e->ignored_file_patterns));
+	ui_table_add_row(GTK_TABLE(table), 2, label, e->ignored_file_patterns, NULL);
+	ui_widget_set_tooltip_text(e->ignored_file_patterns,
+		_("Space separated list of patterns that are used to identify files "
+		  "that are not displayed in the project tree."));
+	str = g_strjoinv(" ", g_prj->ignored_file_patterns);
+	gtk_entry_set_text(GTK_ENTRY(e->ignored_file_patterns), str);
+	g_free(str);
+
+	label = gtk_label_new(_("Ignored directory patterns:"));
 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
 	e->ignored_dirs_patterns = gtk_entry_new();
 	ui_entry_add_clear_icon(GTK_ENTRY(e->ignored_dirs_patterns));
-	ui_table_add_row(GTK_TABLE(table), 2, label, e->ignored_dirs_patterns, NULL);
+	ui_table_add_row(GTK_TABLE(table), 3, label, e->ignored_dirs_patterns, NULL);
 	ui_widget_set_tooltip_text(e->ignored_dirs_patterns,
 		_("Space separated list of patterns that are used to identify directories "
 		  "that are not scanned for source files."));
@@ -475,17 +563,24 @@ gint gprj_project_add_properties_tab(GtkWidget *notebook)
 	gtk_entry_set_text(GTK_ENTRY(e->ignored_dirs_patterns), str);
 	g_free(str);
 
-	gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 6);
-
-	e->generate_tags = gtk_check_button_new_with_label(_("Generate tags for all project files"));
-	ui_widget_set_tooltip_text(e->generate_tags,
+	label = gtk_label_new(_("Generate tags for all project files:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+	e->generate_tag_prefs = gtk_combo_box_text_new();
+	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(e->generate_tag_prefs), _("Auto (generate if less than 500 files)"));
+	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(e->generate_tag_prefs), _("Yes"));
+	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(e->generate_tag_prefs), _("No"));
+	gtk_combo_box_set_active(GTK_COMBO_BOX_TEXT(e->generate_tag_prefs), g_prj->generate_tag_prefs);
+	ui_table_add_row(GTK_TABLE(table), 4, label, e->generate_tag_prefs, NULL);
+	ui_widget_set_tooltip_text(e->generate_tag_prefs,
 		_("Generate tag list for all project files instead of only for the currently opened files. "
-		  "Too slow for big projects (>1000 files) and should be disabled in this case."));
-	gtk_box_pack_start(GTK_BOX(vbox), e->generate_tags, FALSE, FALSE, 6);
-	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e->generate_tags), g_prj->generate_tags);
+		  "Might be slow for big projects."));
+
+	gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 6);
 
 	hbox1 = gtk_hbox_new(FALSE, 0);
-	label = gtk_label_new(_("Note: set the patterns of files belonging to the project under the Project tab."));
+	label = gtk_label_new(_("Note: the patterns above affect only the sidebar and are not used in the Find in Files\n"
+	"dialog. You can further restrict the files belonging to the project by setting the\n"
+	"File Patterns under the Project tab (these are also used for the Find in Files dialog)."));
 	gtk_box_pack_start(GTK_BOX(hbox1), label, FALSE, FALSE, 0);
 	gtk_box_pack_start(GTK_BOX(vbox), hbox1, FALSE, FALSE, 6);
 
@@ -506,36 +601,131 @@ void gprj_project_close(void)
 	if (!g_prj)
 		return;  /* can happen on plugin reload */
 
-	if (g_prj->generate_tags)
-		remove_all_tags();
+	clear_idle_queue(&s_idle_add_funcs);
+	clear_idle_queue(&s_idle_remove_funcs);
 
-	deferred_op_queue_clean();
+	g_slist_foreach(g_prj->roots, (GFunc)close_root, NULL);
+	g_slist_free(g_prj->roots);
 
 	g_strfreev(g_prj->source_patterns);
 	g_strfreev(g_prj->header_patterns);
 	g_strfreev(g_prj->ignored_dirs_patterns);
-
-	g_hash_table_destroy(g_prj->file_tag_table);
+	g_strfreev(g_prj->ignored_file_patterns);
 
 	g_free(g_prj);
 	g_prj = NULL;
 }
 
 
-void gprj_project_add_file_tag(gchar *filename)
+gboolean gprj_project_is_in_project(const gchar * filename)
 {
-	deferred_op_queue_enqueue(filename, DeferredTagOpAdd);
+	GSList *elem;
+	
+	if (!filename || !g_prj || !geany_data->app->project || !g_prj->roots)
+		return FALSE;
+	
+	foreach_slist (elem, g_prj->roots)
+	{
+		GPrjRoot *root = elem->data;
+		if (g_hash_table_contains(root->file_table, filename))
+			return TRUE;
+	}
+
+	return FALSE;
 }
 
 
-void gprj_project_remove_file_tag(gchar *filename)
+static gboolean add_tm_idle(gpointer foo)
 {
-	deferred_op_queue_enqueue(filename, DeferredTagOpRemove);
+	GSList *elem2;
+	
+	if (!g_prj || !s_idle_add_funcs)
+		return FALSE;
+
+	foreach_slist (elem2, s_idle_add_funcs)
+	{
+		GSList *elem;
+		gchar *fname = elem2->data;
+		
+		foreach_slist (elem, g_prj->roots)
+		{
+			GPrjRoot *root = elem->data;
+			TMSourceFile *sf = g_hash_table_lookup(root->file_table, fname);
+			
+			if (sf != NULL && !document_find_by_filename(fname))
+			{
+				tm_workspace_add_source_file(sf);
+				break;  /* single file representation in TM is enough */
+			}
+		}
+	}
+	
+	clear_idle_queue(&s_idle_add_funcs);
+
+	return FALSE;
 }
 
 
-gboolean gprj_project_is_in_project(const gchar * filename)
+/* This function gets called when document is being closed by Geany and we need
+ * to add the TMSourceFile from the tag manager because Geany removes it on
+ * document close.
+ * 
+ * Additional problem: The tag removal in Geany happens after this function is called. 
+ * To be sure, perform on idle after this happens (even though from my knowledge of TM
+ * this shouldn't probably matter). */
+void gprj_project_add_single_tm_file(gchar *filename)
+{
+	if (s_idle_add_funcs == NULL)
+		plugin_idle_add(geany_plugin, (GSourceFunc)add_tm_idle, NULL);
+	
+	s_idle_add_funcs = g_slist_prepend(s_idle_add_funcs, g_strdup(filename));
+}
+
+
+static gboolean remove_tm_idle(gpointer foo)
+{
+	GSList *elem2;
+	
+	if (!g_prj || !s_idle_remove_funcs)
+		return FALSE;
+
+	foreach_slist (elem2, s_idle_remove_funcs)
+	{
+		GSList *elem;
+		gchar *fname = elem2->data;
+
+		foreach_slist (elem, g_prj->roots)
+		{
+			GPrjRoot *root = elem->data;
+			TMSourceFile *sf = g_hash_table_lookup(root->file_table, fname);
+			
+			if (sf != NULL)
+				tm_workspace_remove_source_file(sf);
+		}
+	}
+	
+	clear_idle_queue(&s_idle_remove_funcs);
+
+	return FALSE;
+}
+
+
+/* This function gets called when document is being opened by Geany and we need
+ * to remove the TMSourceFile from the tag manager because Geany inserts
+ * it for the newly open tab. Even though tag manager would handle two identical
+ * files, the file inserted by the plugin isn't updated automatically in TM
+ * so any changes wouldn't be reflected in the tags array (e.g. removed function
+ * from source file would still be found in TM)
+ * 
+ * Additional problem: The document being opened may be caused
+ * by going to tag definition/declaration - tag processing is in progress
+ * when this function is called and if we remove the TmSourceFile now, line
+ * number for the searched tag won't be found. For this reason delay the tag
+ * TmSourceFile removal until idle */
+void gprj_project_remove_single_tm_file(gchar *filename)
 {
-	return filename && g_prj && geany_data->app->project &&
-		g_hash_table_lookup(g_prj->file_tag_table, filename) != NULL;
+	if (s_idle_remove_funcs == NULL)
+		plugin_idle_add(geany_plugin, (GSourceFunc)remove_tm_idle, NULL);
+	
+	s_idle_remove_funcs = g_slist_prepend(s_idle_remove_funcs, g_strdup(filename));
 }


Modified: gproject/src/gproject-project.h
26 lines changed, 17 insertions(+), 9 deletions(-)
===================================================================
@@ -19,21 +19,28 @@
 #ifndef __GPROJECT_PROJECT_H__
 #define __GPROJECT_PROJECT_H__
 
-
 typedef struct
 {
-	TMSourceFile *tag;
-} TagObject;
+	gchar *base_dir;
+	GHashTable *file_table; /* contains all file names within base_dir, maps file_name->TMSourceFile */
+} GPrjRoot;
 
+typedef enum
+{
+	GPrjTagAuto,
+	GPrjTagYes,
+	GPrjTagNo,
+} GPrjTagPrefs;
 
 typedef struct
 {
 	gchar **source_patterns;
 	gchar **header_patterns;
 	gchar **ignored_dirs_patterns;
-	gboolean generate_tags;
-
-	GHashTable *file_tag_table;
+	gchar **ignored_file_patterns;
+	GPrjTagPrefs generate_tag_prefs;
+	
+	GSList *roots;  /* list of GPrjRoot; the project root is always the first followed by external dirs roots */
 } GPrj;
 
 extern GPrj *g_prj;
@@ -48,11 +55,12 @@ void gprj_project_save(GKeyFile * key_file);
 void gprj_project_read_properties_tab(void);
 void gprj_project_rescan(void);
 
+void gprj_project_add_external_dir(const gchar *dirname);
+void gprj_project_remove_external_dir(const gchar *dirname);
 
-void gprj_project_add_file_tag(gchar *filename);
-void gprj_project_remove_file_tag(gchar *filename);
+void gprj_project_add_single_tm_file(gchar *filename);
+void gprj_project_remove_single_tm_file(gchar *filename);
 
 gboolean gprj_project_is_in_project(const gchar * filename);
 
-
 #endif


Modified: gproject/src/gproject-sidebar.c
694 lines changed, 595 insertions(+), 99 deletions(-)
===================================================================
@@ -30,6 +30,7 @@
 #include "gproject-project.h"
 #include "gproject-sidebar.h"
 
+extern GeanyPlugin *geany_plugin;
 extern GeanyData *geany_data;
 extern GeanyFunctions *geany_functions;
 
@@ -37,21 +38,31 @@ enum
 {
 	FILEVIEW_COLUMN_ICON,
 	FILEVIEW_COLUMN_NAME,
+	FILEVIEW_COLUMN_COLOR,
 	FILEVIEW_N_COLUMNS,
 };
 
+typedef enum
+{
+	MATCH_FULL,
+	MATCH_PREFIX,
+	MATCH_PATTERN
+} MatchType;
+
+static GdkColor external_color;
 
 static GtkWidget *s_file_view_vbox = NULL;
 static GtkWidget *s_file_view = NULL;
 static GtkTreeStore *s_file_store = NULL;
-static gboolean s_follow_editor = FALSE;
+static gboolean s_follow_editor = TRUE;
 
 static struct
 {
 	GtkWidget *expand;
 	GtkWidget *collapse;
 	GtkWidget *follow;
-} s_project_toolbar = {NULL, NULL, NULL};
+	GtkWidget *add;
+} s_project_toolbar = {NULL, NULL, NULL, NULL};
 
 
 static struct
@@ -69,9 +80,23 @@ static struct
 {
 	GtkWidget *widget;
 
+	GtkWidget *dir_label;
+	GtkWidget *combo;
+	GtkWidget *combo_match;
+	GtkWidget *case_sensitive;
+	GtkWidget *declaration;
+} s_ft_dialog = {NULL, NULL, NULL, NULL, NULL, NULL};
+
+
+static struct
+{
+	GtkWidget *widget;
+
 	GtkWidget *find_in_directory;
 	GtkWidget *find_file;
+	GtkWidget *find_tag;
 	GtkWidget *expand;
+	GtkWidget *remove_external_dir;
 } s_popup_menu;
 
 
@@ -137,7 +162,10 @@ static gint show_dialog_find_file(gchar *path, gchar **pattern, gboolean *case_s
 		gtk_widget_show_all(vbox);
 	}
 
-	gtk_label_set_text(GTK_LABEL(s_fif_dialog.dir_label), path);
+	if (path)
+		gtk_label_set_text(GTK_LABEL(s_fif_dialog.dir_label), path);
+	else
+		gtk_label_set_text(GTK_LABEL(s_fif_dialog.dir_label), _("project or external directory"));
 	entry = gtk_bin_get_child(GTK_BIN(s_fif_dialog.combo));
 	selection = get_selection();
 	if (selection)
@@ -164,6 +192,22 @@ static gint show_dialog_find_file(gchar *path, gchar **pattern, gboolean *case_s
 }
 
 
+static gboolean topmost_selected(GtkTreeModel *model, GtkTreeIter *iter, gboolean first)
+{
+	GtkTreePath *path, *first_path;
+	gboolean ret, is_first;
+	
+	first_path = gtk_tree_path_new_first();
+	path = gtk_tree_model_get_path(model, iter);
+	
+	is_first = gtk_tree_path_compare(first_path, path) == 0;
+	ret = gtk_tree_path_get_depth(path) == 1 && ((is_first && first) || (!is_first && !first));
+	gtk_tree_path_free(first_path);
+	gtk_tree_path_free(path);
+	return ret;
+}
+
+
 static gchar *build_path(GtkTreeIter *iter)
 {
 	GtkTreeIter node;
@@ -190,12 +234,14 @@ static gchar *build_path(GtkTreeIter *iter)
 		node = parent;
 	}
 
-	gtk_tree_model_get(model, &node, FILEVIEW_COLUMN_NAME, &name, -1);
-	SETPTR(path, g_build_filename(name, path, NULL));
-	g_free(name);
-
-	SETPTR(path, g_build_filename(geany_data->app->project->base_path, path, NULL));
-
+	if (topmost_selected(model, &node, TRUE))
+		SETPTR(path, g_build_filename(geany_data->app->project->base_path, path, NULL));
+	else
+	{
+		gtk_tree_model_get(model, &node, FILEVIEW_COLUMN_NAME, &name, -1);
+		SETPTR(path, g_build_filename(name, path, NULL));
+		g_free(name);
+	}
 	return path;
 }
 
@@ -206,9 +252,25 @@ static void on_expand_all(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gp
 }
 
 
-static void on_collapse_all(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer user_data)
+static void collapse(void)
 {
+	GtkTreeIter iter;
+	GtkTreePath *tree_path;
+	GtkTreeModel *model = GTK_TREE_MODEL(s_file_store);
+	
 	gtk_tree_view_collapse_all(GTK_TREE_VIEW(s_file_view));
+	
+	/* expand the project folder */
+	gtk_tree_model_iter_children(model, &iter, NULL);
+	tree_path = gtk_tree_model_get_path (model, &iter);
+	gtk_tree_view_expand_to_path(GTK_TREE_VIEW(s_file_view), tree_path);
+	gtk_tree_path_free(tree_path);
+}
+
+
+static void on_collapse_all(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer user_data)
+{
+	collapse();
 }
 
 
@@ -219,6 +281,53 @@ static void on_follow_active(GtkToggleToolButton *button, G_GNUC_UNUSED gpointer
 }
 
 
+static void on_add_external(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer user_data)
+{
+	GtkWidget *dialog;
+
+	dialog = gtk_file_chooser_dialog_new(_("Add External Directory"),
+		GTK_WINDOW(geany->main_widgets->window), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+		_("_Cancel"), GTK_RESPONSE_CANCEL,
+		_("Add"), GTK_RESPONSE_ACCEPT, NULL);
+
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
+	{
+		gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+		
+		gprj_project_add_external_dir(filename);
+		gprj_sidebar_update(TRUE);
+		project_write_config();
+		
+		g_free (filename);
+	}
+
+	gtk_widget_destroy(dialog);
+}
+
+
+static void on_remove_external_dir(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
+{
+	GtkTreeSelection *treesel;
+	GtkTreeModel *model;
+	GtkTreeIter iter, parent;
+	gchar *name;
+	
+	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
+	if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
+		return;
+
+	if (gtk_tree_model_iter_parent(model, &parent, &iter))
+		return;
+		
+	gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_NAME, &name, -1);
+	gprj_project_remove_external_dir(name);
+	gprj_sidebar_update(TRUE);
+	project_write_config();
+	
+	g_free(name);
+}
+
+
 static void find_file_recursive(GtkTreeIter *iter, gboolean case_sensitive, gboolean full_path, GPatternSpec *pattern)
 {
 	GtkTreeModel *model = GTK_TREE_MODEL(s_file_store);
@@ -281,7 +390,7 @@ static void find_file(GtkTreeIter *iter)
 
 	path = build_path(iter);
 
-	if (show_dialog_find_file(path, &pattern_str, &case_sensitive, &full_path) == GTK_RESPONSE_ACCEPT)
+	if (show_dialog_find_file(iter ? path : NULL, &pattern_str, &case_sensitive, &full_path) == GTK_RESPONSE_ACCEPT)
 	{
 		GPatternSpec *pattern;
 
@@ -301,6 +410,245 @@ static void find_file(GtkTreeIter *iter)
 }
 
 
+static void create_dialog_find_tag(void)
+{
+	GtkWidget *label, *vbox, *ebox, *entry;
+	GtkSizeGroup *size_group;
+
+	if (s_ft_dialog.widget)
+		return;
+
+	s_ft_dialog.widget = gtk_dialog_new_with_buttons(
+		_("Find Tag"), GTK_WINDOW(geany->main_widgets->window),
+		GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
+	gtk_dialog_add_button(GTK_DIALOG(s_ft_dialog.widget), "gtk-find", GTK_RESPONSE_ACCEPT);
+	gtk_dialog_set_default_response(GTK_DIALOG(s_ft_dialog.widget), GTK_RESPONSE_ACCEPT);
+
+	vbox = ui_dialog_vbox_new(GTK_DIALOG(s_ft_dialog.widget));
+	gtk_box_set_spacing(GTK_BOX(vbox), 9);
+
+	size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+
+	label = gtk_label_new(_("Search for:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	gtk_size_group_add_widget(size_group, label);
+
+	s_ft_dialog.combo = gtk_combo_box_text_new_with_entry();
+	entry = gtk_bin_get_child(GTK_BIN(s_ft_dialog.combo));
+
+	ui_entry_add_clear_icon(GTK_ENTRY(entry));
+	gtk_entry_set_width_chars(GTK_ENTRY(entry), 40);
+	gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
+	ui_entry_add_clear_icon(GTK_ENTRY(entry));
+	gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
+
+	ebox = gtk_hbox_new(FALSE, 6);
+	gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(ebox), s_ft_dialog.combo, TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), ebox, TRUE, FALSE, 0);
+
+	label = gtk_label_new(_("Match type:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	gtk_size_group_add_widget(size_group, label);
+
+	s_ft_dialog.combo_match = gtk_combo_box_text_new();
+	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(s_ft_dialog.combo_match), "exact");
+	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(s_ft_dialog.combo_match), "prefix");
+	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(s_ft_dialog.combo_match), "pattern");
+	gtk_combo_box_set_active(GTK_COMBO_BOX(s_ft_dialog.combo_match), 1);
+	gtk_label_set_mnemonic_widget(GTK_LABEL(label), s_ft_dialog.combo_match);
+
+	ebox = gtk_hbox_new(FALSE, 6);
+	gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(ebox), s_ft_dialog.combo_match, TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), ebox, TRUE, FALSE, 0);
+
+	label = gtk_label_new(_("Search inside:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	gtk_size_group_add_widget(size_group, label);
+	s_ft_dialog.dir_label = gtk_label_new("");
+	gtk_misc_set_alignment(GTK_MISC(s_ft_dialog.dir_label), 0, 0.5);
+
+	ebox = gtk_hbox_new(FALSE, 6);
+	gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(ebox), s_ft_dialog.dir_label, TRUE, TRUE, 0);
+
+	gtk_box_pack_start(GTK_BOX(vbox), ebox, TRUE, FALSE, 0);
+
+	s_ft_dialog.case_sensitive = gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
+	gtk_button_set_focus_on_click(GTK_BUTTON(s_ft_dialog.case_sensitive), FALSE);
+
+	s_ft_dialog.declaration = gtk_check_button_new_with_mnemonic(_("_Declaration"));
+	gtk_button_set_focus_on_click(GTK_BUTTON(s_ft_dialog.declaration), FALSE);
+
+	g_object_unref(G_OBJECT(size_group));   /* auto destroy the size group */
+
+	gtk_container_add(GTK_CONTAINER(vbox), s_ft_dialog.case_sensitive);
+	gtk_container_add(GTK_CONTAINER(vbox), s_ft_dialog.declaration);
+	gtk_widget_show_all(vbox);
+}
+
+
+static const char *tm_tag_type_name(const TMTag *tag)
+{
+	g_return_val_if_fail(tag, NULL);
+	switch(tag->type)
+	{
+		case tm_tag_class_t: return "class";
+		case tm_tag_enum_t: return "enum";
+		case tm_tag_enumerator_t: return "enumval";
+		case tm_tag_field_t: return "field";
+		case tm_tag_function_t: return "function";
+		case tm_tag_interface_t: return "interface";
+		case tm_tag_member_t: return "member";
+		case tm_tag_method_t: return "method";
+		case tm_tag_namespace_t: return "namespace";
+		case tm_tag_package_t: return "package";
+		case tm_tag_prototype_t: return "prototype";
+		case tm_tag_struct_t: return "struct";
+		case tm_tag_typedef_t: return "typedef";
+		case tm_tag_union_t: return "union";
+		case tm_tag_variable_t: return "variable";
+		case tm_tag_externvar_t: return "extern";
+		case tm_tag_macro_t: return "define";
+		case tm_tag_macro_with_arg_t: return "macro";
+		default: return NULL;
+	}
+	return NULL;
+}
+
+
+static gboolean match(TMTag *tag, const gchar *name, gboolean declaration, gboolean case_sensitive, 
+	MatchType match_type, GPatternSpec *pspec, gchar *path)
+{
+	const gint forward_types = tm_tag_prototype_t | tm_tag_externvar_t;
+	gboolean matches = FALSE;
+	gint type;
+	
+	type = declaration ? forward_types : tm_tag_max_t - forward_types;
+	matches = tag->type & type;
+	
+	if (matches)
+	{
+		gchar *name_case;
+		
+		if (case_sensitive)
+			name_case = g_strdup(tag->name);
+		else
+			name_case = g_utf8_strdown(tag->name, -1);
+
+		switch (match_type)
+		{
+			case MATCH_FULL: 
+				matches = g_strcmp0(name_case, name) == 0;
+				break;
+			case MATCH_PATTERN:
+				matches = g_pattern_match_string(pspec, name_case);
+				break;
+			case MATCH_PREFIX:
+				matches = g_str_has_prefix(name_case, name);
+				break;
+		}
+		g_free(name_case);
+	}
+		
+	if (matches && path)
+	{
+		gchar *relpath;
+		
+		relpath = get_file_relative_path(path, tag->file->file_name);
+		matches = relpath && !g_str_has_prefix(relpath, "..");
+		g_free(relpath);
+	}
+	
+	return matches;
+}
+
+
+static void find_tags(const gchar *name, gboolean declaration, gboolean case_sensitive, MatchType match_type, gchar *path)
+{
+	GPtrArray *tags_array = geany_data->app->tm_workspace->tags_array;
+	guint i;
+	gchar *name_case;
+	GPatternSpec *pspec;
+	
+	if (case_sensitive)
+		name_case = g_strdup(name);
+	else
+		name_case = g_utf8_strdown(name, -1);
+		
+	pspec = g_pattern_spec_new(name_case);
+
+	msgwin_set_messages_dir(geany_data->app->project->base_path);
+	msgwin_clear_tab(MSG_MESSAGE);
+	for (i = 0; i < tags_array->len; i++) /* TODO: binary search */
+	{
+		TMTag *tag = tags_array->pdata[i];
+
+		if (match(tag, name_case, declaration, case_sensitive, match_type, pspec, path))
+		{
+			gchar *scopestr = tag->scope ? g_strconcat(tag->scope, "::", NULL) : g_strdup("");
+			gchar *relpath;
+			
+			relpath = get_file_relative_path(geany_data->app->project->base_path, tag->file->file_name);
+			msgwin_msg_add(COLOR_BLACK, -1, NULL, "%s:%lu:\n\t[%s]\t %s%s%s", relpath,
+				tag->line, tm_tag_type_name(tag), scopestr, tag->name, tag->arglist ? tag->arglist : "");
+			g_free(scopestr);
+			g_free(relpath);
+		}
+	}
+	msgwin_switch_tab(MSG_MESSAGE, TRUE);
+	
+	g_free(name_case);
+	g_free(pspec);
+}
+
+
+static void find_tag(GtkTreeIter *iter)
+{
+	gchar *selection;
+	gchar *path;
+	GtkWidget *entry;
+
+	create_dialog_find_tag();
+
+	entry = gtk_bin_get_child(GTK_BIN(s_ft_dialog.combo));
+
+	path = build_path(iter);
+	if (iter)
+		gtk_label_set_text(GTK_LABEL(s_ft_dialog.dir_label), path);
+	else
+		gtk_label_set_text(GTK_LABEL(s_ft_dialog.dir_label), _("project or external directory"));
+
+	selection = get_selection();
+	if (selection)
+		gtk_entry_set_text(GTK_ENTRY(entry), selection);
+	g_free(selection);
+
+	gtk_widget_grab_focus(entry);
+
+	if (gtk_dialog_run(GTK_DIALOG(s_ft_dialog.widget)) == GTK_RESPONSE_ACCEPT)
+	{
+		const gchar *name;
+		gboolean case_sensitive, declaration;
+		MatchType match_type;
+
+		name = gtk_entry_get_text(GTK_ENTRY(entry));
+		case_sensitive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s_ft_dialog.case_sensitive));
+		declaration = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s_ft_dialog.declaration));
+		match_type = gtk_combo_box_get_active(GTK_COMBO_BOX(s_ft_dialog.combo_match));
+
+		ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(s_ft_dialog.combo), name, 0);
+
+		find_tags(name, declaration, case_sensitive, match_type, iter?path:NULL);
+	}
+	
+	g_free(path);
+	gtk_widget_hide(s_ft_dialog.widget);
+}
+
+
 static void on_find_file(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
 {
 	GtkTreeSelection *treesel;
@@ -323,6 +671,28 @@ static void on_find_file(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpoi
 }
 
 
+static void on_find_tag(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
+{
+	GtkTreeSelection *treesel;
+	GtkTreeModel *model;
+	GtkTreeIter iter, parent;
+
+	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
+	if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
+		return;
+
+	if (!gtk_tree_model_iter_has_child(model, &iter))
+	{
+		if (gtk_tree_model_iter_parent(model, &parent, &iter))
+			find_tag(&parent);
+		else
+			find_tag(NULL);
+	}
+	else
+		find_tag(&iter);
+}
+
+
 static void on_reload_project(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
 {
 	gprj_project_rescan();
@@ -352,6 +722,7 @@ static void on_open_clicked(void)
 				gtk_tree_view_collapse_row(tree_view, tree_path);
 			else
 				gtk_tree_view_expand_row(tree_view, tree_path, FALSE);
+			gtk_tree_path_free(tree_path);
 		}
 		else
 		{
@@ -375,6 +746,32 @@ static void on_open_clicked(void)
 }
 
 
+static gboolean on_button_release(G_GNUC_UNUSED GtkWidget * widget, GdkEventButton * event,
+		G_GNUC_UNUSED gpointer user_data)
+{
+	if (event->button == 3)
+	{
+		GtkTreeSelection *treesel;
+		GtkTreeModel *model;
+		GtkTreeIter iter;
+
+		treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
+
+		if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
+			return FALSE;
+
+		gtk_widget_set_sensitive(s_popup_menu.expand, gtk_tree_model_iter_has_child(model, &iter));
+		gtk_widget_set_sensitive(s_popup_menu.remove_external_dir, topmost_selected(model, &iter, FALSE));
+
+		gtk_menu_popup(GTK_MENU(s_popup_menu.widget), NULL, NULL, NULL, NULL,
+						event->button, event->time);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
 static gboolean on_button_press(G_GNUC_UNUSED GtkWidget * widget, GdkEventButton * event,
 		G_GNUC_UNUSED gpointer user_data)
 {
@@ -391,9 +788,9 @@ static gboolean on_button_press(G_GNUC_UNUSED GtkWidget * widget, GdkEventButton
 static gboolean on_key_press(G_GNUC_UNUSED GtkWidget * widget, GdkEventKey * event, G_GNUC_UNUSED gpointer data)
 {
 	if (event->keyval == GDK_Return
-	    || event->keyval == GDK_ISO_Enter
-	    || event->keyval == GDK_KP_Enter
-	    || event->keyval == GDK_space)
+		|| event->keyval == GDK_ISO_Enter
+		|| event->keyval == GDK_KP_Enter
+		|| event->keyval == GDK_space)
 	{
 		on_open_clicked();
 		return TRUE;
@@ -411,7 +808,11 @@ static void expand_all(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpoint
 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
 
 	if (gtk_tree_selection_get_selected(treesel, &model, &iter))
-		gtk_tree_view_expand_row(GTK_TREE_VIEW(s_file_view), gtk_tree_model_get_path (model, &iter), TRUE);
+	{
+		GtkTreePath *tree_path = gtk_tree_model_get_path (model, &iter);
+		gtk_tree_view_expand_row(GTK_TREE_VIEW(s_file_view), tree_path, TRUE);
+		gtk_tree_path_free(tree_path);
+	}
 }
 
 
@@ -442,23 +843,14 @@ static void on_find_in_files(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED
 }
 
 
-static void build_file_list(gpointer name, G_GNUC_UNUSED gpointer value, GSList **lst)
-{
-	gchar *item;
-
-	item = get_file_relative_path(geany_data->app->project->base_path, name);
-	*lst = g_slist_prepend(*lst, item);
-}
-
-
 static void create_branch(gint level, GSList *leaf_list, GtkTreeIter *parent,
-	GSList *header_patterns, GSList *source_patterns)
+	GSList *header_patterns, GSList *source_patterns, gboolean project)
 {
 	GSList *dir_list = NULL;
 	GSList *file_list = NULL;
 	GSList *elem;
 
-	for (elem = leaf_list; elem != NULL; elem = g_slist_next(elem))
+	foreach_slist (elem, leaf_list)
 	{
 		gchar **path_arr = elem->data;
 
@@ -478,7 +870,7 @@ static void create_branch(gint level, GSList *leaf_list, GtkTreeIter *parent,
 
 		last_dir_name = path_arr[level];
 
-		for (elem = dir_list; elem != NULL; elem = g_slist_next(elem))
+		foreach_slist (elem, dir_list)
 		{
 			gboolean dir_changed;
 
@@ -491,8 +883,10 @@ static void create_branch(gint level, GSList *leaf_list, GtkTreeIter *parent,
 				gtk_tree_store_set(s_file_store, &iter,
 					FILEVIEW_COLUMN_ICON, icon_dir,
 					FILEVIEW_COLUMN_NAME, last_dir_name, -1);
-
-				create_branch(level+1, tmp_list, &iter, header_patterns, source_patterns);
+				if (!project)
+					gtk_tree_store_set(s_file_store, &iter, FILEVIEW_COLUMN_COLOR, &external_color, -1);
+				
+				create_branch(level+1, tmp_list, &iter, header_patterns, source_patterns, project);
 
 				g_slist_free(tmp_list);
 				tmp_list = NULL;
@@ -506,15 +900,17 @@ static void create_branch(gint level, GSList *leaf_list, GtkTreeIter *parent,
 		gtk_tree_store_set(s_file_store, &iter,
 			FILEVIEW_COLUMN_ICON, icon_dir,
 			FILEVIEW_COLUMN_NAME, last_dir_name, -1);
+		if (!project)
+			gtk_tree_store_set(s_file_store, &iter, FILEVIEW_COLUMN_COLOR, &external_color, -1);
 
-		create_branch(level+1, tmp_list, &iter, header_patterns, source_patterns);
+		create_branch(level+1, tmp_list, &iter, header_patterns, source_patterns, project);
 
 		g_slist_free(tmp_list);
 		g_slist_free(dir_list);
 		g_object_unref(icon_dir);
 	}
 
-	for (elem = file_list; elem != NULL; elem = g_slist_next(elem))
+	foreach_slist (elem, file_list)
 	{
 		GtkTreeIter iter;
 		gchar **path_arr = elem->data;
@@ -536,6 +932,8 @@ static void create_branch(gint level, GSList *leaf_list, GtkTreeIter *parent,
 			gtk_tree_store_set(s_file_store, &iter,
 				FILEVIEW_COLUMN_ICON, icon,
 				FILEVIEW_COLUMN_NAME, path_arr[level], -1);
+			if (!project)
+				gtk_tree_store_set(s_file_store, &iter, FILEVIEW_COLUMN_COLOR, &external_color, -1);
 		}
 		else if (patterns_match(source_patterns, path_arr[level]))
 		{
@@ -545,6 +943,8 @@ static void create_branch(gint level, GSList *leaf_list, GtkTreeIter *parent,
 			gtk_tree_store_set(s_file_store, &iter,
 				FILEVIEW_COLUMN_ICON, icon,
 				FILEVIEW_COLUMN_NAME, path_arr[level], -1);
+			if (!project)
+				gtk_tree_store_set(s_file_store, &iter, FILEVIEW_COLUMN_COLOR, &external_color, -1);
 		}
 		else
 		{
@@ -554,6 +954,8 @@ static void create_branch(gint level, GSList *leaf_list, GtkTreeIter *parent,
 			gtk_tree_store_set(s_file_store, &iter,
 				FILEVIEW_COLUMN_ICON, icon,
 				FILEVIEW_COLUMN_NAME, path_arr[level], -1);
+			if (!project)
+				gtk_tree_store_set(s_file_store, &iter, FILEVIEW_COLUMN_COLOR, &external_color, -1);
 		}
 
 		if (icon)
@@ -575,27 +977,27 @@ static void set_intro_message(const gchar *msg)
 	gtk_widget_set_sensitive(s_project_toolbar.expand, FALSE);
 	gtk_widget_set_sensitive(s_project_toolbar.collapse, FALSE);
 	gtk_widget_set_sensitive(s_project_toolbar.follow, FALSE);
+	gtk_widget_set_sensitive(s_project_toolbar.add, FALSE);
 }
 
 
-static void load_project(void)
+static void load_project_root(GPrjRoot *root, GtkTreeIter *parent, GSList *header_patterns, GSList *source_patterns, gboolean project)
 {
 	GSList *lst = NULL;
 	GSList *path_list = NULL;
-	GSList *elem, *header_patterns, *source_patterns;
-
-	gtk_tree_store_clear(s_file_store);
-
-	if (!g_prj || !geany_data->app->project)
-		return;
-
-	header_patterns = get_precompiled_patterns(g_prj->header_patterns);
-	source_patterns = get_precompiled_patterns(g_prj->source_patterns);
+	GSList *elem;
+	GHashTableIter iter;
+	gpointer key, value;
 
-	g_hash_table_foreach(g_prj->file_tag_table, (GHFunc)build_file_list, &lst);
+	g_hash_table_iter_init(&iter, root->file_table);
+	while (g_hash_table_iter_next(&iter, &key, &value))
+	{
+		gchar *path = get_file_relative_path(root->base_dir, key);
+		lst = g_slist_prepend(lst, path);
+	}
 	lst = g_slist_sort(lst, (GCompareFunc) strcmp);
 
-	for (elem = lst; elem != NULL; elem = g_slist_next(elem))
+	foreach_slist (elem, lst)
 	{
 		gchar **path_split;
 
@@ -604,25 +1006,75 @@ static void load_project(void)
 	}
 
 	if (path_list != NULL)
+		create_branch(0, path_list, parent, header_patterns, source_patterns, project);
+
+	if (project)
 	{
-		create_branch(0, path_list, NULL, header_patterns, source_patterns);
+		if (path_list != NULL)
+		{
+			gtk_widget_set_sensitive(s_project_toolbar.expand, TRUE);
+			gtk_widget_set_sensitive(s_project_toolbar.collapse, TRUE);
+			gtk_widget_set_sensitive(s_project_toolbar.follow, TRUE);
+			gtk_widget_set_sensitive(s_project_toolbar.add, TRUE);
+		}
+		else
+			set_intro_message(_("Set file patterns under Project->Properties"));
+	}
+	
+	g_slist_foreach(lst, (GFunc) g_free, NULL);
+	g_slist_free(lst);
+	g_slist_foreach(path_list, (GFunc) g_strfreev, NULL);
+	g_slist_free(path_list);
+}
+
+
+static void load_project(void)
+{
+	GSList *elem, *header_patterns, *source_patterns;
+	GtkTreeIter iter;
+	gboolean first = TRUE;
+	GIcon *icon_dir;
+	
+	gtk_tree_store_clear(s_file_store);
+
+	if (!g_prj || !geany_data->app->project)
+		return;
+
+	icon_dir = g_icon_new_for_string("gtk-directory", NULL);
+
+	header_patterns = get_precompiled_patterns(g_prj->header_patterns);
+	source_patterns = get_precompiled_patterns(g_prj->source_patterns);
+	
+	foreach_slist (elem, g_prj->roots)
+	{
+		GPrjRoot *root = elem->data;
+		gchar *name;
 		
-		gtk_widget_set_sensitive(s_project_toolbar.expand, TRUE);
-		gtk_widget_set_sensitive(s_project_toolbar.collapse, TRUE);
-		gtk_widget_set_sensitive(s_project_toolbar.follow, TRUE);
+		if (first)
+			name = g_strconcat("<b>", geany_data->app->project->name, "</b>", NULL);
+		else
+			name = g_strdup(root->base_dir);
+
+		gtk_tree_store_append(s_file_store, &iter, NULL);
+		gtk_tree_store_set(s_file_store, &iter,
+			FILEVIEW_COLUMN_ICON, icon_dir,
+			FILEVIEW_COLUMN_NAME, name, -1);
+		if (!first)
+			gtk_tree_store_set(s_file_store, &iter, FILEVIEW_COLUMN_COLOR, &external_color, -1);
+
+		load_project_root(root, &iter, header_patterns, source_patterns, first);
+
+		first = FALSE;
+		g_free(name);
 	}
-	else
-		set_intro_message(_("Set file patterns under Project->Properties"));
+	
+	collapse();
 
 	g_slist_foreach(header_patterns, (GFunc) g_pattern_spec_free, NULL);
 	g_slist_free(header_patterns);
 	g_slist_foreach(source_patterns, (GFunc) g_pattern_spec_free, NULL);
 	g_slist_free(source_patterns);
-
-	g_slist_foreach(lst, (GFunc) g_free, NULL);
-	g_slist_free(lst);
-	g_slist_foreach(path_list, (GFunc) g_strfreev, NULL);
-	g_slist_free(path_list);
+	g_object_unref(icon_dir);
 }
 
 
@@ -638,10 +1090,13 @@ static gboolean find_in_tree(GtkTreeIter *parent, gchar **path_split, gint level
 	while (iterate)
 	{
 		gchar *name;
+		gint cmpres;
 
 		gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_NAME, &name, -1);
 
-		if (g_strcmp0(name, path_split[level]) == 0)
+		cmpres = g_strcmp0(name, path_split[level]);
+		g_free(name);
+		if (cmpres == 0)
 		{
 			GtkTreeIter foo;
 			if (path_split[level+1] == NULL && !gtk_tree_model_iter_children (model, &foo, &iter))
@@ -660,38 +1115,60 @@ static gboolean find_in_tree(GtkTreeIter *parent, gchar **path_split, gint level
 }
 
 
-static void follow_editor(void)
+static gboolean follow_editor_on_idle(gpointer foo)
 {
-	GtkTreeIter found_iter;
-	gchar *path;
+	GtkTreeIter root_iter, found_iter;
+	gchar *path = NULL;
 	gchar **path_split;
 	GeanyDocument *doc;
+	GSList *elem;
+	GtkTreeModel *model;
 
 	doc = document_get_current();
 
-	if (!doc || !doc->file_name || !geany_data->app->project)
-		return;
-
-	path = get_file_relative_path(geany_data->app->project->base_path, doc->file_name);
+	if (!doc || !doc->file_name || !geany_data->app->project || !g_prj)
+		return FALSE;
 
+	model = GTK_TREE_MODEL(s_file_store);
+	gtk_tree_model_iter_children(model, &root_iter, NULL);
+	foreach_slist (elem, g_prj->roots)
+	{
+		GPrjRoot *root = elem->data;
+		
+		path = get_file_relative_path(root->base_dir, doc->file_name);
+		if (path != NULL && !g_str_has_prefix(path, ".."))
+			break;
+			
+		g_free(path);
+		path = NULL;
+		gtk_tree_model_iter_next(model, &root_iter);
+	}
+	
 	if (!path)
-		return;
+		return FALSE;
 
 	path_split = g_strsplit_set(path, "/\\", 0);
 
-	if (find_in_tree(NULL, path_split, 0, &found_iter))
+	if (find_in_tree(&root_iter, path_split, 0, &found_iter))
 	{
 		GtkTreePath *tree_path;
-		GtkTreeModel *model;
+		GtkTreeSelection *treesel;
 
-		model = GTK_TREE_MODEL(s_file_store);
 		tree_path = gtk_tree_model_get_path (model, &found_iter);
 
 		gtk_tree_view_expand_to_path(GTK_TREE_VIEW(s_file_view), tree_path);
 		gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(s_file_view), tree_path,
 			NULL, FALSE, 0.0, 0.0);
-		gtk_tree_view_set_cursor(GTK_TREE_VIEW(s_file_view), tree_path, NULL, FALSE);
+			
+		treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
+		gtk_tree_selection_select_iter(treesel, &found_iter);
+		gtk_tree_path_free(tree_path);
 	}
+	
+	g_free(path);
+	g_strfreev(path_split);
+	
+	return FALSE;
 }
 
 
@@ -699,8 +1176,9 @@ void gprj_sidebar_update(gboolean reload)
 {
 	if (reload)
 		load_project();
-	if (s_follow_editor)
-		follow_editor();
+	if (s_follow_editor) 
+		/* perform on idle - avoids unnecessary jumps on project load */
+		plugin_idle_add(geany_plugin, (GSourceFunc)follow_editor_on_idle, NULL);
 }
 
 
@@ -710,29 +1188,9 @@ void gprj_sidebar_find_file_in_active(void)
 }
 
 
-static gboolean on_button_release(G_GNUC_UNUSED GtkWidget * widget, GdkEventButton * event,
-		  G_GNUC_UNUSED gpointer user_data)
+void gprj_sidebar_find_tag_in_active(void)
 {
-	if (event->button == 3)
-	{
-		GtkTreeSelection *treesel;
-		GtkTreeModel *model;
-		GtkTreeIter iter;
-
-		treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
-
-		if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
-			return FALSE;
-
-
-		gtk_widget_set_sensitive(s_popup_menu.expand, gtk_tree_model_iter_has_child(model, &iter));
-
-		gtk_menu_popup(GTK_MENU(s_popup_menu.widget), NULL, NULL, NULL, NULL,
-						event->button, event->time);
-		return TRUE;
-	}
-
-	return FALSE;
+	find_tag(NULL);
 }
 
 
@@ -753,6 +1211,8 @@ void gprj_sidebar_init(void)
 	gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar), GTK_ICON_SIZE_MENU);
 	gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
 
+	external_color = gtk_widget_get_style(toolbar)->bg[GTK_STATE_NORMAL];
+
 	item = GTK_WIDGET(gtk_tool_button_new(NULL, NULL));
 	gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON(item), "gproject-refresh");
 	ui_widget_set_tooltip_text(item, _("Reload all"));
@@ -763,6 +1223,16 @@ void gprj_sidebar_init(void)
 	gtk_container_add(GTK_CONTAINER(toolbar), item);
 
 	item = GTK_WIDGET(gtk_tool_button_new(NULL, NULL));
+	gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON(item), "gproject-add-external");
+	ui_widget_set_tooltip_text(item, _("Add external directory"));
+	g_signal_connect(item, "clicked", G_CALLBACK(on_add_external), NULL);
+	gtk_container_add(GTK_CONTAINER(toolbar), item);
+	s_project_toolbar.add = item;
+
+	item = GTK_WIDGET(gtk_separator_tool_item_new());
+	gtk_container_add(GTK_CONTAINER(toolbar), item);
+
+	item = GTK_WIDGET(gtk_tool_button_new(NULL, NULL));
 	gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON(item), "gproject-expand");
 	ui_widget_set_tooltip_text(item, _("Expand all"));
 	g_signal_connect(item, "clicked", G_CALLBACK(on_expand_all), NULL);
@@ -771,7 +1241,7 @@ void gprj_sidebar_init(void)
 
 	item = GTK_WIDGET(gtk_tool_button_new(NULL, NULL));
 	gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON(item), "gproject-collapse");
-	ui_widget_set_tooltip_text(item, _("Collapse all"));
+	ui_widget_set_tooltip_text(item, _("Collapse to project root"));
 	g_signal_connect(item, "clicked", G_CALLBACK(on_collapse_all), NULL);
 	gtk_container_add(GTK_CONTAINER(toolbar), item);
 	s_project_toolbar.collapse = item;
@@ -780,6 +1250,7 @@ void gprj_sidebar_init(void)
 	gtk_container_add(GTK_CONTAINER(toolbar), item);
 
 	item = GTK_WIDGET(gtk_toggle_tool_button_new());
+	gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(item), TRUE);
 	gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON(item), "gproject-follow");
 	ui_widget_set_tooltip_text(item, _("Follow active editor"));
 	g_signal_connect(item, "clicked", G_CALLBACK(on_follow_active), NULL);
@@ -792,17 +1263,19 @@ void gprj_sidebar_init(void)
 
 	s_file_view = gtk_tree_view_new();
 
-	s_file_store = gtk_tree_store_new(FILEVIEW_N_COLUMNS, G_TYPE_ICON, G_TYPE_STRING);
+	s_file_store = gtk_tree_store_new(FILEVIEW_N_COLUMNS, G_TYPE_ICON, G_TYPE_STRING, GDK_TYPE_COLOR);
 	gtk_tree_view_set_model(GTK_TREE_VIEW(s_file_view), GTK_TREE_MODEL(s_file_store));
 
 	renderer = gtk_cell_renderer_pixbuf_new();
 	column = gtk_tree_view_column_new();
 	gtk_tree_view_column_pack_start(column, renderer, FALSE);
-	gtk_tree_view_column_set_attributes(column, renderer, "gicon", FILEVIEW_COLUMN_ICON, NULL);
-
+	gtk_tree_view_column_add_attribute(column, renderer, "gicon", FILEVIEW_COLUMN_ICON);
+	gtk_tree_view_column_add_attribute(column, renderer, "cell-background-gdk", FILEVIEW_COLUMN_COLOR);
+	
 	renderer = gtk_cell_renderer_text_new();
 	gtk_tree_view_column_pack_start(column, renderer, TRUE);
-	gtk_tree_view_column_set_attributes(column, renderer, "text", FILEVIEW_COLUMN_NAME, NULL);
+	gtk_tree_view_column_add_attribute(column, renderer, "markup", FILEVIEW_COLUMN_NAME);
+	gtk_tree_view_column_add_attribute(column, renderer, "cell-background-gdk", FILEVIEW_COLUMN_COLOR);
 
 	gtk_tree_view_append_column(GTK_TREE_VIEW(s_file_view), column);
 
@@ -819,12 +1292,13 @@ void gprj_sidebar_init(void)
 
 	g_signal_connect(G_OBJECT(s_file_view), "button-release-event",
 			G_CALLBACK(on_button_release), NULL);
+/*	row-activated grabs focus for the sidebar, use button-press-event instead */
 	g_signal_connect(G_OBJECT(s_file_view), "button-press-event",
 			G_CALLBACK(on_button_press), NULL);
 	g_signal_connect(G_OBJECT(s_file_view), "key-press-event",
 			G_CALLBACK(on_key_press), NULL);
 
-	set_intro_message(_("(Re)open the project to start using the plugin"));
+	set_intro_message(_("(Re)open project to start using the plugin"));
 	gprj_sidebar_activate(FALSE);
 
 	/**** popup menu ****/
@@ -858,13 +1332,35 @@ void gprj_sidebar_init(void)
 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_find_file), NULL);
 	s_popup_menu.find_file = item;
 
+	image = gtk_image_new_from_stock(GTK_STOCK_FIND, GTK_ICON_SIZE_MENU);
+	gtk_widget_show(image);
+	item = gtk_image_menu_item_new_with_mnemonic(_("Find Tag"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
+	gtk_widget_show(item);
+	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
+	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_find_tag), NULL);
+	s_popup_menu.find_tag = item;
+
+	item = gtk_separator_menu_item_new();
+	gtk_widget_show(item);
+	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
+
+	image = gtk_image_new_from_stock(GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU);
+	gtk_widget_show(image);
+	item = gtk_image_menu_item_new_with_mnemonic(_("Remove External Directory"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
+	gtk_widget_show(item);
+	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
+	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_remove_external_dir), NULL);
+	s_popup_menu.remove_external_dir = item;
+
 	item = gtk_separator_menu_item_new();
 	gtk_widget_show(item);
 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
 
 	item = gtk_image_menu_item_new_with_mnemonic(_("H_ide Sidebar"));
 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
-				      gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU));
+					  gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU));
 	gtk_widget_show(item);
 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
 	g_signal_connect_swapped((gpointer) item, "activate",
@@ -877,7 +1373,7 @@ void gprj_sidebar_init(void)
 	gtk_container_set_focus_chain(GTK_CONTAINER(s_file_view_vbox), focus_chain);
 	scrollwin = gtk_scrolled_window_new(NULL, NULL);
 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
-				       GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+					   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
 	gtk_container_add(GTK_CONTAINER(scrollwin), s_file_view);
 	gtk_box_pack_start(GTK_BOX(s_file_view_vbox), scrollwin, TRUE, TRUE, 0);
 


Modified: gproject/src/gproject-sidebar.h
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -25,6 +25,7 @@ void gprj_sidebar_cleanup(void);
 void gprj_sidebar_activate(gboolean activate);
 
 void gprj_sidebar_find_file_in_active(void);
+void gprj_sidebar_find_tag_in_active(void);
 
 void gprj_sidebar_update(gboolean reload);
 


Modified: gproject/src/gproject-utils.c
4 lines changed, 2 insertions(+), 2 deletions(-)
===================================================================
@@ -32,7 +32,7 @@ static gchar *relpath(const gchar *origin_dir, const gchar *dest_dir)
 	gchar **originv, **destv;
 	gchar *ret = NULL;
 	guint i, j;
-
+	
 	origin = tm_get_real_path(origin_dir);
 	dest = tm_get_real_path(dest_dir);
 
@@ -119,7 +119,7 @@ GSList *get_precompiled_patterns(gchar **patterns)
 gboolean patterns_match(GSList *patterns, const gchar *str)
 {
 	GSList *elem;
-	for (elem = patterns; elem != NULL; elem = g_slist_next(elem))
+	foreach_slist (elem, patterns)
 	{
 		GPatternSpec *pattern = elem->data;
 		if (g_pattern_match_string(pattern, str))


Modified: gproject/wscript_build
3 lines changed, 2 insertions(+), 1 deletions(-)
===================================================================
@@ -25,8 +25,9 @@ from build.wafutils import build_plugin, target_is_win32
 
 name = 'GProject'
 includes = ['gproject/src']
+defines = ['PLUGIN="%s"' % name.lower()]
 
-build_plugin(bld, name, includes=includes)
+build_plugin(bld, name, includes=includes, defines=defines)
 
 # Icons
 prefix = '${G_PREFIX}/' if target_is_win32(bld) else ''



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


More information about the Plugins-Commits mailing list