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