json.rs

  1use anyhow::{anyhow, Result};
  2use async_trait::async_trait;
  3use collections::HashMap;
  4use feature_flags::FeatureFlagAppExt;
  5use futures::StreamExt;
  6use gpui::AppContext;
  7use language::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate};
  8use lsp::LanguageServerBinary;
  9use node_runtime::NodeRuntime;
 10use serde_json::{json, Value};
 11use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};
 12use smol::fs;
 13use std::{
 14    any::Any,
 15    ffi::OsString,
 16    path::{Path, PathBuf},
 17    sync::{Arc, OnceLock},
 18};
 19use util::{async_maybe, paths, ResultExt};
 20
 21const SERVER_PATH: &str = "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver";
 22
 23fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
 24    vec![server_path.into(), "--stdio".into()]
 25}
 26
 27pub struct JsonLspAdapter {
 28    node: Arc<dyn NodeRuntime>,
 29    languages: Arc<LanguageRegistry>,
 30    workspace_config: OnceLock<Value>,
 31}
 32
 33impl JsonLspAdapter {
 34    pub fn new(node: Arc<dyn NodeRuntime>, languages: Arc<LanguageRegistry>) -> Self {
 35        Self {
 36            node,
 37            languages,
 38            workspace_config: Default::default(),
 39        }
 40    }
 41
 42    fn get_workspace_config(language_names: Vec<String>, cx: &mut AppContext) -> Value {
 43        let action_names = cx.all_action_names();
 44        let staff_mode = cx.is_staff();
 45
 46        let font_names = &cx.text_system().all_font_names();
 47        let settings_schema = cx.global::<SettingsStore>().json_schema(
 48            &SettingsJsonSchemaParams {
 49                language_names: &language_names,
 50                staff_mode,
 51                font_names,
 52            },
 53            cx,
 54        );
 55        let tasks_schema = task::static_source::DefinitionProvider::generate_json_schema();
 56        serde_json::json!({
 57            "json": {
 58                "format": {
 59                    "enable": true,
 60                },
 61                "schemas": [
 62                    {
 63                        "fileMatch": [
 64                            schema_file_match(&paths::SETTINGS),
 65                            &*paths::LOCAL_SETTINGS_RELATIVE_PATH,
 66                        ],
 67                        "schema": settings_schema,
 68                    },
 69                    {
 70                        "fileMatch": [schema_file_match(&paths::KEYMAP)],
 71                        "schema": KeymapFile::generate_json_schema(&action_names),
 72                    },
 73                    {
 74                        "fileMatch": [
 75                            schema_file_match(&paths::TASKS),
 76                            &*paths::LOCAL_TASKS_RELATIVE_PATH,
 77                        ],
 78                        "schema": tasks_schema,
 79                    }
 80                ]
 81            }
 82        })
 83    }
 84}
 85
 86#[async_trait]
 87impl LspAdapter for JsonLspAdapter {
 88    fn name(&self) -> LanguageServerName {
 89        LanguageServerName("json-language-server".into())
 90    }
 91
 92    async fn fetch_latest_server_version(
 93        &self,
 94        _: &dyn LspAdapterDelegate,
 95    ) -> Result<Box<dyn 'static + Send + Any>> {
 96        Ok(Box::new(
 97            self.node
 98                .npm_package_latest_version("vscode-json-languageserver")
 99                .await?,
100        ) as Box<_>)
101    }
102
103    async fn fetch_server_binary(
104        &self,
105        version: Box<dyn 'static + Send + Any>,
106        container_dir: PathBuf,
107        _: &dyn LspAdapterDelegate,
108    ) -> Result<LanguageServerBinary> {
109        let version = version.downcast::<String>().unwrap();
110        let server_path = container_dir.join(SERVER_PATH);
111
112        if fs::metadata(&server_path).await.is_err() {
113            self.node
114                .npm_install_packages(
115                    &container_dir,
116                    &[("vscode-json-languageserver", version.as_str())],
117                )
118                .await?;
119        }
120
121        Ok(LanguageServerBinary {
122            path: self.node.binary_path().await?,
123            env: None,
124            arguments: server_binary_arguments(&server_path),
125        })
126    }
127
128    async fn cached_server_binary(
129        &self,
130        container_dir: PathBuf,
131        _: &dyn LspAdapterDelegate,
132    ) -> Option<LanguageServerBinary> {
133        get_cached_server_binary(container_dir, &*self.node).await
134    }
135
136    async fn installation_test_binary(
137        &self,
138        container_dir: PathBuf,
139    ) -> Option<LanguageServerBinary> {
140        get_cached_server_binary(container_dir, &*self.node).await
141    }
142
143    fn initialization_options(&self) -> Option<serde_json::Value> {
144        Some(json!({
145            "provideFormatter": true
146        }))
147    }
148
149    fn workspace_configuration(&self, _workspace_root: &Path, cx: &mut AppContext) -> Value {
150        self.workspace_config
151            .get_or_init(|| Self::get_workspace_config(self.languages.language_names(), cx))
152            .clone()
153    }
154
155    fn language_ids(&self) -> HashMap<String, String> {
156        [("JSON".into(), "jsonc".into())].into_iter().collect()
157    }
158}
159
160async fn get_cached_server_binary(
161    container_dir: PathBuf,
162    node: &dyn NodeRuntime,
163) -> Option<LanguageServerBinary> {
164    async_maybe!({
165        let mut last_version_dir = None;
166        let mut entries = fs::read_dir(&container_dir).await?;
167        while let Some(entry) = entries.next().await {
168            let entry = entry?;
169            if entry.file_type().await?.is_dir() {
170                last_version_dir = Some(entry.path());
171            }
172        }
173
174        let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
175        let server_path = last_version_dir.join(SERVER_PATH);
176        if server_path.exists() {
177            Ok(LanguageServerBinary {
178                path: node.binary_path().await?,
179                env: None,
180                arguments: server_binary_arguments(&server_path),
181            })
182        } else {
183            Err(anyhow!(
184                "missing executable in directory {:?}",
185                last_version_dir
186            ))
187        }
188    })
189    .await
190    .log_err()
191}
192
193fn schema_file_match(path: &Path) -> &Path {
194    path.strip_prefix(path.parent().unwrap().parent().unwrap())
195        .unwrap()
196}