Allow controlling Tailwind via the language_servers setting (#11012)

This PR adds the ability for the Tailwind language server
(`tailwindcss-language-server`) to be controlled by the
`language_servers` setting.

Now in your settings you can indicate that the Tailwind language server
should be used for a given language, even if that language does not have
the Tailwind language server registered for it already:

```json
{
  "languages": {
    "My Language": {
      "language_servers": ["tailwindcss-language-server", "..."]
    }
  }
}
```

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-04-25 17:29:47 -04:00 committed by GitHub
parent c833a7e662
commit 3eac581a62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 113 additions and 18 deletions

View File

@ -46,6 +46,8 @@ struct LanguageRegistryState {
available_languages: Vec<AvailableLanguage>, available_languages: Vec<AvailableLanguage>,
grammars: HashMap<Arc<str>, AvailableGrammar>, grammars: HashMap<Arc<str>, AvailableGrammar>,
lsp_adapters: HashMap<Arc<str>, Vec<Arc<CachedLspAdapter>>>, lsp_adapters: HashMap<Arc<str>, Vec<Arc<CachedLspAdapter>>>,
available_lsp_adapters:
HashMap<LanguageServerName, Arc<dyn Fn() -> Arc<CachedLspAdapter> + 'static + Send + Sync>>,
loading_languages: HashMap<LanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>, loading_languages: HashMap<LanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
subscription: (watch::Sender<()>, watch::Receiver<()>), subscription: (watch::Sender<()>, watch::Receiver<()>),
theme: Option<Arc<Theme>>, theme: Option<Arc<Theme>>,
@ -153,6 +155,7 @@ impl LanguageRegistry {
language_settings: Default::default(), language_settings: Default::default(),
loading_languages: Default::default(), loading_languages: Default::default(),
lsp_adapters: Default::default(), lsp_adapters: Default::default(),
available_lsp_adapters: HashMap::default(),
subscription: watch::channel(), subscription: watch::channel(),
theme: Default::default(), theme: Default::default(),
version: 0, 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<dyn LspAdapter> + '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<Arc<CachedLspAdapter>> {
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<str>, adapter: Arc<dyn LspAdapter>) { pub fn register_lsp_adapter(&self, language_name: Arc<str>, adapter: Arc<dyn LspAdapter>) {
self.state self.state
.write() .write()

View File

@ -789,5 +789,24 @@ mod tests {
), ),
language_server_names(&["deno", "eslint", "tailwind"]) 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",
])
);
} }
} }

View File

@ -107,10 +107,7 @@ pub fn init(
language!("cpp", vec![Arc::new(c::CLspAdapter)]); language!("cpp", vec![Arc::new(c::CLspAdapter)]);
language!( language!(
"css", "css",
vec![ vec![Arc::new(css::CssLspAdapter::new(node_runtime.clone())),]
Arc::new(css::CssLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
]
); );
language!("go", vec![Arc::new(go::GoLspAdapter)]); language!("go", vec![Arc::new(go::GoLspAdapter)]);
language!("gomod"); language!("gomod");
@ -160,13 +157,7 @@ pub fn init(
))] ))]
); );
language!("ruby", vec![Arc::new(ruby::RubyLanguageServer)]); language!("ruby", vec![Arc::new(ruby::RubyLanguageServer)]);
language!( language!("erb", vec![Arc::new(ruby::RubyLanguageServer),]);
"erb",
vec![
Arc::new(ruby::RubyLanguageServer),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
]
);
language!("regex"); language!("regex");
language!( language!(
"yaml", "yaml",
@ -174,14 +165,42 @@ pub fn init(
); );
language!("proto"); 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 = [ let tailwind_languages = [
"Astro", "Astro",
"CSS",
"ERB",
"HEEX", "HEEX",
"HTML", "HTML",
"JavaScript",
"PHP", "PHP",
"Svelte", "Svelte",
"TSX", "TSX",
"JavaScript",
"Vue.js", "Vue.js",
]; ];

View File

@ -3066,12 +3066,34 @@ impl Project {
.map(|lsp_adapter| lsp_adapter.name.clone()) .map(|lsp_adapter| lsp_adapter.name.clone())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let enabled_language_servers = let desired_language_servers =
settings.customized_language_servers(&available_language_servers); settings.customized_language_servers(&available_language_servers);
let enabled_lsp_adapters = available_lsp_adapters
.into_iter() let mut enabled_lsp_adapters: Vec<Arc<CachedLspAdapter>> = Vec::new();
.filter(|adapter| enabled_language_servers.contains(&adapter.name)) for desired_language_server in desired_language_servers {
.collect::<Vec<_>>(); 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!( log::info!(
"starting language servers for {language}: {adapters}", "starting language servers for {language}: {adapters}",
@ -3083,7 +3105,7 @@ impl Project {
); );
for adapter in enabled_lsp_adapters { 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);
} }
} }