[geary/wip/794700-lazy-load-conversations: 7/19] Convert conversation viewer to highlight find terms via the FTS index



commit 04be24a0835a1ec9c48d3545ea652d0d0f9a33fe
Author: Michael Gratton <mike vee net>
Date:   Thu Jan 17 12:27:58 2019 +1100

    Convert conversation viewer to highlight find terms via the FTS index
    
    This means that both search and find use the same highlighting
    mechanism, find picks up search's stemming mechansim, and that we still
    matching messages when doing a find that haven't had their bodies
    loaded.

 src/client/application/geary-controller.vala       |  1 -
 src/client/components/main-window.vala             |  9 +-
 .../conversation-viewer/conversation-list-box.vala | 95 +++++++---------------
 .../conversation-viewer/conversation-viewer.vala   | 84 +++++++++++--------
 4 files changed, 87 insertions(+), 102 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 8dce8fe1..4673e9cf 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -1262,7 +1262,6 @@ public class GearyController : Geary.BaseObject {
                         convo,
                         store,
                         this.avatar_store,
-                        this.application.config,
                         (obj, ret) => {
                             try {
                                 viewer.load_conversation.end(ret);
diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala
index 13c311da..e6577fa1 100644
--- a/src/client/components/main-window.vala
+++ b/src/client/components/main-window.vala
@@ -30,7 +30,7 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
     public MainToolbar main_toolbar { get; private set; }
     public SearchBar search_bar { get; private set; default = new SearchBar(); }
     public ConversationListView conversation_list_view  { get; private set; }
-    public ConversationViewer conversation_viewer { get; private set; default = new ConversationViewer(); }
+    public ConversationViewer conversation_viewer { get; private set; }
     public StatusBar status_bar { get; private set; default = new StatusBar(); }
     private MonitoredSpinner spinner = new MonitoredSpinner();
 
@@ -255,9 +255,12 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
     }
 
     private void setup_layout(Configuration config) {
-        // ConversationListView
         this.conversation_list_view = new ConversationListView(this);
-        // Toolbar
+
+        this.conversation_viewer = new ConversationViewer(
+            this.application.config
+        );
+
         this.main_toolbar = new MainToolbar(config);
         this.main_toolbar.bind_property("search-open", this.search_bar, "search-mode-enabled",
             BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
diff --git a/src/client/conversation-viewer/conversation-list-box.vala 
b/src/client/conversation-viewer/conversation-list-box.vala
index d986b108..a3738915 100644
--- a/src/client/conversation-viewer/conversation-list-box.vala
+++ b/src/client/conversation-viewer/conversation-list-box.vala
@@ -590,72 +590,35 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
     /**
      * Loads search term matches for this list's emails.
      */
-    public async void load_search_terms() {
-        Geary.SearchFolder? search_folder =
-            this.conversation.base_folder as Geary.SearchFolder;
-        Geary.SearchQuery? query = null;
-        if (search_folder != null) {
-            query = search_folder.search_query;
-        }
-        if (query != null) {
-
-            // List all IDs of emails we're viewing.
-            Gee.Collection<Geary.EmailIdentifier> ids =
-                new Gee.ArrayList<Geary.EmailIdentifier>();
-            foreach (Gee.Map.Entry<Geary.EmailIdentifier, EmailRow> entry
-                        in this.email_rows.entries) {
-                if (entry.value.get_visible()) {
-                    ids.add(entry.key);
-                }
-            }
-
-            Gee.Set<string>? search_matches = null;
-            try {
-                search_matches = yield search_folder.get_search_matches_async(
-                    ids, cancellable
-                );
-            } catch (Error e) {
-                debug("Error highlighting search results: %s", e.message);
-                // Continue on here since if nothing else we have the
-                // fudging to fall back on immediately below.
-            }
+    public async void highlight_matching_email(Geary.SearchQuery query)
+        throws GLib.Error {
+        this.search_terms = null;
+        this.search_matches_found = 0;
 
-            if (search_matches == null)
-                search_matches = new Gee.HashSet<string>();
-
-            // This applies a fudge-factor set of matches when the database results
-            // aren't entirely satisfactory, such as when you search for an email
-            // address and the database tokenizes out the @ and ., etc.  It's not meant
-            // to be comprehensive, just a little extra highlighting applied to make
-            // the results look a little closer to what you typed.
-            foreach (string word in query.raw.split(" ")) {
-                if (word.has_suffix("\""))
-                    word = word.substring(0, word.length - 1);
-                if (word.has_prefix("\""))
-                    word = word.substring(1);
-
-                if (!Geary.String.is_empty_or_whitespace(word))
-                    search_matches.add(word);
-            }
+        Geary.Account account = this.conversation.base_folder.account;
+        Gee.Collection<Geary.EmailIdentifier>? matching =
+            yield account.local_search_async(
+                query,
+                this.conversation.get_count(),
+                0,
+                null,
+                this.conversation.get_email_ids(),
+                this.cancellable
+            );
 
-            if (!this.cancellable.is_cancelled()) {
-                highlight_search_terms(search_matches);
-            }
-        }
-    }
+        if (matching != null) {
+            this.search_terms = yield account.get_search_matches_async(
+                query, matching, this.cancellable
+            );
 
-    /**
-     * Applies search term highlighting to all email views.
-     *
-     * Returns true if any were found, else returns false.
-     */
-    public void highlight_search_terms(Gee.Set<string> terms) {
-        this.search_terms = terms;
-        this.search_matches_found = 0;
-        foreach (Gtk.Widget child in get_children()) {
-            EmailRow? row = child as EmailRow;
-            if (row != null) {
-                apply_search_terms(row);
+            if (this.search_terms != null) {
+                foreach (Geary.EmailIdentifier id in matching) {
+                    EmailRow? row = this.email_rows.get(id);
+                    if (row != null) {
+                        row.expand();
+                        apply_search_terms(row);
+                    }
+                }
             }
         }
     }
@@ -742,6 +705,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
 
         if (!this.cancellable.is_cancelled()) {
             EmailRow row = add_email(full_email);
+            row.expand();
             update_first_last_row();
             yield row.view.load_avatars(this.avatar_store);
         }
@@ -927,8 +891,6 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
     }
 
     private void apply_search_terms(EmailRow row) {
-        // Message bodies need to be loaded to be able to search for
-        // them?
         if (row.view.message_bodies_loaded) {
             this.apply_search_terms_impl.begin(row);
         } else {
@@ -942,6 +904,9 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
     private async void apply_search_terms_impl(EmailRow row) {
         bool found = false;
         foreach (ConversationMessage view in row.view) {
+            if (this.search_terms == null) {
+                break;
+            }
             uint count = yield view.highlight_search_terms(this.search_terms);
             if (count > 0) {
                 found = true;
diff --git a/src/client/conversation-viewer/conversation-viewer.vala 
b/src/client/conversation-viewer/conversation-viewer.vala
index f521bdc7..e7393b39 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -26,6 +26,8 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
         get { return (get_visible_child() == this.composer_page); }
     }
 
+    private Configuration config;
+
     // Stack pages
     [GtkChild]
     private Gtk.Spinner loading_page;
@@ -67,8 +69,9 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
     /**
      * Constructs a new conversation view instance.
      */
-    public ConversationViewer() {
+    public ConversationViewer(Configuration config) {
         base_ref();
+        this.config = config;
 
         EmptyPlaceholder no_conversations = new EmptyPlaceholder();
         no_conversations.title = _("No conversations selected");
@@ -198,8 +201,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
      */
     public async void load_conversation(Geary.App.Conversation conversation,
                                         Geary.App.EmailStore email_store,
-                                        Application.AvatarStore avatar_store,
-                                        Configuration config)
+                                        Application.AvatarStore avatar_store)
         throws GLib.Error {
         remove_current_list();
 
@@ -207,7 +209,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
             conversation,
             email_store,
             avatar_store,
-            config,
+            this.config,
             this.conversation_scroller.get_vadjustment()
         );
 
@@ -230,21 +232,25 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
                 this.conversation_find_next.set_sensitive(found);
                 this.conversation_find_prev.set_sensitive(found);
             });
-        Gee.Set<string>? find_terms = get_find_search_terms();
-        if (find_terms != null) {
-            new_list.highlight_search_terms(find_terms);
-        }
-
         add_new_list(new_list);
         set_visible_child(this.conversation_page);
 
         yield new_list.load_conversation();
 
-        // Highlight matching terms from the search if it exists, but
-        // don't clobber any find terms.
-        if (find_terms == null &&
-            conversation.base_folder is Geary.SearchFolder) {
-            yield new_list.load_search_terms();
+        // Highlight matching terms from find if active, otherwise
+        // from the search folder if that's where we are at
+        Geary.SearchQuery? query = get_find_search_query(
+            conversation.base_folder.account
+        );
+        if (query == null) {
+            Geary.SearchFolder? search_folder =
+                conversation.base_folder as Geary.SearchFolder;
+            if (search_folder != null) {
+                query = search_folder.search_query;
+            }
+        }
+        if (query != null) {
+            new_list.highlight_matching_email.begin(query);
         }
     }
 
@@ -315,21 +321,27 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
         base.set_visible_child(widget);
     }
 
-    private Gee.Set<string>? get_find_search_terms() {
-        Gee.Set<string>? terms = null;
-        string search = this.conversation_find_entry.get_text().strip();
-        if (search.length > 0) {
-            terms = new Gee.HashSet<string>();
-            terms.add(search);
+    private Geary.SearchQuery? get_find_search_query(Geary.Account account) {
+        Geary.SearchQuery? query = null;
+        if (this.conversation_find_bar.get_search_mode()) {
+            string text = this.conversation_find_entry.get_text().strip();
+            // Require find string of at least two chars to avoid
+            // opening every message in the conversation as soon as
+            // the user presses a key
+            if (text.length >= 2) {
+                query = account.open_search(
+                    text, this.config.get_search_strategy()
+                );
+            }
         }
-        return terms;
+        return query;
     }
 
     [GtkCallback]
     private void on_find_mode_changed(Object obj, ParamSpec param) {
         if (this.current_list != null) {
             if (this.conversation_find_bar.get_search_mode()) {
-                // Find was enabled
+                // Find became enabled
                 ConversationEmail? email_view =
                     this.current_list.get_selection_view();
                 if (email_view != null) {
@@ -342,13 +354,19 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
                         });
                 }
             } else {
-                // Find was disabled
+                // Find became disabled, re-show search terms if any
                 this.current_list.unmark_search_terms();
-                if (!(this.current_list.conversation.base_folder
-                      is Geary.SearchFolder)) {
-                    //this.current_list.update_collapsed_state();
-                } else {
-                    this.current_list.load_search_terms.begin();
+                Geary.SearchFolder? search_folder = (
+                    this.current_list.conversation.base_folder
+                    as Geary.SearchFolder
+                );
+                if (search_folder != null) {
+                    Geary.SearchQuery? search_query = search_folder.search_query;
+                    if (search_query != null) {
+                        this.current_list.highlight_matching_email.begin(
+                            search_query
+                        );
+                    }
                 }
             }
         }
@@ -359,11 +377,11 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
         this.conversation_find_next.set_sensitive(false);
         this.conversation_find_prev.set_sensitive(false);
         if (this.current_list != null) {
-            Gee.Set<string>? terms = get_find_search_terms();
-            if (terms != null) {
-                // Have a search string
-                this.current_list.highlight_search_terms(terms);
-                // XXX scroll to first match
+            Geary.SearchQuery? query = get_find_search_query(
+                this.current_list.conversation.base_folder.account
+            );
+            if (query != null) {
+                this.current_list.highlight_matching_email.begin(query);
             }
         }
     }


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