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