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