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