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