Extract project_core out of project (#8438)

Piotr Osiewicz created

That's done to unblock work for dynamic tasks (`task` crate has to
access the worktree yet it is a dependency of a `project`).
Release Notes:

- N/A

Change summary

Cargo.lock                                  | 52 ++++++++++++--
Cargo.toml                                  |  3 
crates/project/Cargo.toml                   |  2 
crates/project/src/project.rs               | 80 +---------------------
crates/project_core/Cargo.toml              | 52 ++++++++++++++
crates/project_core/LICENSE-GPL             |  1 
crates/project_core/src/ignore.rs           |  0 
crates/project_core/src/lib.rs              | 81 +++++++++++++++++++++++
crates/project_core/src/project_settings.rs |  0 
crates/project_core/src/worktree.rs         | 28 ++++---
crates/project_core/src/worktree_tests.rs   |  6 
11 files changed, 207 insertions(+), 98 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3940,15 +3940,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
 [[package]]
 name = "globset"
-version = "0.4.13"
+version = "0.4.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d"
+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
 dependencies = [
  "aho-corasick",
  "bstr",
- "fnv",
  "log",
- "regex",
+ "regex-automata 0.4.5",
+ "regex-syntax 0.8.2",
 ]
 
 [[package]]
@@ -4461,17 +4461,16 @@ dependencies = [
 
 [[package]]
 name = "ignore"
-version = "0.4.20"
+version = "0.4.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
+checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
 dependencies = [
+ "crossbeam-deque",
  "globset",
- "lazy_static",
  "log",
  "memchr",
- "regex",
+ "regex-automata 0.4.5",
  "same-file",
- "thread_local",
  "walkdir",
  "winapi-util",
 ]
@@ -6835,6 +6834,7 @@ dependencies = [
  "postage",
  "prettier",
  "pretty_assertions",
+ "project_core",
  "rand 0.8.5",
  "regex",
  "release_channel",
@@ -6855,6 +6855,40 @@ dependencies = [
  "which 6.0.0",
 ]
 
+[[package]]
+name = "project_core"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "client",
+ "clock",
+ "collections",
+ "fs",
+ "futures 0.3.28",
+ "fuzzy",
+ "git",
+ "git2",
+ "gpui",
+ "ignore",
+ "itertools 0.11.0",
+ "language",
+ "log",
+ "lsp",
+ "parking_lot 0.11.2",
+ "postage",
+ "pretty_assertions",
+ "rand 0.8.5",
+ "rpc",
+ "schemars",
+ "serde",
+ "serde_json",
+ "settings",
+ "smol",
+ "sum_tree",
+ "text",
+ "util",
+]
+
 [[package]]
 name = "project_panel"
 version = "0.1.0"

Cargo.toml 🔗

@@ -53,6 +53,7 @@ members = [
     "crates/picker",
     "crates/prettier",
     "crates/project",
+    "crates/project_core",
     "crates/project_panel",
     "crates/project_symbols",
     "crates/quick_action_bar",
@@ -151,6 +152,7 @@ plugin = { path = "crates/plugin" }
 plugin_macros = { path = "crates/plugin_macros" }
 prettier = { path = "crates/prettier" }
 project = { path = "crates/project" }
+project_core = { path = "crates/project_core" }
 project_panel = { path = "crates/project_panel" }
 project_symbols = { path = "crates/project_symbols" }
 quick_action_bar = { path = "crates/quick_action_bar" }
@@ -205,6 +207,7 @@ futures = "0.3"
 git2 = { version = "0.15", default-features = false }
 globset = "0.4"
 hex = "0.4.3"
+ignore = "0.4.22"
 indoc = "1"
 # We explicitly disable http2 support in isahc.
 isahc = { version = "1.7.2", default-features = false, features = ["static-curl", "text-decoding"] }

crates/project/Cargo.toml 🔗

@@ -42,6 +42,7 @@ node_runtime.workspace = true
 parking_lot.workspace = true
 postage.workspace = true
 prettier.workspace = true
+project_core.workspace = true
 rand.workspace = true
 regex.workspace = true
 rpc.workspace = true
@@ -71,6 +72,7 @@ release_channel.workspace = true
 lsp = { workspace = true, features = ["test-support"] }
 prettier = { workspace = true, features = ["test-support"] }
 pretty_assertions.workspace = true
+project_core = { workspace = true, features = ["test-support"] }
 rpc = { workspace = true, features = ["test-support"] }
 settings = { workspace = true, features = ["test-support"] }
 unindent.workspace = true

crates/project/src/project.rs 🔗

@@ -1,18 +1,13 @@
 pub mod debounced_delay;
-mod ignore;
 pub mod lsp_command;
 pub mod lsp_ext_command;
 mod prettier_support;
-pub mod project_settings;
 pub mod search;
 mod task_inventory;
 pub mod terminals;
-pub mod worktree;
 
 #[cfg(test)]
 mod project_tests;
-#[cfg(test)]
-mod worktree_tests;
 
 use anyhow::{anyhow, bail, Context as _, Result};
 use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};
@@ -57,7 +52,8 @@ use node_runtime::NodeRuntime;
 use parking_lot::{Mutex, RwLock};
 use postage::watch;
 use prettier_support::{DefaultPrettier, PrettierInstance};
-use project_settings::{LspSettings, ProjectSettings};
+use project_core::project_settings::{LspSettings, ProjectSettings};
+pub use project_core::{DiagnosticSummary, ProjectEntryId};
 use rand::prelude::*;
 
 use rpc::{ErrorCode, ErrorExt as _};
@@ -96,8 +92,9 @@ use util::{
 pub use fs::*;
 #[cfg(any(test, feature = "test-support"))]
 pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
+pub use project_core::project_settings;
+pub use project_core::worktree::{self, *};
 pub use task_inventory::Inventory;
-pub use worktree::*;
 
 const MAX_SERVER_REINSTALL_ATTEMPT_COUNT: u64 = 4;
 const SERVER_REINSTALL_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
@@ -318,12 +315,6 @@ pub struct ProjectPath {
     pub path: Arc<Path>,
 }
 
-#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize)]
-pub struct DiagnosticSummary {
-    pub error_count: usize,
-    pub warning_count: usize,
-}
-
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Location {
     pub buffer: Model<Buffer>,
@@ -441,67 +432,6 @@ impl Hover {
 #[derive(Default)]
 pub struct ProjectTransaction(pub HashMap<Model<Buffer>, language::Transaction>);
 
-impl DiagnosticSummary {
-    fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
-        let mut this = Self {
-            error_count: 0,
-            warning_count: 0,
-        };
-
-        for entry in diagnostics {
-            if entry.diagnostic.is_primary {
-                match entry.diagnostic.severity {
-                    DiagnosticSeverity::ERROR => this.error_count += 1,
-                    DiagnosticSeverity::WARNING => this.warning_count += 1,
-                    _ => {}
-                }
-            }
-        }
-
-        this
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.error_count == 0 && self.warning_count == 0
-    }
-
-    pub fn to_proto(
-        &self,
-        language_server_id: LanguageServerId,
-        path: &Path,
-    ) -> proto::DiagnosticSummary {
-        proto::DiagnosticSummary {
-            path: path.to_string_lossy().to_string(),
-            language_server_id: language_server_id.0 as u64,
-            error_count: self.error_count as u32,
-            warning_count: self.warning_count as u32,
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct ProjectEntryId(usize);
-
-impl ProjectEntryId {
-    pub const MAX: Self = Self(usize::MAX);
-
-    pub fn new(counter: &AtomicUsize) -> Self {
-        Self(counter.fetch_add(1, SeqCst))
-    }
-
-    pub fn from_proto(id: u64) -> Self {
-        Self(id as usize)
-    }
-
-    pub fn to_proto(&self) -> u64 {
-        self.0 as u64
-    }
-
-    pub fn to_usize(&self) -> usize {
-        self.0
-    }
-}
-
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum FormatTrigger {
     Save,
@@ -7105,7 +7035,7 @@ impl Project {
                                 snapshot.repository_and_work_directory_for_path(&path)?;
                             let repo = snapshot.get_local_repo(&repo)?;
                             let relative_path = path.strip_prefix(&work_directory).ok()?;
-                            let base_text = repo.repo_ptr.lock().load_index_text(relative_path);
+                            let base_text = repo.load_index_text(relative_path);
                             Some((buffer, base_text))
                         })
                         .collect::<Vec<_>>()

crates/project_core/Cargo.toml 🔗

@@ -0,0 +1,52 @@
+[package]
+name = "project_core"
+version = "0.1.0"
+edition = "2021"
+publish = false
+license = "GPL-3.0-or-later"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[features]
+test-support = [
+    "client/test-support",
+    "language/test-support",
+    "settings/test-support",
+    "text/test-support",
+    "gpui/test-support",
+]
+
+[dependencies]
+anyhow.workspace = true
+client.workspace = true
+clock.workspace = true
+collections.workspace = true
+fs.workspace = true
+futures.workspace = true
+fuzzy.workspace = true
+git.workspace = true
+gpui.workspace = true
+ignore.workspace = true
+itertools.workspace = true
+language.workspace = true
+log.workspace = true
+lsp.workspace = true
+parking_lot.workspace = true
+postage.workspace = true
+rpc.workspace = true
+schemars.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+settings.workspace = true
+smol.workspace = true
+sum_tree.workspace = true
+text.workspace = true
+util.workspace = true
+
+[dev-dependencies]
+clock = {workspace = true, features = ["test-support"]}
+collections = { workspace = true, features = ["test-support"] }
+git2.workspace = true
+gpui = {workspace = true, features = ["test-support"]}
+rand.workspace = true
+settings = {workspace = true, features = ["test-support"]}
+pretty_assertions.workspace = true

crates/project_core/src/lib.rs 🔗

@@ -0,0 +1,81 @@
+use std::path::Path;
+use std::sync::atomic::AtomicUsize;
+use std::sync::atomic::Ordering::SeqCst;
+
+use language::DiagnosticEntry;
+use lsp::{DiagnosticSeverity, LanguageServerId};
+use rpc::proto;
+use serde::Serialize;
+
+mod ignore;
+pub mod project_settings;
+pub mod worktree;
+#[cfg(test)]
+mod worktree_tests;
+
+#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct ProjectEntryId(usize);
+
+impl ProjectEntryId {
+    pub const MAX: Self = Self(usize::MAX);
+
+    pub fn new(counter: &AtomicUsize) -> Self {
+        Self(counter.fetch_add(1, SeqCst))
+    }
+
+    pub fn from_proto(id: u64) -> Self {
+        Self(id as usize)
+    }
+
+    pub fn to_proto(&self) -> u64 {
+        self.0 as u64
+    }
+
+    pub fn to_usize(&self) -> usize {
+        self.0
+    }
+}
+
+#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize)]
+pub struct DiagnosticSummary {
+    pub error_count: usize,
+    pub warning_count: usize,
+}
+
+impl DiagnosticSummary {
+    fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
+        let mut this = Self {
+            error_count: 0,
+            warning_count: 0,
+        };
+
+        for entry in diagnostics {
+            if entry.diagnostic.is_primary {
+                match entry.diagnostic.severity {
+                    DiagnosticSeverity::ERROR => this.error_count += 1,
+                    DiagnosticSeverity::WARNING => this.warning_count += 1,
+                    _ => {}
+                }
+            }
+        }
+
+        this
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.error_count == 0 && self.warning_count == 0
+    }
+
+    pub fn to_proto(
+        &self,
+        language_server_id: LanguageServerId,
+        path: &Path,
+    ) -> proto::DiagnosticSummary {
+        proto::DiagnosticSummary {
+            path: path.to_string_lossy().to_string(),
+            language_server_id: language_server_id.0 as u64,
+            error_count: self.error_count as u32,
+            warning_count: self.warning_count as u32,
+        }
+    }
+}

crates/project/src/worktree.rs → crates/project_core/src/worktree.rs 🔗

@@ -1,12 +1,12 @@
 use crate::{
-    copy_recursive, ignore::IgnoreStack, project_settings::ProjectSettings, DiagnosticSummary,
-    ProjectEntryId, RemoveOptions,
+    ignore::IgnoreStack, project_settings::ProjectSettings, DiagnosticSummary, ProjectEntryId,
 };
 use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
 use anyhow::{anyhow, Context as _, Result};
 use client::{proto, Client};
 use clock::ReplicaId;
 use collections::{HashMap, HashSet, VecDeque};
+use fs::{copy_recursive, RemoveOptions};
 use fs::{
     repository::{GitFileStatus, GitRepository, RepoPath},
     Fs,
@@ -257,6 +257,12 @@ pub struct LocalRepositoryEntry {
     pub(crate) git_dir_path: Arc<Path>,
 }
 
+impl LocalRepositoryEntry {
+    pub fn load_index_text(&self, relative_file_path: &Path) -> Option<String> {
+        self.repo_ptr.lock().load_index_text(relative_file_path)
+    }
+}
+
 impl Deref for LocalSnapshot {
     type Target = Snapshot;
 
@@ -718,7 +724,7 @@ impl LocalWorktree {
         path.starts_with(&self.abs_path)
     }
 
-    pub(crate) fn load_buffer(
+    pub fn load_buffer(
         &mut self,
         id: BufferId,
         path: &Path,
@@ -1593,7 +1599,7 @@ impl RemoteWorktree {
         self.completed_scan_id >= scan_id
     }
 
-    pub(crate) fn wait_for_snapshot(&mut self, scan_id: usize) -> impl Future<Output = Result<()>> {
+    pub fn wait_for_snapshot(&mut self, scan_id: usize) -> impl Future<Output = Result<()>> {
         let (tx, rx) = oneshot::channel();
         if self.observed_snapshot(scan_id) {
             let _ = tx.send(());
@@ -1659,7 +1665,7 @@ impl RemoteWorktree {
         })
     }
 
-    pub(crate) fn delete_entry(
+    pub fn delete_entry(
         &mut self,
         id: ProjectEntryId,
         scan_id: usize,
@@ -2093,7 +2099,7 @@ impl Snapshot {
 }
 
 impl LocalSnapshot {
-    pub(crate) fn get_local_repo(&self, repo: &RepositoryEntry) -> Option<&LocalRepositoryEntry> {
+    pub fn get_local_repo(&self, repo: &RepositoryEntry) -> Option<&LocalRepositoryEntry> {
         self.git_repositories.get(&repo.work_directory.0)
     }
 
@@ -2744,7 +2750,7 @@ impl WorktreeId {
         Self(handle_id)
     }
 
-    pub(crate) fn from_proto(id: u64) -> Self {
+    pub fn from_proto(id: u64) -> Self {
         Self(id as usize)
     }
 
@@ -2829,10 +2835,10 @@ pub struct File {
     pub worktree: Model<Worktree>,
     pub path: Arc<Path>,
     pub mtime: SystemTime,
-    pub(crate) entry_id: Option<ProjectEntryId>,
-    pub(crate) is_local: bool,
-    pub(crate) is_deleted: bool,
-    pub(crate) is_private: bool,
+    pub entry_id: Option<ProjectEntryId>,
+    pub is_local: bool,
+    pub is_deleted: bool,
+    pub is_private: bool,
 }
 
 impl language::File for File {

crates/project/src/worktree_tests.rs → crates/project_core/src/worktree_tests.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     project_settings::ProjectSettings,
+    worktree::{Entry, EntryKind, PathChange, Worktree},
     worktree::{Event, Snapshot, WorktreeModelHandle},
-    Entry, EntryKind, PathChange, Project, Worktree,
 };
 use anyhow::Result;
 use client::Client;
@@ -14,7 +14,7 @@ use postage::stream::Stream;
 use pretty_assertions::assert_eq;
 use rand::prelude::*;
 use serde_json::json;
-use settings::SettingsStore;
+use settings::{Settings, SettingsStore};
 use std::{
     env,
     fmt::Write,
@@ -2535,6 +2535,6 @@ fn init_test(cx: &mut gpui::TestAppContext) {
     cx.update(|cx| {
         let settings_store = SettingsStore::test(cx);
         cx.set_global(settings_store);
-        Project::init_settings(cx);
+        ProjectSettings::register(cx);
     });
 }