[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, ¤t, -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, ¤t, -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, ¤tid, -1);
+ gtk_tree_model_get(foreach_cntxt->model, iter, FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, ¤t, -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