1use anyhow::{Context as _, Result, bail};
2use async_compression::futures::bufread::GzipDecoder;
3use async_tar::Archive;
4use async_trait::async_trait;
5use collections::HashMap;
6use dap::DapRegistry;
7use futures::StreamExt;
8use gpui::{App, AsyncApp};
9use http_client::github::{GitHubLspBinaryVersion, latest_github_release};
10use language::{LanguageRegistry, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
11use lsp::{LanguageServerBinary, LanguageServerName};
12use node_runtime::NodeRuntime;
13use project::{ContextProviderWithTasks, Fs, lsp_store::language_server_settings};
14use serde_json::{Value, json};
15use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};
16use smol::{
17 fs::{self},
18 io::BufReader,
19 lock::RwLock,
20};
21use std::{
22 any::Any,
23 env::consts,
24 ffi::OsString,
25 path::{Path, PathBuf},
26 str::FromStr,
27 sync::Arc,
28};
29use task::{AdapterSchemas, TaskTemplate, TaskTemplates, VariableName};
30use util::{ResultExt, archive::extract_zip, fs::remove_matching, maybe, merge_json_value_into};
31
32const SERVER_PATH: &str =
33 "node_modules/vscode-langservers-extracted/bin/vscode-json-language-server";
34
35// Origin: https://github.com/SchemaStore/schemastore
36const TSCONFIG_SCHEMA: &str = include_str!("json/schemas/tsconfig.json");
37const PACKAGE_JSON_SCHEMA: &str = include_str!("json/schemas/package.json");
38
39pub(super) fn json_task_context() -> ContextProviderWithTasks {
40 ContextProviderWithTasks::new(TaskTemplates(vec![
41 TaskTemplate {
42 label: "package script $ZED_CUSTOM_script".to_owned(),
43 command: "npm --prefix $ZED_DIRNAME run".to_owned(),
44 args: vec![VariableName::Custom("script".into()).template_value()],
45 tags: vec!["package-script".into()],
46 ..TaskTemplate::default()
47 },
48 TaskTemplate {
49 label: "composer script $ZED_CUSTOM_script".to_owned(),
50 command: "composer -d $ZED_DIRNAME".to_owned(),
51 args: vec![VariableName::Custom("script".into()).template_value()],
52 tags: vec!["composer-script".into()],
53 ..TaskTemplate::default()
54 },
55 ]))
56}
57
58fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
59 vec![server_path.into(), "--stdio".into()]
60}
61
62pub struct JsonLspAdapter {
63 node: NodeRuntime,
64 languages: Arc<LanguageRegistry>,
65 workspace_config: RwLock<Option<Value>>,
66}
67
68impl JsonLspAdapter {
69 const PACKAGE_NAME: &str = "vscode-langservers-extracted";
70
71 pub fn new(node: NodeRuntime, languages: Arc<LanguageRegistry>) -> Self {
72 Self {
73 node,
74 languages,
75 workspace_config: Default::default(),
76 }
77 }
78
79 fn get_workspace_config(
80 language_names: Vec<String>,
81 adapter_schemas: AdapterSchemas,
82 cx: &mut App,
83 ) -> Value {
84 let keymap_schema = KeymapFile::generate_json_schema_for_registered_actions(cx);
85 let font_names = &cx.text_system().all_font_names();
86 let settings_schema = cx.global::<SettingsStore>().json_schema(
87 &SettingsJsonSchemaParams {
88 language_names: &language_names,
89 font_names,
90 },
91 cx,
92 );
93
94 let tasks_schema = task::TaskTemplates::generate_json_schema();
95 let debug_schema = task::DebugTaskFile::generate_json_schema(&adapter_schemas);
96 let snippets_schema = snippet_provider::format::VsSnippetsFile::generate_json_schema();
97 let tsconfig_schema = serde_json::Value::from_str(TSCONFIG_SCHEMA).unwrap();
98 let package_json_schema = serde_json::Value::from_str(PACKAGE_JSON_SCHEMA).unwrap();
99
100 #[allow(unused_mut)]
101 let mut schemas = serde_json::json!([
102 {
103 "fileMatch": ["tsconfig.json"],
104 "schema":tsconfig_schema
105 },
106 {
107 "fileMatch": ["package.json"],
108 "schema":package_json_schema
109 },
110 {
111 "fileMatch": [
112 schema_file_match(paths::settings_file()),
113 paths::local_settings_file_relative_path()
114 ],
115 "schema": settings_schema,
116 },
117 {
118 "fileMatch": [schema_file_match(paths::keymap_file())],
119 "schema": keymap_schema,
120 },
121 {
122 "fileMatch": [
123 schema_file_match(paths::tasks_file()),
124 paths::local_tasks_file_relative_path()
125 ],
126 "schema": tasks_schema,
127 },
128 {
129 "fileMatch": [
130 schema_file_match(
131 paths::snippets_dir()
132 .join("*.json")
133 .as_path()
134 )
135 ],
136 "schema": snippets_schema,
137 },
138 {
139 "fileMatch": [
140 schema_file_match(paths::debug_scenarios_file()),
141 paths::local_debug_file_relative_path()
142 ],
143 "schema": debug_schema,
144 },
145 ]);
146
147 #[cfg(debug_assertions)]
148 {
149 schemas.as_array_mut().unwrap().push(serde_json::json!(
150 {
151 "fileMatch": [
152 "zed-inspector-style.json"
153 ],
154 "schema": generate_inspector_style_schema(),
155 }
156 ))
157 }
158
159 // This can be viewed via `dev: open language server logs` -> `json-language-server` ->
160 // `Server Info`
161 serde_json::json!({
162 "json": {
163 "format": {
164 "enable": true,
165 },
166 "validate":
167 {
168 "enable": true,
169 },
170 "schemas": schemas
171 }
172 })
173 }
174
175 async fn get_or_init_workspace_config(&self, cx: &mut AsyncApp) -> Result<Value> {
176 {
177 let reader = self.workspace_config.read().await;
178 if let Some(config) = reader.as_ref() {
179 return Ok(config.clone());
180 }
181 }
182 let mut writer = self.workspace_config.write().await;
183
184 let adapter_schemas = cx
185 .read_global::<DapRegistry, _>(|dap_registry, _| dap_registry.to_owned())?
186 .adapters_schema()
187 .await;
188
189 let config = cx.update(|cx| {
190 Self::get_workspace_config(self.languages.language_names().clone(), adapter_schemas, cx)
191 })?;
192 writer.replace(config.clone());
193 return Ok(config);
194 }
195}
196
197#[cfg(debug_assertions)]
198fn generate_inspector_style_schema() -> serde_json_lenient::Value {
199 let schema = schemars::r#gen::SchemaSettings::draft07()
200 .with(|settings| settings.option_add_null_type = false)
201 .into_generator()
202 .into_root_schema_for::<gpui::StyleRefinement>();
203
204 serde_json_lenient::to_value(schema).unwrap()
205}
206
207#[async_trait(?Send)]
208impl LspAdapter for JsonLspAdapter {
209 fn name(&self) -> LanguageServerName {
210 LanguageServerName("json-language-server".into())
211 }
212
213 async fn check_if_user_installed(
214 &self,
215 delegate: &dyn LspAdapterDelegate,
216 _: Arc<dyn LanguageToolchainStore>,
217 _: &AsyncApp,
218 ) -> Option<LanguageServerBinary> {
219 let path = delegate
220 .which("vscode-json-language-server".as_ref())
221 .await?;
222 let env = delegate.shell_env().await;
223
224 Some(LanguageServerBinary {
225 path,
226 env: Some(env),
227 arguments: vec!["--stdio".into()],
228 })
229 }
230
231 async fn fetch_latest_server_version(
232 &self,
233 _: &dyn LspAdapterDelegate,
234 ) -> Result<Box<dyn 'static + Send + Any>> {
235 Ok(Box::new(
236 self.node
237 .npm_package_latest_version(Self::PACKAGE_NAME)
238 .await?,
239 ) as Box<_>)
240 }
241
242 async fn check_if_version_installed(
243 &self,
244 version: &(dyn 'static + Send + Any),
245 container_dir: &PathBuf,
246 _: &dyn LspAdapterDelegate,
247 ) -> Option<LanguageServerBinary> {
248 let version = version.downcast_ref::<String>().unwrap();
249 let server_path = container_dir.join(SERVER_PATH);
250
251 let should_install_language_server = self
252 .node
253 .should_install_npm_package(Self::PACKAGE_NAME, &server_path, &container_dir, &version)
254 .await;
255
256 if should_install_language_server {
257 None
258 } else {
259 Some(LanguageServerBinary {
260 path: self.node.binary_path().await.ok()?,
261 env: None,
262 arguments: server_binary_arguments(&server_path),
263 })
264 }
265 }
266
267 async fn fetch_server_binary(
268 &self,
269 latest_version: Box<dyn 'static + Send + Any>,
270 container_dir: PathBuf,
271 _: &dyn LspAdapterDelegate,
272 ) -> Result<LanguageServerBinary> {
273 let latest_version = latest_version.downcast::<String>().unwrap();
274 let server_path = container_dir.join(SERVER_PATH);
275
276 self.node
277 .npm_install_packages(
278 &container_dir,
279 &[(Self::PACKAGE_NAME, latest_version.as_str())],
280 )
281 .await?;
282
283 Ok(LanguageServerBinary {
284 path: self.node.binary_path().await?,
285 env: None,
286 arguments: server_binary_arguments(&server_path),
287 })
288 }
289
290 async fn cached_server_binary(
291 &self,
292 container_dir: PathBuf,
293 _: &dyn LspAdapterDelegate,
294 ) -> Option<LanguageServerBinary> {
295 get_cached_server_binary(container_dir, &self.node).await
296 }
297
298 async fn initialization_options(
299 self: Arc<Self>,
300 _: &dyn Fs,
301 _: &Arc<dyn LspAdapterDelegate>,
302 ) -> Result<Option<serde_json::Value>> {
303 Ok(Some(json!({
304 "provideFormatter": true
305 })))
306 }
307
308 async fn workspace_configuration(
309 self: Arc<Self>,
310 _: &dyn Fs,
311 delegate: &Arc<dyn LspAdapterDelegate>,
312 _: Arc<dyn LanguageToolchainStore>,
313 cx: &mut AsyncApp,
314 ) -> Result<Value> {
315 let mut config = self.get_or_init_workspace_config(cx).await?;
316
317 let project_options = cx.update(|cx| {
318 language_server_settings(delegate.as_ref(), &self.name(), cx)
319 .and_then(|s| s.settings.clone())
320 })?;
321
322 if let Some(override_options) = project_options {
323 merge_json_value_into(override_options, &mut config);
324 }
325
326 Ok(config)
327 }
328
329 fn language_ids(&self) -> HashMap<String, String> {
330 [
331 ("JSON".into(), "json".into()),
332 ("JSONC".into(), "jsonc".into()),
333 ]
334 .into_iter()
335 .collect()
336 }
337
338 fn is_primary_zed_json_schema_adapter(&self) -> bool {
339 true
340 }
341
342 async fn clear_zed_json_schema_cache(&self) {
343 self.workspace_config.write().await.take();
344 }
345}
346
347async fn get_cached_server_binary(
348 container_dir: PathBuf,
349 node: &NodeRuntime,
350) -> Option<LanguageServerBinary> {
351 maybe!(async {
352 let mut last_version_dir = None;
353 let mut entries = fs::read_dir(&container_dir).await?;
354 while let Some(entry) = entries.next().await {
355 let entry = entry?;
356 if entry.file_type().await?.is_dir() {
357 last_version_dir = Some(entry.path());
358 }
359 }
360
361 let last_version_dir = last_version_dir.context("no cached binary")?;
362 let server_path = last_version_dir.join(SERVER_PATH);
363 anyhow::ensure!(
364 server_path.exists(),
365 "missing executable in directory {last_version_dir:?}"
366 );
367 Ok(LanguageServerBinary {
368 path: node.binary_path().await?,
369 env: None,
370 arguments: server_binary_arguments(&server_path),
371 })
372 })
373 .await
374 .log_err()
375}
376
377#[inline]
378fn schema_file_match(path: &Path) -> String {
379 path.strip_prefix(path.parent().unwrap().parent().unwrap())
380 .unwrap()
381 .display()
382 .to_string()
383 .replace('\\', "/")
384}
385
386pub struct NodeVersionAdapter;
387
388impl NodeVersionAdapter {
389 const SERVER_NAME: LanguageServerName =
390 LanguageServerName::new_static("package-version-server");
391}
392
393#[async_trait(?Send)]
394impl LspAdapter for NodeVersionAdapter {
395 fn name(&self) -> LanguageServerName {
396 Self::SERVER_NAME.clone()
397 }
398
399 async fn fetch_latest_server_version(
400 &self,
401 delegate: &dyn LspAdapterDelegate,
402 ) -> Result<Box<dyn 'static + Send + Any>> {
403 let release = latest_github_release(
404 "zed-industries/package-version-server",
405 true,
406 false,
407 delegate.http_client(),
408 )
409 .await?;
410 let os = match consts::OS {
411 "macos" => "apple-darwin",
412 "linux" => "unknown-linux-gnu",
413 "windows" => "pc-windows-msvc",
414 other => bail!("Running on unsupported os: {other}"),
415 };
416 let suffix = if consts::OS == "windows" {
417 ".zip"
418 } else {
419 ".tar.gz"
420 };
421 let asset_name = format!("{}-{}-{os}{suffix}", Self::SERVER_NAME, consts::ARCH);
422 let asset = release
423 .assets
424 .iter()
425 .find(|asset| asset.name == asset_name)
426 .with_context(|| format!("no asset found matching `{asset_name:?}`"))?;
427 Ok(Box::new(GitHubLspBinaryVersion {
428 name: release.tag_name,
429 url: asset.browser_download_url.clone(),
430 }))
431 }
432
433 async fn check_if_user_installed(
434 &self,
435 delegate: &dyn LspAdapterDelegate,
436 _: Arc<dyn LanguageToolchainStore>,
437 _: &AsyncApp,
438 ) -> Option<LanguageServerBinary> {
439 let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
440 Some(LanguageServerBinary {
441 path,
442 env: None,
443 arguments: Default::default(),
444 })
445 }
446
447 async fn fetch_server_binary(
448 &self,
449 latest_version: Box<dyn 'static + Send + Any>,
450 container_dir: PathBuf,
451 delegate: &dyn LspAdapterDelegate,
452 ) -> Result<LanguageServerBinary> {
453 let version = latest_version.downcast::<GitHubLspBinaryVersion>().unwrap();
454 let destination_path = container_dir.join(format!(
455 "{}-{}{}",
456 Self::SERVER_NAME,
457 version.name,
458 std::env::consts::EXE_SUFFIX
459 ));
460 let destination_container_path =
461 container_dir.join(format!("{}-{}-tmp", Self::SERVER_NAME, version.name));
462 if fs::metadata(&destination_path).await.is_err() {
463 let mut response = delegate
464 .http_client()
465 .get(&version.url, Default::default(), true)
466 .await
467 .context("downloading release")?;
468 if version.url.ends_with(".zip") {
469 extract_zip(&destination_container_path, response.body_mut()).await?;
470 } else if version.url.ends_with(".tar.gz") {
471 let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
472 let archive = Archive::new(decompressed_bytes);
473 archive.unpack(&destination_container_path).await?;
474 }
475
476 fs::copy(
477 destination_container_path.join(format!(
478 "{}{}",
479 Self::SERVER_NAME,
480 std::env::consts::EXE_SUFFIX
481 )),
482 &destination_path,
483 )
484 .await?;
485 remove_matching(&container_dir, |entry| entry != destination_path).await;
486 }
487 Ok(LanguageServerBinary {
488 path: destination_path,
489 env: None,
490 arguments: Default::default(),
491 })
492 }
493
494 async fn cached_server_binary(
495 &self,
496 container_dir: PathBuf,
497 _delegate: &dyn LspAdapterDelegate,
498 ) -> Option<LanguageServerBinary> {
499 get_cached_version_server_binary(container_dir).await
500 }
501}
502
503async fn get_cached_version_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
504 maybe!(async {
505 let mut last = None;
506 let mut entries = fs::read_dir(&container_dir).await?;
507 while let Some(entry) = entries.next().await {
508 last = Some(entry?.path());
509 }
510
511 anyhow::Ok(LanguageServerBinary {
512 path: last.context("no cached binary")?,
513 env: None,
514 arguments: Default::default(),
515 })
516 })
517 .await
518 .log_err()
519}