Optimize two slow code paths (#2728)

Max Brunsfeld created

Linear:
https://linear.app/zed-industries/issue/Z-2578/zed-launches-very-slow-for-user

I was searching for the cause of a slow startup time reported in the
above issue, and I don't think I found it, but I did find two very
noticeable slow code paths while profiling, and fixed them.

###  Notes

1. When starting the JSON language server, we provide it with a JSON
schema for our settings. For the `theme` setting, the JSON schema needs
to read all of the themes in the registry, to generate a list of valid
theme names. Previously, as part of this, we were deserializing each
theme from JSON, which took a lot of CPU. Now, we don't do that.
2. When an FS event occurs within a git repository, we reload the git
status for all entries in that git repository. Previously, we did that
via a separate `libgit2` call per FS entry (including ignored entries,
so many thousands in the case of the `zed` repo). Now we do one
`libgit2` call, asking for all of the statuses. Git carries an index of
all of the files with statuses, so this is fast.

Release Notes:

- Improved the the performance of starting up a  JSON language server.
- Improved the performance of handling changes to git repositories, such
as changing branches or committing.

Change summary

crates/fs/src/repository.rs        |  1 +
crates/project/src/worktree.rs     |  9 +++++----
crates/theme/src/theme_registry.rs | 22 +++++++++++++++++-----
crates/theme/src/theme_settings.rs |  4 ++--
4 files changed, 25 insertions(+), 11 deletions(-)

Detailed changes

crates/fs/src/repository.rs 🔗

@@ -33,6 +33,7 @@ pub trait GitRepository: Send {
     fn statuses(&self) -> Option<TreeMap<RepoPath, GitFileStatus>>;
 
     fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>>;
+
     fn branches(&self) -> Result<Vec<Branch>> {
         Ok(vec![])
     }

crates/project/src/worktree.rs 🔗

@@ -2022,6 +2022,9 @@ impl LocalSnapshot {
     ) -> Vec<Arc<Path>> {
         let mut changes = vec![];
         let mut edits = vec![];
+
+        let statuses = repo_ptr.statuses();
+
         for mut entry in self
             .descendent_entries(false, false, &work_directory.0)
             .cloned()
@@ -2029,10 +2032,8 @@ impl LocalSnapshot {
             let Ok(repo_path) = entry.path.strip_prefix(&work_directory.0) else {
                 continue;
             };
-            let git_file_status = repo_ptr
-                .status(&RepoPath(repo_path.into()))
-                .log_err()
-                .flatten();
+            let repo_path = RepoPath(repo_path.to_path_buf());
+            let git_file_status = statuses.as_ref().and_then(|s| s.get(&repo_path).copied());
             if entry.git_status != git_file_status {
                 entry.git_status = git_file_status;
                 changes.push(entry.path.clone());

crates/theme/src/theme_registry.rs 🔗

@@ -5,6 +5,7 @@ use parking_lot::Mutex;
 use serde::Deserialize;
 use serde_json::Value;
 use std::{
+    borrow::Cow,
     collections::HashMap,
     sync::{
         atomic::{AtomicUsize, Ordering::SeqCst},
@@ -43,7 +44,7 @@ impl ThemeRegistry {
         this
     }
 
-    pub fn list(&self, staff: bool) -> impl Iterator<Item = ThemeMeta> + '_ {
+    pub fn list_names(&self, staff: bool) -> impl Iterator<Item = Cow<str>> + '_ {
         let mut dirs = self.assets.list("themes/");
 
         if !staff {
@@ -53,10 +54,21 @@ impl ThemeRegistry {
                 .collect()
         }
 
-        dirs.into_iter().filter_map(|path| {
-            let filename = path.strip_prefix("themes/")?;
-            let theme_name = filename.strip_suffix(".json")?;
-            self.get(theme_name).ok().map(|theme| theme.meta.clone())
+        fn get_name(path: &str) -> Option<&str> {
+            path.strip_prefix("themes/")?.strip_suffix(".json")
+        }
+
+        dirs.into_iter().filter_map(|path| match path {
+            Cow::Borrowed(path) => Some(Cow::Borrowed(get_name(path)?)),
+            Cow::Owned(path) => Some(Cow::Owned(get_name(&path)?.to_string())),
+        })
+    }
+
+    pub fn list(&self, staff: bool) -> impl Iterator<Item = ThemeMeta> + '_ {
+        self.list_names(staff).filter_map(|theme_name| {
+            self.get(theme_name.as_ref())
+                .ok()
+                .map(|theme| theme.meta.clone())
         })
     }
 

crates/theme/src/theme_settings.rs 🔗

@@ -149,8 +149,8 @@ impl settings::Setting for ThemeSettings {
         let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
         let theme_names = cx
             .global::<Arc<ThemeRegistry>>()
-            .list(params.staff_mode)
-            .map(|theme| Value::String(theme.name.clone()))
+            .list_names(params.staff_mode)
+            .map(|theme_name| Value::String(theme_name.to_string()))
             .collect();
 
         let theme_name_schema = SchemaObject {