1use super::installation::{latest_github_release, GitHubLspBinaryVersion};
2use anyhow::{anyhow, Result};
3use async_compression::futures::bufread::GzipDecoder;
4use async_trait::async_trait;
5use client::http::HttpClient;
6use collections::HashMap;
7use futures::{future::BoxFuture, io::BufReader, FutureExt, StreamExt};
8use gpui::MutableAppContext;
9use language::{
10 LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind,
11};
12use serde_json::json;
13use settings::{keymap_file_json_schema, settings_file_json_schema};
14use smol::fs::{self, File};
15use std::{
16 any::Any,
17 env::consts,
18 future,
19 path::{Path, PathBuf},
20 sync::Arc,
21};
22use theme::ThemeRegistry;
23use util::{paths, ResultExt, StaffMode};
24
25fn server_binary_arguments() -> Vec<String> {
26 vec!["--stdio".into()]
27}
28
29pub struct JsonLspAdapter {
30 languages: Arc<LanguageRegistry>,
31 themes: Arc<ThemeRegistry>,
32}
33
34impl JsonLspAdapter {
35 pub fn new(languages: Arc<LanguageRegistry>, themes: Arc<ThemeRegistry>) -> Self {
36 Self { languages, themes }
37 }
38}
39
40#[async_trait]
41impl LspAdapter for JsonLspAdapter {
42 async fn name(&self) -> LanguageServerName {
43 LanguageServerName("json-language-server".into())
44 }
45
46 async fn server_execution_kind(&self) -> ServerExecutionKind {
47 ServerExecutionKind::Node
48 }
49
50 async fn fetch_latest_server_version(
51 &self,
52 http: Arc<dyn HttpClient>,
53 ) -> Result<Box<dyn 'static + Send + Any>> {
54 let release = latest_github_release("zed-industries/json-language-server", http).await?;
55 let asset_name = format!("json-language-server-darwin-{}.gz", consts::ARCH);
56 let asset = release
57 .assets
58 .iter()
59 .find(|asset| asset.name == asset_name)
60 .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
61 let version = GitHubLspBinaryVersion {
62 name: release.name,
63 url: asset.browser_download_url.clone(),
64 };
65 Ok(Box::new(version) as Box<_>)
66 }
67
68 async fn fetch_server_binary(
69 &self,
70 version: Box<dyn 'static + Send + Any>,
71 http: Arc<dyn HttpClient>,
72 container_dir: PathBuf,
73 ) -> Result<LanguageServerBinary> {
74 let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
75 let destination_path = container_dir.join(format!(
76 "json-language-server-{}-{}",
77 version.name,
78 consts::ARCH
79 ));
80
81 if fs::metadata(&destination_path).await.is_err() {
82 let mut response = http
83 .get(&version.url, Default::default(), true)
84 .await
85 .map_err(|err| anyhow!("error downloading release: {}", err))?;
86 let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
87 let mut file = File::create(&destination_path).await?;
88 futures::io::copy(decompressed_bytes, &mut file).await?;
89 fs::set_permissions(
90 &destination_path,
91 <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
92 )
93 .await?;
94
95 if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() {
96 while let Some(entry) = entries.next().await {
97 if let Some(entry) = entry.log_err() {
98 let entry_path = entry.path();
99 if entry_path.as_path() != destination_path {
100 fs::remove_file(&entry_path).await.log_err();
101 }
102 }
103 }
104 }
105 }
106
107 Ok(LanguageServerBinary {
108 path: destination_path,
109 arguments: server_binary_arguments(),
110 })
111 }
112
113 async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
114 (|| async move {
115 let mut last = None;
116 let mut entries = fs::read_dir(&container_dir).await?;
117 while let Some(entry) = entries.next().await {
118 last = Some(entry?.path());
119 }
120 anyhow::Ok(LanguageServerBinary {
121 path: last.ok_or_else(|| anyhow!("no cached binary"))?,
122 arguments: server_binary_arguments(),
123 })
124 })()
125 .await
126 .log_err()
127 }
128
129 async fn initialization_options(&self) -> Option<serde_json::Value> {
130 Some(json!({
131 "provideFormatter": true
132 }))
133 }
134
135 fn workspace_configuration(
136 &self,
137 cx: &mut MutableAppContext,
138 ) -> Option<BoxFuture<'static, serde_json::Value>> {
139 let action_names = cx.all_action_names().collect::<Vec<_>>();
140 let theme_names = self
141 .themes
142 .list(**cx.default_global::<StaffMode>())
143 .map(|meta| meta.name)
144 .collect();
145 let language_names = self.languages.language_names();
146 Some(
147 future::ready(serde_json::json!({
148 "json": {
149 "format": {
150 "enable": true,
151 },
152 "schemas": [
153 {
154 "fileMatch": [schema_file_match(&paths::SETTINGS)],
155 "schema": settings_file_json_schema(theme_names, &language_names),
156 },
157 {
158 "fileMatch": [schema_file_match(&paths::KEYMAP)],
159 "schema": keymap_file_json_schema(&action_names),
160 }
161 ]
162 }
163 }))
164 .boxed(),
165 )
166 }
167
168 async fn language_ids(&self) -> HashMap<String, String> {
169 [("JSON".into(), "jsonc".into())].into_iter().collect()
170 }
171}
172
173fn schema_file_match(path: &Path) -> &Path {
174 path.strip_prefix(path.parent().unwrap().parent().unwrap())
175 .unwrap()
176}