1use anyhow::{bail, Context, Result};
2use async_compression::futures::bufread::GzipDecoder;
3use async_trait::async_trait;
4use futures::{io::BufReader, StreamExt};
5use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
6use lsp::LanguageServerBinary;
7use smol::fs::{self, File};
8use std::{any::Any, path::PathBuf};
9use util::github::latest_github_release;
10use util::maybe;
11use util::{github::GitHubLspBinaryVersion, ResultExt};
12
13pub struct TaploLspAdapter;
14
15#[async_trait(?Send)]
16impl LspAdapter for TaploLspAdapter {
17 fn name(&self) -> LanguageServerName {
18 LanguageServerName("taplo-ls".into())
19 }
20
21 async fn fetch_latest_server_version(
22 &self,
23 delegate: &dyn LspAdapterDelegate,
24 ) -> Result<Box<dyn 'static + Send + Any>> {
25 let release =
26 latest_github_release("tamasfe/taplo", true, false, delegate.http_client()).await?;
27 let asset_name = format!(
28 "taplo-full-{os}-{arch}.gz",
29 os = match std::env::consts::OS {
30 "macos" => "darwin",
31 "linux" => "linux",
32 "windows" => "windows",
33 other => bail!("Running on unsupported os: {other}"),
34 },
35 arch = std::env::consts::ARCH
36 );
37
38 let asset = release
39 .assets
40 .iter()
41 .find(|asset| asset.name == asset_name)
42 .context(format!("no asset found matching {asset_name:?}"))?;
43
44 Ok(Box::new(GitHubLspBinaryVersion {
45 name: release.tag_name,
46 url: asset.browser_download_url.clone(),
47 }))
48 }
49
50 async fn fetch_server_binary(
51 &self,
52 version: Box<dyn 'static + Send + Any>,
53 container_dir: PathBuf,
54 delegate: &dyn LspAdapterDelegate,
55 ) -> Result<LanguageServerBinary> {
56 let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
57 let binary_path = container_dir.join("taplo");
58
59 if fs::metadata(&binary_path).await.is_err() {
60 let mut response = delegate
61 .http_client()
62 .get(&version.url, Default::default(), true)
63 .await
64 .context("error downloading release")?;
65
66 let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
67 let mut file = File::create(&binary_path).await?;
68
69 futures::io::copy(decompressed_bytes, &mut file).await?;
70
71 // todo("windows")
72 #[cfg(not(windows))]
73 {
74 fs::set_permissions(
75 &binary_path,
76 <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
77 )
78 .await?;
79 }
80 }
81
82 Ok(LanguageServerBinary {
83 path: binary_path,
84 env: None,
85 arguments: vec!["lsp".into(), "stdio".into()],
86 })
87 }
88
89 async fn cached_server_binary(
90 &self,
91 container_dir: PathBuf,
92 _: &dyn LspAdapterDelegate,
93 ) -> Option<LanguageServerBinary> {
94 get_cached_server_binary(container_dir).await
95 }
96
97 async fn installation_test_binary(
98 &self,
99 container_dir: PathBuf,
100 ) -> Option<LanguageServerBinary> {
101 get_cached_server_binary(container_dir)
102 .await
103 .map(|mut binary| {
104 binary.arguments = vec!["--help".into()];
105 binary
106 })
107 }
108}
109
110async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
111 maybe!(async {
112 let mut last = None;
113 let mut entries = fs::read_dir(&container_dir).await?;
114 while let Some(entry) = entries.next().await {
115 last = Some(entry?.path());
116 }
117
118 anyhow::Ok(LanguageServerBinary {
119 path: last.context("no cached binary")?,
120 env: None,
121 arguments: Default::default(),
122 })
123 })
124 .await
125 .log_err()
126}