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