1use anyhow::{anyhow, Context, Result};
2use async_compression::futures::bufread::GzipDecoder;
3use async_tar::Archive;
4use async_trait::async_trait;
5use futures::{io::BufReader, StreamExt};
6use language::{LanguageServerName, LspAdapterDelegate};
7use lsp::LanguageServerBinary;
8use smol::fs;
9use std::env::consts::ARCH;
10use std::ffi::OsString;
11use std::{any::Any, path::PathBuf};
12use util::{github::latest_github_release, maybe};
13use util::{github::GitHubLspBinaryVersion, ResultExt};
14
15pub struct OmniSharpAdapter;
16
17#[async_trait(?Send)]
18impl super::LspAdapter for OmniSharpAdapter {
19 fn name(&self) -> LanguageServerName {
20 LanguageServerName("OmniSharp".into())
21 }
22
23 async fn fetch_latest_server_version(
24 &self,
25 delegate: &dyn LspAdapterDelegate,
26 ) -> Result<Box<dyn 'static + Send + Any>> {
27 let mapped_arch = match ARCH {
28 "aarch64" => Some("arm64"),
29 "x86_64" => Some("x64"),
30 _ => None,
31 };
32
33 match mapped_arch {
34 None => Ok(Box::new(())),
35 Some(arch) => {
36 let release = latest_github_release(
37 "OmniSharp/omnisharp-roslyn",
38 true,
39 false,
40 delegate.http_client(),
41 )
42 .await?;
43 let asset_name = format!("omnisharp-osx-{}-net6.0.tar.gz", arch);
44 let asset = release
45 .assets
46 .iter()
47 .find(|asset| asset.name == asset_name)
48 .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
49 let version = GitHubLspBinaryVersion {
50 name: release.tag_name,
51 url: asset.browser_download_url.clone(),
52 };
53
54 Ok(Box::new(version) as Box<_>)
55 }
56 }
57 }
58
59 async fn fetch_server_binary(
60 &self,
61 version: Box<dyn 'static + Send + Any>,
62 container_dir: PathBuf,
63 delegate: &dyn LspAdapterDelegate,
64 ) -> Result<LanguageServerBinary> {
65 let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
66 let binary_path = container_dir.join("omnisharp");
67
68 if fs::metadata(&binary_path).await.is_err() {
69 let mut response = delegate
70 .http_client()
71 .get(&version.url, Default::default(), true)
72 .await
73 .context("error downloading release")?;
74 let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
75 let archive = Archive::new(decompressed_bytes);
76 archive.unpack(container_dir).await?;
77 }
78
79 // todo("windows")
80 #[cfg(not(windows))]
81 {
82 fs::set_permissions(
83 &binary_path,
84 <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
85 )
86 .await?;
87 }
88 Ok(LanguageServerBinary {
89 path: binary_path,
90 env: None,
91 arguments: server_binary_arguments(),
92 })
93 }
94
95 async fn cached_server_binary(
96 &self,
97 container_dir: PathBuf,
98 _: &dyn LspAdapterDelegate,
99 ) -> Option<LanguageServerBinary> {
100 get_cached_server_binary(container_dir).await
101 }
102
103 async fn installation_test_binary(
104 &self,
105 container_dir: PathBuf,
106 ) -> Option<LanguageServerBinary> {
107 get_cached_server_binary(container_dir)
108 .await
109 .map(|mut binary| {
110 binary.arguments = vec!["--help".into()];
111 binary
112 })
113 }
114}
115
116async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
117 maybe!(async {
118 let mut last_binary_path = None;
119 let mut entries = fs::read_dir(&container_dir).await?;
120 while let Some(entry) = entries.next().await {
121 let entry = entry?;
122 if entry.file_type().await?.is_file()
123 && entry
124 .file_name()
125 .to_str()
126 .map_or(false, |name| name == "omnisharp")
127 {
128 last_binary_path = Some(entry.path());
129 }
130 }
131
132 if let Some(path) = last_binary_path {
133 Ok(LanguageServerBinary {
134 path,
135 env: None,
136 arguments: server_binary_arguments(),
137 })
138 } else {
139 Err(anyhow!("no cached binary"))
140 }
141 })
142 .await
143 .log_err()
144}
145
146fn server_binary_arguments() -> Vec<OsString> {
147 vec!["-lsp".into()]
148}