WIP

Nathan Sobo created

Change summary

Cargo.lock                   | 128 ++++++++++++++++++++++++++++++++++++++
zed/Cargo.toml               |   1 
zed/src/lib.rs               |   1 
zed/src/worktree/worktree.rs |  63 +++++++++--------
4 files changed, 163 insertions(+), 30 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -199,6 +199,15 @@ name = "async-task"
 version = "4.0.3"
 source = "git+https://github.com/zed-industries/async-task?rev=341b57d6de98cdfd7b418567b8de2022ca993a6e#341b57d6de98cdfd7b418567b8de2022ca993a6e"
 
+[[package]]
+name = "atomic"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3410529e8288c463bedb5930f82833bc0c90e5d2fe639a56582a4d09220b281"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "atomic-waker"
 version = "1.0.0"
@@ -524,6 +533,16 @@ dependencies = [
  "crossbeam-utils 0.8.2",
 ]
 
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils 0.8.2",
+]
+
 [[package]]
 name = "crossbeam-utils"
 version = "0.7.2"
@@ -799,6 +818,20 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
 
+[[package]]
+name = "futures"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
 [[package]]
 name = "futures-channel"
 version = "0.3.12"
@@ -806,6 +839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846"
 dependencies = [
  "futures-core",
+ "futures-sink",
 ]
 
 [[package]]
@@ -835,6 +869,31 @@ dependencies = [
  "waker-fn",
 ]
 
+[[package]]
+name = "futures-sink"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23"
+
+[[package]]
+name = "futures-task"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc"
+
+[[package]]
+name = "futures-util"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+]
+
 [[package]]
 name = "generator"
 version = "0.6.23"
@@ -1328,6 +1387,26 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d70072c20945e1ab871c472a285fc772aefd4f5407723c206242f2c6f94595d6"
 
+[[package]]
+name = "pin-project"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc174859768806e91ae575187ada95c91a29e96a98dc5d2cd9a1fed039501ba6"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.4"
@@ -1371,6 +1450,28 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "pollster"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cce106fd2646acbe31a0e4006f75779d535c26a44f153ada196e9edcfc6d944"
+
+[[package]]
+name = "postage"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a63d25391d04a097954b76aba742b6b5b74f213dfe3dbaeeb36e8ddc1c657f0b"
+dependencies = [
+ "atomic",
+ "crossbeam-queue",
+ "futures",
+ "log",
+ "pin-project",
+ "pollster",
+ "static_assertions",
+ "thiserror",
+]
+
 [[package]]
 name = "ppv-lite86"
 version = "0.2.10"
@@ -1862,6 +1963,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
 [[package]]
 name = "strsim"
 version = "0.8.0"
@@ -1933,6 +2040,26 @@ dependencies = [
  "unicode-width",
 ]
 
+[[package]]
+name = "thiserror"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "thread_local"
 version = "1.1.3"
@@ -2298,6 +2425,7 @@ dependencies = [
  "log",
  "num_cpus",
  "parking_lot",
+ "postage",
  "rand 0.8.3",
  "rust-embed",
  "seahash",

zed/Cargo.toml 🔗

@@ -26,6 +26,7 @@ libc = "0.2"
 log = "0.4"
 num_cpus = "1.13.0"
 parking_lot = "0.11.1"
+postage = {version = "0.4.1", features = ["futures-traits"]}
 rand = "0.8.3"
 rust-embed = "5.9.0"
 seahash = "4.1"

zed/src/lib.rs 🔗

@@ -7,6 +7,7 @@ pub mod settings;
 mod sum_tree;
 #[cfg(test)]
 mod test;
+mod throttle;
 mod time;
 mod timer;
 mod util;

zed/src/worktree/worktree.rs 🔗

@@ -5,7 +5,7 @@ use super::{
 };
 use crate::{
     editor::{History, Snapshot},
-    timer,
+    throttle::throttled,
     util::post_inc,
 };
 use anyhow::{anyhow, Result};
@@ -14,6 +14,7 @@ use easy_parallel::Parallel;
 use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task};
 use ignore::dir::{Ignore, IgnoreBuilder};
 use parking_lot::RwLock;
+use postage::watch;
 use smol::prelude::*;
 use std::{
     collections::HashMap,
@@ -37,7 +38,13 @@ struct WorktreeState {
     entries: HashMap<u64, Entry>,
     file_paths: Vec<PathEntry>,
     histories: HashMap<u64, History>,
-    scanning: bool,
+    scan_state: watch::Sender<ScanState>,
+}
+
+#[derive(Clone)]
+enum ScanState {
+    Scanning,
+    Idle,
 }
 
 struct DirToScan {
@@ -53,6 +60,8 @@ impl Worktree {
     where
         T: Into<PathBuf>,
     {
+        let scan_state = watch::channel_with(ScanState::Scanning);
+
         let tree = Self(Arc::new(RwLock::new(WorktreeState {
             id,
             path: path.into(),
@@ -60,22 +69,22 @@ impl Worktree {
             entries: HashMap::new(),
             file_paths: Vec::new(),
             histories: HashMap::new(),
-            scanning: true,
+            scan_state: scan_state.0,
         })));
 
-        let done_scanning = {
+        {
             let tree = tree.clone();
-            ctx.background_executor().spawn(async move {
-                tree.scan_dirs()?;
-                Ok(())
-            })
-        };
-
-        ctx.spawn(done_scanning, Self::done_scanning).detach();
+            std::thread::spawn(move || {
+                if let Err(error) = tree.scan_dirs() {
+                    log::error!("error scanning worktree: {}", error);
+                }
+                tree.set_scan_state(ScanState::Idle);
+            });
+        }
 
         ctx.spawn_stream(
-            timer::repeat(Duration::from_millis(100)).map(|_| ()),
-            Self::scanning,
+            throttled(Duration::from_millis(100), scan_state.1),
+            Self::observe_scan_state,
             |_, _| {},
         )
         .detach();
@@ -83,6 +92,10 @@ impl Worktree {
         tree
     }
 
+    fn set_scan_state(&self, state: ScanState) {
+        *self.0.write().scan_state.borrow_mut() = state;
+    }
+
     fn scan_dirs(&self) -> io::Result<()> {
         let path = self.0.read().path.clone();
         let metadata = fs::metadata(&path)?;
@@ -201,7 +214,8 @@ impl Worktree {
         is_symlink: bool,
         is_ignored: bool,
     ) {
-        let entries = &mut self.0.write().entries;
+        let mut state = self.0.write();
+        let entries = &mut state.entries;
         entries.insert(
             ino,
             Entry::Dir {
@@ -213,6 +227,7 @@ impl Worktree {
                 children: Vec::new(),
             },
         );
+        *state.scan_state.borrow_mut() = ScanState::Scanning;
     }
 
     fn insert_file(
@@ -247,6 +262,7 @@ impl Worktree {
             lowercase_path,
             is_ignored,
         });
+        *state.scan_state.borrow_mut() = ScanState::Scanning;
     }
 
     pub fn entry_path(&self, mut entry_id: u64) -> Result<PathBuf> {
@@ -394,22 +410,9 @@ impl Worktree {
         })
     }
 
-    fn scanning(&mut self, _: (), ctx: &mut ModelContext<Self>) {
-        if self.0.read().scanning {
-            ctx.notify();
-        } else {
-            ctx.halt_stream();
-        }
-    }
-
-    fn done_scanning(&mut self, result: io::Result<()>, ctx: &mut ModelContext<Self>) {
-        log::info!("done scanning");
-        self.0.write().scanning = false;
-        if let Err(error) = result {
-            log::error!("error populating worktree: {}", error);
-        } else {
-            ctx.notify();
-        }
+    fn observe_scan_state(&mut self, _: ScanState, ctx: &mut ModelContext<Self>) {
+        // log::info!("observe {:?}", std::time::Instant::now());
+        ctx.notify()
     }
 }