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 cx: &mut AppContext,
109 ) -> BoxFuture<'static, serde_json::Value> {
110 let action_names = gpui::all_action_names();
111 let staff_mode = cx.is_staff();
112 let language_names = &self.languages.language_names();
113 let settings_schema = cx.global::<SettingsStore>().json_schema(
114 &SettingsJsonSchemaParams {
115 language_names,
116 staff_mode,
117 },
118 cx,
119 );
120
121 future::ready(serde_json::json!({
122 "json": {
123 "format": {
124 "enable": true,
125 },
126 "schemas": [
127 {
128 "fileMatch": [
129 schema_file_match(&paths::SETTINGS),
130 &*paths::LOCAL_SETTINGS_RELATIVE_PATH,
131 ],
132 "schema": settings_schema,
133 },
134 {
135 "fileMatch": [schema_file_match(&paths::KEYMAP)],
136 "schema": KeymapFile::generate_json_schema(&action_names),
137 }
138 ]
139 }
140 }))
141 .boxed()
142 }
143
144 async fn language_ids(&self) -> HashMap<String, String> {
145 [("JSON".into(), "jsonc".into())].into_iter().collect()
146 }
147}
148
149async fn get_cached_server_binary(
150 container_dir: PathBuf,
151 node: &dyn NodeRuntime,
152) -> Option<LanguageServerBinary> {
153 (|| async move {
154 let mut last_version_dir = None;
155 let mut entries = fs::read_dir(&container_dir).await?;
156 while let Some(entry) = entries.next().await {
157 let entry = entry?;
158 if entry.file_type().await?.is_dir() {
159 last_version_dir = Some(entry.path());
160 }
161 }
162
163 let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
164 let server_path = last_version_dir.join(SERVER_PATH);
165 if server_path.exists() {
166 Ok(LanguageServerBinary {
167 path: node.binary_path().await?,
168 arguments: server_binary_arguments(&server_path),
169 })
170 } else {
171 Err(anyhow!(
172 "missing executable in directory {:?}",
173 last_version_dir
174 ))
175 }
176 })()
177 .await
178 .log_err()
179}
180
181fn schema_file_match(path: &Path) -> &Path {
182 path.strip_prefix(path.parent().unwrap().parent().unwrap())
183 .unwrap()
184}