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

@ -0,0 +1,90 @@
use crate::wasm_host::{wit::LanguageServerConfig, WasmExtension};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use futures::{Future, FutureExt};
use gpui::AsyncAppContext;
use language::{Language, LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use std::{
any::Any,
path::{Path, PathBuf},
pin::Pin,
sync::Arc,
};
use wasmtime_wasi::preview2::WasiView as _;
pub struct ExtensionLspAdapter {
pub(crate) extension: WasmExtension,
pub(crate) config: LanguageServerConfig,
pub(crate) work_dir: PathBuf,
}
#[async_trait]
impl LspAdapter for ExtensionLspAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName(self.config.name.clone().into())
}
fn get_language_server_command<'a>(
self: Arc<Self>,
_: Arc<Language>,
_: Arc<Path>,
delegate: Arc<dyn LspAdapterDelegate>,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
_: &'a mut AsyncAppContext,
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
async move {
let command = self
.extension
.call({
let this = self.clone();
|extension, store| {
async move {
let resource = store.data_mut().table().push(delegate)?;
extension
.call_language_server_command(store, &this.config, resource)
.await
}
.boxed()
}
})
.await?
.map_err(|e| anyhow!("{}", e))?;
Ok(LanguageServerBinary {
path: self.work_dir.join(&command.command).into(),
arguments: command.args.into_iter().map(|arg| arg.into()).collect(),
env: Some(command.env.into_iter().collect()),
})
}
.boxed_local()
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
unreachable!("get_language_server_command is overridden")
}
async fn fetch_server_binary(
&self,
_: Box<dyn 'static + Send + Any>,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
unreachable!("get_language_server_command is overridden")
}
async fn cached_server_binary(
&self,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
unreachable!("get_language_server_command is overridden")
}
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
None
}
}