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