1use anyhow::{anyhow, Result};
2use async_trait::async_trait;
3use collections::HashMap;
4use gpui::AsyncAppContext;
5use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
6use lsp::{CodeActionKind, LanguageServerBinary};
7use node_runtime::NodeRuntime;
8use project::project_settings::{BinarySettings, ProjectSettings};
9use serde_json::{json, Value};
10use settings::Settings;
11use std::{
12 any::Any,
13 ffi::OsString,
14 path::{Path, PathBuf},
15 sync::Arc,
16};
17use util::{maybe, ResultExt};
18
19fn typescript_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
20 vec![server_path.into(), "--stdio".into()]
21}
22
23pub struct VtslsLspAdapter {
24 node: Arc<dyn NodeRuntime>,
25}
26
27impl VtslsLspAdapter {
28 const SERVER_PATH: &'static str = "node_modules/@vtsls/language-server/bin/vtsls.js";
29
30 pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
31 VtslsLspAdapter { node }
32 }
33 async fn tsdk_path(adapter: &Arc<dyn LspAdapterDelegate>) -> &'static str {
34 let is_yarn = adapter
35 .read_text_file(PathBuf::from(".yarn/sdks/typescript/lib/typescript.js"))
36 .await
37 .is_ok();
38
39 if is_yarn {
40 ".yarn/sdks/typescript/lib"
41 } else {
42 "node_modules/typescript/lib"
43 }
44 }
45}
46
47struct TypeScriptVersions {
48 typescript_version: String,
49 server_version: String,
50}
51
52const SERVER_NAME: &'static str = "vtsls";
53#[async_trait(?Send)]
54impl LspAdapter for VtslsLspAdapter {
55 fn name(&self) -> LanguageServerName {
56 LanguageServerName(SERVER_NAME.into())
57 }
58
59 async fn fetch_latest_server_version(
60 &self,
61 _: &dyn LspAdapterDelegate,
62 ) -> Result<Box<dyn 'static + Send + Any>> {
63 Ok(Box::new(TypeScriptVersions {
64 typescript_version: self.node.npm_package_latest_version("typescript").await?,
65 server_version: self
66 .node
67 .npm_package_latest_version("@vtsls/language-server")
68 .await?,
69 }) as Box<_>)
70 }
71
72 async fn check_if_user_installed(
73 &self,
74 delegate: &dyn LspAdapterDelegate,
75 cx: &AsyncAppContext,
76 ) -> Option<LanguageServerBinary> {
77 let configured_binary = cx.update(|cx| {
78 ProjectSettings::get_global(cx)
79 .lsp
80 .get(SERVER_NAME)
81 .and_then(|s| s.binary.clone())
82 });
83
84 match configured_binary {
85 Ok(Some(BinarySettings {
86 path: Some(path),
87 arguments,
88 ..
89 })) => Some(LanguageServerBinary {
90 path: path.into(),
91 arguments: arguments
92 .unwrap_or_default()
93 .iter()
94 .map(|arg| arg.into())
95 .collect(),
96 env: None,
97 }),
98 Ok(Some(BinarySettings {
99 path_lookup: Some(false),
100 ..
101 })) => None,
102 _ => {
103 let env = delegate.shell_env().await;
104 let path = delegate.which(SERVER_NAME.as_ref()).await?;
105 Some(LanguageServerBinary {
106 path: path.clone(),
107 arguments: typescript_server_binary_arguments(&path),
108 env: Some(env),
109 })
110 }
111 }
112 }
113
114 async fn fetch_server_binary(
115 &self,
116 latest_version: Box<dyn 'static + Send + Any>,
117 container_dir: PathBuf,
118 _: &dyn LspAdapterDelegate,
119 ) -> Result<LanguageServerBinary> {
120 let latest_version = latest_version.downcast::<TypeScriptVersions>().unwrap();
121 let server_path = container_dir.join(Self::SERVER_PATH);
122 let package_name = "typescript";
123
124 let should_install_language_server = self
125 .node
126 .should_install_npm_package(
127 package_name,
128 &server_path,
129 &container_dir,
130 latest_version.typescript_version.as_str(),
131 )
132 .await;
133
134 if should_install_language_server {
135 self.node
136 .npm_install_packages(
137 &container_dir,
138 &[
139 (package_name, latest_version.typescript_version.as_str()),
140 (
141 "@vtsls/language-server",
142 latest_version.server_version.as_str(),
143 ),
144 ],
145 )
146 .await?;
147 }
148
149 Ok(LanguageServerBinary {
150 path: self.node.binary_path().await?,
151 env: None,
152 arguments: typescript_server_binary_arguments(&server_path),
153 })
154 }
155
156 async fn cached_server_binary(
157 &self,
158 container_dir: PathBuf,
159 _: &dyn LspAdapterDelegate,
160 ) -> Option<LanguageServerBinary> {
161 get_cached_ts_server_binary(container_dir, &*self.node).await
162 }
163
164 async fn installation_test_binary(
165 &self,
166 container_dir: PathBuf,
167 ) -> Option<LanguageServerBinary> {
168 get_cached_ts_server_binary(container_dir, &*self.node).await
169 }
170
171 fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
172 Some(vec![
173 CodeActionKind::QUICKFIX,
174 CodeActionKind::REFACTOR,
175 CodeActionKind::REFACTOR_EXTRACT,
176 CodeActionKind::SOURCE,
177 ])
178 }
179
180 async fn label_for_completion(
181 &self,
182 item: &lsp::CompletionItem,
183 language: &Arc<language::Language>,
184 ) -> Option<language::CodeLabel> {
185 use lsp::CompletionItemKind as Kind;
186 let len = item.label.len();
187 let grammar = language.grammar()?;
188 let highlight_id = match item.kind? {
189 Kind::CLASS | Kind::INTERFACE | Kind::ENUM => grammar.highlight_id_for_name("type"),
190 Kind::CONSTRUCTOR => grammar.highlight_id_for_name("type"),
191 Kind::CONSTANT => grammar.highlight_id_for_name("constant"),
192 Kind::FUNCTION | Kind::METHOD => grammar.highlight_id_for_name("function"),
193 Kind::PROPERTY | Kind::FIELD => grammar.highlight_id_for_name("property"),
194 Kind::VARIABLE => grammar.highlight_id_for_name("variable"),
195 _ => None,
196 }?;
197
198 let one_line = |s: &str| s.replace(" ", "").replace('\n', " ");
199
200 let text = if let Some(description) = item
201 .label_details
202 .as_ref()
203 .and_then(|label_details| label_details.description.as_ref())
204 {
205 format!("{} {}", item.label, one_line(description))
206 } else if let Some(detail) = &item.detail {
207 format!("{} {}", item.label, one_line(detail))
208 } else {
209 item.label.clone()
210 };
211
212 Some(language::CodeLabel {
213 text,
214 runs: vec![(0..len, highlight_id)],
215 filter_range: 0..len,
216 })
217 }
218
219 async fn initialization_options(
220 self: Arc<Self>,
221 adapter: &Arc<dyn LspAdapterDelegate>,
222 ) -> Result<Option<serde_json::Value>> {
223 let tsdk_path = Self::tsdk_path(&adapter).await;
224 let config = serde_json::json!({
225 "tsdk": tsdk_path,
226 "suggest": {
227 "completeFunctionCalls": true
228 },
229 "inlayHints": {
230 "parameterNames": {
231 "enabled": "all",
232 "suppressWhenArgumentMatchesName": false
233 },
234 "parameterTypes": {
235 "enabled": true
236 },
237 "variableTypes": {
238 "enabled": true,
239 "suppressWhenTypeMatchesName": false
240 },
241 "propertyDeclarationTypes": {
242 "enabled": true
243 },
244 "functionLikeReturnTypes": {
245 "enabled": true
246 },
247 "enumMemberValues": {
248 "enabled": true
249 }
250 }
251 });
252
253 Ok(Some(json!({
254 "typescript": config,
255 "javascript": config,
256 "vtsls": {
257 "experimental": {
258 "completion": {
259 "enableServerSideFuzzyMatch": true,
260 "entriesLimit": 5000,
261 }
262 },
263 "autoUseWorkspaceTsdk": true
264 }
265 })))
266 }
267
268 async fn workspace_configuration(
269 self: Arc<Self>,
270 adapter: &Arc<dyn LspAdapterDelegate>,
271 cx: &mut AsyncAppContext,
272 ) -> Result<Value> {
273 let override_options = cx.update(|cx| {
274 ProjectSettings::get_global(cx)
275 .lsp
276 .get(SERVER_NAME)
277 .and_then(|s| s.initialization_options.clone())
278 })?;
279 if let Some(options) = override_options {
280 return Ok(options);
281 }
282 self.initialization_options(adapter)
283 .await
284 .map(|o| o.unwrap())
285 }
286
287 fn language_ids(&self) -> HashMap<String, String> {
288 HashMap::from_iter([
289 ("TypeScript".into(), "typescript".into()),
290 ("JavaScript".into(), "javascript".into()),
291 ("TSX".into(), "typescriptreact".into()),
292 ])
293 }
294}
295
296async fn get_cached_ts_server_binary(
297 container_dir: PathBuf,
298 node: &dyn NodeRuntime,
299) -> Option<LanguageServerBinary> {
300 maybe!(async {
301 let server_path = container_dir.join(VtslsLspAdapter::SERVER_PATH);
302 if server_path.exists() {
303 Ok(LanguageServerBinary {
304 path: node.binary_path().await?,
305 env: None,
306 arguments: typescript_server_binary_arguments(&server_path),
307 })
308 } else {
309 Err(anyhow!(
310 "missing executable in directory {:?}",
311 container_dir
312 ))
313 }
314 })
315 .await
316 .log_err()
317}