[geany/geany-plugins] da2dc1: Merge pull request #695 from LarsGit223/wb-liveupdate

Frank Lanitz git-noreply at xxxxx
Sun Mar 4 06:24:29 UTC 2018


Branch:      refs/heads/master
Author:      Frank Lanitz <frank at frank.uvena.de>
Committer:   GitHub <noreply at github.com>
Date:        Sun, 04 Mar 2018 06:24:29 UTC
Commit:      da2dc1ef31bfb3a6fe13929d0095952d3a5b5b4c
             https://github.com/geany/geany-plugins/commit/da2dc1ef31bfb3a6fe13929d0095952d3a5b5b4c

Log Message:
-----------
Merge pull request #695 from LarsGit223/wb-liveupdate

workbench: support live update. Closes #659.


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