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



commit 10caa53f1404975f5a0e6b72ebcc6ef6851981f1
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 |  166 +++++++++++++++++++++++++++++-
 libide/editor/ide-editor-frame-private.h |    5 +
 libide/editor/ide-editor-frame.c         |   98 ++++++++----------
 3 files changed, 214 insertions(+), 55 deletions(-)
---
diff --git a/libide/editor/ide-editor-frame-actions.c b/libide/editor/ide-editor-frame-actions.c
index 028c53b..c449e23 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,163 @@ 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;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  if (gtk_widget_get_visible (GTK_WIDGET (self->replace_entry)))
+    {
+      gtk_widget_hide (GTK_WIDGET (self->replace_entry));
+      gtk_widget_hide (GTK_WIDGET (self->replace_button));
+      gtk_widget_hide (GTK_WIDGET (self->replace_all_button));
+    }
+  else
+    {
+      gtk_widget_show (GTK_WIDGET (self->replace_entry));
+      gtk_widget_show (GTK_WIDGET (self->replace_button));
+      gtk_widget_show (GTK_WIDGET (self->replace_all_button));
+    }
+}
+
+static void
+ide_editor_frame_actions_toggle_search_options (GSimpleAction *action,
+                                                GVariant      *state,
+                                                gpointer       user_data)
+{
+  IdeEditorFrame *self = user_data;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  if (gtk_widget_get_visible (GTK_WIDGET (self->search_options)))
+    gtk_widget_hide (GTK_WIDGET (self->search_options));
+  else
+    gtk_widget_show_all (GTK_WIDGET (self->search_options));
+}
+
+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_return_if_fail (search_context != NULL);
+
+  search_settings = gtk_source_search_context_get_settings (search_context);
+  search_text = gtk_source_search_settings_get_search_text (search_settings);
+  g_return_if_fail (!ide_str_empty0 (search_text));
+
+  /* replace_text can be an empty string, but can't be NULL */
+  replace_text = gtk_entry_get_text (GTK_ENTRY (self->replace_entry));
+  g_return_if_fail (replace_text != NULL);
+  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);
+  g_return_if_fail (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);
+}
+
+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_return_if_fail (search_context != NULL);
+
+  search_settings = gtk_source_search_context_get_settings (search_context);
+  search_text = gtk_source_search_settings_get_search_text (search_settings);
+  g_return_if_fail (!ide_str_empty0 (search_text));
+
+  /* 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);
+
+  replace_text = gtk_entry_get_text (GTK_ENTRY (self->replace_entry));
+  g_return_if_fail (replace_text != NULL);
+  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);
+    }
+}
+
 static const GActionEntry IdeEditorFrameActions[] = {
   { "find", ide_editor_frame_actions_find, "i" },
   { "next-search-result", ide_editor_frame_actions_next_search_result },
@@ -188,6 +346,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 +369,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]