rust.rs

  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}