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