1use anyhow::Result;
2use async_trait::async_trait;
3use collections::HashMap;
4use gpui::AsyncApp;
5use language::{LanguageName, LspAdapter, LspAdapterDelegate, Toolchain};
6use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
7use node_runtime::{NodeRuntime, VersionStrategy};
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
71 }
72
73 async fn fetch_latest_server_version(
74 &self,
75 _: &dyn LspAdapterDelegate,
76 _: &AsyncApp,
77 ) -> Result<Box<dyn 'static + Send + Any>> {
78 Ok(Box::new(TypeScriptVersions {
79 typescript_version: self.node.npm_package_latest_version("typescript").await?,
80 server_version: self
81 .node
82 .npm_package_latest_version("@vtsls/language-server")
83 .await?,
84 }) as Box<_>)
85 }
86
87 async fn check_if_user_installed(
88 &self,
89 delegate: &dyn LspAdapterDelegate,
90 _: Option<Toolchain>,
91 _: &AsyncApp,
92 ) -> Option<LanguageServerBinary> {
93 let env = delegate.shell_env().await;
94 let path = delegate.which(SERVER_NAME.as_ref()).await?;
95 Some(LanguageServerBinary {
96 path: path.clone(),
97 arguments: typescript_server_binary_arguments(&path),
98 env: Some(env),
99 })
100 }
101
102 async fn fetch_server_binary(
103 &self,
104 latest_version: Box<dyn 'static + Send + Any>,
105 container_dir: PathBuf,
106 _: &dyn LspAdapterDelegate,
107 ) -> Result<LanguageServerBinary> {
108 let latest_version = latest_version.downcast::<TypeScriptVersions>().unwrap();
109 let server_path = container_dir.join(Self::SERVER_PATH);
110
111 let mut packages_to_install = Vec::new();
112
113 if self
114 .node
115 .should_install_npm_package(
116 Self::PACKAGE_NAME,
117 &server_path,
118 &container_dir,
119 VersionStrategy::Latest(&latest_version.server_version),
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 VersionStrategy::Latest(&latest_version.typescript_version),
133 )
134 .await
135 {
136 packages_to_install.push((
137 Self::TYPESCRIPT_PACKAGE_NAME,
138 latest_version.typescript_version.as_str(),
139 ));
140 }
141
142 self.node
143 .npm_install_packages(&container_dir, &packages_to_install)
144 .await?;
145
146 Ok(LanguageServerBinary {
147 path: self.node.binary_path().await?,
148 env: None,
149 arguments: typescript_server_binary_arguments(&server_path),
150 })
151 }
152
153 async fn cached_server_binary(
154 &self,
155 container_dir: PathBuf,
156 _: &dyn LspAdapterDelegate,
157 ) -> Option<LanguageServerBinary> {
158 get_cached_ts_server_binary(container_dir, &self.node).await
159 }
160
161 fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
162 Some(vec![
163 CodeActionKind::QUICKFIX,
164 CodeActionKind::REFACTOR,
165 CodeActionKind::REFACTOR_EXTRACT,
166 CodeActionKind::SOURCE,
167 ])
168 }
169
170 async fn label_for_completion(
171 &self,
172 item: &lsp::CompletionItem,
173 language: &Arc<language::Language>,
174 ) -> Option<language::CodeLabel> {
175 use lsp::CompletionItemKind as Kind;
176 let len = item.label.len();
177 let grammar = language.grammar()?;
178 let highlight_id = match item.kind? {
179 Kind::CLASS | Kind::INTERFACE | Kind::ENUM => grammar.highlight_id_for_name("type"),
180 Kind::CONSTRUCTOR => grammar.highlight_id_for_name("type"),
181 Kind::CONSTANT => grammar.highlight_id_for_name("constant"),
182 Kind::FUNCTION | Kind::METHOD => grammar.highlight_id_for_name("function"),
183 Kind::PROPERTY | Kind::FIELD => grammar.highlight_id_for_name("property"),
184 Kind::VARIABLE => grammar.highlight_id_for_name("variable"),
185 _ => None,
186 }?;
187
188 let text = if let Some(description) = item
189 .label_details
190 .as_ref()
191 .and_then(|label_details| label_details.description.as_ref())
192 {
193 format!("{} {}", item.label, description)
194 } else if let Some(detail) = &item.detail {
195 format!("{} {}", item.label, detail)
196 } else {
197 item.label.clone()
198 };
199 let filter_range = item
200 .filter_text
201 .as_deref()
202 .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
203 .unwrap_or(0..len);
204 Some(language::CodeLabel {
205 text,
206 runs: vec![(0..len, highlight_id)],
207 filter_range,
208 })
209 }
210
211 async fn workspace_configuration(
212 self: Arc<Self>,
213 fs: &dyn Fs,
214 delegate: &Arc<dyn LspAdapterDelegate>,
215 _: Option<Toolchain>,
216 cx: &mut AsyncApp,
217 ) -> Result<Value> {
218 let tsdk_path = Self::tsdk_path(fs, delegate).await;
219 let config = serde_json::json!({
220 "tsdk": tsdk_path,
221 "suggest": {
222 "completeFunctionCalls": true
223 },
224 "inlayHints": {
225 "parameterNames": {
226 "enabled": "all",
227 "suppressWhenArgumentMatchesName": false
228 },
229 "parameterTypes": {
230 "enabled": true
231 },
232 "variableTypes": {
233 "enabled": true,
234 "suppressWhenTypeMatchesName": false
235 },
236 "propertyDeclarationTypes": {
237 "enabled": true
238 },
239 "functionLikeReturnTypes": {
240 "enabled": true
241 },
242 "enumMemberValues": {
243 "enabled": true
244 }
245 },
246 "tsserver": {
247 "maxTsServerMemory": 8092
248 },
249 });
250
251 let mut default_workspace_configuration = serde_json::json!({
252 "typescript": config,
253 "javascript": config,
254 "vtsls": {
255 "experimental": {
256 "completion": {
257 "enableServerSideFuzzyMatch": true,
258 "entriesLimit": 5000,
259 }
260 },
261 "autoUseWorkspaceTsdk": true
262 }
263 });
264
265 let override_options = cx.update(|cx| {
266 language_server_settings(delegate.as_ref(), &SERVER_NAME, cx)
267 .and_then(|s| s.settings.clone())
268 })?;
269
270 if let Some(override_options) = override_options {
271 merge_json_value_into(override_options, &mut default_workspace_configuration)
272 }
273
274 Ok(default_workspace_configuration)
275 }
276
277 fn language_ids(&self) -> HashMap<LanguageName, String> {
278 HashMap::from_iter([
279 (LanguageName::new("TypeScript"), "typescript".into()),
280 (LanguageName::new("JavaScript"), "javascript".into()),
281 (LanguageName::new("TSX"), "typescriptreact".into()),
282 ])
283 }
284}
285
286async fn get_cached_ts_server_binary(
287 container_dir: PathBuf,
288 node: &NodeRuntime,
289) -> Option<LanguageServerBinary> {
290 maybe!(async {
291 let server_path = container_dir.join(VtslsLspAdapter::SERVER_PATH);
292 anyhow::ensure!(
293 server_path.exists(),
294 "missing executable in directory {container_dir:?}"
295 );
296 Ok(LanguageServerBinary {
297 path: node.binary_path().await?,
298 env: None,
299 arguments: typescript_server_binary_arguments(&server_path),
300 })
301 })
302 .await
303 .log_err()
304}