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 fn short_name(&self) -> &'static str {
94 "json"
95 }
96
97 async fn fetch_latest_server_version(
98 &self,
99 _: &dyn LspAdapterDelegate,
100 ) -> Result<Box<dyn 'static + Send + Any>> {
101 Ok(Box::new(
102 self.node
103 .npm_package_latest_version("vscode-json-languageserver")
104 .await?,
105 ) as Box<_>)
106 }
107
108 async fn fetch_server_binary(
109 &self,
110 version: Box<dyn 'static + Send + Any>,
111 container_dir: PathBuf,
112 _: &dyn LspAdapterDelegate,
113 ) -> Result<LanguageServerBinary> {
114 let version = version.downcast::<String>().unwrap();
115 let server_path = container_dir.join(SERVER_PATH);
116
117 if fs::metadata(&server_path).await.is_err() {
118 self.node
119 .npm_install_packages(
120 &container_dir,
121 &[("vscode-json-languageserver", version.as_str())],
122 )
123 .await?;
124 }
125
126 Ok(LanguageServerBinary {
127 path: self.node.binary_path().await?,
128 env: None,
129 arguments: server_binary_arguments(&server_path),
130 })
131 }
132
133 async fn cached_server_binary(
134 &self,
135 container_dir: PathBuf,
136 _: &dyn LspAdapterDelegate,
137 ) -> Option<LanguageServerBinary> {
138 get_cached_server_binary(container_dir, &*self.node).await
139 }
140
141 async fn installation_test_binary(
142 &self,
143 container_dir: PathBuf,
144 ) -> Option<LanguageServerBinary> {
145 get_cached_server_binary(container_dir, &*self.node).await
146 }
147
148 fn initialization_options(&self) -> Option<serde_json::Value> {
149 Some(json!({
150 "provideFormatter": true
151 }))
152 }
153
154 fn workspace_configuration(&self, _workspace_root: &Path, cx: &mut AppContext) -> Value {
155 self.workspace_config
156 .get_or_init(|| Self::get_workspace_config(self.languages.language_names(), cx))
157 .clone()
158 }
159
160 fn language_ids(&self) -> HashMap<String, String> {
161 [("JSON".into(), "jsonc".into())].into_iter().collect()
162 }
163}
164
165async fn get_cached_server_binary(
166 container_dir: PathBuf,
167 node: &dyn NodeRuntime,
168) -> Option<LanguageServerBinary> {
169 async_maybe!({
170 let mut last_version_dir = None;
171 let mut entries = fs::read_dir(&container_dir).await?;
172 while let Some(entry) = entries.next().await {
173 let entry = entry?;
174 if entry.file_type().await?.is_dir() {
175 last_version_dir = Some(entry.path());
176 }
177 }
178
179 let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
180 let server_path = last_version_dir.join(SERVER_PATH);
181 if server_path.exists() {
182 Ok(LanguageServerBinary {
183 path: node.binary_path().await?,
184 env: None,
185 arguments: server_binary_arguments(&server_path),
186 })
187 } else {
188 Err(anyhow!(
189 "missing executable in directory {:?}",
190 last_version_dir
191 ))
192 }
193 })
194 .await
195 .log_err()
196}
197
198fn schema_file_match(path: &Path) -> &Path {
199 path.strip_prefix(path.parent().unwrap().parent().unwrap())
200 .unwrap()
201}