Branch: refs/heads/master Author: Frank Lanitz frank@frank.uvena.de Committer: GitHub noreply@github.com Date: Sun, 04 Mar 2018 06:24:29 UTC Commit: da2dc1ef31bfb3a6fe13929d0095952d3a5b5b4c https://github.com/geany/geany-plugins/commit/da2dc1ef31bfb3a6fe13929d009595...
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).
plugins-commits@lists.geany.org