Move finding results in the background

Antonio Scandurra created

Change summary

Cargo.lock              |  1 
crates/find/Cargo.toml  |  1 
crates/find/src/find.rs | 92 ++++++++++++++++++++++++++++--------------
3 files changed, 62 insertions(+), 32 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1727,6 +1727,7 @@ dependencies = [
  "editor",
  "gpui",
  "postage",
+ "smol",
  "theme",
  "workspace",
 ]

crates/find/Cargo.toml 🔗

@@ -13,3 +13,4 @@ gpui = { path = "../gpui" }
 theme = { path = "../theme" }
 workspace = { path = "../workspace" }
 postage = { version = "0.4.1", features = ["futures-traits"] }
+smol = { version = "1.2" }

crates/find/src/find.rs 🔗

@@ -1,10 +1,11 @@
 use aho_corasick::AhoCorasickBuilder;
 use editor::{char_kind, Editor, EditorSettings};
 use gpui::{
-    action, elements::*, keymap::Binding, Entity, MutableAppContext, RenderContext, View,
+    action, elements::*, keymap::Binding, Entity, MutableAppContext, RenderContext, Task, View,
     ViewContext, ViewHandle,
 };
 use postage::watch;
+use smol::future::yield_now;
 use std::sync::Arc;
 use workspace::{ItemViewHandle, Settings, Toolbar, Workspace};
 
@@ -33,6 +34,7 @@ struct FindBar {
     settings: watch::Receiver<Settings>,
     query_editor: ViewHandle<Editor>,
     active_editor: Option<ViewHandle<Editor>>,
+    pending_search: Option<Task<()>>,
     case_sensitive_mode: bool,
     whole_word_mode: bool,
     regex_mode: bool,
@@ -116,6 +118,7 @@ impl FindBar {
             whole_word_mode: false,
             regex_mode: false,
             settings,
+            pending_search: None,
         }
     }
 
@@ -184,26 +187,38 @@ impl FindBar {
         _: &editor::Event,
         cx: &mut ViewContext<Self>,
     ) {
-        if let Some(editor) = &self.active_editor {
-            let search = self.query_editor.read(cx).text(cx);
-            let theme = &self.settings.borrow().theme.find;
-            editor.update(cx, |editor, cx| {
-                if search.is_empty() {
-                    editor.clear_highlighted_ranges::<Self>(cx);
-                    return;
-                }
-
-                let search = AhoCorasickBuilder::new()
-                    .auto_configure(&[&search])
-                    .ascii_case_insensitive(!self.case_sensitive_mode)
-                    .build(&[&search]);
-                let buffer = editor.buffer().read(cx).snapshot(cx);
-                let ranges = search
-                    .stream_find_iter(buffer.bytes_in_range(0..buffer.len()))
-                    .filter_map(|mat| {
+        self.update_matches(cx);
+    }
+
+    fn update_matches(&mut self, cx: &mut ViewContext<Self>) {
+        let search = self.query_editor.read(cx).text(cx);
+        self.pending_search.take();
+        if let Some(editor) = self.active_editor.as_ref() {
+            if search.is_empty() {
+                editor.update(cx, |editor, cx| editor.clear_highlighted_ranges::<Self>(cx));
+            } else {
+                let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
+                let case_sensitive_mode = self.case_sensitive_mode;
+                let whole_word_mode = self.whole_word_mode;
+                let ranges = cx.background().spawn(async move {
+                    const YIELD_INTERVAL: usize = 20000;
+
+                    let search = AhoCorasickBuilder::new()
+                        .auto_configure(&[&search])
+                        .ascii_case_insensitive(!case_sensitive_mode)
+                        .build(&[&search]);
+                    let mut ranges = Vec::new();
+                    for (ix, mat) in search
+                        .stream_find_iter(buffer.bytes_in_range(0..buffer.len()))
+                        .enumerate()
+                    {
+                        if (ix + 1) % YIELD_INTERVAL == 0 {
+                            yield_now().await;
+                        }
+
                         let mat = mat.unwrap();
 
-                        if self.whole_word_mode {
+                        if whole_word_mode {
                             let prev_kind =
                                 buffer.reversed_chars_at(mat.start()).next().map(char_kind);
                             let start_kind =
@@ -211,21 +226,34 @@ impl FindBar {
                             let end_kind =
                                 char_kind(buffer.reversed_chars_at(mat.end()).next().unwrap());
                             let next_kind = buffer.chars_at(mat.end()).next().map(char_kind);
-                            if Some(start_kind) != prev_kind && Some(end_kind) != next_kind {
-                                Some(
-                                    buffer.anchor_after(mat.start())
-                                        ..buffer.anchor_before(mat.end()),
-                                )
-                            } else {
-                                None
+                            if Some(start_kind) == prev_kind || Some(end_kind) == next_kind {
+                                continue;
                             }
-                        } else {
-                            Some(buffer.anchor_after(mat.start())..buffer.anchor_before(mat.end()))
                         }
-                    })
-                    .collect();
-                editor.highlight_ranges::<Self>(ranges, theme.match_background, cx);
-            });
+
+                        ranges.push(
+                            buffer.anchor_after(mat.start())..buffer.anchor_before(mat.end()),
+                        );
+                    }
+
+                    ranges
+                });
+
+                let editor = editor.downgrade();
+                self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move {
+                    let ranges = ranges.await;
+                    if let Some((this, editor)) =
+                        cx.read(|cx| this.upgrade(cx).zip(editor.upgrade(cx)))
+                    {
+                        this.update(&mut cx, |this, cx| {
+                            let theme = &this.settings.borrow().theme.find;
+                            editor.update(cx, |editor, cx| {
+                                editor.highlight_ranges::<Self>(ranges, theme.match_background, cx)
+                            });
+                        });
+                    }
+                }));
+            }
         }
     }
 }