[evolution/449-support-markdown-in-composer] EMarkdownEditor: Implement search & replace



commit 8132e4d77dceee633bec3be70bfe62a743c1f745
Author: Milan Crha <mcrha redhat com>
Date:   Fri Feb 11 08:23:04 2022 +0100

    EMarkdownEditor: Implement search & replace

 src/e-util/e-markdown-editor.c | 307 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 307 insertions(+)
---
diff --git a/src/e-util/e-markdown-editor.c b/src/e-util/e-markdown-editor.c
index 160d7c5a24..1c9950b839 100644
--- a/src/e-util/e-markdown-editor.c
+++ b/src/e-util/e-markdown-editor.c
@@ -485,17 +485,224 @@ e_markdown_editor_select_all (EContentEditor *cnt_editor)
        g_signal_emit_by_name (self->priv->text_view, "select-all", 0, TRUE, NULL);
 }
 
+static gunichar *
+e_markdown_editor_prepare_search_text (const gchar *text,
+                                      guint32 *flags)
+{
+       gunichar *search_text;
+       guint ii;
+
+       if (!text || !*text)
+               return NULL;
+
+       /* Fine-tune the direction flags:
+          forward & next = forward
+          forward & previous = backward
+          backward & next = backward
+          backward & previous = forward
+       */
+       if ((*flags & E_CONTENT_EDITOR_FIND_MODE_BACKWARDS) == 0 &&
+           (*flags & E_CONTENT_EDITOR_FIND_PREVIOUS) != 0) {
+               *flags = ((*flags) & ~(E_CONTENT_EDITOR_FIND_MODE_BACKWARDS | E_CONTENT_EDITOR_FIND_PREVIOUS 
| E_CONTENT_EDITOR_FIND_NEXT)) |
+                       E_CONTENT_EDITOR_FIND_MODE_BACKWARDS;
+       } else if ((*flags & E_CONTENT_EDITOR_FIND_MODE_BACKWARDS) != 0 &&
+                  (*flags & E_CONTENT_EDITOR_FIND_PREVIOUS) != 0) {
+               *flags = ((*flags) & ~(E_CONTENT_EDITOR_FIND_MODE_BACKWARDS | E_CONTENT_EDITOR_FIND_PREVIOUS 
| E_CONTENT_EDITOR_FIND_NEXT));
+       }
+
+       search_text = g_utf8_to_ucs4 (text, -1, NULL, NULL, NULL);
+
+       if (search_text && (*flags & E_CONTENT_EDITOR_FIND_MODE_BACKWARDS) != 0) {
+               guint len;
+
+               for (len = 0; search_text[len]; len++) {
+                       /* Just count them */
+               }
+
+               if (len) {
+                       len--;
+
+                       /* Swap the letters backwards */
+                       for (ii = 0; ii < len; ii++) {
+                               gunichar chr = search_text[ii];
+                               search_text[ii] = search_text[len];
+                               search_text[len] = chr;
+                               len--;
+                       }
+               }
+       }
+
+       if (search_text && (*flags & E_CONTENT_EDITOR_FIND_CASE_INSENSITIVE) != 0) {
+               for (ii = 0; search_text[ii]; ii++) {
+                       search_text[ii] = g_unichar_tolower (search_text[ii]);
+               }
+       }
+
+       return search_text;
+}
+
+static gboolean
+e_markdown_editor_do_search_text (GtkTextBuffer *buffer,
+                                 const gunichar *search_text,
+                                 guint32 flags,
+                                 gboolean *did_wrap_around,
+                                 const GtkTextIter *from_iter,
+                                 const GtkTextIter *limit, /* used only after wrap around */
+                                 GtkTextIter *out_occur_start,
+                                 GtkTextIter *out_occur_end)
+{
+       GtkTextIter iter, from_cursor;
+       gboolean case_insensitive = (flags & E_CONTENT_EDITOR_FIND_CASE_INSENSITIVE) != 0;
+       gboolean wrap_around = (!*did_wrap_around) && (flags & E_CONTENT_EDITOR_FIND_WRAP_AROUND) != 0;
+       gboolean backwards = (flags & E_CONTENT_EDITOR_FIND_MODE_BACKWARDS) != 0;
+       gboolean found = FALSE;
+
+       if (from_iter) {
+               iter = *from_iter;
+       } else {
+               gtk_text_buffer_get_selection_bounds (buffer, &from_cursor, &iter);
+
+               if (!backwards)
+                       from_cursor = iter;
+
+               if (!limit)
+                       limit = &from_cursor;
+
+               iter = from_cursor;
+       }
+
+       if (backwards && !gtk_text_iter_backward_char (&iter)) {
+               if (wrap_around) {
+                       wrap_around = FALSE;
+                       gtk_text_buffer_get_end_iter (buffer, &iter);
+                       if (!gtk_text_iter_backward_char (&iter))
+                               return FALSE;
+               } else {
+                       return FALSE;
+               }
+       }
+
+       while (!found) {
+               gunichar chr;
+
+               chr = gtk_text_iter_get_char (&iter);
+
+               if (chr) {
+                       if ((case_insensitive && g_unichar_tolower (chr) == search_text[0]) ||
+                           (!case_insensitive && chr == search_text[0])) {
+                               GtkTextIter next = iter;
+                               guint ii;
+
+                               for (ii = 1; !found; ii++) {
+                                       if (!search_text[ii]) {
+                                               /* To have selected also the last character */
+                                               if (backwards)
+                                                       gtk_text_iter_forward_char (&iter);
+                                               else
+                                                       gtk_text_iter_forward_char (&next);
+
+                                               found = TRUE;
+                                               if (backwards) {
+                                                       *out_occur_start = next;
+                                                       *out_occur_end = iter;
+                                               } else {
+                                                       *out_occur_start = iter;
+                                                       *out_occur_end = next;
+                                               }
+                                               break;
+                                       }
+
+                                       if ((backwards && !gtk_text_iter_backward_char (&next)) ||
+                                           (!backwards && !gtk_text_iter_forward_char (&next)))
+                                               break;
+
+                                       if (*did_wrap_around && !gtk_text_iter_compare (&iter, limit))
+                                               break;
+
+                                       chr = gtk_text_iter_get_char (&next);
+
+                                       if (!chr)
+                                               break;
+
+                                       if ((case_insensitive && g_unichar_tolower (chr) == search_text[ii]) 
||
+                                           (!case_insensitive && chr == search_text[ii])) {
+                                               /* matched the next letter */
+                                       } else {
+                                               break;
+                                       }
+                               }
+
+                               if (found)
+                                       break;
+                       }
+
+                       if (!found && *did_wrap_around && !gtk_text_iter_compare (&iter, limit))
+                               break;
+               }
+
+               if ((backwards && !gtk_text_iter_backward_char (&iter)) ||
+                   (!backwards && !gtk_text_iter_forward_char (&iter))) {
+                       if (!wrap_around)
+                               break;
+
+                       *did_wrap_around = TRUE;
+                       wrap_around = FALSE;
+
+                       if (backwards)
+                               gtk_text_buffer_get_end_iter (buffer, &iter);
+                       else
+                               gtk_text_buffer_get_start_iter (buffer, &iter);
+
+                       if (!gtk_text_iter_compare (&iter, limit))
+                               break;
+               }
+       }
+
+       return found;
+}
+
 static void
 e_markdown_editor_find (EContentEditor *cnt_editor,
                        guint32 flags,
                        const gchar *text)
 {
+       EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+       GtkTextBuffer *buffer;
+       GtkTextIter occur_start, occur_end;
+       gboolean did_wrap_around = FALSE;
+       gunichar *search_text;
+
+       search_text = e_markdown_editor_prepare_search_text (text, &flags);
+
+       if (!search_text) {
+               e_content_editor_emit_find_done (cnt_editor, 0);
+               return;
+       }
+
+       buffer = gtk_text_view_get_buffer (self->priv->text_view);
+
+       if (e_markdown_editor_do_search_text (buffer, search_text, flags, &did_wrap_around, NULL, NULL, 
&occur_start, &occur_end)) {
+               gtk_text_buffer_select_range (buffer, &occur_start, &occur_end);
+               e_content_editor_emit_find_done (cnt_editor, 1);
+       } else {
+               e_content_editor_emit_find_done (cnt_editor, 0);
+       }
+
+       g_free (search_text);
 }
 
 static void
 e_markdown_editor_replace (EContentEditor *cnt_editor,
                           const gchar *replacement)
 {
+       EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+       GtkTextBuffer *buffer;
+       GtkTextIter start, end;
+
+       buffer = gtk_text_view_get_buffer (self->priv->text_view);
+       gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
+       gtk_text_buffer_delete (buffer, &start, &end);
+       gtk_text_buffer_insert_at_cursor (buffer, replacement, -1);
 }
 
 static void
@@ -504,6 +711,78 @@ e_markdown_editor_replace_all (EContentEditor *cnt_editor,
                               const gchar *find_text,
                               const gchar *replace_with)
 {
+       EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+       GtkTextBuffer *buffer;
+       GtkTextIter occur_start, occur_end, limit, last_replace;
+       gboolean did_wrap_around = FALSE;
+       gunichar *search_text;
+       guint count = 0, replace_len = 0;
+
+       search_text = e_markdown_editor_prepare_search_text (find_text, &flags);
+
+       if (!search_text) {
+               e_content_editor_emit_replace_all_done (cnt_editor, 0);
+               return;
+       }
+
+       buffer = gtk_text_view_get_buffer (self->priv->text_view);
+       gtk_text_buffer_get_selection_bounds (buffer, &occur_start, &occur_end);
+
+       /* Different bound than in search, to replace also current match */
+       if ((flags & E_CONTENT_EDITOR_FIND_MODE_BACKWARDS) != 0)
+               limit = occur_end;
+       else
+               limit = occur_start;
+
+       if (replace_with)
+               replace_len = g_utf8_strlen (replace_with, -1);
+
+       last_replace = limit;
+
+       while (e_markdown_editor_do_search_text (buffer, search_text, flags, &did_wrap_around, &last_replace, 
&limit, &occur_start, &occur_end)) {
+               GtkTextMark *mark;
+               gboolean last_match;
+
+               last_match = did_wrap_around && !gtk_text_iter_compare (&occur_start, &limit);
+
+               if (last_match && !(flags & E_CONTENT_EDITOR_FIND_MODE_BACKWARDS))
+                       break;
+
+               /* Remember where the limit was... */
+               mark = gtk_text_buffer_create_mark (buffer, NULL, &limit, !(flags & 
E_CONTENT_EDITOR_FIND_MODE_BACKWARDS));
+
+               gtk_text_buffer_delete (buffer, &occur_start, &occur_end);
+
+               last_replace = occur_start;
+
+               if (replace_with && *replace_with) {
+                       gtk_text_buffer_insert (buffer, &occur_start, replace_with, -1);
+
+                       /* Get on the first letter of the replaced word */
+                       if ((flags & E_CONTENT_EDITOR_FIND_MODE_BACKWARDS) != 0 &&
+                           !gtk_text_iter_backward_chars (&occur_start, replace_len)) {
+                               break;
+                       }
+
+                       last_replace = occur_start;
+               }
+
+               /* ... then restore the limit, after the buffer changed (which invalidated the iterator) */
+               gtk_text_buffer_get_iter_at_mark (buffer, &limit, mark);
+               gtk_text_buffer_delete_mark (buffer, mark);
+
+               count++;
+
+               if (last_match)
+                       break;
+       }
+
+       g_free (search_text);
+
+       if (count)
+               gtk_text_buffer_select_range (buffer, &last_replace, &last_replace);
+
+       e_content_editor_emit_replace_all_done (cnt_editor, count);
 }
 
 static void
@@ -689,6 +968,32 @@ e_markdown_editor_insert_signature (EContentEditor *cnt_editor,
        return g_strdup (self->priv->signature_uid);
 }
 
+static void
+e_markdown_editor_on_dialog_open (EContentEditor *cnt_editor,
+                                 const gchar *name)
+{
+       if (g_strcmp0 (name, E_CONTENT_EDITOR_DIALOG_REPLACE) == 0) {
+               EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+               GtkTextBuffer *buffer;
+
+               buffer = gtk_text_view_get_buffer (self->priv->text_view);
+               gtk_text_buffer_begin_user_action (buffer);
+       }
+}
+
+static void
+e_markdown_editor_on_dialog_close (EContentEditor *cnt_editor,
+                                  const gchar *name)
+{
+       if (g_strcmp0 (name, E_CONTENT_EDITOR_DIALOG_REPLACE) == 0) {
+               EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+               GtkTextBuffer *buffer;
+
+               buffer = gtk_text_view_get_buffer (self->priv->text_view);
+               gtk_text_buffer_end_user_action (buffer);
+       }
+}
+
 static gboolean
 e_markdown_editor_can_copy (EMarkdownEditor *self)
 {
@@ -1858,6 +2163,8 @@ e_markdown_editor_content_editor_init (EContentEditorInterface *iface)
        iface->selection_restore = e_markdown_editor_selection_restore;
        iface->get_current_signature_uid = e_markdown_editor_get_current_signature_uid;
        iface->insert_signature = e_markdown_editor_insert_signature;
+       iface->on_dialog_open = e_markdown_editor_on_dialog_open;
+       iface->on_dialog_close = e_markdown_editor_on_dialog_close;
 }
 
 static void


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