Cleanup logic for registering languages and grammars (#7593)

Max Brunsfeld and Marshall created

This is a refactor, follow-up to the work we've been doing on loading
WASM language extensions.

Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>

Change summary

crates/extension/src/extension_store.rs  |  24 
crates/language/src/buffer_tests.rs      |   1 
crates/language/src/language.rs          | 916 +------------------------
crates/language/src/language_registry.rs | 799 ++++++++++++++++++++++
crates/project/src/project_tests.rs      |  21 
crates/zed/src/languages.rs              |  13 
6 files changed, 873 insertions(+), 901 deletions(-)

Detailed changes

crates/extension/src/extension_store.rs 🔗

@@ -141,21 +141,27 @@ impl ExtensionStore {
     }
 
     fn manifest_updated(&mut self, manifest: Manifest, cx: &mut ModelContext<Self>) {
-        for (grammar_name, grammar) in &manifest.grammars {
-            let mut grammar_path = self.extensions_dir.clone();
-            grammar_path.extend([grammar.extension.as_ref(), grammar.path.as_path()]);
-            self.language_registry
-                .register_grammar(grammar_name.clone(), grammar_path);
-        }
+        self.language_registry
+            .register_wasm_grammars(manifest.grammars.iter().map(|(grammar_name, grammar)| {
+                let mut grammar_path = self.extensions_dir.clone();
+                grammar_path.extend([grammar.extension.as_ref(), grammar.path.as_path()]);
+                (grammar_name.clone(), grammar_path)
+            }));
+
         for (language_name, language) in &manifest.languages {
             let mut language_path = self.extensions_dir.clone();
             language_path.extend([language.extension.as_ref(), language.path.as_path()]);
-            self.language_registry.register_extension(
-                language_path.into(),
+            self.language_registry.register_language(
                 language_name.clone(),
                 language.grammar.clone(),
                 language.matcher.clone(),
-                load_plugin_queries,
+                vec![],
+                move || {
+                    let config = std::fs::read(language_path.join("config.toml"))?;
+                    let config: LanguageConfig = ::toml::from_slice(&config)?;
+                    let queries = load_plugin_queries(&language_path);
+                    Ok((config, queries))
+                },
             );
         }
         let fs = self.fs.clone();

crates/language/src/buffer_tests.rs 🔗

@@ -5,6 +5,7 @@ use crate::language_settings::{
 use crate::Buffer;
 use clock::ReplicaId;
 use collections::BTreeMap;
+use futures::FutureExt as _;
 use gpui::{AppContext, Model};
 use gpui::{Context, TestAppContext};
 use indoc::indoc;

crates/language/src/language.rs 🔗

@@ -9,6 +9,7 @@
 mod buffer;
 mod diagnostic_set;
 mod highlight_map;
+mod language_registry;
 pub mod language_settings;
 mod outline;
 pub mod proto;
@@ -20,30 +21,22 @@ pub mod markdown;
 
 use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
-use collections::{hash_map, HashMap, HashSet};
-use futures::{
-    channel::{mpsc, oneshot},
-    future::Shared,
-    FutureExt, TryFutureExt as _,
-};
-use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
+use collections::{HashMap, HashSet};
+use gpui::{AppContext, AsyncAppContext, Task};
 pub use highlight_map::HighlightMap;
 use lazy_static::lazy_static;
 use lsp::{CodeActionKind, LanguageServerBinary};
-use parking_lot::{Mutex, RwLock};
-use postage::watch;
+use parking_lot::Mutex;
 use regex::Regex;
 use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
 use serde_json::Value;
 use std::{
     any::Any,
-    borrow::Cow,
     cell::RefCell,
-    ffi::OsStr,
     fmt::Debug,
     hash::Hash,
     mem,
-    ops::{Not, Range},
+    ops::Range,
     path::{Path, PathBuf},
     str,
     sync::{
@@ -52,15 +45,17 @@ use std::{
     },
 };
 use syntax_map::SyntaxSnapshot;
-use theme::{SyntaxTheme, Theme};
+use theme::SyntaxTheme;
 use tree_sitter::{self, wasmtime, Query, WasmStore};
-use unicase::UniCase;
-use util::{http::HttpClient, paths::PathExt};
-use util::{post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
+use util::http::HttpClient;
 
 pub use buffer::Operation;
 pub use buffer::*;
 pub use diagnostic_set::DiagnosticEntry;
+pub use language_registry::{
+    LanguageQueries, LanguageRegistry, LanguageServerBinaryStatus, PendingLanguageServer,
+    QUERY_FILENAME_PREFIXES,
+};
 pub use lsp::LanguageServerId;
 pub use outline::{Outline, OutlineItem};
 pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer};
@@ -74,27 +69,6 @@ pub fn init(cx: &mut AppContext) {
     language_settings::init(cx);
 }
 
-#[derive(Clone, Default)]
-struct LspBinaryStatusSender {
-    txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(Arc<Language>, LanguageServerBinaryStatus)>>>>,
-}
-
-impl LspBinaryStatusSender {
-    fn subscribe(&self) -> mpsc::UnboundedReceiver<(Arc<Language>, LanguageServerBinaryStatus)> {
-        let (tx, rx) = mpsc::unbounded();
-        self.txs.lock().push(tx);
-        rx
-    }
-
-    fn send(&self, language: Arc<Language>, status: LanguageServerBinaryStatus) {
-        let mut txs = self.txs.lock();
-        txs.retain(|tx| {
-            tx.unbounded_send((language.clone(), status.clone()))
-                .is_ok()
-        });
-    }
-}
-
 thread_local! {
     static PARSER: RefCell<Parser> = {
         let mut parser = Parser::new();
@@ -104,10 +78,11 @@ thread_local! {
 }
 
 lazy_static! {
-    pub(crate) static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default();
+    static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default();
+
+    static ref WASM_ENGINE: wasmtime::Engine = wasmtime::Engine::default();
+
     /// A shared grammar for plain text, exposed for reuse by downstream crates.
-    #[doc(hidden)]
-    pub static ref WASM_ENGINE: wasmtime::Engine = wasmtime::Engine::default();
     pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
         LanguageConfig {
             name: "Plain Text".into(),
@@ -457,33 +432,6 @@ pub struct LanguageMatcher {
     pub first_line_pattern: Option<Regex>,
 }
 
-pub const QUERY_FILENAME_PREFIXES: &[(
-    &str,
-    fn(&mut LanguageQueries) -> &mut Option<Cow<'static, str>>,
-)] = &[
-    ("highlights", |q| &mut q.highlights),
-    ("brackets", |q| &mut q.brackets),
-    ("outline", |q| &mut q.outline),
-    ("indents", |q| &mut q.indents),
-    ("embedding", |q| &mut q.embedding),
-    ("injections", |q| &mut q.injections),
-    ("overrides", |q| &mut q.overrides),
-    ("redactions", |q| &mut q.redactions),
-];
-
-/// Tree-sitter language queries for a given language.
-#[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 embedding: Option<Cow<'static, str>>,
-    pub injections: Option<Cow<'static, str>>,
-    pub overrides: Option<Cow<'static, str>>,
-    pub redactions: Option<Cow<'static, str>>,
-}
-
 /// Represents a language for the given range. Some languages (e.g. HTML)
 /// interleave several languages together, thus a single buffer might actually contain
 /// several nested scopes.
@@ -650,7 +598,7 @@ pub struct Language {
 
     #[cfg(any(test, feature = "test-support"))]
     fake_adapter: Option<(
-        mpsc::UnboundedSender<lsp::FakeLanguageServer>,
+        futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>,
         Arc<FakeLspAdapter>,
     )>,
 }
@@ -725,775 +673,6 @@ struct BracketConfig {
     close_capture_ix: u32,
 }
 
-#[derive(Clone)]
-pub enum LanguageServerBinaryStatus {
-    CheckingForUpdate,
-    Downloading,
-    Downloaded,
-    Cached,
-    Failed { error: String },
-}
-
-type AvailableLanguageId = usize;
-
-#[derive(Clone)]
-struct AvailableLanguage {
-    id: AvailableLanguageId,
-    name: Arc<str>,
-    grammar: Option<Arc<str>>,
-    source: AvailableLanguageSource,
-    lsp_adapters: Vec<Arc<dyn LspAdapter>>,
-    loaded: bool,
-}
-
-enum AvailableGrammar {
-    Native(tree_sitter::Language),
-    Loaded(PathBuf, tree_sitter::Language),
-    Loading(PathBuf, Vec<oneshot::Sender<Result<tree_sitter::Language>>>),
-    Unloaded(PathBuf),
-}
-
-#[derive(Clone)]
-enum AvailableLanguageSource {
-    BuiltIn {
-        asset_dir: &'static str,
-        get_queries: fn(&str) -> LanguageQueries,
-        config: LanguageConfig,
-    },
-    Extension {
-        path: Arc<Path>,
-        get_queries: fn(&Path) -> LanguageQueries,
-        matcher: LanguageMatcher,
-    },
-}
-
-pub struct LanguageRegistry {
-    state: RwLock<LanguageRegistryState>,
-    language_server_download_dir: Option<Arc<Path>>,
-    login_shell_env_loaded: Shared<Task<()>>,
-    #[allow(clippy::type_complexity)]
-    lsp_binary_paths: Mutex<
-        HashMap<LanguageServerName, Shared<Task<Result<LanguageServerBinary, Arc<anyhow::Error>>>>>,
-    >,
-    executor: Option<BackgroundExecutor>,
-    lsp_binary_status_tx: LspBinaryStatusSender,
-}
-
-struct LanguageRegistryState {
-    next_language_server_id: usize,
-    languages: Vec<Arc<Language>>,
-    available_languages: Vec<AvailableLanguage>,
-    grammars: HashMap<Arc<str>, AvailableGrammar>,
-    next_available_language_id: AvailableLanguageId,
-    loading_languages: HashMap<AvailableLanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
-    subscription: (watch::Sender<()>, watch::Receiver<()>),
-    theme: Option<Arc<Theme>>,
-    version: usize,
-    reload_count: usize,
-}
-
-pub struct PendingLanguageServer {
-    pub server_id: LanguageServerId,
-    pub task: Task<Result<lsp::LanguageServer>>,
-    pub container_dir: Option<Arc<Path>>,
-}
-
-impl LanguageRegistry {
-    pub fn new(login_shell_env_loaded: Task<()>) -> Self {
-        Self {
-            state: RwLock::new(LanguageRegistryState {
-                next_language_server_id: 0,
-                languages: vec![PLAIN_TEXT.clone()],
-                available_languages: Default::default(),
-                grammars: Default::default(),
-                next_available_language_id: 0,
-                loading_languages: Default::default(),
-                subscription: watch::channel(),
-                theme: Default::default(),
-                version: 0,
-                reload_count: 0,
-            }),
-            language_server_download_dir: None,
-            login_shell_env_loaded: login_shell_env_loaded.shared(),
-            lsp_binary_paths: Default::default(),
-            executor: None,
-            lsp_binary_status_tx: Default::default(),
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn test() -> Self {
-        Self::new(Task::ready(()))
-    }
-
-    pub fn set_executor(&mut self, executor: BackgroundExecutor) {
-        self.executor = Some(executor);
-    }
-
-    /// Clear out all of the loaded languages and reload them from scratch.
-    pub fn reload(&self) {
-        self.state.write().reload();
-    }
-
-    /// Clear out the given languages and reload them from scratch.
-    pub fn reload_languages(&self, languages: &[Arc<str>], grammars: &[Arc<str>]) {
-        self.state.write().reload_languages(languages, grammars);
-    }
-
-    pub fn register(
-        &self,
-        asset_dir: &'static str,
-        config: LanguageConfig,
-        lsp_adapters: Vec<Arc<dyn LspAdapter>>,
-        get_queries: fn(&str) -> LanguageQueries,
-    ) {
-        let state = &mut *self.state.write();
-        state.available_languages.push(AvailableLanguage {
-            id: post_inc(&mut state.next_available_language_id),
-            name: config.name.clone(),
-            grammar: config.grammar.clone(),
-            source: AvailableLanguageSource::BuiltIn {
-                config,
-                get_queries,
-                asset_dir,
-            },
-            lsp_adapters,
-            loaded: false,
-        });
-    }
-
-    pub fn register_extension(
-        &self,
-        path: Arc<Path>,
-        name: Arc<str>,
-        grammar_name: Option<Arc<str>>,
-        matcher: LanguageMatcher,
-        get_queries: fn(&Path) -> LanguageQueries,
-    ) {
-        let state = &mut *self.state.write();
-        let source = AvailableLanguageSource::Extension {
-            path,
-            get_queries,
-            matcher,
-        };
-        for existing_language in &mut state.available_languages {
-            if existing_language.name == name
-                && matches!(
-                    existing_language.source,
-                    AvailableLanguageSource::Extension { .. }
-                )
-            {
-                existing_language.source = source;
-                return;
-            }
-        }
-        state.available_languages.push(AvailableLanguage {
-            id: post_inc(&mut state.next_available_language_id),
-            grammar: grammar_name,
-            name,
-            source,
-            lsp_adapters: Vec::new(),
-            loaded: false,
-        });
-    }
-
-    pub fn add_grammars(
-        &self,
-        grammars: impl IntoIterator<Item = (impl Into<Arc<str>>, tree_sitter::Language)>,
-    ) {
-        self.state.write().grammars.extend(
-            grammars
-                .into_iter()
-                .map(|(name, grammar)| (name.into(), AvailableGrammar::Native(grammar))),
-        );
-    }
-
-    pub fn register_grammar(&self, name: Arc<str>, path: PathBuf) {
-        self.state
-            .write()
-            .grammars
-            .insert(name, AvailableGrammar::Unloaded(path));
-    }
-
-    pub fn language_names(&self) -> Vec<String> {
-        let state = self.state.read();
-        let mut result = state
-            .available_languages
-            .iter()
-            .filter_map(|l| l.loaded.not().then_some(l.name.to_string()))
-            .chain(state.languages.iter().map(|l| l.config.name.to_string()))
-            .collect::<Vec<_>>();
-        result.sort_unstable_by_key(|language_name| language_name.to_lowercase());
-        result
-    }
-
-    pub fn add(&self, language: Arc<Language>) {
-        self.state.write().add(language);
-    }
-
-    pub fn subscribe(&self) -> watch::Receiver<()> {
-        self.state.read().subscription.1.clone()
-    }
-
-    /// The number of times that the registry has been changed,
-    /// by adding languages or reloading.
-    pub fn version(&self) -> usize {
-        self.state.read().version
-    }
-
-    /// The number of times that the registry has been reloaded.
-    pub fn reload_count(&self) -> usize {
-        self.state.read().reload_count
-    }
-
-    pub fn set_theme(&self, theme: Arc<Theme>) {
-        let mut state = self.state.write();
-        state.theme = Some(theme.clone());
-        for language in &state.languages {
-            language.set_theme(theme.syntax());
-        }
-    }
-
-    pub fn set_language_server_download_dir(&mut self, path: impl Into<Arc<Path>>) {
-        self.language_server_download_dir = Some(path.into());
-    }
-
-    pub fn language_for_name(
-        self: &Arc<Self>,
-        name: &str,
-    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
-        let name = UniCase::new(name);
-        self.get_or_load_language(|language_name, _| UniCase::new(language_name) == name)
-    }
-
-    pub fn language_for_name_or_extension(
-        self: &Arc<Self>,
-        string: &str,
-    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
-        let string = UniCase::new(string);
-        self.get_or_load_language(|name, config| {
-            UniCase::new(name) == string
-                || config
-                    .path_suffixes
-                    .iter()
-                    .any(|suffix| UniCase::new(suffix) == string)
-        })
-    }
-
-    pub fn language_for_file(
-        self: &Arc<Self>,
-        path: impl AsRef<Path>,
-        content: Option<&Rope>,
-    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
-        let path = path.as_ref();
-        let filename = path.file_name().and_then(|name| name.to_str());
-        let extension = path.extension_or_hidden_file_name();
-        let path_suffixes = [extension, filename];
-        self.get_or_load_language(|_, config| {
-            let path_matches = config
-                .path_suffixes
-                .iter()
-                .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())));
-            let content_matches = content.zip(config.first_line_pattern.as_ref()).map_or(
-                false,
-                |(content, pattern)| {
-                    let end = content.clip_point(Point::new(0, 256), Bias::Left);
-                    let end = content.point_to_offset(end);
-                    let text = content.chunks_in_range(0..end).collect::<String>();
-                    pattern.is_match(&text)
-                },
-            );
-            path_matches || content_matches
-        })
-    }
-
-    fn get_or_load_language(
-        self: &Arc<Self>,
-        callback: impl Fn(&str, &LanguageMatcher) -> bool,
-    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
-        let (tx, rx) = oneshot::channel();
-
-        let mut state = self.state.write();
-        if let Some(language) = state
-            .languages
-            .iter()
-            .find(|language| callback(language.config.name.as_ref(), &language.config.matcher))
-        {
-            let _ = tx.send(Ok(language.clone()));
-        } else if let Some(executor) = self.executor.clone() {
-            if let Some(language) = state
-                .available_languages
-                .iter()
-                .rfind(|l| {
-                    !l.loaded
-                        && match &l.source {
-                            AvailableLanguageSource::BuiltIn { config, .. } => {
-                                callback(l.name.as_ref(), &config.matcher)
-                            }
-                            AvailableLanguageSource::Extension { matcher, .. } => {
-                                callback(l.name.as_ref(), &matcher)
-                            }
-                        }
-                })
-                .cloned()
-            {
-                match state.loading_languages.entry(language.id) {
-                    hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(tx),
-                    hash_map::Entry::Vacant(entry) => {
-                        let this = self.clone();
-                        executor
-                            .spawn(async move {
-                                let id = language.id;
-                                let name = language.name.clone();
-                                let language = async {
-                                    let (config, queries) = match language.source {
-                                        AvailableLanguageSource::BuiltIn {
-                                            asset_dir,
-                                            get_queries,
-                                            config,
-                                        } => (config, (get_queries)(asset_dir)),
-                                        AvailableLanguageSource::Extension {
-                                            path,
-                                            get_queries,
-                                            ..
-                                        } => {
-                                            let config = std::fs::read(path.join("config.toml"));
-                                            let config: LanguageConfig =
-                                                ::toml::from_slice(&config?)?;
-                                            (config, get_queries(path.as_ref()))
-                                        }
-                                    };
-
-                                    let grammar = if let Some(grammar) = config.grammar.clone() {
-                                        Some(this.get_or_load_grammar(grammar).await?)
-                                    } else {
-                                        None
-                                    };
-
-                                    Language::new(config, grammar)
-                                        .with_lsp_adapters(language.lsp_adapters)
-                                        .await
-                                        .with_queries(queries)
-                                }
-                                .await;
-
-                                match language {
-                                    Ok(language) => {
-                                        let language = Arc::new(language);
-                                        let mut state = this.state.write();
-
-                                        state.add(language.clone());
-                                        state.mark_language_loaded(id);
-                                        if let Some(mut txs) = state.loading_languages.remove(&id) {
-                                            for tx in txs.drain(..) {
-                                                let _ = tx.send(Ok(language.clone()));
-                                            }
-                                        }
-                                    }
-                                    Err(e) => {
-                                        log::error!("failed to load language {name}:\n{:?}", e);
-                                        let mut state = this.state.write();
-                                        state.mark_language_loaded(id);
-                                        if let Some(mut txs) = state.loading_languages.remove(&id) {
-                                            for tx in txs.drain(..) {
-                                                let _ = tx.send(Err(anyhow!(
-                                                    "failed to load language {}: {}",
-                                                    name,
-                                                    e
-                                                )));
-                                            }
-                                        }
-                                    }
-                                };
-                            })
-                            .detach();
-                        entry.insert(vec![tx]);
-                    }
-                }
-            } else {
-                let _ = tx.send(Err(anyhow!("language not found")));
-            }
-        } else {
-            let _ = tx.send(Err(anyhow!("executor does not exist")));
-        }
-
-        rx.unwrap()
-    }
-
-    fn get_or_load_grammar(
-        self: &Arc<Self>,
-        name: Arc<str>,
-    ) -> UnwrapFuture<oneshot::Receiver<Result<tree_sitter::Language>>> {
-        let (tx, rx) = oneshot::channel();
-        let mut state = self.state.write();
-
-        if let Some(grammar) = state.grammars.get_mut(name.as_ref()) {
-            match grammar {
-                AvailableGrammar::Native(grammar) | AvailableGrammar::Loaded(_, grammar) => {
-                    tx.send(Ok(grammar.clone())).ok();
-                }
-                AvailableGrammar::Loading(_, txs) => {
-                    txs.push(tx);
-                }
-                AvailableGrammar::Unloaded(wasm_path) => {
-                    if let Some(executor) = &self.executor {
-                        let this = self.clone();
-                        executor
-                            .spawn({
-                                let wasm_path = wasm_path.clone();
-                                async move {
-                                    let wasm_bytes = std::fs::read(&wasm_path)?;
-                                    let grammar_name = wasm_path
-                                        .file_stem()
-                                        .and_then(OsStr::to_str)
-                                        .ok_or_else(|| anyhow!("invalid grammar filename"))?;
-                                    let grammar = PARSER.with(|parser| {
-                                        let mut parser = parser.borrow_mut();
-                                        let mut store = parser.take_wasm_store().unwrap();
-                                        let grammar =
-                                            store.load_language(&grammar_name, &wasm_bytes);
-                                        parser.set_wasm_store(store).unwrap();
-                                        grammar
-                                    })?;
-
-                                    if let Some(AvailableGrammar::Loading(_, txs)) =
-                                        this.state.write().grammars.insert(
-                                            name,
-                                            AvailableGrammar::Loaded(wasm_path, grammar.clone()),
-                                        )
-                                    {
-                                        for tx in txs {
-                                            tx.send(Ok(grammar.clone())).ok();
-                                        }
-                                    }
-
-                                    anyhow::Ok(())
-                                }
-                            })
-                            .detach();
-                        *grammar = AvailableGrammar::Loading(wasm_path.clone(), vec![tx]);
-                    }
-                }
-            }
-        } else {
-            tx.send(Err(anyhow!("no such grammar {}", name))).ok();
-        }
-
-        rx.unwrap()
-    }
-
-    pub fn to_vec(&self) -> Vec<Arc<Language>> {
-        self.state.read().languages.iter().cloned().collect()
-    }
-
-    pub fn create_pending_language_server(
-        self: &Arc<Self>,
-        stderr_capture: Arc<Mutex<Option<String>>>,
-        language: Arc<Language>,
-        adapter: Arc<CachedLspAdapter>,
-        root_path: Arc<Path>,
-        delegate: Arc<dyn LspAdapterDelegate>,
-        cx: &mut AppContext,
-    ) -> Option<PendingLanguageServer> {
-        let server_id = self.state.write().next_language_server_id();
-        log::info!(
-            "starting language server {:?}, path: {root_path:?}, id: {server_id}",
-            adapter.name.0
-        );
-
-        #[cfg(any(test, feature = "test-support"))]
-        if language.fake_adapter.is_some() {
-            let task = cx.spawn(|cx| async move {
-                let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
-                let (server, mut fake_server) = lsp::FakeLanguageServer::new(
-                    fake_adapter.name.to_string(),
-                    fake_adapter.capabilities.clone(),
-                    cx.clone(),
-                );
-
-                if let Some(initializer) = &fake_adapter.initializer {
-                    initializer(&mut fake_server);
-                }
-
-                let servers_tx = servers_tx.clone();
-                cx.background_executor()
-                    .spawn(async move {
-                        if fake_server
-                            .try_receive_notification::<lsp::notification::Initialized>()
-                            .await
-                            .is_some()
-                        {
-                            servers_tx.unbounded_send(fake_server).ok();
-                        }
-                    })
-                    .detach();
-
-                Ok(server)
-            });
-
-            return Some(PendingLanguageServer {
-                server_id,
-                task,
-                container_dir: None,
-            });
-        }
-
-        let download_dir = self
-            .language_server_download_dir
-            .clone()
-            .ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
-            .log_err()?;
-        let this = self.clone();
-        let language = language.clone();
-        let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref()));
-        let root_path = root_path.clone();
-        let adapter = adapter.clone();
-        let login_shell_env_loaded = self.login_shell_env_loaded.clone();
-        let lsp_binary_statuses = self.lsp_binary_status_tx.clone();
-
-        let task = {
-            let container_dir = container_dir.clone();
-            cx.spawn(move |mut cx| async move {
-                login_shell_env_loaded.await;
-
-                let entry = this
-                    .lsp_binary_paths
-                    .lock()
-                    .entry(adapter.name.clone())
-                    .or_insert_with(|| {
-                        let adapter = adapter.clone();
-                        let language = language.clone();
-                        let delegate = delegate.clone();
-                        cx.spawn(|cx| {
-                            get_binary(
-                                adapter,
-                                language,
-                                delegate,
-                                container_dir,
-                                lsp_binary_statuses,
-                                cx,
-                            )
-                            .map_err(Arc::new)
-                        })
-                        .shared()
-                    })
-                    .clone();
-
-                let binary = match entry.await {
-                    Ok(binary) => binary,
-                    Err(err) => anyhow::bail!("{err}"),
-                };
-
-                if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
-                    task.await?;
-                }
-
-                lsp::LanguageServer::new(
-                    stderr_capture,
-                    server_id,
-                    binary,
-                    &root_path,
-                    adapter.code_action_kinds(),
-                    cx,
-                )
-            })
-        };
-
-        Some(PendingLanguageServer {
-            server_id,
-            task,
-            container_dir: Some(container_dir),
-        })
-    }
-
-    pub fn language_server_binary_statuses(
-        &self,
-    ) -> mpsc::UnboundedReceiver<(Arc<Language>, LanguageServerBinaryStatus)> {
-        self.lsp_binary_status_tx.subscribe()
-    }
-
-    pub fn delete_server_container(
-        &self,
-        adapter: Arc<CachedLspAdapter>,
-        cx: &mut AppContext,
-    ) -> Task<()> {
-        log::info!("deleting server container");
-
-        let mut lock = self.lsp_binary_paths.lock();
-        lock.remove(&adapter.name);
-
-        let download_dir = self
-            .language_server_download_dir
-            .clone()
-            .expect("language server download directory has not been assigned before deleting server container");
-
-        cx.spawn(|_| async move {
-            let container_dir = download_dir.join(adapter.name.0.as_ref());
-            smol::fs::remove_dir_all(container_dir)
-                .await
-                .context("server container removal")
-                .log_err();
-        })
-    }
-
-    pub fn next_language_server_id(&self) -> LanguageServerId {
-        self.state.write().next_language_server_id()
-    }
-}
-
-impl LanguageRegistryState {
-    fn next_language_server_id(&mut self) -> LanguageServerId {
-        LanguageServerId(post_inc(&mut self.next_language_server_id))
-    }
-
-    fn add(&mut self, language: Arc<Language>) {
-        if let Some(theme) = self.theme.as_ref() {
-            language.set_theme(theme.syntax());
-        }
-        self.languages.push(language);
-        self.version += 1;
-        *self.subscription.0.borrow_mut() = ();
-    }
-
-    fn reload(&mut self) {
-        self.languages.clear();
-        self.version += 1;
-        self.reload_count += 1;
-        for language in &mut self.available_languages {
-            language.loaded = false;
-        }
-        *self.subscription.0.borrow_mut() = ();
-    }
-
-    fn reload_languages(
-        &mut self,
-        languages_to_reload: &[Arc<str>],
-        grammars_to_reload: &[Arc<str>],
-    ) {
-        for (name, grammar) in self.grammars.iter_mut() {
-            if grammars_to_reload.contains(name) {
-                if let AvailableGrammar::Loaded(path, _) = grammar {
-                    *grammar = AvailableGrammar::Unloaded(path.clone());
-                }
-            }
-        }
-
-        self.languages.retain(|language| {
-            let should_reload = languages_to_reload.contains(&language.config.name)
-                || language
-                    .config
-                    .grammar
-                    .as_ref()
-                    .map_or(false, |grammar| grammars_to_reload.contains(&grammar));
-            !should_reload
-        });
-
-        for language in &mut self.available_languages {
-            if languages_to_reload.contains(&language.name)
-                || language
-                    .grammar
-                    .as_ref()
-                    .map_or(false, |grammar| grammars_to_reload.contains(grammar))
-            {
-                language.loaded = false;
-            }
-        }
-
-        self.version += 1;
-        self.reload_count += 1;
-        *self.subscription.0.borrow_mut() = ();
-    }
-
-    /// Mark the given language as having been loaded, so that the
-    /// language registry won't try to load it again.
-    fn mark_language_loaded(&mut self, id: AvailableLanguageId) {
-        for language in &mut self.available_languages {
-            if language.id == id {
-                language.loaded = true;
-                break;
-            }
-        }
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl Default for LanguageRegistry {
-    fn default() -> Self {
-        Self::test()
-    }
-}
-
-async fn get_binary(
-    adapter: Arc<CachedLspAdapter>,
-    language: Arc<Language>,
-    delegate: Arc<dyn LspAdapterDelegate>,
-    container_dir: Arc<Path>,
-    statuses: LspBinaryStatusSender,
-    mut cx: AsyncAppContext,
-) -> Result<LanguageServerBinary> {
-    if !container_dir.exists() {
-        smol::fs::create_dir_all(&container_dir)
-            .await
-            .context("failed to create container directory")?;
-    }
-
-    if let Some(task) = adapter.will_fetch_server(&delegate, &mut cx) {
-        task.await?;
-    }
-
-    let binary = fetch_latest_binary(
-        adapter.clone(),
-        language.clone(),
-        delegate.as_ref(),
-        &container_dir,
-        statuses.clone(),
-    )
-    .await;
-
-    if let Err(error) = binary.as_ref() {
-        if let Some(binary) = adapter
-            .cached_server_binary(container_dir.to_path_buf(), delegate.as_ref())
-            .await
-        {
-            statuses.send(language.clone(), LanguageServerBinaryStatus::Cached);
-            return Ok(binary);
-        } else {
-            statuses.send(
-                language.clone(),
-                LanguageServerBinaryStatus::Failed {
-                    error: format!("{:?}", error),
-                },
-            );
-        }
-    }
-
-    binary
-}
-
-async fn fetch_latest_binary(
-    adapter: Arc<CachedLspAdapter>,
-    language: Arc<Language>,
-    delegate: &dyn LspAdapterDelegate,
-    container_dir: &Path,
-    lsp_binary_statuses_tx: LspBinaryStatusSender,
-) -> Result<LanguageServerBinary> {
-    let container_dir: Arc<Path> = container_dir.into();
-    lsp_binary_statuses_tx.send(
-        language.clone(),
-        LanguageServerBinaryStatus::CheckingForUpdate,
-    );
-
-    let version_info = adapter.fetch_latest_server_version(delegate).await?;
-    lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloading);
-
-    let binary = adapter
-        .fetch_server_binary(version_info, container_dir.to_path_buf(), delegate)
-        .await?;
-    lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloaded);
-
-    Ok(binary)
-}
-
 impl Language {
     pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> Self {
         Self {
@@ -1831,8 +1010,8 @@ impl Language {
     pub async fn set_fake_lsp_adapter(
         &mut self,
         fake_lsp_adapter: Arc<FakeLspAdapter>,
-    ) -> mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
-        let (servers_tx, servers_rx) = mpsc::unbounded();
+    ) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
+        let (servers_tx, servers_rx) = futures::channel::mpsc::unbounded();
         self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone()));
         let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await;
         self.adapters = vec![adapter];
@@ -2255,19 +1434,14 @@ mod tests {
 
         languages.set_executor(cx.executor());
         let languages = Arc::new(languages);
-        languages.register(
-            "/javascript",
-            LanguageConfig {
-                name: "JavaScript".into(),
-                matcher: LanguageMatcher {
-                    path_suffixes: vec!["js".into()],
-                    first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
-                },
-                ..Default::default()
+        languages.register_test_language(LanguageConfig {
+            name: "JavaScript".into(),
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["js".into()],
+                first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
             },
-            vec![],
-            |_| Default::default(),
-        );
+            ..Default::default()
+        });
 
         languages
             .language_for_file("the/script", None)
@@ -2293,38 +1467,28 @@ mod tests {
         let mut languages = LanguageRegistry::test();
         languages.set_executor(cx.executor());
         let languages = Arc::new(languages);
-        languages.add_grammars([
+        languages.register_native_grammars([
             ("json", tree_sitter_json::language()),
             ("rust", tree_sitter_rust::language()),
         ]);
-        languages.register(
-            "/JSON",
-            LanguageConfig {
-                name: "JSON".into(),
-                grammar: Some("json".into()),
-                matcher: LanguageMatcher {
-                    path_suffixes: vec!["json".into()],
-                    ..Default::default()
-                },
+        languages.register_test_language(LanguageConfig {
+            name: "JSON".into(),
+            grammar: Some("json".into()),
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["json".into()],
                 ..Default::default()
             },
-            vec![],
-            |_| Default::default(),
-        );
-        languages.register(
-            "/rust",
-            LanguageConfig {
-                name: "Rust".into(),
-                grammar: Some("rust".into()),
-                matcher: LanguageMatcher {
-                    path_suffixes: vec!["rs".into()],
-                    ..Default::default()
-                },
+            ..Default::default()
+        });
+        languages.register_test_language(LanguageConfig {
+            name: "Rust".into(),
+            grammar: Some("rust".into()),
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".into()],
                 ..Default::default()
             },
-            vec![],
-            |_| Default::default(),
-        );
+            ..Default::default()
+        });
         assert_eq!(
             languages.language_names(),
             &[

crates/language/src/language_registry.rs 🔗

@@ -0,0 +1,799 @@
+use crate::{
+    CachedLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageServerName, LspAdapter,
+    LspAdapterDelegate, PARSER, PLAIN_TEXT,
+};
+use anyhow::{anyhow, Context as _, Result};
+use collections::{hash_map, HashMap};
+use futures::{
+    channel::{mpsc, oneshot},
+    future::Shared,
+    FutureExt as _, TryFutureExt as _,
+};
+use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
+use lsp::{LanguageServerBinary, LanguageServerId};
+use parking_lot::{Mutex, RwLock};
+use postage::watch;
+use std::{
+    borrow::Cow,
+    ffi::OsStr,
+    ops::Not,
+    path::{Path, PathBuf},
+    sync::Arc,
+};
+use sum_tree::Bias;
+use text::{Point, Rope};
+use theme::Theme;
+use unicase::UniCase;
+use util::{paths::PathExt, post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
+
+pub struct LanguageRegistry {
+    state: RwLock<LanguageRegistryState>,
+    language_server_download_dir: Option<Arc<Path>>,
+    login_shell_env_loaded: Shared<Task<()>>,
+    #[allow(clippy::type_complexity)]
+    lsp_binary_paths: Mutex<
+        HashMap<LanguageServerName, Shared<Task<Result<LanguageServerBinary, Arc<anyhow::Error>>>>>,
+    >,
+    executor: Option<BackgroundExecutor>,
+    lsp_binary_status_tx: LspBinaryStatusSender,
+}
+
+struct LanguageRegistryState {
+    next_language_server_id: usize,
+    languages: Vec<Arc<Language>>,
+    available_languages: Vec<AvailableLanguage>,
+    grammars: HashMap<Arc<str>, AvailableGrammar>,
+    next_available_language_id: AvailableLanguageId,
+    loading_languages: HashMap<AvailableLanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
+    subscription: (watch::Sender<()>, watch::Receiver<()>),
+    theme: Option<Arc<Theme>>,
+    version: usize,
+    reload_count: usize,
+}
+
+#[derive(Clone)]
+pub enum LanguageServerBinaryStatus {
+    CheckingForUpdate,
+    Downloading,
+    Downloaded,
+    Cached,
+    Failed { error: String },
+}
+
+pub struct PendingLanguageServer {
+    pub server_id: LanguageServerId,
+    pub task: Task<Result<lsp::LanguageServer>>,
+    pub container_dir: Option<Arc<Path>>,
+}
+
+#[derive(Clone)]
+struct AvailableLanguage {
+    id: AvailableLanguageId,
+    name: Arc<str>,
+    grammar: Option<Arc<str>>,
+    matcher: LanguageMatcher,
+    load: Arc<dyn Fn() -> Result<(LanguageConfig, LanguageQueries)> + 'static + Send + Sync>,
+    lsp_adapters: Vec<Arc<dyn LspAdapter>>,
+    loaded: bool,
+}
+
+type AvailableLanguageId = usize;
+
+enum AvailableGrammar {
+    Native(tree_sitter::Language),
+    Loaded(PathBuf, tree_sitter::Language),
+    Loading(PathBuf, Vec<oneshot::Sender<Result<tree_sitter::Language>>>),
+    Unloaded(PathBuf),
+}
+
+pub const QUERY_FILENAME_PREFIXES: &[(
+    &str,
+    fn(&mut LanguageQueries) -> &mut Option<Cow<'static, str>>,
+)] = &[
+    ("highlights", |q| &mut q.highlights),
+    ("brackets", |q| &mut q.brackets),
+    ("outline", |q| &mut q.outline),
+    ("indents", |q| &mut q.indents),
+    ("embedding", |q| &mut q.embedding),
+    ("injections", |q| &mut q.injections),
+    ("overrides", |q| &mut q.overrides),
+    ("redactions", |q| &mut q.redactions),
+];
+
+/// Tree-sitter language queries for a given language.
+#[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 embedding: Option<Cow<'static, str>>,
+    pub injections: Option<Cow<'static, str>>,
+    pub overrides: Option<Cow<'static, str>>,
+    pub redactions: Option<Cow<'static, str>>,
+}
+
+#[derive(Clone, Default)]
+struct LspBinaryStatusSender {
+    txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(Arc<Language>, LanguageServerBinaryStatus)>>>>,
+}
+
+impl LanguageRegistry {
+    pub fn new(login_shell_env_loaded: Task<()>) -> Self {
+        Self {
+            state: RwLock::new(LanguageRegistryState {
+                next_language_server_id: 0,
+                languages: vec![PLAIN_TEXT.clone()],
+                available_languages: Default::default(),
+                grammars: Default::default(),
+                next_available_language_id: 0,
+                loading_languages: Default::default(),
+                subscription: watch::channel(),
+                theme: Default::default(),
+                version: 0,
+                reload_count: 0,
+            }),
+            language_server_download_dir: None,
+            login_shell_env_loaded: login_shell_env_loaded.shared(),
+            lsp_binary_paths: Default::default(),
+            executor: None,
+            lsp_binary_status_tx: Default::default(),
+        }
+    }
+
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn test() -> Self {
+        Self::new(Task::ready(()))
+    }
+
+    pub fn set_executor(&mut self, executor: BackgroundExecutor) {
+        self.executor = Some(executor);
+    }
+
+    /// Clears out all of the loaded languages and reload them from scratch.
+    pub fn reload(&self) {
+        self.state.write().reload();
+    }
+
+    /// Clears out the given languages and reload them from scratch.
+    pub fn reload_languages(&self, languages: &[Arc<str>], grammars: &[Arc<str>]) {
+        self.state.write().reload_languages(languages, grammars);
+    }
+
+    #[cfg(any(feature = "test-support", test))]
+    pub fn register_test_language(&self, config: LanguageConfig) {
+        self.register_language(
+            config.name.clone(),
+            config.grammar.clone(),
+            config.matcher.clone(),
+            vec![],
+            move || Ok((config.clone(), Default::default())),
+        )
+    }
+
+    /// Adds a language to the registry, which can be loaded if needed.
+    pub fn register_language(
+        &self,
+        name: Arc<str>,
+        grammar_name: Option<Arc<str>>,
+        matcher: LanguageMatcher,
+        lsp_adapters: Vec<Arc<dyn LspAdapter>>,
+        load: impl Fn() -> Result<(LanguageConfig, LanguageQueries)> + 'static + Send + Sync,
+    ) {
+        let load = Arc::new(load);
+        let state = &mut *self.state.write();
+
+        for existing_language in &mut state.available_languages {
+            if existing_language.name == name {
+                existing_language.grammar = grammar_name;
+                existing_language.matcher = matcher;
+                existing_language.lsp_adapters = lsp_adapters;
+                existing_language.load = load;
+                return;
+            }
+        }
+
+        state.available_languages.push(AvailableLanguage {
+            id: post_inc(&mut state.next_available_language_id),
+            name,
+            grammar: grammar_name,
+            matcher,
+            load,
+            lsp_adapters,
+            loaded: false,
+        });
+    }
+
+    /// Adds grammars to the registry. Language configurations reference a grammar by name. The
+    /// grammar controls how the source code is parsed.
+    pub fn register_native_grammars(
+        &self,
+        grammars: impl IntoIterator<Item = (impl Into<Arc<str>>, tree_sitter::Language)>,
+    ) {
+        self.state.write().grammars.extend(
+            grammars
+                .into_iter()
+                .map(|(name, grammar)| (name.into(), AvailableGrammar::Native(grammar))),
+        );
+    }
+
+    /// Adds paths to WASM grammar files, which can be loaded if needed.
+    pub fn register_wasm_grammars(
+        &self,
+        grammars: impl IntoIterator<Item = (impl Into<Arc<str>>, PathBuf)>,
+    ) {
+        self.state.write().grammars.extend(
+            grammars
+                .into_iter()
+                .map(|(name, path)| (name.into(), AvailableGrammar::Unloaded(path))),
+        );
+    }
+
+    pub fn language_names(&self) -> Vec<String> {
+        let state = self.state.read();
+        let mut result = state
+            .available_languages
+            .iter()
+            .filter_map(|l| l.loaded.not().then_some(l.name.to_string()))
+            .chain(state.languages.iter().map(|l| l.config.name.to_string()))
+            .collect::<Vec<_>>();
+        result.sort_unstable_by_key(|language_name| language_name.to_lowercase());
+        result
+    }
+
+    pub fn add(&self, language: Arc<Language>) {
+        self.state.write().add(language);
+    }
+
+    pub fn subscribe(&self) -> watch::Receiver<()> {
+        self.state.read().subscription.1.clone()
+    }
+
+    /// Returns the number of times that the registry has been changed,
+    /// by adding languages or reloading.
+    pub fn version(&self) -> usize {
+        self.state.read().version
+    }
+
+    /// Returns the number of times that the registry has been reloaded.
+    pub fn reload_count(&self) -> usize {
+        self.state.read().reload_count
+    }
+
+    pub fn set_theme(&self, theme: Arc<Theme>) {
+        let mut state = self.state.write();
+        state.theme = Some(theme.clone());
+        for language in &state.languages {
+            language.set_theme(theme.syntax());
+        }
+    }
+
+    pub fn set_language_server_download_dir(&mut self, path: impl Into<Arc<Path>>) {
+        self.language_server_download_dir = Some(path.into());
+    }
+
+    pub fn language_for_name(
+        self: &Arc<Self>,
+        name: &str,
+    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
+        let name = UniCase::new(name);
+        self.get_or_load_language(|language_name, _| UniCase::new(language_name) == name)
+    }
+
+    pub fn language_for_name_or_extension(
+        self: &Arc<Self>,
+        string: &str,
+    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
+        let string = UniCase::new(string);
+        self.get_or_load_language(|name, config| {
+            UniCase::new(name) == string
+                || config
+                    .path_suffixes
+                    .iter()
+                    .any(|suffix| UniCase::new(suffix) == string)
+        })
+    }
+
+    pub fn language_for_file(
+        self: &Arc<Self>,
+        path: impl AsRef<Path>,
+        content: Option<&Rope>,
+    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
+        let path = path.as_ref();
+        let filename = path.file_name().and_then(|name| name.to_str());
+        let extension = path.extension_or_hidden_file_name();
+        let path_suffixes = [extension, filename];
+        self.get_or_load_language(|_, config| {
+            let path_matches = config
+                .path_suffixes
+                .iter()
+                .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())));
+            let content_matches = content.zip(config.first_line_pattern.as_ref()).map_or(
+                false,
+                |(content, pattern)| {
+                    let end = content.clip_point(Point::new(0, 256), Bias::Left);
+                    let end = content.point_to_offset(end);
+                    let text = content.chunks_in_range(0..end).collect::<String>();
+                    pattern.is_match(&text)
+                },
+            );
+            path_matches || content_matches
+        })
+    }
+
+    fn get_or_load_language(
+        self: &Arc<Self>,
+        callback: impl Fn(&str, &LanguageMatcher) -> bool,
+    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
+        let (tx, rx) = oneshot::channel();
+
+        let mut state = self.state.write();
+        if let Some(language) = state
+            .languages
+            .iter()
+            .find(|language| callback(language.config.name.as_ref(), &language.config.matcher))
+        {
+            let _ = tx.send(Ok(language.clone()));
+        } else if let Some(executor) = self.executor.clone() {
+            if let Some(language) = state
+                .available_languages
+                .iter()
+                .rfind(|l| !l.loaded && callback(&l.name, &l.matcher))
+                .cloned()
+            {
+                match state.loading_languages.entry(language.id) {
+                    hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(tx),
+                    hash_map::Entry::Vacant(entry) => {
+                        let this = self.clone();
+                        executor
+                            .spawn(async move {
+                                let id = language.id;
+                                let name = language.name.clone();
+                                let language = async {
+                                    let (config, queries) = (language.load)()?;
+
+                                    let grammar = if let Some(grammar) = config.grammar.clone() {
+                                        Some(this.get_or_load_grammar(grammar).await?)
+                                    } else {
+                                        None
+                                    };
+
+                                    Language::new(config, grammar)
+                                        .with_lsp_adapters(language.lsp_adapters)
+                                        .await
+                                        .with_queries(queries)
+                                }
+                                .await;
+
+                                match language {
+                                    Ok(language) => {
+                                        let language = Arc::new(language);
+                                        let mut state = this.state.write();
+
+                                        state.add(language.clone());
+                                        state.mark_language_loaded(id);
+                                        if let Some(mut txs) = state.loading_languages.remove(&id) {
+                                            for tx in txs.drain(..) {
+                                                let _ = tx.send(Ok(language.clone()));
+                                            }
+                                        }
+                                    }
+                                    Err(e) => {
+                                        log::error!("failed to load language {name}:\n{:?}", e);
+                                        let mut state = this.state.write();
+                                        state.mark_language_loaded(id);
+                                        if let Some(mut txs) = state.loading_languages.remove(&id) {
+                                            for tx in txs.drain(..) {
+                                                let _ = tx.send(Err(anyhow!(
+                                                    "failed to load language {}: {}",
+                                                    name,
+                                                    e
+                                                )));
+                                            }
+                                        }
+                                    }
+                                };
+                            })
+                            .detach();
+                        entry.insert(vec![tx]);
+                    }
+                }
+            } else {
+                let _ = tx.send(Err(anyhow!("language not found")));
+            }
+        } else {
+            let _ = tx.send(Err(anyhow!("executor does not exist")));
+        }
+
+        rx.unwrap()
+    }
+
+    fn get_or_load_grammar(
+        self: &Arc<Self>,
+        name: Arc<str>,
+    ) -> UnwrapFuture<oneshot::Receiver<Result<tree_sitter::Language>>> {
+        let (tx, rx) = oneshot::channel();
+        let mut state = self.state.write();
+
+        if let Some(grammar) = state.grammars.get_mut(name.as_ref()) {
+            match grammar {
+                AvailableGrammar::Native(grammar) | AvailableGrammar::Loaded(_, grammar) => {
+                    tx.send(Ok(grammar.clone())).ok();
+                }
+                AvailableGrammar::Loading(_, txs) => {
+                    txs.push(tx);
+                }
+                AvailableGrammar::Unloaded(wasm_path) => {
+                    if let Some(executor) = &self.executor {
+                        let this = self.clone();
+                        executor
+                            .spawn({
+                                let wasm_path = wasm_path.clone();
+                                async move {
+                                    let wasm_bytes = std::fs::read(&wasm_path)?;
+                                    let grammar_name = wasm_path
+                                        .file_stem()
+                                        .and_then(OsStr::to_str)
+                                        .ok_or_else(|| anyhow!("invalid grammar filename"))?;
+                                    let grammar = PARSER.with(|parser| {
+                                        let mut parser = parser.borrow_mut();
+                                        let mut store = parser.take_wasm_store().unwrap();
+                                        let grammar =
+                                            store.load_language(&grammar_name, &wasm_bytes);
+                                        parser.set_wasm_store(store).unwrap();
+                                        grammar
+                                    })?;
+
+                                    if let Some(AvailableGrammar::Loading(_, txs)) =
+                                        this.state.write().grammars.insert(
+                                            name,
+                                            AvailableGrammar::Loaded(wasm_path, grammar.clone()),
+                                        )
+                                    {
+                                        for tx in txs {
+                                            tx.send(Ok(grammar.clone())).ok();
+                                        }
+                                    }
+
+                                    anyhow::Ok(())
+                                }
+                            })
+                            .detach();
+                        *grammar = AvailableGrammar::Loading(wasm_path.clone(), vec![tx]);
+                    }
+                }
+            }
+        } else {
+            tx.send(Err(anyhow!("no such grammar {}", name))).ok();
+        }
+
+        rx.unwrap()
+    }
+
+    pub fn to_vec(&self) -> Vec<Arc<Language>> {
+        self.state.read().languages.iter().cloned().collect()
+    }
+
+    pub fn create_pending_language_server(
+        self: &Arc<Self>,
+        stderr_capture: Arc<Mutex<Option<String>>>,
+        language: Arc<Language>,
+        adapter: Arc<CachedLspAdapter>,
+        root_path: Arc<Path>,
+        delegate: Arc<dyn LspAdapterDelegate>,
+        cx: &mut AppContext,
+    ) -> Option<PendingLanguageServer> {
+        let server_id = self.state.write().next_language_server_id();
+        log::info!(
+            "starting language server {:?}, path: {root_path:?}, id: {server_id}",
+            adapter.name.0
+        );
+
+        #[cfg(any(test, feature = "test-support"))]
+        if language.fake_adapter.is_some() {
+            let task = cx.spawn(|cx| async move {
+                let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
+                let (server, mut fake_server) = lsp::FakeLanguageServer::new(
+                    fake_adapter.name.to_string(),
+                    fake_adapter.capabilities.clone(),
+                    cx.clone(),
+                );
+
+                if let Some(initializer) = &fake_adapter.initializer {
+                    initializer(&mut fake_server);
+                }
+
+                let servers_tx = servers_tx.clone();
+                cx.background_executor()
+                    .spawn(async move {
+                        if fake_server
+                            .try_receive_notification::<lsp::notification::Initialized>()
+                            .await
+                            .is_some()
+                        {
+                            servers_tx.unbounded_send(fake_server).ok();
+                        }
+                    })
+                    .detach();
+
+                Ok(server)
+            });
+
+            return Some(PendingLanguageServer {
+                server_id,
+                task,
+                container_dir: None,
+            });
+        }
+
+        let download_dir = self
+            .language_server_download_dir
+            .clone()
+            .ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
+            .log_err()?;
+        let this = self.clone();
+        let language = language.clone();
+        let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref()));
+        let root_path = root_path.clone();
+        let adapter = adapter.clone();
+        let login_shell_env_loaded = self.login_shell_env_loaded.clone();
+        let lsp_binary_statuses = self.lsp_binary_status_tx.clone();
+
+        let task = {
+            let container_dir = container_dir.clone();
+            cx.spawn(move |mut cx| async move {
+                login_shell_env_loaded.await;
+
+                let entry = this
+                    .lsp_binary_paths
+                    .lock()
+                    .entry(adapter.name.clone())
+                    .or_insert_with(|| {
+                        let adapter = adapter.clone();
+                        let language = language.clone();
+                        let delegate = delegate.clone();
+                        cx.spawn(|cx| {
+                            get_binary(
+                                adapter,
+                                language,
+                                delegate,
+                                container_dir,
+                                lsp_binary_statuses,
+                                cx,
+                            )
+                            .map_err(Arc::new)
+                        })
+                        .shared()
+                    })
+                    .clone();
+
+                let binary = match entry.await {
+                    Ok(binary) => binary,
+                    Err(err) => anyhow::bail!("{err}"),
+                };
+
+                if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
+                    task.await?;
+                }
+
+                lsp::LanguageServer::new(
+                    stderr_capture,
+                    server_id,
+                    binary,
+                    &root_path,
+                    adapter.code_action_kinds(),
+                    cx,
+                )
+            })
+        };
+
+        Some(PendingLanguageServer {
+            server_id,
+            task,
+            container_dir: Some(container_dir),
+        })
+    }
+
+    pub fn language_server_binary_statuses(
+        &self,
+    ) -> mpsc::UnboundedReceiver<(Arc<Language>, LanguageServerBinaryStatus)> {
+        self.lsp_binary_status_tx.subscribe()
+    }
+
+    pub fn delete_server_container(
+        &self,
+        adapter: Arc<CachedLspAdapter>,
+        cx: &mut AppContext,
+    ) -> Task<()> {
+        log::info!("deleting server container");
+
+        let mut lock = self.lsp_binary_paths.lock();
+        lock.remove(&adapter.name);
+
+        let download_dir = self
+            .language_server_download_dir
+            .clone()
+            .expect("language server download directory has not been assigned before deleting server container");
+
+        cx.spawn(|_| async move {
+            let container_dir = download_dir.join(adapter.name.0.as_ref());
+            smol::fs::remove_dir_all(container_dir)
+                .await
+                .context("server container removal")
+                .log_err();
+        })
+    }
+
+    pub fn next_language_server_id(&self) -> LanguageServerId {
+        self.state.write().next_language_server_id()
+    }
+}
+
+#[cfg(any(test, feature = "test-support"))]
+impl Default for LanguageRegistry {
+    fn default() -> Self {
+        Self::test()
+    }
+}
+
+impl LanguageRegistryState {
+    fn next_language_server_id(&mut self) -> LanguageServerId {
+        LanguageServerId(post_inc(&mut self.next_language_server_id))
+    }
+
+    fn add(&mut self, language: Arc<Language>) {
+        if let Some(theme) = self.theme.as_ref() {
+            language.set_theme(theme.syntax());
+        }
+        self.languages.push(language);
+        self.version += 1;
+        *self.subscription.0.borrow_mut() = ();
+    }
+
+    fn reload(&mut self) {
+        self.languages.clear();
+        self.version += 1;
+        self.reload_count += 1;
+        for language in &mut self.available_languages {
+            language.loaded = false;
+        }
+        *self.subscription.0.borrow_mut() = ();
+    }
+
+    fn reload_languages(
+        &mut self,
+        languages_to_reload: &[Arc<str>],
+        grammars_to_reload: &[Arc<str>],
+    ) {
+        for (name, grammar) in self.grammars.iter_mut() {
+            if grammars_to_reload.contains(name) {
+                if let AvailableGrammar::Loaded(path, _) = grammar {
+                    *grammar = AvailableGrammar::Unloaded(path.clone());
+                }
+            }
+        }
+
+        self.languages.retain(|language| {
+            let should_reload = languages_to_reload.contains(&language.config.name)
+                || language
+                    .config
+                    .grammar
+                    .as_ref()
+                    .map_or(false, |grammar| grammars_to_reload.contains(&grammar));
+            !should_reload
+        });
+
+        for language in &mut self.available_languages {
+            if languages_to_reload.contains(&language.name)
+                || language
+                    .grammar
+                    .as_ref()
+                    .map_or(false, |grammar| grammars_to_reload.contains(grammar))
+            {
+                language.loaded = false;
+            }
+        }
+
+        self.version += 1;
+        self.reload_count += 1;
+        *self.subscription.0.borrow_mut() = ();
+    }
+
+    /// Mark the given language as having been loaded, so that the
+    /// language registry won't try to load it again.
+    fn mark_language_loaded(&mut self, id: AvailableLanguageId) {
+        for language in &mut self.available_languages {
+            if language.id == id {
+                language.loaded = true;
+                break;
+            }
+        }
+    }
+}
+
+impl LspBinaryStatusSender {
+    fn subscribe(&self) -> mpsc::UnboundedReceiver<(Arc<Language>, LanguageServerBinaryStatus)> {
+        let (tx, rx) = mpsc::unbounded();
+        self.txs.lock().push(tx);
+        rx
+    }
+
+    fn send(&self, language: Arc<Language>, status: LanguageServerBinaryStatus) {
+        let mut txs = self.txs.lock();
+        txs.retain(|tx| {
+            tx.unbounded_send((language.clone(), status.clone()))
+                .is_ok()
+        });
+    }
+}
+
+async fn get_binary(
+    adapter: Arc<CachedLspAdapter>,
+    language: Arc<Language>,
+    delegate: Arc<dyn LspAdapterDelegate>,
+    container_dir: Arc<Path>,
+    statuses: LspBinaryStatusSender,
+    mut cx: AsyncAppContext,
+) -> Result<LanguageServerBinary> {
+    if !container_dir.exists() {
+        smol::fs::create_dir_all(&container_dir)
+            .await
+            .context("failed to create container directory")?;
+    }
+
+    if let Some(task) = adapter.will_fetch_server(&delegate, &mut cx) {
+        task.await?;
+    }
+
+    let binary = fetch_latest_binary(
+        adapter.clone(),
+        language.clone(),
+        delegate.as_ref(),
+        &container_dir,
+        statuses.clone(),
+    )
+    .await;
+
+    if let Err(error) = binary.as_ref() {
+        if let Some(binary) = adapter
+            .cached_server_binary(container_dir.to_path_buf(), delegate.as_ref())
+            .await
+        {
+            statuses.send(language.clone(), LanguageServerBinaryStatus::Cached);
+            return Ok(binary);
+        } else {
+            statuses.send(
+                language.clone(),
+                LanguageServerBinaryStatus::Failed {
+                    error: format!("{:?}", error),
+                },
+            );
+        }
+    }
+
+    binary
+}
+
+async fn fetch_latest_binary(
+    adapter: Arc<CachedLspAdapter>,
+    language: Arc<Language>,
+    delegate: &dyn LspAdapterDelegate,
+    container_dir: &Path,
+    lsp_binary_statuses_tx: LspBinaryStatusSender,
+) -> Result<LanguageServerBinary> {
+    let container_dir: Arc<Path> = container_dir.into();
+    lsp_binary_statuses_tx.send(
+        language.clone(),
+        LanguageServerBinaryStatus::CheckingForUpdate,
+    );
+
+    let version_info = adapter.fetch_latest_server_version(delegate).await?;
+    lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloading);
+
+    let binary = adapter
+        .fetch_server_binary(version_info, container_dir.to_path_buf(), delegate)
+        .await?;
+    lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloaded);
+
+    Ok(binary)
+}

crates/project/src/project_tests.rs 🔗

@@ -2861,21 +2861,16 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
     let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
 
     let languages = project.update(cx, |project, _| project.languages().clone());
-    languages.add_grammars([("rust", tree_sitter_rust::language())]);
-    languages.register(
-        "/some/path",
-        LanguageConfig {
-            name: "Rust".into(),
-            grammar: Some("rust".into()),
-            matcher: LanguageMatcher {
-                path_suffixes: vec!["rs".into()],
-                ..Default::default()
-            },
+    languages.register_native_grammars([("rust", tree_sitter_rust::language())]);
+    languages.register_test_language(LanguageConfig {
+        name: "Rust".into(),
+        grammar: Some("rust".into()),
+        matcher: LanguageMatcher {
+            path_suffixes: vec!["rs".into()],
             ..Default::default()
         },
-        vec![],
-        |_| Default::default(),
-    );
+        ..Default::default()
+    });
 
     let buffer = project.update(cx, |project, cx| {
         project.create_buffer("", None, cx).unwrap()

crates/zed/src/languages.rs 🔗

@@ -62,7 +62,7 @@ pub fn init(
     ElixirSettings::register(cx);
     DenoSettings::register(cx);
 
-    languages.add_grammars([
+    languages.register_native_grammars([
         ("bash", tree_sitter_bash::language()),
         ("beancount", tree_sitter_beancount::language()),
         ("c", tree_sitter_c::language()),
@@ -115,8 +115,15 @@ pub fn init(
         ("zig", tree_sitter_zig::language()),
     ]);
 
-    let language = |name: &'static str, adapters| {
-        languages.register(name, load_config(name), adapters, load_queries)
+    let language = |asset_dir_name: &'static str, adapters| {
+        let config = load_config(asset_dir_name);
+        languages.register_language(
+            config.name.clone(),
+            config.grammar.clone(),
+            config.matcher.clone(),
+            adapters,
+            move || Ok((config.clone(), load_queries(asset_dir_name))),
+        )
     };
 
     language("bash", vec![]);