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 fn short_name(&self) -> &'static str {
31 "gleam"
32 }
33
34 async fn fetch_latest_server_version(
35 &self,
36 delegate: &dyn LspAdapterDelegate,
37 ) -> Result<Box<dyn 'static + Send + Any>> {
38 let release =
39 latest_github_release("gleam-lang/gleam", true, false, delegate.http_client()).await?;
40 let asset_name = format!(
41 "gleam-{version}-{arch}-{os}.tar.gz",
42 version = release.tag_name,
43 arch = std::env::consts::ARCH,
44 os = match consts::OS {
45 "macos" => "apple-darwin",
46 "linux" => "unknown-linux-musl",
47 "windows" => "pc-windows-msvc",
48 other => bail!("Running on unsupported os: {other}"),
49 },
50 );
51 let asset = release
52 .assets
53 .iter()
54 .find(|asset| asset.name == asset_name)
55 .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
56 Ok(Box::new(GitHubLspBinaryVersion {
57 name: release.tag_name,
58 url: asset.browser_download_url.clone(),
59 }))
60 }
61
62 async fn fetch_server_binary(
63 &self,
64 version: Box<dyn 'static + Send + Any>,
65 container_dir: PathBuf,
66 delegate: &dyn LspAdapterDelegate,
67 ) -> Result<LanguageServerBinary> {
68 let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
69 let binary_path = container_dir.join("gleam");
70
71 if fs::metadata(&binary_path).await.is_err() {
72 let mut response = delegate
73 .http_client()
74 .get(&version.url, Default::default(), true)
75 .await
76 .map_err(|err| anyhow!("error downloading release: {}", err))?;
77 let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
78 let archive = Archive::new(decompressed_bytes);
79 archive.unpack(container_dir).await?;
80 }
81
82 Ok(LanguageServerBinary {
83 path: binary_path,
84 env: None,
85 arguments: server_binary_arguments(),
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!["--version".into()];
105 binary
106 })
107 }
108}
109
110async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
111 async_maybe!({
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.ok_or_else(|| anyhow!("no cached binary"))?,
120 env: None,
121 arguments: server_binary_arguments(),
122 })
123 })
124 .await
125 .log_err()
126}