Add initial support for defining language server adapters in WebAssembly-based extensions (#8645)

This PR adds **internal** ability to run arbitrary language servers via
WebAssembly extensions. The functionality isn't exposed yet - we're just
landing this in this early state because there have been a lot of
changes to the `LspAdapter` trait, and other language server logic.

## Next steps

* Currently, wasm extensions can only define how to *install* and run a
language server, they can't yet implement the other LSP adapter methods,
such as formatting completion labels and workspace symbols.
* We don't have an automatic way to install or develop these types of
extensions
* We don't have a way to package these types of extensions in our
extensions repo, to make them available via our extensions API.
* The Rust extension API crate, `zed-extension-api` has not yet been
published to crates.io, because we still consider the API a work in
progress.

Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
Max Brunsfeld 2024-03-01 16:00:55 -08:00 committed by GitHub
parent f3f2225a8e
commit 268fa1cbaf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
84 changed files with 3714 additions and 1973 deletions

View file

@ -36,10 +36,6 @@ impl LspAdapter for AstroLspAdapter {
LanguageServerName("astro-language-server".into())
}
fn short_name(&self) -> &'static str {
"astro"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -20,10 +20,6 @@ impl super::LspAdapter for CLspAdapter {
LanguageServerName("clangd".into())
}
fn short_name(&self) -> &'static str {
"clangd"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
@ -296,7 +292,7 @@ mod tests {
});
});
});
let language = crate::language("c", tree_sitter_c::language(), None).await;
let language = crate::language("c", tree_sitter_c::language());
cx.new_model(|cx| {
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")

View file

@ -18,10 +18,6 @@ impl super::LspAdapter for ClojureLspAdapter {
LanguageServerName("clojure-lsp".into())
}
fn short_name(&self) -> &'static str {
"clojure"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,

View file

@ -21,10 +21,6 @@ impl super::LspAdapter for OmniSharpAdapter {
LanguageServerName("OmniSharp".into())
}
fn short_name(&self) -> &'static str {
"OmniSharp"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,

View file

@ -37,10 +37,6 @@ impl LspAdapter for CssLspAdapter {
LanguageServerName("vscode-css-language-server".into())
}
fn short_name(&self) -> &'static str {
"css"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -19,10 +19,6 @@ impl LspAdapter for DartLanguageServer {
LanguageServerName("dart".into())
}
fn short_name(&self) -> &'static str {
"dart"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -62,10 +62,6 @@ impl LspAdapter for DenoLspAdapter {
LanguageServerName("deno-language-server".into())
}
fn short_name(&self) -> &'static str {
"deno-ts"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,

View file

@ -36,10 +36,6 @@ impl LspAdapter for DockerfileLspAdapter {
LanguageServerName("docker-langserver".into())
}
fn short_name(&self) -> &'static str {
"dockerfile"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -71,10 +71,6 @@ impl LspAdapter for ElixirLspAdapter {
LanguageServerName("elixir-ls".into())
}
fn short_name(&self) -> &'static str {
"elixir-ls"
}
fn will_start_server(
&self,
delegate: &Arc<dyn LspAdapterDelegate>,
@ -302,10 +298,6 @@ impl LspAdapter for NextLspAdapter {
LanguageServerName("next-ls".into())
}
fn short_name(&self) -> &'static str {
"next-ls"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
@ -460,10 +452,6 @@ impl LspAdapter for LocalLspAdapter {
LanguageServerName("local-ls".into())
}
fn short_name(&self) -> &'static str {
"local-ls"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -40,10 +40,6 @@ impl LspAdapter for ElmLspAdapter {
LanguageServerName(SERVER_NAME.into())
}
fn short_name(&self) -> &'static str {
"elmLS"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -12,10 +12,6 @@ impl LspAdapter for ErlangLspAdapter {
LanguageServerName("erlang_ls".into())
}
fn short_name(&self) -> &'static str {
"erlang_ls"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -27,10 +27,6 @@ impl LspAdapter for GleamLspAdapter {
LanguageServerName("gleam".into())
}
fn short_name(&self) -> &'static str {
"gleam"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,

View file

@ -38,10 +38,6 @@ impl super::LspAdapter for GoLspAdapter {
LanguageServerName("gopls".into())
}
fn short_name(&self) -> &'static str {
"gopls"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
@ -58,23 +54,16 @@ impl super::LspAdapter for GoLspAdapter {
Ok(Box::new(version) as Box<_>)
}
fn check_if_user_installed(
async fn check_if_user_installed(
&self,
delegate: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Option<Task<Option<LanguageServerBinary>>> {
let delegate = delegate.clone();
Some(cx.spawn(|cx| async move {
match cx.update(|cx| delegate.which_command(OsString::from("gopls"), cx)) {
Ok(task) => task.await.map(|(path, env)| LanguageServerBinary {
path,
arguments: server_binary_arguments(),
env: Some(env),
}),
Err(_) => None,
}
}))
delegate: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let (path, env) = delegate.which_command(OsString::from("gopls")).await?;
Some(LanguageServerBinary {
path,
arguments: server_binary_arguments(),
env: Some(env),
})
}
fn will_fetch_server(
@ -423,12 +412,8 @@ mod tests {
#[gpui::test]
async fn test_go_label_for_completion() {
let language = language(
"go",
tree_sitter_go::language(),
Some(Arc::new(GoLspAdapter)),
)
.await;
let adapter = Arc::new(GoLspAdapter);
let language = language("go", tree_sitter_go::language());
let theme = SyntaxTheme::new_test([
("type", Hsla::default()),
@ -446,13 +431,16 @@ mod tests {
let highlight_number = grammar.highlight_id_for_name("number").unwrap();
assert_eq!(
language
.label_for_completion(&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FUNCTION),
label: "Hello".to_string(),
detail: Some("func(a B) c.D".to_string()),
..Default::default()
})
adapter
.label_for_completion(
&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FUNCTION),
label: "Hello".to_string(),
detail: Some("func(a B) c.D".to_string()),
..Default::default()
},
&language
)
.await,
Some(CodeLabel {
text: "Hello(a B) c.D".to_string(),
@ -467,13 +455,16 @@ mod tests {
// Nested methods
assert_eq!(
language
.label_for_completion(&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::METHOD),
label: "one.two.Three".to_string(),
detail: Some("func() [3]interface{}".to_string()),
..Default::default()
})
adapter
.label_for_completion(
&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::METHOD),
label: "one.two.Three".to_string(),
detail: Some("func() [3]interface{}".to_string()),
..Default::default()
},
&language
)
.await,
Some(CodeLabel {
text: "one.two.Three() [3]interface{}".to_string(),
@ -488,13 +479,16 @@ mod tests {
// Nested fields
assert_eq!(
language
.label_for_completion(&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FIELD),
label: "two.Three".to_string(),
detail: Some("a.Bcd".to_string()),
..Default::default()
})
adapter
.label_for_completion(
&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FIELD),
label: "two.Three".to_string(),
detail: Some("a.Bcd".to_string()),
..Default::default()
},
&language
)
.await,
Some(CodeLabel {
text: "two.Three a.Bcd".to_string(),

View file

@ -12,10 +12,6 @@ impl LspAdapter for HaskellLanguageServer {
LanguageServerName("hls".into())
}
fn short_name(&self) -> &'static str {
"hls"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -37,10 +37,6 @@ impl LspAdapter for HtmlLspAdapter {
LanguageServerName("vscode-html-language-server".into())
}
fn short_name(&self) -> &'static str {
"html"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -90,10 +90,6 @@ impl LspAdapter for JsonLspAdapter {
LanguageServerName("json-language-server".into())
}
fn short_name(&self) -> &'static str {
"json"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -122,15 +122,17 @@ pub fn init(
("dart", tree_sitter_dart::language()),
]);
let language = |asset_dir_name: &'static str, adapters| {
let language = |asset_dir_name: &'static str, adapters: Vec<Arc<dyn LspAdapter>>| {
let config = load_config(asset_dir_name);
for adapter in adapters {
languages.register_lsp_adapter(config.name.clone(), adapter);
}
languages.register_language(
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
adapters,
move || Ok((config.clone(), load_queries(asset_dir_name))),
)
);
};
language(
@ -329,15 +331,9 @@ pub fn init(
}
#[cfg(any(test, feature = "test-support"))]
pub async fn language(
name: &str,
grammar: tree_sitter::Language,
lsp_adapter: Option<Arc<dyn LspAdapter>>,
) -> Arc<Language> {
pub fn language(name: &str, grammar: tree_sitter::Language) -> Arc<Language> {
Arc::new(
Language::new(load_config(name), Some(grammar))
.with_lsp_adapters(lsp_adapter.into_iter().collect())
.await
.with_queries(load_queries(name))
.unwrap(),
)

View file

@ -22,10 +22,6 @@ impl super::LspAdapter for LuaLspAdapter {
LanguageServerName("lua-language-server".into())
}
fn short_name(&self) -> &'static str {
"lua"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,

View file

@ -12,10 +12,6 @@ impl LspAdapter for NuLanguageServer {
LanguageServerName("nu".into())
}
fn short_name(&self) -> &'static str {
"nu"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -18,10 +18,6 @@ impl LspAdapter for OCamlLspAdapter {
LanguageServerName("ocamllsp".into())
}
fn short_name(&self) -> &'static str {
"ocaml"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -40,10 +40,6 @@ impl LspAdapter for IntelephenseLspAdapter {
LanguageServerName("intelephense".into())
}
fn short_name(&self) -> &'static str {
"php"
}
async fn fetch_latest_server_version(
&self,
_delegate: &dyn LspAdapterDelegate,

View file

@ -35,10 +35,6 @@ impl LspAdapter for PrismaLspAdapter {
LanguageServerName("prisma-language-server".into())
}
fn short_name(&self) -> &'static str {
"prisma-language-server"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -39,10 +39,6 @@ impl LspAdapter for PurescriptLspAdapter {
LanguageServerName("purescript-language-server".into())
}
fn short_name(&self) -> &'static str {
"purescript"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -34,10 +34,6 @@ impl LspAdapter for PythonLspAdapter {
LanguageServerName("pyright".into())
}
fn short_name(&self) -> &'static str {
"pyright"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
@ -188,7 +184,7 @@ mod tests {
#[gpui::test]
async fn test_python_autoindent(cx: &mut TestAppContext) {
cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
let language = crate::language("python", tree_sitter_python::language(), None).await;
let language = crate::language("python", tree_sitter_python::language());
cx.update(|cx| {
let test_settings = SettingsStore::test(cx);
cx.set_global(test_settings);

View file

@ -12,10 +12,6 @@ impl LspAdapter for RubyLanguageServer {
LanguageServerName("solargraph".into())
}
fn short_name(&self) -> &'static str {
"solargraph"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -23,10 +23,6 @@ impl LspAdapter for RustLspAdapter {
LanguageServerName("rust-analyzer".into())
}
fn short_name(&self) -> &'static str {
"rust"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
@ -360,12 +356,8 @@ mod tests {
#[gpui::test]
async fn test_rust_label_for_completion() {
let language = language(
"rust",
tree_sitter_rust::language(),
Some(Arc::new(RustLspAdapter)),
)
.await;
let adapter = Arc::new(RustLspAdapter);
let language = language("rust", tree_sitter_rust::language());
let grammar = language.grammar().unwrap();
let theme = SyntaxTheme::new_test([
("type", Hsla::default()),
@ -382,13 +374,16 @@ mod tests {
let highlight_field = grammar.highlight_id_for_name("property").unwrap();
assert_eq!(
language
.label_for_completion(&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FUNCTION),
label: "hello(…)".to_string(),
detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
..Default::default()
})
adapter
.label_for_completion(
&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FUNCTION),
label: "hello(…)".to_string(),
detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
..Default::default()
},
&language
)
.await,
Some(CodeLabel {
text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
@ -404,13 +399,16 @@ mod tests {
})
);
assert_eq!(
language
.label_for_completion(&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FUNCTION),
label: "hello(…)".to_string(),
detail: Some("async fn(&mut Option<T>) -> Vec<T>".to_string()),
..Default::default()
})
adapter
.label_for_completion(
&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FUNCTION),
label: "hello(…)".to_string(),
detail: Some("async fn(&mut Option<T>) -> Vec<T>".to_string()),
..Default::default()
},
&language
)
.await,
Some(CodeLabel {
text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
@ -426,13 +424,16 @@ mod tests {
})
);
assert_eq!(
language
.label_for_completion(&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FIELD),
label: "len".to_string(),
detail: Some("usize".to_string()),
..Default::default()
})
adapter
.label_for_completion(
&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FIELD),
label: "len".to_string(),
detail: Some("usize".to_string()),
..Default::default()
},
&language
)
.await,
Some(CodeLabel {
text: "len: usize".to_string(),
@ -442,13 +443,16 @@ mod tests {
);
assert_eq!(
language
.label_for_completion(&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FUNCTION),
label: "hello(…)".to_string(),
detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
..Default::default()
})
adapter
.label_for_completion(
&lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FUNCTION),
label: "hello(…)".to_string(),
detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
..Default::default()
},
&language
)
.await,
Some(CodeLabel {
text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
@ -467,12 +471,8 @@ mod tests {
#[gpui::test]
async fn test_rust_label_for_symbol() {
let language = language(
"rust",
tree_sitter_rust::language(),
Some(Arc::new(RustLspAdapter)),
)
.await;
let adapter = Arc::new(RustLspAdapter);
let language = language("rust", tree_sitter_rust::language());
let grammar = language.grammar().unwrap();
let theme = SyntaxTheme::new_test([
("type", Hsla::default()),
@ -488,8 +488,8 @@ mod tests {
let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
assert_eq!(
language
.label_for_symbol("hello", lsp::SymbolKind::FUNCTION)
adapter
.label_for_symbol("hello", lsp::SymbolKind::FUNCTION, &language)
.await,
Some(CodeLabel {
text: "fn hello".to_string(),
@ -499,8 +499,8 @@ mod tests {
);
assert_eq!(
language
.label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER)
adapter
.label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER, &language)
.await,
Some(CodeLabel {
text: "type World".to_string(),
@ -524,7 +524,7 @@ mod tests {
});
});
let language = crate::language("rust", tree_sitter_rust::language(), None).await;
let language = crate::language("rust", tree_sitter_rust::language());
cx.new_model(|cx| {
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")

View file

@ -36,10 +36,6 @@ impl LspAdapter for SvelteLspAdapter {
LanguageServerName("svelte-language-server".into())
}
fn short_name(&self) -> &'static str {
"svelte"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -38,10 +38,6 @@ impl LspAdapter for TailwindLspAdapter {
LanguageServerName("tailwindcss-language-server".into())
}
fn short_name(&self) -> &'static str {
"tailwind"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -5,7 +5,7 @@ use futures::StreamExt;
pub use language::*;
use lsp::{CodeActionKind, LanguageServerBinary};
use smol::fs::{self, File};
use std::{any::Any, ffi::OsString, path::PathBuf, str};
use std::{any::Any, ffi::OsString, path::PathBuf};
use util::{
async_maybe,
fs::remove_matching,
@ -25,10 +25,6 @@ impl LspAdapter for TerraformLspAdapter {
LanguageServerName("terraform-ls".into())
}
fn short_name(&self) -> &'static str {
"terraform-ls"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,

View file

@ -18,10 +18,6 @@ impl LspAdapter for TaploLspAdapter {
LanguageServerName("taplo-ls".into())
}
fn short_name(&self) -> &'static str {
"taplo-ls"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,

View file

@ -56,10 +56,6 @@ impl LspAdapter for TypeScriptLspAdapter {
LanguageServerName("typescript-language-server".into())
}
fn short_name(&self) -> &'static str {
"tsserver"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
@ -283,10 +279,6 @@ impl LspAdapter for EsLintLspAdapter {
LanguageServerName(Self::SERVER_NAME.into())
}
fn short_name(&self) -> &'static str {
"eslint"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
@ -409,12 +401,7 @@ mod tests {
#[gpui::test]
async fn test_outline(cx: &mut TestAppContext) {
let language = crate::language(
"typescript",
tree_sitter_typescript::language_typescript(),
None,
)
.await;
let language = crate::language("typescript", tree_sitter_typescript::language_typescript());
let text = r#"
function a() {

View file

@ -12,10 +12,6 @@ impl LspAdapter for UiuaLanguageServer {
LanguageServerName("uiua".into())
}
fn short_name(&self) -> &'static str {
"uiua"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -44,10 +44,6 @@ impl super::LspAdapter for VueLspAdapter {
LanguageServerName("vue-language-server".into())
}
fn short_name(&self) -> &'static str {
"vue-language-server"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -39,10 +39,6 @@ impl LspAdapter for YamlLspAdapter {
LanguageServerName("yaml-language-server".into())
}
fn short_name(&self) -> &'static str {
"yaml"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,

View file

@ -3,13 +3,11 @@ use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use futures::{io::BufReader, StreamExt};
use gpui::{AsyncAppContext, Task};
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use smol::fs;
use std::env::consts::{ARCH, OS};
use std::ffi::OsString;
use std::sync::Arc;
use std::{any::Any, path::PathBuf};
use util::async_maybe;
use util::github::latest_github_release;
@ -23,10 +21,6 @@ impl LspAdapter for ZlsAdapter {
LanguageServerName("zls".into())
}
fn short_name(&self) -> &'static str {
"zls"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
@ -47,23 +41,16 @@ impl LspAdapter for ZlsAdapter {
Ok(Box::new(version) as Box<_>)
}
fn check_if_user_installed(
async fn check_if_user_installed(
&self,
delegate: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Option<Task<Option<LanguageServerBinary>>> {
let delegate = delegate.clone();
Some(cx.spawn(|cx| async move {
match cx.update(|cx| delegate.which_command(OsString::from("zls"), cx)) {
Ok(task) => task.await.map(|(path, env)| LanguageServerBinary {
path,
arguments: vec![],
env: Some(env),
}),
Err(_) => None,
}
}))
delegate: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let (path, env) = delegate.which_command(OsString::from("zls")).await?;
Some(LanguageServerBinary {
path,
arguments: vec![],
env: Some(env),
})
}
async fn fetch_server_binary(