[geany/geany-plugins] b2183b: workbench: support live update. Closes #659.

LarsDW223 git-noreply at xxxxx
Sat Feb 17 17:33:10 UTC 2018


Branch:      refs/heads/master
Author:      LarsDW223 <lars_paulsen at web.de>
Committer:   LarsDW223 <lars_paulsen at web.de>
Date:        Sat, 17 Feb 2018 17:33:10 UTC
Commit:      b2183bd9b1407a348e543f06b25debab873c5e0e
             https://github.com/geany/geany-plugins/commit/b2183bd9b1407a348e543f06b25debab873c5e0e

Log Message:
-----------
workbench: support live update. Closes #659.

With this PR the workbench file list and sidebar will automatically be updated
if a file is created, removed or renamed. The feature uses the file monitoring
from the GIO lib to monitor directories. If required, the feature can be
disabled in the workbench settings dialog.


Modified Paths:
--------------
    workbench/README
    workbench/src/Makefile.am
    workbench/src/dialogs.c
    workbench/src/menu.c
    workbench/src/plugin_main.c
    workbench/src/popup_menu.c
    workbench/src/sidebar.c
    workbench/src/sidebar.h
    workbench/src/wb_monitor.c
    workbench/src/wb_monitor.h
    workbench/src/wb_project.c
    workbench/src/wb_project.h
    workbench/src/workbench.c
    workbench/src/workbench.h

Modified: workbench/README
31 lines changed, 29 insertions(+), 2 deletions(-)
===================================================================
@@ -53,8 +53,8 @@ related settings is done using the context menu on the sidebar. Some
 items in the context menu will only be active if you right click inside
 a project, directory or bookmark.
 
-The Workbench context menu:
----------------------------
+The Workbench context menu
+--------------------------
 
 These are the available items:
 
@@ -141,6 +141,33 @@ These are the available items:
   Select this item to create a new directory at the current selected position
   in the file tree. Available since version 1.02 of the workbench plugin.
 
+The Workbench settings
+----------------------
+The following settings exist for a workbench:
+
+**Rescan all projects on open**
+  If the option is activated (default), then all projects will be re-scanned
+  on opening of a workbench.
+
+**Enable live update**
+  If the option is activated (default), then the list of files and the sidebar
+  will be updated automatically if a file or directory is created, removed or renamed.
+  A manual re-scan is not required if the option is enabled. This feature is available
+  since version 1.03 of the workbench plugin. If you open a workbench file
+  which has been created with an older version of the workbench plugin
+  then the option will be added with value "activated".
+
+Live update
+-----------
+From version 1.03 on the workbench plugin supports an automatic live update
+of the file list and the sidebar.
+
+This feature will only work if your system supports directory file monitoring.
+If the workbench plugin cannot setup file monitoring for a directory then it
+will output a message in the message window. The message has the following form::
+
+    Could not setup file monitoring for directory: "exampledir". Error: <some error message>
+
 Known issues
 ============
 


Modified: workbench/src/Makefile.am
2 lines changed, 2 insertions(+), 0 deletions(-)
===================================================================
@@ -11,6 +11,8 @@ workbench_la_SOURCES = \
 	workbench.c \
 	wb_project.h \
 	wb_project.c \
+	wb_monitor.h \
+	wb_monitor.c \
 	dialogs.h \
 	dialogs.c \
 	menu.h \


Modified: workbench/src/dialogs.c
18 lines changed, 17 insertions(+), 1 deletions(-)
===================================================================
@@ -407,11 +407,12 @@ gboolean dialogs_directory_settings(WB_PROJECT_DIR *directory)
 gboolean dialogs_workbench_settings(WORKBENCH *workbench)
 {
 	gint result;
-	GtkWidget *w_rescan_projects_on_open;
+	GtkWidget *w_rescan_projects_on_open, *w_enable_live_update;
 	GtkWidget *dialog, *content_area;
 	GtkWidget *vbox, *hbox, *table;
 	GtkDialogFlags flags;
 	gboolean changed, rescan_projects_on_open, rescan_projects_on_open_old;
+	gboolean enable_live_update, enable_live_update_old;
 
 	/* Create the widgets */
 	flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
@@ -437,6 +438,15 @@ gboolean dialogs_workbench_settings(WORKBENCH *workbench)
 	rescan_projects_on_open_old = workbench_get_rescan_projects_on_open(workbench);
 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w_rescan_projects_on_open), rescan_projects_on_open_old);
 
+	w_enable_live_update = gtk_check_button_new_with_mnemonic(_("_Enable live update"));
+	ui_table_add_row(GTK_TABLE(table), 1, w_enable_live_update, NULL);
+	gtk_widget_set_tooltip_text(w_enable_live_update,
+		_("If the option is activated (default), then the list of files and the sidebar"
+		  " will be updated automatically if a file or directory is created, removed or renamed."
+		  "A manual re-scan is not required if the option is enabled."));
+	enable_live_update_old = workbench_get_enable_live_update(workbench);
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w_enable_live_update), enable_live_update_old);
+
 	gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 6);
 
 	hbox = gtk_hbox_new(FALSE, 0);
@@ -456,6 +466,12 @@ gboolean dialogs_workbench_settings(WORKBENCH *workbench)
 			changed = TRUE;
 			workbench_set_rescan_projects_on_open(workbench, rescan_projects_on_open);
 		}
+		enable_live_update = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w_enable_live_update));
+		if (enable_live_update != enable_live_update_old)
+		{
+			changed = TRUE;
+			workbench_set_enable_live_update(workbench, enable_live_update);
+		}
 	}
 
 	gtk_widget_destroy(dialog);


Modified: workbench/src/menu.c
18 lines changed, 18 insertions(+), 0 deletions(-)
===================================================================
@@ -148,9 +148,27 @@ static void item_workbench_settings_activate_cb(G_GNUC_UNUSED GtkMenuItem *menui
 {
 	if (wb_globals.opened_wb != NULL)
 	{
+		gboolean enable_live_update_old, enable_live_update;
+
+		enable_live_update_old = workbench_get_enable_live_update(wb_globals.opened_wb);
 		if (dialogs_workbench_settings(wb_globals.opened_wb))
 		{
 			sidebar_update(SIDEBAR_CONTEXT_WB_SETTINGS_CHANGED, NULL);
+
+			enable_live_update = workbench_get_enable_live_update(wb_globals.opened_wb);
+			if (enable_live_update != enable_live_update_old)
+			{
+				if (enable_live_update == TRUE)
+				{
+					/* Start/create all file monitors. */
+					workbench_enable_live_update(wb_globals.opened_wb);
+				}
+				else
+				{
+					/* Stop/free all file monitors. */
+					workbench_disable_live_update(wb_globals.opened_wb);
+				}
+			}
 		}
 	}
 }


Modified: workbench/src/plugin_main.c
8 lines changed, 5 insertions(+), 3 deletions(-)
===================================================================
@@ -46,7 +46,8 @@ static void plugin_workbench_on_doc_open(G_GNUC_UNUSED GObject * obj, G_GNUC_UNU
 	project = workbench_file_is_included(wb_globals.opened_wb, doc->file_name);
 	if (project != NULL)
 	{
-		wb_project_remove_single_tm_file(project, doc->file_name);
+		wb_project_add_idle_action(WB_PROJECT_IDLE_ACTION_ID_REMOVE_SINGLE_TM_FILE,
+			project, g_strdup(doc->file_name));
 	}
 }
 
@@ -69,7 +70,8 @@ static void plugin_workbench_on_doc_close(G_GNUC_UNUSED GObject * obj, GeanyDocu
 	project = workbench_file_is_included(wb_globals.opened_wb, doc->file_name);
 	if (project != NULL)
 	{
-		wb_project_add_single_tm_file(project, doc->file_name);
+		wb_project_add_idle_action(WB_PROJECT_IDLE_ACTION_ID_ADD_SINGLE_TM_FILE,
+			project, g_strdup(doc->file_name));
 	}
 }
 
@@ -128,7 +130,7 @@ void geany_load_module(GeanyPlugin *plugin)
 	/* Set metadata */
 	plugin->info->name = _("Workbench");
 	plugin->info->description = _("Manage and customize multiple projects.");
-	plugin->info->version = "1.02";
+	plugin->info->version = "1.03";
 	plugin->info->author = "LarsGit223";
 
 	/* Set functions */


Modified: workbench/src/popup_menu.c
20 lines changed, 16 insertions(+), 4 deletions(-)
===================================================================
@@ -561,8 +561,15 @@ static void popup_menu_on_new_file(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_U
 		else
 		{
 			fclose(new_file);
-			wb_project_dir_rescan(context.project, context.directory);
-			sidebar_update(SIDEBAR_CONTEXT_DIRECTORY_RESCANNED, &context);
+
+			if (workbench_get_enable_live_update(wb_globals.opened_wb) == FALSE)
+			{
+				/* Live update is disabled. We need to update the file list and
+				   the sidebar manually. */
+				wb_project_dir_rescan(context.project, context.directory);
+				sidebar_update(SIDEBAR_CONTEXT_DIRECTORY_RESCANNED, &context);
+			}
+
 			document_open_file(filename, FALSE, NULL, NULL);
 		}
 	}
@@ -595,8 +602,13 @@ static void popup_menu_on_new_directory(G_GNUC_UNUSED GtkMenuItem *menuitem, G_G
 	filename = dialogs_create_new_directory(abs_path);
 	if (filename != NULL)
 	{
-		wb_project_dir_rescan(context.project, context.directory);
-		sidebar_update(SIDEBAR_CONTEXT_DIRECTORY_RESCANNED, &context);
+		if (workbench_get_enable_live_update(wb_globals.opened_wb) == FALSE)
+		{
+			/* Live update is disabled. We need to update the file list and
+			   the sidebar manually. */
+			wb_project_dir_rescan(context.project, context.directory);
+			sidebar_update(SIDEBAR_CONTEXT_DIRECTORY_RESCANNED, &context);
+		}
 	}
 
 	g_free(abs_path);


Modified: workbench/src/sidebar.c
374 lines changed, 362 insertions(+), 12 deletions(-)
===================================================================
@@ -33,18 +33,6 @@
 #include "popup_menu.h"
 #include "utils.h"
 
-enum
-{
-	DATA_ID_UNSET = 0,
-	DATA_ID_WB_BOOKMARK,
-	DATA_ID_PROJECT,
-	DATA_ID_PRJ_BOOKMARK,
-	DATA_ID_DIRECTORY,
-	DATA_ID_NO_DIRS,
-	DATA_ID_SUB_DIRECTORY,
-	DATA_ID_FILE,
-};
-
 enum
 {
 	FILEVIEW_COLUMN_ICON,
@@ -61,12 +49,29 @@ typedef enum
 	MATCH_PATTERN
 } MatchType;
 
+typedef struct
+{
+	gboolean iter_valid;
+	GtkTreeIter iter;
+	gboolean parent_valid;
+	GtkTreeIter parent;
+}ITER_SEARCH_RESULT;
+
 typedef struct
 {
 	GeanyProject *project;
 	GPtrArray *expanded_paths;
 } ExpandData;
 
+typedef struct
+{
+	SIDEBAR_CONTEXT *context;
+	GtkTreeModel *model;
+	guint dataid;
+	void (*func)(SIDEBAR_CONTEXT *, gpointer userdata);
+	gpointer userdata;
+}SB_CALLFOREACH_CONTEXT;
+
 typedef struct SIDEBAR
 {
     GtkWidget *file_view_vbox;
@@ -76,6 +81,9 @@ typedef struct SIDEBAR
 }SIDEBAR;
 static SIDEBAR sidebar = {NULL, NULL, NULL, NULL};
 
+static gboolean sidebar_get_directory_iter(WB_PROJECT *prj, WB_PROJECT_DIR *dir, GtkTreeIter *iter);
+static gboolean sidebar_get_filepath_iter (WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath, ITER_SEARCH_RESULT *result);
+
 /* Remove all child nodes below parent */
 static void sidebar_remove_children(GtkTreeIter *parent)
 {
@@ -363,6 +371,104 @@ static void sidebar_insert_project_directories (WB_PROJECT *project, GtkTreeIter
 }
 
 
+/* Add a file to the sidebar. filepath can be a file or directory. */
+static void sidebar_add_file (WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath)
+{
+	GIcon *icon = NULL;
+	gchar *name;
+	guint dataid;
+	ITER_SEARCH_RESULT search_result;
+
+	if (!sidebar_get_filepath_iter(prj, root, filepath, &search_result))
+	{
+		return;
+	}
+	else
+	{
+		if (search_result.iter_valid)
+		{
+			/* Error, file already exists in sidebar tree. */
+			return;
+		}
+		else
+		{
+			/* This is the expected result as we want to add a new file node.
+			   But parent should be valid. */
+			if (!search_result.parent_valid)
+			{
+				return;
+			}
+		}
+	}
+
+	/* Collect data */
+	name = g_path_get_basename(filepath);
+	if (g_file_test (filepath, G_FILE_TEST_IS_DIR))
+	{
+		dataid = DATA_ID_SUB_DIRECTORY;
+		icon = g_icon_new_for_string("folder", NULL);
+	}
+	else
+	{
+		dataid = DATA_ID_FILE;
+
+		gchar *content_type = g_content_type_guess(filepath, NULL, 0, NULL);
+		if (content_type)
+		{
+			icon = g_content_type_get_icon(content_type);
+			if (icon)
+			{
+				GtkIconInfo *icon_info;
+
+				icon_info = gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(), icon, 16, 0);
+				if (!icon_info)
+				{
+					g_object_unref(icon);
+					icon = NULL;
+				}
+				else
+					gtk_icon_info_free(icon_info);
+			}
+			g_free(content_type);
+		}
+
+	}
+
+	/* Create new row/node in sidebar tree. */
+	gtk_tree_store_insert_with_values(sidebar.file_store, &search_result.iter, &search_result.parent, -1,
+		FILEVIEW_COLUMN_ICON, icon,
+		FILEVIEW_COLUMN_NAME, name,
+		FILEVIEW_COLUMN_DATA_ID, dataid,
+		FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, g_strdup(filepath),
+		-1);
+
+	if (icon)
+	{
+		g_object_unref(icon);
+	}
+}
+
+
+/* Remove a file from the sidebar. filepath can be a file or directory. */
+static void sidebar_remove_file (WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath)
+{
+	ITER_SEARCH_RESULT search_result;
+
+	if (!sidebar_get_filepath_iter(prj, root, filepath, &search_result))
+	{
+		return;
+	}
+	else
+	{
+		if (search_result.iter_valid)
+		{
+			/* File was found, remove the node. */
+			gtk_tree_store_remove(sidebar.file_store, &search_result.iter);
+		}
+	}
+}
+
+
 /* Get the GtkTreeIter for project prj */
 static gboolean sidebar_get_project_iter(WB_PROJECT *prj, GtkTreeIter *iter)
 {
@@ -386,6 +492,140 @@ static gboolean sidebar_get_project_iter(WB_PROJECT *prj, GtkTreeIter *iter)
 }
 
 
+/* Get the GtkTreeIter for directory dir in project prj */
+static gboolean sidebar_get_directory_iter(WB_PROJECT *prj, WB_PROJECT_DIR *dir, GtkTreeIter *iter)
+{
+	GtkTreeIter parent;
+	GtkTreeModel *model;
+
+	model = gtk_tree_view_get_model(GTK_TREE_VIEW(sidebar.file_view));
+	if (sidebar_get_project_iter(prj, iter))
+	{
+		parent = *iter;
+		if (gtk_tree_model_iter_children (model, iter, &parent))
+		{
+			WB_PROJECT_DIR *current;
+
+			do
+			{
+				gtk_tree_model_get(model, iter, FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, &current, -1);
+				if (current == dir)
+				{
+					return TRUE;
+				}
+			}while (gtk_tree_model_iter_next(model, iter));
+		}
+	}
+	return FALSE;
+}
+
+
+/* Get the GtkTreeIter for filepath (absolute) in directory root in project prj */
+static gboolean sidebar_get_filepath_iter (WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath, ITER_SEARCH_RESULT *search_result)
+{
+	guint len, index;
+	gchar *part, **parts, *found = NULL, *last_found, *abs_base_dir;
+	GtkTreeIter iter, parent;
+	GtkTreeModel *model;
+
+	if (search_result == NULL)
+	{
+		return FALSE;
+	}
+	search_result->iter_valid = FALSE;
+	search_result->parent_valid = FALSE;
+
+	abs_base_dir = get_combined_path(wb_project_get_filename(prj), wb_project_dir_get_base_dir(root));
+	len = strlen(abs_base_dir);
+	if (strncmp(abs_base_dir, filepath, len) != 0)
+	{
+		/* Error, return. */
+		return FALSE;
+	}
+	if (filepath[len] == G_DIR_SEPARATOR)
+	{
+		len++;
+	}
+	part = g_strdup(&(filepath[len]));
+	if (strlen(part) == 0)
+	{
+		return FALSE;
+	}
+	parts = g_strsplit(part, G_DIR_SEPARATOR_S, -1);
+	if (parts[0] == NULL)
+	{
+		return FALSE;
+	}
+
+	if (sidebar_get_directory_iter(prj, root, &iter))
+	{
+		index = 0;
+		last_found = NULL;
+		model = gtk_tree_view_get_model(GTK_TREE_VIEW(sidebar.file_view));
+
+		while (parts[index] != NULL)
+		{
+			parent = iter;
+			if (gtk_tree_model_iter_children(model, &iter, &parent))
+			{
+				gchar *current;
+
+				found = NULL;
+				do
+				{
+					gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_NAME, &current, -1);
+
+					if (g_strcmp0(current, parts[index]) == 0)
+					{
+						found = current;
+						last_found = current;
+						index++;
+						//parent = iter;
+						break;
+					}
+				}while (gtk_tree_model_iter_next(model, &iter));
+
+				/* Did we find the next match? */
+				if (found == NULL)
+				{
+					/* No, abort. */
+					break;
+				}
+			}
+			else
+			{
+				break;
+			}
+		}
+
+		/* Did we find a full match? */
+		if (parts[index] == NULL && found != NULL)
+		{
+			/* Yes. */
+			search_result->iter_valid = TRUE;
+			search_result->iter = iter;
+
+			search_result->parent_valid = TRUE;
+			search_result->parent = parent;
+		}
+		else if (parts[index+1] == NULL &&
+				 (last_found != NULL || parts[1] == NULL))
+		{
+			/* Not a full match but everything but the last part
+			   of the filepath. At least return the parent. */
+			search_result->parent_valid = TRUE;
+			search_result->parent = parent;
+		}
+	}
+
+	g_free(part);
+	g_free(abs_base_dir);
+	g_strfreev(parts);
+
+	return TRUE;
+}
+
+
 /* Remove all rows from the side bar tree with the given data id */
 static void sidebar_remove_nodes_with_data_id(guint toremove, GtkTreeIter *start)
 {
@@ -710,6 +950,12 @@ void sidebar_update (SIDEBAR_EVENT event, SIDEBAR_CONTEXT *context)
 			}
 		}
 		break;
+		case SIDEBAR_CONTEXT_FILE_ADDED:
+			sidebar_add_file(context->project, context->directory, context->file);
+		break;
+		case SIDEBAR_CONTEXT_FILE_REMOVED:
+			sidebar_remove_file(context->project, context->directory, context->file);
+		break;
 	}
 }
 
@@ -1325,3 +1571,107 @@ void sidebar_toggle_selected_project_dir_expansion (void)
 		gtk_tree_path_free(path);
 	}
 }
+
+
+/* Helper function to set context according to given parameters. */
+static void sidebar_set_context (SIDEBAR_CONTEXT *context, guint dataid, gpointer data)
+{
+	if (data != NULL)
+	{
+		switch (dataid)
+		{
+			case DATA_ID_WB_BOOKMARK:
+				memset(context, 0, sizeof(*context));
+				context->wb_bookmark = data;
+			break;
+			case DATA_ID_PROJECT:
+				memset(context, 0, sizeof(*context));
+				context->project = data;
+			break;
+			case DATA_ID_PRJ_BOOKMARK:
+				context->prj_bookmark = data;
+				context->directory = NULL;
+				context->subdir = NULL;
+				context->file = NULL;
+			break;
+			case DATA_ID_DIRECTORY:
+				context->directory = data;
+				context->subdir = NULL;
+				context->file = NULL;
+			break;
+			case DATA_ID_NO_DIRS:
+				/* Has not got any data. */
+			break;
+			case DATA_ID_SUB_DIRECTORY:
+				context->subdir = data;
+				context->file = NULL;
+			break;
+			case DATA_ID_FILE:
+				context->file = data;
+			break;
+		}
+	}
+}
+
+
+/* Internal call foreach function. Traverses the whole sidebar. */
+void sidebar_call_foreach_int(SB_CALLFOREACH_CONTEXT *foreach_cntxt,
+							  GtkTreeIter *iter)
+{
+	guint currentid;
+	gpointer current;
+	GtkTreeIter children;
+
+	do
+	{
+		gtk_tree_model_get(foreach_cntxt->model, iter, FILEVIEW_COLUMN_DATA_ID, &currentid, -1);
+		gtk_tree_model_get(foreach_cntxt->model, iter, FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, &current, -1);
+
+		sidebar_set_context(foreach_cntxt->context, currentid, current);
+		if (currentid == foreach_cntxt->dataid)
+		{
+			/* Found requested node. Call callback function. */
+			foreach_cntxt->func(foreach_cntxt->context, foreach_cntxt->userdata);
+		}
+
+		/* If the node has childs then check them to. */
+		if (gtk_tree_model_iter_children (foreach_cntxt->model, &children, iter))
+		{
+			sidebar_call_foreach_int(foreach_cntxt, &children);
+		}
+	}while (gtk_tree_model_iter_next(foreach_cntxt->model, iter));
+}
+
+
+/** Call a callback function for each matching node.
+ *
+ * This function traverses the complete sidebar tree and calls func each time
+ * a node with dataid is found. The function is passed the sidebar context
+ * for the current position in hte tree and the given userdata.
+ * 
+ * @param dataid   Which nodes are of interest?
+ * @param func     Callback function
+ * @param userdata Pointer to user data. Is transparently passed through
+ *                 on calling func.
+ *
+ **/
+void sidebar_call_foreach(guint dataid,
+						  void (*func)(SIDEBAR_CONTEXT *, gpointer userdata),
+						  gpointer userdata)
+{
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	SIDEBAR_CONTEXT context;
+	SB_CALLFOREACH_CONTEXT foreach_cntxt;
+
+	model = gtk_tree_view_get_model(GTK_TREE_VIEW(sidebar.file_view));
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		foreach_cntxt.context = &context;
+		foreach_cntxt.model = model;
+		foreach_cntxt.dataid = dataid;
+		foreach_cntxt.func = func;
+		foreach_cntxt.userdata = userdata;
+		sidebar_call_foreach_int(&foreach_cntxt, &iter);
+	}
+}


Modified: workbench/src/sidebar.h
17 lines changed, 17 insertions(+), 0 deletions(-)
===================================================================
@@ -22,6 +22,18 @@
 #include <gtk/gtk.h>
 #include "wb_project.h"
 
+enum
+{
+	DATA_ID_UNSET = 0,
+	DATA_ID_WB_BOOKMARK,
+	DATA_ID_PROJECT,
+	DATA_ID_PRJ_BOOKMARK,
+	DATA_ID_DIRECTORY,
+	DATA_ID_NO_DIRS,
+	DATA_ID_SUB_DIRECTORY,
+	DATA_ID_FILE,
+};
+
 typedef struct
 {
 	WB_PROJECT     *project;
@@ -50,6 +62,8 @@ typedef enum
 	SIDEBAR_CONTEXT_WB_BOOKMARK_REMOVED,
 	SIDEBAR_CONTEXT_PRJ_BOOKMARK_ADDED,
 	SIDEBAR_CONTEXT_PRJ_BOOKMARK_REMOVED,
+	SIDEBAR_CONTEXT_FILE_ADDED,
+	SIDEBAR_CONTEXT_FILE_REMOVED,
 }SIDEBAR_EVENT;
 
 void sidebar_init(void);
@@ -76,4 +90,7 @@ GPtrArray *sidebar_get_selected_project_filelist (void);
 GPtrArray *sidebar_get_selected_directory_filelist (void);
 GPtrArray *sidebar_get_selected_subdir_filelist (void);
 
+void sidebar_call_foreach(guint dataid,
+	void (func)(SIDEBAR_CONTEXT *, gpointer userdata), gpointer userdata);
+
 #endif


Modified: workbench/src/wb_monitor.c
267 lines changed, 267 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2018 LarsGit223
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/*
+ * Code for file monitoring.
+ */
+#include <glib/gstdio.h>
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <geanyplugin.h>
+#include "wb_globals.h"
+#include "workbench.h"
+#include "wb_monitor.h"
+#include "utils.h"
+
+struct S_WB_MONITOR
+{
+	GHashTable *monitors;
+};
+
+typedef struct
+{
+	GFileMonitor *monitor;
+	WB_PROJECT *prj;
+	WB_PROJECT_DIR *dir;
+}WB_MONITOR_ENTRY;
+
+
+/** Create a new, empty WB_MONITOR.
+ *
+ * @return Address of the new WB_MONITOR
+ * 
+ **/
+WB_MONITOR *wb_monitor_new(void)
+{
+	WB_MONITOR *monitor;
+
+	monitor = g_new0(WB_MONITOR, 1);
+
+	return monitor;
+}
+
+
+/* Create a new monitor entry */
+static WB_MONITOR_ENTRY *wb_monitor_entry_new (GFileMonitor *monitor,
+							WB_PROJECT *prj, WB_PROJECT_DIR *dir)
+{
+	WB_MONITOR_ENTRY *new;
+
+	new =  g_new0(WB_MONITOR_ENTRY, 1);
+	new->monitor = monitor;
+	new->prj = prj;
+	new->dir = dir;
+
+	return new;
+}
+
+
+/* Free a monitor entry */
+static void wb_monitor_entry_free (gpointer data)
+{
+	WB_MONITOR_ENTRY *entry = data;
+
+	if (data != NULL)
+	{
+		g_object_unref(entry->monitor);
+		g_free(entry);
+	}
+}
+
+
+/* Callback function for file monitoring. */
+static void wb_monitor_file_changed_cb(G_GNUC_UNUSED GFileMonitor *monitor,
+									   G_GNUC_UNUSED GFile *file,
+									   G_GNUC_UNUSED GFile *other_file,
+									   GFileMonitorEvent event,
+									   WB_MONITOR_ENTRY *entry)
+{
+	const gchar *event_string = NULL;
+	gchar *file_path, *other_file_path = NULL;
+
+	g_return_if_fail(entry != NULL);
+
+	g_message("%s: event: %d", G_STRFUNC, event);
+
+	file_path = g_file_get_path (file);
+	if (other_file != NULL)
+	{
+		other_file_path = g_file_get_path (other_file);
+	}
+	switch (event)
+	{
+		case G_FILE_MONITOR_EVENT_CREATED:
+			event_string = "FILE_CREATED";
+			workbench_process_add_file_event (wb_globals.opened_wb,
+				entry->prj, entry->dir, file_path);
+			break;
+
+		case G_FILE_MONITOR_EVENT_DELETED:
+			event_string = "FILE_DELETED";
+			workbench_process_remove_file_event (wb_globals.opened_wb,
+				entry->prj, entry->dir, file_path);
+			break;
+
+		case G_FILE_MONITOR_EVENT_RENAMED:
+			event_string = "FILE_RENAMED";
+			workbench_process_remove_file_event (wb_globals.opened_wb,
+				entry->prj, entry->dir, file_path);
+			workbench_process_add_file_event (wb_globals.opened_wb,
+				entry->prj, entry->dir, other_file_path);
+			break;
+
+		case G_FILE_MONITOR_EVENT_MOVED_IN:
+			event_string = "FILE_MOVED_IN";
+			workbench_process_add_file_event (wb_globals.opened_wb,
+				entry->prj, entry->dir, file_path);
+			break;
+
+		case G_FILE_MONITOR_EVENT_MOVED_OUT:
+			event_string = "FILE_MOVED_OUT";
+			workbench_process_remove_file_event (wb_globals.opened_wb,
+				entry->prj, entry->dir, file_path);
+			break;
+
+		default:
+			break;
+	}
+
+	if (event_string != NULL)
+	{
+		g_message("%s: Prj: \"%s\" Dir: \"%s\" %s: \"%s\"", G_STRFUNC, wb_project_get_name(entry->prj),
+			wb_project_dir_get_name(entry->dir), event_string, file_path);
+	}
+
+	g_free(file_path);
+	g_free(other_file_path);
+}
+
+
+/** Add a new file monitor
+ *
+ * Add a new file monitor for dirpath. The monitor will only be created
+ * if the settings option "Enable live monitor" is set to on and if
+ * no file monitor exists for dirpath already. If monitor creation fails,
+ * that means g_file_monitor_directory returns NULL, then a message is
+ * output on the statusbar.
+ * 
+ * @param monitor The global monitor management
+ * @param prj     The project to which dirpath belongs
+ * @param dir     The directory (WB_PROJECT_DIR) to which dirpath belongs
+ * @param dirpath The path of the directory
+ * 
+ **/
+void wb_monitor_add_dir(WB_MONITOR *monitor, WB_PROJECT *prj,
+						WB_PROJECT_DIR *dir, const gchar *dirpath)
+{
+	GFileMonitor *newmon;
+	GFile *file;
+	GError *error = NULL;
+	WB_MONITOR_ENTRY *entry;
+
+	g_return_if_fail(monitor != NULL);
+	g_return_if_fail(dir != NULL);
+	g_return_if_fail(dirpath != NULL);
+
+	if (workbench_get_enable_live_update(wb_globals.opened_wb) == FALSE)
+	{
+		/* Return if the feature is disabled. */
+		return;
+	}
+
+	if (monitor->monitors == NULL)
+	{
+		monitor->monitors = g_hash_table_new_full
+			(g_str_hash, g_str_equal, g_free, wb_monitor_entry_free);
+	}
+	if (g_hash_table_contains(monitor->monitors, dirpath))
+	{
+		/* A monitor for that path already exists,
+		   do not create another one. */
+		return;
+	}
+
+	/* Setup file monitor for directory */
+	file = g_file_new_for_path(dirpath);
+	newmon = g_file_monitor_directory
+		(file, G_FILE_MONITOR_WATCH_MOVES, NULL, &error);
+	if (newmon == NULL)
+	{
+		/* Create monitor failed. Report error. */
+		ui_set_statusbar(TRUE,
+			_("Could not setup file monitoring for directory: \"%s\". Error: %s"),
+			dirpath, error->message);
+		g_error_free (error);
+		return;
+	}
+	else
+	{
+		/* Add file monitor to hash table. */
+		entry = wb_monitor_entry_new(newmon, prj, dir);
+		g_hash_table_insert(monitor->monitors, (gpointer)g_strdup(dirpath), entry);
+
+		g_signal_connect(newmon, "changed",
+			G_CALLBACK(wb_monitor_file_changed_cb), entry);
+
+		/* ToDo: make rate limit configurable */
+		g_file_monitor_set_rate_limit(newmon, 5 * 1000);
+	}
+	g_object_unref(file);
+}
+
+
+/** Remove a file monitor
+ *
+ * Remove the file monitor for dirpath.
+ * 
+ * @param monitor The global monitor management
+ * @param dirpath The path of the directory
+ * 
+ **/
+gboolean wb_monitor_remove_dir(WB_MONITOR *monitor, const gchar *dirpath)
+{
+	if (monitor == NULL || dirpath == NULL)
+	{
+		return FALSE;
+	}
+
+	/* Free the entry. The hash table will call the destroy function
+	   wb_monitor_entry_free which is doing the work for us. */
+	return g_hash_table_remove(monitor->monitors, dirpath);
+}
+
+
+/** Free monitor management/all file monitors.
+ * 
+ * @param monitor The global monitor management
+ * 
+ **/
+void wb_monitor_free(WB_MONITOR *monitor)
+{
+	if (monitor != NULL)
+	{
+		if (monitor->monitors != NULL)
+		{
+			g_hash_table_unref(monitor->monitors);
+			monitor->monitors = NULL;
+		}
+	}
+}


Modified: workbench/src/wb_monitor.h
33 lines changed, 33 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018 LarsGit223
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __WB_MONITOR_H__
+#define __WB_MONITOR_H__
+
+#include <glib.h>
+#include "wb_project.h"
+
+typedef struct S_WB_MONITOR WB_MONITOR;
+
+WB_MONITOR *wb_monitor_new(void);
+void wb_monitor_add_dir(WB_MONITOR *monitor, WB_PROJECT *prj,
+						WB_PROJECT_DIR *dir, const gchar *dirpath);
+gboolean wb_monitor_remove_dir(WB_MONITOR *monitor, const gchar *dirpath);
+void wb_monitor_free(WB_MONITOR *monitor);
+
+#endif


Modified: workbench/src/wb_project.c
508 lines changed, 406 insertions(+), 102 deletions(-)
===================================================================
@@ -29,6 +29,7 @@
 #include <../../utils/src/filelist.h>
 #include "wb_globals.h"
 #include "wb_project.h"
+#include "sidebar.h"
 #include "utils.h"
 
 extern GeanyData *geany_data;
@@ -40,6 +41,13 @@ typedef enum
 	WB_PROJECT_TAG_PREFS_NO,
 }WB_PROJECT_TAG_PREFS;
 
+typedef struct
+{
+	WB_PROJECT_IDLE_ACTION_ID id;
+	gpointer param_a;
+	gpointer param_b;
+}WB_PROJECT_IDLE_ACTION;
+
 typedef struct
 {
 	GKeyFile *kf;
@@ -64,13 +72,21 @@ struct S_WB_PROJECT
 	gchar     *filename;
 	gchar     *name;
 	gboolean  modified;
-	GSList    *s_idle_add_funcs;
-	GSList    *s_idle_remove_funcs;
+	//GSList    *s_idle_add_funcs;
+	//GSList    *s_idle_remove_funcs;
 	GSList    *directories;  /* list of WB_PROJECT_DIR; */
 	WB_PROJECT_TAG_PREFS generate_tag_prefs;
 	GPtrArray *bookmarks;
 };
 
+typedef struct
+{
+	guint len;
+	const gchar *string;
+}WB_PROJECT_TEMP_DATA;
+
+static GSList *s_idle_actions = NULL;
+static void wb_project_dir_update_tags(WB_PROJECT_DIR *root);
 
 /** Set the projects modified marker.
  *
@@ -212,26 +228,26 @@ static gboolean match_basename(gconstpointer pft, gconstpointer user_data)
 
 
 /* Clear idle queue */
-static void wb_project_clear_idle_queue(GSList **queue)
+static void wb_project_clear_idle_queue(void)
 {
-	if (queue == NULL || *queue == NULL)
+	if (s_idle_actions == NULL)
 	{
 		return;
 	}
 
-	g_slist_free_full(*queue, g_free);
-	*queue = NULL;
+	g_slist_free_full(s_idle_actions, g_free);
+	s_idle_actions = NULL;
 }
 
 
 /* Create a new project dir with base path "utf8_base_dir" */
-static WB_PROJECT_DIR *wb_project_dir_new(const gchar *utf8_base_dir)
+static WB_PROJECT_DIR *wb_project_dir_new(WB_PROJECT *prj, const gchar *utf8_base_dir)
 {
 	guint offset;
 
 	if (utf8_base_dir == NULL)
 	{
-	    return NULL;
+		return NULL;
 	}
 	WB_PROJECT_DIR *dir = g_new0(WB_PROJECT_DIR, 1);
 	dir->base_dir = g_strdup(utf8_base_dir);
@@ -250,6 +266,7 @@ static WB_PROJECT_DIR *wb_project_dir_new(const gchar *utf8_base_dir)
 	}
 	dir->name = g_strdup(&(dir->base_dir[offset]));
 	dir->is_prj_base_dir = FALSE;
+
 	return dir;
 }
 
@@ -551,6 +568,200 @@ static guint wb_project_dir_rescan_int(WB_PROJECT *prj, WB_PROJECT_DIR *root)
 }
 
 
+/* Add a new file to the project directory and update the sidebar. */
+static void wb_project_dir_add_file_int(WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath)
+{
+	gboolean matches;
+	gchar **file_patterns = NULL;
+	SIDEBAR_CONTEXT context;
+	WB_MONITOR *monitor = NULL;
+
+	if (root->file_patterns && root->file_patterns[0])
+	{
+		file_patterns = root->file_patterns;
+	}
+
+	matches = gp_filelist_filepath_matches_patterns(filepath,
+		file_patterns, root->ignored_dirs_patterns, root->ignored_file_patterns);
+	if (!matches)
+	{
+		/* Ignore it. */
+		return;
+	}
+
+	/* Update file table and counters. */
+	g_hash_table_insert(root->file_table, g_strdup(filepath), NULL);
+	if (g_file_test(filepath, G_FILE_TEST_IS_DIR))
+	{
+		root->subdir_count++;
+		monitor = workbench_get_monitor(wb_globals.opened_wb);
+		wb_monitor_add_dir(monitor, prj, root, filepath);
+	}
+	else if (g_file_test(filepath, G_FILE_TEST_IS_REGULAR))
+	{
+		root->file_count++;
+	}
+
+	/* Update sidebar. */
+	memset(&context, 0, sizeof(context));
+	context.project = prj;
+	context.directory = root;
+	context.file = (gchar *)filepath;
+	sidebar_update(SIDEBAR_CONTEXT_FILE_ADDED, &context);
+
+	/* If the file is a directory we also have to manually add all files
+	   contained in it. */
+	if (monitor != NULL)
+	{
+		GSList *scanned, *elem = NULL;
+
+		scanned = gp_filelist_scan_directory_full(&(root->file_count), &(root->subdir_count),
+			filepath, file_patterns, root->ignored_dirs_patterns, root->ignored_file_patterns,
+			FILELIST_FLAG_ADD_DIRS);
+
+		foreach_slist(elem, scanned)
+		{
+			char *path = elem->data;
+
+			if (path)
+			{
+				wb_project_dir_add_file(prj, root, path);
+			}
+		}
+
+		g_slist_foreach(scanned, (GFunc) g_free, NULL);
+		g_slist_free(scanned);
+	}
+}
+
+/** Add a new file to the project directory and update the sidebar.
+ * 
+ * The file is only added if it matches the pattern settings.
+ *
+ * @param prj      The project to add it to.
+ * @param root     The directory to add it to.
+ * @param filepath The file to add.
+ *
+ **/
+void wb_project_dir_add_file(WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath)
+{
+	wb_project_dir_add_file_int(prj, root, filepath);
+	wb_project_add_idle_action(WB_PROJECT_IDLE_ACTION_ID_UPDATE_TAGS,
+		root, NULL);
+
+}
+
+
+/* Check if the filepath is equal for the length of the directory path in px_temp */
+static gboolean wb_project_dir_remove_child (gpointer key, gpointer value, gpointer user_data)
+{
+	WB_PROJECT_TEMP_DATA *px_temp;
+	TMSourceFile *sf;
+
+	px_temp = user_data;
+	if (strncmp(px_temp->string, key, px_temp->len) == 0)
+	{
+		/* We found a child of our removed directory.
+		   Remove it from the hash table. This will also free
+		   the tags. We do not need to update the sidebar as we
+		   already deleted the parent directory/node. */
+		sf = value;
+		if (sf != NULL)
+		{
+			tm_workspace_remove_source_file(sf);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+
+/** Remove a file from the project directory and update the sidebar.
+ * 
+ * If the file still exists, it is only removed if it matches the pattern settings.
+ *
+ * @param prj      The project to remove it from.
+ * @param root     The directory to remove it from.
+ * @param filepath The file to remove.
+ *
+ **/
+void wb_project_dir_remove_file(WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath)
+{
+	gboolean matches, was_dir;
+	gchar **file_patterns = NULL;
+	WB_MONITOR *monitor;
+
+	if (root->file_patterns && root->file_patterns[0])
+	{
+		file_patterns = root->file_patterns;
+	}
+
+	if (g_file_test(filepath, G_FILE_TEST_EXISTS))
+	{
+		matches = gp_filelist_filepath_matches_patterns(filepath,
+			file_patterns, root->ignored_dirs_patterns, root->ignored_file_patterns);
+	}
+	else
+	{
+		/* If the file does not exist any more, then always try to remove it. */
+		matches = TRUE;
+	}
+
+	if (matches)
+	{
+		SIDEBAR_CONTEXT context;
+		TMSourceFile *sf;
+
+		/* Update file table and counters. */
+		sf = g_hash_table_lookup (root->file_table, filepath);
+		if (sf != NULL)
+		{
+			tm_workspace_remove_source_file(sf);
+		}
+		g_hash_table_remove(root->file_table, filepath);
+
+		/* If the file already has been deleted, we cannot determine if it
+		   was a file or directory at this point. But the monitors will
+		   help us out, see code at end of function. */
+
+		/* Update sidebar. */
+		memset(&context, 0, sizeof(context));
+		context.project = prj;
+		context.directory = root;
+		context.file = (gchar *)filepath;
+		sidebar_update(SIDEBAR_CONTEXT_FILE_REMOVED, &context);
+	}
+
+	/* Remove the file monitor for filepath. This will only return TRUE
+	   if there is a file monitor for filepath and that means that file-
+	   path was a directory. So we can determine if filepath was a dir
+	   or not even if it has been deleted. */
+	monitor = workbench_get_monitor(wb_globals.opened_wb);
+	was_dir = wb_monitor_remove_dir(monitor, filepath);
+	if (was_dir)
+	{
+		WB_PROJECT_TEMP_DATA x_temp;
+
+		x_temp.len = strlen(filepath);
+		x_temp.string = filepath;
+		g_hash_table_foreach_remove(root->file_table,
+			wb_project_dir_remove_child, &x_temp);
+
+		if (root->subdir_count > 0)
+		{
+			root->subdir_count--;
+		}
+	}
+	else
+	{
+		if (root->file_count > 0)
+		{
+			root->file_count--;
+		}
+	}
+}
+
+
 /* 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
@@ -631,6 +842,37 @@ static void wb_project_dir_regenerate_tags(WB_PROJECT_DIR *root, G_GNUC_UNUSED g
 }
 
 
+/* Update tags for new files */
+static void wb_project_dir_update_tags(WB_PROJECT_DIR *root)
+{
+	GHashTableIter iter;
+	gpointer key, value;
+	GPtrArray *source_files;
+
+	source_files = g_ptr_array_new();
+	g_hash_table_iter_init(&iter, root->file_table);
+	while (g_hash_table_iter_next(&iter, &key, &value))
+	{
+		if (value == NULL)
+		{
+			TMSourceFile *sf;
+			gchar *utf8_path = key;
+			gchar *locale_path = utils_get_locale_from_utf8(utf8_path);
+
+			sf = tm_source_file_new(locale_path, filetypes_detect(utf8_path)->name);
+			if (sf && !document_find_by_filename(utf8_path))
+				g_ptr_array_add(source_files, sf);
+
+			g_hash_table_insert(root->file_table, g_strdup(utf8_path), sf);
+			g_free(locale_path);
+		}
+	}
+
+	tm_workspace_add_source_files(source_files);
+	g_ptr_array_free(source_files, TRUE);
+}
+
+
 /** Rescan/update the file list of a project dir.
  *
  * @param project   The project to which directory belongs
@@ -661,14 +903,14 @@ void wb_project_rescan(WB_PROJECT *prj)
 {
 	GSList *elem = NULL;
 	guint filenum = 0;
+	GHashTableIter iter;
 
 	if (!prj)
 	{
 		return;
 	}
 
-	wb_project_clear_idle_queue(&prj->s_idle_add_funcs);
-	wb_project_clear_idle_queue(&prj->s_idle_remove_funcs);
+	wb_project_clear_idle_queue();
 
 	foreach_slist(elem, prj->directories)
 	{
@@ -679,6 +921,37 @@ void wb_project_rescan(WB_PROJECT *prj)
 	{
 		g_slist_foreach(prj->directories, (GFunc)wb_project_dir_regenerate_tags, NULL);
 	}
+
+	/* Create file monitors for directories. */
+	if (workbench_get_enable_live_update(wb_globals.opened_wb) == TRUE)
+	{
+		WB_MONITOR *monitor;
+
+		monitor = workbench_get_monitor(wb_globals.opened_wb);
+		foreach_slist(elem, prj->directories)
+		{
+			gpointer path, value;
+			GHashTable *filehash;
+			gchar *abs_path;
+
+			/* First add monitor for base dir */
+			abs_path = get_combined_path(wb_project_get_filename(prj),
+										wb_project_dir_get_base_dir(elem->data));
+			wb_monitor_add_dir(monitor, prj, elem->data, abs_path);
+			g_free(abs_path);
+
+			/* Now add all dirs in file table */
+			filehash = ((WB_PROJECT_DIR *)elem->data)->file_table;
+			g_hash_table_iter_init(&iter, filehash);
+			while (g_hash_table_iter_next (&iter, &path, &value))
+			{
+				if (path != NULL && g_file_test(path, G_FILE_TEST_IS_DIR))
+				{
+					wb_monitor_add_dir(monitor, prj, elem->data, path);
+				}
+			}
+		}
+	}
 }
 
 
@@ -732,141 +1005,144 @@ gboolean wb_project_file_is_included(WB_PROJECT *prj, const gchar *filename)
 }
 
 
-static gboolean add_tm_idle(gpointer foo)
+/* Add single tm file. Only to be called on-idle! */
+static void wb_project_add_single_tm_file(WB_PROJECT *prj, const gchar *filename)
 {
-	WB_PROJECT *prj;
-	GSList *elem2 = NULL;
-
-	prj = (WB_PROJECT *)foo;
-	if (prj == NULL || prj->s_idle_add_funcs == NULL)
-	{
-		return FALSE;
-	}
+	GSList *elem = NULL;
 
-	foreach_slist (elem2, prj->s_idle_add_funcs)
+	foreach_slist (elem, prj->directories)
 	{
-		GSList *elem = NULL;
-		gchar *utf8_fname = elem2->data;
+		WB_PROJECT_DIR *dir = elem->data;
+		TMSourceFile *sf = g_hash_table_lookup(dir->file_table, filename);
 
-		foreach_slist (elem, prj->directories)
+		if (sf != NULL && !document_find_by_filename(filename))
 		{
-			WB_PROJECT_DIR *dir = elem->data;
-			TMSourceFile *sf = g_hash_table_lookup(dir->file_table, utf8_fname);
-
-			if (sf != NULL && !document_find_by_filename(utf8_fname))
-			{
-				tm_workspace_add_source_file(sf);
-				break;  /* single file representation in TM is enough */
-			}
+			tm_workspace_add_source_file(sf);
+			break;  /* single file representation in TM is enough */
 		}
 	}
-
-	wb_project_clear_idle_queue(&(prj->s_idle_add_funcs));
-
-	return FALSE;
 }
 
 
-/* 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.
+/* 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 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 wb_project_add_single_tm_file(WB_PROJECT *prj, const gchar *filename)
+ * 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 */
+static void wb_project_remove_single_tm_file(WB_PROJECT *prj, const gchar *filename)
 {
-	if (prj == NULL)
-	{
-		return;
-	}
+	GSList *elem = NULL;
 
-	if (prj->s_idle_add_funcs == NULL)
+	foreach_slist (elem, prj->directories)
 	{
-		plugin_idle_add(wb_globals.geany_plugin, (GSourceFunc)add_tm_idle, prj);
-	}
+		WB_PROJECT_DIR *dir = elem->data;
+		TMSourceFile *sf = g_hash_table_lookup(dir->file_table, filename);
 
-	prj->s_idle_add_funcs = g_slist_prepend(prj->s_idle_add_funcs, g_strdup(filename));
+		if (sf != NULL)
+		{
+			tm_workspace_remove_source_file(sf);
+		}
+	}
 }
 
 
-static gboolean remove_tm_idle(gpointer foo)
+/* On-idle callback function. */
+static gboolean wb_project_on_idle_callback(gpointer foo)
 {
-	WB_PROJECT *prj;
-	GSList *elem2 = NULL;
-
-	prj = (WB_PROJECT *)foo;
-	if (prj == NULL || prj->s_idle_remove_funcs == NULL)
-	{
-		return FALSE;
-	}
+	GSList *elem = NULL;
+	WB_PROJECT_IDLE_ACTION *action;
 
-	foreach_slist (elem2, prj->s_idle_remove_funcs)
+	foreach_slist (elem, s_idle_actions)
 	{
-
-		GSList *elem = NULL;
-		gchar *utf8_fname = elem2->data;
-
-		foreach_slist (elem, prj->directories)
+		action = elem->data;
+		switch (action->id)
 		{
-			WB_PROJECT_DIR *dir = elem->data;
-			TMSourceFile *sf = g_hash_table_lookup(dir->file_table, utf8_fname);
+			case WB_PROJECT_IDLE_ACTION_ID_ADD_SINGLE_TM_FILE:
+				if (action->param_a != NULL && action->param_b != NULL)
+				{
+					wb_project_add_single_tm_file
+						(action->param_a, action->param_b);
+					g_free(action->param_b);
+				}
+			break;
 
-			if (sf != NULL)
-			{
-				tm_workspace_remove_source_file(sf);
-			}
+			case WB_PROJECT_IDLE_ACTION_ID_REMOVE_SINGLE_TM_FILE:
+				if (action->param_a != NULL && action->param_b != NULL)
+				{
+					wb_project_remove_single_tm_file
+						(action->param_a, action->param_b);
+					g_free(action->param_b);
+				}
+			break;
+
+			case WB_PROJECT_IDLE_ACTION_ID_UPDATE_TAGS:
+				if (action->param_a != NULL)
+				{
+					wb_project_dir_update_tags(action->param_a);
+				}
+			break;
 		}
 	}
 
-	wb_project_clear_idle_queue(&(prj->s_idle_remove_funcs));
+	wb_project_clear_idle_queue();
+
 	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)
+/** Add a new idle action to the list.
  *
- * 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 wb_project_remove_single_tm_file(WB_PROJECT *prj, const gchar *utf8_filename)
+ * The function allocates a new WB_PROJECT_IDLE_ACTION structure and fills
+ * in the values passed. On-idle genay will then call wb_project_on_idle_callback
+ * and that function will call the function related to the action ID
+ * and pass the relevant parameters to it.
+ * 
+ * @param id The action to execute on-idle
+ * @param param_a Parameter A
+ * @param param_a Parameter B
+ *
+ **/
+void wb_project_add_idle_action(WB_PROJECT_IDLE_ACTION_ID id, gpointer param_a, gpointer param_b)
 {
-	if (prj == NULL)
-	{
-		return;
-	}
+	WB_PROJECT_IDLE_ACTION *action;
 
-	if (prj->s_idle_remove_funcs == NULL)
+	action = g_new0(WB_PROJECT_IDLE_ACTION, 1);
+	action->id = id;
+	action->param_a = param_a;
+	action->param_b = param_b;
+
+	if (s_idle_actions == NULL)
 	{
-		plugin_idle_add(wb_globals.geany_plugin, (GSourceFunc)remove_tm_idle, prj);
+		plugin_idle_add(wb_globals.geany_plugin, (GSourceFunc)wb_project_on_idle_callback, NULL);
 	}
-	prj->s_idle_remove_funcs = g_slist_prepend(prj->s_idle_remove_funcs, g_strdup(utf8_filename));
+
+	s_idle_actions = g_slist_prepend(s_idle_actions, action);
 }
 
 
 /* Add a directory to the project */
 static WB_PROJECT_DIR *wb_project_add_directory_int(WB_PROJECT *prj, const gchar *dirname, gboolean rescan)
 {
-    if (prj != NULL)
-    {
-		WB_PROJECT_DIR *new_dir = wb_project_dir_new(dirname);
+	if (prj != NULL)
+	{
+		WB_PROJECT_DIR *new_dir = wb_project_dir_new(prj, dirname);
 
-        if (prj->directories != NULL)
-        {
+		if (prj->directories != NULL)
+		{
 			GSList *lst = prj->directories->next;
 			lst = g_slist_prepend(lst, new_dir);
 			lst = g_slist_sort(lst, (GCompareFunc)wb_project_dir_comparator);
 			prj->directories->next = lst;
-	    }
-	    else
-	    {
+		}
+		else
+		{
 			prj->directories = g_slist_append(prj->directories, new_dir);
 		}
 
@@ -875,8 +1151,8 @@ static WB_PROJECT_DIR *wb_project_add_directory_int(WB_PROJECT *prj, const gchar
 			wb_project_rescan(prj);
 		}
 		return new_dir;
-    }
-    return NULL;
+	}
+	return NULL;
 }
 
 
@@ -921,7 +1197,7 @@ gboolean wb_project_remove_directory (WB_PROJECT *prj, WB_PROJECT_DIR *dir)
 		wb_project_rescan(prj);
 		prj->modified = TRUE;
 	}
-    return FALSE;
+	return FALSE;
 }
 
 
@@ -1498,3 +1774,31 @@ void wb_project_free(WB_PROJECT *prj)
 	g_free(prj->name);
 	g_free(prj);
 }
+
+
+/** Check if dir is a valid reference to a directory of prj.
+ *
+ * @param prj The project to search in
+ * @param dir The directory to search for
+ * @return TRUE  dir is a directory in prj
+ *         FALSE dir was not found in prj
+ **/
+gboolean wb_project_is_valid_dir_reference(WB_PROJECT *prj, WB_PROJECT_DIR *dir)
+{
+	GSList *elem = NULL;
+
+	if (prj == NULL)
+	{
+		return FALSE;
+	}
+
+	foreach_slist(elem, prj->directories)
+	{
+		if (elem->data == dir)
+		{
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}


Modified: workbench/src/wb_project.h
14 lines changed, 12 insertions(+), 2 deletions(-)
===================================================================
@@ -21,6 +21,13 @@
 
 #include <glib.h>
 
+typedef enum
+{
+	WB_PROJECT_IDLE_ACTION_ID_ADD_SINGLE_TM_FILE,
+	WB_PROJECT_IDLE_ACTION_ID_REMOVE_SINGLE_TM_FILE,
+	WB_PROJECT_IDLE_ACTION_ID_UPDATE_TAGS,
+}WB_PROJECT_IDLE_ACTION_ID;
+
 typedef struct S_WB_PROJECT WB_PROJECT;
 typedef struct S_WB_PROJECT_DIR WB_PROJECT_DIR;
 
@@ -38,8 +45,7 @@ gboolean wb_project_add_directory(WB_PROJECT *prj, const gchar *dirname);
 gboolean wb_project_remove_directory (WB_PROJECT *prj, WB_PROJECT_DIR *dir);
 void wb_project_rescan(WB_PROJECT *prj);
 gboolean wb_project_file_is_included(WB_PROJECT *prj, const gchar *filename);
-void wb_project_add_single_tm_file(WB_PROJECT *prj, const gchar *filename);
-void wb_project_remove_single_tm_file(WB_PROJECT *prj, const gchar *filename);
+gboolean wb_project_is_valid_dir_reference(WB_PROJECT *prj, WB_PROJECT_DIR *dir);
 
 void wb_project_dir_set_is_prj_base_dir (WB_PROJECT_DIR *directory, gboolean value);
 gboolean wb_project_dir_get_is_prj_base_dir (WB_PROJECT_DIR *directory);
@@ -55,6 +61,8 @@ gboolean wb_project_dir_set_ignored_file_patterns (WB_PROJECT_DIR *directory, gc
 guint wb_project_dir_rescan(WB_PROJECT *prj, WB_PROJECT_DIR *root);
 gchar *wb_project_dir_get_info (WB_PROJECT_DIR *dir);
 gboolean wb_project_dir_file_is_included(WB_PROJECT_DIR *dir, const gchar *filename);
+void wb_project_dir_add_file(WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath);
+void wb_project_dir_remove_file(WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath);
 
 gboolean wb_project_add_bookmark(WB_PROJECT *prj, const gchar *filename);
 gboolean wb_project_remove_bookmark(WB_PROJECT *prj, const gchar *filename);
@@ -67,4 +75,6 @@ gboolean wb_project_load(WB_PROJECT *prj, gchar *filename, GError **error);
 
 gchar *wb_project_get_info (WB_PROJECT *prj);
 
+void wb_project_add_idle_action(WB_PROJECT_IDLE_ACTION_ID id, gpointer param_a, gpointer param_b);
+
 #endif


Modified: workbench/src/workbench.c
224 lines changed, 223 insertions(+), 1 deletions(-)
===================================================================
@@ -27,7 +27,9 @@
 #include <glib/gstdio.h>
 #include <geanyplugin.h>
 #include "workbench.h"
+#include "sidebar.h"
 #include "wb_project.h"
+#include "wb_monitor.h"
 #include "utils.h"
 
 typedef struct
@@ -45,8 +47,10 @@ struct S_WORKBENCH
 	gchar     *name;
 	gboolean  modified;
 	gboolean  rescan_projects_on_open;
+	gboolean  enable_live_update;
 	GPtrArray *projects;
 	GPtrArray *bookmarks;
+	WB_MONITOR *monitor;
 };
 
 /* Create a new, empty workbench project entry */
@@ -81,12 +85,14 @@ WORKBENCH *workbench_new(void)
 {
 	WORKBENCH *new_wb;
 
-	new_wb = g_new(WORKBENCH, 1);
+	new_wb = g_new0(WORKBENCH, 1);
 	memset(new_wb, 0, sizeof(*new_wb));
 	new_wb->modified = FALSE;
 	new_wb->rescan_projects_on_open = TRUE;
+	new_wb->enable_live_update = TRUE;
 	new_wb->projects = g_ptr_array_new();
 	new_wb->bookmarks = g_ptr_array_new();
+	new_wb->monitor = wb_monitor_new();
 
 	return new_wb;
 }
@@ -117,6 +123,7 @@ void workbench_free(WORKBENCH *wb)
 		}
 	}
 
+	wb_monitor_free(wb->monitor);
 	g_ptr_array_free (wb->projects, TRUE);
 	g_free(wb);
 }
@@ -209,6 +216,42 @@ gboolean workbench_get_rescan_projects_on_open(WORKBENCH *wb)
 }
 
 
+/** Set the "Enable live update" option.
+ *
+ * @param wb    The workbench
+ * @param value The value to set
+ *
+ **/
+void workbench_set_enable_live_update(WORKBENCH *wb, gboolean value)
+{
+	if (wb != NULL)
+	{
+		if (wb->enable_live_update != value)
+		{
+			wb->enable_live_update = value;
+			wb->modified = TRUE;
+		}
+	}
+}
+
+
+/** Get the "Enable live update" option.
+ *
+ * @param wb The workbench
+ * @return TRUE = use file monitoring for live update of the file list,
+ *         FALSE = don't
+ *
+ **/
+gboolean workbench_get_enable_live_update(WORKBENCH *wb)
+{
+	if (wb != NULL)
+	{
+		return wb->enable_live_update;
+	}
+	return FALSE;
+}
+
+
 /** Set the filename.
  *
  * @param wb       The workbench
@@ -256,6 +299,22 @@ const gchar *workbench_get_filename(WORKBENCH *wb)
 }
 
 
+/** Get the monitor.
+ *
+ * @param wb The workbench
+ * @return Reference to the WB_MONITOR (can be NULL).
+ *
+ **/
+WB_MONITOR *workbench_get_monitor(WORKBENCH *wb)
+{
+	if (wb != NULL)
+	{
+		return wb->monitor;
+	}
+	return NULL;
+}
+
+
 /** Get the name.
  *
  * @param wb The workbench
@@ -590,6 +649,7 @@ gboolean workbench_save(WORKBENCH *wb, GError **error)
 		g_key_file_set_string(kf, "General", "filetype", "workbench");
 		g_key_file_set_string(kf, "General", "version", "1.0");
 		g_key_file_set_boolean(kf, "General", "RescanProjectsOnOpen", wb->rescan_projects_on_open);
+		g_key_file_set_boolean(kf, "General", "EnableLiveUpdate", wb->enable_live_update);
 
 		/* Save Workbench bookmarks as string list */
 		boomarks_size = workbench_get_bookmarks_count(wb);
@@ -713,6 +773,16 @@ gboolean workbench_load(WORKBENCH *wb, const gchar *filename, GError **error)
 		}
 		workbench_set_filename(wb, filename);
 		wb->rescan_projects_on_open = g_key_file_get_boolean(kf, "General", "RescanProjectsOnOpen", error);
+		if (g_key_file_has_key (kf, "General", "EnableLiveUpdate", error))
+		{
+			wb->enable_live_update = g_key_file_get_boolean(kf, "General", "EnableLiveUpdate", error);
+		}
+		else
+		{
+			/* Not found. Might happen if the workbench was created with an older version of the plugin.
+			   Initialize with TRUE. */
+			wb->enable_live_update = TRUE;
+		}
 
 		/* Load Workbench bookmarks from string list */
 		bookmarks_strings = g_key_file_get_string_list (kf, "General", "Bookmarks", NULL, error);
@@ -801,3 +871,155 @@ gboolean workbench_load(WORKBENCH *wb, const gchar *filename, GError **error)
 
 	return success;
 }
+
+
+/* Check if the given pointers are still valid references. */
+static gboolean workbench_references_are_valid(WORKBENCH *wb, WB_PROJECT *prj, WB_PROJECT_DIR *dir)
+{
+	guint index;
+	WB_PROJECT_ENTRY *entry;
+
+	if (wb == NULL)
+	{
+		return FALSE;
+	}
+
+	/* Try to find the project. */
+	for (index = 0 ; index < wb->projects->len ; index++)
+	{
+		entry = g_ptr_array_index(wb->projects, index);
+		if (((WB_PROJECT_ENTRY *)entry)->project == prj)
+		{
+			break;
+		}
+	}
+	if (index >= wb->projects->len)
+	{
+		return FALSE;
+	}
+
+	/* Project exists in this workbench, let the project validate
+	   the directory. */
+	return wb_project_is_valid_dir_reference(prj, dir);
+}
+
+
+/** Process the add file event.
+ *
+ * The function processes the add file event. The pointers are checked for
+ * validity and on success the task is passed on to the project dir. file can
+ * be the path of a regular file or a directory.
+ *
+ * @param wb   The workbench
+ * @param prj  The project
+ * @param dir  The directory
+ * @param file The new file to add to project/directory
+ *
+ **/
+void workbench_process_add_file_event(WORKBENCH *wb, WB_PROJECT *prj, WB_PROJECT_DIR *dir, const gchar *file)
+{
+	if (workbench_references_are_valid(wb, prj, dir) == FALSE)
+	{
+		/* Should not happen, log a message and return. */
+		g_message("%s: invalid references: wb: %p, prj: %p, dir: %p",
+			G_STRFUNC, wb, prj, dir);
+		return;
+	}
+
+	wb_project_dir_add_file(prj, dir, file);
+}
+
+
+/** Process the remove file event.
+ *
+ * The function processes the remove file event. The pointers are checked for
+ * validity and on success the task is passed on to the project dir. file can
+ * be the path of a regular file or a directory.
+ *
+ * @param wb   The workbench
+ * @param prj  The project
+ * @param dir  The directory
+ * @param file The file to remove from project/directory
+ *
+ **/
+void workbench_process_remove_file_event(WORKBENCH *wb, WB_PROJECT *prj, WB_PROJECT_DIR *dir, const gchar *file)
+{
+	if (workbench_references_are_valid(wb, prj, dir) == FALSE)
+	{
+		/* Should not happen, log a message and return. */
+		g_message("%s: invalid references: wb: %p, prj: %p, dir: %p",
+			G_STRFUNC, wb, prj, dir);
+		return;
+	}
+
+	wb_project_dir_remove_file(prj, dir, file);
+}
+
+
+/* Foreach callback function for creating file monitors. */
+static void workbench_enable_live_update_foreach_cb(SIDEBAR_CONTEXT *context,
+													gpointer userdata)
+{
+	gchar *dirpath = NULL;
+	gchar *abs_path = NULL;
+	WB_MONITOR *monitor;
+
+	if (context->project != NULL && context->directory != NULL)
+	{
+		if (context->subdir != NULL)
+		{
+			dirpath = context->subdir;
+		}
+		else
+		{
+			abs_path = get_combined_path(wb_project_get_filename(context->project),
+										wb_project_dir_get_base_dir(context->directory));
+			dirpath = abs_path;
+		}
+	}
+
+	if (dirpath != NULL)
+	{
+		monitor = userdata;
+		wb_monitor_add_dir(monitor, context->project, context->directory, dirpath);
+	}
+
+	g_free(abs_path);
+}
+
+
+/** Enable live update.
+ *
+ * The function enables live update of the workbench by creating file
+ * monitors for all directories in the sidebars file tree.
+ *
+ * @param wb       The workbench
+ *
+ **/
+void workbench_enable_live_update(WORKBENCH *wb)
+{
+	if (wb != NULL)
+	{
+		sidebar_call_foreach(DATA_ID_DIRECTORY,
+			workbench_enable_live_update_foreach_cb, wb->monitor);
+		sidebar_call_foreach(DATA_ID_SUB_DIRECTORY,
+			workbench_enable_live_update_foreach_cb, wb->monitor);
+	}
+}
+
+
+/** Disable live update.
+ *
+ * The function disables live update of the workbench by freeing all
+ * file monitors.
+ *
+ * @param wb       The workbench
+ *
+ **/
+void workbench_disable_live_update(WORKBENCH *wb)
+{
+	if (wb != NULL)
+	{
+		wb_monitor_free(wb->monitor);
+	}
+}


Modified: workbench/src/workbench.h
11 lines changed, 11 insertions(+), 0 deletions(-)
===================================================================
@@ -21,6 +21,7 @@
 
 #include <glib.h>
 #include "wb_project.h"
+#include "wb_monitor.h"
 
 typedef enum
 {
@@ -37,8 +38,12 @@ void workbench_free(WORKBENCH *wb);
 gboolean workbench_is_empty(WORKBENCH *wb);
 guint workbench_get_project_count(WORKBENCH *wb);
 gboolean workbench_is_modified(WORKBENCH *wb);
+
 void workbench_set_rescan_projects_on_open(WORKBENCH *wb, gboolean value);
 gboolean workbench_get_rescan_projects_on_open(WORKBENCH *wb);
+void workbench_set_enable_live_update(WORKBENCH *wb, gboolean value);
+gboolean workbench_get_enable_live_update(WORKBENCH *wb);
+
 WB_PROJECT *workbench_get_project_at_index(WORKBENCH *wb, guint index);
 PROJECT_ENTRY_STATUS workbench_get_project_status_at_index(WORKBENCH *wb, guint index);
 PROJECT_ENTRY_STATUS workbench_get_project_status_by_address(WORKBENCH *wb, WB_PROJECT *address);
@@ -49,6 +54,7 @@ WB_PROJECT *workbench_file_is_included (WORKBENCH *wb, const gchar *filename);
 void workbench_set_filename(WORKBENCH *wb, const gchar *filename);
 const gchar *workbench_get_filename(WORKBENCH *wb);
 gchar *workbench_get_name(WORKBENCH *wb);
+WB_MONITOR *workbench_get_monitor(WORKBENCH *wb);
 
 gboolean workbench_save(WORKBENCH *wb, GError **error);
 gboolean workbench_load(WORKBENCH *wb, const gchar *filename, GError **error);
@@ -58,4 +64,9 @@ gboolean workbench_remove_bookmark(WORKBENCH *wb, const gchar *filename);
 gchar *workbench_get_bookmark_at_index (WORKBENCH *wb, guint index);
 guint workbench_get_bookmarks_count(WORKBENCH *wb);
 
+void workbench_process_add_file_event(WORKBENCH *wb, WB_PROJECT *prj, WB_PROJECT_DIR *dir, const gchar *file);
+void workbench_process_remove_file_event(WORKBENCH *wb, WB_PROJECT *prj, WB_PROJECT_DIR *dir, const gchar *file);
+void workbench_enable_live_update(WORKBENCH *wb);
+void workbench_disable_live_update(WORKBENCH *wb);
+
 #endif



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