[gtksourceview/wip/search: 4/5] Higher-level asynchronous search and replace API



commit 4423feff5acade12ce4f3521ca220951cd75b239
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Sat Jun 15 10:46:39 2013 +0200

    Higher-level asynchronous search and replace API
    
    And the implementation, the documentation, some unit tests, a test for
    the performances, a mini program with a basic UI, and utilities
    functions.

 docs/reference/Makefile.am                    |    1 +
 docs/reference/gtksourceview-3.0-sections.txt |   43 +-
 docs/reference/gtksourceview-docs.xml         |    1 +
 gtksourceview/Makefile.am                     |    4 +
 gtksourceview/gtksource.h                     |    1 +
 gtksourceview/gtksourcebuffer.c               |  717 ++++++++-
 gtksourceview/gtksourcebuffer.h               |   72 +
 gtksourceview/gtksourcesearch.c               | 2125 +++++++++++++++++++++++++
 gtksourceview/gtksourcesearch.h               |  156 ++
 gtksourceview/gtksourcetypes-private.h        |    1 +
 gtksourceview/gtksourceutils.c                |  191 +++
 gtksourceview/gtksourceutils.h                |   34 +
 po/POTFILES.in                                |    2 +
 tests/Makefile.am                             |   41 +-
 tests/test-search-performances.c              |  183 +++
 tests/test-search-ui.c                        |  441 +++++
 tests/test-search-ui.gresource.xml            |    6 +
 tests/test-search-ui.ui                       |  289 ++++
 tests/test-search.c                           |  265 +++
 19 files changed, 4532 insertions(+), 41 deletions(-)
---
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index 0ee0152..58d5593 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -37,6 +37,7 @@ IGNORE_HFILES =                                       \
        gtksourcelanguage-private.h             \
        gtksourcepixbufhelper.h                 \
        gtksourceregex.h                        \
+       gtksourcesearch.h                       \
        gtksourcestyle-private.h                \
        gtksourcetypes-private.h                \
        gtksourceundomanagerdefault.h           \
diff --git a/docs/reference/gtksourceview-3.0-sections.txt b/docs/reference/gtksourceview-3.0-sections.txt
index 1e82636..a432ef3 100644
--- a/docs/reference/gtksourceview-3.0-sections.txt
+++ b/docs/reference/gtksourceview-3.0-sections.txt
@@ -15,14 +15,6 @@ gtk_source_buffer_set_highlight_matching_brackets
 gtk_source_buffer_get_highlight_matching_brackets
 gtk_source_buffer_set_style_scheme
 gtk_source_buffer_get_style_scheme
-gtk_source_buffer_get_max_undo_levels
-gtk_source_buffer_set_max_undo_levels
-gtk_source_buffer_redo
-gtk_source_buffer_undo
-gtk_source_buffer_can_redo
-gtk_source_buffer_can_undo
-gtk_source_buffer_begin_not_undoable_action
-gtk_source_buffer_end_not_undoable_action
 gtk_source_buffer_ensure_highlight
 gtk_source_buffer_create_source_mark
 gtk_source_buffer_forward_iter_to_source_mark
@@ -34,8 +26,36 @@ gtk_source_buffer_iter_has_context_class
 gtk_source_buffer_get_context_classes_at_iter
 gtk_source_buffer_iter_forward_to_context_class_toggle
 gtk_source_buffer_iter_backward_to_context_class_toggle
+<SUBSECTION>
+gtk_source_buffer_get_max_undo_levels
+gtk_source_buffer_set_max_undo_levels
+gtk_source_buffer_redo
+gtk_source_buffer_undo
+gtk_source_buffer_can_redo
+gtk_source_buffer_can_undo
+gtk_source_buffer_begin_not_undoable_action
+gtk_source_buffer_end_not_undoable_action
 gtk_source_buffer_get_undo_manager
 gtk_source_buffer_set_undo_manager
+<SUBSECTION>
+gtk_source_buffer_set_search_text
+gtk_source_buffer_get_search_text
+gtk_source_buffer_set_case_sensitive_search
+gtk_source_buffer_get_case_sensitive_search
+gtk_source_buffer_set_search_at_word_boundaries
+gtk_source_buffer_get_search_at_word_boundaries
+gtk_source_buffer_set_search_wrap_around
+gtk_source_buffer_get_search_wrap_around
+gtk_source_buffer_get_search_occurrences_count
+gtk_source_buffer_get_search_occurrence_position
+gtk_source_buffer_forward_search
+gtk_source_buffer_forward_search_async
+gtk_source_buffer_forward_search_finish
+gtk_source_buffer_backward_search
+gtk_source_buffer_backward_search_async
+gtk_source_buffer_backward_search_finish
+gtk_source_buffer_search_replace
+gtk_source_buffer_search_replace_all
 <SUBSECTION Standard>
 GtkSourceBufferClass
 GTK_SOURCE_IS_BUFFER
@@ -600,3 +620,10 @@ GtkSourceCompletionWordsClass
 GtkSourceCompletionWordsPrivate
 gtk_source_completion_words_get_type
 </SECTION>
+
+<SECTION>
+<FILE>utils</FILE>
+<TITLE>GtkSourceUtils</TITLE>
+gtk_source_utils_unescape_search_text
+gtk_source_utils_escape_search_text
+</SECTION>
diff --git a/docs/reference/gtksourceview-docs.xml b/docs/reference/gtksourceview-docs.xml
index abcdc61..90fee63 100644
--- a/docs/reference/gtksourceview-docs.xml
+++ b/docs/reference/gtksourceview-docs.xml
@@ -37,6 +37,7 @@
     <xi:include href="xml/stylescheme.xml"/>
     <xi:include href="xml/styleschememanager.xml"/>
     <xi:include href="xml/undomanager.xml"/>
+    <xi:include href="xml/utils.xml"/>
     <xi:include href="xml/view.xml"/>
   </chapter>
 
diff --git a/gtksourceview/Makefile.am b/gtksourceview/Makefile.am
index 269854e..88e281f 100644
--- a/gtksourceview/Makefile.am
+++ b/gtksourceview/Makefile.am
@@ -44,6 +44,7 @@ libgtksourceview_headers =                    \
        gtksourcestyleschememanager.h           \
        gtksourcetypes.h                        \
        gtksourceundomanager.h                  \
+       gtksourceutils.h                        \
        gtksourceview.h
 
 libgtksourceview_private_headers = \
@@ -59,6 +60,7 @@ libgtksourceview_private_headers = \
        gtksourcelanguage-private.h             \
        gtksourcepixbufhelper.h                 \
        gtksourceregex.h                        \
+       gtksourcesearch.h                       \
        gtksourcestyle-private.h                \
        gtksourcetypes-private.h                \
        gtksourceundomanagerdefault.h           \
@@ -75,6 +77,7 @@ libgtksourceview_private_c_files = \
        gtksourcelanguage-parser-1.c    \
        gtksourcelanguage-parser-2.c    \
        gtksourceregex.c                \
+       gtksourcesearch.c               \
        gtksourceundomanager.c          \
        gtksourceundomanagerdefault.c   \
        gtksourceview-i18n.c            \
@@ -103,6 +106,7 @@ libgtksourceview_c_files = \
        gtksourcestyle.c                \
        gtksourcestylescheme.c          \
        gtksourcestyleschememanager.c   \
+       gtksourceutils.c                \
        gtksourceview.c
 
 # Split in a helper library for unit tests
diff --git a/gtksourceview/gtksource.h b/gtksourceview/gtksource.h
index 31a5919..d95a037 100644
--- a/gtksourceview/gtksource.h
+++ b/gtksourceview/gtksource.h
@@ -41,6 +41,7 @@
 #include <gtksourceview/gtksourcestylescheme.h>
 #include <gtksourceview/gtksourcestyleschememanager.h>
 #include <gtksourceview/gtksourceundomanager.h>
+#include <gtksourceview/gtksourceutils.h>
 #include <gtksourceview/gtksourceview.h>
 #include <gtksourceview/gtksourceview-typebuiltins.h>
 
diff --git a/gtksourceview/gtksourcebuffer.c b/gtksourceview/gtksourcebuffer.c
index 4828fc5..bea13a1 100644
--- a/gtksourceview/gtksourcebuffer.c
+++ b/gtksourceview/gtksourcebuffer.c
@@ -30,10 +30,10 @@
 #include <string.h>
 #include <gtk/gtk.h>
 
+#include "gtksourcebuffer.h"
 #include "gtksourceview-i18n.h"
 #include "gtksourcelanguage.h"
 #include "gtksourcelanguage-private.h"
-#include "gtksourcebuffer.h"
 #include "gtksourceundomanager.h"
 #include "gtksourceview-marshal.h"
 #include "gtksourcestylescheme.h"
@@ -42,6 +42,7 @@
 #include "gtksourceundomanagerdefault.h"
 #include "gtksourceview-typebuiltins.h"
 #include "gtksourcemark.h"
+#include "gtksourcesearch.h"
 
 /**
  * SECTION:buffer
@@ -49,32 +50,71 @@
  * @Title: GtkSourceBuffer
  * @See_also: #GtkTextBuffer, #GtkSourceView
  *
- * The #GtkSourceBuffer object is the model for #GtkSourceView widgets.
- * It extends the #GtkTextBuffer object by adding features useful to display
- * and edit source code as syntax highlighting and bracket matching. It
- * also implements support for undo/redo operations.
- *
- * To create a #GtkSourceBuffer use gtk_source_buffer_new() or
- * gtk_source_buffer_new_with_language(). The second form is just a convenience
- * function which allows you to initially set a #GtkSourceLanguage.
- *
- * By default highlighting is enabled, but you can disable it with
- * gtk_source_buffer_set_highlight_syntax().
- *
- * A custom #GtkSourceUndoManager can be implemented and set with
- * gtk_source_buffer_set_undo_manager(). However the default implementation
- * should be suitable for most uses. By default, actions that can be undone or
- * redone are defined as groups of operations between a call to
- * gtk_text_buffer_begin_user_action() and gtk_text_buffer_end_user_action(). In
- * general, this happens whenever the user presses any key which modifies the
- * buffer. But the default undo manager will try to merge similar consecutive
- * actions, such as multiple character insertions on the same line, into one
- * action. But, inserting a newline starts a new action.
- *
- * The default undo manager remembers the "modified" state of the buffer, and
- * restore it when an action is undone or redone. It can be useful in a text
- * editor to know whether the file is saved. See gtk_text_buffer_get_modified()
- * and gtk_text_buffer_set_modified().
+ * <para>
+ *   The #GtkSourceBuffer object is the model for #GtkSourceView widgets.
+ *   It extends the #GtkTextBuffer object by adding features useful to display
+ *   and edit source code such as syntax highlighting and bracket matching. It
+ *   also implements support for undo/redo operations, and for the search and
+ *   replace.
+ * </para>
+ * <para>
+ *   To create a #GtkSourceBuffer use gtk_source_buffer_new() or
+ *   gtk_source_buffer_new_with_language(). The second form is just a convenience
+ *   function which allows you to initially set a #GtkSourceLanguage.
+ * </para>
+ * <para>
+ *   By default highlighting is enabled, but you can disable it with
+ *   gtk_source_buffer_set_highlight_syntax().
+ * </para>
+ * <refsect2>
+ *   <title>Undo and Redo</title>
+ *   <para>
+ *     A custom #GtkSourceUndoManager can be implemented and set with
+ *     gtk_source_buffer_set_undo_manager(). However the default implementation
+ *     should be suitable for most uses. By default, actions that can be undone or
+ *     redone are defined as groups of operations between a call to
+ *     gtk_text_buffer_begin_user_action() and gtk_text_buffer_end_user_action(). In
+ *     general, this happens whenever the user presses any key which modifies the
+ *     buffer. But the default undo manager will try to merge similar consecutive
+ *     actions, such as multiple character insertions on the same line, into one
+ *     action. But, inserting a newline starts a new action.
+ *   </para>
+ *   <para>
+ *     The default undo manager remembers the "modified" state of the buffer, and
+ *     restore it when an action is undone or redone. It can be useful in a text
+ *     editor to know whether the file is saved. See gtk_text_buffer_get_modified()
+ *     and gtk_text_buffer_set_modified().
+ *   </para>
+ * </refsect2>
+ * <refsect2>
+ *   <title>Search and Replace</title>
+ *   <para>
+ *     To set the text to search, use gtk_source_buffer_set_search_text(). The
+ *     search occurrences will be highlighted, and the total number of
+ *     occurrences can be retrieved with
+ *     gtk_source_buffer_get_search_occurrences_count(). The buffer is scanned
+ *     asynchronously, so it doesn't block the user interface. For each search,
+ *     the buffer is scanned at most once. After that, navigating through the
+ *     occurrences doesn't require to re-scan the buffer entirely.
+ *   </para>
+ *   <para>
+ *     You can tune the search with the following properties:
+ *     #GtkSourceBuffer:case-sensitive-search,
+ *     #GtkSourceBuffer:search-at-word-boundaries and
+ *     #GtkSourceBuffer:search-wrap-around.
+ *   </para>
+ *   <para>
+ *     To search forward, use gtk_source_buffer_forward_search() or
+ *     gtk_source_buffer_forward_search_async() for the asynchronous version.
+ *     The backward search is done similarly. To replace a search match, or all
+ *     matches, use gtk_source_buffer_search_replace() and
+ *     gtk_source_buffer_search_replace_all().
+ *   </para>
+ *   <para>
+ *     To know the position of a certain match, use
+ *     gtk_source_buffer_get_search_occurrence_position().
+ *   </para>
+ * </refsect2>
  */
 
 /*
@@ -120,7 +160,12 @@ enum {
        PROP_MAX_UNDO_LEVELS,
        PROP_LANGUAGE,
        PROP_STYLE_SCHEME,
-       PROP_UNDO_MANAGER
+       PROP_UNDO_MANAGER,
+       PROP_SEARCH_TEXT,
+       PROP_SEARCH_OCCURRENCES_COUNT,
+       PROP_CASE_SENSITIVE_SEARCH,
+       PROP_SEARCH_AT_WORD_BOUNDARIES,
+       PROP_SEARCH_WRAP_AROUND
 };
 
 struct _GtkSourceBufferPrivate
@@ -140,6 +185,8 @@ struct _GtkSourceBufferPrivate
        GtkSourceUndoManager  *undo_manager;
        gint                   max_undo_levels;
 
+       GtkSourceSearch       *search;
+
        guint                  highlight_syntax : 1;
        guint                  highlight_brackets : 1;
        guint                  constructed : 1;
@@ -335,6 +382,87 @@ gtk_source_buffer_class_init (GtkSourceBufferClass *klass)
                                                              GTK_SOURCE_TYPE_UNDO_MANAGER,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 
+       /**
+        * GtkSourceBuffer:search-text:
+        *
+        * A search string, or %NULL if the search is disabled.
+        *
+        * Since: 3.10
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_SEARCH_TEXT,
+                                        g_param_spec_string ("search-text",
+                                                             _("Search text"),
+                                                             _("The text to search"),
+                                                             NULL,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+       /**
+        * GtkSourceBuffer:search-occurrences-count:
+        *
+        * The total number of search occurrences. If the search is disabled,
+        * the value is 0.
+        *
+        * Since: 3.10
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_SEARCH_OCCURRENCES_COUNT,
+                                        g_param_spec_uint ("search-occurrences-count",
+                                                           _("Search occurrences count"),
+                                                           _("Total number of search occurrences"),
+                                                           0,
+                                                           G_MAXUINT,
+                                                           0,
+                                                           G_PARAM_READABLE));
+
+       /**
+        * GtkSourceBuffer:case-sensitive-search:
+        *
+        * Whether the search is case sensitive.
+        *
+        * Since: 3.10
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_CASE_SENSITIVE_SEARCH,
+                                        g_param_spec_boolean ("case-sensitive-search",
+                                                              _("Case sensitive search"),
+                                                              _("Case sensitive search"),
+                                                              FALSE,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+       /**
+        * GtkSourceBuffer:search-at-word-boundaries:
+        *
+        * If %TRUE, a search match must start and end a word. The match can
+        * span multiple words.
+        *
+        * Since: 3.10
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_SEARCH_AT_WORD_BOUNDARIES,
+                                        g_param_spec_boolean ("search-at-word-boundaries",
+                                                              _("Search at word boundaries"),
+                                                              _("Search at word boundaries"),
+                                                              FALSE,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+       /**
+        * GtkSourceBuffer:search-wrap-around:
+        *
+        * For a forward search, continue at the beginning of the buffer if no
+        * search occurrence is found. For a backward search, continue at the
+        * end of the buffer.
+        *
+        * Since: 3.10
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_SEARCH_WRAP_AROUND,
+                                        g_param_spec_boolean ("search-wrap-around",
+                                                              _("Search: wrap around"),
+                                                              _("Search: wrap around"),
+                                                              TRUE,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
        param_types[0] = GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE;
        param_types[1] = GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE;
 
@@ -476,6 +604,8 @@ gtk_source_buffer_init (GtkSourceBuffer *buffer)
 
        if (priv->style_scheme != NULL)
                g_object_ref (priv->style_scheme);
+
+       priv->search = _gtk_source_search_new (buffer);
 }
 
 static void
@@ -518,6 +648,7 @@ gtk_source_buffer_dispose (GObject *object)
        g_clear_object (&buffer->priv->highlight_engine);
        g_clear_object (&buffer->priv->language);
        g_clear_object (&buffer->priv->style_scheme);
+       g_clear_object (&buffer->priv->search);
 
        G_OBJECT_CLASS (gtk_source_buffer_parent_class)->dispose (object);
 }
@@ -566,6 +697,26 @@ gtk_source_buffer_set_property (GObject      *object,
                                                            g_value_get_object (value));
                        break;
 
+               case PROP_SEARCH_TEXT:
+                       _gtk_source_search_set_text (source_buffer->priv->search,
+                                                    g_value_get_string (value));
+                       break;
+
+               case PROP_CASE_SENSITIVE_SEARCH:
+                       _gtk_source_search_set_case_sensitive (source_buffer->priv->search,
+                                                              g_value_get_boolean (value));
+                       break;
+
+               case PROP_SEARCH_AT_WORD_BOUNDARIES:
+                       _gtk_source_search_set_at_word_boundaries (source_buffer->priv->search,
+                                                                  g_value_get_boolean (value));
+                       break;
+
+               case PROP_SEARCH_WRAP_AROUND:
+                       _gtk_source_search_set_wrap_around (source_buffer->priv->search,
+                                                           g_value_get_boolean (value));
+                       break;
+
                default:
                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                        break;
@@ -621,6 +772,26 @@ gtk_source_buffer_get_property (GObject    *object,
                        g_value_set_object (value, source_buffer->priv->undo_manager);
                        break;
 
+               case PROP_SEARCH_TEXT:
+                       g_value_set_string (value, _gtk_source_search_get_text (source_buffer->priv->search));
+                       break;
+
+               case PROP_SEARCH_OCCURRENCES_COUNT:
+                       g_value_set_uint (value, _gtk_source_search_get_occurrences_count 
(source_buffer->priv->search));
+                       break;
+
+               case PROP_CASE_SENSITIVE_SEARCH:
+                       g_value_set_boolean (value, _gtk_source_search_get_case_sensitive 
(source_buffer->priv->search));
+                       break;
+
+               case PROP_SEARCH_AT_WORD_BOUNDARIES:
+                       g_value_set_boolean (value, _gtk_source_search_get_at_word_boundaries 
(source_buffer->priv->search));
+                       break;
+
+               case PROP_SEARCH_WRAP_AROUND:
+                       g_value_set_boolean (value, _gtk_source_search_get_wrap_around 
(source_buffer->priv->search));
+                       break;
+
                default:
                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                        break;
@@ -1532,10 +1703,17 @@ _gtk_source_buffer_update_highlight (GtkSourceBuffer   *buffer,
        g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
 
        if (buffer->priv->highlight_engine != NULL)
+       {
                _gtk_source_engine_update_highlight (buffer->priv->highlight_engine,
                                                     start,
                                                     end,
                                                     synchronous);
+       }
+
+       _gtk_source_search_update_highlight (buffer->priv->search,
+                                            start,
+                                            end,
+                                            synchronous);
 }
 
 /**
@@ -2476,3 +2654,486 @@ gtk_source_buffer_get_undo_manager (GtkSourceBuffer *buffer)
 
        return buffer->priv->undo_manager;
 }
+
+/**
+ * gtk_source_buffer_set_search_text:
+ * @buffer: a #GtkSourceBuffer.
+ * @text: the nul-terminated text to search, or %NULL to disable the search.
+ *
+ * Sets the text to search. If @text is %NULL or is empty, the search will be
+ * disabled. A copy of @text will be made, so you can safely free @text after
+ * a call to this function.
+ *
+ * You may be interested to call gtk_source_utils_unescape_search_text() before
+ * this function.
+ *
+ * Since: 3.10
+ */
+void
+gtk_source_buffer_set_search_text (GtkSourceBuffer *buffer,
+                                  const gchar     *text)
+{
+       const gchar *cur_text;
+
+       g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
+
+       cur_text = _gtk_source_search_get_text (buffer->priv->search);
+
+       if (cur_text == NULL && (text == NULL || *text == '\0'))
+       {
+               return;
+       }
+
+       if (g_strcmp0 (cur_text, text) != 0)
+       {
+               _gtk_source_search_set_text (buffer->priv->search, text);
+               g_object_notify (G_OBJECT (buffer), "search-text");
+       }
+}
+
+/**
+ * gtk_source_buffer_get_search_text:
+ * @buffer: a #GtkSourceBuffer.
+ *
+ * Gets the text to search. The return value must not be freed.
+ *
+ * You may be interested to call gtk_source_utils_escape_search_text() after
+ * this function.
+ *
+ * Returns: the text to search, or %NULL if the search is disabled.
+ * Since: 3.10
+ */
+const gchar *
+gtk_source_buffer_get_search_text (GtkSourceBuffer *buffer)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
+
+       return _gtk_source_search_get_text (buffer->priv->search);
+}
+
+/**
+ * gtk_source_buffer_set_case_sensitive_search:
+ * @buffer: a #GtkSourceBuffer.
+ * @case_sensitive: the setting.
+ *
+ * Enables or disables the case sensitivity for the search.
+ *
+ * Since: 3.10
+ */
+void
+gtk_source_buffer_set_case_sensitive_search (GtkSourceBuffer *buffer,
+                                            gboolean         case_sensitive)
+{
+       gboolean cur_val;
+
+       g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
+
+       case_sensitive = case_sensitive != FALSE;
+
+       cur_val = _gtk_source_search_get_case_sensitive (buffer->priv->search);
+
+       if (cur_val != case_sensitive)
+       {
+               _gtk_source_search_set_case_sensitive (buffer->priv->search,
+                                                      case_sensitive);
+
+               g_object_notify (G_OBJECT (buffer), "case-sensitive-search");
+       }
+}
+
+/**
+ * gtk_source_buffer_get_case_sensitive_search:
+ * @buffer: a #GtkSourceBuffer.
+ *
+ * Returns: whether the search is case sensitive.
+ * Since: 3.10
+ */
+gboolean
+gtk_source_buffer_get_case_sensitive_search (GtkSourceBuffer *buffer)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
+
+       return _gtk_source_search_get_case_sensitive (buffer->priv->search);
+}
+
+/**
+ * gtk_source_buffer_set_search_at_word_boundaries:
+ * @buffer: a #GtkSourceBuffer.
+ * @at_word_boundaries: the setting.
+ *
+ * Change whether the search is done at word boundaries. If @at_word_boundaries
+ * is %TRUE, a search match must start and end a word. The match can span
+ * multiple words. See also gtk_text_iter_starts_word() and
+ * gtk_text_iter_ends_word().
+ *
+ * Since: 3.10
+ */
+void
+gtk_source_buffer_set_search_at_word_boundaries (GtkSourceBuffer *buffer,
+                                                gboolean         at_word_boundaries)
+{
+       gboolean cur_val;
+
+       g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
+
+       at_word_boundaries = at_word_boundaries != FALSE;
+
+       cur_val = _gtk_source_search_get_at_word_boundaries (buffer->priv->search);
+
+       if (cur_val != at_word_boundaries)
+       {
+               _gtk_source_search_set_at_word_boundaries (buffer->priv->search,
+                                                          at_word_boundaries);
+
+               g_object_notify (G_OBJECT (buffer), "search-at-word-boundaries");
+       }
+}
+
+/**
+ * gtk_source_buffer_get_search_at_word_boundaries:
+ * @buffer: a #GtkSourceBuffer.
+ *
+ * Returns: whether to search at word boundaries.
+ * Since: 3.10
+ */
+gboolean
+gtk_source_buffer_get_search_at_word_boundaries (GtkSourceBuffer *buffer)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
+
+       return _gtk_source_search_get_at_word_boundaries (buffer->priv->search);
+}
+
+/**
+ * gtk_source_buffer_set_search_wrap_around:
+ * @buffer: a #GtkSourceBuffer.
+ * @wrap_around: the setting.
+ *
+ * Enables or disables the wrap around search. If @wrap_around is %TRUE, the
+ * forward search continues at the beginning of the buffer if no search
+ * occurrences are found. Similarly, the backward search continues to search at
+ * the end of the buffer.
+ *
+ * Since: 3.10
+ */
+void
+gtk_source_buffer_set_search_wrap_around (GtkSourceBuffer *buffer,
+                                         gboolean         wrap_around)
+{
+       gboolean cur_val;
+
+       g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
+
+       wrap_around = wrap_around != FALSE;
+
+       cur_val = _gtk_source_search_get_wrap_around (buffer->priv->search);
+
+       if (cur_val != wrap_around)
+       {
+               _gtk_source_search_set_wrap_around (buffer->priv->search,
+                                                   wrap_around);
+
+               g_object_notify (G_OBJECT (buffer), "search-wrap-around");
+       }
+}
+
+/**
+ * gtk_source_buffer_get_search_wrap_around:
+ * @buffer: a #GtkSourceBuffer.
+ *
+ * Returns: whether to wrap around the search.
+ * Since: 3.10
+ */
+gboolean
+gtk_source_buffer_get_search_wrap_around (GtkSourceBuffer *buffer)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
+
+       return _gtk_source_search_get_wrap_around (buffer->priv->search);
+}
+
+/**
+ * gtk_source_buffer_get_search_occurrences_count:
+ * @buffer: a #GtkSourceBuffer.
+ *
+ * Gets the total number of search occurrences.
+ *
+ * Returns: the total number of search occurrences.
+ * Since: 3.10
+ */
+guint
+gtk_source_buffer_get_search_occurrences_count (GtkSourceBuffer *buffer)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), 0);
+
+       return _gtk_source_search_get_occurrences_count (buffer->priv->search);
+}
+
+/**
+ * gtk_source_buffer_get_search_occurrence_position:
+ * @buffer: a #GtkSourceBuffer.
+ * @match_start: the start of the occurrence.
+ * @match_end: the end of the occurrence.
+ *
+ * Gets the position of a search occurrence. If the buffer is not already fully
+ * scanned, the position may be unknown, and -1 is returned. Therefore you
+ * should call this function when you know that the buffer is fully scanned.
+ *
+ * Returns: the position of the search occurrence. The first occurrence has the
+ * position 1 (not 0). Returns 0 if @match_start and @match_end doesn't delimit
+ * an occurrence. Returns -1 if the position is not yet known.
+ *
+ * Since: 3.10
+ */
+gint
+gtk_source_buffer_get_search_occurrence_position (GtkSourceBuffer   *buffer,
+                                                 const GtkTextIter *match_start,
+                                                 const GtkTextIter *match_end)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), -1);
+       g_return_val_if_fail (match_start != NULL, -1);
+       g_return_val_if_fail (match_end != NULL, -1);
+
+       return _gtk_source_search_get_occurrence_position (buffer->priv->search,
+                                                          match_start,
+                                                          match_end);
+}
+
+/**
+ * gtk_source_buffer_forward_search:
+ * @buffer: a #GtkSourceBuffer.
+ * @iter: start of search.
+ * @match_start: return location for start of match, or %NULL.
+ * @match_end: return location for end of match, or %NULL.
+ *
+ * Synchronous forward search.
+ *
+ * Returns: whether a match was found.
+ * Since: 3.10
+ */
+gboolean
+gtk_source_buffer_forward_search (GtkSourceBuffer   *buffer,
+                                 const GtkTextIter *iter,
+                                 GtkTextIter       *match_start,
+                                 GtkTextIter       *match_end)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
+       g_return_val_if_fail (iter != NULL, FALSE);
+
+       return _gtk_source_search_forward (buffer->priv->search,
+                                          iter,
+                                          match_start,
+                                          match_end);
+}
+
+/**
+ * gtk_source_buffer_forward_search_async:
+ * @buffer: a #GtkSourceBuffer.
+ * @iter: start of search.
+ * @cancellable: a #GCancellable, or %NULL.
+ * @callback: a #GAsyncReadyCallback to call when the operation is finished.
+ * @user_data: the data to pass to the @callback function.
+ *
+ * Asynchronous forward search. See the #GAsyncResult documentation to know
+ * how to use this function.
+ *
+ * If the operation is cancelled, the @callback will only be called if
+ * @cancellable was not %NULL. gtk_source_buffer_forward_search_async() takes
+ * ownership of @cancellable, so you can unref it after calling this function.
+ *
+ * Since: 3.10
+ */
+void
+gtk_source_buffer_forward_search_async (GtkSourceBuffer     *buffer,
+                                       const GtkTextIter   *iter,
+                                       GCancellable        *cancellable,
+                                       GAsyncReadyCallback  callback,
+                                       gpointer             user_data)
+{
+       g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
+       g_return_if_fail (iter != NULL);
+
+       _gtk_source_search_forward_async (buffer->priv->search,
+                                         iter,
+                                         cancellable,
+                                         callback,
+                                         user_data);
+}
+
+/**
+ * gtk_source_buffer_forward_search_finish:
+ * @buffer: a #GtkSourceBuffer.
+ * @result: a #GAsyncResult.
+ * @match_start: return location for start of match, or %NULL.
+ * @match_end: return location for end of match, or %NULL.
+ * @error: a #GError, or %NULL.
+ *
+ * Finishes a forward search started with
+ * gtk_source_buffer_forward_search_async().
+ *
+ * Returns: whether a match was found.
+ * Since: 3.10
+ */
+gboolean
+gtk_source_buffer_forward_search_finish (GtkSourceBuffer  *buffer,
+                                        GAsyncResult     *result,
+                                        GtkTextIter      *match_start,
+                                        GtkTextIter      *match_end,
+                                        GError          **error)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
+
+       return _gtk_source_search_forward_finish (buffer->priv->search,
+                                                 result,
+                                                 match_start,
+                                                 match_end,
+                                                 error);
+}
+
+/**
+ * gtk_source_buffer_backward_search:
+ * @buffer: a #GtkSourceBuffer.
+ * @iter: start of search.
+ * @match_start: return location for start of match, or %NULL.
+ * @match_end: return location for end of match, or %NULL.
+ *
+ * Synchronous backward search.
+ *
+ * Returns: whether a match was found.
+ * Since: 3.10
+ */
+gboolean
+gtk_source_buffer_backward_search (GtkSourceBuffer   *buffer,
+                                  const GtkTextIter *iter,
+                                  GtkTextIter       *match_start,
+                                  GtkTextIter       *match_end)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
+       g_return_val_if_fail (iter != NULL, FALSE);
+
+       return _gtk_source_search_backward (buffer->priv->search,
+                                           iter,
+                                           match_start,
+                                           match_end);
+}
+
+/**
+ * gtk_source_buffer_backward_search_async:
+ * @buffer: a #GtkSourceBuffer.
+ * @iter: start of search.
+ * @cancellable: a #GCancellable, or %NULL.
+ * @callback: a #GAsyncReadyCallback to call when the operation is finished.
+ * @user_data: the data to pass to the @callback function.
+ *
+ * Asynchronous backward search. See the #GAsyncResult documentation to know
+ * how to use this function.
+ *
+ * If the operation is cancelled, the @callback will only be called if
+ * @cancellable was not %NULL. gtk_source_buffer_backward_search_async() takes
+ * ownership of @cancellable, so you can unref it after calling this function.
+ *
+ * Since: 3.10
+ */
+void
+gtk_source_buffer_backward_search_async (GtkSourceBuffer     *buffer,
+                                        const GtkTextIter   *iter,
+                                        GCancellable        *cancellable,
+                                        GAsyncReadyCallback  callback,
+                                        gpointer             user_data)
+{
+       g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
+       g_return_if_fail (iter != NULL);
+
+       _gtk_source_search_backward_async (buffer->priv->search,
+                                          iter,
+                                          cancellable,
+                                          callback,
+                                          user_data);
+}
+
+/**
+ * gtk_source_buffer_backward_search_finish:
+ * @buffer: a #GtkSourceBuffer.
+ * @result: a #GAsyncResult.
+ * @match_start: return location for start of match, or %NULL.
+ * @match_end: return location for end of match, or %NULL.
+ * @error: a #GError, or %NULL.
+ *
+ * Finishes a backward search started with
+ * gtk_source_buffer_backward_search_async().
+ *
+ * Returns: whether a match was found.
+ * Since: 3.10
+ */
+gboolean
+gtk_source_buffer_backward_search_finish (GtkSourceBuffer  *buffer,
+                                         GAsyncResult     *result,
+                                         GtkTextIter      *match_start,
+                                         GtkTextIter      *match_end,
+                                         GError          **error)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
+
+       return _gtk_source_search_backward_finish (buffer->priv->search,
+                                                  result,
+                                                  match_start,
+                                                  match_end,
+                                                  error);
+}
+
+/**
+ * gtk_source_buffer_search_replace:
+ * @buffer: a #GtkSourceBuffer.
+ * @match_start: the start of the match to replace.
+ * @match_end: the end of the match to replace.
+ * @replace: the replacement text.
+ * @replace_length: the length of @replace in bytes, or -1.
+ *
+ * Replaces a search match by another text. If @match_start and @match_end
+ * doesn't correspond to a search match, %FALSE is returned.
+ *
+ * Returns: whether the match has been replaced.
+ * Since: 3.10
+ */
+gboolean
+gtk_source_buffer_search_replace (GtkSourceBuffer   *buffer,
+                                 const GtkTextIter *match_start,
+                                 const GtkTextIter *match_end,
+                                 const gchar       *replace,
+                                 gint               replace_length)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
+       g_return_val_if_fail (match_start != NULL, FALSE);
+       g_return_val_if_fail (match_end != NULL, FALSE);
+       g_return_val_if_fail (replace != NULL, FALSE);
+
+       return _gtk_source_search_replace (buffer->priv->search,
+                                          match_start,
+                                          match_end,
+                                          replace,
+                                          replace_length);
+}
+
+/**
+ * gtk_source_buffer_search_replace_all:
+ * @buffer: a #GtkSourceBuffer.
+ * @replace: the replacement text.
+ * @replace_length: the length of @replace in bytes, or -1.
+ *
+ * Replaces all search matches by another text.
+ *
+ * Returns: the number of replaced matches.
+ * Since: 3.10
+ */
+guint
+gtk_source_buffer_search_replace_all (GtkSourceBuffer *buffer,
+                                     const gchar     *replace,
+                                     gint             replace_length)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), 0);
+       g_return_val_if_fail (replace != NULL, 0);
+
+       return _gtk_source_search_replace_all (buffer->priv->search,
+                                              replace,
+                                              replace_length);
+}
diff --git a/gtksourceview/gtksourcebuffer.h b/gtksourceview/gtksourcebuffer.h
index 80b365e..e9fd66e 100644
--- a/gtksourceview/gtksourcebuffer.h
+++ b/gtksourceview/gtksourcebuffer.h
@@ -175,6 +175,78 @@ GtkSourceUndoManager       *gtk_source_buffer_get_undo_manager     (GtkSourceBuffer      
  *buffe
 void                    gtk_source_buffer_set_undo_manager     (GtkSourceBuffer        *buffer,
                                                                 GtkSourceUndoManager   *manager);
 
+void                    gtk_source_buffer_set_search_text                      (GtkSourceBuffer        
*buffer,
+                                                                                const gchar            
*text);
+
+const gchar            *gtk_source_buffer_get_search_text                      (GtkSourceBuffer        
*buffer);
+
+void                    gtk_source_buffer_set_case_sensitive_search            (GtkSourceBuffer        
*buffer,
+                                                                                gboolean                
case_sensitive);
+
+gboolean                gtk_source_buffer_get_case_sensitive_search            (GtkSourceBuffer        
*buffer);
+
+void                    gtk_source_buffer_set_search_at_word_boundaries        (GtkSourceBuffer        
*buffer,
+                                                                                gboolean                
at_word_boundaries);
+
+gboolean                gtk_source_buffer_get_search_at_word_boundaries        (GtkSourceBuffer        
*buffer);
+
+void                    gtk_source_buffer_set_search_wrap_around               (GtkSourceBuffer        
*buffer,
+                                                                                gboolean                
wrap_around);
+
+gboolean                gtk_source_buffer_get_search_wrap_around               (GtkSourceBuffer        
*buffer);
+
+guint                   gtk_source_buffer_get_search_occurrences_count         (GtkSourceBuffer        
*buffer);
+
+gint                    gtk_source_buffer_get_search_occurrence_position       (GtkSourceBuffer        
*buffer,
+                                                                                const GtkTextIter      
*match_start,
+                                                                                const GtkTextIter      
*match_end);
+
+
+gboolean                gtk_source_buffer_forward_search       (GtkSourceBuffer        *buffer,
+                                                                const GtkTextIter      *iter,
+                                                                GtkTextIter            *match_start,
+                                                                GtkTextIter            *match_end);
+
+void                    gtk_source_buffer_forward_search_async (GtkSourceBuffer        *buffer,
+                                                                const GtkTextIter      *iter,
+                                                                GCancellable           *cancellable,
+                                                                GAsyncReadyCallback     callback,
+                                                                gpointer                user_data);
+
+gboolean                gtk_source_buffer_forward_search_finish(GtkSourceBuffer        *buffer,
+                                                                GAsyncResult           *result,
+                                                                GtkTextIter            *match_start,
+                                                                GtkTextIter            *match_end,
+                                                                GError                **error);
+
+gboolean                gtk_source_buffer_backward_search      (GtkSourceBuffer        *buffer,
+                                                                const GtkTextIter      *iter,
+                                                                GtkTextIter            *match_start,
+                                                                GtkTextIter            *match_end);
+
+void                    gtk_source_buffer_backward_search_async(GtkSourceBuffer        *buffer,
+                                                                const GtkTextIter      *iter,
+                                                                GCancellable           *cancellable,
+                                                                GAsyncReadyCallback     callback,
+                                                                gpointer                user_data);
+
+gboolean                gtk_source_buffer_backward_search_finish
+                                                               (GtkSourceBuffer        *buffer,
+                                                                GAsyncResult           *result,
+                                                                GtkTextIter            *match_start,
+                                                                GtkTextIter            *match_end,
+                                                                GError                **error);
+
+gboolean                gtk_source_buffer_search_replace       (GtkSourceBuffer        *buffer,
+                                                                const GtkTextIter      *match_start,
+                                                                const GtkTextIter      *match_end,
+                                                                const gchar            *replace,
+                                                                gint                    replace_length);
+
+guint                   gtk_source_buffer_search_replace_all   (GtkSourceBuffer        *buffer,
+                                                                const gchar            *replace,
+                                                                gint                    replace_length);
+
 /* private */
 void                    _gtk_source_buffer_update_highlight    (GtkSourceBuffer        *buffer,
                                                                 const GtkTextIter      *start,
diff --git a/gtksourceview/gtksourcesearch.c b/gtksourceview/gtksourcesearch.c
new file mode 100644
index 0000000..c512083
--- /dev/null
+++ b/gtksourceview/gtksourcesearch.c
@@ -0,0 +1,2125 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcesearch.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2013 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "gtksourcesearch.h"
+#include "gtksourcebuffer.h"
+#include "gtksourcestylescheme.h"
+#include "gtktextregion.h"
+#include "gtksourcestyle-private.h"
+
+#include <string.h>
+
+/* Implementation overview:
+ *
+ * When the state of the search changes (the text to search or the options), we
+ * have to update the highlighting and the properties values (the number of
+ * occurrences). To do so, a simple solution is to first remove all the
+ * found_tag, so we have a clean buffer to analyze. The problem with this
+ * solution is that there is some flickering when the user modifies the text to
+ * search, because removing all the found_tag's can take some time. So we keep
+ * the old found_tag's, and when we must highlight the matches in a certain
+ * region, we first remove the found_tag's in this region and then we highlight
+ * the newly found matches by applying the found_tag to them.
+ *
+ * If we only want to highlight the matches, without counting the number of
+ * occurrences, a good solution would be to highlight only the visible region of
+ * the buffer on the screen. So it would be useless to always scan all the
+ * buffer.
+ *
+ * But we actually want the number of occurrences! So we have to scan all the
+ * buffer. When the state of the search changes, an idle callback is installed,
+ * which will scan the buffer to highlight the matches. To avoid flickering, the
+ * visible region on the screen is put in a higher priority region to highlight,
+ * so the idle callback will first scan this region.
+ *
+ * Why highlighting the non-visible matches? What we want is to (1) highlight
+ * the visible matches and (2) count the number of occurrences. The code would
+ * indeed be simpler if these two tasks were clearly separated (in two different
+ * idle callbacks, with different regions to scan). With this simpler solution,
+ * we would always use forward_search() and backward_search() to navigate
+ * through the occurrences. But we can do better than that!
+ * forward_to_tag_toggle() and backward_to_tag_toggle() are far more efficient:
+ * once the buffer has been scanned, going to the previous or the next
+ * occurrence is done in O(1). We must just pay attention to contiguous matches.
+ *
+ * While the user is typing the text in the search entry, the buffer is scanned
+ * to count the number of occurrences. And when the user wants to do an
+ * operation (go to the next occurrence for example), chances are that the
+ * buffer has already been scanned entirely, so almost all the operations will
+ * be really fast.
+ *
+ * Extreme example:
+ * <occurrence> [1 GB of text] <next-occurrence>
+ * Once the buffer is scanned, switching between the occurrences will be almost
+ * instantaneous.
+ *
+ * So how to count the number of occurrences then? Remember that the buffer
+ * contents can be modified during the scan, and that we keep the old
+ * found_tag's. Moreover, when we encounter an old found_tag region, in the
+ * general case we can not say how many occurrences there are in this region,
+ * since a found_tag region can contain contiguous matches. Take for example the
+ * found_tag region "aa": was it the "aa" search match, or two times "a"?
+ * The implemented solution is to set occurrences_count to 0 when the search
+ * state changes, even if old matches are still there. Because it is not
+ * possible to count the old matches to decrement occurrences_count (and storing
+ * the previous search text would not be sufficient, because even older matches
+ * can still be there). To increment and decrement occurrences_count, there is
+ * the scan_region, the region to scan. If an occurrence is contained in
+ * scan_region, it means that it has not already been scanned, so
+ * occurrences_count doesn't take into account this occurrence. On the other
+ * hand, if we find an occurrence outside scan_region, the occurrence is
+ * normally correctly highlighted, and occurrences_count take it into account.
+ *
+ * So when we highlight or when we remove the highlight of an occurrence (on
+ * text insertion, deletion, when scanning, etc.), we increment or decrement
+ * occurrences_count depending on whether the occurrence was already taken into
+ * account by occurrences_count.
+ *
+ * If the code seems too complicated and contains strange bugs, you have two
+ * choices:
+ * - Write more unit tests, understand correctly the code and fix it.
+ * - Rewrite the code to implement the simpler solution explained above :-)
+ */
+
+/*
+#define ENABLE_DEBUG
+*/
+#undef ENABLE_DEBUG
+
+#ifdef ENABLE_DEBUG
+#define DEBUG(x) (x)
+#else
+#define DEBUG(x)
+#endif
+
+/* Maximum number of lines to scan in one batch.
+ * A lower value means more overhead when scanning the buffer asynchronously.
+ */
+#define SCAN_BATCH_SIZE 100
+
+struct _GtkSourceSearchPrivate
+{
+       GtkTextBuffer *buffer;
+
+       /* The region to scan and highlight. If NULL, the scan is finished. */
+       GtkTextRegion *scan_region;
+
+       /* The region to scan and highlight in priority. I.e. the visible part
+        * of the buffer on the screen.
+        */
+       GtkTextRegion *high_priority_region;
+
+       /* An asynchronous running task. task_region has a higher priority than
+        * scan_region, but a lower priority than high_priority_region.
+        */
+       GTask *task;
+       GtkTextRegion *task_region;
+
+       gulong idle_scan_id;
+
+       guint occurrences_count;
+
+       GtkTextTag *found_tag;
+
+       /* State of the search. If text is NULL, the search is disabled. */
+       gchar *text;
+       gint text_nb_lines;
+       GtkTextSearchFlags flags;
+       guint at_word_boundaries : 1;
+       guint wrap_around : 1;
+};
+
+/* Data for the asynchronous forward and backward search tasks. */
+typedef struct
+{
+       GtkTextMark *start_at;
+       GtkTextIter match_start;
+       GtkTextIter match_end;
+       guint found : 1;
+       guint wrapped_around : 1;
+
+       /* forward or backward */
+       guint is_forward : 1;
+} ForwardBackwardData;
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceSearch, _gtk_source_search, G_TYPE_OBJECT);
+
+static void            install_idle_scan               (GtkSourceSearch *search);
+
+static gboolean
+dispose_has_run (GtkSourceSearch *search)
+{
+       return search->priv->buffer == NULL;
+}
+
+static void
+sync_found_tag (GtkSourceSearch *search)
+{
+       GtkSourceStyleScheme *style_scheme;
+       GtkSourceStyle *style = NULL;
+
+       if (dispose_has_run (search))
+       {
+               return;
+       }
+
+       style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (search->priv->buffer));
+
+       if (style_scheme != NULL)
+       {
+               style = gtk_source_style_scheme_get_style (style_scheme, "search-match");
+       }
+
+       if (style == NULL)
+       {
+               g_warning ("search-match style not available.");
+       }
+
+       _gtk_source_style_apply (style, search->priv->found_tag);
+}
+
+/* Make sure to call this function when the buffer is constructed, else the tag
+ * table is created too early.
+ */
+static void
+init_found_tag (GtkSourceSearch *search)
+{
+       search->priv->found_tag = gtk_text_buffer_create_tag (search->priv->buffer, NULL, NULL);
+
+       sync_found_tag (search);
+
+       g_signal_connect_object (search->priv->buffer,
+                                "notify::style-scheme",
+                                G_CALLBACK (sync_found_tag),
+                                search,
+                                G_CONNECT_SWAPPED);
+}
+
+static void
+text_tag_set_highest_priority (GtkTextTag    *tag,
+                              GtkTextBuffer *buffer)
+{
+       GtkTextTagTable *table;
+       gint n;
+
+       table = gtk_text_buffer_get_tag_table (buffer);
+       n = gtk_text_tag_table_get_size (table);
+       gtk_text_tag_set_priority (tag, n - 1);
+}
+
+/* A TextRegion can contain empty subregions. So checking the number of
+ * subregions is not sufficient.
+ * When calling gtk_text_region_add() with equal iters, the subregion is not
+ * added. But when a subregion becomes empty, due to text deletion, the
+ * subregion is not removed from the TextRegion.
+ */
+static gboolean
+is_text_region_empty (GtkTextRegion *region)
+{
+       GtkTextRegionIterator region_iter;
+
+       if (region == NULL)
+       {
+               return TRUE;
+       }
+
+       gtk_text_region_get_iterator (region, &region_iter, 0);
+
+       while (!gtk_text_region_iterator_is_end (&region_iter))
+       {
+               GtkTextIter region_start;
+               GtkTextIter region_end;
+
+               gtk_text_region_iterator_get_subregion (&region_iter,
+                                                       &region_start,
+                                                       &region_end);
+
+               if (!gtk_text_iter_equal (&region_start, &region_end))
+               {
+                       return FALSE;
+               }
+
+               gtk_text_region_iterator_next (&region_iter);
+       }
+
+       return TRUE;
+}
+
+/* Sets @start and @end to the first non-empty subregion.
+ * Returns FALSE if the region is empty.
+ */
+static gboolean
+get_first_subregion (GtkTextRegion *region,
+                    GtkTextIter   *start,
+                    GtkTextIter   *end)
+{
+       GtkTextRegionIterator region_iter;
+
+       if (region == NULL)
+       {
+               return FALSE;
+       }
+
+       gtk_text_region_get_iterator (region, &region_iter, 0);
+
+       while (!gtk_text_region_iterator_is_end (&region_iter))
+       {
+               gtk_text_region_iterator_get_subregion (&region_iter, start, end);
+
+               if (!gtk_text_iter_equal (start, end))
+               {
+                       return TRUE;
+               }
+
+               gtk_text_region_iterator_next (&region_iter);
+       }
+
+       return FALSE;
+}
+
+/* Sets @start and @end to the last non-empty subregion.
+ * Returns FALSE if the region is empty.
+ */
+static gboolean
+get_last_subregion (GtkTextRegion *region,
+                   GtkTextIter   *start,
+                   GtkTextIter   *end)
+{
+       GtkTextRegionIterator region_iter;
+       gboolean found = FALSE;
+
+       if (region == NULL)
+       {
+               return FALSE;
+       }
+
+       gtk_text_region_get_iterator (region, &region_iter, 0);
+
+       while (!gtk_text_region_iterator_is_end (&region_iter))
+       {
+               GtkTextIter start_subregion;
+               GtkTextIter end_subregion;
+
+               gtk_text_region_iterator_get_subregion (&region_iter,
+                                                       &start_subregion,
+                                                       &end_subregion);
+
+               if (!gtk_text_iter_equal (&start_subregion, &end_subregion))
+               {
+                       found = TRUE;
+                       *start = start_subregion;
+                       *end = end_subregion;
+               }
+
+               gtk_text_region_iterator_next (&region_iter);
+       }
+
+       return found;
+}
+
+static void
+clear_task (GtkSourceSearch *search)
+{
+       if (search->priv->task_region != NULL)
+       {
+               gtk_text_region_destroy (search->priv->task_region, TRUE);
+               search->priv->task_region = NULL;
+       }
+
+       if (search->priv->task != NULL)
+       {
+               GCancellable *cancellable = g_task_get_cancellable (search->priv->task);
+
+               if (cancellable != NULL)
+               {
+                       g_cancellable_cancel (cancellable);
+                       g_task_return_error_if_cancelled (search->priv->task);
+               }
+
+               g_clear_object (&search->priv->task);
+       }
+}
+
+static void
+clear_search (GtkSourceSearch *search)
+{
+       if (search->priv->scan_region != NULL)
+       {
+               gtk_text_region_destroy (search->priv->scan_region, TRUE);
+               search->priv->scan_region = NULL;
+       }
+
+       if (search->priv->high_priority_region != NULL)
+       {
+               gtk_text_region_destroy (search->priv->high_priority_region, TRUE);
+               search->priv->high_priority_region = NULL;
+       }
+
+       if (search->priv->idle_scan_id != 0)
+       {
+               g_source_remove (search->priv->idle_scan_id);
+               search->priv->idle_scan_id = 0;
+       }
+
+       clear_task (search);
+
+       search->priv->occurrences_count = 0;
+}
+
+static gboolean
+basic_forward_search (GtkSourceSearch   *search,
+                     const GtkTextIter *iter,
+                     GtkTextIter       *match_start,
+                     GtkTextIter       *match_end,
+                     const GtkTextIter *limit)
+{
+       GtkTextIter begin_search = *iter;
+
+       if (search->priv->text == NULL)
+       {
+               return FALSE;
+       }
+
+       while (TRUE)
+       {
+               gboolean found = gtk_text_iter_forward_search (&begin_search,
+                                                              search->priv->text,
+                                                              search->priv->flags,
+                                                              match_start,
+                                                              match_end,
+                                                              limit);
+
+               if (!found || !search->priv->at_word_boundaries)
+               {
+                       return found;
+               }
+
+               if (gtk_text_iter_starts_word (match_start) &&
+                   gtk_text_iter_ends_word (match_end))
+               {
+                       return TRUE;
+               }
+
+               begin_search = *match_end;
+       }
+}
+
+static gboolean
+basic_backward_search (GtkSourceSearch   *search,
+                      const GtkTextIter *iter,
+                      GtkTextIter       *match_start,
+                      GtkTextIter       *match_end,
+                      const GtkTextIter *limit)
+{
+       GtkTextIter begin_search = *iter;
+
+       if (search->priv->text == NULL)
+       {
+               return FALSE;
+       }
+
+       while (TRUE)
+       {
+               gboolean found = gtk_text_iter_backward_search (&begin_search,
+                                                               search->priv->text,
+                                                               search->priv->flags,
+                                                               match_start,
+                                                               match_end,
+                                                               limit);
+
+               if (!found || !search->priv->at_word_boundaries)
+               {
+                       return found;
+               }
+
+               if (gtk_text_iter_starts_word (match_start) &&
+                   gtk_text_iter_ends_word (match_end))
+               {
+                       return TRUE;
+               }
+
+               begin_search = *match_start;
+       }
+}
+
+static void
+forward_backward_data_free (ForwardBackwardData *data)
+{
+       if (data->start_at != NULL)
+       {
+               GtkTextBuffer *buffer = gtk_text_mark_get_buffer (data->start_at);
+               gtk_text_buffer_delete_mark (buffer, data->start_at);
+       }
+
+       g_slice_free (ForwardBackwardData, data);
+}
+
+/* Returns TRUE if finished. */
+static gboolean
+smart_forward_search_async_step (GtkSourceSearch *search,
+                                GtkTextIter     *start_at,
+                                gboolean        *wrapped_around)
+{
+       GtkTextIter iter = *start_at;
+       GtkTextIter limit;
+       GtkTextRegion *region = NULL;
+       ForwardBackwardData *task_data;
+
+       if (gtk_text_iter_is_end (start_at))
+       {
+               if (search->priv->text != NULL &&
+                   !*wrapped_around &&
+                   search->priv->wrap_around)
+               {
+                       gtk_text_buffer_get_start_iter (search->priv->buffer, start_at);
+                       *wrapped_around = TRUE;
+                       return FALSE;
+               }
+
+               task_data = g_slice_new0 (ForwardBackwardData);
+               task_data->found = FALSE;
+               task_data->is_forward = TRUE;
+               task_data->wrapped_around = *wrapped_around;
+
+               g_task_return_pointer (search->priv->task,
+                                      task_data,
+                                      (GDestroyNotify)forward_backward_data_free);
+
+               g_clear_object (&search->priv->task);
+               return TRUE;
+       }
+
+       if (!gtk_text_iter_has_tag (&iter, search->priv->found_tag))
+       {
+               gtk_text_iter_forward_to_tag_toggle (&iter, search->priv->found_tag);
+       }
+
+       limit = iter;
+       gtk_text_iter_forward_to_tag_toggle (&limit, search->priv->found_tag);
+
+       if (search->priv->scan_region != NULL)
+       {
+               region = gtk_text_region_intersect (search->priv->scan_region, start_at, &limit);
+       }
+
+       if (is_text_region_empty (region))
+       {
+               GtkTextIter match_start;
+               GtkTextIter match_end;
+               gboolean found;
+
+               found = basic_forward_search (search, &iter, &match_start, &match_end, &limit);
+
+               if (region != NULL)
+               {
+                       gtk_text_region_destroy (region, TRUE);
+               }
+
+               if (found)
+               {
+                       task_data = g_slice_new0 (ForwardBackwardData);
+                       task_data->found = TRUE;
+                       task_data->match_start = match_start;
+                       task_data->match_end = match_end;
+                       task_data->is_forward = TRUE;
+                       task_data->wrapped_around = *wrapped_around;
+
+                       g_task_return_pointer (search->priv->task,
+                                              task_data,
+                                              (GDestroyNotify)forward_backward_data_free);
+
+                       g_clear_object (&search->priv->task);
+                       return TRUE;
+               }
+
+               *start_at = limit;
+               return FALSE;
+       }
+
+       task_data = g_slice_new0 (ForwardBackwardData);
+       task_data->is_forward = TRUE;
+       task_data->wrapped_around = *wrapped_around;
+       task_data->start_at = gtk_text_buffer_create_mark (search->priv->buffer,
+                                                          NULL,
+                                                          start_at,
+                                                          TRUE);
+
+       g_task_set_task_data (search->priv->task,
+                             task_data,
+                             (GDestroyNotify)forward_backward_data_free);
+
+       if (search->priv->task_region != NULL)
+       {
+               gtk_text_region_destroy (search->priv->task_region, TRUE);
+       }
+
+       search->priv->task_region = region;
+
+       install_idle_scan (search);
+
+       /* The idle that scan the task region will call
+        * smart_forward_search_async() to continue the task. But for the
+        * moment, we are done.
+        */
+       return TRUE;
+}
+
+static void
+smart_forward_search_async (GtkSourceSearch   *search,
+                           const GtkTextIter *start_at,
+                           gboolean           wrapped_around)
+{
+       GtkTextIter iter = *start_at;
+
+       if (search->priv->found_tag == NULL)
+       {
+               init_found_tag (search);
+       }
+
+       /* A recursive function would have been more natural, but a loop is
+        * better to avoid stack overflows.
+        */
+       while (!smart_forward_search_async_step (search, &iter, &wrapped_around));
+}
+
+
+/* Returns TRUE if finished. */
+static gboolean
+smart_backward_search_async_step (GtkSourceSearch *search,
+                                 GtkTextIter     *start_at,
+                                 gboolean        *wrapped_around)
+{
+       GtkTextIter iter = *start_at;
+       GtkTextIter limit;
+       GtkTextRegion *region = NULL;
+       ForwardBackwardData *task_data;
+
+       if (gtk_text_iter_is_start (start_at))
+       {
+               if (search->priv->text != NULL &&
+                   !*wrapped_around &&
+                   search->priv->wrap_around)
+               {
+                       gtk_text_buffer_get_end_iter (search->priv->buffer, start_at);
+                       *wrapped_around = TRUE;
+                       return FALSE;
+               }
+
+               task_data = g_slice_new0 (ForwardBackwardData);
+               task_data->found = FALSE;
+               task_data->is_forward = FALSE;
+               task_data->wrapped_around = *wrapped_around;
+
+               g_task_return_pointer (search->priv->task,
+                                      task_data,
+                                      (GDestroyNotify)forward_backward_data_free);
+
+               g_clear_object (&search->priv->task);
+               return TRUE;
+       }
+
+       if (gtk_text_iter_begins_tag (&iter, search->priv->found_tag) ||
+           (!gtk_text_iter_has_tag (&iter, search->priv->found_tag) &&
+            !gtk_text_iter_ends_tag (&iter, search->priv->found_tag)))
+       {
+               gtk_text_iter_backward_to_tag_toggle (&iter, search->priv->found_tag);
+       }
+
+       limit = iter;
+       gtk_text_iter_backward_to_tag_toggle (&limit, search->priv->found_tag);
+
+       if (search->priv->scan_region != NULL)
+       {
+               region = gtk_text_region_intersect (search->priv->scan_region, &limit, start_at);
+       }
+
+       if (is_text_region_empty (region))
+       {
+               GtkTextIter match_start;
+               GtkTextIter match_end;
+               gboolean found;
+
+               found = basic_backward_search (search, &iter, &match_start, &match_end, &limit);
+
+               if (region != NULL)
+               {
+                       gtk_text_region_destroy (region, TRUE);
+               }
+
+               if (found)
+               {
+                       task_data = g_slice_new0 (ForwardBackwardData);
+                       task_data->found = TRUE;
+                       task_data->match_start = match_start;
+                       task_data->match_end = match_end;
+                       task_data->is_forward = FALSE;
+                       task_data->wrapped_around = *wrapped_around;
+
+                       g_task_return_pointer (search->priv->task,
+                                              task_data,
+                                              (GDestroyNotify)forward_backward_data_free);
+
+                       g_clear_object (&search->priv->task);
+                       return TRUE;
+               }
+
+               *start_at = limit;
+               return FALSE;
+       }
+
+       task_data = g_slice_new0 (ForwardBackwardData);
+       task_data->is_forward = FALSE;
+       task_data->wrapped_around = *wrapped_around;
+       task_data->start_at = gtk_text_buffer_create_mark (search->priv->buffer,
+                                                          NULL,
+                                                          start_at,
+                                                          TRUE);
+
+       g_task_set_task_data (search->priv->task,
+                             task_data,
+                             (GDestroyNotify)forward_backward_data_free);
+
+       if (search->priv->task_region != NULL)
+       {
+               gtk_text_region_destroy (search->priv->task_region, TRUE);
+       }
+
+       search->priv->task_region = region;
+
+       install_idle_scan (search);
+
+       /* The idle that scan the task region will call
+        * smart_backward_search_async() to continue the task. But for the
+        * moment, we are done.
+        */
+       return TRUE;
+}
+
+static void
+smart_backward_search_async (GtkSourceSearch   *search,
+                            const GtkTextIter *start_at,
+                            gboolean           wrapped_around)
+{
+       GtkTextIter iter = *start_at;
+
+       if (search->priv->found_tag == NULL)
+       {
+               init_found_tag (search);
+       }
+
+       /* A recursive function would have been more natural, but a loop is
+        * better to avoid stack overflows.
+        */
+       while (!smart_backward_search_async_step (search, &iter, &wrapped_around));
+}
+
+/* Adjust the subregion so we are sure that all matches that are visible or
+ * partially visible between @start and @end are highlighted.
+ */
+static void
+adjust_subregion (GtkSourceSearch *search,
+                 GtkTextIter     *start,
+                 GtkTextIter     *end)
+{
+       DEBUG ({
+               g_print ("adjust_subregion(), before adjusting: [%u (%u), %u (%u)]\n",
+                        gtk_text_iter_get_line (start), gtk_text_iter_get_offset (start),
+                        gtk_text_iter_get_line (end), gtk_text_iter_get_offset (end));
+       });
+
+       gtk_text_iter_backward_lines (start, MAX (0, search->priv->text_nb_lines - 1));
+       gtk_text_iter_forward_lines (end, MAX (0, search->priv->text_nb_lines - 1));
+
+       if (!gtk_text_iter_starts_line (start))
+       {
+               gtk_text_iter_set_line_offset (start, 0);
+       }
+
+       if (!gtk_text_iter_ends_line (end))
+       {
+               gtk_text_iter_forward_to_line_end (end);
+       }
+
+       /* When we are in the middle of a found_tag, a simple solution is to
+        * always backward_to_tag_toggle(). The problem is that occurrences can
+        * be contiguous. So a full scan of the buffer can have a O(n^2) in the
+        * worst case, if we use the simple solution. Therefore we use a more
+        * complicated solution, that checks if we are in an old found_tag or
+        * not.
+        */
+
+       if (gtk_text_iter_has_tag (start, search->priv->found_tag))
+       {
+               if (is_text_region_empty (search->priv->scan_region))
+               {
+                       /* 'start' is in a correct match, we can skip it. */
+                       gtk_text_iter_forward_to_tag_toggle (start, search->priv->found_tag);
+               }
+               else
+               {
+                       GtkTextIter tag_start = *start;
+                       GtkTextIter tag_end = *start;
+                       GtkTextRegion *region;
+
+                       if (!gtk_text_iter_begins_tag (&tag_start, search->priv->found_tag))
+                       {
+                               gtk_text_iter_backward_to_tag_toggle (&tag_start, search->priv->found_tag);
+                       }
+
+                       gtk_text_iter_forward_to_tag_toggle (&tag_end, search->priv->found_tag);
+
+                       region = gtk_text_region_intersect (search->priv->scan_region,
+                                                           &tag_start,
+                                                           &tag_end);
+
+                       if (is_text_region_empty (region))
+                       {
+                               /* 'region' has already been scanned, so 'start' is in a
+                                * correct match, we can skip it.
+                                */
+                               *start = tag_end;
+                       }
+                       else
+                       {
+                               /* 'region' has not already been scanned completely, so
+                                * 'start' is most probably in an old match that must be
+                                * removed.
+                                */
+                               *start = tag_start;
+                       }
+
+                       if (region != NULL)
+                       {
+                               gtk_text_region_destroy (region, TRUE);
+                       }
+               }
+       }
+
+       /* Symmetric for 'end'. */
+
+       if (gtk_text_iter_has_tag (end, search->priv->found_tag))
+       {
+               if (is_text_region_empty (search->priv->scan_region))
+               {
+                       /* 'end' is in a correct match, we can skip it. */
+
+                       if (!gtk_text_iter_begins_tag (end, search->priv->found_tag))
+                       {
+                               gtk_text_iter_backward_to_tag_toggle (end, search->priv->found_tag);
+                       }
+               }
+               else
+               {
+                       GtkTextIter tag_start = *end;
+                       GtkTextIter tag_end = *end;
+                       GtkTextRegion *region;
+
+                       if (!gtk_text_iter_begins_tag (&tag_start, search->priv->found_tag))
+                       {
+                               gtk_text_iter_backward_to_tag_toggle (&tag_start, search->priv->found_tag);
+                       }
+
+                       gtk_text_iter_forward_to_tag_toggle (&tag_end, search->priv->found_tag);
+
+                       region = gtk_text_region_intersect (search->priv->scan_region,
+                                                           &tag_start,
+                                                           &tag_end);
+
+                       if (is_text_region_empty (region))
+                       {
+                               /* 'region' has already been scanned, so 'end' is in a
+                                * correct match, we can skip it.
+                                */
+                               *end = tag_start;
+                       }
+                       else
+                       {
+                               /* 'region' has not already been scanned completely, so
+                                * 'end' is most probably in an old match that must be
+                                * removed.
+                                */
+                               *end = tag_end;
+                       }
+
+                       if (region != NULL)
+                       {
+                               gtk_text_region_destroy (region, TRUE);
+                       }
+               }
+       }
+
+       DEBUG ({
+               g_print ("adjust_subregion(), after adjusting: [%u (%u), %u (%u)]\n",
+                        gtk_text_iter_get_line (start), gtk_text_iter_get_offset (start),
+                        gtk_text_iter_get_line (end), gtk_text_iter_get_offset (end));
+       });
+}
+
+/* Do not take into account the scan_region. Search just with
+ * forward_to_tag_toggle(), and checks that it is not an old match.
+ */
+static gboolean
+smart_forward_search_without_scanning (GtkSourceSearch   *search,
+                                      const GtkTextIter *start_at,
+                                      GtkTextIter       *match_start,
+                                      GtkTextIter       *match_end,
+                                      const GtkTextIter *stop_at)
+{
+       GtkTextIter iter = *start_at;
+
+       g_assert (start_at != NULL);
+       g_assert (stop_at != NULL);
+
+       if (search->priv->text == NULL)
+       {
+               return FALSE;
+       }
+
+       if (search->priv->found_tag == NULL)
+       {
+               init_found_tag (search);
+       }
+
+       while (gtk_text_iter_compare (&iter, stop_at) < 0)
+       {
+               GtkTextIter limit;
+
+               if (!gtk_text_iter_has_tag (&iter, search->priv->found_tag))
+               {
+                       gtk_text_iter_forward_to_tag_toggle (&iter, search->priv->found_tag);
+               }
+
+               limit = iter;
+               gtk_text_iter_forward_to_tag_toggle (&limit, search->priv->found_tag);
+
+               if (gtk_text_iter_compare (stop_at, &limit) < 0)
+               {
+                       limit = *stop_at;
+               }
+
+               if (basic_forward_search (search, &iter, match_start, match_end, &limit))
+               {
+                       return TRUE;
+               }
+
+               iter = limit;
+       }
+
+       return FALSE;
+}
+
+/* Remove the occurrences in the range. @start and @end may be adjusted, if they
+ * are in a found_tag region.
+ */
+static void
+remove_occurrences_in_range (GtkSourceSearch *search,
+                            GtkTextIter     *start,
+                            GtkTextIter     *end)
+{
+       GtkTextIter iter;
+       GtkTextIter match_start;
+       GtkTextIter match_end;
+
+       if (search->priv->found_tag == NULL)
+       {
+               init_found_tag (search);
+       }
+
+       if (gtk_text_iter_has_tag (start, search->priv->found_tag) &&
+           !gtk_text_iter_begins_tag (start, search->priv->found_tag))
+       {
+               gtk_text_iter_backward_to_tag_toggle (start, search->priv->found_tag);
+       }
+
+       if (gtk_text_iter_has_tag (end, search->priv->found_tag) &&
+           !gtk_text_iter_begins_tag (end, search->priv->found_tag))
+       {
+               gtk_text_iter_forward_to_tag_toggle (end, search->priv->found_tag);
+       }
+
+       iter = *start;
+
+       while (smart_forward_search_without_scanning (search, &iter, &match_start, &match_end, end))
+       {
+               if (search->priv->scan_region == NULL)
+               {
+                       /* The occurrence has already been scanned, and thus
+                        * occurrence_count take it into account. */
+                       search->priv->occurrences_count--;
+               }
+               else
+               {
+                       GtkTextRegion *region = gtk_text_region_intersect (search->priv->scan_region,
+                                                                          &match_start,
+                                                                          &match_end);
+
+                       if (is_text_region_empty (region))
+                       {
+                               search->priv->occurrences_count--;
+                       }
+
+                       if (region != NULL)
+                       {
+                               gtk_text_region_destroy (region, TRUE);
+                       }
+               }
+
+               iter = match_end;
+       }
+
+       gtk_text_buffer_remove_tag (search->priv->buffer,
+                                   search->priv->found_tag,
+                                   start,
+                                   end);
+}
+
+static void
+scan_subregion (GtkSourceSearch *search,
+               GtkTextIter     *start,
+               GtkTextIter     *end)
+{
+       GtkTextIter iter;
+       GtkTextIter *limit;
+       gboolean found = TRUE;
+
+       if (search->priv->found_tag == NULL)
+       {
+               init_found_tag (search);
+       }
+
+       /* Make sure the 'found' tag has the priority over syntax highlighting
+        * tags. */
+       text_tag_set_highest_priority (search->priv->found_tag,
+                                      search->priv->buffer);
+
+       adjust_subregion (search, start, end);
+       remove_occurrences_in_range (search, start, end);
+
+       if (search->priv->scan_region != NULL)
+       {
+               DEBUG ({
+                       g_print ("Region to scan, before:\n");
+                       gtk_text_region_debug_print (search->priv->scan_region);
+               });
+
+               gtk_text_region_subtract (search->priv->scan_region, start, end);
+
+               DEBUG ({
+                       g_print ("Region to scan, after:\n");
+                       gtk_text_region_debug_print (search->priv->scan_region);
+               });
+       }
+
+       if (search->priv->task_region != NULL)
+       {
+               gtk_text_region_subtract (search->priv->task_region, start, end);
+       }
+
+       if (search->priv->text == NULL)
+       {
+               /* We have removed the found_tag, we are done. */
+               return;
+       }
+
+       iter = *start;
+
+       if (gtk_text_iter_is_end (end))
+       {
+               limit = NULL;
+       }
+       else
+       {
+               limit = end;
+       }
+
+       do
+       {
+               GtkTextIter match_start;
+               GtkTextIter match_end;
+
+               found = basic_forward_search (search, &iter, &match_start, &match_end, limit);
+
+               if (found)
+               {
+                       gtk_text_buffer_apply_tag (search->priv->buffer,
+                                                  search->priv->found_tag,
+                                                  &match_start,
+                                                  &match_end);
+
+                       search->priv->occurrences_count++;
+               }
+
+               iter = match_end;
+
+       } while (found);
+}
+
+static void
+scan_all_region (GtkSourceSearch *search,
+                GtkTextRegion   *region_to_highlight)
+{
+       gint nb_subregions = gtk_text_region_subregions (region_to_highlight);
+       GtkTextIter start_search;
+       GtkTextIter end_search;
+
+       if (nb_subregions == 0)
+       {
+               return;
+       }
+
+       gtk_text_region_nth_subregion (region_to_highlight,
+                                      0,
+                                      &start_search,
+                                      NULL);
+
+       gtk_text_region_nth_subregion (region_to_highlight,
+                                      nb_subregions - 1,
+                                      NULL,
+                                      &end_search);
+
+       gtk_text_iter_order (&start_search, &end_search);
+
+       scan_subregion (search, &start_search, &end_search);
+}
+
+/* Scan a chunk of the region. If the region is small enough, all the region
+ * will be scanned. But if the region is big, scanning only the chunk will not
+ * block the UI normally. Begin the scan at the beginning of the region.
+ */
+static void
+scan_region_forward (GtkSourceSearch *search,
+                    GtkTextRegion   *region)
+{
+       gint nb_remaining_lines = SCAN_BATCH_SIZE;
+       GtkTextIter start;
+       GtkTextIter end;
+
+       while (nb_remaining_lines > 0 &&
+              get_first_subregion (region, &start, &end))
+       {
+               GtkTextIter limit = start;
+               gint start_line;
+               gint limit_line;
+
+               gtk_text_iter_forward_lines (&limit, nb_remaining_lines);
+
+               if (gtk_text_iter_compare (&end, &limit) < 0)
+               {
+                       limit = end;
+               }
+
+               scan_subregion (search, &start, &limit);
+
+               start_line = gtk_text_iter_get_line (&start);
+               limit_line = gtk_text_iter_get_line (&limit);
+
+               nb_remaining_lines -= limit_line - start_line;
+       }
+}
+
+/* Same as scan_region_forward(), but begins the scan at the end of the region. */
+static void
+scan_region_backward (GtkSourceSearch *search,
+                     GtkTextRegion   *region)
+{
+       gint nb_remaining_lines = SCAN_BATCH_SIZE;
+       GtkTextIter start;
+       GtkTextIter end;
+
+       while (nb_remaining_lines > 0 &&
+              get_last_subregion (region, &start, &end))
+       {
+               GtkTextIter limit = end;
+               gint limit_line;
+               gint end_line;
+
+               gtk_text_iter_backward_lines (&limit, nb_remaining_lines);
+
+               if (gtk_text_iter_compare (&limit, &start) < 0)
+               {
+                       limit = start;
+               }
+
+               scan_subregion (search, &limit, &end);
+
+               limit_line = gtk_text_iter_get_line (&limit);
+               end_line = gtk_text_iter_get_line (&end);
+
+               nb_remaining_lines -= end_line - limit_line;
+       }
+}
+
+static void
+scan_task_region (GtkSourceSearch *search)
+{
+       ForwardBackwardData *task_data = g_task_get_task_data (search->priv->task);
+       GtkTextIter start_at;
+
+       if (task_data->is_forward)
+       {
+               scan_region_forward (search, search->priv->task_region);
+       }
+       else
+       {
+               scan_region_backward (search, search->priv->task_region);
+       }
+
+       if (search->priv->task_region != NULL)
+       {
+               gtk_text_region_destroy (search->priv->task_region, TRUE);
+               search->priv->task_region = NULL;
+       }
+
+       gtk_text_buffer_get_iter_at_mark (search->priv->buffer,
+                                         &start_at,
+                                         task_data->start_at);
+
+       if (task_data->is_forward)
+       {
+               smart_forward_search_async (search,
+                                           &start_at,
+                                           task_data->wrapped_around);
+       }
+       else
+       {
+               smart_backward_search_async (search,
+                                            &start_at,
+                                            task_data->wrapped_around);
+       }
+}
+
+static gboolean
+idle_scan_cb (GtkSourceSearch *search)
+{
+       if (search->priv->high_priority_region != NULL)
+       {
+               /* Normally the high priority region is not really big, since it
+                * is the visible area on the screen. So we can highlight it in
+                * one batch.
+                */
+               scan_all_region (search, search->priv->high_priority_region);
+
+               gtk_text_region_destroy (search->priv->high_priority_region, TRUE);
+               search->priv->high_priority_region = NULL;
+
+               return G_SOURCE_CONTINUE;
+       }
+
+       if (search->priv->task_region != NULL)
+       {
+               scan_task_region (search);
+               return G_SOURCE_CONTINUE;
+       }
+
+       scan_region_forward (search, search->priv->scan_region);
+
+       if (is_text_region_empty (search->priv->scan_region))
+       {
+               search->priv->idle_scan_id = 0;
+
+               g_object_notify (G_OBJECT (search->priv->buffer), "search-occurrences-count");
+
+               if (search->priv->scan_region != NULL)
+               {
+                       gtk_text_region_destroy (search->priv->scan_region, TRUE);
+                       search->priv->scan_region = NULL;
+               }
+
+               return G_SOURCE_REMOVE;
+       }
+
+       return G_SOURCE_CONTINUE;
+}
+
+static void
+install_idle_scan (GtkSourceSearch *search)
+{
+       if (search->priv->idle_scan_id == 0)
+       {
+               search->priv->idle_scan_id = g_idle_add ((GSourceFunc)idle_scan_cb, search);
+       }
+}
+
+/* Returns TRUE when finished. */
+static gboolean
+smart_forward_search_step (GtkSourceSearch *search,
+                          GtkTextIter     *start_at,
+                          GtkTextIter     *match_start,
+                          GtkTextIter     *match_end)
+{
+       GtkTextIter iter = *start_at;
+       GtkTextIter limit;
+       GtkTextRegion *region = NULL;
+
+       if (!gtk_text_iter_has_tag (&iter, search->priv->found_tag))
+       {
+               gtk_text_iter_forward_to_tag_toggle (&iter, search->priv->found_tag);
+       }
+
+       limit = iter;
+       gtk_text_iter_forward_to_tag_toggle (&limit, search->priv->found_tag);
+
+       if (search->priv->scan_region != NULL)
+       {
+               region = gtk_text_region_intersect (search->priv->scan_region, start_at, &limit);
+       }
+
+       if (is_text_region_empty (region))
+       {
+               gboolean found = basic_forward_search (search, &iter, match_start, match_end, &limit);
+
+               if (region != NULL)
+               {
+                       gtk_text_region_destroy (region, TRUE);
+               }
+
+               if (found)
+               {
+                       return TRUE;
+               }
+
+               *start_at = limit;
+               return FALSE;
+       }
+
+       scan_all_region (search, region);
+       gtk_text_region_destroy (region, TRUE);
+       return FALSE;
+}
+
+/* Doesn't wrap around. */
+static gboolean
+smart_forward_search (GtkSourceSearch   *search,
+                     const GtkTextIter *start_at,
+                     GtkTextIter       *match_start,
+                     GtkTextIter       *match_end)
+{
+       GtkTextIter iter = *start_at;
+
+       if (search->priv->text == NULL)
+       {
+               return FALSE;
+       }
+
+       if (search->priv->found_tag == NULL)
+       {
+               init_found_tag (search);
+       }
+
+       while (!gtk_text_iter_is_end (&iter))
+       {
+               if (smart_forward_search_step (search, &iter, match_start, match_end))
+               {
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+/* Returns TRUE when finished. */
+static gboolean
+smart_backward_search_step (GtkSourceSearch *search,
+                           GtkTextIter     *start_at,
+                           GtkTextIter     *match_start,
+                           GtkTextIter     *match_end)
+{
+       GtkTextIter iter = *start_at;
+       GtkTextIter limit;
+       GtkTextRegion *region = NULL;
+
+       if (gtk_text_iter_begins_tag (&iter, search->priv->found_tag) ||
+           (!gtk_text_iter_has_tag (&iter, search->priv->found_tag) &&
+            !gtk_text_iter_ends_tag (&iter, search->priv->found_tag)))
+       {
+               gtk_text_iter_backward_to_tag_toggle (&iter, search->priv->found_tag);
+       }
+
+       limit = iter;
+       gtk_text_iter_backward_to_tag_toggle (&limit, search->priv->found_tag);
+
+       if (search->priv->scan_region != NULL)
+       {
+               region = gtk_text_region_intersect (search->priv->scan_region, &limit, start_at);
+       }
+
+       if (is_text_region_empty (region))
+       {
+               gboolean found = basic_backward_search (search, &iter, match_start, match_end, &limit);
+
+               if (region != NULL)
+               {
+                       gtk_text_region_destroy (region, TRUE);
+               }
+
+               if (found)
+               {
+                       return TRUE;
+               }
+
+               *start_at = limit;
+               return FALSE;
+       }
+
+       scan_all_region (search, region);
+       gtk_text_region_destroy (region, TRUE);
+       return FALSE;
+}
+
+/* Doesn't wrap around. */
+static gboolean
+smart_backward_search (GtkSourceSearch   *search,
+                      const GtkTextIter *start_at,
+                      GtkTextIter       *match_start,
+                      GtkTextIter       *match_end)
+{
+       GtkTextIter iter = *start_at;
+
+       if (search->priv->text == NULL)
+       {
+               return FALSE;
+       }
+
+       if (search->priv->found_tag == NULL)
+       {
+               init_found_tag (search);
+       }
+
+       while (!gtk_text_iter_is_start (&iter))
+       {
+               if (smart_backward_search_step (search, &iter, match_start, match_end))
+               {
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+static void
+add_subregion_to_scan (GtkSourceSearch   *search,
+                      const GtkTextIter *subregion_start,
+                      const GtkTextIter *subregion_end)
+{
+       GtkTextIter start = *subregion_start;
+       GtkTextIter end = *subregion_end;
+
+       if (search->priv->scan_region == NULL)
+       {
+               search->priv->scan_region = gtk_text_region_new (search->priv->buffer);
+       }
+
+       DEBUG ({
+               g_print ("add_subregion_to_scan(): region to scan, before:\n");
+               gtk_text_region_debug_print (search->priv->scan_region);
+       });
+
+       gtk_text_region_add (search->priv->scan_region, &start, &end);
+
+       DEBUG ({
+               g_print ("add_subregion_to_scan(): region to scan, after:\n");
+               gtk_text_region_debug_print (search->priv->scan_region);
+       });
+
+       install_idle_scan (search);
+
+       /* The highlighting can be modified a bit backward and forward the
+        * region.
+        */
+       gtk_text_iter_backward_lines (&start, search->priv->text_nb_lines);
+       gtk_text_iter_forward_lines (&end, search->priv->text_nb_lines);
+
+       g_signal_emit_by_name (search->priv->buffer, "highlight-updated", &start, &end);
+}
+
+static void
+update (GtkSourceSearch *search)
+{
+       GtkTextIter start;
+       GtkTextIter end;
+
+       if (dispose_has_run (search))
+       {
+               return;
+       }
+
+       clear_search (search);
+
+       search->priv->scan_region = gtk_text_region_new (search->priv->buffer);
+
+       gtk_text_buffer_get_bounds (search->priv->buffer, &start, &end);
+       add_subregion_to_scan (search, &start, &end);
+}
+
+static void
+insert_text_before_cb (GtkSourceSearch *search,
+                      GtkTextIter     *location,
+                      gchar           *text,
+                      gint             length)
+{
+       clear_task (search);
+
+       if (search->priv->text != NULL)
+       {
+               GtkTextIter start = *location;
+               GtkTextIter end = *location;
+
+               remove_occurrences_in_range (search, &start, &end);
+               add_subregion_to_scan (search, &start, &end);
+       }
+}
+
+static void
+insert_text_after_cb (GtkSourceSearch *search,
+                     GtkTextIter     *location,
+                     gchar           *text,
+                     gint             length)
+{
+       GtkTextIter start;
+       GtkTextIter end;
+
+       start = end = *location;
+
+       gtk_text_iter_backward_chars (&start,
+                                     g_utf8_strlen (text, length));
+
+       add_subregion_to_scan (search, &start, &end);
+}
+
+static void
+delete_range_before_cb (GtkSourceSearch *search,
+                       GtkTextIter     *delete_start,
+                       GtkTextIter     *delete_end)
+{
+       GtkTextIter start_buffer;
+       GtkTextIter end_buffer;
+
+       clear_task (search);
+
+       gtk_text_buffer_get_bounds (search->priv->buffer, &start_buffer, &end_buffer);
+
+       if (gtk_text_iter_equal (delete_start, &start_buffer) &&
+           gtk_text_iter_equal (delete_end, &end_buffer))
+       {
+               /* Special case when removing all the text. */
+               search->priv->occurrences_count = 0;
+               return;
+       }
+
+       if (search->priv->text != NULL)
+       {
+               GtkTextIter start = *delete_start;
+               GtkTextIter end = *delete_end;
+
+               gtk_text_iter_backward_lines (&start, search->priv->text_nb_lines);
+               gtk_text_iter_forward_lines (&end, search->priv->text_nb_lines);
+
+               remove_occurrences_in_range (search, &start, &end);
+               add_subregion_to_scan (search, &start, &end);
+       }
+}
+
+static void
+delete_range_after_cb (GtkSourceSearch *search,
+                      GtkTextIter     *start,
+                      GtkTextIter     *end)
+{
+       add_subregion_to_scan (search, start, end);
+}
+
+static void
+set_buffer (GtkSourceSearch *search,
+           GtkSourceBuffer *buffer)
+{
+       g_assert (search->priv->buffer == NULL);
+
+       search->priv->buffer = GTK_TEXT_BUFFER (buffer);
+
+       g_signal_connect_object (buffer,
+                                "insert-text",
+                                G_CALLBACK (insert_text_before_cb),
+                                search,
+                                G_CONNECT_SWAPPED);
+
+       g_signal_connect_object (buffer,
+                                "insert-text",
+                                G_CALLBACK (insert_text_after_cb),
+                                search,
+                                G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+
+       g_signal_connect_object (buffer,
+                                "delete-range",
+                                G_CALLBACK (delete_range_before_cb),
+                                search,
+                                G_CONNECT_SWAPPED);
+
+       g_signal_connect_object (buffer,
+                                "delete-range",
+                                G_CALLBACK (delete_range_after_cb),
+                                search,
+                                G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+}
+
+static void
+_gtk_source_search_dispose (GObject *object)
+{
+       GtkSourceSearch *search = GTK_SOURCE_SEARCH (object);
+
+       clear_search (search);
+
+       search->priv->buffer = NULL;
+
+       G_OBJECT_CLASS (_gtk_source_search_parent_class)->dispose (object);
+}
+
+static void
+_gtk_source_search_finalize (GObject *object)
+{
+       GtkSourceSearch *search = GTK_SOURCE_SEARCH (object);
+
+       g_free (search->priv->text);
+
+       G_OBJECT_CLASS (_gtk_source_search_parent_class)->finalize (object);
+}
+
+static void
+_gtk_source_search_class_init (GtkSourceSearchClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->dispose = _gtk_source_search_dispose;
+       object_class->finalize = _gtk_source_search_finalize;
+}
+
+static void
+_gtk_source_search_init (GtkSourceSearch *search)
+{
+       search->priv = _gtk_source_search_get_instance_private (search);
+}
+
+GtkSourceSearch *
+_gtk_source_search_new (GtkSourceBuffer *buffer)
+{
+       GtkSourceSearch *search;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
+
+       search = g_object_new (GTK_SOURCE_TYPE_SEARCH, NULL);
+       set_buffer (search, buffer);
+
+       return search;
+}
+
+static gint
+compute_number_of_lines (const gchar *text)
+{
+       const gchar *p;
+       gint len;
+       gint nb_of_lines = 1;
+
+       if (text == NULL)
+       {
+               return 0;
+       }
+
+       len = strlen (text);
+       p = text;
+
+       while (len > 0)
+       {
+               gint delimiter;
+               gint next_paragraph;
+
+               pango_find_paragraph_boundary (p, len, &delimiter, &next_paragraph);
+
+               if (delimiter == next_paragraph)
+               {
+                       /* not found */
+                       break;
+               }
+
+               p += next_paragraph;
+               len -= next_paragraph;
+               nb_of_lines++;
+       }
+
+       return nb_of_lines;
+}
+
+void
+_gtk_source_search_set_text (GtkSourceSearch *search,
+                            const gchar     *text)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
+       g_return_if_fail (text == NULL || g_utf8_validate (text, -1, NULL));
+
+       g_free (search->priv->text);
+
+       if (text == NULL || *text == '\0')
+       {
+               search->priv->text = NULL;
+       }
+       else
+       {
+               search->priv->text = g_strdup (text);
+       }
+
+       search->priv->text_nb_lines = compute_number_of_lines (search->priv->text);
+
+       update (search);
+}
+
+const gchar *
+_gtk_source_search_get_text (GtkSourceSearch *search)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), NULL);
+
+       return search->priv->text;
+}
+
+void
+_gtk_source_search_set_case_sensitive (GtkSourceSearch *search,
+                                      gboolean         case_sensitive)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
+
+       if (case_sensitive)
+       {
+               search->priv->flags &= ~GTK_TEXT_SEARCH_CASE_INSENSITIVE;
+       }
+       else
+       {
+               search->priv->flags |= GTK_TEXT_SEARCH_CASE_INSENSITIVE;
+       }
+
+       update (search);
+}
+
+gboolean
+_gtk_source_search_get_case_sensitive (GtkSourceSearch *search)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), FALSE);
+
+       return (search->priv->flags & GTK_TEXT_SEARCH_CASE_INSENSITIVE) == 0;
+}
+
+void
+_gtk_source_search_set_at_word_boundaries (GtkSourceSearch *search,
+                                          gboolean         at_word_boundaries)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
+
+       search->priv->at_word_boundaries = at_word_boundaries;
+       update (search);
+}
+
+gboolean
+_gtk_source_search_get_at_word_boundaries (GtkSourceSearch *search)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), FALSE);
+
+       return search->priv->at_word_boundaries;
+}
+
+void
+_gtk_source_search_set_wrap_around (GtkSourceSearch *search,
+                                   gboolean         wrap_around)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
+
+       search->priv->wrap_around = wrap_around;
+       update (search);
+}
+
+gboolean
+_gtk_source_search_get_wrap_around (GtkSourceSearch *search)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), FALSE);
+
+       return search->priv->wrap_around;
+}
+
+guint
+_gtk_source_search_get_occurrences_count (GtkSourceSearch *search)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), 0);
+
+       return search->priv->occurrences_count;
+}
+
+gint
+_gtk_source_search_get_occurrence_position (GtkSourceSearch   *search,
+                                           const GtkTextIter *match_start,
+                                           const GtkTextIter *match_end)
+{
+       GtkTextIter m_start;
+       GtkTextIter m_end;
+       GtkTextIter iter;
+       gboolean found;
+       gint position = 0;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), -1);
+       g_return_val_if_fail (match_start != NULL, -1);
+       g_return_val_if_fail (match_end != NULL, -1);
+
+       if (dispose_has_run (search))
+       {
+               return -1;
+       }
+
+       /* Verify that the occurrence is correct. */
+
+       found = basic_forward_search (search,
+                                     match_start,
+                                     &m_start,
+                                     &m_end,
+                                     match_end);
+
+       if (!found ||
+           !gtk_text_iter_equal (match_start, &m_start) ||
+           !gtk_text_iter_equal (match_end, &m_end))
+       {
+               return 0;
+       }
+
+       /* Verify that the scan region is empty between the start of the buffer
+        * and the end of the occurrence.
+        */
+
+       gtk_text_buffer_get_start_iter (search->priv->buffer, &iter);
+
+       if (search->priv->scan_region != NULL)
+       {
+               GtkTextRegion *region = gtk_text_region_intersect (search->priv->scan_region,
+                                                                  &iter,
+                                                                  match_end);
+
+               gboolean empty = is_text_region_empty (region);
+
+               if (region != NULL)
+               {
+                       gtk_text_region_destroy (region, TRUE);
+               }
+
+               if (!empty)
+               {
+                       return -1;
+               }
+       }
+
+       /* Everything is fine, count the number of previous occurrences. */
+
+       while (smart_forward_search_without_scanning (search, &iter, &m_start, &m_end, match_start))
+       {
+               position++;
+               iter = m_end;
+       }
+
+       return position + 1;
+}
+
+void
+_gtk_source_search_update_highlight (GtkSourceSearch   *search,
+                                    const GtkTextIter *start,
+                                    const GtkTextIter *end,
+                                    gboolean           synchronous)
+{
+       GtkTextRegion *region_to_highlight;
+
+       g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
+       g_return_if_fail (start != NULL);
+       g_return_if_fail (end != NULL);
+
+       if (dispose_has_run (search) ||
+           is_text_region_empty (search->priv->scan_region))
+       {
+               return;
+       }
+
+       region_to_highlight = gtk_text_region_intersect (search->priv->scan_region,
+                                                        start,
+                                                        end);
+
+       if (is_text_region_empty (region_to_highlight))
+       {
+               if (region_to_highlight != NULL)
+               {
+                       gtk_text_region_destroy (region_to_highlight, TRUE);
+               }
+
+               return;
+       }
+
+       if (synchronous)
+       {
+               scan_all_region (search, region_to_highlight);
+               gtk_text_region_destroy (region_to_highlight, TRUE);
+       }
+       else
+       {
+               if (search->priv->high_priority_region != NULL)
+               {
+                       /* The high priority region is used to highlight the
+                        * region visible on screen. So if we are here, that
+                        * means that the visible region has changed. So we can
+                        * destroy the old high_priority_region.
+                        */
+                       gtk_text_region_destroy (search->priv->high_priority_region, TRUE);
+               }
+
+               search->priv->high_priority_region = region_to_highlight;
+               install_idle_scan (search);
+       }
+}
+
+gboolean
+_gtk_source_search_forward (GtkSourceSearch   *search,
+                           const GtkTextIter *iter,
+                           GtkTextIter       *match_start,
+                           GtkTextIter       *match_end)
+{
+       gboolean found;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), FALSE);
+       g_return_val_if_fail (iter != NULL, FALSE);
+
+       if (dispose_has_run (search))
+       {
+               return FALSE;
+       }
+
+       found = smart_forward_search (search, iter, match_start, match_end);
+
+       if (!found && search->priv->wrap_around)
+       {
+               GtkTextIter start_iter;
+               gtk_text_buffer_get_start_iter (search->priv->buffer, &start_iter);
+
+               found = smart_forward_search (search, &start_iter, match_start, match_end);
+       }
+
+       return found;
+}
+
+void
+_gtk_source_search_forward_async (GtkSourceSearch     *search,
+                                 const GtkTextIter   *iter,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
+       g_return_if_fail (iter != NULL);
+
+       if (dispose_has_run (search))
+       {
+               return;
+       }
+
+       clear_task (search);
+       search->priv->task = g_task_new (search->priv->buffer, cancellable, callback, user_data);
+
+       smart_forward_search_async (search, iter, FALSE);
+}
+
+gboolean
+_gtk_source_search_forward_finish (GtkSourceSearch  *search,
+                                  GAsyncResult     *result,
+                                  GtkTextIter      *match_start,
+                                  GtkTextIter      *match_end,
+                                  GError          **error)
+{
+       ForwardBackwardData *data;
+       gboolean found;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), FALSE);
+
+       if (dispose_has_run (search))
+       {
+               return FALSE;
+       }
+
+       g_return_val_if_fail (g_task_is_valid (result, search->priv->buffer), FALSE);
+
+       data = g_task_propagate_pointer (G_TASK (result), error);
+
+       if (data == NULL)
+       {
+               return FALSE;
+       }
+
+       found = data->found;
+
+       if (found)
+       {
+               if (match_start != NULL)
+               {
+                       *match_start = data->match_start;
+               }
+
+               if (match_end != NULL)
+               {
+                       *match_end = data->match_end;
+               }
+       }
+
+       forward_backward_data_free (data);
+       return found;
+}
+
+gboolean
+_gtk_source_search_backward (GtkSourceSearch   *search,
+                            const GtkTextIter *iter,
+                            GtkTextIter       *match_start,
+                            GtkTextIter       *match_end)
+{
+       gboolean found;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), FALSE);
+       g_return_val_if_fail (iter != NULL, FALSE);
+
+       if (dispose_has_run (search))
+       {
+               return FALSE;
+       }
+
+       found = smart_backward_search (search, iter, match_start, match_end);
+
+       if (!found && search->priv->wrap_around)
+       {
+               GtkTextIter end_iter;
+
+               gtk_text_buffer_get_end_iter (search->priv->buffer, &end_iter);
+
+               found = smart_backward_search (search, &end_iter, match_start, match_end);
+       }
+
+       return found;
+}
+
+void
+_gtk_source_search_backward_async (GtkSourceSearch     *search,
+                                  const GtkTextIter   *iter,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
+       g_return_if_fail (iter != NULL);
+
+       if (dispose_has_run (search))
+       {
+               return;
+       }
+
+       clear_task (search);
+       search->priv->task = g_task_new (search->priv->buffer, cancellable, callback, user_data);
+
+       smart_backward_search_async (search, iter, FALSE);
+}
+
+gboolean
+_gtk_source_search_backward_finish (GtkSourceSearch  *search,
+                                   GAsyncResult     *result,
+                                   GtkTextIter      *match_start,
+                                   GtkTextIter      *match_end,
+                                   GError          **error)
+{
+       return _gtk_source_search_forward_finish (search,
+                                                 result,
+                                                 match_start,
+                                                 match_end,
+                                                 error);
+}
+
+gboolean
+_gtk_source_search_replace (GtkSourceSearch   *search,
+                           const GtkTextIter *match_start,
+                           const GtkTextIter *match_end,
+                           const gchar       *replace,
+                           gint               replace_length)
+{
+       GtkTextIter start;
+       GtkTextIter end;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), FALSE);
+       g_return_val_if_fail (match_start != NULL, FALSE);
+       g_return_val_if_fail (match_end != NULL, FALSE);
+       g_return_val_if_fail (replace != NULL, FALSE);
+
+       if (dispose_has_run (search))
+       {
+               return FALSE;
+       }
+
+       basic_forward_search (search, match_start, &start, &end, match_end);
+
+       if (!gtk_text_iter_equal (match_start, &start) ||
+           !gtk_text_iter_equal (match_end, &end))
+       {
+               return FALSE;
+       }
+
+       gtk_text_buffer_begin_user_action (search->priv->buffer);
+
+       gtk_text_buffer_delete (search->priv->buffer, &start, &end);
+       gtk_text_buffer_insert (search->priv->buffer, &start, replace, replace_length);
+
+       gtk_text_buffer_end_user_action (search->priv->buffer);
+
+       return TRUE;
+}
+
+guint
+_gtk_source_search_replace_all (GtkSourceSearch *search,
+                               const gchar     *replace,
+                               gint             replace_length)
+{
+       GtkTextIter iter;
+       GtkTextIter match_start;
+       GtkTextIter match_end;
+       guint nb_matches_replaced = 0;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), 0);
+       g_return_val_if_fail (replace != NULL, 0);
+
+       if (dispose_has_run (search))
+       {
+               return 0;
+       }
+
+       gtk_text_buffer_get_start_iter (search->priv->buffer, &iter);
+
+       gtk_text_buffer_begin_user_action (search->priv->buffer);
+
+       while (smart_forward_search (search, &iter, &match_start, &match_end))
+       {
+               gtk_text_buffer_delete (search->priv->buffer, &match_start, &match_end);
+               gtk_text_buffer_insert (search->priv->buffer, &match_end, replace, replace_length);
+
+               iter = match_end;
+               nb_matches_replaced++;
+       }
+
+       gtk_text_buffer_end_user_action (search->priv->buffer);
+
+       return nb_matches_replaced;
+}
diff --git a/gtksourceview/gtksourcesearch.h b/gtksourceview/gtksourcesearch.h
new file mode 100644
index 0000000..19c6cfa
--- /dev/null
+++ b/gtksourceview/gtksourcesearch.h
@@ -0,0 +1,156 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*-
+ * gtksourcesearch.h
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2013 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __GTK_SOURCE_SEARCH_H__
+#define __GTK_SOURCE_SEARCH_H__
+
+#include <gtk/gtk.h>
+#include "gtksourcetypes.h"
+#include "gtksourcetypes-private.h"
+#include "gtksourcebuffer.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_SEARCH             (_gtk_source_search_get_type ())
+#define GTK_SOURCE_SEARCH(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_SOURCE_TYPE_SEARCH, 
GtkSourceSearch))
+#define GTK_SOURCE_SEARCH_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_SOURCE_TYPE_SEARCH, 
GtkSourceSearchClass))
+#define GTK_SOURCE_IS_SEARCH(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_SOURCE_TYPE_SEARCH))
+#define GTK_SOURCE_IS_SEARCH_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_SOURCE_TYPE_SEARCH))
+#define GTK_SOURCE_SEARCH_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_SOURCE_TYPE_SEARCH, 
GtkSourceSearchClass))
+
+typedef struct _GtkSourceSearchClass    GtkSourceSearchClass;
+typedef struct _GtkSourceSearchPrivate  GtkSourceSearchPrivate;
+
+struct _GtkSourceSearch
+{
+       GObject parent;
+
+       GtkSourceSearchPrivate *priv;
+};
+
+struct _GtkSourceSearchClass
+{
+       GObjectClass parent_class;
+};
+
+G_GNUC_INTERNAL
+GType                  _gtk_source_search_get_type                     (void) G_GNUC_CONST;
+
+G_GNUC_INTERNAL
+GtkSourceSearch *      _gtk_source_search_new                          (GtkSourceBuffer        *buffer);
+
+G_GNUC_INTERNAL
+void                   _gtk_source_search_set_text                     (GtkSourceSearch        *search,
+                                                                        const gchar            *text);
+
+G_GNUC_INTERNAL
+const gchar *          _gtk_source_search_get_text                     (GtkSourceSearch        *search);
+
+G_GNUC_INTERNAL
+void                   _gtk_source_search_set_case_sensitive           (GtkSourceSearch        *search,
+                                                                        gboolean                
case_sensitive);
+
+G_GNUC_INTERNAL
+gboolean               _gtk_source_search_get_case_sensitive           (GtkSourceSearch        *search);
+
+G_GNUC_INTERNAL
+void                   _gtk_source_search_set_at_word_boundaries       (GtkSourceSearch        *search,
+                                                                        gboolean                
at_word_boundaries);
+
+G_GNUC_INTERNAL
+gboolean               _gtk_source_search_get_at_word_boundaries       (GtkSourceSearch        *search);
+
+G_GNUC_INTERNAL
+void                   _gtk_source_search_set_wrap_around              (GtkSourceSearch        *search,
+                                                                        gboolean                wrap_around);
+
+G_GNUC_INTERNAL
+gboolean               _gtk_source_search_get_wrap_around              (GtkSourceSearch        *search);
+
+G_GNUC_INTERNAL
+guint                  _gtk_source_search_get_occurrences_count        (GtkSourceSearch        *search);
+
+G_GNUC_INTERNAL
+gint                   _gtk_source_search_get_occurrence_position      (GtkSourceSearch        *search,
+                                                                        const GtkTextIter      *match_start,
+                                                                        const GtkTextIter      *match_end);
+
+G_GNUC_INTERNAL
+void                   _gtk_source_search_update_highlight             (GtkSourceSearch        *search,
+                                                                        const GtkTextIter      *start,
+                                                                        const GtkTextIter      *end,
+                                                                        gboolean                synchronous);
+
+G_GNUC_INTERNAL
+gboolean               _gtk_source_search_forward                      (GtkSourceSearch        *search,
+                                                                        const GtkTextIter      *iter,
+                                                                        GtkTextIter            *match_start,
+                                                                        GtkTextIter            *match_end);
+
+G_GNUC_INTERNAL
+void                   _gtk_source_search_forward_async                (GtkSourceSearch        *search,
+                                                                        const GtkTextIter      *iter,
+                                                                        GCancellable           *cancellable,
+                                                                        GAsyncReadyCallback     callback,
+                                                                        gpointer                user_data);
+
+G_GNUC_INTERNAL
+gboolean               _gtk_source_search_forward_finish               (GtkSourceSearch        *search,
+                                                                        GAsyncResult           *result,
+                                                                        GtkTextIter            *match_start,
+                                                                        GtkTextIter            *match_end,
+                                                                        GError                **error);
+
+G_GNUC_INTERNAL
+gboolean               _gtk_source_search_backward                     (GtkSourceSearch        *search,
+                                                                        const GtkTextIter      *iter,
+                                                                        GtkTextIter            *match_start,
+                                                                        GtkTextIter            *match_end);
+
+G_GNUC_INTERNAL
+void                   _gtk_source_search_backward_async               (GtkSourceSearch        *search,
+                                                                        const GtkTextIter      *iter,
+                                                                        GCancellable           *cancellable,
+                                                                        GAsyncReadyCallback     callback,
+                                                                        gpointer                user_data);
+
+G_GNUC_INTERNAL
+gboolean               _gtk_source_search_backward_finish              (GtkSourceSearch        *search,
+                                                                        GAsyncResult           *result,
+                                                                        GtkTextIter            *match_start,
+                                                                        GtkTextIter            *match_end,
+                                                                        GError                **error);
+
+G_GNUC_INTERNAL
+gboolean               _gtk_source_search_replace                      (GtkSourceSearch        *search,
+                                                                        const GtkTextIter      *match_start,
+                                                                        const GtkTextIter      *match_end,
+                                                                        const gchar            *replace,
+                                                                        gint                    
replace_length);
+
+G_GNUC_INTERNAL
+guint                  _gtk_source_search_replace_all                  (GtkSourceSearch        *search,
+                                                                        const gchar            *replace,
+                                                                        gint                    
replace_length);
+
+G_END_DECLS
+
+#endif /* __GTK_SOURCE_SEARCH_H__ */
diff --git a/gtksourceview/gtksourcetypes-private.h b/gtksourceview/gtksourcetypes-private.h
index 6b88c2c..58ccf31 100644
--- a/gtksourceview/gtksourcetypes-private.h
+++ b/gtksourceview/gtksourcetypes-private.h
@@ -34,6 +34,7 @@ typedef struct _GtkSourceGutterRendererLines  GtkSourceGutterRendererLines;
 typedef struct _GtkSourceGutterRendererMarks   GtkSourceGutterRendererMarks;
 typedef struct _GtkSourcePixbufHelper          GtkSourcePixbufHelper;
 typedef struct _GtkSourceRegex                 GtkSourceRegex;
+typedef struct _GtkSourceSearch                        GtkSourceSearch;
 typedef struct _GtkSourceUndoManagerDefault    GtkSourceUndoManagerDefault;
 
 G_END_DECLS
diff --git a/gtksourceview/gtksourceutils.c b/gtksourceview/gtksourceutils.c
new file mode 100644
index 0000000..c589031
--- /dev/null
+++ b/gtksourceview/gtksourceutils.c
@@ -0,0 +1,191 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourceutils.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2005 - Paolo Borelli
+ * Copyright (C) 2013 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * SECTION:utils
+ * @title: GtkSourceUtils
+ * @short_description: Utilities functions
+ *
+ * Utilities functions.
+ */
+
+#include "gtksourceutils.h"
+#include <string.h>
+
+/**
+ * gtk_source_utils_unescape_search_text:
+ * @text: the text to unescape.
+ *
+ * Use this function before gtk_source_buffer_set_search_text(), to unescape
+ * certain sequences of characters: \n, \r, \t and \\. The purpose is to easily
+ * write those characters in a search entry.
+ *
+ * See also: gtk_source_utils_escape_search_text().
+ *
+ * Returns: the unescaped @text.
+ * Since: 3.10
+ */
+gchar *
+gtk_source_utils_unescape_search_text (const gchar *text)
+{
+       GString *str;
+       gint length;
+       gboolean drop_prev = FALSE;
+       const gchar *cur;
+       const gchar *end;
+       const gchar *prev;
+
+       if (text == NULL)
+       {
+               return NULL;
+       }
+
+       length = strlen (text);
+
+       str = g_string_new ("");
+
+       cur = text;
+       end = text + length;
+       prev = NULL;
+
+       while (cur != end)
+       {
+               const gchar *next;
+               next = g_utf8_next_char (cur);
+
+               if (prev && (*prev == '\\'))
+               {
+                       switch (*cur)
+                       {
+                               case 'n':
+                                       str = g_string_append (str, "\n");
+                                       break;
+                               case 'r':
+                                       str = g_string_append (str, "\r");
+                                       break;
+                               case 't':
+                                       str = g_string_append (str, "\t");
+                                       break;
+                               case '\\':
+                                       str = g_string_append (str, "\\");
+                                       drop_prev = TRUE;
+                                       break;
+                               default:
+                                       str = g_string_append (str, "\\");
+                                       str = g_string_append_len (str, cur, next - cur);
+                                       break;
+                       }
+               }
+               else if (*cur != '\\')
+               {
+                       str = g_string_append_len (str, cur, next - cur);
+               }
+               else if ((next == end) && (*cur == '\\'))
+               {
+                       str = g_string_append (str, "\\");
+               }
+
+               if (!drop_prev)
+               {
+                       prev = cur;
+               }
+               else
+               {
+                       prev = NULL;
+                       drop_prev = FALSE;
+               }
+
+               cur = next;
+       }
+
+       return g_string_free (str, FALSE);
+}
+
+/**
+ * gtk_source_utils_escape_search_text:
+ * @text: the text to escape.
+ *
+ * Use this function after gtk_source_buffer_get_search_text(), to escape
+ * certain characters: \n, \r, \t and \.
+ *
+ * See also: gtk_source_utils_unescape_search_text().
+ *
+ * Returns: the escaped @text.
+ * Since: 3.10
+ */
+gchar *
+gtk_source_utils_escape_search_text (const gchar* text)
+{
+       GString *str;
+       gint length;
+       const gchar *p;
+       const gchar *end;
+
+       if (text == NULL)
+       {
+               return NULL;
+       }
+
+       length = strlen (text);
+
+       /* no escape when typing.
+        * The short circuit works only for ascii, but we only
+        * care about not escaping a single '\' */
+       if (length == 1)
+       {
+               return g_strdup (text);
+       }
+
+       str = g_string_new ("");
+
+       p = text;
+       end = text + length;
+
+       while (p != end)
+       {
+               const gchar *next;
+               next = g_utf8_next_char (p);
+
+               switch (*p)
+               {
+                       case '\n':
+                               g_string_append (str, "\\n");
+                               break;
+                       case '\r':
+                               g_string_append (str, "\\r");
+                               break;
+                       case '\t':
+                               g_string_append (str, "\\t");
+                               break;
+                       case '\\':
+                               g_string_append (str, "\\\\");
+                               break;
+                       default:
+                               g_string_append_len (str, p, next - p);
+                               break;
+               }
+
+               p = next;
+       }
+
+       return g_string_free (str, FALSE);
+}
diff --git a/gtksourceview/gtksourceutils.h b/gtksourceview/gtksourceutils.h
new file mode 100644
index 0000000..2a39ed9
--- /dev/null
+++ b/gtksourceview/gtksourceutils.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*-
+ * gtksourceutils.h
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2013 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __GTK_SOURCE_UTILS_H__
+#define __GTK_SOURCE_UTILS_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+gchar          *gtk_source_utils_unescape_search_text          (const gchar    *text);
+gchar          *gtk_source_utils_escape_search_text            (const gchar    *text);
+
+G_END_DECLS
+
+#endif /* __GTK_SOURCE_UTILS_H__ */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index aebf5cf..c20b581 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -131,9 +131,11 @@ gtksourceview/gtksourcemarkattributes.c
 gtksourceview/gtksourcemark.c
 gtksourceview/gtksourceprintcompositor.c
 gtksourceview/gtksourceregex.c
+gtksourceview/gtksourcesearch.c
 gtksourceview/gtksourcestyle.c
 gtksourceview/gtksourcestylescheme.c
 gtksourceview/gtksourcestyleschememanager.c
 gtksourceview/gtksourceundomanagerdefault.c
+gtksourceview/gtksourceutils.c
 gtksourceview/gtksourceview.c
 gtksourceview/gtksourceview-i18n.c
diff --git a/tests/Makefile.am b/tests/Makefile.am
index ef30933..332989b 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -13,12 +13,16 @@ AM_CPPFLAGS =                               \
 noinst_PROGRAMS = $(TEST_PROGS) $(UNIT_TEST_PROGS)
 TESTS = $(UNIT_TEST_PROGS)
 
-BUILT_SOURCES = \
-       test-completion-resources.c
+BUILT_SOURCES =                                \
+       test-completion-resources.c     \
+       test-search-ui-resources.c
 
 test-completion-resources.c: test-completion.gresource.xml $(shell $(GLIB_COMPILE_RESOURCES) 
--generate-dependencies $(srcdir)/test-completion.gresource.xml)
        $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(srcdir) --generate-source 
$(srcdir)/test-completion.gresource.xml
 
+test-search-ui-resources.c: test-search-ui.gresource.xml $(shell $(GLIB_COMPILE_RESOURCES) 
--generate-dependencies $(srcdir)/test-search-ui.gresource.xml)
+       $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(srcdir) --generate-source 
$(srcdir)/test-search-ui.gresource.xml
+
 TEST_PROGS = test-widget
 test_widget_SOURCES = test-widget.c
 test_widget_LDADD =                    \
@@ -27,14 +31,31 @@ test_widget_LDADD =                         \
        $(TESTS_LIBS)
 
 TEST_PROGS += test-completion
-test_completion_SOURCES =      \
-       $(BUILT_SOURCES)        \
-       test-completion.c
+test_completion_SOURCES =              \
+       test-completion.c               \
+       test-completion-resources.c
 test_completion_LDADD =                \
        $(top_builddir)/gtksourceview/libgtksourceview-3.0.la \
        $(DEP_LIBS)                     \
        $(TESTS_LIBS)
 
+TEST_PROGS += test-search-ui
+test_search_ui_SOURCES =               \
+       test-search-ui.c                \
+       test-search-ui-resources.c
+test_search_ui_LDADD =                                         \
+       $(top_builddir)/gtksourceview/libgtksourceview-3.0.la   \
+       $(DEP_LIBS)                                             \
+       $(TESTS_LIBS)
+
+TEST_PROGS += test-search-performances
+test_search_performances_SOURCES = \
+       test-search-performances.c
+test_search_performances_LDADD =                               \
+       $(top_builddir)/gtksourceview/libgtksourceview-3.0.la   \
+       $(DEP_LIBS)                                             \
+       $(TESTS_LIBS)
+
 UNIT_TEST_PROGS = test-languagemanager
 test_languagemanager_SOURCES =         \
        test-languagemanager.c
@@ -119,6 +140,14 @@ test_undo_manager_LDADD =                                  \
        $(DEP_LIBS)                                             \
        $(TESTS_LIBS)
 
+UNIT_TEST_PROGS += test-search
+test_search_SOURCES = test-search.c
+test_search_LDADD =                                                    \
+       $(top_builddir)/gtksourceview/libgtksourceview-3.0.la           \
+       $(top_builddir)/gtksourceview/libgtksourceview-private.la       \
+       $(DEP_LIBS)                                                     \
+       $(TESTS_LIBS)
+
 python_tests =                 \
        test-completion.py      \
        test-widget.py
@@ -129,6 +158,8 @@ EXTRA_DIST =                                \
        styles/classic.xml              \
        test-completion.gresource.xml   \
        test-completion.ui              \
+       test-search-ui.gresource.xml    \
+       test-search-ui.ui               \
        $(python_tests)
 
 -include $(top_srcdir)/git.mk
diff --git a/tests/test-search-performances.c b/tests/test-search-performances.c
new file mode 100644
index 0000000..42b8a69
--- /dev/null
+++ b/tests/test-search-performances.c
@@ -0,0 +1,183 @@
+/*
+ * test-search-performances.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2013 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <gtk/gtk.h>
+#include <gtksourceview/gtksource.h>
+
+/* This measures the execution times for basic search (with
+ * gtk_text_iter_forward_search()), and "smart" search (the first search with
+ * gtk_text_iter_forward_search(), later searches with
+ * gtk_text_iter_forward_to_tag_toggle()).
+ * For the "smart" search, only the first search is measured. Later searches
+ * are really fast (going to the previous/next occurrence is done in O(1)).
+ * Different search flags are also tested. We can see a big difference between
+ * the case sensitive search and case insensitive.
+ */
+
+static void
+on_notify_search_occurrences_count_cb (GtkSourceBuffer *buffer,
+                                      GParamSpec      *spec,
+                                      GTimer          *timer)
+{
+       g_print ("smart asynchronous search, case sensitive: %lf seconds.\n",
+                g_timer_elapsed (timer, NULL));
+
+       gtk_main_quit ();
+}
+
+int
+main (int argc, char *argv[])
+{
+       GtkSourceBuffer *buffer;
+       GtkTextIter iter;
+       GtkTextIter match_end;
+       GTimer *timer;
+       gint i;
+       GtkTextSearchFlags flags;
+
+       gtk_init (&argc, &argv);
+
+       buffer = gtk_source_buffer_new (NULL);
+
+       gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter);
+
+       for (i = 0; i < 1000000; i++)
+       {
+               gtk_text_buffer_insert (GTK_TEXT_BUFFER (buffer),
+                                       &iter,
+                                       "A line of text to fill the text buffer. Is it long enough?\n",
+                                       -1);
+       }
+
+       gtk_text_buffer_insert (GTK_TEXT_BUFFER (buffer), &iter, "foo\n", -1);
+
+       /* Basic search, no flags */
+
+       timer = g_timer_new ();
+
+       gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter);
+
+       flags = 0;
+
+       while (gtk_text_iter_forward_search (&iter, "foo", flags, NULL, &match_end, NULL))
+       {
+               iter = match_end;
+       }
+
+       g_timer_stop (timer);
+       g_print ("basic forward search, no flags: %lf seconds.\n",
+                g_timer_elapsed (timer, NULL));
+
+       /* Basic search, with flags always enabled by gsv */
+
+       g_timer_start (timer);
+
+       gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter);
+
+       flags = GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY;
+
+       while (gtk_text_iter_forward_search (&iter, "foo", flags, NULL, &match_end, NULL))
+       {
+               iter = match_end;
+       }
+
+       g_timer_stop (timer);
+       g_print ("basic forward search, visible and text only flags: %lf seconds.\n",
+                g_timer_elapsed (timer, NULL));
+
+       /* Basic search, with default flags in gsv */
+
+       g_timer_start (timer);
+
+       gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter);
+
+       flags = GTK_TEXT_SEARCH_VISIBLE_ONLY |
+               GTK_TEXT_SEARCH_TEXT_ONLY |
+               GTK_TEXT_SEARCH_CASE_INSENSITIVE;
+
+       while (gtk_text_iter_forward_search (&iter, "foo", flags, NULL, &match_end, NULL))
+       {
+               iter = match_end;
+       }
+
+       g_timer_stop (timer);
+       g_print ("basic forward search, all flags: %lf seconds.\n",
+                g_timer_elapsed (timer, NULL));
+
+       /* Smart forward search, with default flags in gsv */
+
+       g_timer_start (timer);
+
+       gtk_source_buffer_set_search_wrap_around (buffer, FALSE);
+       gtk_source_buffer_set_search_text (buffer, "foo");
+
+       gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter);
+
+       while (gtk_source_buffer_forward_search (buffer, &iter, NULL, &match_end))
+       {
+               iter = match_end;
+       }
+
+       g_timer_stop (timer);
+       g_print ("smart synchronous forward search, case insensitive: %lf seconds.\n",
+                g_timer_elapsed (timer, NULL));
+
+       /* Smart forward search, case sensitive */
+
+       g_timer_start (timer);
+
+       gtk_source_buffer_set_search_text (buffer, NULL);
+       gtk_source_buffer_set_case_sensitive_search (buffer, TRUE);
+       gtk_source_buffer_set_search_text (buffer, "foo");
+
+       gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter);
+
+       while (gtk_source_buffer_forward_search (buffer, &iter, NULL, &match_end))
+       {
+               iter = match_end;
+       }
+
+       g_timer_stop (timer);
+       g_print ("smart synchronous forward search, case sensitive: %lf seconds.\n",
+                g_timer_elapsed (timer, NULL));
+
+       /* Smart search, case sensitive, asynchronous */
+
+       /* The asynchronous overhead doesn't depend on the search flags, it
+        * depends on the maximum number of lines to scan in one batch, and
+        * (obviously), on the buffer size.
+        * You can tune SCAN_BATCH_SIZE in gtksourcesearch.c to see a difference
+        * in the overhead.
+        */
+
+       g_signal_connect (buffer,
+                         "notify::search-occurrences-count",
+                         G_CALLBACK (on_notify_search_occurrences_count_cb),
+                         timer);
+
+       g_timer_start (timer);
+
+       gtk_source_buffer_set_search_text (buffer, NULL);
+       gtk_source_buffer_set_search_text (buffer, "foo");
+
+       gtk_main ();
+       return 0;
+}
diff --git a/tests/test-search-ui.c b/tests/test-search-ui.c
new file mode 100644
index 0000000..b1ac15f
--- /dev/null
+++ b/tests/test-search-ui.c
@@ -0,0 +1,441 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*-
+ * test-search-ui.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2013 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <gtk/gtk.h>
+#include <gtksourceview/gtksource.h>
+
+#define TEST_TYPE_SEARCH_UI             (test_search_ui_get_type ())
+#define TEST_SEARCH_UI(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_TYPE_SEARCH_UI, 
TestSearchUI))
+#define TEST_SEARCH_UI_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_TYPE_SEARCH_UI, 
TestSearchUIClass))
+#define TEST_IS_SEARCH_UI(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_TYPE_SEARCH_UI))
+#define TEST_IS_SEARCH_UI_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), TEST_TYPE_SEARCH_UI))
+#define TEST_SEARCH_UI_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_TYPE_SEARCH_UI, 
TestSearchUIClass))
+
+typedef struct _TestSearchUI        TestSearchUI;
+typedef struct _TestSearchUIClass   TestSearchUIClass;
+typedef struct _TestSearchUIPrivate TestSearchUIPrivate;
+
+struct _TestSearchUI
+{
+       GtkGrid parent;
+       TestSearchUIPrivate *priv;
+};
+
+struct _TestSearchUIClass
+{
+       GtkGridClass parent_class;
+};
+
+struct _TestSearchUIPrivate
+{
+       GtkSourceView *source_view;
+       GtkSourceBuffer *source_buffer;
+       GtkEntry *replace_entry;
+       GtkLabel *label_occurrences;
+
+       guint idle_update_label_id;
+};
+
+GType test_search_ui_get_type (void);
+
+G_DEFINE_TYPE_WITH_PRIVATE (TestSearchUI, test_search_ui, GTK_TYPE_GRID)
+
+static void
+open_file (TestSearchUI *search,
+          const gchar  *filename)
+{
+       gchar *contents;
+       GError *error = NULL;
+       GtkSourceLanguageManager *language_manager;
+       GtkSourceLanguage *language;
+       GtkTextIter iter;
+
+       if (!g_file_get_contents (filename, &contents, NULL, &error))
+       {
+               g_error ("Impossible to load file: %s", error->message);
+       }
+
+       gtk_text_buffer_set_text (GTK_TEXT_BUFFER (search->priv->source_buffer),
+                                 contents,
+                                 -1);
+
+       language_manager = gtk_source_language_manager_get_default ();
+       language = gtk_source_language_manager_get_language (language_manager, "c");
+       gtk_source_buffer_set_language (search->priv->source_buffer, language);
+
+       gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (search->priv->source_buffer),
+                                       &iter);
+
+       gtk_text_buffer_select_range (GTK_TEXT_BUFFER (search->priv->source_buffer),
+                                     &iter,
+                                     &iter);
+
+       g_free (contents);
+}
+
+static void
+update_label (TestSearchUI *search)
+{
+       guint occurrences_count;
+       GtkTextIter select_start;
+       GtkTextIter select_end;
+       gint occurrence_pos;
+       gchar *text;
+
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (search->priv->source_buffer);
+
+       gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer),
+                                             &select_start,
+                                             &select_end);
+
+       occurrence_pos = gtk_source_buffer_get_search_occurrence_position (search->priv->source_buffer,
+                                                                          &select_start,
+                                                                          &select_end);
+
+       if (occurrence_pos > 0)
+       {
+               text = g_strdup_printf ("%d of %u", occurrence_pos, occurrences_count);
+       }
+       else
+       {
+               text = g_strdup_printf ("%u occurrences", occurrences_count);
+       }
+
+       gtk_label_set_text (search->priv->label_occurrences, text);
+       g_free (text);
+}
+
+/* The search entry is a GtkSearchEntry. The "changed" signal is delayed on a
+ * GtkSearchEntry (but not with a simple GtkEntry). That's why the
+ * "notify::text" signal is used instead.
+ * With the "changed" signal, the search highlighting takes some time to be
+ * updated, while with the notify signal, it is immediate.
+ *
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=700229
+ */
+static void
+search_entry_text_notify_cb (TestSearchUI *search,
+                            GParamSpec   *spec,
+                            GtkEntry     *entry)
+{
+       const gchar *text = gtk_entry_get_text (entry);
+       gchar *unescaped_text = gtk_source_utils_unescape_search_text (text);
+
+       gtk_source_buffer_set_search_text (search->priv->source_buffer, unescaped_text);
+       g_free (unescaped_text);
+}
+
+static void
+select_search_occurrence (TestSearchUI      *search,
+                         const GtkTextIter *match_start,
+                         const GtkTextIter *match_end)
+{
+       GtkTextMark *insert;
+
+       gtk_text_buffer_select_range (GTK_TEXT_BUFFER (search->priv->source_buffer),
+                                     match_start,
+                                     match_end);
+
+       insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (search->priv->source_buffer));
+
+       gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (search->priv->source_view),
+                                           insert);
+}
+
+static void
+backward_search_finished (GtkSourceBuffer *buffer,
+                         GAsyncResult    *result,
+                         TestSearchUI    *search)
+{
+       GtkTextIter match_start;
+       GtkTextIter match_end;
+
+       if (gtk_source_buffer_backward_search_finish (buffer,
+                                                     result,
+                                                     &match_start,
+                                                     &match_end,
+                                                     NULL))
+       {
+               select_search_occurrence (search, &match_start, &match_end);
+       }
+}
+
+static void
+button_previous_clicked_cb (TestSearchUI *search,
+                           GtkButton    *button)
+{
+       GtkTextIter start_at;
+
+       gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer),
+                                             &start_at,
+                                             NULL);
+
+       gtk_source_buffer_backward_search_async (search->priv->source_buffer,
+                                                &start_at,
+                                                NULL,
+                                                (GAsyncReadyCallback)backward_search_finished,
+                                                search);
+}
+
+static void
+forward_search_finished (GtkSourceBuffer *buffer,
+                        GAsyncResult    *result,
+                        TestSearchUI    *search)
+{
+       GtkTextIter match_start;
+       GtkTextIter match_end;
+
+       if (gtk_source_buffer_forward_search_finish (buffer,
+                                                    result,
+                                                    &match_start,
+                                                    &match_end,
+                                                    NULL))
+       {
+               select_search_occurrence (search, &match_start, &match_end);
+       }
+}
+
+static void
+button_next_clicked_cb (TestSearchUI *search,
+                       GtkButton    *button)
+{
+       GtkTextIter start_at;
+
+       gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer),
+                                             NULL,
+                                             &start_at);
+
+       gtk_source_buffer_forward_search_async (search->priv->source_buffer,
+                                               &start_at,
+                                               NULL,
+                                               (GAsyncReadyCallback)forward_search_finished,
+                                               search);
+}
+
+static void
+button_replace_clicked_cb (TestSearchUI *search,
+                          GtkButton    *button)
+{
+       GtkTextIter match_start;
+       GtkTextIter match_end;
+       GtkTextIter iter;
+       GtkEntryBuffer *entry_buffer;
+       gint replace_length;
+
+       gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer),
+                                             &match_start,
+                                             &match_end);
+
+       entry_buffer = gtk_entry_get_buffer (search->priv->replace_entry);
+       replace_length = gtk_entry_buffer_get_bytes (entry_buffer);
+
+       gtk_source_buffer_search_replace (search->priv->source_buffer,
+                                         &match_start,
+                                         &match_end,
+                                         gtk_entry_get_text (search->priv->replace_entry),
+                                         replace_length);
+
+       gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer),
+                                             NULL,
+                                             &iter);
+
+       gtk_source_buffer_forward_search_async (search->priv->source_buffer,
+                                               &iter,
+                                               NULL,
+                                               (GAsyncReadyCallback)forward_search_finished,
+                                               search);
+}
+
+static void
+button_replace_all_clicked_cb (TestSearchUI *search,
+                              GtkButton    *button)
+{
+       GtkEntryBuffer *entry_buffer = gtk_entry_get_buffer (search->priv->replace_entry);
+       gint replace_length = gtk_entry_buffer_get_bytes (entry_buffer);
+
+       gtk_source_buffer_search_replace_all (search->priv->source_buffer,
+                                             gtk_entry_get_text (search->priv->replace_entry),
+                                             replace_length);
+}
+
+static gboolean
+update_label_idle_cb (TestSearchUI *search)
+{
+       search->priv->idle_update_label_id = 0;
+
+       update_label (search);
+
+       return G_SOURCE_REMOVE;
+}
+
+static void
+mark_set_cb (GtkTextBuffer *buffer,
+            GtkTextIter   *location,
+            GtkTextMark   *mark,
+            TestSearchUI  *search)
+{
+       GtkTextMark *insert;
+       GtkTextMark *selection_bound;
+
+       insert = gtk_text_buffer_get_insert (buffer);
+       selection_bound = gtk_text_buffer_get_selection_bound (buffer);
+
+       if ((mark == insert || mark == selection_bound) &&
+           search->priv->idle_update_label_id == 0)
+       {
+               search->priv->idle_update_label_id = g_idle_add ((GSourceFunc)update_label_idle_cb,
+                                                                search);
+       }
+}
+
+static void
+match_case_toggled_cb (TestSearchUI    *search,
+                      GtkToggleButton *button)
+{
+       gtk_source_buffer_set_case_sensitive_search (search->priv->source_buffer,
+                                                    gtk_toggle_button_get_active (button));
+}
+
+static void
+at_word_boundaries_toggled_cb (TestSearchUI    *search,
+                              GtkToggleButton *button)
+{
+       gtk_source_buffer_set_search_at_word_boundaries (search->priv->source_buffer,
+                                                        gtk_toggle_button_get_active (button));
+}
+
+static void
+wrap_around_toggled_cb (TestSearchUI    *search,
+                       GtkToggleButton *button)
+{
+       gtk_source_buffer_set_search_wrap_around (search->priv->source_buffer,
+                                                 gtk_toggle_button_get_active (button));
+}
+
+static void
+test_search_ui_dispose (GObject *object)
+{
+       TestSearchUI *search = TEST_SEARCH_UI (object);
+
+       g_clear_object (&search->priv->source_buffer);
+
+       G_OBJECT_CLASS (test_search_ui_parent_class)->dispose (object);
+}
+
+static void
+test_search_ui_class_init (TestSearchUIClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+       object_class->dispose = test_search_ui_dispose;
+
+       gtk_widget_class_set_template_from_resource (widget_class,
+                                                    "/org/gnome/gtksourceview/tests/ui/test-search-ui.ui");
+
+       gtk_widget_class_bind_child (widget_class, TestSearchUIPrivate, source_view);
+       gtk_widget_class_bind_child (widget_class, TestSearchUIPrivate, replace_entry);
+       gtk_widget_class_bind_child (widget_class, TestSearchUIPrivate, label_occurrences);
+
+       gtk_widget_class_bind_callback (widget_class, search_entry_text_notify_cb);
+       gtk_widget_class_bind_callback (widget_class, button_previous_clicked_cb);
+       gtk_widget_class_bind_callback (widget_class, button_next_clicked_cb);
+       gtk_widget_class_bind_callback (widget_class, button_replace_clicked_cb);
+       gtk_widget_class_bind_callback (widget_class, button_replace_all_clicked_cb);
+
+       /* It is also possible to bind the properties with
+        * g_object_bind_property(), between the check buttons and the source
+        * buffer. But GtkBuilder and Glade don't support that yet.
+        */
+       gtk_widget_class_bind_callback (widget_class, match_case_toggled_cb);
+       gtk_widget_class_bind_callback (widget_class, at_word_boundaries_toggled_cb);
+       gtk_widget_class_bind_callback (widget_class, wrap_around_toggled_cb);
+}
+
+static void
+test_search_ui_init (TestSearchUI *search)
+{
+       PangoFontDescription *font;
+
+       search->priv = test_search_ui_get_instance_private (search);
+
+       gtk_widget_init_template (GTK_WIDGET (search));
+
+       font = pango_font_description_from_string ("Monospace 10");
+       gtk_widget_override_font (GTK_WIDGET (search->priv->source_view), font);
+       pango_font_description_free (font);
+
+       /* FIXME: bug? Normally the tab width is 8 by default. */
+       gtk_source_view_set_tab_width (search->priv->source_view, 8);
+
+       /* Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=643732:
+        * "Source view is created with a GtkTextBuffer instead of GtkSourceBuffer"
+        */
+       search->priv->source_buffer = gtk_source_buffer_new (NULL);
+
+       gtk_text_view_set_buffer (GTK_TEXT_VIEW (search->priv->source_view),
+                                 GTK_TEXT_BUFFER (search->priv->source_buffer));
+
+       open_file (search, TOP_SRCDIR "/gtksourceview/gtksourcesearch.c");
+
+       g_signal_connect_swapped (search->priv->source_buffer,
+                                 "notify::search-occurrences-count",
+                                 G_CALLBACK (update_label),
+                                 search);
+
+       g_signal_connect (search->priv->source_buffer,
+                         "mark-set",
+                         G_CALLBACK (mark_set_cb),
+                         search);
+}
+
+static TestSearchUI *
+test_search_ui_new (void)
+{
+       return g_object_new (test_search_ui_get_type (), NULL);
+}
+
+gint
+main (gint argc, gchar *argv[])
+{
+       GtkWidget *window;
+       TestSearchUI *search;
+
+       gtk_init (&argc, &argv);
+
+       window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+       gtk_window_set_default_size (GTK_WINDOW (window), 700, 500);
+
+       g_signal_connect (window,
+                         "destroy",
+                         G_CALLBACK (gtk_main_quit),
+                         NULL);
+
+       search = test_search_ui_new ();
+       gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (search));
+
+       gtk_widget_show_all (window);
+
+       gtk_main ();
+
+       return 0;
+}
diff --git a/tests/test-search-ui.gresource.xml b/tests/test-search-ui.gresource.xml
new file mode 100644
index 0000000..1577bad
--- /dev/null
+++ b/tests/test-search-ui.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/gtksourceview/tests/ui">
+    <file preprocess="xml-stripblanks">test-search-ui.ui</file>
+  </gresource>
+</gresources>
diff --git a/tests/test-search-ui.ui b/tests/test-search-ui.ui
new file mode 100644
index 0000000..d2d7cc7
--- /dev/null
+++ b/tests/test-search-ui.ui
@@ -0,0 +1,289 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.10 -->
+  <!-- interface-requires gtksourceview 3.0 -->
+  <object class="GtkImage" id="image1">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-go-up</property>
+    <property name="icon_size">1</property>
+  </object>
+  <object class="GtkImage" id="image2">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-go-down</property>
+    <property name="icon_size">1</property>
+  </object>
+  <object class="GtkImage" id="image3">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-find-and-replace</property>
+    <property name="icon_size">1</property>
+  </object>
+  <object class="GtkImage" id="image4">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-find-and-replace</property>
+    <property name="icon_size">1</property>
+  </object>
+  <template class="TestSearchUI" parent="GtkGrid">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="border_width">6</property>
+    <property name="row_spacing">6</property>
+    <child>
+      <object class="GtkScrolledWindow" id="scrolledwindow1">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="shadow_type">in</property>
+        <child>
+          <object class="GtkSourceView" id="source_view">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="has_tooltip">True</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <property name="left_margin">2</property>
+            <property name="right_margin">2</property>
+            <property name="tab_width">4</property>
+            <property name="auto_indent">True</property>
+            <property name="indent_on_tab">False</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">0</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkGrid" id="grid2">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="column_spacing">6</property>
+        <child>
+          <object class="GtkLabel" id="label1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="xalign">0</property>
+            <property name="label">Search:</property>
+            <attributes>
+              <attribute name="weight" value="bold"/>
+            </attributes>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkSearchEntry" id="search_entry">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="primary_icon_name">edit-find-symbolic</property>
+            <property name="primary_icon_activatable">False</property>
+            <property name="primary_icon_sensitive">False</property>
+            <signal name="notify::text" handler="search_entry_text_notify_cb" object="TestSearchUI" 
swapped="yes"/>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label_occurrences">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label">0 occurrences</property>
+          </object>
+          <packing>
+            <property name="left_attach">4</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="button_previous">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="image">image1</property>
+            <signal name="clicked" handler="button_previous_clicked_cb" object="TestSearchUI" swapped="yes"/>
+          </object>
+          <packing>
+            <property name="left_attach">2</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="button_next">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="image">image2</property>
+            <signal name="clicked" handler="button_next_clicked_cb" object="TestSearchUI" swapped="yes"/>
+          </object>
+          <packing>
+            <property name="left_attach">3</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">1</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkGrid" id="grid3">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkCheckButton" id="checkbutton_match_case">
+            <property name="label">Match case</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="xalign">0</property>
+            <property name="draw_indicator">True</property>
+            <signal name="toggled" handler="match_case_toggled_cb" object="TestSearchUI" swapped="yes"/>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="checkbutton_at_word_boundaries">
+            <property name="label">At word boundaries</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="xalign">0</property>
+            <property name="draw_indicator">True</property>
+            <signal name="toggled" handler="at_word_boundaries_toggled_cb" object="TestSearchUI" 
swapped="yes"/>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">1</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="checkbutton_wrap_around">
+            <property name="label">Wrap around</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="xalign">0</property>
+            <property name="active">True</property>
+            <property name="draw_indicator">True</property>
+            <signal name="toggled" handler="wrap_around_toggled_cb" object="TestSearchUI" swapped="yes"/>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">2</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">2</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkGrid" id="grid4">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="column_spacing">6</property>
+        <child>
+          <object class="GtkLabel" id="label2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="xalign">0</property>
+            <property name="label">Replace:</property>
+            <attributes>
+              <attribute name="weight" value="bold"/>
+            </attributes>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="replace_entry">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="button_replace">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="image">image3</property>
+            <signal name="clicked" handler="button_replace_clicked_cb" object="TestSearchUI" swapped="yes"/>
+          </object>
+          <packing>
+            <property name="left_attach">2</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="button_replace_all">
+            <property name="label">All</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="image">image4</property>
+            <property name="always_show_image">True</property>
+            <signal name="clicked" handler="button_replace_all_clicked_cb" object="TestSearchUI" 
swapped="yes"/>
+          </object>
+          <packing>
+            <property name="left_attach">3</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">3</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+  </template>
+</interface>
diff --git a/tests/test-search.c b/tests/test-search.c
new file mode 100644
index 0000000..3e725e5
--- /dev/null
+++ b/tests/test-search.c
@@ -0,0 +1,265 @@
+/*
+ * test-search.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2013 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <gtk/gtk.h>
+#include <gtksourceview/gtksource.h>
+#include "gtksourceview/gtksourcesearch.h"
+
+static void
+flush_queue (void)
+{
+       while (gtk_events_pending ())
+       {
+               gtk_main_iteration ();
+       }
+}
+
+/* Without insertion or deletion of text in the buffer afterwards. */
+static void
+test_occurrences_count_simple (void)
+{
+       GtkSourceBuffer *buffer = gtk_source_buffer_new (NULL);
+       GtkTextIter iter;
+       guint occurrences_count;
+
+       gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter);
+       gtk_text_buffer_insert (GTK_TEXT_BUFFER (buffer), &iter, "Some foo\nSome bar\n", -1);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (buffer);
+       g_assert_cmpuint (occurrences_count, ==, 0);
+
+       gtk_source_buffer_set_search_text (buffer, "world");
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (buffer);
+       g_assert_cmpuint (occurrences_count, ==, 0);
+
+       gtk_source_buffer_set_search_text (buffer, "Some");
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (buffer);
+       g_assert_cmpuint (occurrences_count, ==, 2);
+
+       gtk_source_buffer_set_search_text (buffer, "foo");
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (buffer);
+       g_assert_cmpuint (occurrences_count, ==, 1);
+
+       gtk_source_buffer_set_search_text (buffer, "world");
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (buffer);
+       g_assert_cmpuint (occurrences_count, ==, 0);
+
+       g_object_unref (buffer);
+}
+
+static void
+test_occurrences_count_with_insert (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+       GtkTextIter iter;
+       guint occurrences_count;
+
+       /* Contents: "foobar" */
+       gtk_text_buffer_get_start_iter (text_buffer, &iter);
+       gtk_text_buffer_insert (text_buffer, &iter, "foobar", -1);
+
+       gtk_source_buffer_set_search_text (source_buffer, "foo");
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 1);
+
+       /* Contents: "foobar " */
+       gtk_text_buffer_get_end_iter (text_buffer, &iter);
+       gtk_text_buffer_insert (text_buffer, &iter, " ", -1);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 1);
+
+       /* Contents: "foobar foobeer" */
+       gtk_text_buffer_get_end_iter (text_buffer, &iter);
+       gtk_text_buffer_insert (text_buffer, &iter, "foobeer", -1);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 2);
+
+       /* Contents: "foo bar foobeer" */
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &iter, 3);
+       gtk_text_buffer_insert (text_buffer, &iter, " ", -1);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 2);
+
+       /* Contents: "foto bar foobeer" */
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &iter, 2);
+       gtk_text_buffer_insert (text_buffer, &iter, "t", -1);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 1);
+
+       /* Contents: "footo bar foobeer" */
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &iter, 2);
+       gtk_text_buffer_insert (text_buffer, &iter, "o", -1);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 2);
+
+       /* Contents: "foofooto bar foobeer" */
+       gtk_text_buffer_get_start_iter (text_buffer, &iter);
+       gtk_text_buffer_insert (text_buffer, &iter, "foo", -1);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 3);
+
+       /* Contents: "fooTfooto bar foobeer" */
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &iter, 3);
+       gtk_text_buffer_insert (text_buffer, &iter, "T", -1);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 3);
+
+       g_object_unref (source_buffer);
+}
+
+static void
+test_occurrences_count_with_delete (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+       GtkTextIter start;
+       GtkTextIter end;
+       guint occurrences_count;
+
+       gtk_source_buffer_set_search_text (source_buffer, "foo");
+
+       /* Contents: "foo" -> "" */
+       gtk_text_buffer_set_text (text_buffer, "foo", -1);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 1);
+
+       gtk_text_buffer_get_bounds (text_buffer, &start, &end);
+       gtk_text_buffer_delete (text_buffer, &start, &end);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 0);
+
+       /* Contents: "foo" -> "oo" */
+       gtk_text_buffer_set_text (text_buffer, "foo", -1);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 1);
+
+       gtk_text_buffer_get_start_iter (text_buffer, &start);
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &end, 1);
+       gtk_text_buffer_delete (text_buffer, &start, &end);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 0);
+
+       /* Contents: "foobar foobeer" -> "foobar" */
+       gtk_text_buffer_set_text (text_buffer, "foobar foobeer", -1);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 2);
+
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &start, 6);
+       gtk_text_buffer_get_end_iter (text_buffer, &end);
+       gtk_text_buffer_delete (text_buffer, &start, &end);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 1);
+
+       /* Contents: "foo[foo]foo" -> "foofoo" */
+       gtk_text_buffer_set_text (text_buffer, "foofoofoo", -1);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 3);
+
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &start, 3);
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &end, 6);
+       gtk_text_buffer_delete (text_buffer, &start, &end);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 2);
+
+       /* Contents: "fo[of]oo" -> "fooo" */
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &start, 2);
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &end, 4);
+       gtk_text_buffer_delete (text_buffer, &start, &end);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 1);
+
+       /* Contents: "foto" -> "foo" */
+       gtk_text_buffer_set_text (text_buffer, "foto", -1);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 0);
+
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &start, 2);
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &end, 3);
+       gtk_text_buffer_delete (text_buffer, &start, &end);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 1);
+
+       g_object_unref (source_buffer);
+}
+
+static void
+test_occurrences_count_multiple_lines (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+       guint occurrences_count;
+
+       gtk_source_buffer_set_search_text (source_buffer, "world\nhello");
+
+       gtk_text_buffer_set_text (text_buffer, "hello world\nhello world\nhello world\n", -1);
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 2);
+
+       gtk_source_buffer_set_search_text (source_buffer, "world\n");
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 3);
+
+       gtk_source_buffer_set_search_text (source_buffer, "\nhello world\n");
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpuint (occurrences_count, ==, 1);
+
+       g_object_unref (source_buffer);
+}
+
+int
+main (int argc, char **argv)
+{
+       gtk_test_init (&argc, &argv);
+
+       g_test_add_func ("/Search/occurrences-count/simple", test_occurrences_count_simple);
+       g_test_add_func ("/Search/occurrences-count/with-insert", test_occurrences_count_with_insert);
+       g_test_add_func ("/Search/occurrences-count/with-delete", test_occurrences_count_with_delete);
+       g_test_add_func ("/Search/occurrences-count/multiple-lines", test_occurrences_count_multiple_lines);
+
+       return g_test_run ();
+}



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