Merge pull request #2096 from zed-industries/lazy-load-languages

Max Brunsfeld created

Load languages lazily in the background

Change summary

crates/language/src/buffer_tests.rs    |  20 +
crates/language/src/language.rs        | 256 ++++++++++++++++++++-------
crates/language/src/syntax_map.rs      |  26 +-
crates/project/src/project.rs          |   8 
crates/zed/src/languages.rs            | 122 +++++-------
crates/zed/src/languages/c.rs          |  14 
crates/zed/src/languages/go.rs         |   5 
crates/zed/src/languages/python.rs     |  15 
crates/zed/src/languages/rust.rs       |  27 +-
crates/zed/src/languages/typescript.rs |  10 
crates/zed/src/main.rs                 |  14 -
crates/zed/src/zed.rs                  |   4 
12 files changed, 311 insertions(+), 210 deletions(-)

Detailed changes

crates/language/src/buffer_tests.rs 🔗

@@ -51,7 +51,7 @@ fn test_line_endings(cx: &mut gpui::MutableAppContext) {
 
 #[gpui::test]
 fn test_select_language() {
-    let registry = LanguageRegistry::test();
+    let registry = Arc::new(LanguageRegistry::test());
     registry.add(Arc::new(Language::new(
         LanguageConfig {
             name: "Rust".into(),
@@ -71,27 +71,33 @@ fn test_select_language() {
 
     // matching file extension
     assert_eq!(
-        registry.select_language("zed/lib.rs").map(|l| l.name()),
+        registry.language_for_path("zed/lib.rs").map(|l| l.name()),
         Some("Rust".into())
     );
     assert_eq!(
-        registry.select_language("zed/lib.mk").map(|l| l.name()),
+        registry.language_for_path("zed/lib.mk").map(|l| l.name()),
         Some("Make".into())
     );
 
     // matching filename
     assert_eq!(
-        registry.select_language("zed/Makefile").map(|l| l.name()),
+        registry.language_for_path("zed/Makefile").map(|l| l.name()),
         Some("Make".into())
     );
 
     // matching suffix that is not the full file extension or filename
-    assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None);
     assert_eq!(
-        registry.select_language("zed/a.cars").map(|l| l.name()),
+        registry.language_for_path("zed/cars").map(|l| l.name()),
+        None
+    );
+    assert_eq!(
+        registry.language_for_path("zed/a.cars").map(|l| l.name()),
+        None
+    );
+    assert_eq!(
+        registry.language_for_path("zed/sumk").map(|l| l.name()),
         None
     );
-    assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None);
 }
 
 #[gpui::test]

crates/language/src/language.rs 🔗

@@ -16,7 +16,7 @@ use futures::{
     future::{BoxFuture, Shared},
     FutureExt, TryFutureExt,
 };
-use gpui::{MutableAppContext, Task};
+use gpui::{executor::Background, MutableAppContext, Task};
 use highlight_map::HighlightMap;
 use lazy_static::lazy_static;
 use parking_lot::{Mutex, RwLock};
@@ -26,6 +26,7 @@ use serde::{de, Deserialize, Deserializer};
 use serde_json::Value;
 use std::{
     any::Any,
+    borrow::Cow,
     cell::RefCell,
     fmt::Debug,
     hash::Hash,
@@ -89,8 +90,7 @@ pub struct CachedLspAdapter {
 }
 
 impl CachedLspAdapter {
-    pub async fn new<T: LspAdapter>(adapter: T) -> Arc<Self> {
-        let adapter = Box::new(adapter);
+    pub async fn new(adapter: Box<dyn LspAdapter>) -> Arc<Self> {
         let name = adapter.name().await;
         let server_args = adapter.server_args().await;
         let initialization_options = adapter.initialization_options().await;
@@ -248,6 +248,16 @@ pub struct LanguageConfig {
     pub overrides: HashMap<String, LanguageConfigOverride>,
 }
 
+#[derive(Debug, Default)]
+pub struct LanguageQueries {
+    pub highlights: Option<Cow<'static, str>>,
+    pub brackets: Option<Cow<'static, str>>,
+    pub indents: Option<Cow<'static, str>>,
+    pub outline: Option<Cow<'static, str>>,
+    pub injections: Option<Cow<'static, str>>,
+    pub overrides: Option<Cow<'static, str>>,
+}
+
 #[derive(Clone)]
 pub struct LanguageScope {
     language: Arc<Language>,
@@ -407,8 +417,17 @@ pub enum LanguageServerBinaryStatus {
     Failed { error: String },
 }
 
+struct AvailableLanguage {
+    path: &'static str,
+    config: LanguageConfig,
+    grammar: tree_sitter::Language,
+    lsp_adapter: Option<Box<dyn LspAdapter>>,
+    get_queries: fn(&str) -> LanguageQueries,
+}
+
 pub struct LanguageRegistry {
     languages: RwLock<Vec<Arc<Language>>>,
+    available_languages: RwLock<Vec<AvailableLanguage>>,
     language_server_download_dir: Option<Arc<Path>>,
     lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
     lsp_binary_statuses_rx: async_broadcast::Receiver<(Arc<Language>, LanguageServerBinaryStatus)>,
@@ -422,6 +441,7 @@ pub struct LanguageRegistry {
     >,
     subscription: RwLock<(watch::Sender<()>, watch::Receiver<()>)>,
     theme: RwLock<Option<Arc<Theme>>>,
+    executor: Option<Arc<Background>>,
     version: AtomicUsize,
 }
 
@@ -431,6 +451,7 @@ impl LanguageRegistry {
         Self {
             language_server_download_dir: None,
             languages: Default::default(),
+            available_languages: Default::default(),
             lsp_binary_statuses_tx,
             lsp_binary_statuses_rx,
             login_shell_env_loaded: login_shell_env_loaded.shared(),
@@ -438,6 +459,7 @@ impl LanguageRegistry {
             subscription: RwLock::new(watch::channel()),
             theme: Default::default(),
             version: Default::default(),
+            executor: None,
         }
     }
 
@@ -446,6 +468,44 @@ impl LanguageRegistry {
         Self::new(Task::ready(()))
     }
 
+    pub fn set_executor(&mut self, executor: Arc<Background>) {
+        self.executor = Some(executor);
+    }
+
+    pub fn register(
+        &self,
+        path: &'static str,
+        config: LanguageConfig,
+        grammar: tree_sitter::Language,
+        lsp_adapter: Option<Box<dyn LspAdapter>>,
+        get_queries: fn(&str) -> LanguageQueries,
+    ) {
+        self.available_languages.write().push(AvailableLanguage {
+            path,
+            config,
+            grammar,
+            lsp_adapter,
+            get_queries,
+        });
+    }
+
+    pub fn language_names(&self) -> Vec<String> {
+        let mut result = self
+            .available_languages
+            .read()
+            .iter()
+            .map(|l| l.config.name.to_string())
+            .chain(
+                self.languages
+                    .read()
+                    .iter()
+                    .map(|l| l.config.name.to_string()),
+            )
+            .collect::<Vec<_>>();
+        result.sort_unstable();
+        result
+    }
+
     pub fn add(&self, language: Arc<Language>) {
         if let Some(theme) = self.theme.read().clone() {
             language.set_theme(&theme.editor.syntax);
@@ -474,58 +534,79 @@ impl LanguageRegistry {
         self.language_server_download_dir = Some(path.into());
     }
 
-    pub fn language_for_name(&self, name: &str) -> Option<Arc<Language>> {
+    pub fn language_for_name(self: &Arc<Self>, name: &str) -> Option<Arc<Language>> {
         let name = UniCase::new(name);
-        self.languages
-            .read()
-            .iter()
-            .find(|language| UniCase::new(language.name()) == name)
-            .cloned()
+        self.get_or_load_language(|config| UniCase::new(config.name.as_ref()) == name)
     }
 
-    pub fn language_for_extension(&self, extension: &str) -> Option<Arc<Language>> {
-        let extension = UniCase::new(extension);
-        self.languages
-            .read()
-            .iter()
-            .find(|language| {
-                language
-                    .config
+    pub fn language_for_name_or_extension(self: &Arc<Self>, string: &str) -> Option<Arc<Language>> {
+        let string = UniCase::new(string);
+        self.get_or_load_language(|config| {
+            UniCase::new(config.name.as_ref()) == string
+                || config
                     .path_suffixes
                     .iter()
-                    .any(|suffix| UniCase::new(suffix) == extension)
-            })
-            .cloned()
-    }
-
-    pub fn to_vec(&self) -> Vec<Arc<Language>> {
-        self.languages.read().iter().cloned().collect()
-    }
-
-    pub fn language_names(&self) -> Vec<String> {
-        self.languages
-            .read()
-            .iter()
-            .map(|language| language.name().to_string())
-            .collect()
+                    .any(|suffix| UniCase::new(suffix) == string)
+        })
     }
 
-    pub fn select_language(&self, path: impl AsRef<Path>) -> Option<Arc<Language>> {
+    pub fn language_for_path(self: &Arc<Self>, path: impl AsRef<Path>) -> Option<Arc<Language>> {
         let path = path.as_ref();
         let filename = path.file_name().and_then(|name| name.to_str());
         let extension = path.extension().and_then(|name| name.to_str());
         let path_suffixes = [extension, filename];
-        self.languages
+        self.get_or_load_language(|config| {
+            config
+                .path_suffixes
+                .iter()
+                .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
+        })
+    }
+
+    fn get_or_load_language(
+        self: &Arc<Self>,
+        callback: impl Fn(&LanguageConfig) -> bool,
+    ) -> Option<Arc<Language>> {
+        if let Some(language) = self
+            .languages
             .read()
             .iter()
-            .find(|language| {
-                language
-                    .config
-                    .path_suffixes
-                    .iter()
-                    .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
-            })
-            .cloned()
+            .find(|language| callback(&language.config))
+        {
+            return Some(language.clone());
+        }
+
+        if let Some(executor) = self.executor.clone() {
+            let mut available_languages = self.available_languages.write();
+
+            if let Some(ix) = available_languages.iter().position(|l| callback(&l.config)) {
+                let language = available_languages.remove(ix);
+                drop(available_languages);
+                let name = language.config.name.clone();
+                let this = self.clone();
+                executor
+                    .spawn(async move {
+                        let queries = (language.get_queries)(&language.path);
+                        let language = Language::new(language.config, Some(language.grammar))
+                            .with_lsp_adapter(language.lsp_adapter)
+                            .await;
+                        match language.with_queries(queries) {
+                            Ok(language) => this.add(Arc::new(language)),
+                            Err(err) => {
+                                log::error!("failed  to load language {}: {}", name, err);
+                                return;
+                            }
+                        };
+                    })
+                    .detach();
+            }
+        }
+
+        None
+    }
+
+    pub fn to_vec(&self) -> Vec<Arc<Language>> {
+        self.languages.read().iter().cloned().collect()
     }
 
     pub fn start_language_server(
@@ -729,12 +810,70 @@ impl Language {
         self.grammar.as_ref().map(|g| g.id)
     }
 
+    pub fn with_queries(mut self, queries: LanguageQueries) -> Result<Self> {
+        if let Some(query) = queries.highlights {
+            self = self
+                .with_highlights_query(query.as_ref())
+                .expect("failed to evaluate highlights query");
+        }
+        if let Some(query) = queries.brackets {
+            self = self
+                .with_brackets_query(query.as_ref())
+                .expect("failed to load brackets query");
+        }
+        if let Some(query) = queries.indents {
+            self = self
+                .with_indents_query(query.as_ref())
+                .expect("failed to load indents query");
+        }
+        if let Some(query) = queries.outline {
+            self = self
+                .with_outline_query(query.as_ref())
+                .expect("failed to load outline query");
+        }
+        if let Some(query) = queries.injections {
+            self = self
+                .with_injection_query(query.as_ref())
+                .expect("failed to load injection query");
+        }
+        if let Some(query) = queries.overrides {
+            self = self
+                .with_override_query(query.as_ref())
+                .expect("failed to load override query");
+        }
+        Ok(self)
+    }
     pub fn with_highlights_query(mut self, source: &str) -> Result<Self> {
         let grammar = self.grammar_mut();
         grammar.highlights_query = Some(Query::new(grammar.ts_language, source)?);
         Ok(self)
     }
 
+    pub fn with_outline_query(mut self, source: &str) -> Result<Self> {
+        let grammar = self.grammar_mut();
+        let query = Query::new(grammar.ts_language, source)?;
+        let mut item_capture_ix = None;
+        let mut name_capture_ix = None;
+        let mut context_capture_ix = None;
+        get_capture_indices(
+            &query,
+            &mut [
+                ("item", &mut item_capture_ix),
+                ("name", &mut name_capture_ix),
+                ("context", &mut context_capture_ix),
+            ],
+        );
+        if let Some((item_capture_ix, name_capture_ix)) = item_capture_ix.zip(name_capture_ix) {
+            grammar.outline_config = Some(OutlineConfig {
+                query,
+                item_capture_ix,
+                name_capture_ix,
+                context_capture_ix,
+            });
+        }
+        Ok(self)
+    }
+
     pub fn with_brackets_query(mut self, source: &str) -> Result<Self> {
         let grammar = self.grammar_mut();
         let query = Query::new(grammar.ts_language, source)?;
@@ -785,31 +924,6 @@ impl Language {
         Ok(self)
     }
 
-    pub fn with_outline_query(mut self, source: &str) -> Result<Self> {
-        let grammar = self.grammar_mut();
-        let query = Query::new(grammar.ts_language, source)?;
-        let mut item_capture_ix = None;
-        let mut name_capture_ix = None;
-        let mut context_capture_ix = None;
-        get_capture_indices(
-            &query,
-            &mut [
-                ("item", &mut item_capture_ix),
-                ("name", &mut name_capture_ix),
-                ("context", &mut context_capture_ix),
-            ],
-        );
-        if let Some((item_capture_ix, name_capture_ix)) = item_capture_ix.zip(name_capture_ix) {
-            grammar.outline_config = Some(OutlineConfig {
-                query,
-                item_capture_ix,
-                name_capture_ix,
-                context_capture_ix,
-            });
-        }
-        Ok(self)
-    }
-
     pub fn with_injection_query(mut self, source: &str) -> Result<Self> {
         let grammar = self.grammar_mut();
         let query = Query::new(grammar.ts_language, source)?;
@@ -882,8 +996,10 @@ impl Language {
         Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap()
     }
 
-    pub fn with_lsp_adapter(mut self, lsp_adapter: Arc<CachedLspAdapter>) -> Self {
-        self.adapter = Some(lsp_adapter);
+    pub async fn with_lsp_adapter(mut self, lsp_adapter: Option<Box<dyn LspAdapter>>) -> Self {
+        if let Some(adapter) = lsp_adapter {
+            self.adapter = Some(CachedLspAdapter::new(adapter).await);
+        }
         self
     }
 
@@ -894,7 +1010,7 @@ impl Language {
     ) -> mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
         let (servers_tx, servers_rx) = mpsc::unbounded();
         self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone()));
-        let adapter = CachedLspAdapter::new(fake_lsp_adapter).await;
+        let adapter = CachedLspAdapter::new(Box::new(fake_lsp_adapter)).await;
         self.adapter = Some(adapter);
         servers_rx
     }

crates/language/src/syntax_map.rs 🔗

@@ -381,7 +381,12 @@ impl SyntaxSnapshot {
                 cursor.next(text);
                 while let Some(layer) = cursor.item() {
                     let SyntaxLayerContent::Pending { language_name } = &layer.content else { unreachable!() };
-                    if language_for_injection(language_name, &registry).is_some() {
+                    if {
+                        let language_registry = &registry;
+                        language_registry.language_for_name_or_extension(language_name)
+                    }
+                    .is_some()
+                    {
                         resolved_injection_ranges.push(layer.range.to_offset(text));
                     }
 
@@ -1066,7 +1071,7 @@ fn get_injections(
     config: &InjectionConfig,
     text: &BufferSnapshot,
     node: Node,
-    language_registry: &LanguageRegistry,
+    language_registry: &Arc<LanguageRegistry>,
     depth: usize,
     changed_ranges: &[Range<usize>],
     combined_injection_ranges: &mut HashMap<Arc<Language>, Vec<tree_sitter::Range>>,
@@ -1078,7 +1083,8 @@ fn get_injections(
     combined_injection_ranges.clear();
     for pattern in &config.patterns {
         if let (Some(language_name), true) = (pattern.language.as_ref(), pattern.combined) {
-            if let Some(language) = language_for_injection(language_name, language_registry) {
+            if let Some(language) = language_registry.language_for_name_or_extension(language_name)
+            {
                 combined_injection_ranges.insert(language, Vec::new());
             }
         }
@@ -1123,7 +1129,10 @@ fn get_injections(
             };
 
             if let Some(language_name) = language_name {
-                let language = language_for_injection(&language_name, language_registry);
+                let language = {
+                    let language_name: &str = &language_name;
+                    language_registry.language_for_name_or_extension(language_name)
+                };
                 let range = text.anchor_before(step_range.start)..text.anchor_after(step_range.end);
                 if let Some(language) = language {
                     if combined {
@@ -1171,15 +1180,6 @@ fn get_injections(
     }
 }
 
-fn language_for_injection(
-    language_name: &str,
-    language_registry: &LanguageRegistry,
-) -> Option<Arc<Language>> {
-    language_registry
-        .language_for_name(language_name)
-        .or_else(|| language_registry.language_for_extension(language_name))
-}
-
 fn splice_included_ranges(
     mut ranges: Vec<tree_sitter::Range>,
     changed_ranges: &[Range<usize>],

crates/project/src/project.rs 🔗

@@ -1802,7 +1802,7 @@ impl Project {
     ) -> Option<()> {
         // If the buffer has a language, set it and start the language server if we haven't already.
         let full_path = buffer.read(cx).file()?.full_path(cx);
-        let new_language = self.languages.select_language(&full_path)?;
+        let new_language = self.languages.language_for_path(&full_path)?;
         buffer.update(cx, |buffer, cx| {
             if buffer.language().map_or(true, |old_language| {
                 !Arc::ptr_eq(old_language, &new_language)
@@ -2211,7 +2211,7 @@ impl Project {
             })
             .collect();
         for (worktree_id, worktree_abs_path, full_path) in language_server_lookup_info {
-            let language = self.languages.select_language(&full_path)?;
+            let language = self.languages.language_for_path(&full_path)?;
             self.restart_language_server(worktree_id, worktree_abs_path, language, cx);
         }
 
@@ -3171,7 +3171,7 @@ impl Project {
                             let signature = this.symbol_signature(&project_path);
                             let language = this
                                 .languages
-                                .select_language(&project_path.path)
+                                .language_for_path(&project_path.path)
                                 .unwrap_or(adapter_language.clone());
                             let language_server_name = adapter.name.clone();
                             Some(async move {
@@ -5947,7 +5947,7 @@ impl Project {
                 worktree_id,
                 path: PathBuf::from(serialized_symbol.path).into(),
             };
-            let language = languages.select_language(&path.path);
+            let language = languages.language_for_path(&path.path);
             Ok(Symbol {
                 language_server_name: LanguageServerName(
                     serialized_symbol.language_server_name.into(),

crates/zed/src/languages.rs 🔗

@@ -1,7 +1,5 @@
 use anyhow::Context;
-use gpui::executor::Background;
 pub use language::*;
-use lazy_static::lazy_static;
 use rust_embed::RustEmbed;
 use std::{borrow::Cow, str, sync::Arc};
 
@@ -32,32 +30,17 @@ mod typescript;
 #[exclude = "*.rs"]
 struct LanguageDir;
 
-// TODO - Remove this once the `init` function is synchronous again.
-lazy_static! {
-    pub static ref LANGUAGE_NAMES: Vec<String> = LanguageDir::iter()
-        .filter_map(|path| {
-            if path.ends_with("config.toml") {
-                let config = LanguageDir::get(&path)?;
-                let config = toml::from_slice::<LanguageConfig>(&config.data).ok()?;
-                Some(config.name.to_string())
-            } else {
-                None
-            }
-        })
-        .collect();
-}
-
-pub async fn init(languages: Arc<LanguageRegistry>, _executor: Arc<Background>) {
+pub fn init(languages: Arc<LanguageRegistry>) {
     for (name, grammar, lsp_adapter) in [
         (
             "c",
             tree_sitter_c::language(),
-            Some(CachedLspAdapter::new(c::CLspAdapter).await),
+            Some(Box::new(c::CLspAdapter) as Box<dyn LspAdapter>),
         ),
         (
             "cpp",
             tree_sitter_cpp::language(),
-            Some(CachedLspAdapter::new(c::CLspAdapter).await),
+            Some(Box::new(c::CLspAdapter)),
         ),
         (
             "css",
@@ -67,17 +50,17 @@ pub async fn init(languages: Arc<LanguageRegistry>, _executor: Arc<Background>)
         (
             "elixir",
             tree_sitter_elixir::language(),
-            Some(CachedLspAdapter::new(elixir::ElixirLspAdapter).await),
+            Some(Box::new(elixir::ElixirLspAdapter)),
         ),
         (
             "go",
             tree_sitter_go::language(),
-            Some(CachedLspAdapter::new(go::GoLspAdapter).await),
+            Some(Box::new(go::GoLspAdapter)),
         ),
         (
             "json",
             tree_sitter_json::language(),
-            Some(CachedLspAdapter::new(json::JsonLspAdapter).await),
+            Some(Box::new(json::JsonLspAdapter)),
         ),
         (
             "markdown",
@@ -87,12 +70,12 @@ pub async fn init(languages: Arc<LanguageRegistry>, _executor: Arc<Background>)
         (
             "python",
             tree_sitter_python::language(),
-            Some(CachedLspAdapter::new(python::PythonLspAdapter).await),
+            Some(Box::new(python::PythonLspAdapter)),
         ),
         (
             "rust",
             tree_sitter_rust::language(),
-            Some(CachedLspAdapter::new(rust::RustLspAdapter).await),
+            Some(Box::new(rust::RustLspAdapter)),
         ),
         (
             "toml",
@@ -102,89 +85,82 @@ pub async fn init(languages: Arc<LanguageRegistry>, _executor: Arc<Background>)
         (
             "tsx",
             tree_sitter_typescript::language_tsx(),
-            Some(CachedLspAdapter::new(typescript::TypeScriptLspAdapter).await),
+            Some(Box::new(typescript::TypeScriptLspAdapter)),
         ),
         (
             "typescript",
             tree_sitter_typescript::language_typescript(),
-            Some(CachedLspAdapter::new(typescript::TypeScriptLspAdapter).await),
+            Some(Box::new(typescript::TypeScriptLspAdapter)),
         ),
         (
             "javascript",
             tree_sitter_typescript::language_tsx(),
-            Some(CachedLspAdapter::new(typescript::TypeScriptLspAdapter).await),
+            Some(Box::new(typescript::TypeScriptLspAdapter)),
         ),
         (
             "html",
             tree_sitter_html::language(),
-            Some(CachedLspAdapter::new(html::HtmlLspAdapter).await),
+            Some(Box::new(html::HtmlLspAdapter)),
         ),
         (
             "ruby",
             tree_sitter_ruby::language(),
-            Some(CachedLspAdapter::new(ruby::RubyLanguageServer).await),
+            Some(Box::new(ruby::RubyLanguageServer)),
         ),
         (
             "erb",
             tree_sitter_embedded_template::language(),
-            Some(CachedLspAdapter::new(ruby::RubyLanguageServer).await),
+            Some(Box::new(ruby::RubyLanguageServer)),
+        ),
+        (
+            "scheme",
+            tree_sitter_scheme::language(),
+            None, //
+        ),
+        (
+            "racket",
+            tree_sitter_racket::language(),
+            None, //
         ),
-        ("scheme", tree_sitter_scheme::language(), None),
-        ("racket", tree_sitter_racket::language(), None),
     ] {
-        languages.add(language(name, grammar, lsp_adapter));
+        languages.register(name, load_config(name), grammar, lsp_adapter, load_queries);
     }
 }
 
-pub(crate) fn language(
+#[cfg(any(test, feature = "test-support"))]
+pub async fn language(
     name: &str,
     grammar: tree_sitter::Language,
-    lsp_adapter: Option<Arc<CachedLspAdapter>>,
+    lsp_adapter: Option<Box<dyn LspAdapter>>,
 ) -> Arc<Language> {
-    let config = toml::from_slice(
+    Arc::new(
+        Language::new(load_config(name), Some(grammar))
+            .with_lsp_adapter(lsp_adapter)
+            .await
+            .with_queries(load_queries(name))
+            .unwrap(),
+    )
+}
+
+fn load_config(name: &str) -> LanguageConfig {
+    toml::from_slice(
         &LanguageDir::get(&format!("{}/config.toml", name))
             .unwrap()
             .data,
     )
     .with_context(|| format!("failed to load config.toml for language {name:?}"))
-    .unwrap();
-
-    let mut language = Language::new(config, Some(grammar));
+    .unwrap()
+}
 
-    if let Some(query) = load_query(name, "/highlights") {
-        language = language
-            .with_highlights_query(query.as_ref())
-            .expect("failed to evaluate highlights query");
-    }
-    if let Some(query) = load_query(name, "/brackets") {
-        language = language
-            .with_brackets_query(query.as_ref())
-            .expect("failed to load brackets query");
-    }
-    if let Some(query) = load_query(name, "/indents") {
-        language = language
-            .with_indents_query(query.as_ref())
-            .expect("failed to load indents query");
-    }
-    if let Some(query) = load_query(name, "/outline") {
-        language = language
-            .with_outline_query(query.as_ref())
-            .expect("failed to load outline query");
-    }
-    if let Some(query) = load_query(name, "/injections") {
-        language = language
-            .with_injection_query(query.as_ref())
-            .expect("failed to load injection query");
-    }
-    if let Some(query) = load_query(name, "/overrides") {
-        language = language
-            .with_override_query(query.as_ref())
-            .expect("failed to load override query");
-    }
-    if let Some(lsp_adapter) = lsp_adapter {
-        language = language.with_lsp_adapter(lsp_adapter)
+fn load_queries(name: &str) -> LanguageQueries {
+    LanguageQueries {
+        highlights: load_query(name, "/highlights"),
+        brackets: load_query(name, "/brackets"),
+        indents: load_query(name, "/indents"),
+        outline: load_query(name, "/outline"),
+        injections: load_query(name, "/injections"),
+        overrides: load_query(name, "/overrides"),
     }
-    Arc::new(language)
 }
 
 fn load_query(name: &str, filename_prefix: &str) -> Option<Cow<'static, str>> {

crates/zed/src/languages/c.rs 🔗

@@ -248,17 +248,19 @@ impl super::LspAdapter for CLspAdapter {
 
 #[cfg(test)]
 mod tests {
-    use gpui::MutableAppContext;
+    use gpui::TestAppContext;
     use language::{AutoindentMode, Buffer};
     use settings::Settings;
 
     #[gpui::test]
-    fn test_c_autoindent(cx: &mut MutableAppContext) {
+    async fn test_c_autoindent(cx: &mut TestAppContext) {
         cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
-        let mut settings = Settings::test(cx);
-        settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
-        cx.set_global(settings);
-        let language = crate::languages::language("c", tree_sitter_c::language(), None);
+        cx.update(|cx| {
+            let mut settings = Settings::test(cx);
+            settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
+            cx.set_global(settings);
+        });
+        let language = crate::languages::language("c", tree_sitter_c::language(), None).await;
 
         cx.add_model(|cx| {
             let mut buffer = Buffer::new(0, "", cx).with_language(language, cx);

crates/zed/src/languages/go.rs 🔗

@@ -314,8 +314,9 @@ mod tests {
         let language = language(
             "go",
             tree_sitter_go::language(),
-            Some(CachedLspAdapter::new(GoLspAdapter).await),
-        );
+            Some(Box::new(GoLspAdapter)),
+        )
+        .await;
 
         let theme = SyntaxTheme::new(vec![
             ("type".into(), Color::green().into()),

crates/zed/src/languages/python.rs 🔗

@@ -165,17 +165,20 @@ impl LspAdapter for PythonLspAdapter {
 
 #[cfg(test)]
 mod tests {
-    use gpui::{ModelContext, MutableAppContext};
+    use gpui::{ModelContext, TestAppContext};
     use language::{AutoindentMode, Buffer};
     use settings::Settings;
 
     #[gpui::test]
-    fn test_python_autoindent(cx: &mut MutableAppContext) {
+    async fn test_python_autoindent(cx: &mut TestAppContext) {
         cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
-        let language = crate::languages::language("python", tree_sitter_python::language(), None);
-        let mut settings = Settings::test(cx);
-        settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
-        cx.set_global(settings);
+        let language =
+            crate::languages::language("python", tree_sitter_python::language(), None).await;
+        cx.update(|cx| {
+            let mut settings = Settings::test(cx);
+            settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
+            cx.set_global(settings);
+        });
 
         cx.add_model(|cx| {
             let mut buffer = Buffer::new(0, "", cx).with_language(language, cx);

crates/zed/src/languages/rust.rs 🔗

@@ -255,8 +255,8 @@ impl LspAdapter for RustLspAdapter {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::languages::{language, CachedLspAdapter};
-    use gpui::{color::Color, MutableAppContext};
+    use crate::languages::language;
+    use gpui::{color::Color, TestAppContext};
     use settings::Settings;
     use theme::SyntaxTheme;
 
@@ -306,8 +306,9 @@ mod tests {
         let language = language(
             "rust",
             tree_sitter_rust::language(),
-            Some(CachedLspAdapter::new(RustLspAdapter).await),
-        );
+            Some(Box::new(RustLspAdapter)),
+        )
+        .await;
         let grammar = language.grammar().unwrap();
         let theme = SyntaxTheme::new(vec![
             ("type".into(), Color::green().into()),
@@ -391,8 +392,9 @@ mod tests {
         let language = language(
             "rust",
             tree_sitter_rust::language(),
-            Some(CachedLspAdapter::new(RustLspAdapter).await),
-        );
+            Some(Box::new(RustLspAdapter)),
+        )
+        .await;
         let grammar = language.grammar().unwrap();
         let theme = SyntaxTheme::new(vec![
             ("type".into(), Color::green().into()),
@@ -431,12 +433,15 @@ mod tests {
     }
 
     #[gpui::test]
-    fn test_rust_autoindent(cx: &mut MutableAppContext) {
+    async fn test_rust_autoindent(cx: &mut TestAppContext) {
         cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
-        let language = crate::languages::language("rust", tree_sitter_rust::language(), None);
-        let mut settings = Settings::test(cx);
-        settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
-        cx.set_global(settings);
+        cx.update(|cx| {
+            let mut settings = Settings::test(cx);
+            settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
+            cx.set_global(settings);
+        });
+
+        let language = crate::languages::language("rust", tree_sitter_rust::language(), None).await;
 
         cx.add_model(|cx| {
             let mut buffer = Buffer::new(0, "", cx).with_language(language, cx);

crates/zed/src/languages/typescript.rs 🔗

@@ -154,17 +154,17 @@ impl LspAdapter for TypeScriptLspAdapter {
 
 #[cfg(test)]
 mod tests {
-
-    use gpui::MutableAppContext;
+    use gpui::TestAppContext;
     use unindent::Unindent;
 
     #[gpui::test]
-    fn test_outline(cx: &mut MutableAppContext) {
+    async fn test_outline(cx: &mut TestAppContext) {
         let language = crate::languages::language(
             "typescript",
             tree_sitter_typescript::language_typescript(),
             None,
-        );
+        )
+        .await;
 
         let text = r#"
             function a() {
@@ -183,7 +183,7 @@ mod tests {
 
         let buffer =
             cx.add_model(|cx| language::Buffer::new(0, text, cx).with_language(language, cx));
-        let outline = buffer.read(cx).snapshot().outline(None).unwrap();
+        let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None).unwrap());
         assert_eq!(
             outline
                 .items

crates/zed/src/main.rs 🔗

@@ -120,11 +120,10 @@ fn main() {
 
         let client = client::Client::new(http.clone(), cx);
         let mut languages = LanguageRegistry::new(login_shell_env_loaded);
+        languages.set_executor(cx.background().clone());
         languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
         let languages = Arc::new(languages);
-        let init_languages = cx
-            .background()
-            .spawn(languages::init(languages.clone(), cx.background().clone()));
+        languages::init(languages.clone());
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
 
         watch_keymap_file(keymap_file, cx);
@@ -151,14 +150,7 @@ fn main() {
         cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx))
             .detach();
 
-        cx.spawn({
-            let languages = languages.clone();
-            |cx| async move {
-                cx.read(|cx| languages.set_theme(cx.global::<Settings>().theme.clone()));
-                init_languages.await;
-            }
-        })
-        .detach();
+        languages.set_theme(cx.global::<Settings>().theme.clone());
         cx.observe_global::<Settings, _>({
             let languages = languages.clone();
             move |cx| languages.set_theme(cx.global::<Settings>().theme.clone())

crates/zed/src/zed.rs 🔗

@@ -306,7 +306,7 @@ pub fn initialize_workspace(
         )
         .map(|meta| meta.name)
         .collect();
-    let language_names = &languages::LANGUAGE_NAMES;
+    let language_names = app_state.languages.language_names();
 
     workspace.project().update(cx, |project, cx| {
         let action_names = cx.all_action_names().collect::<Vec<_>>();
@@ -318,7 +318,7 @@ pub fn initialize_workspace(
                 "schemas": [
                     {
                         "fileMatch": [schema_file_match(&paths::SETTINGS)],
-                        "schema": settings_file_json_schema(theme_names, language_names),
+                        "schema": settings_file_json_schema(theme_names, &language_names),
                     },
                     {
                         "fileMatch": [schema_file_match(&paths::KEYMAP)],