json.rs

  1use anyhow::{anyhow, Result};
  2use async_trait::async_trait;
  3use collections::HashMap;
  4use feature_flags::FeatureFlagAppExt;
  5use futures::StreamExt;
  6use gpui::{AppContext, AsyncAppContext};
  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::{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::TaskTemplates::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(?Send)]
 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        latest_version: Box<dyn 'static + Send + Any>,
106        container_dir: PathBuf,
107        _: &dyn LspAdapterDelegate,
108    ) -> Result<LanguageServerBinary> {
109        let latest_version = latest_version.downcast::<String>().unwrap();
110        let server_path = container_dir.join(SERVER_PATH);
111        let package_name = "vscode-json-languageserver";
112
113        let should_install_language_server = self
114            .node
115            .should_install_npm_package(package_name, &server_path, &container_dir, &latest_version)
116            .await;
117
118        if should_install_language_server {
119            self.node
120                .npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())])
121                .await?;
122        }
123
124        Ok(LanguageServerBinary {
125            path: self.node.binary_path().await?,
126            env: None,
127            arguments: server_binary_arguments(&server_path),
128        })
129    }
130
131    async fn cached_server_binary(
132        &self,
133        container_dir: PathBuf,
134        _: &dyn LspAdapterDelegate,
135    ) -> Option<LanguageServerBinary> {
136        get_cached_server_binary(container_dir, &*self.node).await
137    }
138
139    async fn installation_test_binary(
140        &self,
141        container_dir: PathBuf,
142    ) -> Option<LanguageServerBinary> {
143        get_cached_server_binary(container_dir, &*self.node).await
144    }
145
146    async fn initialization_options(
147        self: Arc<Self>,
148        _: &Arc<dyn LspAdapterDelegate>,
149    ) -> Result<Option<serde_json::Value>> {
150        Ok(Some(json!({
151            "provideFormatter": true
152        })))
153    }
154
155    async fn workspace_configuration(
156        self: Arc<Self>,
157        _: &Arc<dyn LspAdapterDelegate>,
158        cx: &mut AsyncAppContext,
159    ) -> Result<Value> {
160        cx.update(|cx| {
161            self.workspace_config
162                .get_or_init(|| Self::get_workspace_config(self.languages.language_names(), cx))
163                .clone()
164        })
165    }
166
167    fn language_ids(&self) -> HashMap<String, String> {
168        [("JSON".into(), "jsonc".into())].into_iter().collect()
169    }
170}
171
172async fn get_cached_server_binary(
173    container_dir: PathBuf,
174    node: &dyn NodeRuntime,
175) -> Option<LanguageServerBinary> {
176    maybe!(async {
177        let mut last_version_dir = None;
178        let mut entries = fs::read_dir(&container_dir).await?;
179        while let Some(entry) = entries.next().await {
180            let entry = entry?;
181            if entry.file_type().await?.is_dir() {
182                last_version_dir = Some(entry.path());
183            }
184        }
185
186        let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
187        let server_path = last_version_dir.join(SERVER_PATH);
188        if server_path.exists() {
189            Ok(LanguageServerBinary {
190                path: node.binary_path().await?,
191                env: None,
192                arguments: server_binary_arguments(&server_path),
193            })
194        } else {
195            Err(anyhow!(
196                "missing executable in directory {:?}",
197                last_version_dir
198            ))
199        }
200    })
201    .await
202    .log_err()
203}
204
205fn schema_file_match(path: &Path) -> &Path {
206    path.strip_prefix(path.parent().unwrap().parent().unwrap())
207        .unwrap()
208}