[gtksourceview] Regex search



commit 55917044c69091dc4e1743d69844b47877a6dd4e
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Thu Jul 11 16:44:08 2013 +0200

    Regex search

 docs/reference/gtksourceview-3.0-sections.txt |    3 +
 gtksourceview/gtksourcebuffer.c               |  137 ++++-
 gtksourceview/gtksourcebuffer.h               |    7 +
 gtksourceview/gtksourcesearch.c               | 1053 +++++++++++++++++++++++--
 gtksourceview/gtksourcesearch.h               |   10 +
 tests/test-search-ui.c                        |   46 +-
 tests/test-search-ui.ui                       |   58 ++-
 tests/test-search.c                           |  622 +++++++++++++++
 8 files changed, 1862 insertions(+), 74 deletions(-)
---
diff --git a/docs/reference/gtksourceview-3.0-sections.txt b/docs/reference/gtksourceview-3.0-sections.txt
index e04cebc..f30fb49 100644
--- a/docs/reference/gtksourceview-3.0-sections.txt
+++ b/docs/reference/gtksourceview-3.0-sections.txt
@@ -46,6 +46,9 @@ 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_set_regex_search
+gtk_source_buffer_get_regex_search
+gtk_source_buffer_get_regex_search_error
 gtk_source_buffer_set_highlight_search
 gtk_source_buffer_get_highlight_search
 gtk_source_buffer_get_search_occurrences_count
diff --git a/gtksourceview/gtksourcebuffer.c b/gtksourceview/gtksourcebuffer.c
index d307764..a8ea187 100644
--- a/gtksourceview/gtksourcebuffer.c
+++ b/gtksourceview/gtksourcebuffer.c
@@ -106,8 +106,9 @@
  *   <para>
  *     You can tune the search with the following properties:
  *     #GtkSourceBuffer:case-sensitive-search,
- *     #GtkSourceBuffer:search-at-word-boundaries and
- *     #GtkSourceBuffer:search-wrap-around.
+ *     #GtkSourceBuffer:search-at-word-boundaries,
+ *     #GtkSourceBuffer:search-wrap-around and
+ *     #GtkSourceBuffer:regex-search.
  *   </para>
  *   <para>
  *     To search forward, use gtk_source_buffer_forward_search() or
@@ -177,7 +178,9 @@ enum {
        PROP_SEARCH_OCCURRENCES_COUNT,
        PROP_CASE_SENSITIVE_SEARCH,
        PROP_SEARCH_AT_WORD_BOUNDARIES,
-       PROP_SEARCH_WRAP_AROUND
+       PROP_SEARCH_WRAP_AROUND,
+       PROP_REGEX_SEARCH,
+       PROP_REGEX_SEARCH_ERROR
 };
 
 struct _GtkSourceBufferPrivate
@@ -410,7 +413,9 @@ gtk_source_buffer_class_init (GtkSourceBufferClass *klass)
        /**
         * GtkSourceBuffer:search-text:
         *
-        * A search string, or %NULL if the search is disabled.
+        * A search string, or %NULL if the search is disabled. If the regular
+        * expression search is enabled, #GtkSourceBuffer:search-text is the
+        * pattern.
         *
         * Since: 3.10
         */
@@ -489,6 +494,40 @@ gtk_source_buffer_class_init (GtkSourceBufferClass *klass)
                                                               TRUE,
                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 
+       /**
+        * GtkSourceBuffer:regex-search:
+        *
+        * Search by regular expressions with #GtkSourceBuffer:search-text as
+        * the pattern.
+        *
+        * Since: 3.10
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_REGEX_SEARCH,
+                                        g_param_spec_boolean ("regex-search",
+                                                              _("Regex search"),
+                                                              _("Search by regular expression"),
+                                                              FALSE,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+       /**
+        * GtkSourceBuffer:regex-search-error:
+        *
+        * If the regex search pattern doesn't follow all the rules,
+        * #GtkSourceBuffer:regex-search-error will be set. If the pattern
+        * is valid, #GtkSourceBuffer:regex-search-error is %NULL.
+        *
+        * Free with g_error_free().
+        *
+        * Since: 3.10
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_REGEX_SEARCH_ERROR,
+                                        g_param_spec_pointer ("regex-search-error",
+                                                              _("Regex search error"),
+                                                              _("Regular expression search error"),
+                                                              G_PARAM_READABLE));
+
        param_types[0] = GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE;
        param_types[1] = GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE;
 
@@ -743,6 +782,11 @@ gtk_source_buffer_set_property (GObject      *object,
                                                            g_value_get_boolean (value));
                        break;
 
+               case PROP_REGEX_SEARCH:
+                       _gtk_source_search_set_regex_enabled (source_buffer->priv->search,
+                                                             g_value_get_boolean (value));
+                       break;
+
                default:
                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                        break;
@@ -822,6 +866,14 @@ gtk_source_buffer_get_property (GObject    *object,
                        g_value_set_boolean (value, _gtk_source_search_get_wrap_around 
(source_buffer->priv->search));
                        break;
 
+               case PROP_REGEX_SEARCH:
+                       g_value_set_boolean (value, _gtk_source_search_get_regex_enabled 
(source_buffer->priv->search));
+                       break;
+
+               case PROP_REGEX_SEARCH_ERROR:
+                       g_value_set_pointer (value, _gtk_source_search_get_regex_error 
(source_buffer->priv->search));
+                       break;
+
                default:
                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                        break;
@@ -2885,6 +2937,75 @@ gtk_source_buffer_get_search_wrap_around (GtkSourceBuffer *buffer)
 }
 
 /**
+ * gtk_source_buffer_set_regex_search:
+ * @buffer: a #GtkSourceBuffer.
+ * @regex: the setting.
+ *
+ * Enables or disables whether to search by regular expressions.
+ * If enabled, the #GtkSourceBuffer:search-text property contains the pattern of
+ * the regular expression.
+ *
+ * See also gtk_source_buffer_get_regex_search_error().
+ *
+ * Since: 3.10
+ */
+void
+gtk_source_buffer_set_regex_search (GtkSourceBuffer *buffer,
+                                   gboolean         regex)
+{
+       gboolean cur_val;
+
+       g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
+
+       regex = regex != FALSE;
+
+       cur_val = _gtk_source_search_get_regex_enabled (buffer->priv->search);
+
+       if (cur_val != regex)
+       {
+               _gtk_source_search_set_regex_enabled (buffer->priv->search, regex);
+
+               g_object_notify (G_OBJECT (buffer), "regex-search");
+       }
+}
+
+/**
+ * gtk_source_buffer_get_regex_search:
+ * @buffer: a #GtkSourceBuffer.
+ *
+ * Returns: whether to search by regular expressions.
+ * Since: 3.10
+ */
+gboolean
+gtk_source_buffer_get_regex_search (GtkSourceBuffer *buffer)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
+
+       return _gtk_source_search_get_regex_enabled (buffer->priv->search);
+}
+
+/**
+ * gtk_source_buffer_get_regex_search_error:
+ * @buffer: a #GtkSourceBuffer.
+ *
+ * Regular expression patterns must follow certain rules. If
+ * #GtkSourceBuffer:search-text breaks a rule, the error can be retrieved with
+ * this function. The error domain is #G_REGEX_ERROR.
+ *
+ * Free the return value with g_error_free().
+ *
+ * Returns: the #GError, or %NULL if the pattern is valid.
+ * Since: 3.10
+ */
+GError *
+gtk_source_buffer_get_regex_search_error (GtkSourceBuffer *buffer)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
+
+       return _gtk_source_search_get_regex_error (buffer->priv->search);
+}
+
+/**
  * gtk_source_buffer_set_highlight_search:
  * @buffer: a #GtkSourceBuffer.
  * @highlight: the setting.
@@ -3175,6 +3296,10 @@ gtk_source_buffer_backward_search_finish (GtkSourceBuffer  *buffer,
  * Replaces a search match by another text. If @match_start and @match_end
  * doesn't correspond to a search match, %FALSE is returned.
  *
+ * For a regular expression replacement, you can check if @replace is valid by
+ * calling g_regex_check_replacement(). The @replace text can contain
+ * backreferences; read the g_regex_replace() documentation for more details.
+ *
  * Returns: whether the match has been replaced.
  * Since: 3.10
  */
@@ -3206,6 +3331,10 @@ gtk_source_buffer_search_replace (GtkSourceBuffer   *buffer,
  * Replaces all search matches by another text. It is a synchronous function, so
  * it can block the user interface.
  *
+ * For a regular expression replacement, you can check if @replace is valid by
+ * calling g_regex_check_replacement(). The @replace text can contain
+ * backreferences; read the g_regex_replace() documentation for more details.
+ *
  * Returns: the number of replaced matches.
  * Since: 3.10
  */
diff --git a/gtksourceview/gtksourcebuffer.h b/gtksourceview/gtksourcebuffer.h
index c95b1d2..e6cf6ec 100644
--- a/gtksourceview/gtksourcebuffer.h
+++ b/gtksourceview/gtksourcebuffer.h
@@ -200,6 +200,13 @@ void                        gtk_source_buffer_set_search_wrap_around               
(GtkSourceBuffer        *buffer,
 
 gboolean                gtk_source_buffer_get_search_wrap_around               (GtkSourceBuffer        
*buffer);
 
+void                    gtk_source_buffer_set_regex_search                     (GtkSourceBuffer        
*buffer,
+                                                                                gboolean                
regex);
+
+gboolean                gtk_source_buffer_get_regex_search                     (GtkSourceBuffer        
*buffer);
+
+GError                 *gtk_source_buffer_get_regex_search_error               (GtkSourceBuffer        
*buffer);
+
 void                    gtk_source_buffer_set_highlight_search                 (GtkSourceBuffer        
*buffer,
                                                                                 gboolean                
highlight);
 
diff --git a/gtksourceview/gtksourcesearch.c b/gtksourceview/gtksourcesearch.c
index bcd2c76..cda77b7 100644
--- a/gtksourceview/gtksourcesearch.c
+++ b/gtksourceview/gtksourcesearch.c
@@ -22,8 +22,9 @@
 #include "gtksourcesearch.h"
 #include "gtksourcebuffer.h"
 #include "gtksourcestylescheme.h"
-#include "gtktextregion.h"
 #include "gtksourcestyle-private.h"
+#include "gtksourceutils.h"
+#include "gtktextregion.h"
 
 #include <string.h>
 
@@ -99,6 +100,73 @@
  * - Rewrite the code to implement the simpler solution explained above :-)
  */
 
+/* Regex search:
+ *
+ * With a regex, we don't know how many lines a match can span. A regex will
+ * most probably match only one line, but a regex can contain something like
+ * "\n*", or the dot metacharacter can also match newlines, with the "?s" option
+ * (see G_REGEX_DOTALL).
+ * Therefore a simple solution is to always begin the search at the beginning of
+ * the document. Only the scan_region is taken into account for scanning the
+ * buffer.
+ *
+ * For non-regex searches, when there is an insertion or deletion in the buffer,
+ * we don't need to re-scan all the buffer. If there is an unmodified match in
+ * the neighborhood, no need to re-scan it. For a regex search, it is more
+ * complicated. An insertion or deletion outside a match can modify a match
+ * located in the neighborhood. Take for example the regex "(aa)+" with the
+ * buffer contents "aaa". There is one occurrence: the first two letters. If we
+ * insert an extra 'a' at the end of the buffer, the occurrence is modified to
+ * take the next two letters. That's why the buffer is re-scanned entirely on
+ * each insertion or deletion in the buffer.
+ *
+ * For searching the matches, the easiest solution is to retrieve all the buffer
+ * contents, and search the occurrences on this big string. But it takes a lot
+ * of memory space. It is better to do multi-segment matching, also called
+ * incremental matching. See the pcrepartial(3) manpage. The matching is done
+ * segment by segment, with the G_REGEX_MATCH_PARTIAL_HARD flag (for reasons
+ * explained in the manpage). We begin by the first segment of the buffer as the
+ * subject string. If a partial match is returned, we append the next segment to
+ * the subject string, and we try again to find a complete match. When a
+ * complete match is returned, we must continue to search the next occurrences.
+ * The max lookbehind of the pattern must be retrieved. The start of the next
+ * subject string is located at max_lookbehind characters before the end of the
+ * previously found match. Similarly, if no match is found (neither a complete
+ * match nor a partial match), we take the next segment, with the last
+ * max_lookbehind characters from the previous segment.
+ *
+ * TODO/idea:
+ * What we would like to support in applications is the incremental search:
+ * while we type the pattern, the buffer is scanned and the matches are
+ * highlighted. When the pattern is not fully typed, strange things can happen,
+ * including a pattern that match the entire buffer. And if the user is
+ * working on a really big file, catastrophe: the UI is blocked!
+ * To avoid this problem, a solution is to search the buffer differently
+ * depending on the situation:
+ * - First situation: the subject string to scan is small enough, we retrieve it
+ *   and scan it directly.
+ * - Second situation: the subject string to scan is too big, it will take
+ *   too much time to retrieve it and scan it directly. We handle this situation
+ *   in three phases: (1) retrieving the subject string, chunks by chunks, in
+ *   several idle loop iterations. (2) Once the subject string is retrieved
+ *   completely, we launch the regex matching in a thread. (3) Once the thread
+ *   is finished, we highlight the matches in the buffer. And voilà.
+ *
+ * Known issue:
+ * To search at word boundaries, \b is added at the beginning and at the
+ * end of the pattern. But \b is not the same as
+ * gtk_text_iter_starts_word() and gtk_text_iter_ends_word(). \b for
+ * example doesn't take the underscore as a word boundary.
+ * Using gtk_text_iter_starts_word() and ends_word() for regex searches
+ * is not easily possible: if the GRegex return a match, but doesn't
+ * start and end a word, maybe a shorter match (for a greedy pattern)
+ * start and end a word, or a longer match (for an ungreedy pattern). To
+ * be able to use the gtk_text_iter_starts_word() and ends_word()
+ * functions for regex search, g_regex_match_all_full() must be used, to
+ * retrieve _all_ matches, and test the word boundaries until a match is
+ * found at word boundaries.
+ */
+
 /*
 #define ENABLE_DEBUG
 */
@@ -142,9 +210,12 @@ struct _GtkSourceSearchPrivate
        /* State of the search. If text is NULL, the search is disabled. */
        gchar *text;
        gint text_nb_lines;
+       GRegex *regex;
+       GError *regex_error;
        GtkTextSearchFlags flags;
        guint at_word_boundaries : 1;
        guint wrap_around : 1;
+       guint regex_enabled : 1;
        guint highlight : 1;
 };
 
@@ -391,6 +462,174 @@ clear_search (GtkSourceSearch *search)
        search->priv->occurrences_count = 0;
 }
 
+static void
+regex_search_get_real_start (GtkSourceSearch   *search,
+                            const GtkTextIter *start,
+                            GtkTextIter       *real_start,
+                            gint              *start_pos)
+{
+       gint max_lookbehind = g_regex_get_max_lookbehind (search->priv->regex);
+
+       *real_start = *start;
+
+       for (*start_pos = 0; *start_pos < max_lookbehind; (*start_pos)++)
+       {
+               if (!gtk_text_iter_backward_char (real_start))
+               {
+                       break;
+               }
+       }
+}
+
+static GRegexMatchFlags
+regex_search_get_match_options (const GtkTextIter *real_start,
+                               const GtkTextIter *end)
+{
+       GRegexMatchFlags match_options = 0;
+
+       if (!gtk_text_iter_starts_line (real_start))
+       {
+               match_options |= G_REGEX_MATCH_NOTBOL;
+       }
+
+       if (!gtk_text_iter_ends_line (end))
+       {
+               match_options |= G_REGEX_MATCH_NOTEOL;
+       }
+
+       return match_options;
+}
+
+/* Get the @match_start and @match_end iters of the @match_info.
+ * g_match_info_fetch_pos() returns byte positions. To get the iters, we need to
+ * know the number of UTF-8 characters. A GMatchInfo can contain several matches
+ * (with g_match_info_next()). So instead of calling g_utf8_strlen() each time
+ * at the beginning of @subject, @iter and @iter_byte_pos are used to remember
+ * where g_utf8_strlen() stopped.
+ */
+static gboolean
+regex_search_fetch_match (GMatchInfo  *match_info,
+                         const gchar *subject,
+                         gssize       subject_length,
+                         GtkTextIter *iter,
+                         gint        *iter_byte_pos,
+                         GtkTextIter *match_start,
+                         GtkTextIter *match_end)
+{
+       gint start_byte_pos;
+       gint end_byte_pos;
+       gint nb_chars;
+
+       g_assert (*iter_byte_pos <= subject_length);
+       g_assert (match_start != NULL);
+       g_assert (match_end != NULL);
+
+       if (!g_match_info_matches (match_info))
+       {
+               return FALSE;
+       }
+
+       if (!g_match_info_fetch_pos (match_info, 0, &start_byte_pos, &end_byte_pos))
+       {
+               g_warning ("Impossible to fetch regex match position.");
+               return FALSE;
+       }
+
+       g_assert (start_byte_pos < subject_length);
+       g_assert (end_byte_pos <= subject_length);
+       g_assert (*iter_byte_pos <= start_byte_pos);
+       g_assert (start_byte_pos < end_byte_pos);
+
+       nb_chars = g_utf8_strlen (subject + *iter_byte_pos,
+                                 start_byte_pos - *iter_byte_pos);
+
+       *match_start = *iter;
+       gtk_text_iter_forward_chars (match_start, nb_chars);
+
+       nb_chars = g_utf8_strlen (subject + start_byte_pos,
+                                 end_byte_pos - start_byte_pos);
+
+       *match_end = *match_start;
+       gtk_text_iter_forward_chars (match_end, nb_chars);
+
+       *iter = *match_end;
+       *iter_byte_pos = end_byte_pos;
+
+       return TRUE;
+}
+
+static gboolean
+basic_forward_regex_search (GtkSourceSearch   *search,
+                           const GtkTextIter *start_at,
+                           GtkTextIter       *match_start,
+                           GtkTextIter       *match_end,
+                           const GtkTextIter *limit)
+{
+       GtkTextIter real_start;
+       GtkTextIter end;
+       gint start_pos;
+       gchar *subject;
+       gssize subject_length;
+       GRegexMatchFlags match_options;
+       GMatchInfo *match_info;
+       GError *error = NULL;
+       GtkTextIter iter;
+       gint iter_byte_pos;
+       gboolean found;
+
+       if (search->priv->regex == NULL)
+       {
+               return FALSE;
+       }
+
+       regex_search_get_real_start (search, start_at, &real_start, &start_pos);
+
+       if (limit == NULL)
+       {
+               gtk_text_buffer_get_end_iter (search->priv->buffer, &end);
+       }
+       else
+       {
+               end = *limit;
+       }
+
+       match_options = regex_search_get_match_options (&real_start, &end);
+
+       subject = gtk_text_iter_get_visible_text (&real_start, &end);
+       subject_length = strlen (subject);
+
+       g_regex_match_full (search->priv->regex,
+                           subject,
+                           subject_length,
+                           start_pos,
+                           match_options,
+                           &match_info,
+                           &error);
+
+       iter = real_start;
+       iter_byte_pos = 0;
+
+       found = regex_search_fetch_match (match_info,
+                                         subject,
+                                         subject_length,
+                                         &iter,
+                                         &iter_byte_pos,
+                                         match_start,
+                                         match_end);
+
+       if (error != NULL)
+       {
+               g_warning ("Regex matching error: %s", error->message);
+               g_error_free (error);
+               found = FALSE;
+       }
+
+       g_free (subject);
+       g_match_info_free (match_info);
+
+       return found;
+}
+
 static gboolean
 basic_forward_search (GtkSourceSearch   *search,
                      const GtkTextIter *iter,
@@ -405,6 +644,15 @@ basic_forward_search (GtkSourceSearch   *search,
                return FALSE;
        }
 
+       if (search->priv->regex_enabled)
+       {
+               return basic_forward_regex_search (search,
+                                                  iter,
+                                                  match_start,
+                                                  match_end,
+                                                  limit);
+       }
+
        while (TRUE)
        {
                gboolean found = gtk_text_iter_forward_search (&begin_search,
@@ -429,6 +677,94 @@ basic_forward_search (GtkSourceSearch   *search,
        }
 }
 
+/* We fake the backward regex search by doing a forward search, and taking the
+ * last match.
+ */
+static gboolean
+basic_backward_regex_search (GtkSourceSearch   *search,
+                            const GtkTextIter *start_at,
+                            GtkTextIter       *match_start,
+                            GtkTextIter       *match_end,
+                            const GtkTextIter *limit)
+{
+       GtkTextIter start;
+       GtkTextIter real_start;
+       GtkTextIter end;
+       gint start_pos;
+       gchar *subject;
+       gssize subject_length;
+       GRegexMatchFlags match_options;
+       GMatchInfo *match_info;
+       GError *error = NULL;
+       GtkTextIter iter;
+       gint iter_byte_pos;
+       gboolean found;
+       GtkTextIter tmp_match_start;
+       GtkTextIter tmp_match_end;
+
+       if (search->priv->regex == NULL)
+       {
+               return FALSE;
+       }
+
+       if (limit == NULL)
+       {
+               gtk_text_buffer_get_start_iter (search->priv->buffer, &start);
+       }
+       else
+       {
+               start = *limit;
+       }
+
+       regex_search_get_real_start (search, &start, &real_start, &start_pos);
+
+       end = *start_at;
+
+       match_options = regex_search_get_match_options (&real_start, &end);
+
+       subject = gtk_text_iter_get_visible_text (&real_start, &end);
+       subject_length = strlen (subject);
+
+       g_regex_match_full (search->priv->regex,
+                           subject,
+                           subject_length,
+                           start_pos,
+                           match_options,
+                           &match_info,
+                           &error);
+
+       iter = real_start;
+       iter_byte_pos = 0;
+
+       while (regex_search_fetch_match (match_info,
+                                        subject,
+                                        subject_length,
+                                        &iter,
+                                        &iter_byte_pos,
+                                        &tmp_match_start,
+                                        &tmp_match_end))
+       {
+               found = TRUE;
+
+               *match_start = tmp_match_start;
+               *match_end = tmp_match_end;
+
+               g_match_info_next (match_info, &error);
+       }
+
+       if (error != NULL)
+       {
+               g_warning ("Regex matching error: %s", error->message);
+               g_error_free (error);
+               found = FALSE;
+       }
+
+       g_free (subject);
+       g_match_info_free (match_info);
+
+       return found;
+}
+
 static gboolean
 basic_backward_search (GtkSourceSearch   *search,
                       const GtkTextIter *iter,
@@ -443,6 +779,15 @@ basic_backward_search (GtkSourceSearch   *search,
                return FALSE;
        }
 
+       if (search->priv->regex_enabled)
+       {
+               return basic_backward_regex_search (search,
+                                                   iter,
+                                                   match_start,
+                                                   match_end,
+                                                   limit);
+       }
+
        while (TRUE)
        {
                gboolean found = gtk_text_iter_backward_search (&begin_search,
@@ -487,6 +832,7 @@ smart_forward_search_async_step (GtkSourceSearch *search,
 {
        GtkTextIter iter = *start_at;
        GtkTextIter limit;
+       GtkTextIter region_start = *start_at;
        GtkTextRegion *region = NULL;
        ForwardBackwardData *task_data;
 
@@ -518,30 +864,38 @@ smart_forward_search_async_step (GtkSourceSearch *search,
        {
                gtk_text_iter_forward_to_tag_toggle (&iter, search->priv->found_tag);
        }
+       else if (!gtk_text_iter_begins_tag (&iter, search->priv->found_tag))
+       {
+               gtk_text_iter_backward_to_tag_toggle (&iter, search->priv->found_tag);
+               region_start = iter;
+       }
 
        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);
+               region = gtk_text_region_intersect (search->priv->scan_region, &region_start, &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)
+               while (basic_forward_search (search, &iter, &match_start, &match_end, &limit))
                {
+                       if (gtk_text_iter_compare (&match_start, start_at) < 0)
+                       {
+                               iter = match_end;
+                               continue;
+                       }
+
                        task_data = g_slice_new0 (ForwardBackwardData);
                        task_data->found = TRUE;
                        task_data->match_start = match_start;
@@ -616,6 +970,7 @@ smart_backward_search_async_step (GtkSourceSearch *search,
 {
        GtkTextIter iter = *start_at;
        GtkTextIter limit;
+       GtkTextIter region_end = *start_at;
        GtkTextRegion *region = NULL;
        ForwardBackwardData *task_data;
 
@@ -649,30 +1004,38 @@ smart_backward_search_async_step (GtkSourceSearch *search,
        {
                gtk_text_iter_backward_to_tag_toggle (&iter, search->priv->found_tag);
        }
+       else if (gtk_text_iter_has_tag (&iter, search->priv->found_tag))
+       {
+               gtk_text_iter_forward_to_tag_toggle (&iter, search->priv->found_tag);
+               region_end = iter;
+       }
 
        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);
+               region = gtk_text_region_intersect (search->priv->scan_region, &limit, &region_end);
        }
 
        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)
+               while (basic_backward_search (search, &iter, &match_start, &match_end, &limit))
                {
+                       if (gtk_text_iter_compare (start_at, &match_end) < 0)
+                       {
+                               iter = match_start;
+                               continue;
+                       }
+
                        task_data = g_slice_new0 (ForwardBackwardData);
                        task_data->found = TRUE;
                        task_data->match_start = match_start;
@@ -890,11 +1253,15 @@ smart_forward_search_without_scanning (GtkSourceSearch   *search,
                                       GtkTextIter       *match_end,
                                       const GtkTextIter *stop_at)
 {
-       GtkTextIter iter = *start_at;
+       GtkTextIter iter;
 
        g_assert (start_at != NULL);
+       g_assert (match_start != NULL);
+       g_assert (match_end != NULL);
        g_assert (stop_at != NULL);
 
+       iter = *start_at;
+
        if (search->priv->text == NULL)
        {
                return FALSE;
@@ -913,6 +1280,10 @@ smart_forward_search_without_scanning (GtkSourceSearch   *search,
                {
                        gtk_text_iter_forward_to_tag_toggle (&iter, search->priv->found_tag);
                }
+               else if (!gtk_text_iter_begins_tag (&iter, search->priv->found_tag))
+               {
+                       gtk_text_iter_backward_to_tag_toggle (&iter, search->priv->found_tag);
+               }
 
                limit = iter;
                gtk_text_iter_forward_to_tag_toggle (&limit, search->priv->found_tag);
@@ -922,9 +1293,14 @@ smart_forward_search_without_scanning (GtkSourceSearch   *search,
                        limit = *stop_at;
                }
 
-               if (basic_forward_search (search, &iter, match_start, match_end, &limit))
+               while (basic_forward_search (search, &iter, match_start, match_end, &limit))
                {
-                       return TRUE;
+                       if (gtk_text_iter_compare (start_at, match_start) <= 0)
+                       {
+                               return TRUE;
+                       }
+
+                       iter = *match_end;
                }
 
                iter = limit;
@@ -1175,20 +1551,11 @@ scan_region_backward (GtkSourceSearch *search,
 }
 
 static void
-scan_task_region (GtkSourceSearch *search)
+resume_task (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);
@@ -1213,8 +1580,25 @@ scan_task_region (GtkSourceSearch *search)
        }
 }
 
+static void
+scan_task_region (GtkSourceSearch *search)
+{
+       ForwardBackwardData *task_data = g_task_get_task_data (search->priv->task);
+
+       if (task_data->is_forward)
+       {
+               scan_region_forward (search, search->priv->task_region);
+       }
+       else
+       {
+               scan_region_backward (search, search->priv->task_region);
+       }
+
+       resume_task (search);
+}
+
 static gboolean
-idle_scan_cb (GtkSourceSearch *search)
+idle_scan_normal_search (GtkSourceSearch *search)
 {
        if (search->priv->high_priority_region != NULL)
        {
@@ -1256,6 +1640,314 @@ idle_scan_cb (GtkSourceSearch *search)
        return G_SOURCE_CONTINUE;
 }
 
+/* Just remove the found_tag's located in the high-priority region. For big
+ * documents, if the pattern is modified, it can take some time to re-scan all
+ * the buffer, so it's better to clear the highlighting as soon as possible. If
+ * the highlighting is not cleared, the user can wrongly think that the new
+ * pattern matches the old occurrences.
+ * The drawback of clearing the highlighting is that for small documents, there
+ * is some flickering.
+ */
+static void
+regex_search_handle_high_priority_region (GtkSourceSearch *search)
+{
+       GtkTextIter start;
+       GtkTextIter end;
+       GtkTextRegion *region;
+       GtkTextRegionIterator region_iter;
+       gint nb_subregions = gtk_text_region_subregions (search->priv->high_priority_region);
+
+       if (nb_subregions == 0)
+       {
+               return;
+       }
+
+       gtk_text_region_nth_subregion (search->priv->high_priority_region,
+                                      0,
+                                      &start,
+                                      NULL);
+
+       gtk_text_region_nth_subregion (search->priv->high_priority_region,
+                                      nb_subregions - 1,
+                                      NULL,
+                                      &end);
+
+       region = gtk_text_region_intersect (search->priv->scan_region,
+                                           &start,
+                                           &end);
+
+       gtk_text_region_get_iterator (region, &region_iter, 0);
+
+       while (!gtk_text_region_iterator_is_end (&region_iter))
+       {
+               GtkTextIter subregion_start;
+               GtkTextIter subregion_end;
+
+               gtk_text_region_iterator_get_subregion (&region_iter,
+                                                       &subregion_start,
+                                                       &subregion_end);
+
+               gtk_text_buffer_remove_tag (search->priv->buffer,
+                                           search->priv->found_tag,
+                                           &subregion_start,
+                                           &subregion_end);
+
+               gtk_text_region_iterator_next (&region_iter);
+       }
+
+       gtk_text_region_destroy (region, TRUE);
+}
+
+/* Returns TRUE if the segment is finished, and FALSE on partial match. */
+static gboolean
+regex_search_scan_segment (GtkSourceSearch   *search,
+                          const GtkTextIter *segment_start,
+                          const GtkTextIter *segment_end,
+                          GtkTextIter       *stopped_at)
+{
+       GtkTextIter real_start;
+       gint start_pos;
+       gchar *subject;
+       gssize subject_length;
+       GRegexMatchFlags match_options;
+       GMatchInfo *match_info;
+       GError *error = NULL;
+       GtkTextIter iter;
+       gint iter_byte_pos;
+       gboolean segment_finished;
+       GtkTextIter match_start;
+       GtkTextIter match_end;
+
+       g_assert (stopped_at != NULL);
+
+       gtk_text_buffer_remove_tag (search->priv->buffer,
+                                   search->priv->found_tag,
+                                   segment_start,
+                                   segment_end);
+
+       if (search->priv->regex == NULL)
+       {
+               *stopped_at = *segment_end;
+               return TRUE;
+       }
+
+       regex_search_get_real_start (search,
+                                    segment_start,
+                                    &real_start,
+                                    &start_pos);
+
+       DEBUG ({
+              g_print ("\n*** regex search - scan segment ***\n");
+              g_print ("start position in the subject: %d\n", start_pos);
+       });
+
+       match_options = regex_search_get_match_options (&real_start, segment_end);
+
+       if (match_options & G_REGEX_MATCH_NOTBOL)
+       {
+               DEBUG ({
+                      g_print ("match notbol\n");
+               });
+       }
+
+       if (match_options & G_REGEX_MATCH_NOTEOL)
+       {
+               DEBUG ({
+                      g_print ("match noteol\n");
+               });
+       }
+
+       if (!gtk_text_iter_is_end (segment_end))
+       {
+               match_options |= G_REGEX_MATCH_PARTIAL_HARD;
+
+               DEBUG ({
+                      g_print ("match partial hard\n");
+               });
+       }
+
+       subject = gtk_text_iter_get_visible_text (&real_start, segment_end);
+       subject_length = strlen (subject);
+
+       DEBUG ({
+              gchar *subject_escaped = gtk_source_utils_escape_search_text (subject);
+              g_print ("subject (escaped): %s\n", subject_escaped);
+              g_free (subject_escaped);
+       });
+
+       g_regex_match_full (search->priv->regex,
+                           subject,
+                           subject_length,
+                           start_pos,
+                           match_options,
+                           &match_info,
+                           &error);
+
+       iter = real_start;
+       iter_byte_pos = 0;
+
+       while (regex_search_fetch_match (match_info,
+                                        subject,
+                                        subject_length,
+                                        &iter,
+                                        &iter_byte_pos,
+                                        &match_start,
+                                        &match_end))
+       {
+               gtk_text_buffer_apply_tag (search->priv->buffer,
+                                          search->priv->found_tag,
+                                          &match_start,
+                                          &match_end);
+
+               DEBUG ({
+                        gchar *match_text = gtk_text_iter_get_visible_text (&match_start, &match_end);
+                        gchar *match_escaped = gtk_source_utils_escape_search_text (match_text);
+                        g_print ("match found (escaped): %s\n", match_escaped);
+                        g_free (match_text);
+                        g_free (match_escaped);
+               });
+
+               search->priv->occurrences_count++;
+
+               g_match_info_next (match_info, &error);
+       }
+
+       if (error != NULL)
+       {
+               g_warning ("Regex matching error: %s", error->message);
+               g_error_free (error);
+       }
+
+       if (g_match_info_is_partial_match (match_info))
+       {
+               *stopped_at = iter;
+               segment_finished = FALSE;
+
+               DEBUG ({
+                      g_print ("partial match\n");
+               });
+       }
+       else
+       {
+               *stopped_at = *segment_end;
+               segment_finished = TRUE;
+       }
+
+       g_free (subject);
+       g_match_info_free (match_info);
+
+       return segment_finished;
+}
+
+static void
+regex_search_scan_chunk (GtkSourceSearch   *search,
+                        const GtkTextIter *chunk_start,
+                        const GtkTextIter *chunk_end)
+{
+       GtkTextIter segment_start = *chunk_start;
+
+       if (search->priv->found_tag == NULL)
+       {
+               init_found_tag (search);
+       }
+
+       while (gtk_text_iter_compare (&segment_start, chunk_end) < 0)
+       {
+               GtkTextIter segment_end;
+               GtkTextIter stopped_at;
+               gint nb_lines = 1;
+
+               segment_end = segment_start;
+               gtk_text_iter_forward_line (&segment_end);
+
+               while (!regex_search_scan_segment (search,
+                                                  &segment_start,
+                                                  &segment_end,
+                                                  &stopped_at))
+               {
+                       segment_start = stopped_at;
+                       gtk_text_iter_forward_lines (&segment_end, nb_lines);
+                       nb_lines <<= 1;
+               }
+
+               segment_start = stopped_at;
+       }
+
+       gtk_text_region_subtract (search->priv->scan_region, chunk_start, &segment_start);
+
+       if (search->priv->task_region != NULL)
+       {
+               gtk_text_region_subtract (search->priv->task_region, chunk_start, &segment_start);
+       }
+}
+
+static void
+regex_search_scan_next_chunk (GtkSourceSearch *search)
+{
+       GtkTextIter chunk_start;
+       GtkTextIter chunk_end;
+
+       if (is_text_region_empty (search->priv->scan_region))
+       {
+               return;
+       }
+
+       gtk_text_region_nth_subregion (search->priv->scan_region, 0, &chunk_start, NULL);
+
+       chunk_end = chunk_start;
+       gtk_text_iter_forward_lines (&chunk_end, SCAN_BATCH_SIZE);
+
+       regex_search_scan_chunk (search, &chunk_start, &chunk_end);
+}
+
+static gboolean
+idle_scan_regex_search (GtkSourceSearch *search)
+{
+       if (search->priv->high_priority_region != NULL)
+       {
+               regex_search_handle_high_priority_region (search);
+
+               gtk_text_region_destroy (search->priv->high_priority_region, TRUE);
+               search->priv->high_priority_region = NULL;
+
+               return G_SOURCE_CONTINUE;
+       }
+
+       regex_search_scan_next_chunk (search);
+
+       if (search->priv->task != NULL &&
+           is_text_region_empty (search->priv->task_region))
+       {
+               resume_task (search);
+               return G_SOURCE_CONTINUE;
+       }
+
+       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 gboolean
+idle_scan_cb (GtkSourceSearch *search)
+{
+       return search->priv->regex_enabled ?
+              idle_scan_regex_search (search) :
+              idle_scan_normal_search (search);
+}
+
 static void
 install_idle_scan (GtkSourceSearch *search)
 {
@@ -1274,33 +1966,42 @@ smart_forward_search_step (GtkSourceSearch *search,
 {
        GtkTextIter iter = *start_at;
        GtkTextIter limit;
+       GtkTextIter region_start = *start_at;
        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);
        }
+       else if (!gtk_text_iter_begins_tag (&iter, search->priv->found_tag))
+       {
+               gtk_text_iter_backward_to_tag_toggle (&iter, search->priv->found_tag);
+               region_start = iter;
+       }
 
        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);
+               region = gtk_text_region_intersect (search->priv->scan_region, &region_start, &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)
+               while (basic_forward_search (search, &iter, match_start, match_end, &limit))
                {
-                       return TRUE;
+                       if (gtk_text_iter_compare (start_at, match_start) <= 0)
+                       {
+                               return TRUE;
+                       }
+
+                       iter = *match_end;
                }
 
                *start_at = limit;
@@ -1351,6 +2052,7 @@ smart_backward_search_step (GtkSourceSearch *search,
 {
        GtkTextIter iter = *start_at;
        GtkTextIter limit;
+       GtkTextIter region_end = *start_at;
        GtkTextRegion *region = NULL;
 
        if (gtk_text_iter_begins_tag (&iter, search->priv->found_tag) ||
@@ -1359,27 +2061,35 @@ smart_backward_search_step (GtkSourceSearch *search,
        {
                gtk_text_iter_backward_to_tag_toggle (&iter, search->priv->found_tag);
        }
+       else if (gtk_text_iter_has_tag (&iter, search->priv->found_tag))
+       {
+               gtk_text_iter_forward_to_tag_toggle (&iter, search->priv->found_tag);
+               region_end = iter;
+       }
 
        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);
+               region = gtk_text_region_intersect (search->priv->scan_region, &limit, &region_end);
        }
 
        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)
+               while (basic_backward_search (search, &iter, match_start, match_end, &limit))
                {
-                       return TRUE;
+                       if (gtk_text_iter_compare (match_end, start_at) <= 0)
+                       {
+                               return TRUE;
+                       }
+
+                       iter = *match_start;
                }
 
                *start_at = limit;
@@ -1458,6 +2168,63 @@ add_subregion_to_scan (GtkSourceSearch   *search,
 }
 
 static void
+update_regex (GtkSourceSearch *search)
+{
+       gboolean regex_error_changed = FALSE;
+
+       if (search->priv->regex != NULL)
+       {
+               g_regex_unref (search->priv->regex);
+               search->priv->regex = NULL;
+       }
+
+       if (search->priv->regex_error != NULL)
+       {
+               g_error_free (search->priv->regex_error);
+               search->priv->regex_error = NULL;
+               regex_error_changed = TRUE;
+       }
+
+       if (search->priv->regex_enabled && search->priv->text != NULL)
+       {
+               GRegexCompileFlags compile_flags = G_REGEX_OPTIMIZE | G_REGEX_MULTILINE;
+               gchar *pattern = search->priv->text;
+
+               search->priv->text_nb_lines = 0;
+
+               if (search->priv->flags & GTK_TEXT_SEARCH_CASE_INSENSITIVE)
+               {
+                       compile_flags |= G_REGEX_CASELESS;
+               }
+
+               if (search->priv->at_word_boundaries)
+               {
+                       pattern = g_strdup_printf ("\\b%s\\b", search->priv->text);
+               }
+
+               search->priv->regex = g_regex_new (pattern,
+                                                  compile_flags,
+                                                  G_REGEX_MATCH_NOTEMPTY,
+                                                  &search->priv->regex_error);
+
+               if (search->priv->regex_error != NULL)
+               {
+                       regex_error_changed = TRUE;
+               }
+
+               if (search->priv->at_word_boundaries)
+               {
+                       g_free (pattern);
+               }
+       }
+
+       if (regex_error_changed)
+       {
+               g_object_notify (G_OBJECT (search->priv->buffer), "regex-search-error");
+       }
+}
+
+static void
 update (GtkSourceSearch *search)
 {
        GtkTextIter start;
@@ -1484,7 +2251,8 @@ insert_text_before_cb (GtkSourceSearch *search,
 {
        clear_task (search);
 
-       if (search->priv->text != NULL)
+       if (!search->priv->regex_enabled &&
+           search->priv->text != NULL)
        {
                GtkTextIter start = *location;
                GtkTextIter end = *location;
@@ -1500,15 +2268,22 @@ insert_text_after_cb (GtkSourceSearch *search,
                      gchar           *text,
                      gint             length)
 {
-       GtkTextIter start;
-       GtkTextIter end;
+       if (search->priv->regex_enabled)
+       {
+               update (search);
+       }
+       else
+       {
+               GtkTextIter start;
+               GtkTextIter end;
 
-       start = end = *location;
+               start = end = *location;
 
-       gtk_text_iter_backward_chars (&start,
-                                     g_utf8_strlen (text, length));
+               gtk_text_iter_backward_chars (&start,
+                                             g_utf8_strlen (text, length));
 
-       add_subregion_to_scan (search, &start, &end);
+               add_subregion_to_scan (search, &start, &end);
+       }
 }
 
 static void
@@ -1521,6 +2296,11 @@ delete_range_before_cb (GtkSourceSearch *search,
 
        clear_task (search);
 
+       if (search->priv->regex_enabled)
+       {
+               return;
+       }
+
        gtk_text_buffer_get_bounds (search->priv->buffer, &start_buffer, &end_buffer);
 
        if (gtk_text_iter_equal (delete_start, &start_buffer) &&
@@ -1549,7 +2329,14 @@ delete_range_after_cb (GtkSourceSearch *search,
                       GtkTextIter     *start,
                       GtkTextIter     *end)
 {
-       add_subregion_to_scan (search, start, end);
+       if (search->priv->regex_enabled)
+       {
+               update (search);
+       }
+       else
+       {
+               add_subregion_to_scan (search, start, end);
+       }
 }
 
 static void
@@ -1604,6 +2391,16 @@ _gtk_source_search_finalize (GObject *object)
 
        g_free (search->priv->text);
 
+       if (search->priv->regex != NULL)
+       {
+               g_regex_unref (search->priv->regex);
+       }
+
+       if (search->priv->regex_error != NULL)
+       {
+               g_error_free (search->priv->regex_error);
+       }
+
        G_OBJECT_CLASS (_gtk_source_search_parent_class)->finalize (object);
 }
 
@@ -1689,8 +2486,16 @@ _gtk_source_search_set_text (GtkSourceSearch *search,
                search->priv->text = g_strdup (text);
        }
 
-       search->priv->text_nb_lines = compute_number_of_lines (search->priv->text);
+       if (search->priv->regex_enabled)
+       {
+               search->priv->text_nb_lines = 0;
+       }
+       else
+       {
+               search->priv->text_nb_lines = compute_number_of_lines (search->priv->text);
+       }
 
+       update_regex (search);
        update (search);
 }
 
@@ -1717,6 +2522,7 @@ _gtk_source_search_set_case_sensitive (GtkSourceSearch *search,
                search->priv->flags |= GTK_TEXT_SEARCH_CASE_INSENSITIVE;
        }
 
+       update_regex (search);
        update (search);
 }
 
@@ -1735,6 +2541,8 @@ _gtk_source_search_set_at_word_boundaries (GtkSourceSearch *search,
        g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
 
        search->priv->at_word_boundaries = at_word_boundaries;
+
+       update_regex (search);
        update (search);
 }
 
@@ -1765,6 +2573,39 @@ _gtk_source_search_get_wrap_around (GtkSourceSearch *search)
 }
 
 void
+_gtk_source_search_set_regex_enabled (GtkSourceSearch *search,
+                                     gboolean         regex_enabled)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
+
+       search->priv->regex_enabled = regex_enabled;
+
+       update_regex (search);
+       update (search);
+}
+
+gboolean
+_gtk_source_search_get_regex_enabled (GtkSourceSearch *search)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), FALSE);
+
+       return search->priv->regex_enabled;
+}
+
+GError *
+_gtk_source_search_get_regex_error (GtkSourceSearch *search)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), NULL);
+
+       if (search->priv->regex_error == NULL)
+       {
+               return NULL;
+       }
+
+       return g_error_copy (search->priv->regex_error);
+}
+
+void
 _gtk_source_search_set_highlight (GtkSourceSearch *search,
                                  gboolean         highlight)
 {
@@ -1816,11 +2657,11 @@ _gtk_source_search_get_occurrence_position (GtkSourceSearch   *search,
 
        /* Verify that the occurrence is correct. */
 
-       found = basic_forward_search (search,
-                                     match_start,
-                                     &m_start,
-                                     &m_end,
-                                     match_end);
+       found = smart_forward_search_without_scanning (search,
+                                                      match_start,
+                                                      &m_start,
+                                                      &m_end,
+                                                      match_end);
 
        if (!found ||
            !gtk_text_iter_equal (match_start, &m_start) ||
@@ -1897,12 +2738,7 @@ _gtk_source_search_update_highlight (GtkSourceSearch   *search,
                return;
        }
 
-       if (synchronous)
-       {
-               scan_all_region (search, region_to_highlight);
-               gtk_text_region_destroy (region_to_highlight, TRUE);
-       }
-       else
+       if (!synchronous)
        {
                if (search->priv->high_priority_region != NULL)
                {
@@ -1916,6 +2752,24 @@ _gtk_source_search_update_highlight (GtkSourceSearch   *search,
 
                search->priv->high_priority_region = region_to_highlight;
                install_idle_scan (search);
+               return;
+       }
+
+       if (search->priv->regex_enabled)
+       {
+               GtkTextIter start;
+
+               gtk_text_region_nth_subregion (search->priv->scan_region,
+                                              0,
+                                              &start,
+                                              NULL);
+
+               regex_search_scan_chunk (search, &start, end);
+       }
+       else
+       {
+               scan_all_region (search, region_to_highlight);
+               gtk_text_region_destroy (region_to_highlight, TRUE);
        }
 }
 
@@ -2079,6 +2933,55 @@ _gtk_source_search_backward_finish (GtkSourceSearch  *search,
                                                  error);
 }
 
+/* Returns %TRUE if replaced. */
+static gboolean
+regex_replace (GtkSourceSearch *search,
+              GtkTextIter     *match_start,
+              GtkTextIter     *match_end,
+              const gchar     *replace)
+{
+       GtkTextIter real_start;
+       gint start_pos;
+       gchar *subject;
+       gchar *subject_replaced;
+       GRegexMatchFlags match_options;
+       GError *error = NULL;
+
+       g_assert (search->priv->regex != NULL);
+
+       regex_search_get_real_start (search, match_start, &real_start, &start_pos);
+
+       subject = gtk_text_iter_get_visible_text (&real_start, match_end);
+
+       match_options = regex_search_get_match_options (&real_start, match_end);
+
+       subject_replaced = g_regex_replace (search->priv->regex,
+                                           subject,
+                                           -1,
+                                           start_pos,
+                                           replace,
+                                           match_options,
+                                           &error);
+
+       g_free (subject);
+
+       if (error != NULL)
+       {
+               g_warning ("Regex replace error: %s", error->message);
+               g_error_free (error);
+               g_free (subject_replaced);
+               return FALSE;
+       }
+
+       gtk_text_buffer_begin_user_action (search->priv->buffer);
+       gtk_text_buffer_delete (search->priv->buffer, match_start, match_end);
+       gtk_text_buffer_insert (search->priv->buffer, match_end, subject_replaced, -1);
+       gtk_text_buffer_end_user_action (search->priv->buffer);
+
+       g_free (subject_replaced);
+       return TRUE;
+}
+
 gboolean
 _gtk_source_search_replace (GtkSourceSearch   *search,
                            const GtkTextIter *match_start,
@@ -2099,7 +3002,10 @@ _gtk_source_search_replace (GtkSourceSearch   *search,
                return FALSE;
        }
 
-       basic_forward_search (search, match_start, &start, &end, match_end);
+       if (!smart_forward_search (search, match_start, &start, &end))
+       {
+               return FALSE;
+       }
 
        if (!gtk_text_iter_equal (match_start, &start) ||
            !gtk_text_iter_equal (match_end, &end))
@@ -2107,11 +3013,14 @@ _gtk_source_search_replace (GtkSourceSearch   *search,
                return FALSE;
        }
 
-       gtk_text_buffer_begin_user_action (search->priv->buffer);
+       if (search->priv->regex_enabled)
+       {
+               return regex_replace (search, &start, &end, replace);
+       }
 
+       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;
@@ -2127,6 +3036,7 @@ _gtk_source_search_replace_all (GtkSourceSearch *search,
        GtkTextIter match_end;
        guint nb_matches_replaced = 0;
        gboolean highlight_matching_brackets;
+       gboolean has_regex_references = FALSE;
 
        g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), 0);
        g_return_val_if_fail (replace != NULL, 0);
@@ -2136,6 +3046,12 @@ _gtk_source_search_replace_all (GtkSourceSearch *search,
                return 0;
        }
 
+       if (search->priv->regex_enabled &&
+           !g_regex_check_replacement (replace, &has_regex_references, NULL))
+       {
+               return 0;
+       }
+
        g_signal_handlers_block_by_func (search->priv->buffer, insert_text_before_cb, search);
        g_signal_handlers_block_by_func (search->priv->buffer, insert_text_after_cb, search);
        g_signal_handlers_block_by_func (search->priv->buffer, delete_range_before_cb, search);
@@ -2153,11 +3069,26 @@ _gtk_source_search_replace_all (GtkSourceSearch *search,
 
        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);
+               gboolean replaced = FALSE;
+
+               if (has_regex_references)
+               {
+                       replaced = regex_replace (search, &match_start, &match_end, replace);
+               }
+               else
+               {
+                       gtk_text_buffer_delete (search->priv->buffer, &match_start, &match_end);
+                       gtk_text_buffer_insert (search->priv->buffer, &match_end, replace, replace_length);
+
+                       replaced = TRUE;
+               }
+
+               if (replaced)
+               {
+                       nb_matches_replaced++;
+               }
 
                iter = match_end;
-               nb_matches_replaced++;
        }
 
        gtk_text_buffer_end_user_action (search->priv->buffer);
diff --git a/gtksourceview/gtksourcesearch.h b/gtksourceview/gtksourcesearch.h
index d8b10d0..6413043 100644
--- a/gtksourceview/gtksourcesearch.h
+++ b/gtksourceview/gtksourcesearch.h
@@ -86,6 +86,16 @@ G_GNUC_INTERNAL
 gboolean               _gtk_source_search_get_wrap_around              (GtkSourceSearch        *search);
 
 G_GNUC_INTERNAL
+void                   _gtk_source_search_set_regex_enabled            (GtkSourceSearch        *search,
+                                                                        gboolean                
regex_enabled);
+
+G_GNUC_INTERNAL
+gboolean               _gtk_source_search_get_regex_enabled            (GtkSourceSearch        *search);
+
+G_GNUC_INTERNAL
+GError *               _gtk_source_search_get_regex_error              (GtkSourceSearch        *search);
+
+G_GNUC_INTERNAL
 void                   _gtk_source_search_set_highlight                (GtkSourceSearch        *search,
                                                                         gboolean                highlight);
 
diff --git a/tests/test-search-ui.c b/tests/test-search-ui.c
index b00dc1e..a184418 100644
--- a/tests/test-search-ui.c
+++ b/tests/test-search-ui.c
@@ -50,6 +50,7 @@ struct _TestSearchUIPrivate
        GtkSourceBuffer *source_buffer;
        GtkEntry *replace_entry;
        GtkLabel *label_occurrences;
+       GtkLabel *label_regex_error;
 
        guint idle_update_label_id;
 };
@@ -92,7 +93,7 @@ open_file (TestSearchUI *search,
 }
 
 static void
-update_label (TestSearchUI *search)
+update_label_occurrences (TestSearchUI *search)
 {
        gint occurrences_count;
        GtkTextIter select_start;
@@ -127,6 +128,26 @@ update_label (TestSearchUI *search)
        g_free (text);
 }
 
+static void
+update_label_regex_error (TestSearchUI *search)
+{
+       GError *error;
+
+       error = gtk_source_buffer_get_regex_search_error (search->priv->source_buffer);
+
+       if (error == NULL)
+       {
+               gtk_label_set_text (search->priv->label_regex_error, "");
+               gtk_widget_hide (GTK_WIDGET (search->priv->label_regex_error));
+       }
+       else
+       {
+               gtk_label_set_text (search->priv->label_regex_error, error->message);
+               gtk_widget_show (GTK_WIDGET (search->priv->label_regex_error));
+               g_error_free (error);
+       }
+}
+
 /* 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.
@@ -285,7 +306,7 @@ update_label_idle_cb (TestSearchUI *search)
 {
        search->priv->idle_update_label_id = 0;
 
-       update_label (search);
+       update_label_occurrences (search);
 
        return G_SOURCE_REMOVE;
 }
@@ -343,6 +364,14 @@ wrap_around_toggled_cb (TestSearchUI    *search,
 }
 
 static void
+regex_toggled_cb (TestSearchUI    *search,
+                 GtkToggleButton *button)
+{
+       gtk_source_buffer_set_regex_search (search->priv->source_buffer,
+                                           gtk_toggle_button_get_active (button));
+}
+
+static void
 test_search_ui_dispose (GObject *object)
 {
        TestSearchUI *search = TEST_SEARCH_UI (object);
@@ -366,6 +395,7 @@ test_search_ui_class_init (TestSearchUIClass *klass)
        gtk_widget_class_bind_template_child_private (widget_class, TestSearchUI, source_view);
        gtk_widget_class_bind_template_child_private (widget_class, TestSearchUI, replace_entry);
        gtk_widget_class_bind_template_child_private (widget_class, TestSearchUI, label_occurrences);
+       gtk_widget_class_bind_template_child_private (widget_class, TestSearchUI, label_regex_error);
 
        gtk_widget_class_bind_template_callback (widget_class, search_entry_text_notify_cb);
        gtk_widget_class_bind_template_callback (widget_class, button_previous_clicked_cb);
@@ -381,6 +411,7 @@ test_search_ui_class_init (TestSearchUIClass *klass)
        gtk_widget_class_bind_template_callback (widget_class, match_case_toggled_cb);
        gtk_widget_class_bind_template_callback (widget_class, at_word_boundaries_toggled_cb);
        gtk_widget_class_bind_template_callback (widget_class, wrap_around_toggled_cb);
+       gtk_widget_class_bind_template_callback (widget_class, regex_toggled_cb);
 }
 
 static void
@@ -408,13 +439,20 @@ test_search_ui_init (TestSearchUI *search)
 
        g_signal_connect_swapped (search->priv->source_buffer,
                                  "notify::search-occurrences-count",
-                                 G_CALLBACK (update_label),
+                                 G_CALLBACK (update_label_occurrences),
                                  search);
 
        g_signal_connect (search->priv->source_buffer,
                          "mark-set",
                          G_CALLBACK (mark_set_cb),
                          search);
+
+       g_signal_connect_swapped (search->priv->source_buffer,
+                                 "notify::regex-search-error",
+                                 G_CALLBACK (update_label_regex_error),
+                                 search);
+
+       update_label_regex_error (search);
 }
 
 static TestSearchUI *
@@ -443,7 +481,7 @@ main (gint argc, gchar *argv[])
        search = test_search_ui_new ();
        gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (search));
 
-       gtk_widget_show_all (window);
+       gtk_widget_show (window);
 
        gtk_main ();
 
diff --git a/tests/test-search-ui.ui b/tests/test-search-ui.ui
index 7da2630..9fbab66 100644
--- a/tests/test-search-ui.ui
+++ b/tests/test-search-ui.ui
@@ -59,11 +59,13 @@
       <object class="GtkGrid" id="grid2">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
+        <property name="row_spacing">6</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="halign">start</property>
             <property name="xalign">0</property>
             <property name="label">Search:</property>
             <attributes>
@@ -72,15 +74,17 @@
           </object>
           <packing>
             <property name="left_attach">0</property>
-            <property name="top_attach">0</property>
+            <property name="top_attach">1</property>
             <property name="width">1</property>
             <property name="height">1</property>
           </packing>
         </child>
         <child>
           <object class="GtkSearchEntry" id="search_entry">
+            <property name="width_request">300</property>
             <property name="visible">True</property>
             <property name="can_focus">True</property>
+            <property name="halign">start</property>
             <property name="primary_icon_name">edit-find-symbolic</property>
             <property name="primary_icon_activatable">False</property>
             <property name="primary_icon_sensitive">False</property>
@@ -88,7 +92,7 @@
           </object>
           <packing>
             <property name="left_attach">1</property>
-            <property name="top_attach">0</property>
+            <property name="top_attach">1</property>
             <property name="width">1</property>
             <property name="height">1</property>
           </packing>
@@ -97,11 +101,12 @@
           <object class="GtkLabel" id="label_occurrences">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
+            <property name="halign">start</property>
             <property name="label">0 occurrences</property>
           </object>
           <packing>
             <property name="left_attach">4</property>
-            <property name="top_attach">0</property>
+            <property name="top_attach">1</property>
             <property name="width">1</property>
             <property name="height">1</property>
           </packing>
@@ -111,12 +116,13 @@
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <property name="receives_default">True</property>
+            <property name="halign">start</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="top_attach">1</property>
             <property name="width">1</property>
             <property name="height">1</property>
           </packing>
@@ -126,16 +132,40 @@
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <property name="receives_default">True</property>
+            <property name="halign">start</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="top_attach">1</property>
             <property name="width">1</property>
             <property name="height">1</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkLabel" id="label_regex_error">
+            <property name="can_focus">False</property>
+            <property name="halign">start</property>
+            <property name="xalign">0</property>
+            <property name="label">Regex search error</property>
+            <property name="wrap">True</property>
+            <property name="selectable">True</property>
+            <property name="max_width_chars">72</property>
+            <attributes>
+              <attribute name="foreground" value="#cccc00000000"/>
+            </attributes>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">0</property>
+            <property name="width">4</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
       </object>
       <packing>
         <property name="left_attach">0</property>
@@ -218,6 +248,23 @@
             <property name="height">1</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkCheckButton" id="checkbutton_regex">
+            <property name="label">Regex</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="regex_toggled_cb" object="TestSearchUI" swapped="yes"/>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">4</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
       </object>
       <packing>
         <property name="left_attach">0</property>
@@ -250,6 +297,7 @@
         </child>
         <child>
           <object class="GtkEntry" id="replace_entry">
+            <property name="width_request">300</property>
             <property name="visible">True</property>
             <property name="can_focus">True</property>
           </object>
diff --git a/tests/test-search.c b/tests/test-search.c
index 1ef5fa8..8468a9d 100644
--- a/tests/test-search.c
+++ b/tests/test-search.c
@@ -23,6 +23,25 @@
 #include <gtksourceview/gtksource.h>
 #include "gtksourceview/gtksourcesearch.h"
 
+typedef struct
+{
+       gint match_start_offset;
+       gint match_end_offset;
+       guint found : 1;
+} SearchResult;
+
+typedef struct
+{
+       SearchResult *results;
+       gint result_num;
+       guint forward : 1;
+} AsyncData;
+
+static void check_async_search_results (GtkSourceBuffer *source_buffer,
+                                       SearchResult    *results,
+                                       gboolean         forward,
+                                       gboolean         start_check);
+
 static void
 flush_queue (void)
 {
@@ -251,6 +270,595 @@ test_occurrences_count_multiple_lines (void)
        g_object_unref (source_buffer);
 }
 
+static void
+test_case_sensitivity (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+       gboolean case_sensitive;
+       gint occurrences_count;
+
+       gtk_text_buffer_set_text (text_buffer, "Case", -1);
+       gtk_source_buffer_set_search_text (source_buffer, "case");
+
+       gtk_source_buffer_set_case_sensitive_search (source_buffer, TRUE);
+       case_sensitive = gtk_source_buffer_get_case_sensitive_search (source_buffer);
+       g_assert (case_sensitive);
+
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpint (occurrences_count, ==, 0);
+
+       gtk_source_buffer_set_case_sensitive_search (source_buffer, FALSE);
+       case_sensitive = gtk_source_buffer_get_case_sensitive_search (source_buffer);
+       g_assert (!case_sensitive);
+
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpint (occurrences_count, ==, 1);
+
+       g_object_unref (source_buffer);
+}
+
+static void
+test_search_at_word_boundaries (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+       gboolean at_word_boundaries;
+       gint occurrences_count;
+
+       gtk_text_buffer_set_text (text_buffer, "AtWordBoundaries AtWord", -1);
+       gtk_source_buffer_set_search_text (source_buffer, "AtWord");
+
+       gtk_source_buffer_set_search_at_word_boundaries (source_buffer, TRUE);
+       at_word_boundaries = gtk_source_buffer_get_search_at_word_boundaries (source_buffer);
+       g_assert (at_word_boundaries);
+
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpint (occurrences_count, ==, 1);
+
+       gtk_source_buffer_set_search_at_word_boundaries (source_buffer, FALSE);
+       at_word_boundaries = gtk_source_buffer_get_search_at_word_boundaries (source_buffer);
+       g_assert (!at_word_boundaries);
+
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpint (occurrences_count, ==, 2);
+
+       g_object_unref (source_buffer);
+}
+
+static void
+check_search_results (GtkSourceBuffer *source_buffer,
+                     SearchResult    *results,
+                     gboolean         forward)
+{
+       GtkTextIter iter;
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+
+       gtk_text_buffer_get_start_iter (text_buffer, &iter);
+
+       do
+       {
+               gint i;
+               gboolean found;
+               GtkTextIter match_start;
+               GtkTextIter match_end;
+
+               i = gtk_text_iter_get_offset (&iter);
+
+               if (forward)
+               {
+                       found = gtk_source_buffer_forward_search (source_buffer,
+                                                                 &iter,
+                                                                 &match_start,
+                                                                 &match_end);
+               }
+               else
+               {
+                       found = gtk_source_buffer_backward_search (source_buffer,
+                                                                  &iter,
+                                                                  &match_start,
+                                                                  &match_end);
+               }
+
+               g_assert (found == results[i].found);
+
+               if (found)
+               {
+                       gint match_start_offset = gtk_text_iter_get_offset (&match_start);
+                       gint match_end_offset = gtk_text_iter_get_offset (&match_end);
+
+                       g_assert_cmpint (match_start_offset, ==, results[i].match_start_offset);
+                       g_assert_cmpint (match_end_offset, ==, results[i].match_end_offset);
+               }
+       }
+       while (gtk_text_iter_forward_char (&iter));
+}
+
+static void
+finish_check_result (GtkSourceBuffer *source_buffer,
+                    GAsyncResult    *result,
+                    AsyncData       *data)
+{
+       GtkTextIter match_start;
+       GtkTextIter match_end;
+       gboolean found;
+       SearchResult search_result = data->results[data->result_num];
+
+       if (data->forward)
+       {
+               found = gtk_source_buffer_forward_search_finish (source_buffer,
+                                                                result,
+                                                                &match_start,
+                                                                &match_end,
+                                                                NULL);
+       }
+       else
+       {
+               found = gtk_source_buffer_backward_search_finish (source_buffer,
+                                                                 result,
+                                                                 &match_start,
+                                                                 &match_end,
+                                                                 NULL);
+       }
+
+       g_assert (found == search_result.found);
+
+       if (found)
+       {
+               gint match_start_offset = gtk_text_iter_get_offset (&match_start);
+               gint match_end_offset = gtk_text_iter_get_offset (&match_end);
+
+               g_assert_cmpint (match_start_offset, ==, search_result.match_start_offset);
+               g_assert_cmpint (match_end_offset, ==, search_result.match_end_offset);
+       }
+
+       check_async_search_results (source_buffer,
+                                   data->results,
+                                   data->forward,
+                                   FALSE);
+
+       g_slice_free (AsyncData, data);
+}
+
+static void
+check_async_search_results (GtkSourceBuffer *source_buffer,
+                           SearchResult    *results,
+                           gboolean         forward,
+                           gboolean         start_check)
+{
+       static GtkTextIter iter;
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+       AsyncData *data;
+
+       if (start_check)
+       {
+               gtk_text_buffer_get_start_iter (text_buffer, &iter);
+       }
+       else if (!gtk_text_iter_forward_char (&iter))
+       {
+               gtk_main_quit ();
+               return;
+       }
+
+       data = g_slice_new (AsyncData);
+       data->results = results;
+       data->result_num = gtk_text_iter_get_offset (&iter);
+       data->forward = forward;
+
+       if (forward)
+       {
+               gtk_source_buffer_forward_search_async (source_buffer,
+                                                       &iter,
+                                                       NULL,
+                                                       (GAsyncReadyCallback)finish_check_result,
+                                                       data);
+       }
+       else
+       {
+               gtk_source_buffer_backward_search_async (source_buffer,
+                                                        &iter,
+                                                        NULL,
+                                                        (GAsyncReadyCallback)finish_check_result,
+                                                        data);
+       }
+}
+
+static void
+test_forward_search (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+       gboolean wrap_around;
+
+       static SearchResult results1[] =
+       {
+               { 0, 2, TRUE },
+               { 2, 4, TRUE },
+               { 2, 4, TRUE },
+               { 0, 2, TRUE },
+               { 0, 2, TRUE }
+       };
+
+       static SearchResult results2[] =
+       {
+               { 0, 2, TRUE },
+               { 2, 4, TRUE },
+               { 2, 4, TRUE },
+               { 0, 0, FALSE },
+               { 0, 0, FALSE }
+       };
+
+       gtk_text_buffer_set_text (text_buffer, "aaaa", -1);
+       gtk_source_buffer_set_search_text (source_buffer, "aa");
+
+       /* Wrap around: TRUE */
+
+       gtk_source_buffer_set_search_wrap_around (source_buffer, TRUE);
+       wrap_around = gtk_source_buffer_get_search_wrap_around (source_buffer);
+       g_assert (wrap_around);
+
+       check_search_results (source_buffer, results1, TRUE);
+
+       g_test_trap_subprocess ("/Search/forward/subprocess/async-wrap-around", 0, 0);
+       g_test_trap_assert_passed ();
+
+       /* Wrap around: FALSE */
+
+       gtk_source_buffer_set_search_wrap_around (source_buffer, FALSE);
+       wrap_around = gtk_source_buffer_get_search_wrap_around (source_buffer);
+       g_assert (!wrap_around);
+
+       check_search_results (source_buffer, results2, TRUE);
+
+       g_test_trap_subprocess ("/Search/forward/subprocess/async-normal", 0, 0);
+       g_test_trap_assert_passed ();
+
+       g_object_unref (source_buffer);
+}
+
+static void
+test_async_forward_search_normal (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+
+       static SearchResult results[] =
+       {
+               { 0, 2, TRUE },
+               { 2, 4, TRUE },
+               { 2, 4, TRUE },
+               { 0, 0, FALSE },
+               { 0, 0, FALSE }
+       };
+
+       gtk_text_buffer_set_text (text_buffer, "aaaa", -1);
+       gtk_source_buffer_set_search_text (source_buffer, "aa");
+       gtk_source_buffer_set_search_wrap_around (source_buffer, FALSE);
+
+       check_async_search_results (source_buffer, results, TRUE, TRUE);
+
+       gtk_main ();
+       g_object_unref (source_buffer);
+}
+
+static void
+test_async_forward_search_wrap_around (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+
+       static SearchResult results[] =
+       {
+               { 0, 2, TRUE },
+               { 2, 4, TRUE },
+               { 2, 4, TRUE },
+               { 0, 2, TRUE },
+               { 0, 2, TRUE }
+       };
+
+       gtk_text_buffer_set_text (text_buffer, "aaaa", -1);
+       gtk_source_buffer_set_search_text (source_buffer, "aa");
+       gtk_source_buffer_set_search_wrap_around (source_buffer, TRUE);
+
+       check_async_search_results (source_buffer, results, TRUE, TRUE);
+
+       gtk_main ();
+       g_object_unref (source_buffer);
+}
+
+static void
+test_backward_search (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+
+       static SearchResult results1[] =
+       {
+               { 2, 4, TRUE },
+               { 2, 4, TRUE },
+               { 0, 2, TRUE },
+               { 0, 2, TRUE },
+               { 2, 4, TRUE }
+       };
+
+       static SearchResult results2[] =
+       {
+               { 0, 0, FALSE },
+               { 0, 0, FALSE },
+               { 0, 2, TRUE },
+               { 0, 2, TRUE },
+               { 2, 4, TRUE }
+       };
+
+       gtk_text_buffer_set_text (text_buffer, "aaaa", -1);
+       gtk_source_buffer_set_search_text (source_buffer, "aa");
+
+       /* Wrap around: TRUE */
+
+       gtk_source_buffer_set_search_wrap_around (source_buffer, TRUE);
+       check_search_results (source_buffer, results1, FALSE);
+
+       g_test_trap_subprocess ("/Search/backward/subprocess/async-wrap-around", 0, 0);
+       g_test_trap_assert_passed ();
+
+       /* Wrap around: FALSE */
+
+       gtk_source_buffer_set_search_wrap_around (source_buffer, FALSE);
+       check_search_results (source_buffer, results2, FALSE);
+
+       g_test_trap_subprocess ("/Search/backward/subprocess/async-normal", 0, 0);
+       g_test_trap_assert_passed ();
+
+       g_object_unref (source_buffer);
+}
+
+static void
+test_async_backward_search_normal (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+
+       static SearchResult results[] =
+       {
+               { 0, 0, FALSE },
+               { 0, 0, FALSE },
+               { 0, 2, TRUE },
+               { 0, 2, TRUE },
+               { 2, 4, TRUE }
+       };
+
+       gtk_text_buffer_set_text (text_buffer, "aaaa", -1);
+       gtk_source_buffer_set_search_text (source_buffer, "aa");
+       gtk_source_buffer_set_search_wrap_around (source_buffer, FALSE);
+
+       check_async_search_results (source_buffer, results, FALSE, TRUE);
+
+       gtk_main ();
+       g_object_unref (source_buffer);
+}
+
+static void
+test_async_backward_search_wrap_around (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+
+       static SearchResult results[] =
+       {
+               { 2, 4, TRUE },
+               { 2, 4, TRUE },
+               { 0, 2, TRUE },
+               { 0, 2, TRUE },
+               { 2, 4, TRUE }
+       };
+
+       gtk_text_buffer_set_text (text_buffer, "aaaa", -1);
+       gtk_source_buffer_set_search_text (source_buffer, "aa");
+       gtk_source_buffer_set_search_wrap_around (source_buffer, TRUE);
+
+       check_async_search_results (source_buffer, results, FALSE, TRUE);
+
+       gtk_main ();
+       g_object_unref (source_buffer);
+}
+
+static void
+test_highlight (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       gboolean highlight;
+
+       gtk_source_buffer_set_highlight_search (source_buffer, TRUE);
+       highlight = gtk_source_buffer_get_highlight_search (source_buffer);
+       g_assert (highlight);
+
+       gtk_source_buffer_set_highlight_search (source_buffer, FALSE);
+       highlight = gtk_source_buffer_get_highlight_search (source_buffer);
+       g_assert (!highlight);
+
+       g_object_unref (source_buffer);
+}
+
+static void
+test_get_search_text (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       const gchar *search_text;
+
+       search_text = gtk_source_buffer_get_search_text (source_buffer);
+       g_assert (search_text == NULL);
+
+       gtk_source_buffer_set_search_text (source_buffer, "");
+       search_text = gtk_source_buffer_get_search_text (source_buffer);
+       g_assert (search_text == NULL);
+
+       gtk_source_buffer_set_search_text (source_buffer, "search-text");
+       search_text = gtk_source_buffer_get_search_text (source_buffer);
+       g_assert_cmpstr (search_text, ==, "search-text");
+
+       g_object_unref (source_buffer);
+}
+
+static void
+test_occurrence_position (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+       GtkTextIter start;
+       GtkTextIter end;
+       gint pos;
+
+       gtk_text_buffer_set_text (text_buffer, "aaaa", -1);
+       gtk_source_buffer_set_search_text (source_buffer, "aa");
+       flush_queue ();
+
+       gtk_text_buffer_get_start_iter (text_buffer, &start);
+       end = start;
+       gtk_text_iter_forward_chars (&end, 2);
+
+       pos = gtk_source_buffer_get_search_occurrence_position (source_buffer, &start, &end);
+       g_assert_cmpint (pos, ==, 1);
+
+       gtk_text_iter_forward_char (&start);
+       gtk_text_iter_forward_char (&end);
+       pos = gtk_source_buffer_get_search_occurrence_position (source_buffer, &start, &end);
+       g_assert_cmpint (pos, ==, 0);
+
+       gtk_text_iter_forward_char (&start);
+       gtk_text_iter_forward_char (&end);
+       pos = gtk_source_buffer_get_search_occurrence_position (source_buffer, &start, &end);
+       g_assert_cmpint (pos, ==, 2);
+
+       g_object_unref (source_buffer);
+}
+
+static void
+test_replace (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+       GtkTextIter start;
+       GtkTextIter end;
+       gboolean replaced;
+       gchar *contents;
+
+       gtk_text_buffer_set_text (text_buffer, "aaaa", -1);
+       gtk_source_buffer_set_search_text (source_buffer, "aa");
+       flush_queue ();
+
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &start, 1);
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &end, 3);
+
+       replaced = gtk_source_buffer_search_replace (source_buffer, &start, &end, "bb", 2);
+       g_assert (!replaced);
+
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &start, 2);
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &end, 4);
+
+       replaced = gtk_source_buffer_search_replace (source_buffer, &start, &end, "bb", 2);
+       g_assert (replaced);
+
+       gtk_text_buffer_get_start_iter (text_buffer, &start);
+       gtk_text_buffer_get_end_iter (text_buffer, &end);
+
+       contents = gtk_text_iter_get_visible_text (&start, &end);
+       g_assert_cmpstr (contents, ==, "aabb");
+
+       g_free (contents);
+       g_object_unref (source_buffer);
+}
+
+static void
+test_replace_all (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+       gint nb_replacements;
+       GtkTextIter start;
+       GtkTextIter end;
+       gchar *contents;
+
+       gtk_text_buffer_set_text (text_buffer, "aaaa", -1);
+       gtk_source_buffer_set_search_text (source_buffer, "aa");
+       flush_queue ();
+
+       nb_replacements = gtk_source_buffer_search_replace_all (source_buffer, "bb", 2);
+       g_assert_cmpint (nb_replacements, ==, 2);
+
+       gtk_text_buffer_get_start_iter (text_buffer, &start);
+       gtk_text_buffer_get_end_iter (text_buffer, &end);
+
+       contents = gtk_text_iter_get_visible_text (&start, &end);
+       g_assert_cmpstr (contents, ==, "bbbb");
+
+       g_free (contents);
+       g_object_unref (source_buffer);
+}
+
+static void
+test_regex (void)
+{
+       GtkSourceBuffer *source_buffer = gtk_source_buffer_new (NULL);
+       GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (source_buffer);
+       gboolean regex_enabled;
+       gint occurrences_count;
+       GtkTextIter start;
+       GtkTextIter end;
+       gchar *contents;
+
+       gtk_text_buffer_set_text (text_buffer, "hello\nworld\n", -1);
+       gtk_source_buffer_set_regex_search (source_buffer, TRUE);
+       regex_enabled = gtk_source_buffer_get_regex_search (source_buffer);
+       g_assert (regex_enabled);
+
+       /* Simple regex */
+
+       gtk_source_buffer_set_search_text (source_buffer, "\\w+");
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpint (occurrences_count, ==, 2);
+
+       /* Test partial matching */
+
+       gtk_source_buffer_set_search_text (source_buffer, "(.*\n)*");
+       flush_queue ();
+       occurrences_count = gtk_source_buffer_get_search_occurrences_count (source_buffer);
+       g_assert_cmpint (occurrences_count, ==, 1);
+
+       /* Test replace */
+
+       gtk_text_buffer_set_text (text_buffer, "aa#bb", -1);
+       gtk_source_buffer_set_search_text (source_buffer, "(\\w+)#(\\w+)");
+       flush_queue ();
+
+       gtk_text_buffer_get_start_iter (text_buffer, &start);
+       gtk_text_buffer_get_end_iter (text_buffer, &end);
+       gtk_source_buffer_search_replace (source_buffer, &start, &end, "\\2#\\1", -1);
+
+       gtk_text_buffer_get_start_iter (text_buffer, &start);
+       gtk_text_buffer_get_end_iter (text_buffer, &end);
+       contents = gtk_text_iter_get_visible_text (&start, &end);
+       g_assert_cmpstr (contents, ==, "bb#aa");
+       g_free (contents);
+
+       /* Test replace all */
+
+       gtk_text_buffer_set_text (text_buffer, "aa#bb cc#dd", -1);
+       gtk_source_buffer_search_replace_all (source_buffer, "\\2#\\1", -1);
+
+       gtk_text_buffer_get_start_iter (text_buffer, &start);
+       gtk_text_buffer_get_end_iter (text_buffer, &end);
+       contents = gtk_text_iter_get_visible_text (&start, &end);
+       g_assert_cmpstr (contents, ==, "bb#aa dd#cc");
+       g_free (contents);
+
+       g_object_unref (source_buffer);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -260,6 +868,20 @@ main (int argc, char **argv)
        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);
+       g_test_add_func ("/Search/case-sensitivity", test_case_sensitivity);
+       g_test_add_func ("/Search/at-word-boundaries", test_search_at_word_boundaries);
+       g_test_add_func ("/Search/forward", test_forward_search);
+       g_test_add_func ("/Search/forward/subprocess/async-normal", test_async_forward_search_normal);
+       g_test_add_func ("/Search/forward/subprocess/async-wrap-around", 
test_async_forward_search_wrap_around);
+       g_test_add_func ("/Search/backward", test_backward_search);
+       g_test_add_func ("/Search/backward/subprocess/async-normal", test_async_backward_search_normal);
+       g_test_add_func ("/Search/backward/subprocess/async-wrap-around", 
test_async_backward_search_wrap_around);
+       g_test_add_func ("/Search/highlight", test_highlight);
+       g_test_add_func ("/Search/get-search-text", test_get_search_text);
+       g_test_add_func ("/Search/occurrence-position", test_occurrence_position);
+       g_test_add_func ("/Search/replace", test_replace);
+       g_test_add_func ("/Search/replace", test_replace_all);
+       g_test_add_func ("/Search/regex", test_regex);
 
        return g_test_run ();
 }


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