zed_extension_api: Fork new version (#12160)

This PR forks a new version of the `zed_extension_api` in preparation
for some upcoming changes that require breaking changes to the WIT.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
This commit is contained in:
Marshall Bowers 2024-05-22 19:07:52 -04:00 committed by GitHub
parent 2564d5d648
commit 80bd40cfa3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 886 additions and 321 deletions

View file

@ -1,7 +1,8 @@
mod since_v0_0_1;
mod since_v0_0_4;
mod since_v0_0_6;
use since_v0_0_6 as latest;
mod since_v0_0_7;
use since_v0_0_7 as latest;
use super::{wasm_engine, WasmState};
use anyhow::{Context, Result};
@ -46,6 +47,7 @@ pub fn wasm_api_version_range() -> RangeInclusive<SemanticVersion> {
}
pub enum Extension {
V007(since_v0_0_7::Extension),
V006(since_v0_0_6::Extension),
V004(since_v0_0_4::Extension),
V001(since_v0_0_1::Extension),
@ -62,6 +64,15 @@ impl Extension {
latest::Extension::instantiate_async(store, &component, latest::linker())
.await
.context("failed to instantiate wasm extension")?;
Ok((Self::V007(extension), instance))
} else if version >= since_v0_0_6::MIN_VERSION {
let (extension, instance) = since_v0_0_6::Extension::instantiate_async(
store,
&component,
since_v0_0_6::linker(),
)
.await
.context("failed to instantiate wasm extension")?;
Ok((Self::V006(extension), instance))
} else if version >= since_v0_0_4::MIN_VERSION {
let (extension, instance) = since_v0_0_4::Extension::instantiate_async(
@ -86,6 +97,7 @@ impl Extension {
pub async fn call_init_extension(&self, store: &mut Store<WasmState>) -> Result<()> {
match self {
Extension::V007(ext) => ext.call_init_extension(store).await,
Extension::V006(ext) => ext.call_init_extension(store).await,
Extension::V004(ext) => ext.call_init_extension(store).await,
Extension::V001(ext) => ext.call_init_extension(store).await,
@ -100,10 +112,14 @@ impl Extension {
resource: Resource<Arc<dyn LspAdapterDelegate>>,
) -> Result<Result<Command, String>> {
match self {
Extension::V006(ext) => {
Extension::V007(ext) => {
ext.call_language_server_command(store, &language_server_id.0, resource)
.await
}
Extension::V006(ext) => Ok(ext
.call_language_server_command(store, &language_server_id.0, resource)
.await?
.map(|command| command.into())),
Extension::V004(ext) => Ok(ext
.call_language_server_command(store, config, resource)
.await?
@ -123,6 +139,14 @@ impl Extension {
resource: Resource<Arc<dyn LspAdapterDelegate>>,
) -> Result<Result<Option<String>, String>> {
match self {
Extension::V007(ext) => {
ext.call_language_server_initialization_options(
store,
&language_server_id.0,
resource,
)
.await
}
Extension::V006(ext) => {
ext.call_language_server_initialization_options(
store,
@ -153,6 +177,14 @@ impl Extension {
resource: Resource<Arc<dyn LspAdapterDelegate>>,
) -> Result<Result<Option<String>, String>> {
match self {
Extension::V007(ext) => {
ext.call_language_server_workspace_configuration(
store,
&language_server_id.0,
resource,
)
.await
}
Extension::V006(ext) => {
ext.call_language_server_workspace_configuration(
store,
@ -172,11 +204,20 @@ impl Extension {
completions: Vec<latest::Completion>,
) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
match self {
Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
Extension::V006(ext) => {
Extension::V007(ext) => {
ext.call_labels_for_completions(store, &language_server_id.0, &completions)
.await
}
Extension::V006(ext) => Ok(ext
.call_labels_for_completions(store, &language_server_id.0, &completions)
.await?
.map(|labels| {
labels
.into_iter()
.map(|label| label.map(Into::into))
.collect()
})),
Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
}
}
@ -187,11 +228,20 @@ impl Extension {
symbols: Vec<latest::Symbol>,
) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
match self {
Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
Extension::V006(ext) => {
Extension::V007(ext) => {
ext.call_labels_for_symbols(store, &language_server_id.0, &symbols)
.await
}
Extension::V006(ext) => Ok(ext
.call_labels_for_symbols(store, &language_server_id.0, &symbols)
.await?
.map(|labels| {
labels
.into_iter()
.map(|label| label.map(Into::into))
.collect()
})),
Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
}
}
}

View file

@ -1,36 +1,26 @@
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
use ::settings::Settings;
use anyhow::{anyhow, bail, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use super::latest;
use crate::wasm_host::WasmState;
use anyhow::Result;
use async_trait::async_trait;
use futures::{io::BufReader, FutureExt as _};
use language::{
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
};
use project::project_settings::ProjectSettings;
use language::LspAdapterDelegate;
use semantic_version::SemanticVersion;
use std::{
env,
path::{Path, PathBuf},
sync::{Arc, OnceLock},
};
use util::maybe;
use std::sync::{Arc, OnceLock};
use wasmtime::component::{Linker, Resource};
pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 0, 6);
pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 0, 6);
wasmtime::component::bindgen!({
async: true,
path: "../extension_api/wit/since_v0.0.6",
with: {
"worktree": ExtensionWorktree,
"zed:extension/github": latest::zed::extension::github,
"zed:extension/lsp": latest::zed::extension::lsp,
"zed:extension/nodejs": latest::zed::extension::nodejs,
"zed:extension/platform": latest::zed::extension::platform,
},
});
pub use self::zed::extension::*;
mod settings {
include!("../../../../extension_api/wit/since_v0.0.6/settings.rs");
}
@ -39,7 +29,93 @@ pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
pub fn linker() -> &'static Linker<WasmState> {
static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
LINKER.get_or_init(|| {
super::new_linker(|linker, f| {
Extension::add_to_linker(linker, f)?;
latest::zed::extension::github::add_to_linker(linker, f)?;
latest::zed::extension::nodejs::add_to_linker(linker, f)?;
latest::zed::extension::platform::add_to_linker(linker, f)?;
Ok(())
})
})
}
impl From<Command> for latest::Command {
fn from(value: Command) -> Self {
Self {
command: value.command,
args: value.args,
env: value.env,
}
}
}
impl From<SettingsLocation> for latest::SettingsLocation {
fn from(value: SettingsLocation) -> Self {
Self {
worktree_id: value.worktree_id,
path: value.path,
}
}
}
impl From<LanguageServerInstallationStatus> for latest::LanguageServerInstallationStatus {
fn from(value: LanguageServerInstallationStatus) -> Self {
match value {
LanguageServerInstallationStatus::None => Self::None,
LanguageServerInstallationStatus::Downloading => Self::Downloading,
LanguageServerInstallationStatus::CheckingForUpdate => Self::CheckingForUpdate,
LanguageServerInstallationStatus::Failed(message) => Self::Failed(message),
}
}
}
impl From<DownloadedFileType> for latest::DownloadedFileType {
fn from(value: DownloadedFileType) -> Self {
match value {
DownloadedFileType::Gzip => Self::Gzip,
DownloadedFileType::GzipTar => Self::GzipTar,
DownloadedFileType::Zip => Self::Zip,
DownloadedFileType::Uncompressed => Self::Uncompressed,
}
}
}
impl From<Range> for latest::Range {
fn from(value: Range) -> Self {
Self {
start: value.start,
end: value.end,
}
}
}
impl From<CodeLabelSpan> for latest::CodeLabelSpan {
fn from(value: CodeLabelSpan) -> Self {
match value {
CodeLabelSpan::CodeRange(range) => Self::CodeRange(range.into()),
CodeLabelSpan::Literal(literal) => Self::Literal(literal.into()),
}
}
}
impl From<CodeLabelSpanLiteral> for latest::CodeLabelSpanLiteral {
fn from(value: CodeLabelSpanLiteral) -> Self {
Self {
text: value.text,
highlight_name: value.highlight_name,
}
}
}
impl From<CodeLabel> for latest::CodeLabel {
fn from(value: CodeLabel) -> Self {
Self {
code: value.code,
spans: value.spans.into_iter().map(Into::into).collect(),
filter_range: value.filter_range.into(),
}
}
}
#[async_trait]
@ -48,16 +124,14 @@ impl HostWorktree for WasmState {
&mut self,
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
) -> wasmtime::Result<u64> {
let delegate = self.table.get(&delegate)?;
Ok(delegate.worktree_id())
latest::HostWorktree::id(self, delegate).await
}
async fn root_path(
&mut self,
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
) -> wasmtime::Result<String> {
let delegate = self.table.get(&delegate)?;
Ok(delegate.worktree_root_path().to_string_lossy().to_string())
latest::HostWorktree::root_path(self, delegate).await
}
async fn read_text_file(
@ -65,19 +139,14 @@ impl HostWorktree for WasmState {
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
path: String,
) -> wasmtime::Result<Result<String, String>> {
let delegate = self.table.get(&delegate)?;
Ok(delegate
.read_text_file(path.into())
.await
.map_err(|error| error.to_string()))
latest::HostWorktree::read_text_file(self, delegate, path).await
}
async fn shell_env(
&mut self,
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
) -> wasmtime::Result<EnvVars> {
let delegate = self.table.get(&delegate)?;
Ok(delegate.shell_env().await.into_iter().collect())
latest::HostWorktree::shell_env(self, delegate).await
}
async fn which(
@ -85,11 +154,7 @@ impl HostWorktree for WasmState {
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
binary_name: String,
) -> wasmtime::Result<Option<String>> {
let delegate = self.table.get(&delegate)?;
Ok(delegate
.which(binary_name.as_ref())
.await
.map(|path| path.to_string_lossy().to_string()))
latest::HostWorktree::which(self, delegate, binary_name).await
}
fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
@ -98,107 +163,6 @@ impl HostWorktree for WasmState {
}
}
#[async_trait]
impl nodejs::Host for WasmState {
async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
self.host
.node_runtime
.binary_path()
.await
.map(|path| path.to_string_lossy().to_string())
.to_wasmtime_result()
}
async fn npm_package_latest_version(
&mut self,
package_name: String,
) -> wasmtime::Result<Result<String, String>> {
self.host
.node_runtime
.npm_package_latest_version(&package_name)
.await
.to_wasmtime_result()
}
async fn npm_package_installed_version(
&mut self,
package_name: String,
) -> wasmtime::Result<Result<Option<String>, String>> {
self.host
.node_runtime
.npm_package_installed_version(&self.work_dir(), &package_name)
.await
.to_wasmtime_result()
}
async fn npm_install_package(
&mut self,
package_name: String,
version: String,
) -> wasmtime::Result<Result<(), String>> {
self.host
.node_runtime
.npm_install_packages(&self.work_dir(), &[(&package_name, &version)])
.await
.to_wasmtime_result()
}
}
#[async_trait]
impl lsp::Host for WasmState {}
#[async_trait]
impl github::Host for WasmState {
async fn latest_github_release(
&mut self,
repo: String,
options: github::GithubReleaseOptions,
) -> wasmtime::Result<Result<github::GithubRelease, String>> {
maybe!(async {
let release = http::github::latest_github_release(
&repo,
options.require_assets,
options.pre_release,
self.host.http_client.clone(),
)
.await?;
Ok(github::GithubRelease {
version: release.tag_name,
assets: release
.assets
.into_iter()
.map(|asset| github::GithubReleaseAsset {
name: asset.name,
download_url: asset.browser_download_url,
})
.collect(),
})
})
.await
.to_wasmtime_result()
}
}
#[async_trait]
impl platform::Host for WasmState {
async fn current_platform(&mut self) -> Result<(platform::Os, platform::Architecture)> {
Ok((
match env::consts::OS {
"macos" => platform::Os::Mac,
"linux" => platform::Os::Linux,
"windows" => platform::Os::Windows,
_ => panic!("unsupported os"),
},
match env::consts::ARCH {
"aarch64" => platform::Architecture::Aarch64,
"x86" => platform::Architecture::X86,
"x86_64" => platform::Architecture::X8664,
_ => panic!("unsupported architecture"),
},
))
}
}
#[async_trait]
impl ExtensionImports for WasmState {
async fn get_settings(
@ -207,50 +171,13 @@ impl ExtensionImports for WasmState {
category: String,
key: Option<String>,
) -> wasmtime::Result<Result<String, String>> {
self.on_main_thread(|cx| {
async move {
let location = location
.as_ref()
.map(|location| ::settings::SettingsLocation {
worktree_id: location.worktree_id as usize,
path: Path::new(&location.path),
});
cx.update(|cx| match category.as_str() {
"language" => {
let settings =
AllLanguageSettings::get(location, cx).language(key.as_deref());
Ok(serde_json::to_string(&settings::LanguageSettings {
tab_size: settings.tab_size,
})?)
}
"lsp" => {
let settings = key
.and_then(|key| {
ProjectSettings::get(location, cx)
.lsp
.get(&Arc::<str>::from(key))
})
.cloned()
.unwrap_or_default();
Ok(serde_json::to_string(&settings::LspSettings {
binary: settings.binary.map(|binary| settings::BinarySettings {
path: binary.path,
arguments: binary.arguments,
}),
settings: settings.settings,
initialization_options: settings.initialization_options,
})?)
}
_ => {
bail!("Unknown settings category: {}", category);
}
})
}
.boxed_local()
})
.await?
.to_wasmtime_result()
latest::ExtensionImports::get_settings(
self,
location.map(|location| location.into()),
category,
key,
)
.await
}
async fn set_language_server_installation_status(
@ -258,23 +185,12 @@ impl ExtensionImports for WasmState {
server_name: String,
status: LanguageServerInstallationStatus,
) -> wasmtime::Result<()> {
let status = match status {
LanguageServerInstallationStatus::CheckingForUpdate => {
LanguageServerBinaryStatus::CheckingForUpdate
}
LanguageServerInstallationStatus::Downloading => {
LanguageServerBinaryStatus::Downloading
}
LanguageServerInstallationStatus::None => LanguageServerBinaryStatus::None,
LanguageServerInstallationStatus::Failed(error) => {
LanguageServerBinaryStatus::Failed { error }
}
};
self.host
.language_registry
.update_lsp_status(language::LanguageServerName(server_name.into()), status);
Ok(())
latest::ExtensionImports::set_language_server_installation_status(
self,
server_name,
status.into(),
)
.await
}
async fn download_file(
@ -283,103 +199,10 @@ impl ExtensionImports for WasmState {
path: String,
file_type: DownloadedFileType,
) -> wasmtime::Result<Result<(), String>> {
maybe!(async {
let path = PathBuf::from(path);
let extension_work_dir = self.host.work_dir.join(self.manifest.id.as_ref());
self.host.fs.create_dir(&extension_work_dir).await?;
let destination_path = self
.host
.writeable_path_from_extension(&self.manifest.id, &path)?;
let mut response = self
.host
.http_client
.get(&url, Default::default(), true)
.await
.map_err(|err| anyhow!("error downloading release: {}", err))?;
if !response.status().is_success() {
Err(anyhow!(
"download failed with status {}",
response.status().to_string()
))?;
}
let body = BufReader::new(response.body_mut());
match file_type {
DownloadedFileType::Uncompressed => {
futures::pin_mut!(body);
self.host
.fs
.create_file_with(&destination_path, body)
.await?;
}
DownloadedFileType::Gzip => {
let body = GzipDecoder::new(body);
futures::pin_mut!(body);
self.host
.fs
.create_file_with(&destination_path, body)
.await?;
}
DownloadedFileType::GzipTar => {
let body = GzipDecoder::new(body);
futures::pin_mut!(body);
self.host
.fs
.extract_tar_file(&destination_path, Archive::new(body))
.await?;
}
DownloadedFileType::Zip => {
let file_name = destination_path
.file_name()
.ok_or_else(|| anyhow!("invalid download path"))?
.to_string_lossy();
let zip_filename = format!("{file_name}.zip");
let mut zip_path = destination_path.clone();
zip_path.set_file_name(zip_filename);
futures::pin_mut!(body);
self.host.fs.create_file_with(&zip_path, body).await?;
let unzip_status = std::process::Command::new("unzip")
.current_dir(&extension_work_dir)
.arg("-d")
.arg(&destination_path)
.arg(&zip_path)
.output()?
.status;
if !unzip_status.success() {
Err(anyhow!("failed to unzip {} archive", path.display()))?;
}
}
}
Ok(())
})
.await
.to_wasmtime_result()
latest::ExtensionImports::download_file(self, url, path, file_type.into()).await
}
async fn make_file_executable(&mut self, path: String) -> wasmtime::Result<Result<(), String>> {
#[allow(unused)]
let path = self
.host
.writeable_path_from_extension(&self.manifest.id, Path::new(&path))?;
#[cfg(unix)]
{
use std::fs::{self, Permissions};
use std::os::unix::fs::PermissionsExt;
return fs::set_permissions(&path, Permissions::from_mode(0o755))
.map_err(|error| anyhow!("failed to set permissions for path {path:?}: {error}"))
.to_wasmtime_result();
}
#[cfg(not(unix))]
Ok(Ok(()))
latest::ExtensionImports::make_file_executable(self, path).await
}
}

View file

@ -0,0 +1,385 @@
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
use ::settings::Settings;
use anyhow::{anyhow, bail, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use futures::{io::BufReader, FutureExt as _};
use language::{
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
};
use project::project_settings::ProjectSettings;
use semantic_version::SemanticVersion;
use std::{
env,
path::{Path, PathBuf},
sync::{Arc, OnceLock},
};
use util::maybe;
use wasmtime::component::{Linker, Resource};
pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 0, 7);
pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 0, 7);
wasmtime::component::bindgen!({
async: true,
path: "../extension_api/wit/since_v0.0.7",
with: {
"worktree": ExtensionWorktree,
},
});
pub use self::zed::extension::*;
mod settings {
include!("../../../../extension_api/wit/since_v0.0.7/settings.rs");
}
pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
pub fn linker() -> &'static Linker<WasmState> {
static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
}
#[async_trait]
impl HostWorktree for WasmState {
async fn id(
&mut self,
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
) -> wasmtime::Result<u64> {
let delegate = self.table.get(&delegate)?;
Ok(delegate.worktree_id())
}
async fn root_path(
&mut self,
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
) -> wasmtime::Result<String> {
let delegate = self.table.get(&delegate)?;
Ok(delegate.worktree_root_path().to_string_lossy().to_string())
}
async fn read_text_file(
&mut self,
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
path: String,
) -> wasmtime::Result<Result<String, String>> {
let delegate = self.table.get(&delegate)?;
Ok(delegate
.read_text_file(path.into())
.await
.map_err(|error| error.to_string()))
}
async fn shell_env(
&mut self,
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
) -> wasmtime::Result<EnvVars> {
let delegate = self.table.get(&delegate)?;
Ok(delegate.shell_env().await.into_iter().collect())
}
async fn which(
&mut self,
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
binary_name: String,
) -> wasmtime::Result<Option<String>> {
let delegate = self.table.get(&delegate)?;
Ok(delegate
.which(binary_name.as_ref())
.await
.map(|path| path.to_string_lossy().to_string()))
}
fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
// We only ever hand out borrows of worktrees.
Ok(())
}
}
#[async_trait]
impl nodejs::Host for WasmState {
async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
self.host
.node_runtime
.binary_path()
.await
.map(|path| path.to_string_lossy().to_string())
.to_wasmtime_result()
}
async fn npm_package_latest_version(
&mut self,
package_name: String,
) -> wasmtime::Result<Result<String, String>> {
self.host
.node_runtime
.npm_package_latest_version(&package_name)
.await
.to_wasmtime_result()
}
async fn npm_package_installed_version(
&mut self,
package_name: String,
) -> wasmtime::Result<Result<Option<String>, String>> {
self.host
.node_runtime
.npm_package_installed_version(&self.work_dir(), &package_name)
.await
.to_wasmtime_result()
}
async fn npm_install_package(
&mut self,
package_name: String,
version: String,
) -> wasmtime::Result<Result<(), String>> {
self.host
.node_runtime
.npm_install_packages(&self.work_dir(), &[(&package_name, &version)])
.await
.to_wasmtime_result()
}
}
#[async_trait]
impl lsp::Host for WasmState {}
#[async_trait]
impl github::Host for WasmState {
async fn latest_github_release(
&mut self,
repo: String,
options: github::GithubReleaseOptions,
) -> wasmtime::Result<Result<github::GithubRelease, String>> {
maybe!(async {
let release = http::github::latest_github_release(
&repo,
options.require_assets,
options.pre_release,
self.host.http_client.clone(),
)
.await?;
Ok(github::GithubRelease {
version: release.tag_name,
assets: release
.assets
.into_iter()
.map(|asset| github::GithubReleaseAsset {
name: asset.name,
download_url: asset.browser_download_url,
})
.collect(),
})
})
.await
.to_wasmtime_result()
}
}
#[async_trait]
impl platform::Host for WasmState {
async fn current_platform(&mut self) -> Result<(platform::Os, platform::Architecture)> {
Ok((
match env::consts::OS {
"macos" => platform::Os::Mac,
"linux" => platform::Os::Linux,
"windows" => platform::Os::Windows,
_ => panic!("unsupported os"),
},
match env::consts::ARCH {
"aarch64" => platform::Architecture::Aarch64,
"x86" => platform::Architecture::X86,
"x86_64" => platform::Architecture::X8664,
_ => panic!("unsupported architecture"),
},
))
}
}
#[async_trait]
impl ExtensionImports for WasmState {
async fn get_settings(
&mut self,
location: Option<self::SettingsLocation>,
category: String,
key: Option<String>,
) -> wasmtime::Result<Result<String, String>> {
self.on_main_thread(|cx| {
async move {
let location = location
.as_ref()
.map(|location| ::settings::SettingsLocation {
worktree_id: location.worktree_id as usize,
path: Path::new(&location.path),
});
cx.update(|cx| match category.as_str() {
"language" => {
let settings =
AllLanguageSettings::get(location, cx).language(key.as_deref());
Ok(serde_json::to_string(&settings::LanguageSettings {
tab_size: settings.tab_size,
})?)
}
"lsp" => {
let settings = key
.and_then(|key| {
ProjectSettings::get(location, cx)
.lsp
.get(&Arc::<str>::from(key))
})
.cloned()
.unwrap_or_default();
Ok(serde_json::to_string(&settings::LspSettings {
binary: settings.binary.map(|binary| settings::BinarySettings {
path: binary.path,
arguments: binary.arguments,
}),
settings: settings.settings,
initialization_options: settings.initialization_options,
})?)
}
_ => {
bail!("Unknown settings category: {}", category);
}
})
}
.boxed_local()
})
.await?
.to_wasmtime_result()
}
async fn set_language_server_installation_status(
&mut self,
server_name: String,
status: LanguageServerInstallationStatus,
) -> wasmtime::Result<()> {
let status = match status {
LanguageServerInstallationStatus::CheckingForUpdate => {
LanguageServerBinaryStatus::CheckingForUpdate
}
LanguageServerInstallationStatus::Downloading => {
LanguageServerBinaryStatus::Downloading
}
LanguageServerInstallationStatus::None => LanguageServerBinaryStatus::None,
LanguageServerInstallationStatus::Failed(error) => {
LanguageServerBinaryStatus::Failed { error }
}
};
self.host
.language_registry
.update_lsp_status(language::LanguageServerName(server_name.into()), status);
Ok(())
}
async fn download_file(
&mut self,
url: String,
path: String,
file_type: DownloadedFileType,
) -> wasmtime::Result<Result<(), String>> {
maybe!(async {
let path = PathBuf::from(path);
let extension_work_dir = self.host.work_dir.join(self.manifest.id.as_ref());
self.host.fs.create_dir(&extension_work_dir).await?;
let destination_path = self
.host
.writeable_path_from_extension(&self.manifest.id, &path)?;
let mut response = self
.host
.http_client
.get(&url, Default::default(), true)
.await
.map_err(|err| anyhow!("error downloading release: {}", err))?;
if !response.status().is_success() {
Err(anyhow!(
"download failed with status {}",
response.status().to_string()
))?;
}
let body = BufReader::new(response.body_mut());
match file_type {
DownloadedFileType::Uncompressed => {
futures::pin_mut!(body);
self.host
.fs
.create_file_with(&destination_path, body)
.await?;
}
DownloadedFileType::Gzip => {
let body = GzipDecoder::new(body);
futures::pin_mut!(body);
self.host
.fs
.create_file_with(&destination_path, body)
.await?;
}
DownloadedFileType::GzipTar => {
let body = GzipDecoder::new(body);
futures::pin_mut!(body);
self.host
.fs
.extract_tar_file(&destination_path, Archive::new(body))
.await?;
}
DownloadedFileType::Zip => {
let file_name = destination_path
.file_name()
.ok_or_else(|| anyhow!("invalid download path"))?
.to_string_lossy();
let zip_filename = format!("{file_name}.zip");
let mut zip_path = destination_path.clone();
zip_path.set_file_name(zip_filename);
futures::pin_mut!(body);
self.host.fs.create_file_with(&zip_path, body).await?;
let unzip_status = std::process::Command::new("unzip")
.current_dir(&extension_work_dir)
.arg("-d")
.arg(&destination_path)
.arg(&zip_path)
.output()?
.status;
if !unzip_status.success() {
Err(anyhow!("failed to unzip {} archive", path.display()))?;
}
}
}
Ok(())
})
.await
.to_wasmtime_result()
}
async fn make_file_executable(&mut self, path: String) -> wasmtime::Result<Result<(), String>> {
#[allow(unused)]
let path = self
.host
.writeable_path_from_extension(&self.manifest.id, Path::new(&path))?;
#[cfg(unix)]
{
use std::fs::{self, Permissions};
use std::os::unix::fs::PermissionsExt;
return fs::set_permissions(&path, Permissions::from_mode(0o755))
.map_err(|error| anyhow!("failed to set permissions for path {path:?}: {error}"))
.to_wasmtime_result();
}
#[cfg(not(unix))]
Ok(Ok(()))
}
}