[gtksourceview/wip/search: 1/2] buffer: add set_search() and clear_search() API



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

    buffer: add set_search() and clear_search() API
    
    There are also properties defining the state of the search.
    
    The implementation (that comes from gedit) is in the new private class
    GtkSourceSearch, to try to keep the GtkSourceBuffer small.

 docs/reference/Makefile.am                    |    1 +
 docs/reference/gtksourceview-3.0-sections.txt |    2 +
 gtksourceview/Makefile.am                     |    2 +
 gtksourceview/gtksourcebuffer.c               |  283 +++++++++++-
 gtksourceview/gtksourcebuffer.h               |    8 +
 gtksourceview/gtksourcesearch.c               |  624 +++++++++++++++++++++++++
 gtksourceview/gtksourcesearch.h               |   98 ++++
 gtksourceview/gtksourcetypes-private.h        |    1 +
 po/POTFILES.in                                |    1 +
 9 files changed, 1019 insertions(+), 1 deletions(-)
---
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index 0ee0152..58d5593 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -37,6 +37,7 @@ IGNORE_HFILES =                                       \
        gtksourcelanguage-private.h             \
        gtksourcepixbufhelper.h                 \
        gtksourceregex.h                        \
+       gtksourcesearch.h                       \
        gtksourcestyle-private.h                \
        gtksourcetypes-private.h                \
        gtksourceundomanagerdefault.h           \
diff --git a/docs/reference/gtksourceview-3.0-sections.txt b/docs/reference/gtksourceview-3.0-sections.txt
index 1e82636..09922f0 100644
--- a/docs/reference/gtksourceview-3.0-sections.txt
+++ b/docs/reference/gtksourceview-3.0-sections.txt
@@ -36,6 +36,8 @@ gtk_source_buffer_iter_forward_to_context_class_toggle
 gtk_source_buffer_iter_backward_to_context_class_toggle
 gtk_source_buffer_get_undo_manager
 gtk_source_buffer_set_undo_manager
+gtk_source_buffer_set_search
+gtk_source_buffer_clear_search
 <SUBSECTION Standard>
 GtkSourceBufferClass
 GTK_SOURCE_IS_BUFFER
diff --git a/gtksourceview/Makefile.am b/gtksourceview/Makefile.am
index 53cdbef..1d3278c 100644
--- a/gtksourceview/Makefile.am
+++ b/gtksourceview/Makefile.am
@@ -58,6 +58,7 @@ libgtksourceview_private_headers = \
        gtksourcelanguage-private.h             \
        gtksourcepixbufhelper.h                 \
        gtksourceregex.h                        \
+       gtksourcesearch.h                       \
        gtksourcestyle-private.h                \
        gtksourcetypes-private.h                \
        gtksourceundomanagerdefault.h           \
@@ -74,6 +75,7 @@ libgtksourceview_private_c_files = \
        gtksourcelanguage-parser-1.c    \
        gtksourcelanguage-parser-2.c    \
        gtksourceregex.c                \
+       gtksourcesearch.c               \
        gtksourceundomanager.c          \
        gtksourceundomanagerdefault.c   \
        gtksourceview-i18n.c            \
diff --git a/gtksourceview/gtksourcebuffer.c b/gtksourceview/gtksourcebuffer.c
index 4828fc5..ec11372 100644
--- a/gtksourceview/gtksourcebuffer.c
+++ b/gtksourceview/gtksourcebuffer.c
@@ -42,6 +42,7 @@
 #include "gtksourceundomanagerdefault.h"
 #include "gtksourceview-typebuiltins.h"
 #include "gtksourcemark.h"
+#include "gtksourcesearch.h"
 
 /**
  * SECTION:buffer
@@ -120,7 +121,11 @@ enum {
        PROP_MAX_UNDO_LEVELS,
        PROP_LANGUAGE,
        PROP_STYLE_SCHEME,
-       PROP_UNDO_MANAGER
+       PROP_UNDO_MANAGER,
+       PROP_SEARCH_STRING,
+       PROP_SEARCH_FLAGS,
+       PROP_SEARCH_START,
+       PROP_SEARCH_END
 };
 
 struct _GtkSourceBufferPrivate
@@ -140,6 +145,8 @@ struct _GtkSourceBufferPrivate
        GtkSourceUndoManager  *undo_manager;
        gint                   max_undo_levels;
 
+       GtkSourceSearch       *search;
+
        guint                  highlight_syntax : 1;
        guint                  highlight_brackets : 1;
        guint                  constructed : 1;
@@ -335,6 +342,67 @@ gtk_source_buffer_class_init (GtkSourceBufferClass *klass)
                                                              GTK_SOURCE_TYPE_UNDO_MANAGER,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 
+       /**
+        * GtkSourceBuffer:search-string:
+        *
+        * A search string, or %NULL if the search is cleared.
+        *
+        * Since: 3.10
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_SEARCH_STRING,
+                                        g_param_spec_string ("search-string",
+                                                             _("Search string"),
+                                                             _("Search string"),
+                                                             NULL,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+       /**
+        * GtkSourceBuffer:search-flags:
+        *
+        * Flags affecting how the search is done.
+        *
+        * Since: 3.10
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_SEARCH_FLAGS,
+                                        g_param_spec_flags ("search-flags",
+                                                            _("Search flags"),
+                                                            _("Search flags"),
+                                                            GTK_TYPE_TEXT_SEARCH_FLAGS,
+                                                            GTK_TEXT_SEARCH_VISIBLE_ONLY | 
GTK_TEXT_SEARCH_TEXT_ONLY,
+                                                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+       /**
+        * GtkSourceBuffer:search-start:
+        *
+        * The lower bound for the search, or %NULL for the start of the buffer.
+        *
+        * Since: 3.10
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_SEARCH_START,
+                                        g_param_spec_object ("search-start",
+                                                             _("Search start"),
+                                                             _("Lower bound for the search"),
+                                                             GTK_TYPE_TEXT_MARK,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+       /**
+        * GtkSourceBuffer:search-end:
+        *
+        * The upper bound for the search, or %NULL for the end of the buffer.
+        *
+        * Since: 3.10
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_SEARCH_END,
+                                        g_param_spec_object ("search-end",
+                                                             _("Search end"),
+                                                             _("Upper bound for the search"),
+                                                             GTK_TYPE_TEXT_MARK,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
        param_types[0] = GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE;
        param_types[1] = GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE;
 
@@ -476,6 +544,8 @@ gtk_source_buffer_init (GtkSourceBuffer *buffer)
 
        if (priv->style_scheme != NULL)
                g_object_ref (priv->style_scheme);
+
+       priv->search = _gtk_source_search_new (buffer);
 }
 
 static void
@@ -518,6 +588,7 @@ gtk_source_buffer_dispose (GObject *object)
        g_clear_object (&buffer->priv->highlight_engine);
        g_clear_object (&buffer->priv->language);
        g_clear_object (&buffer->priv->style_scheme);
+       g_clear_object (&buffer->priv->search);
 
        G_OBJECT_CLASS (gtk_source_buffer_parent_class)->dispose (object);
 }
@@ -566,6 +637,30 @@ gtk_source_buffer_set_property (GObject      *object,
                                                            g_value_get_object (value));
                        break;
 
+               case PROP_SEARCH_STRING:
+                       _gtk_source_search_set_string (source_buffer->priv->search,
+                                                      g_value_get_string (value));
+                       _gtk_source_search_update (source_buffer->priv->search);
+                       break;
+
+               case PROP_SEARCH_FLAGS:
+                       _gtk_source_search_set_flags (source_buffer->priv->search,
+                                                     g_value_get_flags (value));
+                       _gtk_source_search_update (source_buffer->priv->search);
+                       break;
+
+               case PROP_SEARCH_START:
+                       _gtk_source_search_set_start (source_buffer->priv->search,
+                                                     g_value_get_object (value));
+                       _gtk_source_search_update (source_buffer->priv->search);
+                       break;
+
+               case PROP_SEARCH_END:
+                       _gtk_source_search_set_end (source_buffer->priv->search,
+                                                   g_value_get_object (value));
+                       _gtk_source_search_update (source_buffer->priv->search);
+                       break;
+
                default:
                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                        break;
@@ -621,6 +716,22 @@ gtk_source_buffer_get_property (GObject    *object,
                        g_value_set_object (value, source_buffer->priv->undo_manager);
                        break;
 
+               case PROP_SEARCH_STRING:
+                       g_value_set_string (value, _gtk_source_search_get_string 
(source_buffer->priv->search));
+                       break;
+
+               case PROP_SEARCH_FLAGS:
+                       g_value_set_flags (value, _gtk_source_search_get_flags (source_buffer->priv->search));
+                       break;
+
+               case PROP_SEARCH_START:
+                       g_value_set_object (value, _gtk_source_search_get_start 
(source_buffer->priv->search));
+                       break;
+
+               case PROP_SEARCH_END:
+                       g_value_set_object (value, _gtk_source_search_get_end (source_buffer->priv->search));
+                       break;
+
                default:
                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                        break;
@@ -1532,10 +1643,17 @@ _gtk_source_buffer_update_highlight (GtkSourceBuffer   *buffer,
        g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
 
        if (buffer->priv->highlight_engine != NULL)
+       {
                _gtk_source_engine_update_highlight (buffer->priv->highlight_engine,
                                                     start,
                                                     end,
                                                     synchronous);
+       }
+
+       _gtk_source_search_update_highlight (buffer->priv->search,
+                                            start,
+                                            end,
+                                            synchronous);
 }
 
 /**
@@ -2476,3 +2594,166 @@ gtk_source_buffer_get_undo_manager (GtkSourceBuffer *buffer)
 
        return buffer->priv->undo_manager;
 }
+
+/**
+ * gtk_source_buffer_set_search:
+ * @buffer: the #GtkSourceBuffer where the search occurs.
+ * @str: a search string.
+ * @flags: flags affecting how the search is done.
+ * @start_iter: lower bound for the search, or %NULL for the start of the buffer.
+ * @end_iter: upper bound for the search, or %NULL for the end of the buffer.
+ *
+ * Starts a new search, defined by @str and @flags, and delimited by @start and
+ * @end. To search the all @buffer, set @start and @end to %NULL.
+ *
+ * The search matches will be highlighted. When the search is finished, call
+ * gtk_source_buffer_clear_search().
+ *
+ * Since: 3.10
+ */
+void
+gtk_source_buffer_set_search (GtkSourceBuffer    *buffer,
+                             const gchar        *str,
+                             GtkTextSearchFlags  flags,
+                             const GtkTextIter  *start_iter,
+                             const GtkTextIter  *end_iter)
+{
+       const gchar *cur_str;
+       GtkTextSearchFlags cur_flags;
+       GtkTextMark *cur_start_mark;
+       GtkTextMark *cur_end_mark;
+       gboolean set_start = FALSE;
+       gboolean set_end = FALSE;
+
+       g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
+
+       /* Update the string */
+
+       cur_str = _gtk_source_search_get_string (buffer->priv->search);
+
+       if (g_strcmp0 (cur_str, str) != 0)
+       {
+               _gtk_source_search_set_string (buffer->priv->search, str);
+               g_object_notify (G_OBJECT (buffer), "search-string");
+       }
+
+       /* Update the flags */
+
+       cur_flags = _gtk_source_search_get_flags (buffer->priv->search);
+
+       if (cur_flags != flags)
+       {
+               _gtk_source_search_set_flags (buffer->priv->search, flags);
+               g_object_notify (G_OBJECT (buffer), "search-flags");
+       }
+
+       /* Update the start */
+
+       cur_start_mark = _gtk_source_search_get_start (buffer->priv->search);
+
+       if (cur_start_mark != NULL)
+       {
+               if (start_iter == NULL)
+               {
+                       _gtk_source_search_set_start (buffer->priv->search, NULL);
+                       g_object_notify (G_OBJECT (buffer), "search-start");
+               }
+               else
+               {
+                       GtkTextIter cur_start_iter;
+
+                       gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
+                                                         &cur_start_iter,
+                                                         cur_start_mark);
+
+                       if (!gtk_text_iter_equal (&cur_start_iter, start_iter))
+                       {
+                               set_start = TRUE;
+                       }
+               }
+       }
+       else if (start_iter != NULL)
+       {
+               set_start = TRUE;
+       }
+
+       if (set_start)
+       {
+               GtkTextMark *start_mark;
+
+               start_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (buffer),
+                                                         NULL,
+                                                         start_iter,
+                                                         TRUE);
+
+               _gtk_source_search_set_start (buffer->priv->search, start_mark);
+               g_object_notify (G_OBJECT (buffer), "search-start");
+       }
+
+       /* Update the end */
+
+       cur_end_mark = _gtk_source_search_get_end (buffer->priv->search);
+
+       if (cur_end_mark != NULL)
+       {
+               if (end_iter == NULL)
+               {
+                       _gtk_source_search_set_end (buffer->priv->search, NULL);
+                       g_object_notify (G_OBJECT (buffer), "search-end");
+               }
+               else
+               {
+                       GtkTextIter cur_end_iter;
+
+                       gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
+                                                         &cur_end_iter,
+                                                         cur_end_mark);
+
+                       if (!gtk_text_iter_equal (&cur_end_iter, end_iter))
+                       {
+                               set_end = TRUE;
+                       }
+               }
+       }
+       else if (end_iter != NULL)
+       {
+               set_end = TRUE;
+       }
+
+       if (set_end)
+       {
+               GtkTextMark *end_mark;
+
+               end_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (buffer),
+                                                       NULL,
+                                                       end_iter,
+                                                       FALSE);
+
+               _gtk_source_search_set_end (buffer->priv->search, end_mark);
+               g_object_notify (G_OBJECT (buffer), "search-end");
+       }
+
+       /* Update the search */
+
+       _gtk_source_search_update (buffer->priv->search);
+}
+
+/**
+ * gtk_source_buffer_clear_search:
+ * @buffer: the #GtkSourceBuffer where the search occurs.
+ *
+ * Clear the search matches highlighting.
+ *
+ * Since: 3.10
+ */
+void
+gtk_source_buffer_clear_search (GtkSourceBuffer *buffer)
+{
+       GtkTextSearchFlags flags;
+
+       g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
+
+       flags = _gtk_source_search_get_flags (buffer->priv->search);
+
+       gtk_source_buffer_set_search (buffer, NULL, flags, NULL, NULL);
+}
diff --git a/gtksourceview/gtksourcebuffer.h b/gtksourceview/gtksourcebuffer.h
index 80b365e..a170862 100644
--- a/gtksourceview/gtksourcebuffer.h
+++ b/gtksourceview/gtksourcebuffer.h
@@ -175,6 +175,14 @@ GtkSourceUndoManager       *gtk_source_buffer_get_undo_manager     (GtkSourceBuffer      
  *buffe
 void                    gtk_source_buffer_set_undo_manager     (GtkSourceBuffer        *buffer,
                                                                 GtkSourceUndoManager   *manager);
 
+void                    gtk_source_buffer_set_search           (GtkSourceBuffer        *buffer,
+                                                                const gchar            *str,
+                                                                GtkTextSearchFlags      flags,
+                                                                const GtkTextIter      *start_iter,
+                                                                const GtkTextIter      *end_iter);
+
+void                    gtk_source_buffer_clear_search         (GtkSourceBuffer        *buffer);
+
 /* private */
 void                    _gtk_source_buffer_update_highlight    (GtkSourceBuffer        *buffer,
                                                                 const GtkTextIter      *start,
diff --git a/gtksourceview/gtksourcesearch.c b/gtksourceview/gtksourcesearch.c
new file mode 100644
index 0000000..f9bf517
--- /dev/null
+++ b/gtksourceview/gtksourcesearch.c
@@ -0,0 +1,624 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcesearch.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2013 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "gtksourcesearch.h"
+#include "gtksourcebuffer.h"
+#include "gtksourcestylescheme.h"
+#include "gtktextregion.h"
+#include "gtksourcestyle-private.h"
+
+#include <string.h>
+
+struct _GtkSourceSearchPrivate
+{
+       GtkTextBuffer *buffer;
+
+       /* State of the search */
+       gchar *str;
+       gint str_nb_lines;
+       GtkTextSearchFlags flags;
+       GtkTextMark *start;
+       GtkTextMark *end;
+
+       /* Contains the remaining region to highlight */
+       GtkTextRegion *region;
+
+       GtkTextTag *found_tag;
+};
+
+#define GTK_SOURCE_SEARCH_GET_PRIVATE(object) \
+        (G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+                                      GTK_SOURCE_TYPE_SEARCH, \
+                                      GtkSourceSearchPrivate))
+
+G_DEFINE_TYPE (GtkSourceSearch, _gtk_source_search, G_TYPE_OBJECT);
+
+static gboolean
+dispose_has_run (GtkSourceSearch *search)
+{
+       return search->priv->buffer == NULL;
+}
+
+static void
+sync_found_tag (GtkSourceSearch *search)
+{
+       GtkSourceStyleScheme *style_scheme;
+       GtkSourceStyle *style = NULL;
+
+       if (dispose_has_run (search))
+       {
+               return;
+       }
+
+       style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (search->priv->buffer));
+
+       if (style_scheme != NULL)
+       {
+               style = gtk_source_style_scheme_get_style (style_scheme, "search-match");
+       }
+
+       if (style == NULL)
+       {
+               g_warning ("search-match style not available.");
+       }
+
+       _gtk_source_style_apply (style, search->priv->found_tag);
+}
+
+static void
+init_found_tag (GtkSourceSearch *search)
+{
+       if (search->priv->found_tag != NULL)
+       {
+               return;
+       }
+
+       search->priv->found_tag = gtk_text_buffer_create_tag (search->priv->buffer, NULL, NULL);
+
+       sync_found_tag (search);
+
+       g_signal_connect_object (search->priv->buffer,
+                                "notify::style-scheme",
+                                G_CALLBACK (sync_found_tag),
+                                search,
+                                G_CONNECT_SWAPPED);
+}
+
+static void
+text_tag_set_highest_priority (GtkTextTag    *tag,
+                              GtkTextBuffer *buffer)
+{
+       GtkTextTagTable *table;
+       gint n;
+
+       table = gtk_text_buffer_get_tag_table (buffer);
+       n = gtk_text_tag_table_get_size (table);
+       gtk_text_tag_set_priority (tag, n - 1);
+}
+
+static void
+_gtk_source_search_dispose (GObject *object)
+{
+       GtkSourceSearch *search = GTK_SOURCE_SEARCH (object);
+
+       if (search->priv->start != NULL)
+       {
+               gtk_text_buffer_delete_mark (search->priv->buffer,
+                                            search->priv->start);
+
+               g_clear_object (&search->priv->start);
+       }
+
+       if (search->priv->end != NULL)
+       {
+               gtk_text_buffer_delete_mark (search->priv->buffer,
+                                            search->priv->end);
+
+               g_clear_object (&search->priv->end);
+       }
+
+       if (search->priv->region != NULL)
+       {
+               gtk_text_region_destroy (search->priv->region, TRUE);
+               search->priv->region = NULL;
+       }
+
+       g_clear_object (&search->priv->buffer);
+
+       G_OBJECT_CLASS (_gtk_source_search_parent_class)->dispose (object);
+}
+
+static void
+_gtk_source_search_finalize (GObject *object)
+{
+       GtkSourceSearch *search = GTK_SOURCE_SEARCH (object);
+
+       g_free (search->priv->str);
+
+       G_OBJECT_CLASS (_gtk_source_search_parent_class)->finalize (object);
+}
+
+static void
+_gtk_source_search_class_init (GtkSourceSearchClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->dispose = _gtk_source_search_dispose;
+       object_class->finalize = _gtk_source_search_finalize;
+
+       g_type_class_add_private (object_class, sizeof (GtkSourceSearchPrivate));
+}
+
+static void
+_gtk_source_search_init (GtkSourceSearch *self)
+{
+       self->priv = GTK_SOURCE_SEARCH_GET_PRIVATE (self);
+
+       self->priv->flags = GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY;
+}
+
+GtkSourceSearch *
+_gtk_source_search_new (GtkSourceBuffer *buffer)
+{
+       GtkSourceSearch *search;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
+
+       search = g_object_new (GTK_SOURCE_TYPE_SEARCH, NULL);
+       search->priv->buffer = GTK_TEXT_BUFFER (buffer);
+       g_object_ref (buffer);
+
+       return search;
+}
+
+static gchar *
+unescape_search_text (const gchar *text)
+{
+       GString *str;
+       gint length;
+       gboolean drop_prev = FALSE;
+       const gchar *cur;
+       const gchar *end;
+       const gchar *prev;
+
+       if (text == NULL)
+       {
+               return NULL;
+       }
+
+       length = strlen (text);
+
+       str = g_string_new ("");
+
+       cur = text;
+       end = text + length;
+       prev = NULL;
+
+       while (cur != end)
+       {
+               const gchar *next;
+               next = g_utf8_next_char (cur);
+
+               if (prev != NULL && *prev == '\\')
+               {
+                       switch (*cur)
+                       {
+                               case 'n':
+                                       str = g_string_append (str, "\n");
+                                       break;
+                               case 'r':
+                                       str = g_string_append (str, "\r");
+                                       break;
+                               case 't':
+                                       str = g_string_append (str, "\t");
+                                       break;
+                               case '\\':
+                                       str = g_string_append (str, "\\");
+                                       drop_prev = TRUE;
+                                       break;
+                               default:
+                                       str = g_string_append (str, "\\");
+                                       str = g_string_append_len (str, cur, next - cur);
+                                       break;
+                       }
+               }
+               else if (*cur != '\\')
+               {
+                       str = g_string_append_len (str, cur, next - cur);
+               }
+               else if (next == end && *cur == '\\')
+               {
+                       str = g_string_append (str, "\\");
+               }
+
+               if (!drop_prev)
+               {
+                       prev = cur;
+               }
+               else
+               {
+                       prev = NULL;
+                       drop_prev = FALSE;
+               }
+
+               cur = next;
+       }
+
+       return g_string_free (str, FALSE);
+}
+
+static gint
+compute_nb_of_lines (const gchar *text)
+{
+       const gchar *p;
+       gint len;
+       gint nb_of_lines = 1;
+
+       if (text == NULL)
+       {
+               return 0;
+       }
+
+       len = strlen (text);
+       p = text;
+
+       while (len > 0)
+       {
+               gint delimiter;
+               gint next_paragraph;
+
+               pango_find_paragraph_boundary (p, len, &delimiter, &next_paragraph);
+
+               if (delimiter == next_paragraph)
+               {
+                       /* not found */
+                       break;
+               }
+
+               p += next_paragraph;
+               len -= next_paragraph;
+               nb_of_lines++;
+       }
+
+       return nb_of_lines;
+}
+
+void
+_gtk_source_search_set_string (GtkSourceSearch *search,
+                              const gchar     *str)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
+       g_return_if_fail (str == NULL || g_utf8_validate (str, -1, NULL));
+
+       g_free (search->priv->str);
+       search->priv->str = unescape_search_text (str);
+       search->priv->str_nb_lines = compute_nb_of_lines (search->priv->str);
+}
+
+const gchar *
+_gtk_source_search_get_string (GtkSourceSearch *search)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), NULL);
+
+       return search->priv->str;
+}
+
+void
+_gtk_source_search_set_flags (GtkSourceSearch    *search,
+                             GtkTextSearchFlags  flags)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
+
+       search->priv->flags = flags;
+}
+
+GtkTextSearchFlags
+_gtk_source_search_get_flags (GtkSourceSearch *search)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), 0);
+
+       return search->priv->flags;
+}
+
+void
+_gtk_source_search_set_start (GtkSourceSearch *search,
+                             GtkTextMark     *start)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
+
+       if (dispose_has_run (search))
+       {
+               return;
+       }
+
+       if (start != NULL)
+       {
+               g_return_if_fail (gtk_text_mark_get_buffer (start) == search->priv->buffer);
+       }
+
+       if (search->priv->start != NULL)
+       {
+               gtk_text_buffer_delete_mark (search->priv->buffer,
+                                            search->priv->start);
+
+               g_object_unref (search->priv->start);
+       }
+
+       search->priv->start = start;
+
+       if (start != NULL)
+       {
+               g_object_ref (start);
+       }
+}
+
+GtkTextMark *
+_gtk_source_search_get_start (GtkSourceSearch *search)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), NULL);
+
+       return search->priv->start;
+}
+
+void
+_gtk_source_search_set_end (GtkSourceSearch *search,
+                           GtkTextMark     *end)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
+
+       if (dispose_has_run (search))
+       {
+               return;
+       }
+
+       if (end != NULL)
+       {
+               g_return_if_fail (gtk_text_mark_get_buffer (end) == search->priv->buffer);
+       }
+
+       if (search->priv->end != NULL)
+       {
+               gtk_text_buffer_delete_mark (search->priv->buffer,
+                                            search->priv->end);
+
+               g_object_unref (search->priv->end);
+       }
+
+       search->priv->end = end;
+
+       if (end != NULL)
+       {
+               g_object_ref (end);
+       }
+}
+
+GtkTextMark *
+_gtk_source_search_get_end (GtkSourceSearch *search)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SEARCH (search), NULL);
+
+       return search->priv->end;
+}
+
+static void
+clear_search (GtkSourceSearch *search)
+{
+       if (search->priv->region != NULL)
+       {
+               gtk_text_region_destroy (search->priv->region, TRUE);
+               search->priv->region = NULL;
+       }
+
+       if (search->priv->found_tag != NULL)
+       {
+               GtkTextIter start;
+               GtkTextIter end;
+
+               gtk_text_buffer_get_bounds (search->priv->buffer, &start, &end);
+
+               gtk_text_buffer_remove_tag (search->priv->buffer,
+                                           search->priv->found_tag,
+                                           &start,
+                                           &end);
+       }
+}
+
+/* Separate update function, so we can change several search properties at once. */
+void
+_gtk_source_search_update (GtkSourceSearch *search)
+{
+       GtkTextIter start;
+       GtkTextIter end;
+
+       g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
+
+       if (dispose_has_run (search))
+       {
+               return;
+       }
+
+       clear_search (search);
+
+       if (search->priv->str == NULL)
+       {
+               return;
+       }
+
+       search->priv->region = gtk_text_region_new (search->priv->buffer);
+
+       if (search->priv->start != NULL)
+       {
+               gtk_text_buffer_get_iter_at_mark (search->priv->buffer,
+                                                 &start,
+                                                 search->priv->start);
+       }
+       else
+       {
+               gtk_text_buffer_get_start_iter (search->priv->buffer, &start);
+       }
+
+       if (search->priv->end != NULL)
+       {
+               gtk_text_buffer_get_iter_at_mark (search->priv->buffer,
+                                                 &end,
+                                                 search->priv->end);
+       }
+       else
+       {
+               gtk_text_buffer_get_end_iter (search->priv->buffer, &end);
+       }
+
+       gtk_text_region_add (search->priv->region, &start, &end);
+}
+
+static void
+highlight_subregion (GtkSourceSearch *search,
+                    GtkTextIter     *start,
+                    GtkTextIter     *end)
+{
+       GtkTextIter iter;
+       gboolean found = TRUE;
+
+       if (search->priv->found_tag == NULL)
+       {
+               init_found_tag (search);
+       }
+
+       /* Make sure the 'found' tag has the priority over syntax highlighting
+        * tags. */
+       text_tag_set_highest_priority (search->priv->found_tag,
+                                      search->priv->buffer);
+
+       g_return_if_fail (search->priv->str_nb_lines > 0);
+
+       gtk_text_iter_backward_lines (start, search->priv->str_nb_lines);
+       gtk_text_iter_forward_lines (end, search->priv->str_nb_lines);
+
+       if (gtk_text_iter_has_tag (start, search->priv->found_tag) &&
+           !gtk_text_iter_begins_tag (start, search->priv->found_tag))
+       {
+               gtk_text_iter_backward_to_tag_toggle (start, search->priv->found_tag);
+       }
+
+       if (gtk_text_iter_has_tag (end, search->priv->found_tag) &&
+           !gtk_text_iter_ends_tag (end, search->priv->found_tag))
+       {
+               gtk_text_iter_forward_to_tag_toggle (end, search->priv->found_tag);
+       }
+
+       /*
+       g_print ("[%u (%u), %u (%u)]\n", gtk_text_iter_get_line (start), gtk_text_iter_get_offset (start),
+                                        gtk_text_iter_get_line (end), gtk_text_iter_get_offset (end));
+       */
+
+       gtk_text_buffer_remove_tag (search->priv->buffer,
+                                   search->priv->found_tag,
+                                   start,
+                                   end);
+
+       if (search->priv->str[0] == '\0')
+       {
+               return;
+       }
+
+       iter = *start;
+
+       do
+       {
+               GtkTextIter match_start;
+               GtkTextIter match_end;
+
+               /* FIXME normally it is not needed.
+               if ((end != NULL) && gtk_text_iter_is_end (end))
+               {
+                       end = NULL;
+               }
+               */
+
+               found = gtk_text_iter_forward_search (&iter,
+                                                     search->priv->str,
+                                                     search->priv->flags,
+                                                     &match_start,
+                                                     &match_end,
+                                                     end);
+
+               iter = match_end;
+
+               if (found)
+               {
+                       gtk_text_buffer_apply_tag (search->priv->buffer,
+                                                  search->priv->found_tag,
+                                                  &match_start,
+                                                  &match_end);
+               }
+
+       } while (found);
+}
+
+void
+_gtk_source_search_update_highlight (GtkSourceSearch   *search,
+                                    const GtkTextIter *start,
+                                    const GtkTextIter *end,
+                                    gboolean           synchronous)
+{
+       GtkTextRegion *region_to_highlight;
+       GtkTextIter start_search;
+       GtkTextIter end_search;
+       gint nb_subregions;
+
+       g_return_if_fail (GTK_SOURCE_IS_SEARCH (search));
+       g_return_if_fail (start != NULL);
+       g_return_if_fail (end != NULL);
+
+       if (dispose_has_run (search) || search->priv->region == NULL)
+       {
+               return;
+       }
+
+       region_to_highlight = gtk_text_region_intersect (search->priv->region,
+                                                        start,
+                                                        end);
+
+       if (region_to_highlight == NULL)
+       {
+               return;
+       }
+
+       nb_subregions = gtk_text_region_subregions (region_to_highlight);
+       g_assert (nb_subregions >= 1);
+
+       gtk_text_region_nth_subregion (region_to_highlight,
+                                      0,
+                                      &start_search,
+                                      NULL);
+
+       gtk_text_region_nth_subregion (region_to_highlight,
+                                      nb_subregions - 1,
+                                      NULL,
+                                      &end_search);
+
+       gtk_text_iter_order (&start_search, &end_search);
+
+       highlight_subregion (search, &start_search, &end_search);
+
+       /* Remove the just highlighted region */
+       gtk_text_region_subtract (search->priv->region, start, end);
+}
diff --git a/gtksourceview/gtksourcesearch.h b/gtksourceview/gtksourcesearch.h
new file mode 100644
index 0000000..d50b147
--- /dev/null
+++ b/gtksourceview/gtksourcesearch.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*-
+ * gtksourcesearch.h
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2013 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __GTK_SOURCE_SEARCH_H__
+#define __GTK_SOURCE_SEARCH_H__
+
+#include <gtk/gtk.h>
+#include "gtksourcetypes.h"
+#include "gtksourcetypes-private.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_SEARCH             (_gtk_source_search_get_type ())
+#define GTK_SOURCE_SEARCH(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_SOURCE_TYPE_SEARCH, 
GtkSourceSearch))
+#define GTK_SOURCE_SEARCH_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_SOURCE_TYPE_SEARCH, 
GtkSourceSearchClass))
+#define GTK_SOURCE_IS_SEARCH(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_SOURCE_TYPE_SEARCH))
+#define GTK_SOURCE_IS_SEARCH_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_SOURCE_TYPE_SEARCH))
+#define GTK_SOURCE_SEARCH_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_SOURCE_TYPE_SEARCH, 
GtkSourceSearchClass))
+
+typedef struct _GtkSourceSearchClass    GtkSourceSearchClass;
+typedef struct _GtkSourceSearchPrivate  GtkSourceSearchPrivate;
+
+struct _GtkSourceSearch
+{
+       GObject parent;
+
+       GtkSourceSearchPrivate *priv;
+};
+
+struct _GtkSourceSearchClass
+{
+       GObjectClass parent_class;
+};
+
+G_GNUC_INTERNAL
+GType                  _gtk_source_search_get_type             (void) G_GNUC_CONST;
+
+G_GNUC_INTERNAL
+GtkSourceSearch *      _gtk_source_search_new                  (GtkSourceBuffer    *buffer);
+
+G_GNUC_INTERNAL
+void                   _gtk_source_search_set_string           (GtkSourceSearch    *search,
+                                                                const gchar        *str);
+
+G_GNUC_INTERNAL
+const gchar *          _gtk_source_search_get_string           (GtkSourceSearch    *search);
+
+G_GNUC_INTERNAL
+void                   _gtk_source_search_set_flags            (GtkSourceSearch    *search,
+                                                                GtkTextSearchFlags  flags);
+
+G_GNUC_INTERNAL
+GtkTextSearchFlags     _gtk_source_search_get_flags            (GtkSourceSearch    *search);
+
+G_GNUC_INTERNAL
+void                   _gtk_source_search_set_start            (GtkSourceSearch    *search,
+                                                                GtkTextMark        *start);
+
+G_GNUC_INTERNAL
+GtkTextMark *          _gtk_source_search_get_start            (GtkSourceSearch    *search);
+
+G_GNUC_INTERNAL
+void                   _gtk_source_search_set_end              (GtkSourceSearch    *search,
+                                                                GtkTextMark        *end);
+
+G_GNUC_INTERNAL
+GtkTextMark *          _gtk_source_search_get_end              (GtkSourceSearch    *search);
+
+G_GNUC_INTERNAL
+void                   _gtk_source_search_update               (GtkSourceSearch    *search);
+
+G_GNUC_INTERNAL
+void                   _gtk_source_search_update_highlight     (GtkSourceSearch    *search,
+                                                                const GtkTextIter  *start,
+                                                                const GtkTextIter  *end,
+                                                                gboolean            synchronous);
+
+G_END_DECLS
+
+#endif /* __GTK_SOURCE_SEARCH_H__ */
diff --git a/gtksourceview/gtksourcetypes-private.h b/gtksourceview/gtksourcetypes-private.h
index 6b88c2c..58ccf31 100644
--- a/gtksourceview/gtksourcetypes-private.h
+++ b/gtksourceview/gtksourcetypes-private.h
@@ -34,6 +34,7 @@ typedef struct _GtkSourceGutterRendererLines  GtkSourceGutterRendererLines;
 typedef struct _GtkSourceGutterRendererMarks   GtkSourceGutterRendererMarks;
 typedef struct _GtkSourcePixbufHelper          GtkSourcePixbufHelper;
 typedef struct _GtkSourceRegex                 GtkSourceRegex;
+typedef struct _GtkSourceSearch                        GtkSourceSearch;
 typedef struct _GtkSourceUndoManagerDefault    GtkSourceUndoManagerDefault;
 
 G_END_DECLS
diff --git a/po/POTFILES.in b/po/POTFILES.in
index aebf5cf..32a6455 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -131,6 +131,7 @@ gtksourceview/gtksourcemarkattributes.c
 gtksourceview/gtksourcemark.c
 gtksourceview/gtksourceprintcompositor.c
 gtksourceview/gtksourceregex.c
+gtksourceview/gtksourcesearch.c
 gtksourceview/gtksourcestyle.c
 gtksourceview/gtksourcestylescheme.c
 gtksourceview/gtksourcestyleschememanager.c


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