1use anyhow::{anyhow, Result};
2use async_compression::futures::bufread::GzipDecoder;
3use async_trait::async_trait;
4use futures::{io::BufReader, StreamExt};
5pub use language::*;
6use lazy_static::lazy_static;
7use lsp::LanguageServerBinary;
8use regex::Regex;
9use smol::fs::{self, File};
10use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc};
11use util::{
12 fs::remove_matching,
13 github::{latest_github_release, GitHubLspBinaryVersion},
14 ResultExt,
15};
16
17pub struct RustLspAdapter;
18
19#[async_trait]
20impl LspAdapter for RustLspAdapter {
21 async fn name(&self) -> LanguageServerName {
22 LanguageServerName("rust-analyzer".into())
23 }
24
25 async fn fetch_latest_server_version(
26 &self,
27 delegate: &dyn LspAdapterDelegate,
28 ) -> Result<Box<dyn 'static + Send + Any>> {
29 let release =
30 latest_github_release("rust-analyzer/rust-analyzer", false, delegate.http_client())
31 .await?;
32 let asset_name = format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH);
33 let asset = release
34 .assets
35 .iter()
36 .find(|asset| asset.name == asset_name)
37 .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
38 Ok(Box::new(GitHubLspBinaryVersion {
39 name: release.name,
40 url: asset.browser_download_url.clone(),
41 }))
42 }
43
44 async fn fetch_server_binary(
45 &self,
46 version: Box<dyn 'static + Send + Any>,
47 container_dir: PathBuf,
48 delegate: &dyn LspAdapterDelegate,
49 ) -> Result<LanguageServerBinary> {
50 let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
51 let destination_path = container_dir.join(format!("rust-analyzer-{}", version.name));
52
53 if fs::metadata(&destination_path).await.is_err() {
54 let mut response = delegate
55 .http_client()
56 .get(&version.url, Default::default(), true)
57 .await
58 .map_err(|err| anyhow!("error downloading release: {}", err))?;
59 let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
60 let mut file = File::create(&destination_path).await?;
61 futures::io::copy(decompressed_bytes, &mut file).await?;
62 fs::set_permissions(
63 &destination_path,
64 <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
65 )
66 .await?;
67
68 remove_matching(&container_dir, |entry| entry != destination_path).await;
69 }
70
71 Ok(LanguageServerBinary {
72 path: destination_path,
73 arguments: Default::default(),
74 })
75 }
76
77 async fn cached_server_binary(
78 &self,
79 container_dir: PathBuf,
80 _: &dyn LspAdapterDelegate,
81 ) -> Option<LanguageServerBinary> {
82 get_cached_server_binary(container_dir).await
83 }
84
85 async fn installation_test_binary(
86 &self,
87 container_dir: PathBuf,
88 ) -> Option<LanguageServerBinary> {
89 get_cached_server_binary(container_dir)
90 .await
91 .map(|mut binary| {
92 binary.arguments = vec!["--help".into()];
93 binary
94 })
95 }
96
97 async fn disk_based_diagnostic_sources(&self) -> Vec<String> {
98 vec!["rustc".into()]
99 }
100
101 async fn disk_based_diagnostics_progress_token(&self) -> Option<String> {
102 Some("rust-analyzer/flycheck".into())
103 }
104
105 fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
106 lazy_static! {
107 static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`$").unwrap();
108 }
109
110 for diagnostic in &mut params.diagnostics {
111 for message in diagnostic
112 .related_information
113 .iter_mut()
114 .flatten()
115 .map(|info| &mut info.message)
116 .chain([&mut diagnostic.message])
117 {
118 if let Cow::Owned(sanitized) = REGEX.replace_all(message, "`$1`") {
119 *message = sanitized;
120 }
121 }
122 }
123 }
124
125 async fn label_for_completion(
126 &self,
127 completion: &lsp::CompletionItem,
128 language: &Arc<Language>,
129 ) -> Option<CodeLabel> {
130 match completion.kind {
131 Some(lsp::CompletionItemKind::FIELD) if completion.detail.is_some() => {
132 let detail = completion.detail.as_ref().unwrap();
133 let name = &completion.label;
134 let text = format!("{}: {}", name, detail);
135 let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
136 let runs = language.highlight_text(&source, 11..11 + text.len());
137 return Some(CodeLabel {
138 text,
139 runs,
140 filter_range: 0..name.len(),
141 });
142 }
143 Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE)
144 if completion.detail.is_some()
145 && completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) =>
146 {
147 let detail = completion.detail.as_ref().unwrap();
148 let name = &completion.label;
149 let text = format!("{}: {}", name, detail);
150 let source = Rope::from(format!("let {} = ();", text).as_str());
151 let runs = language.highlight_text(&source, 4..4 + text.len());
152 return Some(CodeLabel {
153 text,
154 runs,
155 filter_range: 0..name.len(),
156 });
157 }
158 Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD)
159 if completion.detail.is_some() =>
160 {
161 lazy_static! {
162 static ref REGEX: Regex = Regex::new("\\(…?\\)").unwrap();
163 }
164
165 let detail = completion.detail.as_ref().unwrap();
166 if detail.starts_with("fn(") {
167 let text = REGEX.replace(&completion.label, &detail[2..]).to_string();
168 let source = Rope::from(format!("fn {} {{}}", text).as_str());
169 let runs = language.highlight_text(&source, 3..3 + text.len());
170 return Some(CodeLabel {
171 filter_range: 0..completion.label.find('(').unwrap_or(text.len()),
172 text,
173 runs,
174 });
175 }
176 }
177 Some(kind) => {
178 let highlight_name = match kind {
179 lsp::CompletionItemKind::STRUCT
180 | lsp::CompletionItemKind::INTERFACE
181 | lsp::CompletionItemKind::ENUM => Some("type"),
182 lsp::CompletionItemKind::ENUM_MEMBER => Some("variant"),
183 lsp::CompletionItemKind::KEYWORD => Some("keyword"),
184 lsp::CompletionItemKind::VALUE | lsp::CompletionItemKind::CONSTANT => {
185 Some("constant")
186 }
187 _ => None,
188 };
189 let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name?)?;
190 let mut label = CodeLabel::plain(completion.label.clone(), None);
191 label.runs.push((
192 0..label.text.rfind('(').unwrap_or(label.text.len()),
193 highlight_id,
194 ));
195 return Some(label);
196 }
197 _ => {}
198 }
199 None
200 }
201
202 async fn label_for_symbol(
203 &self,
204 name: &str,
205 kind: lsp::SymbolKind,
206 language: &Arc<Language>,
207 ) -> Option<CodeLabel> {
208 let (text, filter_range, display_range) = match kind {
209 lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
210 let text = format!("fn {} () {{}}", name);
211 let filter_range = 3..3 + name.len();
212 let display_range = 0..filter_range.end;
213 (text, filter_range, display_range)
214 }
215 lsp::SymbolKind::STRUCT => {
216 let text = format!("struct {} {{}}", name);
217 let filter_range = 7..7 + name.len();
218 let display_range = 0..filter_range.end;
219 (text, filter_range, display_range)
220 }
221 lsp::SymbolKind::ENUM => {
222 let text = format!("enum {} {{}}", name);
223 let filter_range = 5..5 + name.len();
224 let display_range = 0..filter_range.end;
225 (text, filter_range, display_range)
226 }
227 lsp::SymbolKind::INTERFACE => {
228 let text = format!("trait {} {{}}", name);
229 let filter_range = 6..6 + name.len();
230 let display_range = 0..filter_range.end;
231 (text, filter_range, display_range)
232 }
233 lsp::SymbolKind::CONSTANT => {
234 let text = format!("const {}: () = ();", name);
235 let filter_range = 6..6 + name.len();
236 let display_range = 0..filter_range.end;
237 (text, filter_range, display_range)
238 }
239 lsp::SymbolKind::MODULE => {
240 let text = format!("mod {} {{}}", name);
241 let filter_range = 4..4 + name.len();
242 let display_range = 0..filter_range.end;
243 (text, filter_range, display_range)
244 }
245 lsp::SymbolKind::TYPE_PARAMETER => {
246 let text = format!("type {} {{}}", name);
247 let filter_range = 5..5 + name.len();
248 let display_range = 0..filter_range.end;
249 (text, filter_range, display_range)
250 }
251 _ => return None,
252 };
253
254 Some(CodeLabel {
255 runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
256 text: text[display_range].to_string(),
257 filter_range,
258 })
259 }
260}
261async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
262 (|| async move {
263 let mut last = None;
264 let mut entries = fs::read_dir(&container_dir).await?;
265 while let Some(entry) = entries.next().await {
266 last = Some(entry?.path());
267 }
268
269 anyhow::Ok(LanguageServerBinary {
270 path: last.ok_or_else(|| anyhow!("no cached binary"))?,
271 arguments: Default::default(),
272 })
273 })()
274 .await
275 .log_err()
276}
277
278#[cfg(test)]
279mod tests {
280 use std::num::NonZeroU32;
281
282 use super::*;
283 use crate::languages::language;
284 use gpui::{color::Color, TestAppContext};
285 use language::language_settings::AllLanguageSettings;
286 use settings::SettingsStore;
287 use theme::SyntaxTheme;
288
289 #[gpui::test]
290 async fn test_process_rust_diagnostics() {
291 let mut params = lsp::PublishDiagnosticsParams {
292 uri: lsp::Url::from_file_path("/a").unwrap(),
293 version: None,
294 diagnostics: vec![
295 // no newlines
296 lsp::Diagnostic {
297 message: "use of moved value `a`".to_string(),
298 ..Default::default()
299 },
300 // newline at the end of a code span
301 lsp::Diagnostic {
302 message: "consider importing this struct: `use b::c;\n`".to_string(),
303 ..Default::default()
304 },
305 // code span starting right after a newline
306 lsp::Diagnostic {
307 message: "cannot borrow `self.d` as mutable\n`self` is a `&` reference"
308 .to_string(),
309 ..Default::default()
310 },
311 ],
312 };
313 RustLspAdapter.process_diagnostics(&mut params);
314
315 assert_eq!(params.diagnostics[0].message, "use of moved value `a`");
316
317 // remove trailing newline from code span
318 assert_eq!(
319 params.diagnostics[1].message,
320 "consider importing this struct: `use b::c;`"
321 );
322
323 // do not remove newline before the start of code span
324 assert_eq!(
325 params.diagnostics[2].message,
326 "cannot borrow `self.d` as mutable\n`self` is a `&` reference"
327 );
328 }
329
330 #[gpui::test]
331 async fn test_rust_label_for_completion() {
332 let language = language(
333 "rust",
334 tree_sitter_rust::language(),
335 Some(Arc::new(RustLspAdapter)),
336 )
337 .await;
338 let grammar = language.grammar().unwrap();
339 let theme = SyntaxTheme::new(vec![
340 ("type".into(), Color::green().into()),
341 ("keyword".into(), Color::blue().into()),
342 ("function".into(), Color::red().into()),
343 ("property".into(), Color::white().into()),
344 ]);
345
346 language.set_theme(&theme);
347
348 let highlight_function = grammar.highlight_id_for_name("function").unwrap();
349 let highlight_type = grammar.highlight_id_for_name("type").unwrap();
350 let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
351 let highlight_field = grammar.highlight_id_for_name("property").unwrap();
352
353 assert_eq!(
354 language
355 .label_for_completion(&lsp::CompletionItem {
356 kind: Some(lsp::CompletionItemKind::FUNCTION),
357 label: "hello(…)".to_string(),
358 detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
359 ..Default::default()
360 })
361 .await,
362 Some(CodeLabel {
363 text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
364 filter_range: 0..5,
365 runs: vec![
366 (0..5, highlight_function),
367 (7..10, highlight_keyword),
368 (11..17, highlight_type),
369 (18..19, highlight_type),
370 (25..28, highlight_type),
371 (29..30, highlight_type),
372 ],
373 })
374 );
375
376 assert_eq!(
377 language
378 .label_for_completion(&lsp::CompletionItem {
379 kind: Some(lsp::CompletionItemKind::FIELD),
380 label: "len".to_string(),
381 detail: Some("usize".to_string()),
382 ..Default::default()
383 })
384 .await,
385 Some(CodeLabel {
386 text: "len: usize".to_string(),
387 filter_range: 0..3,
388 runs: vec![(0..3, highlight_field), (5..10, highlight_type),],
389 })
390 );
391
392 assert_eq!(
393 language
394 .label_for_completion(&lsp::CompletionItem {
395 kind: Some(lsp::CompletionItemKind::FUNCTION),
396 label: "hello(…)".to_string(),
397 detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
398 ..Default::default()
399 })
400 .await,
401 Some(CodeLabel {
402 text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
403 filter_range: 0..5,
404 runs: vec![
405 (0..5, highlight_function),
406 (7..10, highlight_keyword),
407 (11..17, highlight_type),
408 (18..19, highlight_type),
409 (25..28, highlight_type),
410 (29..30, highlight_type),
411 ],
412 })
413 );
414 }
415
416 #[gpui::test]
417 async fn test_rust_label_for_symbol() {
418 let language = language(
419 "rust",
420 tree_sitter_rust::language(),
421 Some(Arc::new(RustLspAdapter)),
422 )
423 .await;
424 let grammar = language.grammar().unwrap();
425 let theme = SyntaxTheme::new(vec![
426 ("type".into(), Color::green().into()),
427 ("keyword".into(), Color::blue().into()),
428 ("function".into(), Color::red().into()),
429 ("property".into(), Color::white().into()),
430 ]);
431
432 language.set_theme(&theme);
433
434 let highlight_function = grammar.highlight_id_for_name("function").unwrap();
435 let highlight_type = grammar.highlight_id_for_name("type").unwrap();
436 let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
437
438 assert_eq!(
439 language
440 .label_for_symbol("hello", lsp::SymbolKind::FUNCTION)
441 .await,
442 Some(CodeLabel {
443 text: "fn hello".to_string(),
444 filter_range: 3..8,
445 runs: vec![(0..2, highlight_keyword), (3..8, highlight_function)],
446 })
447 );
448
449 assert_eq!(
450 language
451 .label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER)
452 .await,
453 Some(CodeLabel {
454 text: "type World".to_string(),
455 filter_range: 5..10,
456 runs: vec![(0..4, highlight_keyword), (5..10, highlight_type)],
457 })
458 );
459 }
460
461 #[gpui::test]
462 async fn test_rust_autoindent(cx: &mut TestAppContext) {
463 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
464 cx.update(|cx| {
465 cx.set_global(SettingsStore::test(cx));
466 language::init(cx);
467 cx.update_global::<SettingsStore, _, _>(|store, cx| {
468 store.update_user_settings::<AllLanguageSettings>(cx, |s| {
469 s.defaults.tab_size = NonZeroU32::new(2);
470 });
471 });
472 });
473
474 let language = crate::languages::language("rust", tree_sitter_rust::language(), None).await;
475
476 cx.add_model(|cx| {
477 let mut buffer = Buffer::new(0, "", cx).with_language(language, cx);
478
479 // indent between braces
480 buffer.set_text("fn a() {}", cx);
481 let ix = buffer.len() - 1;
482 buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
483 assert_eq!(buffer.text(), "fn a() {\n \n}");
484
485 // indent between braces, even after empty lines
486 buffer.set_text("fn a() {\n\n\n}", cx);
487 let ix = buffer.len() - 2;
488 buffer.edit([(ix..ix, "\n")], Some(AutoindentMode::EachLine), cx);
489 assert_eq!(buffer.text(), "fn a() {\n\n\n \n}");
490
491 // indent a line that continues a field expression
492 buffer.set_text("fn a() {\n \n}", cx);
493 let ix = buffer.len() - 2;
494 buffer.edit([(ix..ix, "b\n.c")], Some(AutoindentMode::EachLine), cx);
495 assert_eq!(buffer.text(), "fn a() {\n b\n .c\n}");
496
497 // indent further lines that continue the field expression, even after empty lines
498 let ix = buffer.len() - 2;
499 buffer.edit([(ix..ix, "\n\n.d")], Some(AutoindentMode::EachLine), cx);
500 assert_eq!(buffer.text(), "fn a() {\n b\n .c\n \n .d\n}");
501
502 // dedent the line after the field expression
503 let ix = buffer.len() - 2;
504 buffer.edit([(ix..ix, ";\ne")], Some(AutoindentMode::EachLine), cx);
505 assert_eq!(
506 buffer.text(),
507 "fn a() {\n b\n .c\n \n .d;\n e\n}"
508 );
509
510 // indent inside a struct within a call
511 buffer.set_text("const a: B = c(D {});", cx);
512 let ix = buffer.len() - 3;
513 buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
514 assert_eq!(buffer.text(), "const a: B = c(D {\n \n});");
515
516 // indent further inside a nested call
517 let ix = buffer.len() - 4;
518 buffer.edit([(ix..ix, "e: f(\n\n)")], Some(AutoindentMode::EachLine), cx);
519 assert_eq!(buffer.text(), "const a: B = c(D {\n e: f(\n \n )\n});");
520
521 // keep that indent after an empty line
522 let ix = buffer.len() - 8;
523 buffer.edit([(ix..ix, "\n")], Some(AutoindentMode::EachLine), cx);
524 assert_eq!(
525 buffer.text(),
526 "const a: B = c(D {\n e: f(\n \n \n )\n});"
527 );
528
529 buffer
530 });
531 }
532}