[gnome-builder/wip/mwleeds/replace: 27/30] editor-frame: Add actions to make search-and-replace work



commit 8d870b6e693df9d2aca94e92d684af9884d94cfc
Author: Matthew Leeds <mleeds redhat com>
Date:   Fri Jul 1 11:54:27 2016 -0400

    editor-frame: Add actions to make search-and-replace work
    
    This commit adds actions so the "Replace" and "Replace All" buttons
    work, using the GtkSourceView API. It also adds actions to show or hide
    the Replace widgets, to show or hide the search options, and to exit the
    search, which are all buttons in the search box.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=765635

 libide/editor/ide-editor-frame-actions.c |  165 +++++++++++++++++++++++++++++-
 libide/editor/ide-editor-frame-private.h |    5 +
 libide/editor/ide-editor-frame.c         |   98 ++++++++----------
 3 files changed, 213 insertions(+), 55 deletions(-)
---
diff --git a/libide/editor/ide-editor-frame-actions.c b/libide/editor/ide-editor-frame-actions.c
index 028c53b..5c5e2b8 100644
--- a/libide/editor/ide-editor-frame-actions.c
+++ b/libide/editor/ide-editor-frame-actions.c
@@ -20,6 +20,7 @@
 
 #include "ide-editor-frame-actions.h"
 #include "ide-editor-frame-private.h"
+#include "util/ide-gtk.h"
 
 static void
 ide_editor_frame_actions_find (GSimpleAction *action,
@@ -176,6 +177,162 @@ ide_editor_frame_actions_select_all (GSimpleAction *action,
   gtk_editable_select_region (GTK_EDITABLE (self->search_entry), 0, -1);
 }
 
+static void
+ide_editor_frame_actions_toggle_search_replace (GSimpleAction *action,
+                                                GVariant      *state,
+                                                gpointer       user_data)
+{
+  IdeEditorFrame *self = user_data;
+  gboolean visible;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  visible = !gtk_widget_get_visible (GTK_WIDGET (self->replace_entry));
+
+  gtk_widget_set_visible (GTK_WIDGET (self->replace_entry), visible);
+  gtk_widget_set_visible (GTK_WIDGET (self->replace_button), visible);
+  gtk_widget_set_visible (GTK_WIDGET (self->replace_all_button), visible);
+}
+
+static void
+ide_editor_frame_actions_toggle_search_options (GSimpleAction *action,
+                                                GVariant      *state,
+                                                gpointer       user_data)
+{
+  IdeEditorFrame *self = user_data;
+  gboolean visible;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  visible = !gtk_widget_get_visible (GTK_WIDGET (self->search_options));
+
+  gtk_widget_set_visible (GTK_WIDGET (self->search_options), visible);
+}
+
+static void
+ide_editor_frame_actions_exit_search (GSimpleAction *action,
+                                      GVariant      *state,
+                                      gpointer       user_data)
+{
+  IdeEditorFrame *self = user_data;
+  GtkTextBuffer *buffer;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  /* stash the search string for later */
+  g_free (self->previous_search_string);
+  g_object_get (self->search_entry, "text", &self->previous_search_string, NULL);
+
+  /* clear the highlights in the source view */
+  ide_source_view_clear_search (self->source_view);
+
+  /* disable rubberbanding and ensure insert mark is on screen */
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+  ide_source_view_set_rubberband_search (self->source_view, FALSE);
+  ide_source_view_scroll_mark_onscreen (self->source_view,
+                                        gtk_text_buffer_get_insert (buffer),
+                                        TRUE,
+                                        0.5,
+                                        0.5);
+
+  /* finally we can focus the source view */
+  gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
+}
+
+static void
+ide_editor_frame_actions_replace (GSimpleAction *action,
+                                  GVariant      *state,
+                                  gpointer       user_data)
+{
+  IdeEditorFrame *self = user_data;
+  GtkSourceSearchContext *search_context;
+  GtkSourceSearchSettings *search_settings;
+  const gchar *replace_text;
+  gchar *unescaped_replace_text;
+  const gchar *search_text;
+  GError *error = NULL;
+  GtkTextIter start;
+  GtkTextIter end;
+  GtkTextBuffer *buffer;
+  gint occurrence_position;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  search_context = ide_source_view_get_search_context (self->source_view);
+  g_assert (search_context != NULL);
+  search_settings = gtk_source_search_context_get_settings (search_context);
+  search_text = gtk_source_search_settings_get_search_text (search_settings);
+  replace_text = gtk_entry_get_text (GTK_ENTRY (self->replace_entry));
+
+  if (ide_str_empty0 (search_text) || replace_text == NULL)
+    return;
+
+  unescaped_replace_text = gtk_source_utils_unescape_search_text (replace_text);
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+  gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
+  occurrence_position = gtk_source_search_context_get_occurrence_position (search_context, &start, &end);
+
+  if (occurrence_position > 0)
+    {
+      gtk_source_search_context_replace2 (search_context, &start, &end, unescaped_replace_text, -1, &error);
+
+      if (error != NULL)
+        {
+          g_warning ("%s", error->message);
+          g_clear_error (&error);
+        }
+
+      ide_widget_action (GTK_WIDGET (self), "frame", "next-search-result", NULL);
+    }
+
+  g_free (unescaped_replace_text);
+}
+
+static void
+ide_editor_frame_actions_replace_all (GSimpleAction *action,
+                                      GVariant      *state,
+                                      gpointer       user_data)
+{
+  IdeEditorFrame *self = user_data;
+  GtkSourceSearchContext *search_context;
+  GtkSourceSearchSettings *search_settings;
+  const gchar *replace_text;
+  gchar *unescaped_replace_text;
+  const gchar *search_text;
+  GError *error = NULL;
+  GtkSourceCompletion *completion;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  search_context = ide_source_view_get_search_context (self->source_view);
+  g_assert (search_context != NULL);
+  search_settings = gtk_source_search_context_get_settings (search_context);
+  search_text = gtk_source_search_settings_get_search_text (search_settings);
+  replace_text = gtk_entry_get_text (GTK_ENTRY (self->replace_entry));
+
+  if (ide_str_empty0 (search_text) || replace_text == NULL)
+    return;
+
+  /* Temporarily disabling auto completion makes replace more efficient. */
+  completion = gtk_source_view_get_completion (GTK_SOURCE_VIEW (self->source_view));
+  gtk_source_completion_block_interactive (completion);
+
+  unescaped_replace_text = gtk_source_utils_unescape_search_text (replace_text);
+
+  gtk_source_search_context_replace_all (search_context, unescaped_replace_text, -1, &error);
+
+  gtk_source_completion_unblock_interactive (completion);
+
+  if (error != NULL)
+    {
+      g_warning ("%s", error->message);
+      g_clear_error (&error);
+    }
+
+  g_free (unescaped_replace_text);
+}
+
 static const GActionEntry IdeEditorFrameActions[] = {
   { "find", ide_editor_frame_actions_find, "i" },
   { "next-search-result", ide_editor_frame_actions_next_search_result },
@@ -188,6 +345,11 @@ static const GActionEntry IdeEditorFrameSearchActions[] = {
   { "paste-clipboard", ide_editor_frame_actions_paste_clipboard, },
   { "delete-selection", ide_editor_frame_actions_delete_selection, },
   { "select-all", ide_editor_frame_actions_select_all },
+  { "toggle-search-replace", NULL, "b", "false", ide_editor_frame_actions_toggle_search_replace },
+  { "toggle-search-options", NULL, "b", "false", ide_editor_frame_actions_toggle_search_options },
+  { "exit-search", ide_editor_frame_actions_exit_search },
+  { "replace", ide_editor_frame_actions_replace },
+  { "replace-all", ide_editor_frame_actions_replace_all },
 };
 
 void
@@ -206,6 +368,7 @@ ide_editor_frame_actions_init (IdeEditorFrame *self)
   group = g_simple_action_group_new ();
   g_action_map_add_action_entries (G_ACTION_MAP (group), IdeEditorFrameSearchActions,
                                    G_N_ELEMENTS (IdeEditorFrameSearchActions), self);
-  gtk_widget_insert_action_group (GTK_WIDGET (self->search_entry), "search-entry", G_ACTION_GROUP (group));
+  gtk_widget_insert_action_group (GTK_WIDGET (self->search_frame), "search-entry", G_ACTION_GROUP (group));
+
   g_object_unref (group);
 }
diff --git a/libide/editor/ide-editor-frame-private.h b/libide/editor/ide-editor-frame-private.h
index 165e296..c266cd7 100644
--- a/libide/editor/ide-editor-frame-private.h
+++ b/libide/editor/ide-editor-frame-private.h
@@ -41,7 +41,12 @@ struct _IdeEditorFrame
   GtkLabel            *overwrite_label;
   GtkScrolledWindow   *scrolled_window;
   GtkRevealer         *search_revealer;
+  GtkFrame            *search_frame;
   GdTaggedEntry       *search_entry;
+  GtkSearchEntry      *replace_entry;
+  GtkButton           *replace_button;
+  GtkButton           *replace_all_button;
+  GtkGrid             *search_options;
   GdTaggedEntryTag    *search_entry_tag;
   IdeSourceView       *source_view;
   IdeEditorMapBin      *source_map_container;
diff --git a/libide/editor/ide-editor-frame.c b/libide/editor/ide-editor-frame.c
index 59cbb09..c452239 100644
--- a/libide/editor/ide-editor-frame.c
+++ b/libide/editor/ide-editor-frame.c
@@ -373,6 +373,37 @@ search_text_transform_from (GBinding     *binding,
   return TRUE;
 }
 
+static void
+ide_editor_frame_add_search_actions (IdeEditorFrame *self,
+                                     GActionGroup   *group)
+{
+  GPropertyAction *prop_action;
+  GtkSourceSearchContext *search_context;
+  GtkSourceSearchSettings *search_settings;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+  g_assert (G_IS_ACTION_GROUP (group));
+
+  search_context = ide_source_view_get_search_context (self->source_view);
+  search_settings = gtk_source_search_context_get_settings (search_context);
+
+  prop_action = g_property_action_new ("change-case-sensitive", search_settings, "case-sensitive");
+  g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (prop_action));
+  g_object_unref (prop_action);
+
+  prop_action = g_property_action_new ("change-word-boundaries", search_settings, "at-word-boundaries");
+  g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (prop_action));
+  g_object_unref (prop_action);
+
+  prop_action = g_property_action_new ("change-regex-enabled", search_settings, "regex-enabled");
+  g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (prop_action));
+  g_object_unref (prop_action);
+
+  prop_action = g_property_action_new ("change-wrap-around", search_settings, "wrap-around");
+  g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (prop_action));
+  g_object_unref (prop_action);
+}
+
 void
 ide_editor_frame_set_document (IdeEditorFrame *self,
                                IdeBuffer      *buffer)
@@ -416,6 +447,12 @@ ide_editor_frame_set_document (IdeEditorFrame *self,
                            G_CALLBACK (ide_editor_frame_on_search_occurrences_notify),
                            self,
                            G_CONNECT_SWAPPED);
+
+  /*
+   * Add search option property actions
+   */
+  group = gtk_widget_get_action_group (GTK_WIDGET (self->search_frame), "search-entry");
+  ide_editor_frame_add_search_actions (self, group);
 }
 
 static gboolean
@@ -504,33 +541,13 @@ ide_editor_frame__search_key_press_event (IdeEditorFrame *self,
                                          GdkEventKey   *event,
                                          GdTaggedEntry *entry)
 {
-  GtkTextBuffer *buffer;
-
   g_assert (IDE_IS_EDITOR_FRAME (self));
   g_assert (GD_IS_TAGGED_ENTRY (entry));
 
   switch (event->keyval)
     {
     case GDK_KEY_Escape:
-      /* stash the search string for later */
-      g_free (self->previous_search_string);
-      g_object_get (self->search_entry, "text", &self->previous_search_string, NULL);
-
-      /* clear the highlights in the source view */
-      ide_source_view_clear_search (self->source_view);
-
-      /* disable rubberbanding and ensure insert mark is on screen */
-      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
-      ide_source_view_set_rubberband_search (self->source_view, FALSE);
-      ide_source_view_scroll_mark_onscreen (self->source_view,
-                                            gtk_text_buffer_get_insert (buffer),
-                                            TRUE,
-                                            0.5,
-                                            0.5);
-
-      /* finally we can focus the source view */
-      gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
-
+      ide_widget_action (GTK_WIDGET (self->search_frame), "search-entry", "exit-search", NULL);
       return GDK_EVENT_STOP;
 
     case GDK_KEY_KP_Enter:
@@ -697,37 +714,6 @@ ide_editor_frame__source_view_populate_popup (IdeEditorFrame *self,
 }
 
 static void
-ide_editor_frame_add_search_actions (IdeEditorFrame *self,
-                                     GActionGroup   *group)
-{
-  GPropertyAction *prop_action;
-  GtkSourceSearchContext *search_context;
-  GtkSourceSearchSettings *search_settings;
-
-  g_assert (IDE_IS_EDITOR_FRAME (self));
-  g_assert (G_IS_ACTION_GROUP (group));
-
-  search_context = ide_source_view_get_search_context (self->source_view);
-  search_settings = gtk_source_search_context_get_settings (search_context);
-
-  prop_action = g_property_action_new ("change-case-sensitive", search_settings, "case-sensitive");
-  g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (prop_action));
-  g_object_unref (prop_action);
-
-  prop_action = g_property_action_new ("change-word-boundaries", search_settings, "at-word-boundaries");
-  g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (prop_action));
-  g_object_unref (prop_action);
-
-  prop_action = g_property_action_new ("change-regex-enabled", search_settings, "regex-enabled");
-  g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (prop_action));
-  g_object_unref (prop_action);
-
-  prop_action = g_property_action_new ("change-wrap-around", search_settings, "wrap-around");
-  g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (prop_action));
-  g_object_unref (prop_action);
-}
-
-static void
 ide_editor_frame__search_populate_popup (IdeEditorFrame *self,
                                          GtkWidget      *popup,
                                          GdTaggedEntry  *entry)
@@ -746,8 +732,7 @@ ide_editor_frame__search_populate_popup (IdeEditorFrame *self,
       gboolean clipboard_contains_text;
       gboolean entry_has_selection;
 
-      group = gtk_widget_get_action_group (GTK_WIDGET (entry), "search-entry");
-      ide_editor_frame_add_search_actions (self, group);
+      group = gtk_widget_get_action_group (GTK_WIDGET (self->search_frame), "search-entry");
 
       menu = ide_application_get_menu_by_id (IDE_APPLICATION_DEFAULT, "ide-editor-frame-search-menu");
       gtk_menu_shell_bind_model (GTK_MENU_SHELL (popup), G_MENU_MODEL (menu), NULL, TRUE);
@@ -983,7 +968,12 @@ ide_editor_frame_class_init (IdeEditorFrameClass *klass)
   gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, mode_name_label);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, overwrite_label);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, scrolled_window);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, search_frame);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, search_entry);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, replace_entry);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, replace_button);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, replace_all_button);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, search_options);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, search_revealer);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, source_map_container);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, source_overlay);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]