medit/moo/mooedit/mootextsearch.c

847 lines
24 KiB
C

/*
* mootextsearch.c
*
* Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* See COPYING file that comes with this distribution.
*/
#include "mooedit/mootextsearch-private.h"
#include "mooedit/gtksourceview/gtksourceview-api.h"
#include <glib/gregex.h>
#include <string.h>
gboolean
_moo_text_search_regex_forward (const GtkTextIter *search_start,
const GtkTextIter *search_end,
GRegex *regex,
GtkTextIter *match_start,
GtkTextIter *match_end,
char **string,
int *match_offset,
int *match_len,
GMatchInfo **match_infop)
{
GtkTextIter start, end;
GtkTextBuffer *buffer;
int start_offset;
char *text, *text_start;
g_return_val_if_fail (search_start != NULL, FALSE);
g_return_val_if_fail (match_start != NULL && match_end != NULL, FALSE);
g_return_val_if_fail (regex != NULL, FALSE);
buffer = gtk_text_iter_get_buffer (search_start);
start = *search_start;
start_offset = gtk_text_iter_get_line_offset (&start);
if (start_offset)
gtk_text_iter_set_line_offset (&start, 0);
end = *search_start;
if (!gtk_text_iter_ends_line (&end))
gtk_text_iter_forward_to_line_end (&end);
while (TRUE)
{
GMatchInfo *match_info = NULL;
text = gtk_text_buffer_get_slice (buffer, &start, &end, TRUE);
text_start = g_utf8_offset_to_pointer (text, start_offset);
if (g_regex_match_full (regex, text, -1, text_start - text, 0, &match_info, NULL))
{
int start_pos, end_pos;
g_match_info_fetch_pos (match_info, 0, &start_pos, &end_pos);
*match_start = start;
gtk_text_iter_forward_chars (match_start, g_utf8_pointer_to_offset (text, text + start_pos));
if (search_end && gtk_text_iter_compare (match_start, search_end) > 0)
{
g_match_info_free (match_info);
g_free (text);
return FALSE;
}
*match_end = *match_start;
gtk_text_iter_forward_chars (match_end, g_utf8_pointer_to_offset (text + start_pos, text + end_pos));
if (string)
*string = text;
else
g_free (text);
if (match_offset)
*match_offset = start_pos;
if (match_len)
*match_len = end_pos - start_pos;
if (match_infop)
*match_infop = match_info;
else
g_match_info_free (match_info);
return TRUE;
}
g_match_info_free (match_info);
start = end;
start_offset = 0;
if (!gtk_text_iter_forward_line (&start))
break;
if (search_end && gtk_text_iter_compare (&start, search_end) > 0)
break;
end = start;
if (!gtk_text_iter_ends_line (&end))
gtk_text_iter_forward_to_line_end (&end);
}
return FALSE;
}
static gboolean
find_last_match (GRegex *regex,
const char *text,
GRegexMatchFlags flags,
int *start_pos,
int *end_pos,
GMatchInfo **match_infop)
{
int len, start;
GMatchInfo *match_info = NULL;
*start_pos = -1;
len = strlen (text);
start = 0;
/* XXX a leak!!! */
while (g_regex_match_full (regex, text, len, start, flags, &match_info, NULL))
{
g_match_info_fetch_pos (match_info, 0, start_pos, end_pos);
start = *start_pos + 1;
if (start >= len)
break;
g_match_info_free (match_info);
match_info = NULL;
}
if (*start_pos >= 0 && match_infop)
*match_infop = match_info;
else if (match_info)
g_match_info_free (match_info);
return *start_pos >= 0;
}
gboolean
_moo_text_search_regex_backward (const GtkTextIter *search_start,
const GtkTextIter *search_end,
GRegex *regex,
GtkTextIter *match_start,
GtkTextIter *match_end,
char **string,
int *match_offset,
int *match_len,
GMatchInfo **match_info)
{
GtkTextIter slice_start, slice_end;
GtkTextBuffer *buffer;
char *text;
GRegexMatchFlags flags;
g_return_val_if_fail (search_start != NULL, FALSE);
g_return_val_if_fail (match_start != NULL && match_end != NULL, FALSE);
g_return_val_if_fail (regex != NULL, FALSE);
buffer = gtk_text_iter_get_buffer (search_start);
slice_start = *search_start;
slice_end = slice_start;
gtk_text_iter_backward_line (&slice_start);
flags = 0;
if (!gtk_text_iter_ends_line (&slice_end))
flags |= G_REGEX_MATCH_NOTEOL;
while (TRUE)
{
int start_pos, end_pos;
text = gtk_text_buffer_get_slice (buffer, &slice_start, &slice_end, TRUE);
if (find_last_match (regex, text, flags, &start_pos, &end_pos, match_info))
{
*match_start = slice_start;
gtk_text_iter_forward_chars (match_start, g_utf8_pointer_to_offset (text, text + start_pos));
/* XXX how about not last match? */
if (search_end && gtk_text_iter_compare (match_start, search_end) < 0)
{
if (match_info && *match_info)
{
g_match_info_free (*match_info);
*match_info = NULL;
}
g_free (text);
return FALSE;
}
*match_end = *match_start;
gtk_text_iter_forward_chars (match_end, g_utf8_pointer_to_offset (text + start_pos, text + end_pos));
if (string)
*string = text;
else
g_free (text);
if (match_offset)
*match_offset = start_pos;
if (match_len)
*match_len = end_pos - start_pos;
return TRUE;
}
slice_end = slice_start;
flags = 0;
if (gtk_text_iter_is_start (&slice_end))
break;
if (search_end && gtk_text_iter_compare (&slice_end, search_end) < 0)
break;
gtk_text_iter_backward_line (&slice_start);
}
return FALSE;
}
static GRegex *
get_regex (const char *pattern,
MooTextSearchFlags flags,
GError **error)
{
static GRegex *saved_regex;
static char *saved_pattern;
static MooTextSearchFlags saved_flags;
if (!saved_pattern || strcmp (saved_pattern, pattern) || saved_flags != flags)
{
GRegexCompileFlags re_flags = 0;
if (saved_regex)
g_regex_unref (saved_regex);
g_free (saved_pattern);
saved_pattern = g_strdup (pattern);
saved_flags = flags;
if (flags & MOO_TEXT_SEARCH_CASELESS)
re_flags |= G_REGEX_CASELESS;
saved_regex = g_regex_new (saved_pattern,
re_flags | G_REGEX_OPTIMIZE,
0, error);
if (!saved_regex)
{
g_free (saved_pattern);
saved_pattern = NULL;
return NULL;
}
}
return saved_regex;
}
inline static gboolean
is_word_char (const GtkTextIter *iter)
{
gunichar c = gtk_text_iter_get_char (iter);
return c == '_' || g_unichar_isalnum (c);
}
static gboolean
is_whole_word (const GtkTextIter *start,
const GtkTextIter *end)
{
GtkTextIter s = *start;
GtkTextIter e = *end;
gtk_text_iter_order (&s, &e);
if (!gtk_text_iter_starts_line (&s))
{
gtk_text_iter_backward_char (&s);
if (is_word_char (&s))
return FALSE;
}
if (!gtk_text_iter_ends_line (&e) && is_word_char (&e))
return FALSE;
return TRUE;
}
gboolean
moo_text_search_forward (const GtkTextIter *start,
const char *str,
MooTextSearchFlags flags,
GtkTextIter *match_start,
GtkTextIter *match_end,
const GtkTextIter *end)
{
GRegex *regex;
GError *error = NULL;
g_return_val_if_fail (start != NULL, FALSE);
g_return_val_if_fail (str != NULL, FALSE);
g_return_val_if_fail (match_start != NULL && match_end != NULL, FALSE);
if (!(flags & MOO_TEXT_SEARCH_REGEX))
{
GtkSourceSearchFlags gs_flags = 0;
GtkTextIter real_end, real_start;
if (flags & MOO_TEXT_SEARCH_CASELESS)
gs_flags |= GTK_SOURCE_SEARCH_CASE_INSENSITIVE;
/* http://bugzilla.gnome.org/show_bug.cgi?id=321299 */
if (!end || gtk_text_iter_is_end (end))
{
end = NULL;
}
else
{
real_end = *end;
gtk_text_iter_forward_char (&real_end);
end = &real_end;
}
if (!(flags & MOO_TEXT_SEARCH_WHOLE_WORDS))
return gtk_source_iter_forward_search (start, str, gs_flags,
match_start, match_end, end);
real_start = *start;
while (gtk_source_iter_forward_search (&real_start, str, gs_flags,
match_start, match_end, end))
{
if (is_whole_word (match_start, match_end))
return TRUE;
real_start = *match_end;
}
return FALSE;
}
regex = get_regex (str, flags, &error);
if (error)
{
g_warning ("%s: %s", G_STRLOC, error->message);
g_error_free (error);
}
if (!regex)
return FALSE;
return _moo_text_search_regex_forward (start, end, regex,
match_start, match_end,
NULL, NULL, NULL, NULL);
}
gboolean
moo_text_search_backward (const GtkTextIter *start,
const char *str,
MooTextSearchFlags flags,
GtkTextIter *match_start,
GtkTextIter *match_end,
const GtkTextIter *end)
{
GRegex *regex;
GError *error = NULL;
g_return_val_if_fail (start != NULL, FALSE);
g_return_val_if_fail (str != NULL, FALSE);
g_return_val_if_fail (match_start != NULL && match_end != NULL, FALSE);
if (!(flags & MOO_TEXT_SEARCH_REGEX))
{
GtkSourceSearchFlags gs_flags = 0;
GtkTextIter real_start;
if (flags & MOO_TEXT_SEARCH_CASELESS)
gs_flags |= GTK_SOURCE_SEARCH_CASE_INSENSITIVE;
if (!(flags & MOO_TEXT_SEARCH_WHOLE_WORDS))
return gtk_source_iter_backward_search (start, str, gs_flags,
match_start, match_end, end);
real_start = *start;
while (gtk_source_iter_backward_search (&real_start, str, gs_flags,
match_start, match_end, end))
{
if (is_whole_word (match_start, match_end))
return TRUE;
real_start = *match_start;
}
return FALSE;
}
regex = get_regex (str, flags, &error);
if (error)
{
g_warning ("%s: %s", G_STRLOC, error->message);
g_error_free (error);
}
if (!regex)
return FALSE;
return _moo_text_search_regex_backward (start, end, regex,
match_start, match_end,
NULL, NULL, NULL, NULL);
}
static int
moo_text_replace_regex_all_real (GtkTextIter *start,
GtkTextIter *end,
GRegex *regex,
const char *replacement,
gboolean replacement_literal,
MooTextReplaceFunc func,
gpointer func_data)
{
int count = 0;
GtkTextMark *end_mark;
GtkTextBuffer *buffer;
MooTextReplaceResponse response;
gboolean need_end_user_action = FALSE;
char *freeme = NULL;
const char *const_replacement = NULL;
GError *error = NULL;
gboolean was_zero_match = FALSE;
g_return_val_if_fail (start != NULL, 0);
g_return_val_if_fail (regex != NULL, 0);
g_return_val_if_fail (replacement != NULL, 0);
if (replacement_literal)
{
const_replacement = replacement;
}
else
{
gboolean has_references = FALSE;
if (!g_regex_check_replacement (replacement, &has_references, &error))
{
g_warning ("%s: %s", G_STRLOC, error->message);
g_error_free (error);
return 0;
}
if (!has_references)
{
freeme = g_match_info_expand_references (NULL, replacement, &error);
if (error)
{
g_warning ("%s: %s", G_STRLOC, error->message);
g_error_free (error);
return 0;
}
const_replacement = freeme;
}
}
buffer = gtk_text_iter_get_buffer (start);
if (end && !gtk_text_iter_is_end (end))
{
end_mark = gtk_text_buffer_create_mark (buffer, NULL, end, TRUE);
}
else
{
end = NULL;
end_mark = NULL;
}
if (func)
{
response = MOO_TEXT_REPLACE_DO_REPLACE;
}
else
{
gtk_text_buffer_begin_user_action (buffer);
need_end_user_action = TRUE;
response = MOO_TEXT_REPLACE_ALL;
}
while (TRUE)
{
GtkTextIter match_start, match_end;
char *freeme_here = NULL;
const char *real_replacement;
char *string;
int match_len;
GMatchInfo *match_info = NULL;
if (!_moo_text_search_regex_forward (start, end, regex, &match_start, &match_end,
&string, NULL, &match_len, &match_info))
goto out;
if (!match_len)
{
if (was_zero_match && gtk_text_iter_equal (&match_start, start))
{
was_zero_match = FALSE;
g_free (string);
g_match_info_free (match_info);
if (!gtk_text_iter_forward_char (start))
goto out;
continue;
}
was_zero_match = TRUE;
}
else
{
was_zero_match = FALSE;
}
if (const_replacement)
{
real_replacement = const_replacement;
g_free (string);
}
else
{
freeme_here = g_match_info_expand_references (match_info, replacement, &error);
g_free (string);
if (!freeme_here)
{
g_warning ("%s: %s", G_STRLOC, error->message);
g_error_free (error);
g_match_info_free (match_info);
goto out;
}
real_replacement = freeme_here;
}
if (response != MOO_TEXT_REPLACE_ALL)
{
response = func (NULL, regex, real_replacement, &match_start, &match_end, func_data);
if (!response)
{
g_free (freeme_here);
g_match_info_free (match_info);
goto out;
}
}
if (response != MOO_TEXT_REPLACE_SKIP && (match_len || *real_replacement))
{
count++;
if (response == MOO_TEXT_REPLACE_ALL)
{
if (!need_end_user_action)
{
gtk_text_buffer_begin_user_action (buffer);
need_end_user_action = TRUE;
}
}
else
{
gtk_text_buffer_begin_user_action (buffer);
}
gtk_text_buffer_delete (buffer, &match_start, &match_end);
gtk_text_buffer_insert (buffer, &match_end, real_replacement, -1);
if (response != MOO_TEXT_REPLACE_ALL)
gtk_text_buffer_end_user_action (buffer);
}
*start = match_end;
if (was_zero_match && !*real_replacement)
{
if (gtk_text_iter_is_end (start))
{
g_free (freeme_here);
g_match_info_free (match_info);
goto out;
}
gtk_text_iter_forward_char (start);
was_zero_match = FALSE;
}
if (end)
gtk_text_buffer_get_iter_at_mark (buffer, end, end_mark);
g_match_info_free (match_info);
g_free (freeme_here);
}
out:
if (end_mark)
gtk_text_buffer_delete_mark (buffer, end_mark);
if (need_end_user_action)
gtk_text_buffer_end_user_action (buffer);
g_free (freeme);
return count;
}
int
_moo_text_replace_regex_all (GtkTextIter *start,
GtkTextIter *end,
GRegex *regex,
const char *replacement,
gboolean replacement_literal)
{
g_return_val_if_fail (start != NULL, 0);
g_return_val_if_fail (regex != NULL, 0);
g_return_val_if_fail (replacement != NULL, 0);
return moo_text_replace_regex_all_real (start, end, regex, replacement,
replacement_literal, NULL, NULL);
}
int
_moo_text_replace_regex_all_interactive (GtkTextIter *start,
GtkTextIter *end,
GRegex *regex,
const char *replacement,
gboolean replacement_literal,
MooTextReplaceFunc func,
gpointer func_data)
{
g_return_val_if_fail (start != NULL, 0);
g_return_val_if_fail (regex != NULL, 0);
g_return_val_if_fail (replacement != NULL, 0);
g_return_val_if_fail (func != NULL, 0);
return moo_text_replace_regex_all_real (start, end, regex,
replacement, replacement_literal,
func, func_data);
}
int
moo_text_replace_all (GtkTextIter *start,
GtkTextIter *end,
const char *text,
const char *replacement,
MooTextSearchFlags flags)
{
int count = 0;
GtkTextMark *end_mark;
GtkTextBuffer *buffer;
g_return_val_if_fail (start != NULL, 0);
g_return_val_if_fail (text != NULL, 0);
g_return_val_if_fail (text[0] != 0, 0);
g_return_val_if_fail (replacement != NULL, 0);
if (flags & MOO_TEXT_SEARCH_REGEX)
{
GError *error = NULL;
GRegex *regex = get_regex (text, flags, &error);
if (error)
{
g_warning ("%s: %s", G_STRLOC, error->message);
g_error_free (error);
}
if (!regex)
return 0;
return _moo_text_replace_regex_all (start, end, regex, replacement,
flags & MOO_TEXT_SEARCH_REPL_LITERAL);
}
buffer = gtk_text_iter_get_buffer (start);
gtk_text_buffer_begin_user_action (buffer);
if (!end || gtk_text_iter_is_end (end))
end = NULL;
else
gtk_text_iter_forward_char (end);
if (end)
end_mark = gtk_text_buffer_create_mark (buffer, NULL, end, TRUE);
else
end_mark = NULL;
while (TRUE)
{
GtkTextIter match_start, match_end;
if (!moo_text_search_forward (start, text, flags, &match_start, &match_end, end))
goto out;
count++;
gtk_text_buffer_delete (buffer, &match_start, &match_end);
gtk_text_buffer_insert (buffer, &match_end, replacement, -1);
*start = match_end;
if (end)
gtk_text_buffer_get_iter_at_mark (buffer, end, end_mark);
}
out:
if (end_mark)
gtk_text_buffer_delete_mark (buffer, end_mark);
gtk_text_buffer_end_user_action (buffer);
return count;
}
int
_moo_text_replace_all_interactive (GtkTextIter *start,
GtkTextIter *end,
const char *text,
const char *replacement,
MooTextSearchFlags flags,
MooTextReplaceFunc func,
gpointer func_data)
{
int count = 0;
GtkTextMark *end_mark;
GtkTextBuffer *buffer;
MooTextReplaceResponse response = MOO_TEXT_REPLACE_DO_REPLACE;
gboolean need_end_user_action = FALSE;
g_return_val_if_fail (start != NULL, 0);
g_return_val_if_fail (text != NULL, 0);
g_return_val_if_fail (text[0] != 0, 0);
g_return_val_if_fail (replacement != NULL, 0);
g_return_val_if_fail (func != NULL, 0);
if (flags & MOO_TEXT_SEARCH_REGEX)
{
GError *error = NULL;
GRegex *regex = get_regex (text, flags, &error);
if (error)
{
g_warning ("%s: %s", G_STRLOC, error->message);
g_error_free (error);
}
if (!regex)
return 0;
return _moo_text_replace_regex_all_interactive (start, end, regex, replacement,
flags & MOO_TEXT_SEARCH_REPL_LITERAL,
func, func_data);
}
buffer = gtk_text_iter_get_buffer (start);
if (!end || gtk_text_iter_is_end (end))
end = NULL;
else
gtk_text_iter_forward_char (end);
if (end)
end_mark = gtk_text_buffer_create_mark (buffer, NULL, end, TRUE);
else
end_mark = NULL;
while (TRUE)
{
GtkTextIter match_start, match_end;
if (!moo_text_search_forward (start, text, flags, &match_start, &match_end, end))
goto out;
if (response != MOO_TEXT_REPLACE_ALL)
{
response = func (text, NULL, replacement, &match_start, &match_end, func_data);
if (!response)
goto out;
}
if (response != MOO_TEXT_REPLACE_SKIP)
{
count++;
if (response == MOO_TEXT_REPLACE_ALL)
{
if (!need_end_user_action)
{
gtk_text_buffer_begin_user_action (buffer);
need_end_user_action = TRUE;
}
}
else
{
gtk_text_buffer_begin_user_action (buffer);
}
gtk_text_buffer_delete (buffer, &match_start, &match_end);
gtk_text_buffer_insert (buffer, &match_end, replacement, -1);
if (response != MOO_TEXT_REPLACE_ALL)
gtk_text_buffer_end_user_action (buffer);
}
*start = match_end;
if (end)
gtk_text_buffer_get_iter_at_mark (buffer, end, end_mark);
}
out:
if (end_mark)
gtk_text_buffer_delete_mark (buffer, end_mark);
if (need_end_user_action)
gtk_text_buffer_end_user_action (buffer);
return count;
}