json.rs

  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}