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