[gnome-builder] editor: hook up search bar



commit 3494df83e013ae4d1ecc3e766aa5b34b4d4870bd
Author: Christian Hergert <chergert redhat com>
Date:   Tue Jul 11 23:04:34 2017 -0700

    editor: hook up search bar
    
    This starts hooking up the search bar. It is different than
    previous in that we are using a new settings/context at the
    editor layer rather than from the SourceView so that we can
    easily destroy objects when not in use.
    
    There is still a lot of work that can go on to refactor all of
    this, but it is a start.

 data/keybindings/default.css            |    2 +-
 data/keybindings/emacs.css              |    4 +-
 data/keybindings/vim.css                |   33 ++-
 libide/editor/ide-editor-private.h      |    6 +
 libide/editor/ide-editor-search-bar.c   |  473 ++++++++++++++++++++-----------
 libide/editor/ide-editor-search-bar.h   |   14 +-
 libide/editor/ide-editor-search-bar.ui  |   18 +-
 libide/editor/ide-editor-view-actions.c |   64 ++++-
 libide/editor/ide-editor-view.c         |  239 ++++++++++++++++-
 libide/editor/ide-editor-view.h         |   32 ++-
 10 files changed, 674 insertions(+), 211 deletions(-)
---
diff --git a/data/keybindings/default.css b/data/keybindings/default.css
index b5aed2c..43382f5 100644
--- a/data/keybindings/default.css
+++ b/data/keybindings/default.css
@@ -12,7 +12,7 @@
   bind "<shift>F7" { "action" ("frame", "show-spellcheck", "1") };
   bind "<ctrl><shift>e" { "add-cursor" (column) };
   bind "<ctrl><shift>d" { "add-cursor" (match) };
-  bind "<ctrl>h" { "action" ("frame", "find-replace", "3") };
+  bind "<ctrl>h" { "action" ("editor-view", "find-replace", "") };
   bind "<ctrl>o" { "action" ("win", "open-with-dialog", "") };
   bind "<ctrl>s" { "action" ("view", "save", "") };
   bind "<ctrl><shift>s" { "action" ("view", "save-as", "") };
diff --git a/data/keybindings/emacs.css b/data/keybindings/emacs.css
index 39ab044..9ac7fbb 100644
--- a/data/keybindings/emacs.css
+++ b/data/keybindings/emacs.css
@@ -61,8 +61,8 @@
   bind "<ctrl>c" { "set-mode" ("emacs-c", transient) };
   bind "<ctrl>underscore" { "undo" () };
   bind "<alt>x" { "action" ("win", "show-command-bar", "") };
-  bind "<ctrl>r" { "action" ("frame", "find", "2") };
-  bind "<ctrl>s" { "action" ("frame", "find", "3") };
+  bind "<ctrl>r" { "action" ("editor-view", "find", "") };
+  bind "<ctrl>s" { "action" ("editor-view", "find", "") };
   bind "<alt>dollar" { "action" ("frame", "spellcheck", "1") };
   bind "<alt>period" { "goto-definition" () };
   bind "<alt>n" { "move-error" (down) };
diff --git a/data/keybindings/vim.css b/data/keybindings/vim.css
index 905acf5..3c52fe7 100644
--- a/data/keybindings/vim.css
+++ b/data/keybindings/vim.css
@@ -104,7 +104,7 @@
   bind "<ctrl>s" { "action" ("view", "save", "") };
   bind "<ctrl><shift>s" { "action" ("view", "save-as", "") };
   bind "<ctrl><shift>o" { "action" ("win", "open-with-dialog", "") };
-  bind "<ctrl>k" { "action" ("view-stack", "show-list", "") };
+  bind "<ctrl>k" { "action" ("layoutstack", "show-list", "") };
   bind "<ctrl>minus" { "decrease-font-size" () };
   bind "<ctrl>plus" { "increase-font-size" () };
   bind "<ctrl>equal" { "increase-font-size" () };
@@ -173,20 +173,21 @@
   bind "colon" { "action" ("win", "show-command-bar", "") };
 
   /* cycle "tabs" */
-  bind "<ctrl><alt>Page_Up" { "action" ("view-stack", "previous-view", "") };
-  bind "<ctrl><alt>KP_Page_Up" { "action" ("view-stack", "previous-view", "") };
-  bind "<ctrl><alt>Page_Down" { "action" ("view-stack", "next-view", "") };
-  bind "<ctrl><alt>KP_Page_Down" { "action" ("view-stack", "next-view", "") };
+  bind "<ctrl><alt>Page_Up" { "action" ("layoutstack", "previous-view", "") };
+  bind "<ctrl><alt>KP_Page_Up" { "action" ("layoutstack", "previous-view", "") };
+  bind "<ctrl><alt>Page_Down" { "action" ("layoutstack", "next-view", "") };
+  bind "<ctrl><alt>KP_Page_Down" { "action" ("layoutstack", "next-view", "") };
 
   /* replay the last recording */
   bind "period" { "replay-macro" (1) };
 
   /* start search backward */
-  bind "question" { "action" ("frame", "find", "2") };
+  /* TODO: use internal sourceview search */
+  bind "question" { "action" ("editor-view", "find", "") };
 
   /* start search */
-  bind "slash" { "action" ("frame", "find", "3") };
-  bind "KP_Divide" { "action" ("frame", "find", "3") };
+  bind "slash" { "action" ("editor-view", "find", "") };
+  bind "KP_Divide" { "action" ("editor-view", "find", "") };
 
   /* insert at cursor */
   bind "i" { "begin-macro" ()
@@ -553,8 +554,8 @@
   bind "<ctrl>v" { "set-mode" ("vim-visual-block", permanent) };
 
   /* navigation */
-  bind "<ctrl>o" { "action" ("view-stack", "go-backward", "") };
-  bind "<ctrl>i" { "action" ("view-stack", "go-forward", "") };
+  bind "<ctrl>o" { "action" ("layoutstack", "go-backward", "") };
+  bind "<ctrl>i" { "action" ("layoutstack", "go-forward", "") };
 
   /* window controls */
   bind "<ctrl>w" { "set-mode" ("vim-normal-ctrl-w", transient) };
@@ -1517,8 +1518,8 @@
   bind "<shift>u" { "set-mode" ("vim-normal-g-u", transient) };
 
   /* cycle "tabs" */
-  bind "<shift>t" { "action" ("view-stack", "previous-view", "") };
-  bind "t" { "action" ("view-stack", "next-view", "") };
+  bind "<shift>t" { "action" ("layoutstack", "previous-view", "") };
+  bind "t" { "action" ("layoutstack", "next-view", "") };
 }
 
 @binding-set builder-vim-source-view-normal-g-u
@@ -1849,12 +1850,12 @@
 
 @binding-set builder-vim-source-view-normal-ctrl-w
 {
-  bind "v" { "action" ("view-stack", "split-right", "") "grab_focus" () };
-  bind "<ctrl>v" { "action" ("view-stack", "split-right", "") "grab_focus" () };
+  bind "v" { "action" ("layoutstack", "split-right", "") "grab_focus" () };
+  bind "<ctrl>v" { "action" ("layoutstack", "split-right", "") "grab_focus" () };
 
-  bind "c" { "action" ("view", "close", "") };
+  bind "c" { "action" ("layoutstack", "close-view", "") };
 
-  bind "s" { "action" ("view-stack", "split-down", "") };
+  bind "s" { "action" ("layoutstack", "split-view", "") };
 
   bind "w" { "action" ("view-grid", "focus-neighbor", "0") };
   bind "<ctrl>w" { "action" ("view-grid", "focus-neighbor", "0") };
diff --git a/libide/editor/ide-editor-private.h b/libide/editor/ide-editor-private.h
index b47da46..8e6b693 100644
--- a/libide/editor/ide-editor-private.h
+++ b/libide/editor/ide-editor-private.h
@@ -40,6 +40,11 @@ struct _IdeEditorView
   DzlBindingGroup         *buffer_bindings;
   DzlSignalGroup          *buffer_signals;
 
+  GtkSourceSearchSettings *search_settings;
+  GtkSourceSearchContext  *search_context;
+
+  GCancellable            *destroy_cancellable;
+
   GtkSourceMap            *map;
   GtkRevealer             *map_revealer;
   GtkOverlay              *overlay;
@@ -59,6 +64,7 @@ struct _IdeEditorView
 void _ide_editor_view_init_actions          (IdeEditorView        *self);
 void _ide_editor_view_init_settings         (IdeEditorView        *self);
 void _ide_editor_view_init_shortcuts        (IdeEditorView        *self);
+void _ide_editor_view_update_actions        (IdeEditorView        *self);
 void _ide_editor_sidebar_set_open_pages     (IdeEditorSidebar     *self,
                                              GListModel           *open_pages);
 void _ide_editor_perspective_init_actions   (IdeEditorPerspective *self);
diff --git a/libide/editor/ide-editor-search-bar.c b/libide/editor/ide-editor-search-bar.c
index 765f297..7b7b42b 100644
--- a/libide/editor/ide-editor-search-bar.c
+++ b/libide/editor/ide-editor-search-bar.c
@@ -30,15 +30,13 @@ struct _IdeEditorSearchBar
   DzlBin                   parent_instance;
 
   /* Owned references */
-  GtkSourceSearchSettings *search_settings;
-  GtkSourceSearchContext  *search_context;
-  DzlSignalGroup          *search_context_signals;
+  DzlSignalGroup          *buffer_signals;
+  GtkSourceSearchContext  *context;
+  DzlSignalGroup          *context_signals;
+  GtkSourceSearchSettings *settings;
+  DzlSignalGroup          *settings_signals;
   GdTaggedEntryTag        *search_entry_tag;
 
-  /* Weak pointers */
-  IdeBuffer               *buffer;
-  IdeSourceView           *view;
-
   /* Template widgets */
   GtkCheckButton          *case_sensitive;
   GtkButton               *close_button;
@@ -53,8 +51,8 @@ struct _IdeEditorSearchBar
 
 enum {
   PROP_0,
-  PROP_BUFFER,
-  PROP_VIEW,
+  PROP_CONTEXT,
+  PROP_SETTINGS,
   N_PROPS
 };
 
@@ -62,6 +60,131 @@ G_DEFINE_TYPE (IdeEditorSearchBar, ide_editor_search_bar, DZL_TYPE_BIN)
 
 static GParamSpec *properties [N_PROPS];
 
+static void
+ide_editor_search_bar_toggle_search_options (GSimpleAction *action,
+                                             GVariant      *state,
+                                             gpointer       user_data)
+{
+  IdeEditorSearchBar *self = user_data;
+  gboolean visible;
+
+  g_assert (IDE_IS_EDITOR_SEARCH_BAR (self));
+
+  visible = !gtk_widget_get_visible (GTK_WIDGET (self->search_options));
+  gtk_widget_set_visible (GTK_WIDGET (self->search_options), visible);
+}
+
+gboolean
+ide_editor_search_bar_get_replace_mode (IdeEditorSearchBar *self)
+{
+  g_return_val_if_fail (IDE_IS_EDITOR_SEARCH_BAR (self), FALSE);
+
+  return gtk_widget_get_visible (GTK_WIDGET (self->replace_entry));
+}
+
+void
+ide_editor_search_bar_set_replace_mode (IdeEditorSearchBar *self,
+                                        gboolean            replace_mode)
+{
+  g_return_if_fail (IDE_IS_EDITOR_SEARCH_BAR (self));
+
+  gtk_widget_set_visible (GTK_WIDGET (self->replace_entry), replace_mode);
+  gtk_widget_set_visible (GTK_WIDGET (self->replace_button), replace_mode);
+  gtk_widget_set_visible (GTK_WIDGET (self->replace_all_button), replace_mode);
+}
+
+static void
+ide_editor_search_bar_toggle_search_replace (GSimpleAction *action,
+                                             GVariant      *state,
+                                             gpointer       user_data)
+{
+  IdeEditorSearchBar *self = user_data;
+
+  g_assert (IDE_IS_EDITOR_SEARCH_BAR (self));
+
+  ide_editor_search_bar_set_replace_mode (self, !ide_editor_search_bar_get_replace_mode (self));
+}
+
+static void
+ide_editor_search_bar_replace (GSimpleAction *action,
+                               GVariant      *state,
+                               gpointer       user_data)
+{
+  IdeEditorSearchBar *self = user_data;
+  g_autofree gchar *unescaped_replace_text = NULL;
+  g_autoptr(GError) error = NULL;
+  GtkSourceBuffer *buffer;
+  const gchar *replace_text;
+  const gchar *search_text;
+  GtkTextIter begin;
+  GtkTextIter end;
+  gint position;
+
+  g_assert (IDE_IS_EDITOR_SEARCH_BAR (self));
+
+  if (self->settings == NULL || self->context == NULL)
+    return;
+
+  search_text = gtk_source_search_settings_get_search_text (self->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_source_search_context_get_buffer (self->context);
+  gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer), &begin, &end);
+  position = gtk_source_search_context_get_occurrence_position (self->context, &begin, &end);
+
+  if (position > 0)
+    {
+      /* Temporarily disable updating the search position label to prevent flickering */
+      dzl_signal_group_block (self->buffer_signals);
+
+      gtk_source_search_context_replace2 (self->context, &begin, &end,
+                                          unescaped_replace_text, -1, &error);
+
+      /* Re-enable updating the search position label. The next-search-result action
+       * below will cause it to update. */
+      dzl_signal_group_unblock (self->buffer_signals);
+
+      if (error != NULL)
+        g_warning ("%s", error->message);
+
+      dzl_gtk_widget_action (GTK_WIDGET (self), "editor-view", "move-next-search-result", NULL);
+    }
+}
+
+static void
+ide_editor_search_bar_replace_all (GSimpleAction *action,
+                                   GVariant      *state,
+                                   gpointer       user_data)
+{
+  IdeEditorSearchBar *self = user_data;
+  g_autofree gchar *unescaped_replace_text = NULL;
+  g_autoptr(GError) error = NULL;
+  const gchar *replace_text;
+  const gchar *search_text;
+
+  g_assert (IDE_IS_EDITOR_SEARCH_BAR (self));
+
+  if (self->settings == NULL || self->context == NULL)
+    return;
+
+  search_text = gtk_source_search_settings_get_search_text (self->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);
+  gtk_source_search_context_replace_all (self->context, unescaped_replace_text, -1, &error);
+
+  if (error != NULL)
+    g_warning ("%s", error->message);
+}
+
 static gboolean
 maybe_escape_regex (GBinding     *binding,
                     const GValue *from_value,
@@ -81,7 +204,7 @@ maybe_escape_regex (GBinding     *binding,
       const gchar *entry_text = g_value_get_string (from_value);
       g_autofree gchar *unescaped = NULL;
 
-      if (!gtk_source_search_settings_get_regex_enabled (self->search_settings))
+      if (!gtk_source_search_settings_get_regex_enabled (self->settings))
         entry_text = unescaped = gtk_source_utils_unescape_search_text (entry_text);
 
       g_value_set_string (to_value, entry_text);
@@ -112,7 +235,7 @@ update_replace_actions_sensitivity (IdeEditorSearchBar *self)
 {
   g_autoptr(GError) regex_error = NULL;
   g_autoptr(GError) replace_regex_error = NULL;
-  GtkTextBuffer *buffer;
+  GtkSourceBuffer *buffer;
   GtkTextIter begin;
   GtkTextIter end;
   const gchar *search_text;
@@ -125,23 +248,20 @@ update_replace_actions_sensitivity (IdeEditorSearchBar *self)
 
   g_assert (IDE_IS_EDITOR_SEARCH_BAR (self));
 
-  if (self->search_context == NULL ||
-      self->view == NULL ||
-      self->buffer == NULL ||
-      self->search_settings == NULL)
+  if (self->context == NULL || self->settings == NULL)
     return;
 
-  buffer = GTK_TEXT_BUFFER (self->buffer);
+  buffer = gtk_source_search_context_get_buffer (self->context);
 
-  gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
+  gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer), &begin, &end);
   replace_text = gtk_entry_get_text (GTK_ENTRY (self->replace_entry));
 
   /* Gather enough info to determine if Replace or Replace All would make sense */
   search_text = gtk_entry_get_text (GTK_ENTRY (self->search_entry));
-  pos = gtk_source_search_context_get_occurrence_position (self->search_context, &begin, &end);
-  count = gtk_source_search_context_get_occurrences_count (self->search_context);
-  regex_error = gtk_source_search_context_get_regex_error (self->search_context);
-  replace_regex_valid = gtk_source_search_settings_get_regex_enabled (self->search_settings) ?
+  pos = gtk_source_search_context_get_occurrence_position (self->context, &begin, &end);
+  count = gtk_source_search_context_get_occurrences_count (self->context);
+  regex_error = gtk_source_search_context_get_regex_error (self->context);
+  replace_regex_valid = gtk_source_search_settings_get_regex_enabled (self->settings) ?
                         g_regex_check_replacement (replace_text, NULL, &replace_regex_error) :
                         TRUE;
 
@@ -155,10 +275,10 @@ update_replace_actions_sensitivity (IdeEditorSearchBar *self)
                         replace_regex_valid &&
                         count > 0);
 
-  dzl_gtk_widget_action_set (GTK_WIDGET (self), "search-entry", "replace",
+  dzl_gtk_widget_action_set (GTK_WIDGET (self), "search-bar", "replace",
                              "enabled", enable_replace,
                              NULL);
-  dzl_gtk_widget_action_set (GTK_WIDGET (self), "search-entry", "replace-all",
+  dzl_gtk_widget_action_set (GTK_WIDGET (self), "search-bar", "replace-all",
                              "enabled", enable_replace_all,
                              NULL);
 }
@@ -196,7 +316,7 @@ set_position_label (IdeEditorSearchBar *self,
       self->search_entry_tag = gd_tagged_entry_tag_new ("");
       gd_tagged_entry_add_tag (self->search_entry, self->search_entry_tag);
       gd_tagged_entry_tag_set_style (self->search_entry_tag,
-                                     "gb-search-entry-occurrences-tag");
+                                     "search-occurrences-tag");
     }
 
   gd_tagged_entry_tag_set_label (self->search_entry_tag, text);
@@ -207,7 +327,7 @@ update_search_position_label (IdeEditorSearchBar *self)
 {
   g_autofree gchar *text = NULL;
   GtkStyleContext *context;
-  GtkTextBuffer *buffer;
+  GtkSourceBuffer *buffer;
   GtkTextIter begin;
   GtkTextIter end;
   const gchar *search_text;
@@ -216,14 +336,14 @@ update_search_position_label (IdeEditorSearchBar *self)
 
   g_return_if_fail (IDE_IS_EDITOR_SEARCH_BAR (self));
 
-  if (self->buffer == NULL || self->search_context == NULL)
+  if (self->settings == NULL || self->context == NULL)
     return;
 
-  buffer = GTK_TEXT_BUFFER (self->buffer);
+  buffer = gtk_source_search_context_get_buffer (self->context);
 
-  gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
-  pos = gtk_source_search_context_get_occurrence_position (self->search_context, &begin, &end);
-  count = gtk_source_search_context_get_occurrences_count (self->search_context);
+  gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer), &begin, &end);
+  pos = gtk_source_search_context_get_occurrence_position (self->context, &begin, &end);
+  count = gtk_source_search_context_get_occurrences_count (self->context);
 
   if ((pos == -1) || (count == -1))
     {
@@ -264,6 +384,26 @@ on_notify_occurrences_count (IdeEditorSearchBar     *self,
 }
 
 static void
+on_cursor_moved (IdeEditorSearchBar *self,
+                 const GtkTextIter  *iter,
+                 IdeBuffer          *buffer)
+{
+  gint count;
+
+  g_assert (IDE_IS_EDITOR_SEARCH_BAR (self));
+  g_assert (iter != NULL);
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  count = gtk_source_search_context_get_occurrences_count (self->context);
+
+  if (count != -1)
+    {
+      update_search_position_label (self);
+      update_replace_actions_sensitivity (self);
+    }
+}
+
+static void
 on_notify_regex_error (IdeEditorSearchBar     *self,
                        GParamSpec             *pspec,
                        GtkSourceSearchContext *search_context)
@@ -307,9 +447,9 @@ check_replace_text (IdeEditorSearchBar *self)
   const gchar *tooltip_text = NULL;
 
   g_assert (IDE_IS_EDITOR_SEARCH_BAR (self));
-  g_assert (self->search_settings != NULL);
+  g_assert (self->settings != NULL);
 
-  if (self->search_context == NULL)
+  if (self->context == NULL)
     return;
 
   /*
@@ -317,7 +457,7 @@ check_replace_text (IdeEditorSearchBar *self)
    * otherwise remove it. Also set the error message to the tooltip text
    * so that the user can get some info on the error.
    */
-  if (gtk_source_search_settings_get_regex_enabled (self->search_settings))
+  if (gtk_source_search_settings_get_regex_enabled (self->settings))
     {
       const gchar *replace_text;
 
@@ -360,19 +500,68 @@ ide_editor_search_bar_grab_focus (GtkWidget *widget)
 }
 
 static void
-ide_editor_search_bar_finalize (GObject *object)
+ide_editor_search_bar_bind_context (IdeEditorSearchBar     *self,
+                                    GtkSourceSearchContext *context,
+                                    DzlSignalGroup         *context_signals)
+{
+  GtkSourceBuffer *buffer;
+
+  g_assert (IDE_IS_EDITOR_SEARCH_BAR (self));
+  g_assert (GTK_SOURCE_IS_SEARCH_CONTEXT (context));
+  g_assert (DZL_IS_SIGNAL_GROUP (context_signals));
+
+  buffer = gtk_source_search_context_get_buffer (context);
+  dzl_signal_group_set_target (self->buffer_signals, buffer);
+}
+
+static void
+ide_editor_search_bar_unbind_context (IdeEditorSearchBar *self,
+                                      DzlSignalGroup     *context_signals)
+{
+  g_assert (IDE_IS_EDITOR_SEARCH_BAR (self));
+  g_assert (DZL_IS_SIGNAL_GROUP (context_signals));
+
+  if (self->buffer_signals != NULL)
+    dzl_signal_group_set_target (self->buffer_signals, NULL);
+}
+
+static void
+ide_editor_search_bar_bind_settings (IdeEditorSearchBar      *self,
+                                     GtkSourceSearchSettings *settings,
+                                     DzlSignalGroup          *settings_signals)
 {
-  IdeEditorSearchBar *self = (IdeEditorSearchBar *)object;
+  g_autoptr(DzlPropertiesGroup) group = NULL;
+
+  g_assert (IDE_IS_EDITOR_SEARCH_BAR (self));
+  g_assert (GTK_SOURCE_IS_SEARCH_SETTINGS (settings));
+  g_assert (DZL_IS_SIGNAL_GROUP (settings_signals));
+
+  g_object_bind_property_full (self->search_entry, "text",
+                               settings, "search-text",
+                               G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+                               maybe_escape_regex, pacify_null_text,
+                               self, NULL);
 
-  ide_clear_weak_pointer (&self->buffer);
-  ide_clear_weak_pointer (&self->view);
+  group = dzl_properties_group_new (G_OBJECT (settings));
+  dzl_properties_group_add_all_properties (group);
+  gtk_widget_insert_action_group (GTK_WIDGET (self),
+                                  "search-settings",
+                                  G_ACTION_GROUP (group));
+}
 
-  g_clear_object (&self->search_context);
-  g_clear_object (&self->search_settings);
-  g_clear_object (&self->search_context_signals);
+static void
+ide_editor_search_bar_destroy (GtkWidget *widget)
+{
+  IdeEditorSearchBar *self = (IdeEditorSearchBar *)widget;
+
+  g_clear_object (&self->buffer_signals);
+  g_clear_object (&self->context);
+  g_clear_object (&self->context_signals);
   g_clear_object (&self->search_entry_tag);
+  g_clear_object (&self->settings);
+  g_clear_object (&self->settings_signals);
 
-  G_OBJECT_CLASS (ide_editor_search_bar_parent_class)->finalize (object);
+  GTK_WIDGET_CLASS (ide_editor_search_bar_parent_class)->destroy (widget);
 }
 
 static void
@@ -385,12 +574,12 @@ ide_editor_search_bar_get_property (GObject    *object,
 
   switch (prop_id)
     {
-    case PROP_BUFFER:
-      g_value_set_object (value, ide_editor_search_bar_get_buffer (self));
+    case PROP_CONTEXT:
+      g_value_set_object (value, self->context);
       break;
 
-    case PROP_VIEW:
-      g_value_set_object (value, ide_editor_search_bar_get_view (self));
+    case PROP_SETTINGS:
+      g_value_set_object (value, self->settings);
       break;
 
     default:
@@ -408,12 +597,12 @@ ide_editor_search_bar_set_property (GObject      *object,
 
   switch (prop_id)
     {
-    case PROP_BUFFER:
-      ide_editor_search_bar_set_buffer (self, g_value_get_object (value));
+    case PROP_CONTEXT:
+      ide_editor_search_bar_set_context (self, g_value_get_object (value));
       break;
 
-    case PROP_VIEW:
-      ide_editor_search_bar_set_view (self, g_value_get_object (value));
+    case PROP_SETTINGS:
+      ide_editor_search_bar_set_settings (self, g_value_get_object (value));
       break;
 
     default:
@@ -427,14 +616,29 @@ ide_editor_search_bar_class_init (IdeEditorSearchBarClass *klass)
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
-  object_class->finalize = ide_editor_search_bar_finalize;
   object_class->get_property = ide_editor_search_bar_get_property;
   object_class->set_property = ide_editor_search_bar_set_property;
 
+  widget_class->destroy = ide_editor_search_bar_destroy;
   widget_class->grab_focus = ide_editor_search_bar_grab_focus;
 
-  gtk_widget_class_set_template_from_resource (widget_class,
-                                               "/org/gnome/builder/ui/ide-editor-search-bar.ui");
+  properties [PROP_CONTEXT] =
+    g_param_spec_object ("context",
+                         "Context",
+                         "The search context for locating matches",
+                         GTK_SOURCE_TYPE_SEARCH_CONTEXT,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SETTINGS] =
+    g_param_spec_object ("settings",
+                         "Settings",
+                         "The search settings for locating matches",
+                         GTK_SOURCE_TYPE_SEARCH_SETTINGS,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-editor-search-bar.ui");
   gtk_widget_class_bind_template_child (widget_class, IdeEditorSearchBar, case_sensitive);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorSearchBar, close_button);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorSearchBar, replace_all_button);
@@ -450,63 +654,72 @@ ide_editor_search_bar_class_init (IdeEditorSearchBarClass *klass)
   g_type_ensure (GD_TYPE_TAGGED_ENTRY);
 }
 
+static const GActionEntry search_bar_actions[] = {
+  { "toggle-search-options", NULL, "b", "false", ide_editor_search_bar_toggle_search_options },
+  { "toggle-search-replace", NULL, "b", "false", ide_editor_search_bar_toggle_search_replace },
+  { "replace", ide_editor_search_bar_replace },
+  { "replace-all", ide_editor_search_bar_replace_all },
+};
+
 static void
 ide_editor_search_bar_init (IdeEditorSearchBar *self)
 {
-  g_autoptr(GSimpleActionGroup) group = NULL;
-  static const gchar *proxy_names[] = {
-    "case-sensitive",
-    "at-word-boundaries",
-    "regex-enabled",
-    "wrap-around",
-  };
+  g_autoptr(GSimpleActionGroup) actions = NULL;
 
   gtk_widget_init_template (GTK_WIDGET (self));
 
-  self->search_settings = gtk_source_search_settings_new ();
+  self->buffer_signals = dzl_signal_group_new (IDE_TYPE_BUFFER);
 
-  g_object_bind_property_full (self->search_entry, "text",
-                               self->search_settings, "search-text",
-                               G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
-                               maybe_escape_regex, pacify_null_text,
-                               self, NULL);
+  dzl_signal_group_connect_swapped (self->buffer_signals,
+                                    "cursor-moved",
+                                    G_CALLBACK (on_cursor_moved),
+                                    self);
 
-  self->search_context_signals = dzl_signal_group_new (GTK_SOURCE_TYPE_SEARCH_CONTEXT);
+  self->context_signals = dzl_signal_group_new (GTK_SOURCE_TYPE_SEARCH_CONTEXT);
 
-  dzl_signal_group_connect_swapped (self->search_context_signals,
+  dzl_signal_group_connect_swapped (self->context_signals,
                                     "notify::occurrences-count",
                                     G_CALLBACK (on_notify_occurrences_count),
                                     self);
 
-  dzl_signal_group_connect_swapped (self->search_context_signals,
+  dzl_signal_group_connect_swapped (self->context_signals,
                                     "notify::regex-error",
                                     G_CALLBACK (on_notify_regex_error),
                                     self);
 
-  g_signal_connect_object (self->search_settings,
-                           "notify::search-text",
-                           G_CALLBACK (on_notify_search_text),
-                           self,
-                           G_CONNECT_SWAPPED);
+  g_signal_connect_swapped (self->context_signals,
+                            "bind",
+                            G_CALLBACK (ide_editor_search_bar_bind_context),
+                            self);
 
-  g_signal_connect_object (self->search_settings,
-                           "notify::regex-enabled",
-                           G_CALLBACK (on_notify_regex_enabled),
-                           self,
-                           G_CONNECT_SWAPPED);
+  g_signal_connect_swapped (self->context_signals,
+                            "unbind",
+                            G_CALLBACK (ide_editor_search_bar_unbind_context),
+                            self);
 
-  group = g_simple_action_group_new ();
+  self->settings_signals = dzl_signal_group_new (GTK_SOURCE_TYPE_SEARCH_SETTINGS);
 
-  for (guint i = 0; i < G_N_ELEMENTS (proxy_names); i++)
-    {
-      g_autoptr(GPropertyAction) action = NULL;
-      const gchar *name = proxy_names[i];
+  dzl_signal_group_connect_swapped (self->settings_signals,
+                                    "notify::search-text",
+                                    G_CALLBACK (on_notify_search_text),
+                                    self);
 
-      action = g_property_action_new (name, self->search_settings, name);
-      g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
-    }
+  dzl_signal_group_connect_swapped (self->settings_signals,
+                                    "notify::regex-enabled",
+                                    G_CALLBACK (on_notify_regex_enabled),
+                                    self);
 
-  gtk_widget_insert_action_group (GTK_WIDGET (self), "search-entry", G_ACTION_GROUP (group));
+  g_signal_connect_swapped (self->settings_signals,
+                            "bind",
+                            G_CALLBACK (ide_editor_search_bar_bind_settings),
+                            self);
+
+  actions = g_simple_action_group_new ();
+  g_action_map_add_action_entries (G_ACTION_MAP (actions),
+                                   search_bar_actions,
+                                   G_N_ELEMENTS (search_bar_actions),
+                                   self);
+  gtk_widget_insert_action_group (GTK_WIDGET (self), "search-bar", G_ACTION_GROUP (actions));
 }
 
 GtkWidget *
@@ -515,88 +728,30 @@ ide_editor_search_bar_new (void)
   return g_object_new (IDE_TYPE_EDITOR_SEARCH_BAR, NULL);
 }
 
-/**
- * ide_editor_search_bar_get_buffer:
- * @self: a #IdeEditorSearchBar
- *
- * Gets the buffer used by the search bar.
- *
- * Returns: (nullable) (transfer none): An #IdeBuffer or %NULL
- *
- * Since: 3.26
- */
-IdeBuffer *
-ide_editor_search_bar_get_buffer (IdeEditorSearchBar *self)
-{
-  g_return_val_if_fail (IDE_IS_EDITOR_SEARCH_BAR (self), NULL);
-
-  return self->buffer;
-}
-
-/**
- * ide_editor_search_bar_set_buffer:
- * @self: a #IdeEditorSearchBar
- *
- * Sets the buffer used by the search bar.
- *
- * Since: 3.26
- */
 void
-ide_editor_search_bar_set_buffer (IdeEditorSearchBar *self,
-                                  IdeBuffer          *buffer)
+ide_editor_search_bar_set_settings (IdeEditorSearchBar      *self,
+                                    GtkSourceSearchSettings *settings)
 {
   g_return_if_fail (IDE_IS_EDITOR_SEARCH_BAR (self));
-  g_return_if_fail (!buffer || IDE_IS_BUFFER (buffer));
+  g_return_if_fail (!settings || GTK_SOURCE_IS_SEARCH_SETTINGS (settings));
 
-  if (ide_set_weak_pointer (&self->buffer, buffer))
+  if (g_set_object (&self->settings, settings))
     {
-      g_clear_object (&self->search_context);
-      dzl_signal_group_set_target (self->search_context_signals, NULL);
-
-      if (buffer != NULL)
-        {
-          self->search_context = gtk_source_search_context_new (GTK_SOURCE_BUFFER (buffer),
-                                                                self->search_settings);
-          dzl_signal_group_set_target (self->search_context_signals, self->search_context);
-        }
-
-      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUFFER]);
+      dzl_signal_group_set_target (self->settings_signals, settings);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SETTINGS]);
     }
 }
 
-/**
- * ide_editor_search_bar_get_view:
- * @self: a #IdeEditorSearchBar
- *
- * Gets the view used by the search bar.
- *
- * Returns: (nullable) (transfer none): An #IdeBuffer or %NULL
- *
- * Since: 3.26
- */
-IdeSourceView *
-ide_editor_search_bar_get_view (IdeEditorSearchBar *self)
-{
-  g_return_val_if_fail (IDE_IS_EDITOR_SEARCH_BAR (self), NULL);
-
-  return self->view;
-}
-
-/**
- * ide_editor_search_bar_set_view:
- * @self: a #IdeEditorSearchBar
- *
- * Sets the view used by the search bar.
- *
- * Since: 3.26
- */
 void
-ide_editor_search_bar_set_view (IdeEditorSearchBar *self,
-                                IdeSourceView      *view)
+ide_editor_search_bar_set_context (IdeEditorSearchBar     *self,
+                                   GtkSourceSearchContext *context)
 {
   g_return_if_fail (IDE_IS_EDITOR_SEARCH_BAR (self));
-  g_return_if_fail (!view || IDE_IS_SOURCE_VIEW (view));
+  g_return_if_fail (!context || GTK_SOURCE_IS_SEARCH_CONTEXT (context));
 
-  if (ide_set_weak_pointer (&self->view, view))
-    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VIEW]);
+  if (g_set_object (&self->context, context))
+    {
+      dzl_signal_group_set_target (self->context_signals, context);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONTEXT]);
+    }
 }
diff --git a/libide/editor/ide-editor-search-bar.h b/libide/editor/ide-editor-search-bar.h
index 6dd67ad..e727cd3 100644
--- a/libide/editor/ide-editor-search-bar.h
+++ b/libide/editor/ide-editor-search-bar.h
@@ -29,12 +29,12 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (IdeEditorSearchBar, ide_editor_search_bar, IDE, EDITOR_SEARCH_BAR, DzlBin)
 
-GtkWidget     *ide_editor_search_bar_new        (void);
-IdeBuffer     *ide_editor_search_bar_get_buffer (IdeEditorSearchBar *self);
-void           ide_editor_search_bar_set_buffer (IdeEditorSearchBar *self,
-                                                 IdeBuffer          *buffer);
-IdeSourceView *ide_editor_search_bar_get_view   (IdeEditorSearchBar *self);
-void           ide_editor_search_bar_set_view   (IdeEditorSearchBar *self,
-                                                 IdeSourceView      *view);
+GtkWidget *ide_editor_search_bar_new              (void);
+void       ide_editor_search_bar_set_replace_mode (IdeEditorSearchBar      *self,
+                                                   gboolean                 replace_mode);
+void       ide_editor_search_bar_set_context      (IdeEditorSearchBar      *self,
+                                                   GtkSourceSearchContext  *context);
+void       ide_editor_search_bar_set_settings     (IdeEditorSearchBar      *self,
+                                                   GtkSourceSearchSettings *settings);
 
 G_END_DECLS
diff --git a/libide/editor/ide-editor-search-bar.ui b/libide/editor/ide-editor-search-bar.ui
index 464fc73..cca5f73 100644
--- a/libide/editor/ide-editor-search-bar.ui
+++ b/libide/editor/ide-editor-search-bar.ui
@@ -57,7 +57,7 @@
                 </style>
                 <child>
                   <object class="GtkButton">
-                    <property name="action-name">frame.previous-search-result</property>
+                    <property name="action-name">editor-view.move-previous-search-result</property>
                     <property name="visible">true</property>
                     <property name="can_focus">false</property>
                     <property name="receives_default">true</property>
@@ -78,7 +78,7 @@
                 </child>
                 <child>
                   <object class="GtkButton">
-                    <property name="action-name">frame.next-search-result</property>
+                    <property name="action-name">editor-view.move-next-search-result</property>
                     <property name="visible">true</property>
                     <property name="can_focus">false</property>
                     <property name="receives_default">true</property>
@@ -106,7 +106,7 @@
             <child>
               <object class="GtkButton" id="replace_button">
                 <property name="label" translatable="yes">Replace</property>
-                <property name="action-name">search-entry.replace</property>
+                <property name="action-name">search-bar.replace</property>
                 <property name="visible">false</property>
                 <property name="can_focus">true</property>
                 <property name="receives_default">true</property>
@@ -119,7 +119,7 @@
             <child>
               <object class="GtkButton" id="replace_all_button">
                 <property name="label" translatable="yes">Replace All</property>
-                <property name="action-name">search-entry.replace-all</property>
+                <property name="action-name">search-bar.replace-all</property>
                 <property name="visible">false</property>
                 <property name="can_focus">true</property>
                 <property name="receives_default">true</property>
@@ -138,7 +138,7 @@
                 <property name="spacing">8</property>
                 <child>
                   <object class="GtkToggleButton">
-                    <property name="action-name">search-entry.toggle-search-replace</property>
+                    <property name="action-name">search-bar.toggle-search-replace</property>
                     <property name="action-target">true</property>
                     <property name="tooltip-text" translatable="yes">Switch between Search and 
Search-and-Replace</property>
                     <property name="visible">true</property>
@@ -161,7 +161,7 @@
                 </child>
                 <child>
                   <object class="GtkToggleButton">
-                    <property name="action-name">search-entry.toggle-search-options</property>
+                    <property name="action-name">search-bar.toggle-search-options</property>
                     <property name="action-target">true</property>
                     <property name="tooltip-text" translatable="yes">Show or hide search options such as 
case sensitivity</property>
                     <property name="visible">true</property>
@@ -223,7 +223,7 @@
             <property name="column_spacing">8</property>
             <child>
               <object class="GtkCheckButton" id="use_regex">
-                <property name="action-name">search-entry.regex-enabled</property>
+                <property name="action-name">search-settings.regex-enabled</property>
                 <property name="label" translatable="yes">Regular expressions</property>
                 <property name="visible">true</property>
                 <property name="can_focus">false</property>
@@ -238,7 +238,7 @@
             </child>
             <child>
               <object class="GtkCheckButton" id="case_sensitive">
-                <property name="action-name">search-entry.case-sensitive</property>
+                <property name="action-name">search-settings.case-sensitive</property>
                 <property name="label" translatable="yes">Case sensitive</property>
                 <property name="visible">true</property>
                 <property name="can_focus">false</property>
@@ -253,7 +253,7 @@
             </child>
             <child>
               <object class="GtkCheckButton" id="whole_word">
-                <property name="action-name">search-entry.at-word-boundaries</property>
+                <property name="action-name">search-settings.at-word-boundaries</property>
                 <property name="label" translatable="yes">Match whole word only</property>
                 <property name="visible">true</property>
                 <property name="can_focus">false</property>
diff --git a/libide/editor/ide-editor-view-actions.c b/libide/editor/ide-editor-view-actions.c
index 880cf7c..a8e915b 100644
--- a/libide/editor/ide-editor-view-actions.c
+++ b/libide/editor/ide-editor-view-actions.c
@@ -388,7 +388,22 @@ ide_editor_view_actions_save_as (GSimpleAction *action,
 }
 
 static void
-ide_editor_view_actions_focus_search (GSimpleAction *action,
+ide_editor_view_actions_find (GSimpleAction *action,
+                              GVariant      *variant,
+                              gpointer       user_data)
+{
+  IdeEditorView *self = user_data;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  ide_editor_search_bar_set_replace_mode (self->search_bar, FALSE);
+  gtk_revealer_set_reveal_child (self->search_revealer, TRUE);
+  gtk_widget_grab_focus (GTK_WIDGET (self->search_bar));
+}
+
+static void
+ide_editor_view_actions_find_replace (GSimpleAction *action,
                                       GVariant      *variant,
                                       gpointer       user_data)
 {
@@ -397,6 +412,7 @@ ide_editor_view_actions_focus_search (GSimpleAction *action,
   g_assert (G_IS_SIMPLE_ACTION (action));
   g_assert (IDE_IS_EDITOR_VIEW (self));
 
+  ide_editor_search_bar_set_replace_mode (self->search_bar, TRUE);
   gtk_revealer_set_reveal_child (self->search_revealer, TRUE);
   gtk_widget_grab_focus (GTK_WIDGET (self->search_bar));
 }
@@ -435,9 +451,46 @@ ide_editor_view_actions_notify_file_settings (IdeEditorView *self,
   g_object_set (group, "object", file_settings, NULL);
 }
 
+static void
+ide_editor_view_actions_move_next_error (GSimpleAction *action,
+                                         GVariant      *variant,
+                                         gpointer       user_data)
+{
+  ide_editor_view_move_next_error (user_data);
+}
+
+static void
+ide_editor_view_actions_move_previous_error (GSimpleAction *action,
+                                             GVariant      *variant,
+                                             gpointer       user_data)
+{
+  ide_editor_view_move_previous_error (user_data);
+}
+
+static void
+ide_editor_view_actions_move_next_search_result (GSimpleAction *action,
+                                                 GVariant      *variant,
+                                                 gpointer       user_data)
+{
+  ide_editor_view_move_next_search_result (user_data);
+}
+
+static void
+ide_editor_view_actions_move_previous_search_result (GSimpleAction *action,
+                                                     GVariant      *variant,
+                                                     gpointer       user_data)
+{
+  ide_editor_view_move_previous_search_result (user_data);
+}
+
 static const GActionEntry editor_view_entries[] = {
-  { "focus-search", ide_editor_view_actions_focus_search },
+  { "find", ide_editor_view_actions_find },
+  { "find-replace", ide_editor_view_actions_find_replace },
   { "hide-search", ide_editor_view_actions_hide_search },
+  { "move-next-error", ide_editor_view_actions_move_next_error },
+  { "move-next-search-result", ide_editor_view_actions_move_next_search_result },
+  { "move-previous-error", ide_editor_view_actions_move_previous_error },
+  { "move-previous-search-result", ide_editor_view_actions_move_previous_search_result },
   { "print", ide_editor_view_actions_print },
   { "reload", ide_editor_view_actions_reload },
   { "save", ide_editor_view_actions_save },
@@ -502,3 +555,10 @@ _ide_editor_view_init_actions (IdeEditorView *self)
   gtk_widget_insert_action_group (GTK_WIDGET (self), "file-settings", G_ACTION_GROUP (file_props));
   ide_editor_view_actions_notify_file_settings (self, NULL, source_view);
 }
+
+void
+_ide_editor_view_update_actions (IdeEditorView *self)
+{
+  g_return_if_fail (IDE_IS_EDITOR_VIEW (self));
+
+}
diff --git a/libide/editor/ide-editor-view.c b/libide/editor/ide-editor-view.c
index 1f5e7ed..ad8210d 100644
--- a/libide/editor/ide-editor-view.c
+++ b/libide/editor/ide-editor-view.c
@@ -384,12 +384,21 @@ ide_editor_view_hierarchy_changed (GtkWidget *widget,
   g_assert (IDE_IS_EDITOR_VIEW (self));
   g_assert (!old_toplevel || GTK_IS_WIDGET (old_toplevel));
 
-  /* Make sure we chain up if things change in the future */
+  /*
+   * We don't need to chain up today, but if IdeLayoutView starts
+   * using the hierarchy_changed signal to handle anything, we want
+   * to make sure we aren't surprised.
+   */
   if (GTK_WIDGET_CLASS (ide_editor_view_parent_class)->hierarchy_changed)
     GTK_WIDGET_CLASS (ide_editor_view_parent_class)->hierarchy_changed (widget, old_toplevel);
 
   context = ide_widget_get_context (GTK_WIDGET (self));
 
+  /*
+   * We don't want to create addins until the widget has been placed into
+   * the widget tree. That way the addins can get access to the context
+   * or other useful details.
+   */
   if (context != NULL && self->addins == NULL)
     {
       self->addins = ide_extension_set_adapter_new (context,
@@ -442,6 +451,52 @@ ide_editor_view_update_map (IdeEditorView *self)
 }
 
 static void
+search_revealer_notify_reveal_child (IdeEditorView *self,
+                                     GParamSpec    *pspec,
+                                     GtkRevealer   *revealer)
+{
+  GtkSourceCompletion *completion;
+
+  g_return_if_fail (IDE_IS_EDITOR_VIEW (self));
+  g_return_if_fail (pspec != NULL);
+  g_return_if_fail (GTK_IS_REVEALER (revealer));
+
+  completion = gtk_source_view_get_completion (GTK_SOURCE_VIEW (self->source_view));
+
+  if (!gtk_revealer_get_reveal_child (revealer))
+    {
+      /*
+       * Cancel any pending work by the context and release it. We don't need
+       * to hold onto these when they aren't being used because they handle
+       * buffer signals and other extraneous operations.
+       */
+      ide_editor_search_bar_set_context (self->search_bar, NULL);
+      g_clear_object (&self->search_context);
+
+      /* Restore completion that we blocked below. */
+      gtk_source_completion_unblock_interactive (completion);
+    }
+  else
+    {
+      g_assert (self->search_context == NULL);
+
+      self->search_context = g_object_new (GTK_SOURCE_TYPE_SEARCH_CONTEXT,
+                                           "buffer", self->buffer,
+                                           "highlight", TRUE,
+                                           "settings", self->search_settings,
+                                           NULL);
+      ide_editor_search_bar_set_context (self->search_bar, self->search_context);
+
+      /*
+       * Block the completion while the search bar is set. It only
+       * slows things down like search/replace functionality. We'll
+       * restore it above when we clear state.
+       */
+      gtk_source_completion_block_interactive (completion);
+    }
+}
+
+static void
 ide_editor_view_constructed (GObject *object)
 {
   IdeEditorView *self = (IdeEditorView *)object;
@@ -479,6 +534,18 @@ ide_editor_view_constructed (GObject *object)
                             G_CALLBACK (ide_editor_view_source_view_event),
                             self);
 
+  /*
+   * We want to track when the search revealer is visible. We will discard
+   * the search context when the revealer is not visible so that we don't
+   * continue performing expensive buffer operations.
+   */
+  g_signal_connect_swapped (self->search_revealer,
+                            "notify::reveal-child",
+                            G_CALLBACK (search_revealer_notify_reveal_child),
+                            self);
+
+  ide_editor_search_bar_set_settings (self->search_bar, self->search_settings);
+
   ide_editor_view_load_fonts (self);
   ide_editor_view_update_map (self);
 }
@@ -496,6 +563,11 @@ ide_editor_view_destroy (GtkWidget *widget)
 
   g_clear_object (&self->addins);
 
+  g_cancellable_cancel (self->destroy_cancellable);
+  g_clear_object (&self->destroy_cancellable);
+
+  g_clear_object (&self->search_settings);
+  g_clear_object (&self->search_context);
   g_clear_object (&self->editor_settings);
   g_clear_object (&self->insight_settings);
 
@@ -657,6 +729,8 @@ ide_editor_view_init (IdeEditorView *self)
   ide_layout_view_set_can_split (IDE_LAYOUT_VIEW (self), TRUE);
   ide_layout_view_set_menu_id (IDE_LAYOUT_VIEW (self), "ide-editor-view-document-menu");
 
+  self->destroy_cancellable = g_cancellable_new ();
+
   /* Setup signals to monitor on the buffer. */
   self->buffer_signals = dzl_signal_group_new (IDE_TYPE_BUFFER);
 
@@ -685,6 +759,23 @@ ide_editor_view_init (IdeEditorView *self)
                             G_CALLBACK (ide_editor_view_bind_signals),
                             self);
 
+  /*
+   * Setup our search context. The sourceview has it's own search
+   * infrastructure that we want to reserve for use by vim keybindings
+   * and other transient keybinding features. Instead, we have our own
+   * that can have separate state from those.
+   *
+   * We try to avoid creating/maintaining the search-context except
+   * when necessary because has some expensive operations associated
+   * with it's handling of changes to the underlying buffer.
+   */
+  self->search_settings = g_object_new (GTK_SOURCE_TYPE_SEARCH_SETTINGS,
+                                        "at-word-boundaries", FALSE,
+                                        "case-sensitive", FALSE,
+                                        "wrap-around", TRUE,
+                                        NULL);
+
+
   /* Setup bindings for the buffer. */
   self->buffer_bindings = dzl_binding_group_new ();
   dzl_binding_group_bind (self->buffer_bindings, "title", self, "title", 0);
@@ -906,3 +997,149 @@ ide_editor_view_get_language (IdeEditorView *self)
 
   return gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self->buffer));
 }
+
+/**
+ * ide_editor_view_move_next_error:
+ * @self: a #IdeEditorView
+ *
+ * Moves to the next error, if any.
+ *
+ * If there is no error, the insertion cursor is not moved.
+ *
+ * Since: 3.26
+ */
+void
+ide_editor_view_move_next_error (IdeEditorView *self)
+{
+  g_return_if_fail (IDE_IS_EDITOR_VIEW (self));
+
+  g_signal_emit_by_name (self->source_view, "move-error", GTK_DIR_DOWN);
+}
+
+/**
+ * ide_editor_view_move_previous_error:
+ * @self: a #IdeEditorView
+ *
+ * Moves the insertion cursor to the previous error.
+ *
+ * If there is no error, the insertion cursor is not moved.
+ *
+ * Since: 3.26
+ */
+void
+ide_editor_view_move_previous_error (IdeEditorView *self)
+{
+  g_return_if_fail (IDE_IS_EDITOR_VIEW (self));
+
+  g_signal_emit_by_name (self->source_view, "move-error", GTK_DIR_UP);
+}
+
+static void
+ide_editor_view_move_next_search_result_cb (GObject      *object,
+                                            GAsyncResult *result,
+                                            gpointer      user_data)
+{
+  GtkSourceSearchContext *context = (GtkSourceSearchContext *)object;
+  g_autoptr(IdeEditorView) self = user_data;
+  g_autoptr(GError) error = NULL;
+  GtkTextIter begin;
+  GtkTextIter end;
+  gboolean has_wrapped = FALSE;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (self->buffer == NULL)
+    return;
+
+  if (gtk_source_search_context_forward_finish2 (context, result, &begin, &end, &has_wrapped, &error))
+    gtk_text_buffer_select_range (GTK_TEXT_BUFFER (self->buffer), &begin, &end);
+}
+
+/**
+ * ide_editor_view_move_next_search_result:
+ * @self: a #IdeEditorView
+ *
+ * Moves the insertion cursor to the next search result.
+ *
+ * If there is no search result, the insertion cursor is not moved.
+ *
+ * Since: 3.26
+ */
+void
+ide_editor_view_move_next_search_result (IdeEditorView *self)
+{
+  GtkTextIter begin;
+  GtkTextIter end;
+
+  g_return_if_fail (IDE_IS_EDITOR_VIEW (self));
+  g_return_if_fail (self->destroy_cancellable != NULL);
+  g_return_if_fail (self->buffer != NULL);
+
+  if (self->search_context == NULL)
+    return;
+
+  if (gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (self->buffer), &begin, &end))
+    gtk_text_iter_order (&begin, &end);
+
+  gtk_source_search_context_forward_async (self->search_context,
+                                           &end,
+                                           self->destroy_cancellable,
+                                           ide_editor_view_move_next_search_result_cb,
+                                           g_object_ref (self));
+}
+
+static void
+ide_editor_view_move_previous_search_result_cb (GObject      *object,
+                                                GAsyncResult *result,
+                                                gpointer      user_data)
+{
+  GtkSourceSearchContext *context = (GtkSourceSearchContext *)object;
+  g_autoptr(IdeEditorView) self = user_data;
+  g_autoptr(GError) error = NULL;
+  GtkTextIter begin;
+  GtkTextIter end;
+  gboolean has_wrapped = FALSE;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (self->buffer == NULL)
+    return;
+
+  if (gtk_source_search_context_backward_finish2 (context, result, &begin, &end, &has_wrapped, &error))
+    gtk_text_buffer_select_range (GTK_TEXT_BUFFER (self->buffer), &begin, &end);
+}
+
+/**
+ * ide_editor_view_move_previous_search_result:
+ * @self: a #IdeEditorView
+ *
+ * Moves the insertion cursor to the previous search result.
+ *
+ * If there is no search result, the insertion cursor is not moved.
+ *
+ * Since: 3.26
+ */
+void
+ide_editor_view_move_previous_search_result (IdeEditorView *self)
+{
+  GtkTextIter begin;
+  GtkTextIter end;
+
+  g_return_if_fail (IDE_IS_EDITOR_VIEW (self));
+  g_return_if_fail (self->destroy_cancellable != NULL);
+  g_return_if_fail (self->buffer != NULL);
+
+  if (self->search_context == NULL)
+    return;
+
+  if (gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (self->buffer), &begin, &end))
+    gtk_text_iter_order (&begin, &end);
+
+  gtk_source_search_context_backward_async (self->search_context,
+                                            &begin,
+                                            self->destroy_cancellable,
+                                            ide_editor_view_move_previous_search_result_cb,
+                                            g_object_ref (self));
+}
diff --git a/libide/editor/ide-editor-view.h b/libide/editor/ide-editor-view.h
index 443c322..1a36b49 100644
--- a/libide/editor/ide-editor-view.h
+++ b/libide/editor/ide-editor-view.h
@@ -30,19 +30,23 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (IdeEditorView, ide_editor_view, IDE, EDITOR_VIEW, IdeLayoutView)
 
-IdeBuffer         *ide_editor_view_get_buffer        (IdeEditorView     *self);
-IdeSourceView     *ide_editor_view_get_view          (IdeEditorView     *self);
-const gchar       *ide_editor_view_get_language_id   (IdeEditorView     *self);
-void               ide_editor_view_scroll_to_line    (IdeEditorView     *self,
-                                                      guint              line);
-gboolean           ide_editor_view_get_auto_hide_map (IdeEditorView     *self);
-void               ide_editor_view_set_auto_hide_map (IdeEditorView     *self,
-                                                      gboolean           auto_hide_map);
-gboolean           ide_editor_view_get_show_map      (IdeEditorView     *self);
-void               ide_editor_view_set_show_map      (IdeEditorView     *self,
-                                                      gboolean           show_map);
-GtkSourceLanguage *ide_editor_view_get_language      (IdeEditorView     *self);
-void               ide_editor_view_set_language      (IdeEditorView     *self,
-                                                      GtkSourceLanguage *language);
+IdeBuffer         *ide_editor_view_get_buffer                  (IdeEditorView     *self);
+IdeSourceView     *ide_editor_view_get_view                    (IdeEditorView     *self);
+const gchar       *ide_editor_view_get_language_id             (IdeEditorView     *self);
+void               ide_editor_view_scroll_to_line              (IdeEditorView     *self,
+                                                                guint              line);
+gboolean           ide_editor_view_get_auto_hide_map           (IdeEditorView     *self);
+void               ide_editor_view_set_auto_hide_map           (IdeEditorView     *self,
+                                                                gboolean           auto_hide_map);
+gboolean           ide_editor_view_get_show_map                (IdeEditorView     *self);
+void               ide_editor_view_set_show_map                (IdeEditorView     *self,
+                                                                gboolean           show_map);
+GtkSourceLanguage *ide_editor_view_get_language                (IdeEditorView     *self);
+void               ide_editor_view_set_language                (IdeEditorView     *self,
+                                                                GtkSourceLanguage *language);
+void               ide_editor_view_move_next_error             (IdeEditorView     *self);
+void               ide_editor_view_move_previous_error         (IdeEditorView     *self);
+void               ide_editor_view_move_next_search_result     (IdeEditorView     *self);
+void               ide_editor_view_move_previous_search_result (IdeEditorView     *self);
 
 G_END_DECLS


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