From 6dfe1b64c1fa5f4a51738aaa1daa9b7cf8e32c47 Mon Sep 17 00:00:00 2001 From: LarsGit223 Date: Thu, 27 Jun 2019 21:40:54 +0200 Subject: [PATCH] workbench: added option to let git decide which files to display In the directory settings it is now possible to choose between a workbench or a git file filter. The later is new and lets git decide which files to display depending on the contents of the .gitignore file. This also adds a new function to the utils-lib called 'gp_filelist_scan_directory_callback()'. --- build/workbench.m4 | 3 + utils/src/filelist.c | 109 +++++++++++- utils/src/filelist.h | 5 + workbench/README | 8 +- workbench/src/Makefile.am | 3 +- workbench/src/dialogs.c | 227 ++++++++++++++++++------- workbench/src/dialogs.h | 2 +- workbench/src/plugin_main.c | 16 +- workbench/src/popup_menu.c | 2 +- workbench/src/utils.c | 13 ++ workbench/src/utils.h | 1 + workbench/src/wb_project.c | 318 ++++++++++++++++++++++++++++++++---- workbench/src/wb_project.h | 9 + 13 files changed, 617 insertions(+), 99 deletions(-) diff --git a/build/workbench.m4 b/build/workbench.m4 index 0a104ca73..33e445bf9 100644 --- a/build/workbench.m4 +++ b/build/workbench.m4 @@ -3,6 +3,9 @@ AC_DEFUN([GP_CHECK_WORKBENCH], GP_ARG_DISABLE([Workbench], [auto]) GP_CHECK_UTILSLIB([Workbench]) + GP_CHECK_PLUGIN_DEPS([Workbench], [WORKBENCH], + [libgit2 >= 0.21]) + GP_COMMIT_PLUGIN_STATUS([Workbench]) AC_CONFIG_FILES([ workbench/Makefile diff --git a/utils/src/filelist.c b/utils/src/filelist.c index ff9b66ef1..663e02638 100644 --- a/utils/src/filelist.c +++ b/utils/src/filelist.c @@ -39,6 +39,16 @@ typedef struct ScanDirParams; +typedef struct +{ + GSList *filelist; + GHashTable *visited_paths; + void (*callback)(const gchar *path, gboolean *add, gboolean *enter, void *userdata); + void *userdata; +} +ScanDirParamsCallback; + + /** Get precompiled patterns. * * The function builds the precompiled patterns for @a patterns and returns them @@ -48,7 +58,7 @@ ScanDirParams; * @return Pointer to GSList of patterns or NULL if patterns == NULL * **/ -static GSList *filelist_get_precompiled_patterns(gchar **patterns) +GSList *filelist_get_precompiled_patterns(gchar **patterns) { guint i; GSList *pattern_list = NULL; @@ -74,7 +84,7 @@ static GSList *filelist_get_precompiled_patterns(gchar **patterns) * @return TRUE if str matches the pattern, FALSE otherwise * **/ -static gboolean filelist_patterns_match(GSList *patterns, const gchar *str) +gboolean filelist_patterns_match(GSList *patterns, const gchar *str) { GSList *elem = NULL; foreach_slist (elem, patterns) @@ -360,3 +370,98 @@ gboolean gp_filelist_filepath_matches_patterns(const gchar *filepath, gchar **fi return match; } + + +/* Scan directory searchdir. Let a callback-function decide which files + to add and which directories to enter/crawl. */ +static void filelist_scan_directory_callback_int(const gchar *searchdir, ScanDirParamsCallback *params) +{ + GDir *dir; + gchar *locale_path = utils_get_locale_from_utf8(searchdir); + gchar *real_path = utils_get_real_path(locale_path); + + dir = g_dir_open(locale_path, 0, NULL); + if (!dir || !real_path || g_hash_table_lookup(params->visited_paths, real_path)) + { + if (dir != NULL) + { + g_dir_close(dir); + } + g_free(locale_path); + g_free(real_path); + return; + } + + g_hash_table_insert(params->visited_paths, real_path, GINT_TO_POINTER(1)); + + while (TRUE) + { + const gchar *locale_name; + gchar *locale_filename, *utf8_filename, *utf8_name; + gboolean add, enter; + + locale_name = g_dir_read_name(dir); + if (!locale_name) + { + break; + } + + utf8_name = utils_get_utf8_from_locale(locale_name); + locale_filename = g_build_filename(locale_path, locale_name, NULL); + utf8_filename = utils_get_utf8_from_locale(locale_filename); + + /* Call user callback. */ + params->callback(locale_filename, &add, &enter, params->userdata); + + /* Should the file/path be added to the list? */ + if (add) + { + params->filelist = g_slist_prepend(params->filelist, g_strdup(utf8_filename)); + } + + /* If the path is a directory, should it be entered? */ + if (enter && g_file_test(locale_filename, G_FILE_TEST_IS_DIR)) + { + filelist_scan_directory_callback_int(utf8_filename, params); + } + + g_free(utf8_filename); + g_free(locale_filename); + g_free(utf8_name); + } + + g_dir_close(dir); + g_free(locale_path); +} + + +/** Scan a directory and return a list of files and directories. + * + * The function scans directory searchdir and returns a list of files. + * The list will only include files for which the callback function returned + * 'add == TRUE'. Sub-directories will only be scanned if the callback function + * returned 'enter == TRUE'. + * + * @param searchdir Directory which shall be scanned + * @param callback Function to be called to decide which files to add to + * the list and to decide which sub-directories to enter + * or to ignore. + * @param userdata A pointer which is transparently passed through + * to the callback function. + * @return GSList of matched files + * + **/ +GSList *gp_filelist_scan_directory_callback(const gchar *searchdir, + void (*callback)(const gchar *path, gboolean *add, gboolean *enter, void *userdata), + void *userdata) +{ + ScanDirParamsCallback params = { 0 }; + + params.callback = callback; + params.userdata = userdata; + params.visited_paths = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + filelist_scan_directory_callback_int(searchdir, ¶ms); + g_hash_table_destroy(params.visited_paths); + + return params.filelist; +} diff --git a/utils/src/filelist.h b/utils/src/filelist.h index 5865c2f17..98334ca8b 100644 --- a/utils/src/filelist.h +++ b/utils/src/filelist.h @@ -28,12 +28,17 @@ typedef enum FILELIST_FLAG_ADD_DIRS = 1, }FILELIST_FLAG; +GSList *filelist_get_precompiled_patterns(gchar **patterns); +gboolean filelist_patterns_match(GSList *patterns, const gchar *str); GSList *gp_filelist_scan_directory(guint *files, guint *folders, const gchar *searchdir, gchar **file_patterns, gchar **ignored_dirs_patterns, gchar **ignored_file_patterns); GSList *gp_filelist_scan_directory_full(guint *files, guint *folders, const gchar *searchdir, gchar **file_patterns, gchar **ignored_dirs_patterns, gchar **ignored_file_patterns, guint flags); gboolean gp_filelist_filepath_matches_patterns(const gchar *filepath, gchar **file_patterns, gchar **ignored_dirs_patterns, gchar **ignored_file_patterns); +GSList *gp_filelist_scan_directory_callback(const gchar *searchdir, + void (*callback)(const gchar *path, gboolean *add, gboolean *enter, void *userdata), + void *userdata); G_END_DECLS diff --git a/workbench/README b/workbench/README index 61f13d7fb..6f45be0b8 100644 --- a/workbench/README +++ b/workbench/README @@ -93,8 +93,12 @@ These are the available items: **Directory settings** Select this item to change the directory settings. It is only available if you right clicked inside of a project directory. In the directory - settings you can set a filter which controls the files and sub-directories - that will be displayed or not. + settings you can set filters which control the files and sub-directories + that will be displayed or not. You can choose between a filter mechanism + controlled by the workbench plugin or controlled by git. In the later case + the .gitignore settings decide which files are displayed and which not. + This option is only available if the directory is/contains a git + repository. **Fold/unfold directory** Fold or unfold the items belonging to the directory. It is only available diff --git a/workbench/src/Makefile.am b/workbench/src/Makefile.am index 87d0ed89a..568d457b7 100644 --- a/workbench/src/Makefile.am +++ b/workbench/src/Makefile.am @@ -30,9 +30,10 @@ workbench_la_SOURCES = \ workbench_la_CPPFLAGS = $(AM_CPPFLAGS) \ -DG_LOG_DOMAIN=\"Workbench\" -workbench_la_CFLAGS = $(AM_CFLAGS) \ +workbench_la_CFLAGS = $(AM_CFLAGS) $(WORKBENCH_CFLAGS) \ -I$(top_srcdir)/utils/src workbench_la_LIBADD = $(COMMONLIBS) \ + $(WORKBENCH_LIBS) \ $(top_builddir)/utils/src/libgeanypluginutils.la include $(top_srcdir)/build/cppcheck.mk diff --git a/workbench/src/dialogs.c b/workbench/src/dialogs.c index 0437493be..b729640fb 100644 --- a/workbench/src/dialogs.c +++ b/workbench/src/dialogs.c @@ -25,6 +25,7 @@ #include "wb_globals.h" #include "dialogs.h" +#include "utils.h" extern GeanyPlugin *geany_plugin; @@ -265,20 +266,65 @@ static gchar **split_patterns(const gchar *str) return ret; } +typedef struct S_DIALOG_DIR_SETTINGS_DATA +{ + GtkWidget *dialog; + GtkWidget *w_label_file_patterns; + GtkWidget *w_file_patterns; + GtkWidget *w_label_ignored_dirs_patterns; + GtkWidget *w_ignored_dirs_patterns; + GtkWidget *w_label_ignored_file_patterns; + GtkWidget *w_ignored_file_patterns; + GtkWidget *w_scan_mode_wb; + GtkWidget *w_scan_mode_git; +}DIALOG_DIR_SETTINGS_DATA; + +static void button_filter_workbench_toggled(GtkToggleButton *togglebutton, + gpointer user_data) +{ + DIALOG_DIR_SETTINGS_DATA *data; + + data = user_data; + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->w_scan_mode_wb))) + { + gtk_widget_set_sensitive (data->w_label_file_patterns, TRUE); + gtk_widget_set_sensitive (data->w_file_patterns, TRUE); + gtk_widget_set_sensitive (data->w_label_ignored_dirs_patterns, TRUE); + gtk_widget_set_sensitive (data->w_ignored_dirs_patterns, TRUE); + gtk_widget_set_sensitive (data->w_label_ignored_file_patterns, TRUE); + gtk_widget_set_sensitive (data->w_ignored_file_patterns, TRUE); + } + else + { + gtk_widget_set_sensitive (data->w_label_file_patterns, FALSE); + gtk_widget_set_sensitive (data->w_file_patterns, FALSE); + gtk_widget_set_sensitive (data->w_label_ignored_dirs_patterns, FALSE); + gtk_widget_set_sensitive (data->w_ignored_dirs_patterns, FALSE); + gtk_widget_set_sensitive (data->w_label_ignored_file_patterns, FALSE); + gtk_widget_set_sensitive (data->w_ignored_file_patterns, FALSE); + } +} + +static void button_filter_git_toggled(GtkToggleButton *togglebutton, + gpointer user_data) +{ + /* Nothing to do for now... */ +} /** Shows the directory settings dialog. * * The dialog lets the user edit the settings for file patterns, irnored file patterns and * ignored directories patterns. On accept the result is directly stored in @a directory. * + * @param project The project to which directory belongs to * @param directory Location of WB_PROJECT_DIR to store the settings into * @return TRUE if the settings have changed, FALSE otherwise * **/ -gboolean dialogs_directory_settings(WB_PROJECT_DIR *directory) +gboolean dialogs_directory_settings(WB_PROJECT *project, WB_PROJECT_DIR *directory) { - GtkWidget *w_file_patterns, *w_ignored_dirs_patterns, *w_ignored_file_patterns; - GtkWidget *dialog, *label, *content_area; + DIALOG_DIR_SETTINGS_DATA *data; + GtkWidget *label, *content_area; GtkWidget *vbox, *hbox, *hbox1; #if GTK_CHECK_VERSION(3, 4, 0) GtkWidget *grid; @@ -287,17 +333,22 @@ gboolean dialogs_directory_settings(WB_PROJECT_DIR *directory) #endif GtkDialogFlags flags; gchar *file_patterns_old, *ignored_file_patterns_old, *ignored_dirs_patterns_old; + WB_PROJECT_SCAN_MODE scan_mode_old, scan_mode; + gchar *abs_path; gboolean changed; + guint row = 0; + + data = g_new0(DIALOG_DIR_SETTINGS_DATA, 1); /* Create the widgets */ flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT; - dialog = gtk_dialog_new_with_buttons(_("Directory settings"), - GTK_WINDOW(wb_globals.geany_plugin->geany_data->main_widgets->window), - flags, - _("_Cancel"), GTK_RESPONSE_CANCEL, - _("_OK"), GTK_RESPONSE_ACCEPT, - NULL); - content_area = gtk_dialog_get_content_area(GTK_DIALOG (dialog)); + data->dialog = gtk_dialog_new_with_buttons(_("Directory settings"), + GTK_WINDOW(wb_globals.geany_plugin->geany_data->main_widgets->window), + flags, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_ACCEPT, + NULL); + content_area = gtk_dialog_get_content_area(GTK_DIALOG (data->dialog)); vbox = gtk_vbox_new(FALSE, 0); @@ -311,87 +362,105 @@ gboolean dialogs_directory_settings(WB_PROJECT_DIR *directory) gtk_table_set_col_spacings(GTK_TABLE(table), 10); #endif - label = gtk_label_new(_("File patterns:")); + data->w_scan_mode_wb = gtk_radio_button_new_with_label (NULL, _("filter files using the workbench plugin")); + g_signal_connect(data->w_scan_mode_wb, "toggled", + G_CALLBACK(button_filter_workbench_toggled), data); + gtk_box_pack_start(GTK_BOX(vbox), data->w_scan_mode_wb, FALSE, FALSE, 6); + + data->w_label_file_patterns = gtk_label_new(_("File patterns:")); #if GTK_CHECK_VERSION(3, 16, 0) - gtk_label_set_xalign (GTK_LABEL(label), 0); - gtk_label_set_yalign (GTK_LABEL(label), 0); + gtk_label_set_xalign (GTK_LABEL(data->w_label_file_patterns), 0); + gtk_label_set_yalign (GTK_LABEL(data->w_label_file_patterns), 0); #else - gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_misc_set_alignment(GTK_MISC(data->w_label_file_patterns), 0, 0); #endif - w_file_patterns = gtk_entry_new(); + data->w_file_patterns = gtk_entry_new(); #if GTK_CHECK_VERSION(3, 4, 0) - gtk_grid_attach (GTK_GRID(grid), label, 0, 0, 1, 1); - gtk_grid_attach (GTK_GRID(grid), w_file_patterns, 1, 0, 1, 1); - gtk_widget_set_valign (label, GTK_ALIGN_BASELINE); - gtk_widget_set_hexpand (label, FALSE); - gtk_widget_set_valign (w_file_patterns, GTK_ALIGN_BASELINE); - gtk_widget_set_hexpand (w_file_patterns, TRUE); + gtk_grid_attach (GTK_GRID(grid), data->w_label_file_patterns, 0, row, 1, 1); + gtk_grid_attach (GTK_GRID(grid), data->w_file_patterns, 1, row, 1, 1); + gtk_widget_set_valign (data->w_label_file_patterns, GTK_ALIGN_BASELINE); + gtk_widget_set_hexpand (data->w_label_file_patterns, FALSE); + gtk_widget_set_valign (data->w_file_patterns, GTK_ALIGN_BASELINE); + gtk_widget_set_hexpand (data->w_file_patterns, TRUE); #else - ui_table_add_row(GTK_TABLE(table), 0, label, w_file_patterns, NULL); + ui_table_add_row(GTK_TABLE(table), row + 1, data->w_label_file_patterns, data->w_file_patterns, NULL); #endif - ui_entry_add_clear_icon(GTK_ENTRY(w_file_patterns)); - gtk_widget_set_tooltip_text(w_file_patterns, + row++; + ui_entry_add_clear_icon(GTK_ENTRY(data->w_file_patterns)); + gtk_widget_set_tooltip_text(data->w_file_patterns, _("Space separated list of patterns that are used to identify files " "that shall be displayed in the directory tree.")); file_patterns_old = g_strjoinv(" ", wb_project_dir_get_file_patterns(directory)); - gtk_entry_set_text(GTK_ENTRY(w_file_patterns), file_patterns_old); + gtk_entry_set_text(GTK_ENTRY(data->w_file_patterns), file_patterns_old); - label = gtk_label_new(_("Ignored file patterns:")); + data->w_label_ignored_file_patterns = gtk_label_new(_("Ignored file patterns:")); #if GTK_CHECK_VERSION(3, 16, 0) - gtk_label_set_xalign (GTK_LABEL(label), 0); - gtk_label_set_yalign (GTK_LABEL(label), 0); + gtk_label_set_xalign (GTK_LABEL(data->w_label_ignored_file_patterns), 0); + gtk_label_set_yalign (GTK_LABEL(data->w_label_ignored_file_patterns), 0); #else - gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_misc_set_alignment(GTK_MISC(data->w_label_ignored_file_patterns), 0, 0); #endif - w_ignored_file_patterns = gtk_entry_new(); - ui_entry_add_clear_icon(GTK_ENTRY(w_ignored_file_patterns)); + data->w_ignored_file_patterns = gtk_entry_new(); + ui_entry_add_clear_icon(GTK_ENTRY(data->w_ignored_file_patterns)); #if GTK_CHECK_VERSION(3, 4, 0) - gtk_grid_attach (GTK_GRID(grid), label, 0, 1, 1, 1); - gtk_grid_attach (GTK_GRID(grid), w_ignored_file_patterns, 1, 1, 1, 1); - gtk_widget_set_valign (label, GTK_ALIGN_BASELINE); - gtk_widget_set_hexpand (label, FALSE); - gtk_widget_set_valign (w_ignored_file_patterns, GTK_ALIGN_BASELINE); - gtk_widget_set_hexpand (w_ignored_file_patterns, TRUE); + gtk_grid_attach (GTK_GRID(grid), data->w_label_ignored_file_patterns, 0, row, 1, 1); + gtk_grid_attach (GTK_GRID(grid), data->w_ignored_file_patterns, 1, row, 1, 1); + gtk_widget_set_valign (data->w_label_ignored_file_patterns, GTK_ALIGN_BASELINE); + gtk_widget_set_hexpand (data->w_label_ignored_file_patterns, FALSE); + gtk_widget_set_valign (data->w_ignored_file_patterns, GTK_ALIGN_BASELINE); + gtk_widget_set_hexpand (data->w_ignored_file_patterns, TRUE); #else - ui_table_add_row(GTK_TABLE(table), 2, label, w_ignored_file_patterns, NULL); + ui_table_add_row(GTK_TABLE(table), row + 1, data->w_label_ignored_file_patterns, data->w_ignored_file_patterns, NULL); #endif - gtk_widget_set_tooltip_text(w_ignored_file_patterns, + row++; + gtk_widget_set_tooltip_text(data->w_ignored_file_patterns, _("Space separated list of patterns that are used to identify files " "that shall not be displayed in the directory tree.")); ignored_file_patterns_old = g_strjoinv(" ", wb_project_dir_get_ignored_file_patterns(directory)); - gtk_entry_set_text(GTK_ENTRY(w_ignored_file_patterns), ignored_file_patterns_old); + gtk_entry_set_text(GTK_ENTRY(data->w_ignored_file_patterns), ignored_file_patterns_old); - label = gtk_label_new(_("Ignored directory patterns:")); + data->w_label_ignored_dirs_patterns = gtk_label_new(_("Ignored directory patterns:")); #if GTK_CHECK_VERSION(3, 16, 0) - gtk_label_set_xalign (GTK_LABEL(label), 0); - gtk_label_set_yalign (GTK_LABEL(label), 0); + gtk_label_set_xalign (GTK_LABEL(data->w_label_ignored_dirs_patterns), 0); + gtk_label_set_yalign (GTK_LABEL(data->w_label_ignored_dirs_patterns), 0); #else - gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_misc_set_alignment(GTK_MISC(data->w_label_ignored_dirs_patterns), 0, 0); #endif - w_ignored_dirs_patterns = gtk_entry_new(); - ui_entry_add_clear_icon(GTK_ENTRY(w_ignored_dirs_patterns)); + data->w_ignored_dirs_patterns = gtk_entry_new(); + ui_entry_add_clear_icon(GTK_ENTRY(data->w_ignored_dirs_patterns)); #if GTK_CHECK_VERSION(3, 4, 0) - gtk_grid_attach (GTK_GRID(grid), label, 0, 2, 1, 1); - gtk_grid_attach (GTK_GRID(grid), w_ignored_dirs_patterns, 1, 2, 1, 1); - gtk_widget_set_valign (label, GTK_ALIGN_BASELINE); - gtk_widget_set_hexpand (label, FALSE); - gtk_widget_set_valign (w_ignored_dirs_patterns, GTK_ALIGN_BASELINE); - gtk_widget_set_hexpand (w_ignored_dirs_patterns, TRUE); + gtk_grid_attach (GTK_GRID(grid), data->w_label_ignored_dirs_patterns, 0, row, 1, 1); + gtk_grid_attach (GTK_GRID(grid), data->w_ignored_dirs_patterns, 1, row, 1, 1); + gtk_widget_set_valign (data->w_label_ignored_dirs_patterns, GTK_ALIGN_BASELINE); + gtk_widget_set_hexpand (data->w_label_ignored_dirs_patterns, FALSE); + gtk_widget_set_valign (data->w_ignored_dirs_patterns, GTK_ALIGN_BASELINE); + gtk_widget_set_hexpand (data->w_ignored_dirs_patterns, TRUE); #else - ui_table_add_row(GTK_TABLE(table), 3, label, w_ignored_dirs_patterns, NULL); + ui_table_add_row(GTK_TABLE(table), row + 1, data->w_label_ignored_dirs_patterns, data->w_ignored_dirs_patterns, NULL); #endif - gtk_widget_set_tooltip_text(w_ignored_dirs_patterns, + row++; + gtk_widget_set_tooltip_text(data->w_ignored_dirs_patterns, _("Space separated list of patterns that are used to identify directories " "that shall not be scanned for source files.")); ignored_dirs_patterns_old = g_strjoinv(" ", wb_project_dir_get_ignored_dirs_patterns(directory)); - gtk_entry_set_text(GTK_ENTRY(w_ignored_dirs_patterns), ignored_dirs_patterns_old); + gtk_entry_set_text(GTK_ENTRY(data->w_ignored_dirs_patterns), ignored_dirs_patterns_old); +#if GTK_CHECK_VERSION(3, 12, 0) + gtk_widget_set_margin_start (grid, 32); +#endif #if GTK_CHECK_VERSION(3, 4, 0) gtk_box_pack_start(GTK_BOX(vbox), grid, FALSE, FALSE, 6); #else gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 6); #endif + data->w_scan_mode_git = gtk_radio_button_new_with_label ( + gtk_radio_button_get_group(GTK_RADIO_BUTTON (data->w_scan_mode_wb)), + _("filter files using git (.gitignore)")); + g_signal_connect(data->w_scan_mode_git, "toggled", + G_CALLBACK(button_filter_git_toggled), data); + gtk_box_pack_start(GTK_BOX(vbox), data->w_scan_mode_git, FALSE, FALSE, 6); + hbox1 = gtk_hbox_new(FALSE, 0); label = gtk_label_new(_("Note: the patterns above affect only the workbench directory and are not used in the Find in Files\n" "dialog.")); @@ -405,35 +474,71 @@ gboolean dialogs_directory_settings(WB_PROJECT_DIR *directory) gtk_container_add(GTK_CONTAINER (content_area), label); gtk_container_add(GTK_CONTAINER (content_area), hbox); - gtk_widget_show_all(dialog); - gint result = gtk_dialog_run(GTK_DIALOG(dialog)); + + scan_mode_old = wb_project_dir_get_scan_mode(directory); + abs_path = get_combined_path(wb_project_get_filename(project), + wb_project_dir_get_base_dir(directory)); + if (!is_git_repository(abs_path)) + { + /* Disable git filter option if the directory is not a git repository. */ + gtk_widget_set_sensitive (data->w_scan_mode_git, FALSE); + } + else + { + /* Both options are possible, set the active option from + the current settings. */ + if (scan_mode_old == WB_PROJECT_SCAN_MODE_WORKBENCH) + { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->w_scan_mode_wb), TRUE); + } + else + { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->w_scan_mode_git), TRUE); + } + } + g_free(abs_path); + + + gtk_widget_show_all(data->dialog); + gint result = gtk_dialog_run(GTK_DIALOG(data->dialog)); changed = FALSE; if (result == GTK_RESPONSE_ACCEPT) { const gchar *str; gchar **file_patterns, **ignored_dirs_patterns, **ignored_file_patterns; - str = gtk_entry_get_text(GTK_ENTRY(w_file_patterns)); + scan_mode = WB_PROJECT_SCAN_MODE_WORKBENCH; + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->w_scan_mode_git))) + { + scan_mode = WB_PROJECT_SCAN_MODE_GIT; + } + if (scan_mode_old != scan_mode) + { + changed = TRUE; + } + + str = gtk_entry_get_text(GTK_ENTRY(data->w_file_patterns)); if (g_strcmp0(str, file_patterns_old) != 0) { changed = TRUE; } file_patterns = split_patterns(str); - str = gtk_entry_get_text(GTK_ENTRY(w_ignored_dirs_patterns)); + str = gtk_entry_get_text(GTK_ENTRY(data->w_ignored_dirs_patterns)); if (g_strcmp0(str, ignored_dirs_patterns_old) != 0) { changed = TRUE; } ignored_dirs_patterns = split_patterns(str); - str = gtk_entry_get_text(GTK_ENTRY(w_ignored_file_patterns)); + str = gtk_entry_get_text(GTK_ENTRY(data->w_ignored_file_patterns)); if (g_strcmp0(str, ignored_file_patterns_old) != 0) { changed = TRUE; } ignored_file_patterns = split_patterns(str); + wb_project_dir_set_scan_mode(project, directory, scan_mode); wb_project_dir_set_file_patterns(directory, file_patterns); wb_project_dir_set_ignored_dirs_patterns(directory, ignored_dirs_patterns); wb_project_dir_set_ignored_file_patterns(directory, ignored_file_patterns); @@ -446,7 +551,7 @@ gboolean dialogs_directory_settings(WB_PROJECT_DIR *directory) g_free(file_patterns_old); g_free(ignored_file_patterns_old); g_free(ignored_dirs_patterns_old); - gtk_widget_destroy(dialog); + gtk_widget_destroy(data->dialog); return changed; } diff --git a/workbench/src/dialogs.h b/workbench/src/dialogs.h index d0377accd..c772aa6e2 100644 --- a/workbench/src/dialogs.h +++ b/workbench/src/dialogs.h @@ -25,7 +25,7 @@ gchar *dialogs_create_new_workbench(void); gchar *dialogs_open_workbench(void); gchar *dialogs_add_project(void); gchar *dialogs_add_directory(WB_PROJECT *project); -gboolean dialogs_directory_settings(WB_PROJECT_DIR *directory); +gboolean dialogs_directory_settings(WB_PROJECT *project, WB_PROJECT_DIR *directory); gboolean dialogs_workbench_settings(WORKBENCH *workbench); #endif diff --git a/workbench/src/plugin_main.c b/workbench/src/plugin_main.c index faab22ace..6fa6fc849 100644 --- a/workbench/src/plugin_main.c +++ b/workbench/src/plugin_main.c @@ -25,6 +25,7 @@ #include #include +#include #include @@ -34,6 +35,13 @@ #include "idle_queue.h" #include "tm_control.h" + +#if ! defined (LIBGIT2_SOVERSION) || LIBGIT2_SOVERSION < 22 +# define git_libgit2_init git_threads_init +# define git_libgit2_shutdown git_threads_shutdown +#endif + + GeanyPlugin *geany_plugin; GeanyData *geany_data; @@ -80,6 +88,9 @@ static gboolean plugin_workbench_init(GeanyPlugin *plugin, G_GNUC_UNUSED gpointe menu_set_context(MENU_CONTEXT_WB_CLOSED); sidebar_show_intro_message(_("Create or open a workbench\nusing the workbench menu."), FALSE); + /* Init libgit2. */ + git_libgit2_init(); + return TRUE; } @@ -90,6 +101,9 @@ static void plugin_workbench_cleanup(G_GNUC_UNUSED GeanyPlugin *plugin, G_GNUC_U menu_cleanup(); sidebar_cleanup(); wb_tm_control_cleanup(); + + /* Shutdown/cleanup libgit2. */ + git_libgit2_shutdown(); } @@ -117,7 +131,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.08"; + plugin->info->version = "1.09"; plugin->info->author = "LarsGit223"; /* Set functions */ diff --git a/workbench/src/popup_menu.c b/workbench/src/popup_menu.c index a14daf23d..5e5e94cce 100644 --- a/workbench/src/popup_menu.c +++ b/workbench/src/popup_menu.c @@ -432,7 +432,7 @@ static void popup_menu_on_directory_settings(G_GNUC_UNUSED GtkMenuItem * menuite if (sidebar_file_view_get_selected_context(&context) && context.project != NULL && context.directory != NULL) { - if (dialogs_directory_settings(context.directory)) + if (dialogs_directory_settings(context.project, context.directory)) { wb_project_set_modified(context.project, TRUE); wb_project_dir_rescan(context.project, context.directory); diff --git a/workbench/src/utils.c b/workbench/src/utils.c index c1ab23a6a..d9494761a 100644 --- a/workbench/src/utils.c +++ b/workbench/src/utils.c @@ -292,3 +292,16 @@ void close_all_files_in_list(GPtrArray *list) } } } + + +gboolean is_git_repository(gchar *path) +{ + gboolean is_git_repo; + gchar *git_path; + + git_path = g_build_filename(path, ".git", NULL); + is_git_repo = g_file_test(git_path, G_FILE_TEST_IS_DIR); + g_free(git_path); + + return is_git_repo; +} diff --git a/workbench/src/utils.h b/workbench/src/utils.h index d3b26a14b..29842a364 100644 --- a/workbench/src/utils.h +++ b/workbench/src/utils.h @@ -28,5 +28,6 @@ gchar *get_combined_path(const gchar *base, const gchar *relative); gchar *get_any_relative_path (const gchar *base, const gchar *target); void open_all_files_in_list(GPtrArray *list); void close_all_files_in_list(GPtrArray *list); +gboolean is_git_repository(gchar *path); #endif diff --git a/workbench/src/wb_project.c b/workbench/src/wb_project.c index 0cad5715d..9eceae943 100644 --- a/workbench/src/wb_project.c +++ b/workbench/src/wb_project.c @@ -20,6 +20,7 @@ * Code for the WB_PROJECT structure. */ #include +#include #ifdef HAVE_CONFIG_H # include "config.h" @@ -52,15 +53,29 @@ struct S_WB_PROJECT_DIR { gchar *name; gchar *base_dir; + WB_PROJECT_SCAN_MODE scan_mode; gchar **file_patterns; /**< Array of filename extension patterns. */ gchar **ignored_dirs_patterns; gchar **ignored_file_patterns; + git_repository *git_repo; guint file_count; guint subdir_count; GHashTable *file_table; /* contains all file names within base_dir */ gboolean is_prj_base_dir; }; + +typedef struct +{ + guint file_count; + guint subdir_count; + GSList *file_patterns_list; + GSList *ignored_dirs_list; + GSList *ignored_file_list; + git_repository *git_repo; +}SCAN_PARAMS; + + struct S_WB_PROJECT { gchar *filename; @@ -202,6 +217,7 @@ static WB_PROJECT_DIR *wb_project_dir_new(WB_PROJECT *prj, const gchar *utf8_bas WB_PROJECT_DIR *dir = g_new0(WB_PROJECT_DIR, 1); dir->base_dir = g_strdup(utf8_base_dir); dir->file_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + dir->scan_mode = WB_PROJECT_SCAN_MODE_WORKBENCH; offset = strlen(dir->base_dir)-1; while (offset > 0 @@ -261,6 +277,7 @@ gboolean wb_project_dir_get_is_prj_base_dir (WB_PROJECT_DIR *directory) return FALSE; } + /** Get the name of a project dir. * * @param directory The project dir @@ -415,6 +432,79 @@ gboolean wb_project_dir_set_ignored_file_patterns (WB_PROJECT_DIR *directory, gc } +/** Get the scan mode of a project dir. + * + * @param directory The project dir + * @return WB_PROJECT_SCAN_MODE (the scan mode) + * + **/ +WB_PROJECT_SCAN_MODE wb_project_dir_get_scan_mode (WB_PROJECT_DIR *directory) +{ + if (directory != NULL) + { + return directory->scan_mode; + } + + return WB_PROJECT_SCAN_MODE_INVALID; +} + + +/* Open or close the git repository in directory as required. */ +static void wb_project_dir_prepare_git_repo(WB_PROJECT *project, WB_PROJECT_DIR *directory) +{ + gchar *path; + + path = get_combined_path(project->filename, directory->base_dir); + if (directory->scan_mode == WB_PROJECT_SCAN_MODE_GIT) + { + if (directory->git_repo == NULL) + { + if (git_repository_open(&(directory->git_repo), path) != 0) + { + directory->git_repo = NULL; + ui_set_statusbar(TRUE, + _("Failed to open git repository in folder %s."), path); + } + else + { + ui_set_statusbar(TRUE, + _("Opened git repository in folder %s."), path); + } + } + } + else + { + if (directory->git_repo != NULL) + { + git_repository_free(directory->git_repo); + directory->git_repo = NULL; + ui_set_statusbar(TRUE, + _("Closed git repository in folder %s."), path); + } + } + g_free(path); +} + + +/** Set the scan mode of a project dir. + * + * @param directory The project dir + * @param mode The scan mode to set + * @return FALSE if directory is NULL, TRUE otherwise + * + **/ +gboolean wb_project_dir_set_scan_mode(WB_PROJECT *project, WB_PROJECT_DIR *directory, WB_PROJECT_SCAN_MODE mode) +{ + if (directory != NULL) + { + directory->scan_mode = mode; + wb_project_dir_prepare_git_repo(project, directory); + return TRUE; + } + return FALSE; +} + + /* Remove all files contained in the project dir from the tm-workspace */ static void wb_project_dir_remove_from_tm_workspace(WB_PROJECT_DIR *root) { @@ -472,6 +562,111 @@ static guint wb_project_get_file_count(WB_PROJECT *prj) return filenum; } + +/* Callback function for 'gp_filelist_scan_directory_callback', scan mode + 'workbench', the plugin decides which files to add to the filelist. */ +static void scan_mode_workbench_cb(const gchar *path, gboolean *add, gboolean *enter, void *userdata) +{ + SCAN_PARAMS *params = userdata; + + *enter = FALSE; + *add = FALSE; + + if (g_file_test(path, G_FILE_TEST_IS_DIR)) + { + if (!filelist_patterns_match(params->ignored_dirs_list, path)) + { + *enter = TRUE; + *add = TRUE; + } + } + else if (g_file_test(path, G_FILE_TEST_IS_REGULAR)) + { + if (filelist_patterns_match(params->file_patterns_list, path) && + !filelist_patterns_match(params->ignored_file_list, path)) + { + *enter = TRUE; + *add = TRUE; + } + } +} + + +/* Callback function for 'gp_filelist_scan_directory_callback', scan mode + 'git', libgit2 decides which files to add to the filelist. */ +static void scan_mode_git_cb(const gchar *path, gboolean *add, gboolean *enter, void *userdata) +{ + gint ignored; + SCAN_PARAMS *params = userdata; + + *enter = TRUE; + *add = TRUE; + if (params->git_repo != NULL) + { + git_ignore_path_is_ignored(&ignored, params->git_repo, path); + if (ignored > 0) + { + *enter = FALSE; + *add = FALSE; + } + } +} + + +/* Scan a path according to the settings given in parameter root. */ +static GSList *wb_project_dir_scan_directory(WB_PROJECT_DIR *root, const gchar *searchdir, + guint *file_count, guint *subdir_count) +{ + GSList *filelist; + SCAN_PARAMS params = { 0 }; + + if (root->scan_mode != WB_PROJECT_SCAN_MODE_GIT) + { + if (!root->file_patterns || !root->file_patterns[0]) + { + const gchar *all_pattern[] = { "*", NULL }; + params.file_patterns_list = filelist_get_precompiled_patterns((gchar **)all_pattern); + } + else + { + params.file_patterns_list = filelist_get_precompiled_patterns(root->file_patterns); + } + + params.ignored_dirs_list = filelist_get_precompiled_patterns(root->ignored_dirs_patterns); + params.ignored_file_list = filelist_get_precompiled_patterns(root->ignored_file_patterns); + + filelist = gp_filelist_scan_directory_callback + (searchdir, scan_mode_workbench_cb, ¶ms); + + g_slist_foreach(params.file_patterns_list, (GFunc) g_pattern_spec_free, NULL); + g_slist_free(params.file_patterns_list); + + g_slist_foreach(params.ignored_dirs_list, (GFunc) g_pattern_spec_free, NULL); + g_slist_free(params.ignored_dirs_list); + + g_slist_foreach(params.ignored_file_list, (GFunc) g_pattern_spec_free, NULL); + g_slist_free(params.ignored_file_list); + } + else + { + params.git_repo = root->git_repo; + filelist = gp_filelist_scan_directory_callback + (searchdir, scan_mode_git_cb, ¶ms); + } + + if (file_count != NULL) + { + *file_count = params.file_count; + } + if (subdir_count != NULL) + { + *subdir_count = params.subdir_count; + } + + return filelist; +} + + /* Rescan/update the file list of a project dir. */ static guint wb_project_dir_rescan_int(WB_PROJECT *prj, WB_PROJECT_DIR *root) { @@ -479,22 +674,15 @@ static guint wb_project_dir_rescan_int(WB_PROJECT *prj, WB_PROJECT_DIR *root) GSList *elem = NULL; guint filenum = 0; gchar *searchdir; - gchar **file_patterns = NULL; wb_project_dir_remove_from_tm_workspace(root); g_hash_table_remove_all(root->file_table); - if (root->file_patterns && root->file_patterns[0]) - { - file_patterns = root->file_patterns; - } - searchdir = get_combined_path(prj->filename, root->base_dir); root->file_count = 0; root->subdir_count = 0; - lst = gp_filelist_scan_directory_full(&(root->file_count), &(root->subdir_count), - searchdir, file_patterns, root->ignored_dirs_patterns, root->ignored_file_patterns, - FILELIST_FLAG_ADD_DIRS); + lst = wb_project_dir_scan_directory + (root, searchdir, &(root->file_count), &(root->subdir_count)); g_free(searchdir); foreach_slist(elem, lst) @@ -515,22 +703,54 @@ static guint wb_project_dir_rescan_int(WB_PROJECT *prj, WB_PROJECT_DIR *root) } +/* Single check if a path is to be ignored or not. */ +static gboolean wb_project_dir_path_is_ignored(WB_PROJECT_DIR *root, const gchar *filepath) +{ + if (root->scan_mode == WB_PROJECT_SCAN_MODE_WORKBENCH) + { + gchar **file_patterns = NULL; + gboolean matches; + + 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 TRUE; + } + } + else + { + if (root->git_repo != NULL) + { + gint ignored; + + git_ignore_path_is_ignored(&ignored, root->git_repo, filepath); + if (ignored > 0) + { + /* Ignore it. */ + return TRUE; + } + } + } + + /* Do not ignore it. */ + return FALSE; +} + + /* 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) + if (wb_project_dir_path_is_ignored(root, filepath)) { /* Ignore it. */ return; @@ -562,9 +782,8 @@ static void wb_project_dir_add_file_int(WB_PROJECT *prj, WB_PROJECT_DIR *root, c { 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); + scanned = wb_project_dir_scan_directory + (root, filepath, &(root->file_count), &(root->subdir_count)); foreach_slist(elem, scanned) { @@ -655,18 +874,15 @@ static gboolean wb_project_dir_remove_child (gpointer key, gpointer value, gpoin 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); + matches = FALSE; + if (wb_project_dir_path_is_ignored(root, filepath) == FALSE) + { + matches = TRUE; + } } else { @@ -1070,6 +1286,15 @@ static void wb_project_save_directories (gpointer data, gpointer user_data) { g_key_file_set_string(tmp->kf, "Workbench", "Prj-BaseDir", dir->base_dir); + if (dir->scan_mode == WB_PROJECT_SCAN_MODE_WORKBENCH) + { + g_key_file_set_string(tmp->kf, "Workbench", "Prj-ScanMode", "Workbench"); + } + else + { + g_key_file_set_string(tmp->kf, "Workbench", "Prj-ScanMode", "Git"); + } + str = g_strjoinv(";", dir->file_patterns); g_key_file_set_string(tmp->kf, "Workbench", "Prj-FilePatterns", str); g_free(str); @@ -1087,6 +1312,16 @@ static void wb_project_save_directories (gpointer data, gpointer user_data) g_snprintf(key, sizeof(key), "Dir%u-BaseDir", tmp->dir_count); g_key_file_set_string(tmp->kf, "Workbench", key, dir->base_dir); + g_snprintf(key, sizeof(key), "Dir%u-ScanMode", tmp->dir_count); + if (dir->scan_mode == WB_PROJECT_SCAN_MODE_WORKBENCH) + { + g_key_file_set_string(tmp->kf, "Workbench", key, "Workbench"); + } + else + { + g_key_file_set_string(tmp->kf, "Workbench", key, "Git"); + } + g_snprintf(key, sizeof(key), "Dir%u-FilePatterns", tmp->dir_count); str = g_strjoinv(";", dir->file_patterns); g_key_file_set_string(tmp->kf, "Workbench", key, str); @@ -1415,6 +1650,17 @@ gboolean wb_project_load(WB_PROJECT *prj, const gchar *filename, GError **error) { wb_project_dir_set_is_prj_base_dir(new_dir, TRUE); + str = g_key_file_get_string(kf, "Workbench", "Prj-ScanMode", NULL); + if (g_strcmp0(str, "Git") != 0) + { + wb_project_dir_set_scan_mode(prj, new_dir, WB_PROJECT_SCAN_MODE_WORKBENCH); + } + else + { + wb_project_dir_set_scan_mode(prj, new_dir, WB_PROJECT_SCAN_MODE_GIT); + } + g_free(str); + str = g_key_file_get_string(kf, "Workbench", "Prj-FilePatterns", NULL); if (str != NULL) { @@ -1457,6 +1703,18 @@ gboolean wb_project_load(WB_PROJECT *prj, const gchar *filename, GError **error) break; } + g_snprintf(key, sizeof(key), "Dir%u-ScanMode", index); + str = g_key_file_get_string(kf, "Workbench", key, NULL); + if (g_strcmp0(str, "Git") != 0) + { + wb_project_dir_set_scan_mode(prj, new_dir, WB_PROJECT_SCAN_MODE_WORKBENCH); + } + else + { + wb_project_dir_set_scan_mode(prj, new_dir, WB_PROJECT_SCAN_MODE_GIT); + } + g_free(str); + g_snprintf(key, sizeof(key), "Dir%u-FilePatterns", index); str = g_key_file_get_string(kf, "Workbench", key, NULL); if (str != NULL) diff --git a/workbench/src/wb_project.h b/workbench/src/wb_project.h index 2f0493a64..a7d290b03 100644 --- a/workbench/src/wb_project.h +++ b/workbench/src/wb_project.h @@ -24,6 +24,13 @@ typedef struct S_WB_PROJECT WB_PROJECT; typedef struct S_WB_PROJECT_DIR WB_PROJECT_DIR; +typedef enum +{ + WB_PROJECT_SCAN_MODE_INVALID, + WB_PROJECT_SCAN_MODE_WORKBENCH, + WB_PROJECT_SCAN_MODE_GIT, +}WB_PROJECT_SCAN_MODE; + WB_PROJECT *wb_project_new(const gchar *filename); void wb_project_free(WB_PROJECT *prj); @@ -51,6 +58,8 @@ gchar **wb_project_dir_get_ignored_dirs_patterns (WB_PROJECT_DIR *directory); gboolean wb_project_dir_set_ignored_dirs_patterns (WB_PROJECT_DIR *directory, gchar **new); gchar **wb_project_dir_get_ignored_file_patterns (WB_PROJECT_DIR *directory); gboolean wb_project_dir_set_ignored_file_patterns (WB_PROJECT_DIR *directory, gchar **new); +WB_PROJECT_SCAN_MODE wb_project_dir_get_scan_mode (WB_PROJECT_DIR *directory); +gboolean wb_project_dir_set_scan_mode (WB_PROJECT *project, WB_PROJECT_DIR *directory, WB_PROJECT_SCAN_MODE mode); 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);