diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 6d30a80bacc7c6ac19bd1ca4ccd9e0ab3c3b98d4..74fbc588de75c38b8f612c013a5d5f307304cf58 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -46,6 +46,8 @@ struct LanguageRegistryState { available_languages: Vec, grammars: HashMap, AvailableGrammar>, lsp_adapters: HashMap, Vec>>, + available_lsp_adapters: + HashMap Arc + 'static + Send + Sync>>, loading_languages: HashMap>>>>, subscription: (watch::Sender<()>, watch::Receiver<()>), theme: Option>, @@ -153,6 +155,7 @@ impl LanguageRegistry { language_settings: Default::default(), loading_languages: Default::default(), lsp_adapters: Default::default(), + available_lsp_adapters: HashMap::default(), subscription: watch::channel(), theme: Default::default(), version: 0, @@ -213,6 +216,38 @@ impl LanguageRegistry { ) } + /// Registers an available language server adapter. + /// + /// The language server is registered under the language server name, but + /// not bound to a particular language. + /// + /// When a language wants to load this particular language server, it will + /// invoke the `load` function. + pub fn register_available_lsp_adapter( + &self, + name: LanguageServerName, + load: impl Fn() -> Arc + 'static + Send + Sync, + ) { + self.state.write().available_lsp_adapters.insert( + name, + Arc::new(move || { + let lsp_adapter = load(); + CachedLspAdapter::new(lsp_adapter, true) + }), + ); + } + + /// Loads the language server adapter for the language server with the given name. + pub fn load_available_lsp_adapter( + &self, + name: &LanguageServerName, + ) -> Option> { + let state = self.state.read(); + let load_lsp_adapter = state.available_lsp_adapters.get(name)?; + + Some(load_lsp_adapter()) + } + pub fn register_lsp_adapter(&self, language_name: Arc, adapter: Arc) { self.state .write() diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index e65b823bc187809a0fb1a0d1fed13980c8743c83..16b8ab0eb235ae23fdaad4fcee70856212235d36 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -789,5 +789,24 @@ mod tests { ), language_server_names(&["deno", "eslint", "tailwind"]) ); + + // Adding a language server not in the list of available languages servers adds it to the list. + assert_eq!( + LanguageSettings::resolve_language_servers( + &[ + "my-cool-language-server".into(), + LanguageSettings::REST_OF_LANGUAGE_SERVERS.into() + ], + &available_language_servers + ), + language_server_names(&[ + "my-cool-language-server", + "typescript-language-server", + "biome", + "deno", + "eslint", + "tailwind", + ]) + ); } } diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index 3223aa8749292443d53cf2cf0dc8260f05aaa249..3810ba16eb820142472f60fa58ed7a17e788ec39 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -107,10 +107,7 @@ pub fn init( language!("cpp", vec![Arc::new(c::CLspAdapter)]); language!( "css", - vec![ - Arc::new(css::CssLspAdapter::new(node_runtime.clone())), - Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ] + vec![Arc::new(css::CssLspAdapter::new(node_runtime.clone())),] ); language!("go", vec![Arc::new(go::GoLspAdapter)]); language!("gomod"); @@ -160,13 +157,7 @@ pub fn init( ))] ); language!("ruby", vec![Arc::new(ruby::RubyLanguageServer)]); - language!( - "erb", - vec![ - Arc::new(ruby::RubyLanguageServer), - Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ] - ); + language!("erb", vec![Arc::new(ruby::RubyLanguageServer),]); language!("regex"); language!( "yaml", @@ -174,14 +165,42 @@ pub fn init( ); language!("proto"); + // Register Tailwind globally as an available language server. + // + // This will allow users to add Tailwind support for a given language via + // the `language_servers` setting: + // + // ```json + // { + // "languages": { + // "My Language": { + // "language_servers": ["tailwindcss-language-server", "..."] + // } + // } + // } + // ``` + languages.register_available_lsp_adapter( + LanguageServerName("tailwindcss-language-server".into()), + { + let node_runtime = node_runtime.clone(); + move || Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())) + }, + ); + + // Register Tailwind for the existing languages that should have it by default. + // + // This can be driven by the `language_servers` setting once we have a way for + // extensions to provide their own default value for that setting. let tailwind_languages = [ "Astro", + "CSS", + "ERB", "HEEX", "HTML", + "JavaScript", "PHP", "Svelte", "TSX", - "JavaScript", "Vue.js", ]; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 444393f17c204e08c6eeae4b432a5ba8a51b5874..34a6359db4d079d47afc903a96d7d768318b514f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3066,12 +3066,34 @@ impl Project { .map(|lsp_adapter| lsp_adapter.name.clone()) .collect::>(); - let enabled_language_servers = + let desired_language_servers = settings.customized_language_servers(&available_language_servers); - let enabled_lsp_adapters = available_lsp_adapters - .into_iter() - .filter(|adapter| enabled_language_servers.contains(&adapter.name)) - .collect::>(); + + let mut enabled_lsp_adapters: Vec> = Vec::new(); + for desired_language_server in desired_language_servers { + if let Some(adapter) = available_lsp_adapters + .iter() + .find(|adapter| adapter.name == desired_language_server) + { + enabled_lsp_adapters.push(adapter.clone()); + continue; + } + + if let Some(adapter) = self + .languages + .load_available_lsp_adapter(&desired_language_server) + { + self.languages() + .register_lsp_adapter(language.name(), adapter.adapter.clone()); + enabled_lsp_adapters.push(adapter); + continue; + } + + log::warn!( + "no language server found matching '{}'", + desired_language_server.0 + ); + } log::info!( "starting language servers for {language}: {adapters}", @@ -3083,7 +3105,7 @@ impl Project { ); for adapter in enabled_lsp_adapters { - self.start_language_server(worktree, adapter.clone(), language.clone(), cx); + self.start_language_server(worktree, adapter, language.clone(), cx); } }