Start on project-wide find

Antonio Scandurra created

Change summary

Cargo.lock                      | 118 +++++++++++++++++++++++++++++++++++
crates/find/Cargo.toml          |   1 
crates/find/src/find.rs         |   2 
crates/find/src/project_find.rs |  46 +++++++++++++
crates/project/Cargo.toml       |   1 
crates/project/src/project.rs   | 109 +++++++++++++++++++++++++++++++
6 files changed, 275 insertions(+), 2 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -745,7 +745,9 @@ version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d"
 dependencies = [
+ "lazy_static",
  "memchr",
+ "regex-automata",
 ]
 
 [[package]]
@@ -776,6 +778,12 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
 
+[[package]]
+name = "bytecount"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e"
+
 [[package]]
 name = "bytemuck"
 version = "1.5.1"
@@ -1653,6 +1661,15 @@ dependencies = [
  "cfg-if 1.0.0",
 ]
 
+[[package]]
+name = "encoding_rs_io"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83"
+dependencies = [
+ "encoding_rs",
+]
+
 [[package]]
 name = "entities"
 version = "1.0.1"
@@ -1782,6 +1799,7 @@ dependencies = [
  "editor",
  "gpui",
  "postage",
+ "project",
  "regex",
  "smol",
  "theme",
@@ -2248,6 +2266,90 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "grep"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51cb840c560b45a2ffd8abf00190382789d3f596663d5ffeb2e05931c20e8657"
+dependencies = [
+ "grep-cli",
+ "grep-matcher",
+ "grep-printer",
+ "grep-regex",
+ "grep-searcher",
+]
+
+[[package]]
+name = "grep-cli"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dd110c34bb4460d0de5062413b773e385cbf8a85a63fc535590110a09e79e8a"
+dependencies = [
+ "atty",
+ "bstr",
+ "globset",
+ "lazy_static",
+ "log",
+ "regex",
+ "same-file",
+ "termcolor",
+ "winapi-util",
+]
+
+[[package]]
+name = "grep-matcher"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d27563c33062cd33003b166ade2bb4fd82db1fd6a86db764dfdad132d46c1cc"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "grep-printer"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05c271a24daedf5675b61a275a1d0af06e03312ab7856d15433ae6cde044dc72"
+dependencies = [
+ "base64 0.13.0",
+ "bstr",
+ "grep-matcher",
+ "grep-searcher",
+ "serde",
+ "serde_json",
+ "termcolor",
+]
+
+[[package]]
+name = "grep-regex"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121553c9768c363839b92fc2d7cdbbad44a3b70e8d6e7b1b72b05c977527bd06"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "grep-matcher",
+ "log",
+ "regex",
+ "regex-syntax",
+ "thread_local",
+]
+
+[[package]]
+name = "grep-searcher"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fbdbde90ba52adc240d2deef7b6ad1f99f53142d074b771fe9b7bede6c4c23d"
+dependencies = [
+ "bstr",
+ "bytecount",
+ "encoding_rs",
+ "encoding_rs_io",
+ "grep-matcher",
+ "log",
+ "memmap2 0.3.1",
+]
+
 [[package]]
 name = "group"
 version = "0.10.0"
@@ -2911,6 +3013,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "memmap2"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "memoffset"
 version = "0.6.3"
@@ -3563,6 +3674,7 @@ dependencies = [
  "futures",
  "fuzzy",
  "gpui",
+ "grep",
  "ignore",
  "language",
  "lazy_static",
@@ -3866,6 +3978,12 @@ dependencies = [
  "regex-syntax",
 ]
 
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+
 [[package]]
 name = "regex-syntax"
 version = "0.6.25"

crates/find/Cargo.toml 🔗

@@ -10,6 +10,7 @@ path = "src/find.rs"
 collections = { path = "../collections" }
 editor = { path = "../editor" }
 gpui = { path = "../gpui" }
+project = { path = "../project" }
 theme = { path = "../theme" }
 workspace = { path = "../workspace" }
 aho-corasick = "0.7"

crates/find/src/find.rs 🔗

@@ -1,3 +1,5 @@
+mod project_find;
+
 use aho_corasick::AhoCorasickBuilder;
 use anyhow::Result;
 use collections::HashMap;

crates/find/src/project_find.rs 🔗

@@ -0,0 +1,46 @@
+use crate::SearchMode;
+use editor::MultiBuffer;
+use gpui::{Entity, ModelContext, ModelHandle, Task};
+use project::Project;
+
+struct ProjectFind {
+    last_search: SearchParams,
+    project: ModelHandle<Project>,
+    excerpts: ModelHandle<MultiBuffer>,
+    pending_search: Task<Option<()>>,
+}
+
+#[derive(Default)]
+struct SearchParams {
+    query: String,
+    regex: bool,
+    whole_word: bool,
+    case_sensitive: bool,
+}
+
+struct ProjectFindView {
+    model: ModelHandle<ProjectFind>,
+}
+
+impl Entity for ProjectFind {
+    type Event = ();
+}
+
+impl ProjectFind {
+    fn new(project: ModelHandle<Project>, cx: &mut ModelContext<Self>) -> Self {
+        let replica_id = project.read(cx).replica_id();
+        Self {
+            project,
+            last_search: Default::default(),
+            excerpts: cx.add_model(|_| MultiBuffer::new(replica_id)),
+            pending_search: Task::ready(None),
+        }
+    }
+
+    fn search(&mut self, params: SearchParams, cx: &mut ModelContext<Self>) {
+        self.pending_search = cx.spawn_weak(|this, cx| async move {
+            //
+            None
+        });
+    }
+}

crates/project/Cargo.toml 🔗

@@ -29,6 +29,7 @@ util = { path = "../util" }
 anyhow = "1.0.38"
 async-trait = "0.1"
 futures = "0.3"
+grep = "0.2"
 ignore = "0.4"
 lazy_static = "1.4.0"
 libc = "0.2"

crates/project/src/project.rs 🔗

@@ -7,7 +7,7 @@ use anyhow::{anyhow, Context, Result};
 use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
 use clock::ReplicaId;
 use collections::{hash_map, HashMap, HashSet};
-use futures::{future::Shared, Future, FutureExt};
+use futures::{future::Shared, Future, FutureExt, StreamExt};
 use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
 use gpui::{
     AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
@@ -16,7 +16,7 @@ use gpui::{
 use language::{
     range_from_lsp, Anchor, AnchorRangeExt, Bias, Buffer, CodeAction, CodeLabel, Completion,
     Diagnostic, DiagnosticEntry, File as _, Language, LanguageRegistry, Operation, PointUtf16,
-    ToLspPosition, ToOffset, ToPointUtf16, Transaction,
+    Rope, ToLspPosition, ToOffset, ToPointUtf16, Transaction,
 };
 use lsp::{DiagnosticSeverity, DocumentHighlightKind, LanguageServer};
 use lsp_command::*;
@@ -2042,6 +2042,111 @@ impl Project {
         )
     }
 
+    pub fn search(&self, query: &str, cx: &mut ModelContext<Self>) {
+        if self.is_local() {
+            enum SearchItem {
+                Path(PathBuf),
+                Buffer((WeakModelHandle<Buffer>, Rope)),
+            }
+
+            let (queue_tx, queue_rx) = smol::channel::bounded(1024);
+
+            // Submit all worktree paths to the queue.
+            let snapshots = self
+                .strong_worktrees(cx)
+                .filter_map(|tree| {
+                    let tree = tree.read(cx).as_local()?;
+                    Some((tree.abs_path().clone(), tree.snapshot()))
+                })
+                .collect::<Vec<_>>();
+            cx.background()
+                .spawn({
+                    let queue_tx = queue_tx.clone();
+                    async move {
+                        for (snapshot_abs_path, snapshot) in snapshots {
+                            for file in snapshot.files(false, 0) {
+                                if queue_tx
+                                    .send(SearchItem::Path(snapshot_abs_path.join(&file.path)))
+                                    .await
+                                    .is_err()
+                                {
+                                    return;
+                                }
+                            }
+                        }
+                    }
+                })
+                .detach();
+
+            // Submit all the currently-open buffers that are dirty to the queue.
+            let buffers = self
+                .open_buffers
+                .values()
+                .filter_map(|buffer| {
+                    if let OpenBuffer::Loaded(buffer) = buffer {
+                        Some(buffer.clone())
+                    } else {
+                        None
+                    }
+                })
+                .collect::<Vec<_>>();
+            cx.spawn_weak(|_, cx| async move {
+                for buffer in buffers.into_iter().filter_map(|buffer| buffer.upgrade(&cx)) {
+                    let text = buffer.read_with(&cx, |buffer, _| {
+                        if buffer.is_dirty() {
+                            Some(buffer.as_rope().clone())
+                        } else {
+                            None
+                        }
+                    });
+
+                    if let Some(text) = text {
+                        if queue_tx
+                            .send(SearchItem::Buffer((buffer.downgrade(), text)))
+                            .await
+                            .is_err()
+                        {
+                            return;
+                        }
+                    }
+                }
+            })
+            .detach();
+
+            let background = cx.background().clone();
+            cx.background()
+                .spawn(async move {
+                    let workers = background.num_cpus();
+                    background
+                        .scoped(|scope| {
+                            for _ in 0..workers {
+                                let mut paths_rx = queue_rx.clone();
+                                scope.spawn(async move {
+                                    while let Some(item) = paths_rx.next().await {
+                                        match item {
+                                            SearchItem::Path(_) => todo!(),
+                                            SearchItem::Buffer(_) => todo!(),
+                                        }
+                                    }
+                                });
+                            }
+                        })
+                        .await;
+                })
+                .detach();
+            // let multiline = query.contains('\n');
+            // let searcher = grep::searcher::SearcherBuilder::new()
+            //     .multi_line(multiline)
+            //     .build();
+            // searcher.search_path(
+            //     "hey".to_string(),
+            //     "/hello/world",
+            //     grep::searcher::sinks::Lossy(|row, mat| {}),
+            // );
+        } else {
+        }
+    }
+
     fn request_lsp<R: LspCommand>(
         &self,
         buffer_handle: ModelHandle<Buffer>,