[gtksourceview/wip/regex-search: 1/5] Regex search
- From: Sébastien Wilmet <swilmet src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtksourceview/wip/regex-search: 1/5] Regex search
- Date: Sun, 28 Jul 2013 19:06:07 +0000 (UTC)
commit 0491692a36edd5e9eb3a6202324f0f2d24b8762c
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 430cf71..3308374 100644
--- a/gtksourceview/gtksourcebuffer.c
+++ b/gtksourceview/gtksourcebuffer.c
@@ -100,8 +100,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
@@ -171,7 +172,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
@@ -404,7 +407,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
*/
@@ -483,6 +488,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;
@@ -737,6 +776,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;
@@ -816,6 +860,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;
@@ -2877,6 +2929,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.
@@ -3167,6 +3288,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
*/
@@ -3198,6 +3323,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, ®ion_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, ®ion_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, ®ion_iter, 0);
+
+ while (!gtk_text_region_iterator_is_end (®ion_iter))
+ {
+ GtkTextIter subregion_start;
+ GtkTextIter subregion_end;
+
+ gtk_text_region_iterator_get_subregion (®ion_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 (®ion_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, ®ion_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, ®ion_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]