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