From 1b0a572f276a0181f75976b629def72157ab06b6 Mon Sep 17 00:00:00 2001 From: memstream Date: Fri, 8 Aug 2025 18:02:40 +0300 Subject: [PATCH 01/18] commander: replacing the initialization of the plugin data structure with static --- commander/src/commander-plugin.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 62b0fb7ca7..811c1270d9 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -84,7 +84,7 @@ enum { KB_COUNT }; -struct { +static struct { GtkWidget *panel; GtkWidget *entry; GtkWidget *view; @@ -92,11 +92,7 @@ struct { GtkTreeModel *sort; GtkTreePath *last_path; -} plugin_data = { - NULL, NULL, NULL, - NULL, NULL, - NULL -}; +} plugin_data; typedef enum { COL_TYPE_MENU_ITEM = 1 << 0, From 34979f91474a9ed0e6756f583a7f1b8aae5a7763 Mon Sep 17 00:00:00 2001 From: memstream Date: Fri, 8 Aug 2025 18:08:43 +0300 Subject: [PATCH 02/18] commander: fixing all errors related to unused arguments --- commander/src/commander-plugin.c | 50 ++++++++++++++++---------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 811c1270d9..7dcb15c0a2 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -423,10 +423,10 @@ fill_store (GtkListStore *store) } static gint -sort_func (GtkTreeModel *model, - GtkTreeIter *a, - GtkTreeIter *b, - gpointer dummy) +sort_func (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + G_GNUC_UNUSED gpointer dummy) { gint scorea; gint scoreb; @@ -457,9 +457,9 @@ sort_func (GtkTreeModel *model, } static gboolean -on_panel_key_press_event (GtkWidget *widget, - GdkEventKey *event, - gpointer dummy) +on_panel_key_press_event (GtkWidget *widget, + GdkEventKey *event, + G_GNUC_UNUSED gpointer dummy) { switch (event->keyval) { case GDK_KEY_Escape: @@ -496,9 +496,9 @@ on_panel_key_press_event (GtkWidget *widget, } static void -on_entry_text_notify (GObject *object, - GParamSpec *pspec, - gpointer dummy) +on_entry_text_notify (G_GNUC_UNUSED GObject *object, + G_GNUC_UNUSED GParamSpec *pspec, + G_GNUC_UNUSED gpointer dummy) { GtkTreeIter iter; GtkTreeView *view = GTK_TREE_VIEW (plugin_data.view); @@ -518,15 +518,15 @@ on_entry_text_notify (GObject *object, } static void -on_entry_activate (GtkEntry *entry, - gpointer dummy) +on_entry_activate (G_GNUC_UNUSED GtkEntry *entry, + G_GNUC_UNUSED gpointer dummy) { tree_view_activate_focused_row (GTK_TREE_VIEW (plugin_data.view)); } static void -on_panel_hide (GtkWidget *widget, - gpointer dummy) +on_panel_hide (G_GNUC_UNUSED GtkWidget *widget, + G_GNUC_UNUSED gpointer dummy) { GtkTreeView *view = GTK_TREE_VIEW (plugin_data.view); @@ -540,8 +540,8 @@ on_panel_hide (GtkWidget *widget, } static void -on_panel_show (GtkWidget *widget, - gpointer dummy) +on_panel_show (G_GNUC_UNUSED GtkWidget *widget, + G_GNUC_UNUSED gpointer dummy) { GtkTreePath *path; GtkTreeView *view = GTK_TREE_VIEW (plugin_data.view); @@ -569,10 +569,10 @@ on_panel_show (GtkWidget *widget, } static void -on_view_row_activated (GtkTreeView *view, - GtkTreePath *path, - GtkTreeViewColumn *column, - gpointer dummy) +on_view_row_activated (GtkTreeView *view, + GtkTreePath *path, + G_GNUC_UNUSED GtkTreeViewColumn *column, + G_GNUC_UNUSED gpointer dummy) { GtkTreeModel *model = gtk_tree_view_get_model (view); GtkTreeIter iter; @@ -732,9 +732,9 @@ create_panel (void) } static gboolean -on_kb_show_panel (GeanyKeyBinding *kb, - guint key_id, - gpointer data) +on_kb_show_panel (G_GNUC_UNUSED GeanyKeyBinding *kb, + G_GNUC_UNUSED guint key_id, + gpointer data) { const gchar *prefix = data; @@ -755,7 +755,7 @@ on_kb_show_panel (GeanyKeyBinding *kb, } static gboolean -on_plugin_idle_init (gpointer dummy) +on_plugin_idle_init (G_GNUC_UNUSED gpointer dummy) { create_panel (); @@ -763,7 +763,7 @@ on_plugin_idle_init (gpointer dummy) } void -plugin_init (GeanyData *data) +plugin_init (G_GNUC_UNUSED GeanyData *data) { GeanyKeyGroup *group; From 86812cd08e4bee53296ce5a538e8ec0ef3bc2b3e Mon Sep 17 00:00:00 2001 From: memstream Date: Fri, 8 Aug 2025 18:11:16 +0300 Subject: [PATCH 03/18] commander: full compliance should have high priority --- commander/src/commander-plugin.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 7dcb15c0a2..1c2e6eb4fa 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -166,11 +166,15 @@ path_basename (const gchar *path) static gint key_score (const gchar *key_, const gchar *text_) -{ +{ gchar *text = g_utf8_casefold (text_, -1); gchar *key = g_utf8_casefold (key_, -1); gint score; - + + /* full compliance should have high priority */ + if (strcmp (key, text) == 0) + return 0xf000; + score = get_score (key, text) + get_score (key, path_basename (text)) / 2; g_free (text); From c6fef7beba10c17fa573c3952b2c45053983fabb Mon Sep 17 00:00:00 2001 From: memstream Date: Fri, 8 Aug 2025 18:12:32 +0300 Subject: [PATCH 04/18] commander: style correction --- commander/src/commander-plugin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 1c2e6eb4fa..74b19f7570 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -713,7 +713,7 @@ create_panel (void) cell = gtk_cell_renderer_text_new (); col = gtk_tree_view_column_new_with_attributes (NULL, cell, NULL); gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_FIXED); - gtk_tree_view_column_set_cell_data_func(col, cell, score_cell_data, col, NULL); + gtk_tree_view_column_set_cell_data_func (col, cell, score_cell_data, col, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (plugin_data.view), col); #endif cell = gtk_cell_renderer_text_new (); From b8f87eecea1b2d127c7cbebe0f37e4fd82d7d187 Mon Sep 17 00:00:00 2001 From: memstream Date: Fri, 8 Aug 2025 18:16:27 +0300 Subject: [PATCH 05/18] commander: eliminates graphic artifacts --- commander/src/commander-plugin.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 74b19f7570..7211a34127 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -709,6 +709,9 @@ create_panel (void) plugin_data.view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (plugin_data.sort)); gtk_widget_set_can_focus (plugin_data.view, FALSE); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (plugin_data.view), FALSE); + + /* eliminates graphic artifacts */ + gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (plugin_data.view), TRUE); #ifdef DISPLAY_SCORE cell = gtk_cell_renderer_text_new (); col = gtk_tree_view_column_new_with_attributes (NULL, cell, NULL); @@ -721,6 +724,7 @@ create_panel (void) col = gtk_tree_view_column_new_with_attributes (NULL, cell, "markup", COL_LABEL, NULL); + gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_FIXED); gtk_tree_view_append_column (GTK_TREE_VIEW (plugin_data.view), col); g_signal_connect (plugin_data.view, "row-activated", G_CALLBACK (on_view_row_activated), NULL); From 3a0873b58326e4fe37d2a9db56e0bd9741886c11 Mon Sep 17 00:00:00 2001 From: memstream Date: Fri, 8 Aug 2025 18:25:39 +0300 Subject: [PATCH 06/18] commander: settings window where you can set the width and height --- commander/src/commander-plugin.c | 187 ++++++++++++++++++++++++++++++- 1 file changed, 185 insertions(+), 2 deletions(-) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 7211a34127..cf2f362450 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -92,6 +92,11 @@ static struct { GtkTreeModel *sort; GtkTreePath *last_path; + + gchar *config_file; + + gint panel_width; + gint panel_height; } plugin_data; typedef enum { @@ -661,8 +666,8 @@ create_panel (void) plugin_data.panel = g_object_new (GTK_TYPE_WINDOW, "decorated", FALSE, - "default-width", 500, - "default-height", 200, + "default-width", plugin_data.panel_width, + "default-height", plugin_data.panel_height, "transient-for", geany_data->main_widgets->window, "window-position", GTK_WIN_POS_CENTER_ON_PARENT, "type-hint", GDK_WINDOW_TYPE_HINT_DIALOG, @@ -774,6 +779,9 @@ void plugin_init (G_GNUC_UNUSED GeanyData *data) { GeanyKeyGroup *group; + GKeyFile *config; + + /* keybindings */ group = plugin_set_key_group (geany_plugin, "commander", KB_COUNT, NULL); keybindings_set_item_full (group, KB_SHOW_PANEL, 0, 0, "show_panel", @@ -787,6 +795,26 @@ plugin_init (G_GNUC_UNUSED GeanyData *data) "show_panel_files", _("Show Command Panel (Files Only)"), NULL, on_kb_show_panel, (gpointer) "f:", NULL); + + /* config */ + + config = g_key_file_new (); + plugin_data.config_file = g_strconcat (geany->app->configdir, G_DIR_SEPARATOR_S, + "plugins", G_DIR_SEPARATOR_S, + "commander", G_DIR_SEPARATOR_S, + "commander.conf", NULL); + + plugin_data.panel_width = utils_get_setting_integer (config, + "commander", + "panel_width", + 500); + + plugin_data.panel_height = utils_get_setting_integer (config, + "commander", + "panel_height", + 300); + + g_key_file_free (config); /* delay for other plugins to have a chance to load before, so we will * include their items */ @@ -802,6 +830,7 @@ plugin_cleanup (void) if (plugin_data.last_path) { gtk_tree_path_free (plugin_data.last_path); } + g_free (plugin_data.config_file); } void @@ -809,3 +838,157 @@ plugin_help (void) { utils_open_browser (DOCDIR "/" PLUGIN "/README"); } + +static gboolean +ui_cfg_read_check_button (GtkDialog *dialog, GKeyFile *config, + const gchar *widget_code, + const gchar *config_code) +{ + GtkWidget *check_button; + gboolean active; + + check_button = g_object_get_data (G_OBJECT (dialog), widget_code); + active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_button)); + g_key_file_set_boolean (config, "commander", config_code, active); + return active; +} + +static glong +ui_cfg_read_spin_button (GtkDialog *dialog, GKeyFile *config, + const gchar *widget_code, + const gchar *config_code) +{ + GtkWidget *spin_button; + gdouble value; + + spin_button = g_object_get_data (G_OBJECT (dialog), widget_code); + value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (spin_button)); + g_key_file_set_integer (config, "commander", config_code, value); + return value; +} + +static void +plugin_configure_response_cb (GtkDialog *dialog, gint response, G_GNUC_UNUSED gpointer user_data) +{ + GKeyFile *config; + gchar *data; + gchar *config_dir; + + if (response != GTK_RESPONSE_OK && response != GTK_RESPONSE_APPLY) + return; + + /* mkdir config_dir */ + config_dir = g_path_get_dirname (plugin_data.config_file); + if (!g_file_test (config_dir, G_FILE_TEST_IS_DIR) && utils_mkdir (config_dir, TRUE) != 0) { + dialogs_show_msgbox (GTK_MESSAGE_ERROR, _("Plugin configuration directory could not be created.")); + g_free (config_dir); + return; + } + g_free (config_dir); + + /* load config */ + config = g_key_file_new (); + g_key_file_load_from_file (config, plugin_data.config_file, G_KEY_FILE_NONE, NULL); + + /* palette */ + plugin_data.panel_width + = ui_cfg_read_spin_button (dialog, config, + "panel_width_spin_button", + "panel_width"); + plugin_data.panel_height + = ui_cfg_read_spin_button (dialog, config, + "panel_height_spin_button", + "panel_height"); + + gtk_window_resize (GTK_WINDOW (plugin_data.panel), plugin_data.panel_width, plugin_data.panel_height); + + /* write config */ + data = g_key_file_to_data (config, NULL, NULL); + utils_write_file (plugin_data.config_file, data); + g_free (data); + g_key_file_free (config); +} + + +static GtkWidget * +ui_cfg_frame_new (GtkWidget *vbox) +{ + GtkWidget *frame; + GtkWidget *frame_vbox; + + frame = gtk_frame_new (NULL); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 3); + + frame_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (frame), frame_vbox); + + return frame_vbox; +} + +static void +ui_cfg_check_button_new (GtkDialog *dialog, GtkWidget *vbox, + const gchar *code, const gchar *label, gboolean active) +{ + GtkWidget *hbox; + GtkWidget *check_button; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 3); + + check_button = gtk_check_button_new_with_label (label); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), active); + gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 3); + g_object_set_data (G_OBJECT (dialog), code, check_button); +} + +static void +ui_cfg_spin_button_new (GtkDialog *dialog, GtkWidget *vbox, + const gchar *code, const gchar *label_text, + gdouble start, gdouble end, gdouble step, + gdouble value) +{ + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *spin_button; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 3); + + label = gtk_label_new_with_mnemonic (label_text); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 3); + + spin_button = gtk_spin_button_new_with_range (start, end, step); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_button), value); + gtk_box_pack_start (GTK_BOX (hbox), spin_button, FALSE, FALSE, 3); + g_object_set_data (G_OBJECT (dialog), code, spin_button); +} + +GtkWidget * +plugin_configure (GtkDialog *dialog) +{ + GtkWidget *vbox; + GtkWidget *palette_container; + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + + /* palette */ + palette_container = ui_cfg_frame_new (vbox); + + ui_cfg_spin_button_new (dialog, palette_container, + "panel_width_spin_button", + _("Command panel window width"), + 100, 4096, 1, + plugin_data.panel_width); + + ui_cfg_spin_button_new (dialog, palette_container, + "panel_height_spin_button", + _("Command panel window height"), + 100, 4096, 1, + plugin_data.panel_height); + + gtk_widget_show_all (vbox); + + g_signal_connect (dialog, "response", G_CALLBACK (plugin_configure_response_cb), NULL); + + return vbox; +} From 51a601bcdaa21d74462bfa8873bd8c5d07636178 Mon Sep 17 00:00:00 2001 From: memstream Date: Fri, 8 Aug 2025 18:41:29 +0300 Subject: [PATCH 07/18] commander: show project files --- commander/src/Makefile.am | 6 +- commander/src/commander-plugin.c | 347 ++++++++++++++++++++++++++++--- 2 files changed, 325 insertions(+), 28 deletions(-) diff --git a/commander/src/Makefile.am b/commander/src/Makefile.am index b7e6b3b6e3..ee02d087b3 100644 --- a/commander/src/Makefile.am +++ b/commander/src/Makefile.am @@ -6,11 +6,13 @@ geanyplugins_LTLIBRARIES = commander.la commander_la_SOURCES = commander-plugin.c commander_la_CPPFLAGS = $(AM_CPPFLAGS) \ - -DG_LOG_DOMAIN=\"Commander\" + -DG_LOG_DOMAIN=\"Commander\" \ + -I$(top_srcdir)/utils/src commander_la_CFLAGS = $(AM_CFLAGS) \ $(COMMANDER_CFLAGS) commander_la_LIBADD = $(COMMONLIBS) \ - $(COMMANDER_LIBS) + $(COMMANDER_LIBS) \ + -lutil $(top_builddir)/utils/src/libgeanypluginutils.la include $(top_srcdir)/build/cppcheck.mk diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index cf2f362450..c801bec231 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -26,6 +26,7 @@ #include #include +#include /* uncomment to display each row score (for debugging sort) */ @@ -95,6 +96,11 @@ static struct { gchar *config_file; + gboolean show_project_file; + guint show_project_file_limit; + gboolean show_project_file_skip_hidden_file; + gboolean show_project_file_skip_hidden_dir; + gint panel_width; gint panel_height; } plugin_data; @@ -106,14 +112,24 @@ typedef enum { } ColType; enum { - COL_LABEL, - COL_PATH, - COL_TYPE, + COL_LABEL, /* display text */ + + COL_VALUE, /* text that is checked against the input */ + + COL_TYPE, /* enum ColType */ + + COL_WIDGET, COL_DOCUMENT, + COL_FILE, COL_COUNT }; +typedef struct { + gint score; + gchar *path; +} FileQueueItem; + #define PATH_SEPARATOR " \342\206\222 " /* right arrow */ @@ -364,7 +380,7 @@ store_populate_menu_items (GtkListStore *store, gtk_list_store_insert_with_values (store, NULL, -1, COL_LABEL, label, - COL_PATH, path, + COL_VALUE, path, COL_TYPE, COL_TYPE_MENU_ITEM, COL_WIDGET, node->data, -1); @@ -403,32 +419,224 @@ find_menubar (GtkContainer *container) } static void -fill_store (GtkListStore *store) +store_add_file (GtkListStore *store, GeanyDocument *doc, const gchar *path) +{ + gchar *basename; + gchar *label; + + basename = g_path_get_basename (path); + + label = g_markup_printf_escaped ("%s\n" + "%s", + basename, + path); + + gtk_list_store_insert_with_values (store, NULL, -1, + COL_LABEL, label, + COL_VALUE, path, + COL_FILE, path, + COL_TYPE, COL_TYPE_FILE, + COL_DOCUMENT, doc, + -1); + + g_free (basename); + g_free (label); +} + +static gboolean +file_has_allowed_ext (const gchar *filename, GSList *allow_ext) +{ + if (allow_ext == NULL) + return TRUE; + + return filelist_patterns_match (allow_ext, filename); +} + +static FileQueueItem * +file_queue_item_new (gint score, const gchar *filepath) +{ + FileQueueItem *self; + + self = g_new (FileQueueItem, 1); + self->score = score; + self->path = g_strdup (filepath); + return self; +} + +static void +file_queue_item_free (FileQueueItem *self) +{ + g_free (self->path); + g_free (self); +} + +static gint +file_queue_item_cmp (const FileQueueItem *a, const FileQueueItem *b, G_GNUC_UNUSED gpointer udata) +{ + return (a->score > b->score) - (a->score < b->score); +} + +static void +file_queue_add (GQueue *queue, guint limit, const gchar *key, const gchar *filepath) +{ + FileQueueItem *qitem; + FileQueueItem *head; + gint score; + + score = key_score (key, filepath); + + if (g_queue_get_length (queue) > limit) { + head = g_queue_peek_head (queue); + if (score < head->score) { + return; + } + + file_queue_item_free (g_queue_pop_head (queue)); + } + + qitem = file_queue_item_new (score, filepath); + g_queue_insert_sorted (queue, qitem, (GCompareDataFunc) file_queue_item_cmp, NULL); +} + +/* recursively adding project files to the queue */ +static void +store_add_dir (GQueue *queue, const gchar *dir_path, + GSList *allow_ext, const gchar *key, guint *show_count) +{ + GDir *dir; + const gchar *filename; + gchar *filepath; + gboolean add_file; + gboolean is_hidden; + + if ((dir = g_dir_open (dir_path, 0, NULL)) == NULL) + return; + + while ((filename = g_dir_read_name (dir)) != NULL) { + if (strcmp (filename, ".") == 0 || strcmp (filename, "..") == 0) + continue; + + is_hidden = filename[0] == '.'; + + filepath = g_build_path (G_DIR_SEPARATOR_S, dir_path, filename, NULL); + + if (g_file_test (filepath, G_FILE_TEST_IS_REGULAR)) { + add_file = file_has_allowed_ext (filename, allow_ext) + && document_find_by_real_path (filepath) == NULL + && (!is_hidden || !plugin_data.show_project_file_skip_hidden_file); + + if (add_file) { + file_queue_add (queue, plugin_data.show_project_file_limit, key, filepath); + } + } else if (g_file_test (filepath, G_FILE_TEST_IS_DIR)) { + if (!is_hidden || !plugin_data.show_project_file_skip_hidden_dir) { + store_add_dir (queue, filepath, allow_ext, key, show_count); + } + } + + g_free (filepath); + } + + g_dir_close (dir); +} + +/* when entering a search query, the old set of files is deleted and + * a new one is added, this function deletes the old parameters */ +static void +clear_store_project_files (GtkListStore *store) +{ + GtkTreeIter iter; + + gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); + + while (gtk_list_store_iter_is_valid (GTK_LIST_STORE (store), &iter)) { + GValue value_type = G_VALUE_INIT; + gtk_tree_model_get_value (GTK_TREE_MODEL (store), &iter, COL_TYPE, &value_type); + if (g_value_get_int (&value_type) != COL_TYPE_FILE) + goto next_iter; + + GValue value_doc = G_VALUE_INIT; + gtk_tree_model_get_value (GTK_TREE_MODEL (store), &iter, COL_DOCUMENT, &value_doc); + if (g_value_get_pointer (&value_doc) == NULL) { + gtk_list_store_remove (GTK_LIST_STORE (store), &iter); + continue; + } + +next_iter: + gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); + } +} + +static void +store_add_project_file (FileQueueItem *item, GtkListStore *store) { - GtkWidget *menubar; - guint i = 0; + store_add_file (store, NULL, item->path); +} + +static void +fill_store_project_files (GtkListStore *store) +{ + GeanyProject *project; + GSList *patterns; + const gchar *key; + gint key_type; + guint show_project_file_count = 0; + GQueue *file_queue; + + if (!plugin_data.show_project_file) + return; + + clear_store_project_files (store); + + if ((project = geany->app->project) == NULL) + return; + + if (project->base_path == NULL) + return; + + key = get_key (&key_type); + + /* instead of adding thousands of items to the TreeView, it's better + * to select the best matches and show only those */ + file_queue = g_queue_new (); + + patterns = filelist_get_precompiled_patterns (project->file_patterns); + store_add_dir (file_queue, project->base_path, patterns, + key, &show_project_file_count); + + g_slist_free_full (patterns, (GDestroyNotify) g_pattern_spec_free); + + g_queue_foreach (file_queue, (GFunc) store_add_project_file, store); + + g_queue_free_full (file_queue, (GDestroyNotify) file_queue_item_free); +} + +static void +fill_store (GtkListStore *store) +{ + GtkWidget *menubar; + guint i = 0; + /* menu items */ menubar = find_menubar (GTK_CONTAINER (geany_data->main_widgets->window)); store_populate_menu_items (store, GTK_MENU_SHELL (menubar), NULL); /* open files */ foreach_document (i) { - gchar *basename = g_path_get_basename (DOC_FILENAME (documents[i])); - gchar *label = g_markup_printf_escaped ("%s\n" - "%s", - basename, - DOC_FILENAME (documents[i])); - - gtk_list_store_insert_with_values (store, NULL, -1, - COL_LABEL, label, - COL_PATH, DOC_FILENAME (documents[i]), - COL_TYPE, COL_TYPE_FILE, - COL_DOCUMENT, documents[i], - -1); - g_free (basename); - g_free (label); + store_add_file (store, documents[i], DOC_FILENAME (documents[i])); } + + /* project files */ + fill_store_project_files (store); +} + +/* when you enter a search query, some options are updated */ +static void +refresh_store (GtkListStore *store) +{ + /* project files */ + fill_store_project_files (store); } static gint @@ -446,8 +654,8 @@ sort_func (GtkTreeModel *model, gint type; const gchar *key = get_key (&type); - gtk_tree_model_get (model, a, COL_PATH, &patha, COL_TYPE, &typea, -1); - gtk_tree_model_get (model, b, COL_PATH, &pathb, COL_TYPE, &typeb, -1); + gtk_tree_model_get (model, a, COL_VALUE, &patha, COL_TYPE, &typea, -1); + gtk_tree_model_get (model, b, COL_VALUE, &pathb, COL_TYPE, &typeb, -1); scorea = key_score (key, patha); scoreb = key_score (key, pathb); @@ -469,7 +677,7 @@ static gboolean on_panel_key_press_event (GtkWidget *widget, GdkEventKey *event, G_GNUC_UNUSED gpointer dummy) -{ +{ switch (event->keyval) { case GDK_KEY_Escape: gtk_widget_hide (widget); @@ -512,6 +720,9 @@ on_entry_text_notify (G_GNUC_UNUSED GObject *object, GtkTreeIter iter; GtkTreeView *view = GTK_TREE_VIEW (plugin_data.view); GtkTreeModel *model = gtk_tree_view_get_model (view); + + /* input related update */ + refresh_store (plugin_data.store); /* we force re-sorting the whole model from how it was before, and the * back to the new filter. this is somewhat hackish but since we don't @@ -595,8 +806,21 @@ on_view_row_activated (GtkTreeView *view, case COL_TYPE_FILE: { GeanyDocument *doc; gint page; + const gchar *filepath; gtk_tree_model_get (model, &iter, COL_DOCUMENT, &doc, -1); + + if (doc == NULL) { + /* file may not be open yet */ + + gtk_tree_model_get (model, &iter, COL_FILE, &filepath, -1); + if (filepath == NULL) + break; + + if ((doc = document_open_file (filepath, FALSE, NULL, NULL)) == NULL) + break; + } + page = document_get_notebook_page (doc); gtk_notebook_set_current_page (GTK_NOTEBOOK (geany_data->main_widgets->notebook), page); @@ -633,7 +857,7 @@ score_cell_data (GtkTreeViewColumn *column, gint width, old_width; const gchar *key = get_key (&type); - gtk_tree_model_get (model, iter, COL_PATH, &path, COL_TYPE, &pathtype, -1); + gtk_tree_model_get (model, iter, COL_VALUE, &path, COL_TYPE, &pathtype, -1); score = key_score (key, path); if (! (pathtype & type)) { @@ -698,7 +922,8 @@ create_panel (void) G_TYPE_STRING, G_TYPE_INT, GTK_TYPE_WIDGET, - G_TYPE_POINTER); + G_TYPE_POINTER, + G_TYPE_STRING); plugin_data.sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (plugin_data.store)); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (plugin_data.sort), @@ -804,6 +1029,30 @@ plugin_init (G_GNUC_UNUSED GeanyData *data) "commander", G_DIR_SEPARATOR_S, "commander.conf", NULL); + g_key_file_load_from_file (config, plugin_data.config_file, G_KEY_FILE_NONE, NULL); + + plugin_data.show_project_file_limit = utils_get_setting_integer (config, + "commander", + "show_project_file_limit", + 16); + + plugin_data.show_project_file = utils_get_setting_boolean (config, + "commander", + "show_project_file", + TRUE); + + plugin_data.show_project_file_skip_hidden_file = + utils_get_setting_boolean (config, + "commander", + "show_project_file_skip_hidden_file", + FALSE); + + plugin_data.show_project_file_skip_hidden_dir = + utils_get_setting_boolean (config, + "commander", + "show_project_file_skip_hidden_dir", + TRUE); + plugin_data.panel_width = utils_get_setting_integer (config, "commander", "panel_width", @@ -890,6 +1139,27 @@ plugin_configure_response_cb (GtkDialog *dialog, gint response, G_GNUC_UNUSED gp config = g_key_file_new (); g_key_file_load_from_file (config, plugin_data.config_file, G_KEY_FILE_NONE, NULL); + /* project file */ + plugin_data.show_project_file + = ui_cfg_read_check_button (dialog, config, + "show_project_file_check_button", + "show_project_file"); + + plugin_data.show_project_file_limit + = ui_cfg_read_spin_button (dialog, config, + "show_project_file_limit_spin_button", + "show_project_file_limit"); + + plugin_data.show_project_file_skip_hidden_file + = ui_cfg_read_check_button (dialog, config, + "show_project_file_skip_hidden_file_check_button", + "show_project_file_skip_hidden_file"); + + plugin_data.show_project_file_skip_hidden_dir + = ui_cfg_read_check_button (dialog, config, + "show_project_file_skip_hidden_dir_check_button", + "show_project_file_skip_hidden_dir"); + /* palette */ plugin_data.panel_width = ui_cfg_read_spin_button (dialog, config, @@ -967,10 +1237,35 @@ GtkWidget * plugin_configure (GtkDialog *dialog) { GtkWidget *vbox; + GtkWidget *show_project_file_container; GtkWidget *palette_container; vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + /* project files */ + show_project_file_container = ui_cfg_frame_new (vbox); + + ui_cfg_check_button_new (dialog, show_project_file_container, + "show_project_file_check_button", + _("Show project files"), + plugin_data.show_project_file); + + ui_cfg_spin_button_new (dialog, show_project_file_container, + "show_project_file_limit_spin_button", + _("Limit of displayed project files:"), + 0, 1024, 1, + plugin_data.show_project_file_limit); + + ui_cfg_check_button_new (dialog, show_project_file_container, + "show_project_file_skip_hidden_file_check_button", + _("Don't show hidden project files"), + plugin_data.show_project_file_skip_hidden_file); + + ui_cfg_check_button_new (dialog, show_project_file_container, + "show_project_file_skip_hidden_dir_check_button", + _("Don't show hidden project directories"), + plugin_data.show_project_file_skip_hidden_dir); + /* palette */ palette_container = ui_cfg_frame_new (vbox); From 1cb36da94975416ba756bfe5985a3abd99e89249 Mon Sep 17 00:00:00 2001 From: memstream Date: Sat, 9 Aug 2025 11:41:59 +0300 Subject: [PATCH 08/18] commander: show symbols --- commander/src/commander-plugin.c | 134 ++++++++++++++++++++++++++++--- 1 file changed, 122 insertions(+), 12 deletions(-) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index c801bec231..4ea17b7471 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -41,7 +41,7 @@ PLUGIN_VERSION_CHECK(226) PLUGIN_SET_TRANSLATABLE_INFO ( LOCALEDIR, GETTEXT_PACKAGE, _("Commander"), - _("Provides a command panel for quick access to actions, files and more"), + _("Provides a command panel for quick access to actions, files, symbols and more"), VERSION, "Colomban Wendling " ) @@ -82,6 +82,7 @@ enum { KB_SHOW_PANEL, KB_SHOW_PANEL_COMMANDS, KB_SHOW_PANEL_FILES, + KB_SHOW_PANEL_SYMBOLS, KB_COUNT }; @@ -100,15 +101,18 @@ static struct { guint show_project_file_limit; gboolean show_project_file_skip_hidden_file; gboolean show_project_file_skip_hidden_dir; + + gboolean show_symbol; gint panel_width; gint panel_height; } plugin_data; typedef enum { - COL_TYPE_MENU_ITEM = 1 << 0, - COL_TYPE_FILE = 1 << 1, - COL_TYPE_ANY = 0xffff + COL_TYPE_MENU_ITEM = 1 << 0, + COL_TYPE_FILE = 1 << 1, + COL_TYPE_SYMBOL = 1 << 2, + COL_TYPE_ANY = 0xffff } ColType; enum { @@ -122,6 +126,7 @@ enum { COL_WIDGET, COL_DOCUMENT, COL_FILE, + COL_LINE, COL_COUNT }; @@ -130,10 +135,14 @@ typedef struct { gchar *path; } FileQueueItem; +static gulong VISIBLE_TAGS = tm_tag_class_t | tm_tag_enum_t | tm_tag_function_t | + tm_tag_interface_t | tm_tag_method_t | tm_tag_struct_t | + tm_tag_union_t | tm_tag_variable_t | tm_tag_macro_t | + tm_tag_macro_with_arg_t; #define PATH_SEPARATOR " \342\206\222 " /* right arrow */ -#define SEPARATORS " -_./\\\"'" +#define SEPARATORS " -_./\\\"':" #define IS_SEPARATOR(c) (strchr (SEPARATORS, (c)) != NULL) #define next_separator(p) (strpbrk (p, SEPARATORS)) @@ -216,6 +225,9 @@ get_key (gint *type_) } else if (g_str_has_prefix (key, "c:")) { key += 2; type = COL_TYPE_MENU_ITEM; + } else if (g_str_has_prefix (key, "s:")) { + key += 2; + type = COL_TYPE_SYMBOL; } if (type_) { @@ -612,6 +624,60 @@ fill_store_project_files (GtkListStore *store) g_queue_free_full (file_queue, (GDestroyNotify) file_queue_item_free); } +static void +fill_store_symbols (GtkListStore *store) +{ + const TMWorkspace *ws; + guint i; + GPtrArray *tags; + TMTag *tag; + gchar *name; + gchar *label; + + if (!plugin_data.show_symbol) + return; + + if ((ws = geany->app->tm_workspace) == NULL) + return; + + if (ws->tags_array == NULL) + return; + + tags = ws->tags_array; + for (i = 0; i < tags->len; ++i) { + tag = TM_TAG (tags->pdata[i]); + + if (tag->file == NULL) + continue; + + if ((tag->type & VISIBLE_TAGS) == 0) + continue; + + if (tag->scope == NULL) { + name = g_strdup (tag->name); + } else { + name = g_strdup_printf ("%s::%s", tag->scope, tag->name); + } + + label = g_markup_printf_escaped ("%s\n" + "%s:%lu", + name, + tag->file->file_name, + tag->line); + + gtk_list_store_insert_with_values (store, NULL, -1, + COL_LABEL, label, + COL_VALUE, name, + COL_TYPE, COL_TYPE_SYMBOL, + COL_FILE, tag->file->file_name, + COL_LINE, tag->line, + -1); + + g_free (name); + g_free (label); + } +} + static void fill_store (GtkListStore *store) { @@ -629,6 +695,9 @@ fill_store (GtkListStore *store) /* project files */ fill_store_project_files (store); + + /* symbols */ + fill_store_symbols (store); } /* when you enter a search query, some options are updated */ @@ -836,6 +905,22 @@ on_view_row_activated (GtkTreeView *view, break; } + + case COL_TYPE_SYMBOL: { + GeanyDocument *doc; + const gchar *filepath; + gulong line; + + gtk_tree_model_get (model, &iter, COL_FILE, &filepath, -1); + gtk_tree_model_get (model, &iter, COL_LINE, &line, -1); + + if ((doc = document_open_file (filepath, FALSE, NULL, NULL)) == NULL) + break; + + navqueue_goto_line (document_get_current (), doc, line); + + break; + } } gtk_widget_hide (plugin_data.panel); } @@ -923,7 +1008,8 @@ create_panel (void) G_TYPE_INT, GTK_TYPE_WIDGET, G_TYPE_POINTER, - G_TYPE_STRING); + G_TYPE_STRING, + G_TYPE_ULONG); plugin_data.sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (plugin_data.store)); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (plugin_data.sort), @@ -975,9 +1061,9 @@ on_kb_show_panel (G_GNUC_UNUSED GeanyKeyBinding *kb, gpointer data) { const gchar *prefix = data; - + gtk_widget_show (plugin_data.panel); - + if (prefix) { const gchar *key = gtk_entry_get_text (GTK_ENTRY (plugin_data.entry)); @@ -1005,7 +1091,7 @@ plugin_init (G_GNUC_UNUSED GeanyData *data) { GeanyKeyGroup *group; GKeyFile *config; - + /* keybindings */ group = plugin_set_key_group (geany_plugin, "commander", KB_COUNT, NULL); @@ -1020,7 +1106,11 @@ plugin_init (G_GNUC_UNUSED GeanyData *data) "show_panel_files", _("Show Command Panel (Files Only)"), NULL, on_kb_show_panel, (gpointer) "f:", NULL); - + keybindings_set_item_full (group, KB_SHOW_PANEL_SYMBOLS, 0, 0, + "show_panel_symbols", + _("Show Command Panel (Symbols Only)"), NULL, + on_kb_show_panel, (gpointer) "s:", NULL); + /* config */ config = g_key_file_new (); @@ -1053,6 +1143,11 @@ plugin_init (G_GNUC_UNUSED GeanyData *data) "show_project_file_skip_hidden_dir", TRUE); + plugin_data.show_symbol = utils_get_setting_boolean (config, + "commander", + "show_symbol", + TRUE); + plugin_data.panel_width = utils_get_setting_integer (config, "commander", "panel_width", @@ -1064,7 +1159,7 @@ plugin_init (G_GNUC_UNUSED GeanyData *data) 300); g_key_file_free (config); - + /* delay for other plugins to have a chance to load before, so we will * include their items */ plugin_idle_add (geany_plugin, on_plugin_idle_init, NULL); @@ -1114,7 +1209,7 @@ ui_cfg_read_spin_button (GtkDialog *dialog, GKeyFile *config, value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (spin_button)); g_key_file_set_integer (config, "commander", config_code, value); return value; -} +} static void plugin_configure_response_cb (GtkDialog *dialog, gint response, G_GNUC_UNUSED gpointer user_data) @@ -1160,6 +1255,12 @@ plugin_configure_response_cb (GtkDialog *dialog, gint response, G_GNUC_UNUSED gp "show_project_file_skip_hidden_dir_check_button", "show_project_file_skip_hidden_dir"); + /* symbols */ + plugin_data.show_symbol + = ui_cfg_read_check_button (dialog, config, + "show_symbol_check_button", + "show_symbol"); + /* palette */ plugin_data.panel_width = ui_cfg_read_spin_button (dialog, config, @@ -1238,6 +1339,7 @@ plugin_configure (GtkDialog *dialog) { GtkWidget *vbox; GtkWidget *show_project_file_container; + GtkWidget *show_symbol_container; GtkWidget *palette_container; vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); @@ -1266,6 +1368,14 @@ plugin_configure (GtkDialog *dialog) _("Don't show hidden project directories"), plugin_data.show_project_file_skip_hidden_dir); + /* symbols */ + show_symbol_container = ui_cfg_frame_new (vbox); + + ui_cfg_check_button_new (dialog, show_symbol_container, + "show_symbol_check_button", + _("Show symbols"), + plugin_data.show_symbol); + /* palette */ palette_container = ui_cfg_frame_new (vbox); From d5260de18a254574b380e6633c442259ef3051f9 Mon Sep 17 00:00:00 2001 From: memstream Date: Sat, 9 Aug 2025 11:45:43 +0300 Subject: [PATCH 09/18] commander: show icons --- commander/src/commander-plugin.c | 76 +++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 4ea17b7471..f87230154a 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -127,6 +127,7 @@ enum { COL_DOCUMENT, COL_FILE, COL_LINE, + COL_ICON, COL_COUNT }; @@ -624,6 +625,27 @@ fill_store_project_files (GtkListStore *store) g_queue_free_full (file_queue, (GDestroyNotify) file_queue_item_free); } +static const gchar * +get_symbol_icon (const TMTag *tag) +{ + if (tag->type & tm_tag_struct_t) + return "classviewer-struct"; + + if (tag->type & (tm_tag_class_t | tm_tag_interface_t)) + return "classviewer-class"; + + if (tag->type & (tm_tag_function_t | tm_tag_function_t)) + return "classviewer-method"; + + if (tag->type & (tm_tag_macro_t | tm_tag_macro_with_arg_t)) + return "classviewer-macro"; + + if (tag->type & tm_tag_variable_t) + return "classviewer-var"; + + return "classviewer-other"; +} + static void fill_store_symbols (GtkListStore *store) { @@ -671,6 +693,7 @@ fill_store_symbols (GtkListStore *store) COL_TYPE, COL_TYPE_SYMBOL, COL_FILE, tag->file->file_name, COL_LINE, tag->line, + COL_ICON, get_symbol_icon (tag), -1); g_free (name); @@ -926,6 +949,37 @@ on_view_row_activated (GtkTreeView *view, } } +static void +cell_icon_data (G_GNUC_UNUSED GtkTreeViewColumn *column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + G_GNUC_UNUSED gpointer udata) +{ + const gchar *icon_name; + gint type; + + gtk_tree_model_get (model, iter, COL_TYPE, &type, -1); + + switch (type) { + case COL_TYPE_FILE: { + g_object_set (cell, "icon-name", "text-x-generic", NULL); + break; + } + + case COL_TYPE_MENU_ITEM: { + g_object_set (cell, "icon-name", "geany", NULL); + break; + } + + case COL_TYPE_SYMBOL: { + gtk_tree_model_get (model, iter, COL_ICON, &icon_name, -1); + g_object_set (cell, "icon-name", icon_name, NULL); + break; + } + } +} + #ifdef DISPLAY_SCORE static void score_cell_data (GtkTreeViewColumn *column, @@ -967,11 +1021,13 @@ score_cell_data (GtkTreeViewColumn *column, static void create_panel (void) { - GtkWidget *frame; - GtkWidget *box; - GtkWidget *scroll; - GtkTreeViewColumn *col; - GtkCellRenderer *cell; + GtkWidget *frame; + GtkWidget *box; + GtkWidget *scroll; + GtkTreeViewColumn *col_icon; + GtkTreeViewColumn *col; + GtkCellRenderer *cell_icon; + GtkCellRenderer *cell; plugin_data.panel = g_object_new (GTK_TYPE_WINDOW, "decorated", FALSE, @@ -1009,7 +1065,8 @@ create_panel (void) GTK_TYPE_WIDGET, G_TYPE_POINTER, G_TYPE_STRING, - G_TYPE_ULONG); + G_TYPE_ULONG, + G_TYPE_STRING); plugin_data.sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (plugin_data.store)); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (plugin_data.sort), @@ -1028,6 +1085,13 @@ create_panel (void) /* eliminates graphic artifacts */ gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (plugin_data.view), TRUE); + + cell_icon = gtk_cell_renderer_pixbuf_new (); + col_icon = gtk_tree_view_column_new_with_attributes (NULL, cell_icon, NULL); + gtk_tree_view_column_set_cell_data_func (col_icon, cell_icon, cell_icon_data, NULL, NULL); + gtk_tree_view_column_set_sizing (col_icon, GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_append_column (GTK_TREE_VIEW (plugin_data.view), col_icon); + #ifdef DISPLAY_SCORE cell = gtk_cell_renderer_text_new (); col = gtk_tree_view_column_new_with_attributes (NULL, cell, NULL); From 00559538501d2cbcb9c3f37fc5a24a647fa3df71 Mon Sep 17 00:00:00 2001 From: memstream Date: Fri, 22 Aug 2025 20:39:40 +0300 Subject: [PATCH 10/18] commander: file search moved to GTask --- commander/src/commander-plugin.c | 159 +++++++++++++++++++++++-------- 1 file changed, 121 insertions(+), 38 deletions(-) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index f87230154a..58dca6912a 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -87,25 +87,26 @@ enum { }; static struct { - GtkWidget *panel; - GtkWidget *entry; - GtkWidget *view; - GtkListStore *store; - GtkTreeModel *sort; + GtkWidget *panel; + GtkWidget *entry; + GtkWidget *view; + GtkListStore *store; + GtkTreeModel *sort; - GtkTreePath *last_path; + GtkTreePath *last_path; - gchar *config_file; + gchar *config_file; - gboolean show_project_file; - guint show_project_file_limit; - gboolean show_project_file_skip_hidden_file; - gboolean show_project_file_skip_hidden_dir; + GCancellable *show_project_file_task_cancellable; + gboolean show_project_file; + guint show_project_file_limit; + gboolean show_project_file_skip_hidden_file; + gboolean show_project_file_skip_hidden_dir; - gboolean show_symbol; + gboolean show_symbol; - gint panel_width; - gint panel_height; + gint panel_width; + gint panel_height; } plugin_data; typedef enum { @@ -136,6 +137,13 @@ typedef struct { gchar *path; } FileQueueItem; +typedef struct { + GtkListStore *store; + gchar *dir_path; + GSList *patterns; + gchar *key; +} FileSearchTaskInput; + static gulong VISIBLE_TAGS = tm_tag_class_t | tm_tag_enum_t | tm_tag_function_t | tm_tag_interface_t | tm_tag_method_t | tm_tag_struct_t | tm_tag_union_t | tm_tag_variable_t | tm_tag_macro_t | @@ -511,10 +519,20 @@ file_queue_add (GQueue *queue, guint limit, const gchar *key, const gchar *filep g_queue_insert_sorted (queue, qitem, (GCompareDataFunc) file_queue_item_cmp, NULL); } +static void file_search_task_input_free (FileSearchTaskInput *self) +{ + g_free (self->dir_path); + g_slist_free_full (self->patterns, (GDestroyNotify) g_pattern_spec_free); + g_free (self->key); +} + /* recursively adding project files to the queue */ static void -store_add_dir (GQueue *queue, const gchar *dir_path, - GSList *allow_ext, const gchar *key, guint *show_count) +store_add_dir (GQueue *queue, + const gchar *dir_path, + GSList *allow_ext, + const gchar *key, + GCancellable *cancellable) { GDir *dir; const gchar *filename; @@ -522,6 +540,9 @@ store_add_dir (GQueue *queue, const gchar *dir_path, gboolean add_file; gboolean is_hidden; + if (g_cancellable_is_cancelled (cancellable)) + return; + if ((dir = g_dir_open (dir_path, 0, NULL)) == NULL) return; @@ -543,7 +564,7 @@ store_add_dir (GQueue *queue, const gchar *dir_path, } } else if (g_file_test (filepath, G_FILE_TEST_IS_DIR)) { if (!is_hidden || !plugin_data.show_project_file_skip_hidden_dir) { - store_add_dir (queue, filepath, allow_ext, key, show_count); + store_add_dir (queue, filepath, allow_ext, key, cancellable); } } @@ -587,19 +608,68 @@ store_add_project_file (FileQueueItem *item, GtkListStore *store) } static void -fill_store_project_files (GtkListStore *store) +do_show_project_file_task (GTask *task, + G_GNUC_UNUSED gpointer source_object, + gpointer task_data, + GCancellable *cancellable) { - GeanyProject *project; - GSList *patterns; - const gchar *key; - gint key_type; - guint show_project_file_count = 0; - GQueue *file_queue; + FileSearchTaskInput *file_search_task_input; + GQueue *file_queue; - if (!plugin_data.show_project_file) + file_search_task_input = task_data; + + /* To prevent GTK from slowing down, you need to reduce the number of options and + * leave the best ones. GQueue is well suited for such selection. */ + file_queue = g_queue_new (); + + store_add_dir (file_queue, + file_search_task_input->dir_path, + file_search_task_input->patterns, + file_search_task_input->key, + cancellable); + + if (g_task_return_error_if_cancelled (task)) return; - clear_store_project_files (store); + g_task_return_pointer (task, file_queue, NULL); +} + +static void +on_show_project_file_task_ready (G_GNUC_UNUSED GObject *source_object, + GAsyncResult *result, + GError **error) +{ + FileSearchTaskInput *task_input; + GQueue *file_queue; + + task_input = g_task_get_task_data (G_TASK(result)); + file_queue = g_task_propagate_pointer (G_TASK (result), error); + + /* Adding found files to store */ + clear_store_project_files (task_input->store); + g_queue_foreach (file_queue, (GFunc) store_add_project_file, plugin_data.store); + g_queue_free_full (file_queue, (GDestroyNotify) file_queue_item_free); +} + +static void +cancel_fill_store_project_files_task (void) +{ + g_cancellable_cancel (plugin_data.show_project_file_task_cancellable); + g_cancellable_reset (plugin_data.show_project_file_task_cancellable); +} + +static void +add_fill_store_project_files_task (GtkListStore *store) +{ + GeanyProject *project; + GSList *patterns; + const gchar *key; + gint key_type; + FileSearchTaskInput *file_search_task_input; + GTask *task; + + if (!plugin_data.show_project_file) + return; if ((project = geany->app->project) == NULL) return; @@ -609,20 +679,27 @@ fill_store_project_files (GtkListStore *store) key = get_key (&key_type); - /* instead of adding thousands of items to the TreeView, it's better - * to select the best matches and show only those */ - file_queue = g_queue_new (); - patterns = filelist_get_precompiled_patterns (project->file_patterns); - - store_add_dir (file_queue, project->base_path, patterns, - key, &show_project_file_count); - g_slist_free_full (patterns, (GDestroyNotify) g_pattern_spec_free); + file_search_task_input = g_new0 (FileSearchTaskInput, 1); + file_search_task_input->store = store; + file_search_task_input->dir_path = g_strdup(project->base_path); + file_search_task_input->patterns = patterns; + file_search_task_input->key = g_strdup(key); - g_queue_foreach (file_queue, (GFunc) store_add_project_file, store); + cancel_fill_store_project_files_task (); - g_queue_free_full (file_queue, (GDestroyNotify) file_queue_item_free); + /* creating a new task */ + task = g_task_new (NULL, + plugin_data.show_project_file_task_cancellable, + (GAsyncReadyCallback) on_show_project_file_task_ready, + NULL); + + g_task_set_task_data (task, + file_search_task_input, + (GDestroyNotify) file_search_task_input_free); + + g_task_run_in_thread (task, do_show_project_file_task); } static const gchar * @@ -717,7 +794,7 @@ fill_store (GtkListStore *store) } /* project files */ - fill_store_project_files (store); + add_fill_store_project_files_task (store); /* symbols */ fill_store_symbols (store); @@ -728,7 +805,7 @@ static void refresh_store (GtkListStore *store) { /* project files */ - fill_store_project_files (store); + add_fill_store_project_files_task (store); } static gint @@ -1224,6 +1301,9 @@ plugin_init (G_GNUC_UNUSED GeanyData *data) g_key_file_free (config); + /* show project file task */ + plugin_data.show_project_file_task_cancellable = g_cancellable_new (); + /* delay for other plugins to have a chance to load before, so we will * include their items */ plugin_idle_add (geany_plugin, on_plugin_idle_init, NULL); @@ -1232,6 +1312,9 @@ plugin_init (G_GNUC_UNUSED GeanyData *data) void plugin_cleanup (void) { + cancel_fill_store_project_files_task (); + g_object_unref (plugin_data.show_project_file_task_cancellable); + if (plugin_data.panel) { gtk_widget_destroy (plugin_data.panel); } From 320be934f768e88be2f58db8dab7a61a7616c090 Mon Sep 17 00:00:00 2001 From: memstream Date: Fri, 22 Aug 2025 20:47:57 +0300 Subject: [PATCH 11/18] commander: after adding files, move the cursor up --- commander/src/commander-plugin.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 58dca6912a..37a6e31edb 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -526,6 +526,17 @@ static void file_search_task_input_free (FileSearchTaskInput *self) g_free (self->key); } +static void tree_view_cursor_to_top (void) +{ + GtkTreeIter iter; + GtkTreeView *view = GTK_TREE_VIEW (plugin_data.view); + GtkTreeModel *model = gtk_tree_view_get_model (view); + + if (gtk_tree_model_get_iter_first (model, &iter)) { + tree_view_set_cursor_from_iter (view, &iter); + } +} + /* recursively adding project files to the queue */ static void store_add_dir (GQueue *queue, @@ -649,6 +660,8 @@ on_show_project_file_task_ready (G_GNUC_UNUSED GObject *source_object, clear_store_project_files (task_input->store); g_queue_foreach (file_queue, (GFunc) store_add_project_file, plugin_data.store); g_queue_free_full (file_queue, (GDestroyNotify) file_queue_item_free); + + tree_view_cursor_to_top (); } static void @@ -886,7 +899,6 @@ on_entry_text_notify (G_GNUC_UNUSED GObject *object, G_GNUC_UNUSED GParamSpec *pspec, G_GNUC_UNUSED gpointer dummy) { - GtkTreeIter iter; GtkTreeView *view = GTK_TREE_VIEW (plugin_data.view); GtkTreeModel *model = gtk_tree_view_get_model (view); @@ -901,9 +913,7 @@ on_entry_text_notify (G_GNUC_UNUSED GObject *object, gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (model), sort_func, NULL, NULL); - if (gtk_tree_model_get_iter_first (model, &iter)) { - tree_view_set_cursor_from_iter (view, &iter); - } + tree_view_cursor_to_top (); } static void From 17418ab4f532908123ecd48135fc1f87d17ca697 Mon Sep 17 00:00:00 2001 From: memstream Date: Wed, 27 Aug 2025 00:56:19 +0300 Subject: [PATCH 12/18] commander: fix leak --- commander/src/commander-plugin.c | 1 + 1 file changed, 1 insertion(+) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 37a6e31edb..8b22eccc4d 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -713,6 +713,7 @@ add_fill_store_project_files_task (GtkListStore *store) (GDestroyNotify) file_search_task_input_free); g_task_run_in_thread (task, do_show_project_file_task); + g_object_unref (task); } static const gchar * From 301cac2fe81569df5f3921e4d4dd22e7ded07d0e Mon Sep 17 00:00:00 2001 From: memstream Date: Wed, 27 Aug 2025 01:36:59 +0300 Subject: [PATCH 13/18] commander: fix cancellable --- commander/src/commander-plugin.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 8b22eccc4d..dbe5ce71bb 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -656,6 +656,9 @@ on_show_project_file_task_ready (G_GNUC_UNUSED GObject *source_object, task_input = g_task_get_task_data (G_TASK(result)); file_queue = g_task_propagate_pointer (G_TASK (result), error); + if (file_queue == NULL) + return; + /* Adding found files to store */ clear_store_project_files (task_input->store); g_queue_foreach (file_queue, (GFunc) store_add_project_file, plugin_data.store); @@ -667,8 +670,12 @@ on_show_project_file_task_ready (G_GNUC_UNUSED GObject *source_object, static void cancel_fill_store_project_files_task (void) { - g_cancellable_cancel (plugin_data.show_project_file_task_cancellable); - g_cancellable_reset (plugin_data.show_project_file_task_cancellable); + if (plugin_data.show_project_file_task_cancellable != NULL) { + g_cancellable_cancel (plugin_data.show_project_file_task_cancellable); + g_object_unref (plugin_data.show_project_file_task_cancellable); + } + + plugin_data.show_project_file_task_cancellable = g_cancellable_new (); } static void @@ -1312,9 +1319,6 @@ plugin_init (G_GNUC_UNUSED GeanyData *data) g_key_file_free (config); - /* show project file task */ - plugin_data.show_project_file_task_cancellable = g_cancellable_new (); - /* delay for other plugins to have a chance to load before, so we will * include their items */ plugin_idle_add (geany_plugin, on_plugin_idle_init, NULL); From b23d2a1b6f2d0467848bab4552c1957c21eae9b5 Mon Sep 17 00:00:00 2001 From: memstream Date: Wed, 27 Aug 2025 01:43:06 +0300 Subject: [PATCH 14/18] commander: search spinner --- commander/src/commander-plugin.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index dbe5ce71bb..27e954d3fa 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -92,6 +92,7 @@ static struct { GtkWidget *view; GtkListStore *store; GtkTreeModel *sort; + GtkWidget *spinner; GtkTreePath *last_path; @@ -665,6 +666,7 @@ on_show_project_file_task_ready (G_GNUC_UNUSED GObject *source_object, g_queue_free_full (file_queue, (GDestroyNotify) file_queue_item_free); tree_view_cursor_to_top (); + gtk_widget_hide (plugin_data.spinner); } static void @@ -721,6 +723,8 @@ add_fill_store_project_files_task (GtkListStore *store) g_task_run_in_thread (task, do_show_project_file_task); g_object_unref (task); + + gtk_widget_show (plugin_data.spinner); } static const gchar * @@ -1205,6 +1209,12 @@ create_panel (void) G_CALLBACK (on_view_row_activated), NULL); gtk_container_add (GTK_CONTAINER (scroll), plugin_data.view); + /* spinner */ + plugin_data.spinner = gtk_spinner_new (); + gtk_spinner_start (GTK_SPINNER (plugin_data.spinner)); + gtk_box_pack_start (GTK_BOX (box), plugin_data.spinner, FALSE, TRUE, 5); + gtk_widget_set_size_request (plugin_data.spinner, 24, 24); + /* connect entry signals after the view is created as they use it */ g_signal_connect (plugin_data.entry, "notify::text", G_CALLBACK (on_entry_text_notify), NULL); @@ -1212,6 +1222,7 @@ create_panel (void) G_CALLBACK (on_entry_activate), NULL); gtk_widget_show_all (frame); + gtk_widget_hide (plugin_data.spinner); } static gboolean From 67dddd22a541fcec6d02edfd27942688fc84e39e Mon Sep 17 00:00:00 2001 From: memstream Date: Wed, 27 Aug 2025 01:44:00 +0300 Subject: [PATCH 15/18] commander: fix leak --- commander/src/commander-plugin.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 27e954d3fa..1f571567cf 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -640,8 +640,10 @@ do_show_project_file_task (GTask *task, file_search_task_input->key, cancellable); - if (g_task_return_error_if_cancelled (task)) + if (g_task_return_error_if_cancelled (task)) { + g_queue_free_full (file_queue, (GDestroyNotify) file_queue_item_free); return; + } g_task_return_pointer (task, file_queue, NULL); } From 526ff2b8ad2f5ce3e11c0bc1aac578390ad5bfd3 Mon Sep 17 00:00:00 2001 From: memstream Date: Wed, 27 Aug 2025 01:48:07 +0300 Subject: [PATCH 16/18] commander: fix style --- commander/src/commander-plugin.c | 51 ++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 1f571567cf..1527b0f4dc 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -441,7 +441,9 @@ find_menubar (GtkContainer *container) } static void -store_add_file (GtkListStore *store, GeanyDocument *doc, const gchar *path) +store_add_file (GtkListStore *store, + GeanyDocument *doc, + const gchar *path) { gchar *basename; gchar *label; @@ -466,7 +468,8 @@ store_add_file (GtkListStore *store, GeanyDocument *doc, const gchar *path) } static gboolean -file_has_allowed_ext (const gchar *filename, GSList *allow_ext) +file_has_allowed_ext (const gchar *filename, + GSList *allow_ext) { if (allow_ext == NULL) return TRUE; @@ -475,7 +478,8 @@ file_has_allowed_ext (const gchar *filename, GSList *allow_ext) } static FileQueueItem * -file_queue_item_new (gint score, const gchar *filepath) +file_queue_item_new (gint score, + const gchar *filepath) { FileQueueItem *self; @@ -493,13 +497,18 @@ file_queue_item_free (FileQueueItem *self) } static gint -file_queue_item_cmp (const FileQueueItem *a, const FileQueueItem *b, G_GNUC_UNUSED gpointer udata) +file_queue_item_cmp (const FileQueueItem *a, + const FileQueueItem *b, + G_GNUC_UNUSED gpointer udata) { return (a->score > b->score) - (a->score < b->score); } static void -file_queue_add (GQueue *queue, guint limit, const gchar *key, const gchar *filepath) +file_queue_add (GQueue *queue, + guint limit, + const gchar *key, + const gchar *filepath) { FileQueueItem *qitem; FileQueueItem *head; @@ -614,7 +623,8 @@ clear_store_project_files (GtkListStore *store) } static void -store_add_project_file (FileQueueItem *item, GtkListStore *store) +store_add_project_file (FileQueueItem *item, + GtkListStore *store) { store_add_file (store, NULL, item->path); } @@ -1359,7 +1369,8 @@ plugin_help (void) } static gboolean -ui_cfg_read_check_button (GtkDialog *dialog, GKeyFile *config, +ui_cfg_read_check_button (GtkDialog *dialog, + GKeyFile *config, const gchar *widget_code, const gchar *config_code) { @@ -1373,7 +1384,8 @@ ui_cfg_read_check_button (GtkDialog *dialog, GKeyFile *config, } static glong -ui_cfg_read_spin_button (GtkDialog *dialog, GKeyFile *config, +ui_cfg_read_spin_button (GtkDialog *dialog, + GKeyFile *config, const gchar *widget_code, const gchar *config_code) { @@ -1387,7 +1399,9 @@ ui_cfg_read_spin_button (GtkDialog *dialog, GKeyFile *config, } static void -plugin_configure_response_cb (GtkDialog *dialog, gint response, G_GNUC_UNUSED gpointer user_data) +plugin_configure_response_cb (GtkDialog *dialog, + gint response, + G_GNUC_UNUSED gpointer user_data) { GKeyFile *config; gchar *data; @@ -1472,8 +1486,11 @@ ui_cfg_frame_new (GtkWidget *vbox) } static void -ui_cfg_check_button_new (GtkDialog *dialog, GtkWidget *vbox, - const gchar *code, const gchar *label, gboolean active) +ui_cfg_check_button_new (GtkDialog *dialog, + GtkWidget *vbox, + const gchar *code, + const gchar *label, + gboolean active) { GtkWidget *hbox; GtkWidget *check_button; @@ -1488,10 +1505,14 @@ ui_cfg_check_button_new (GtkDialog *dialog, GtkWidget *vbox, } static void -ui_cfg_spin_button_new (GtkDialog *dialog, GtkWidget *vbox, - const gchar *code, const gchar *label_text, - gdouble start, gdouble end, gdouble step, - gdouble value) +ui_cfg_spin_button_new (GtkDialog *dialog, + GtkWidget *vbox, + const gchar *code, + const gchar *label_text, + gdouble start, + gdouble end, + gdouble step, + gdouble value) { GtkWidget *hbox; GtkWidget *label; From 022018419d8fed972e933d015b98839877daae40 Mon Sep 17 00:00:00 2001 From: memstream Date: Wed, 27 Aug 2025 03:56:22 +0300 Subject: [PATCH 17/18] commander: fix scroll to top --- commander/src/commander-plugin.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 1527b0f4dc..0ad5423716 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -541,9 +541,14 @@ static void tree_view_cursor_to_top (void) GtkTreeIter iter; GtkTreeView *view = GTK_TREE_VIEW (plugin_data.view); GtkTreeModel *model = gtk_tree_view_get_model (view); + GtkTreePath *path; if (gtk_tree_model_get_iter_first (model, &iter)) { tree_view_set_cursor_from_iter (view, &iter); + + gtk_tree_view_get_cursor (view, &path, NULL); + gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0.5, 0.5); + gtk_tree_path_free (path); } } From ba33d45ac9d1f2ca620727ced92d0618c92353cd Mon Sep 17 00:00:00 2001 From: memstream Date: Sat, 6 Sep 2025 22:17:52 +0300 Subject: [PATCH 18/18] commander: optimization --- commander/src/commander-plugin.c | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 0ad5423716..5d28a4e150 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -204,26 +204,32 @@ path_basename (const gchar *path) } static gint -key_score (const gchar *key_, +key_score (const gchar *key, const gchar *text_) { - gchar *text = g_utf8_casefold (text_, -1); - gchar *key = g_utf8_casefold (key_, -1); - gint score; + gchar *text = g_utf8_casefold (text_, -1); + gint score; + const gchar *bsname; /* full compliance should have high priority */ if (strcmp (key, text) == 0) return 0xf000; - score = get_score (key, text) + get_score (key, path_basename (text)) / 2; + score = get_score (key, text); + + bsname = path_basename (text); + if (bsname != text) { + score += get_score (key, bsname) / 2; + } else { + score += score / 2; + } g_free (text); - g_free (key); return score; } -static const gchar * +static gchar * get_key (gint *type_) { gint type = COL_TYPE_ANY; @@ -244,7 +250,7 @@ get_key (gint *type_) *type_ = type; } - return key; + return g_utf8_casefold (key, -1); } static void @@ -702,7 +708,7 @@ add_fill_store_project_files_task (GtkListStore *store) { GeanyProject *project; GSList *patterns; - const gchar *key; + gchar *key; gint key_type; FileSearchTaskInput *file_search_task_input; GTask *task; @@ -724,7 +730,7 @@ add_fill_store_project_files_task (GtkListStore *store) file_search_task_input->store = store; file_search_task_input->dir_path = g_strdup(project->base_path); file_search_task_input->patterns = patterns; - file_search_task_input->key = g_strdup(key); + file_search_task_input->key = key; cancel_fill_store_project_files_task (); @@ -863,7 +869,7 @@ sort_func (GtkTreeModel *model, gint typea; gint typeb; gint type; - const gchar *key = get_key (&type); + gchar *key = get_key (&type); gtk_tree_model_get (model, a, COL_VALUE, &patha, COL_TYPE, &typea, -1); gtk_tree_model_get (model, b, COL_VALUE, &pathb, COL_TYPE, &typeb, -1); @@ -880,6 +886,7 @@ sort_func (GtkTreeModel *model, g_free (patha); g_free (pathb); + g_free (key); return scoreb - scorea; } @@ -1110,7 +1117,7 @@ score_cell_data (GtkTreeViewColumn *column, gint pathtype; gint type; gint width, old_width; - const gchar *key = get_key (&type); + gchar *key = get_key (&type); gtk_tree_model_get (model, iter, COL_VALUE, &path, COL_TYPE, &pathtype, -1); @@ -1131,6 +1138,7 @@ score_cell_data (GtkTreeViewColumn *column, g_free (text); g_free (path); + g_free (key); } #endif