Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:34:06.518Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

rust.rs

2394 lines · 91.9 KB · rust
1use anyhow::{Context as _, Result};
2use async_trait::async_trait;
3use collections::HashMap;
4use futures::StreamExt;
5use futures::lock::OwnedMutexGuard;
6use gpui::{App, AppContext, AsyncApp, Entity, SharedString, Task};
7use http_client::github::AssetKind;
8use http_client::github::{GitHubLspBinaryVersion, latest_github_release};
9use http_client::github_download::{GithubBinaryMetadata, download_server_binary};
10pub use language::*;
11use lsp::{InitializeParams, LanguageServerBinary, LanguageServerBinaryOptions};
12use project::lsp_store::lsp_ext_command;
13use project::lsp_store::rust_analyzer_ext::CARGO_DIAGNOSTICS_SOURCE_NAME;
14use project::project_settings::ProjectSettings;
15use regex::Regex;
16use serde_json::json;
17use settings::{SemanticTokenRules, Settings as _};
18use smallvec::SmallVec;
19use smol::fs::{self};
20use std::cmp::Reverse;
21use std::fmt::Display;
22use std::future::Future;
23use std::ops::Range;
24use std::{
25    borrow::Cow,
26    path::{Path, PathBuf},
27    sync::{Arc, LazyLock},
28};
29use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};
30use util::command::{Stdio, new_command};
31use util::fs::{make_file_executable, remove_matching};
32use util::merge_json_value_into;
33use util::rel_path::RelPath;
34use util::{ResultExt, maybe};
35
36use crate::language_settings::LanguageSettings;
37
38pub(crate) fn semantic_token_rules() -> SemanticTokenRules {
39    let content = grammars::get_file("rust/semantic_token_rules.json")
40        .expect("missing rust/semantic_token_rules.json");
41    let json = std::str::from_utf8(&content.data).expect("invalid utf-8 in semantic_token_rules");
42    settings::parse_json_with_comments::<SemanticTokenRules>(json)
43        .expect("failed to parse rust semantic_token_rules.json")
44}
45
46pub struct RustLspAdapter;
47
48#[cfg(target_os = "macos")]
49impl RustLspAdapter {
50    const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz;
51    const ARCH_SERVER_NAME: &str = "apple-darwin";
52}
53
54#[cfg(target_os = "linux")]
55impl RustLspAdapter {
56    const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz;
57    const ARCH_SERVER_NAME: &str = "unknown-linux";
58}
59
60#[cfg(target_os = "freebsd")]
61impl RustLspAdapter {
62    const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz;
63    const ARCH_SERVER_NAME: &str = "unknown-freebsd";
64}
65
66#[cfg(target_os = "windows")]
67impl RustLspAdapter {
68    const GITHUB_ASSET_KIND: AssetKind = AssetKind::Zip;
69    const ARCH_SERVER_NAME: &str = "pc-windows-msvc";
70}
71
72const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("rust-analyzer");
73
74#[cfg(target_os = "linux")]
75enum LibcType {
76    Gnu,
77    Musl,
78}
79
80impl RustLspAdapter {
81    fn convert_rust_analyzer_schema(raw_schema: &serde_json::Value) -> serde_json::Value {
82        let Some(schema_array) = raw_schema.as_array() else {
83            return raw_schema.clone();
84        };
85
86        let mut root_properties = serde_json::Map::new();
87
88        for item in schema_array {
89            if let Some(props) = item.get("properties").and_then(|p| p.as_object()) {
90                for (key, value) in props {
91                    let parts: Vec<&str> = key.split('.').collect();
92
93                    if parts.is_empty() {
94                        continue;
95                    }
96
97                    let parts_to_process = if parts.first() == Some(&"rust-analyzer") {
98                        &parts[1..]
99                    } else {
100                        &parts[..]
101                    };
102
103                    if parts_to_process.is_empty() {
104                        continue;
105                    }
106
107                    let mut current = &mut root_properties;
108
109                    for (i, part) in parts_to_process.iter().enumerate() {
110                        let is_last = i == parts_to_process.len() - 1;
111
112                        if is_last {
113                            current.insert(part.to_string(), value.clone());
114                        } else {
115                            let next_current = current
116                                .entry(part.to_string())
117                                .or_insert_with(|| {
118                                    serde_json::json!({
119                                        "type": "object",
120                                        "properties": {}
121                                    })
122                                })
123                                .as_object_mut()
124                                .expect("should be an object")
125                                .entry("properties")
126                                .or_insert_with(|| serde_json::json!({}))
127                                .as_object_mut()
128                                .expect("properties should be an object");
129
130                            current = next_current;
131                        }
132                    }
133                }
134            }
135        }
136
137        serde_json::json!({
138            "type": "object",
139            "properties": root_properties
140        })
141    }
142
143    #[cfg(target_os = "linux")]
144    async fn determine_libc_type() -> LibcType {
145        use futures::pin_mut;
146
147        async fn from_ldd_version() -> Option<LibcType> {
148            use util::command::new_command;
149
150            let ldd_output = new_command("ldd").arg("--version").output().await.ok()?;
151            let ldd_version = String::from_utf8_lossy(&ldd_output.stdout);
152
153            if ldd_version.contains("GNU libc") || ldd_version.contains("GLIBC") {
154                Some(LibcType::Gnu)
155            } else if ldd_version.contains("musl") {
156                Some(LibcType::Musl)
157            } else {
158                None
159            }
160        }
161
162        if let Some(libc_type) = from_ldd_version().await {
163            return libc_type;
164        }
165
166        let Ok(dir_entries) = smol::fs::read_dir("/lib").await else {
167            // defaulting to gnu because nix doesn't have /lib files due to not following FHS
168            return LibcType::Gnu;
169        };
170        let dir_entries = dir_entries.filter_map(async move |e| e.ok());
171        pin_mut!(dir_entries);
172
173        let mut has_musl = false;
174        let mut has_gnu = false;
175
176        while let Some(entry) = dir_entries.next().await {
177            let file_name = entry.file_name();
178            let file_name = file_name.to_string_lossy();
179            if file_name.starts_with("ld-musl-") {
180                has_musl = true;
181            } else if file_name.starts_with("ld-linux-") {
182                has_gnu = true;
183            }
184        }
185
186        match (has_musl, has_gnu) {
187            (true, _) => LibcType::Musl,
188            (_, true) => LibcType::Gnu,
189            _ => LibcType::Gnu,
190        }
191    }
192
193    #[cfg(target_os = "linux")]
194    async fn build_arch_server_name_linux() -> String {
195        let libc = match Self::determine_libc_type().await {
196            LibcType::Musl => "musl",
197            LibcType::Gnu => "gnu",
198        };
199
200        format!("{}-{}", Self::ARCH_SERVER_NAME, libc)
201    }
202
203    async fn rustup_rust_analyzer_for_worktree(
204        delegate: &dyn LspAdapterDelegate,
205    ) -> Option<PathBuf> {
206        if !Self::workspace_has_rust_toolchain_override(delegate).await {
207            return None;
208        }
209
210        let rustup = delegate.which("rustup".as_ref()).await?;
211        let env = delegate.shell_env().await;
212        let worktree_root = delegate.worktree_root_path();
213        let output = new_command(rustup)
214            .args(["which", "rust-analyzer"])
215            .envs(env.iter())
216            .current_dir(worktree_root)
217            .stdout(Stdio::piped())
218            .stderr(Stdio::piped())
219            .output()
220            .await;
221        let output = match output {
222            Ok(output) if output.status.success() => output,
223            Ok(output) => {
224                log::debug!(
225                    "failed to locate rust-analyzer through rustup in {worktree_root:?}: {}",
226                    String::from_utf8_lossy(&output.stderr)
227                );
228                return None;
229            }
230            Err(err) => {
231                log::debug!(
232                    "failed to run `rustup which rust-analyzer` in {worktree_root:?}: {err:#}"
233                );
234                return None;
235            }
236        };
237
238        let path = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim());
239        Some(path).filter(|p| !p.as_os_str().is_empty())
240    }
241
242    async fn workspace_has_rust_toolchain_override(delegate: &dyn LspAdapterDelegate) -> bool {
243        for file_name in ["rust-toolchain.toml", "rust-toolchain"] {
244            if fs::metadata(delegate.resolve_relative_path(PathBuf::from(file_name)))
245                .await
246                .is_ok()
247            {
248                return true;
249            }
250        }
251
252        false
253    }
254
255    async fn build_asset_name() -> String {
256        let extension = match Self::GITHUB_ASSET_KIND {
257            AssetKind::TarGz => "tar.gz",
258            AssetKind::TarBz2 => "tar.bz2",
259            AssetKind::Gz => "gz",
260            AssetKind::Zip => "zip",
261        };
262
263        #[cfg(target_os = "linux")]
264        let arch_server_name = Self::build_arch_server_name_linux().await;
265        #[cfg(not(target_os = "linux"))]
266        let arch_server_name = Self::ARCH_SERVER_NAME.to_string();
267
268        format!(
269            "{}-{}-{}.{}",
270            SERVER_NAME,
271            std::env::consts::ARCH,
272            &arch_server_name,
273            extension
274        )
275    }
276}
277
278pub(crate) struct CargoManifestProvider;
279
280impl ManifestProvider for CargoManifestProvider {
281    fn name(&self) -> ManifestName {
282        SharedString::new_static("Cargo.toml").into()
283    }
284
285    fn search(
286        &self,
287        ManifestQuery {
288            path,
289            depth,
290            delegate,
291        }: ManifestQuery,
292    ) -> Option<Arc<RelPath>> {
293        let mut outermost_cargo_toml = None;
294        for path in path.ancestors().take(depth) {
295            let p = path.join(RelPath::from_unix_str("Cargo.toml").unwrap());
296            if delegate.exists(&p, Some(false)) {
297                outermost_cargo_toml = Some(Arc::from(path));
298            }
299        }
300
301        outermost_cargo_toml
302    }
303}
304
305#[async_trait(?Send)]
306impl LspAdapter for RustLspAdapter {
307    fn name(&self) -> LanguageServerName {
308        SERVER_NAME
309    }
310
311    fn disk_based_diagnostic_sources(&self) -> Vec<String> {
312        vec![CARGO_DIAGNOSTICS_SOURCE_NAME.to_owned()]
313    }
314
315    fn disk_based_diagnostics_progress_token(&self) -> Option<String> {
316        Some("rust-analyzer/flycheck".into())
317    }
318
319    fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams, _: LanguageServerId) {
320        static REGEX: LazyLock<Regex> =
321            LazyLock::new(|| Regex::new(r"(?m)`([^`]+)\n`$").expect("Failed to create REGEX"));
322
323        for diagnostic in &mut params.diagnostics {
324            for message in diagnostic
325                .related_information
326                .iter_mut()
327                .flatten()
328                .map(|info| &mut info.message)
329                .chain([&mut diagnostic.message])
330            {
331                if let Cow::Owned(sanitized) = REGEX.replace_all(message, "`$1`") {
332                    *message = sanitized;
333                }
334            }
335        }
336    }
337
338    fn diagnostic_message_to_markdown(&self, message: &str) -> Option<String> {
339        static REGEX: LazyLock<Regex> =
340            LazyLock::new(|| Regex::new(r"(?m)\n *").expect("Failed to create REGEX"));
341        Some(REGEX.replace_all(message, "\n\n").to_string())
342    }
343
344    async fn label_for_completion(
345        &self,
346        completion: &lsp::CompletionItem,
347        language: &Arc<Language>,
348    ) -> Option<CodeLabel> {
349        // rust-analyzer calls these detail left and detail right in terms of where it expects things to be rendered
350        // this usually contains signatures of the thing to be completed
351        let detail_right = completion
352            .label_details
353            .as_ref()
354            .and_then(|detail| detail.description.as_ref())
355            .or(completion.detail.as_ref())
356            .map(|detail| detail.trim());
357        // this tends to contain alias and import information
358        let mut detail_left = completion
359            .label_details
360            .as_ref()
361            .and_then(|detail| detail.detail.as_deref());
362        let mk_label = |text: String, filter_range: &dyn Fn() -> Range<usize>, runs| {
363            let filter_range = completion
364                .filter_text
365                .as_deref()
366                .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
367                .or_else(|| {
368                    text.find(&completion.label)
369                        .map(|ix| ix..ix + completion.label.len())
370                })
371                .unwrap_or_else(filter_range);
372
373            CodeLabel::new(text, filter_range, runs)
374        };
375        let mut label = match (detail_right, completion.kind) {
376            (Some(signature), Some(lsp::CompletionItemKind::FIELD)) => {
377                let name = &completion.label;
378                let text = format!("{name}: {signature}");
379                let prefix = "struct S { ";
380                let source = Rope::from_iter([prefix, &text, " }"]);
381                let runs =
382                    language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
383                mk_label(text, &|| 0..completion.label.len(), runs)
384            }
385            (
386                Some(signature),
387                Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE),
388            ) if completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => {
389                let name = &completion.label;
390                let text = format!("{name}: {signature}",);
391                let prefix = "let ";
392                let source = Rope::from_iter([prefix, &text, " = ();"]);
393                let runs =
394                    language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
395                mk_label(text, &|| 0..completion.label.len(), runs)
396            }
397            (
398                function_signature,
399                Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD),
400            ) => {
401                const FUNCTION_PREFIXES: [&str; 6] = [
402                    "async fn",
403                    "async unsafe fn",
404                    "const fn",
405                    "const unsafe fn",
406                    "unsafe fn",
407                    "fn",
408                ];
409                let fn_prefixed = FUNCTION_PREFIXES.iter().find_map(|&prefix| {
410                    function_signature?
411                        .strip_prefix(prefix)
412                        .map(|suffix| (prefix, suffix))
413                });
414                let label = if let Some(label) = completion
415                    .label
416                    .strip_suffix("(…)")
417                    .or_else(|| completion.label.strip_suffix("()"))
418                {
419                    label
420                } else {
421                    &completion.label
422                };
423
424                static FULL_SIGNATURE_REGEX: LazyLock<Regex> =
425                    LazyLock::new(|| Regex::new(r"fn (.+?)\(").expect("Failed to create REGEX"));
426                if let Some((function_signature, match_)) = function_signature
427                    .filter(|it| it.contains(&label))
428                    .and_then(|it| Some((it, FULL_SIGNATURE_REGEX.find(it)?)))
429                {
430                    let source = Rope::from(function_signature);
431                    let runs = language.highlight_text(&source, 0..function_signature.len());
432                    mk_label(
433                        function_signature.to_owned(),
434                        &|| match_.range().start + 3..match_.range().end - 1,
435                        runs,
436                    )
437                } else if let Some((prefix, suffix)) = fn_prefixed {
438                    let text = format!("{label}{suffix}");
439                    let source = Rope::from_iter([prefix, " ", &text, " {}"]);
440                    let run_start = prefix.len() + 1;
441                    let runs = language.highlight_text(&source, run_start..run_start + text.len());
442                    mk_label(text, &|| 0..label.len(), runs)
443                } else if completion
444                    .detail
445                    .as_ref()
446                    .is_some_and(|detail| detail.starts_with("macro_rules! "))
447                {
448                    let text = completion.label.clone();
449                    let len = text.len();
450                    let source = Rope::from(text.as_str());
451                    let runs = language.highlight_text(&source, 0..len);
452                    mk_label(text, &|| 0..completion.label.len(), runs)
453                } else if detail_left.is_none() {
454                    return None;
455                } else {
456                    mk_label(
457                        completion.label.clone(),
458                        &|| 0..completion.label.len(),
459                        vec![],
460                    )
461                }
462            }
463            (_, kind) => {
464                let mut label;
465                let mut runs = vec![];
466
467                if completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
468                    && let Some(
469                        lsp::CompletionTextEdit::InsertAndReplace(lsp::InsertReplaceEdit {
470                            new_text,
471                            ..
472                        })
473                        | lsp::CompletionTextEdit::Edit(lsp::TextEdit { new_text, .. }),
474                    ) = completion.text_edit.as_ref()
475                    && let Ok(mut snippet) = snippet::Snippet::parse(new_text)
476                    && snippet.tabstops.len() > 1
477                {
478                    label = String::new();
479
480                    // we never display the final tabstop
481                    snippet.tabstops.remove(snippet.tabstops.len() - 1);
482
483                    let mut text_pos = 0;
484
485                    let mut all_stop_ranges = snippet
486                        .tabstops
487                        .into_iter()
488                        .flat_map(|stop| stop.ranges)
489                        .collect::<SmallVec<[_; 8]>>();
490                    all_stop_ranges.sort_unstable_by_key(|a| (a.start, Reverse(a.end)));
491
492                    // Placeholders may nest, e.g. `$2` inside `${1:"$2"}`
493                    struct OpenPlaceholder {
494                        snippet_text_end: usize,
495                        label_run_start: usize,
496                    }
497                    let mut open_placeholders = SmallVec::<[OpenPlaceholder; 4]>::new();
498
499                    for range in &all_stop_ranges {
500                        let start_pos = range.start as usize;
501                        let end_pos = range.end as usize;
502
503                        while let Some(placeholder) = open_placeholders.last() {
504                            if placeholder.snippet_text_end > start_pos {
505                                break;
506                            }
507                            label.push_str(&snippet.text[text_pos..placeholder.snippet_text_end]);
508                            text_pos = placeholder.snippet_text_end;
509                            runs.push((
510                                placeholder.label_run_start..label.len(),
511                                HighlightId::TABSTOP_REPLACE_ID,
512                            ));
513                            open_placeholders.pop();
514                        }
515
516                        label.push_str(&snippet.text[text_pos..start_pos]);
517                        text_pos = start_pos;
518
519                        if start_pos == end_pos {
520                            let caret_start = label.len();
521                            label.push('…');
522                            runs.push((caret_start..label.len(), HighlightId::TABSTOP_INSERT_ID));
523                        } else {
524                            open_placeholders.push(OpenPlaceholder {
525                                snippet_text_end: end_pos,
526                                label_run_start: label.len(),
527                            });
528                        }
529                    }
530
531                    while let Some(placeholder) = open_placeholders.pop() {
532                        label.push_str(&snippet.text[text_pos..placeholder.snippet_text_end]);
533                        text_pos = placeholder.snippet_text_end;
534                        runs.push((
535                            placeholder.label_run_start..label.len(),
536                            HighlightId::TABSTOP_REPLACE_ID,
537                        ));
538                    }
539
540                    label.push_str(&snippet.text[text_pos..]);
541                    runs.sort_unstable_by_key(|(range, _)| (range.start, Reverse(range.end)));
542
543                    if detail_left.is_some_and(|detail_left| detail_left == new_text) {
544                        // We only include the left detail if it isn't the snippet again
545                        detail_left.take();
546                    }
547
548                    runs.extend(language.highlight_text(&Rope::from(&label), 0..label.len()));
549                } else {
550                    let highlight_name = kind.and_then(|kind| match kind {
551                        lsp::CompletionItemKind::STRUCT
552                        | lsp::CompletionItemKind::INTERFACE
553                        | lsp::CompletionItemKind::ENUM => Some("type"),
554                        lsp::CompletionItemKind::ENUM_MEMBER => Some("variant"),
555                        lsp::CompletionItemKind::KEYWORD => Some("keyword"),
556                        lsp::CompletionItemKind::VALUE | lsp::CompletionItemKind::CONSTANT => {
557                            Some("constant")
558                        }
559                        _ => None,
560                    });
561
562                    label = completion.label.clone();
563
564                    if let Some(highlight_name) = highlight_name {
565                        let highlight_id =
566                            language.grammar()?.highlight_id_for_name(highlight_name)?;
567                        runs.push((
568                            0..label.rfind('(').unwrap_or(completion.label.len()),
569                            highlight_id,
570                        ));
571                    } else if detail_left.is_none()
572                        && kind != Some(lsp::CompletionItemKind::SNIPPET)
573                    {
574                        return None;
575                    }
576                }
577
578                let label_len = label.len();
579
580                mk_label(label, &|| 0..label_len, runs)
581            }
582        };
583
584        if let Some(detail_left) = detail_left {
585            label.text.push(' ');
586            if !detail_left.starts_with('(') {
587                label.text.push('(');
588            }
589            label.text.push_str(detail_left);
590            if !detail_left.ends_with(')') {
591                label.text.push(')');
592            }
593        }
594
595        Some(label)
596    }
597
598    async fn initialization_options_schema(
599        self: Arc<Self>,
600        delegate: &Arc<dyn LspAdapterDelegate>,
601        cached_binary: OwnedMutexGuard<Option<(bool, LanguageServerBinary)>>,
602        cx: &mut AsyncApp,
603    ) -> Option<serde_json::Value> {
604        let binary = self
605            .get_language_server_command(
606                delegate.clone(),
607                None,
608                LanguageServerBinaryOptions {
609                    allow_path_lookup: true,
610                    allow_binary_download: false,
611                    pre_release: false,
612                },
613                cached_binary,
614                cx.clone(),
615            )
616            .await
617            .0
618            .ok()?;
619
620        let mut command = util::command::new_command(&binary.path);
621        command
622            .arg("--print-config-schema")
623            .stdout(Stdio::piped())
624            .stderr(Stdio::piped());
625        let cmd = command
626            .spawn()
627            .map_err(|e| log::debug!("failed to spawn command {command:?}: {e}"))
628            .ok()?;
629        let output = cmd
630            .output()
631            .await
632            .map_err(|e| log::debug!("failed to execute command {command:?}: {e}"))
633            .ok()?;
634        if !output.status.success() {
635            return None;
636        }
637
638        let raw_schema: serde_json::Value = serde_json::from_slice(output.stdout.as_slice())
639            .map_err(|e| log::debug!("failed to parse rust-analyzer's JSON schema output: {e}"))
640            .ok()?;
641
642        // Convert rust-analyzer's array-based schema format to nested JSON Schema
643        let converted_schema = Self::convert_rust_analyzer_schema(&raw_schema);
644        Some(converted_schema)
645    }
646
647    async fn label_for_symbol(
648        &self,
649        symbol: &language::Symbol,
650        language: &Arc<Language>,
651    ) -> Option<CodeLabel> {
652        let name = &symbol.name;
653        let (prefix, suffix) = match symbol.kind {
654            language::SymbolKind::Method | language::SymbolKind::Function => ("fn ", "();"),
655            language::SymbolKind::Struct => ("struct ", ";"),
656            language::SymbolKind::Enum => ("enum ", "{}"),
657            language::SymbolKind::Interface => ("trait ", "{}"),
658            language::SymbolKind::Constant => ("const ", ":()=();"),
659            language::SymbolKind::Module => ("mod ", ";"),
660            language::SymbolKind::Package => ("extern crate ", ";"),
661            language::SymbolKind::TypeParameter => ("type ", "=();"),
662            language::SymbolKind::EnumMember => {
663                let prefix = "enum E {";
664                return Some(CodeLabel::new(
665                    name.to_string(),
666                    0..name.len(),
667                    language.highlight_text(
668                        &Rope::from_iter([prefix, name, "}"]),
669                        prefix.len()..prefix.len() + name.len(),
670                    ),
671                ));
672            }
673            _ => return None,
674        };
675
676        let filter_range = prefix.len()..prefix.len() + name.len();
677        let display_range = 0..filter_range.end;
678        Some(CodeLabel::new(
679            format!("{prefix}{name}"),
680            filter_range,
681            language.highlight_text(&Rope::from_iter([prefix, name, suffix]), display_range),
682        ))
683    }
684
685    fn prepare_initialize_params(
686        &self,
687        mut original: InitializeParams,
688        cx: &App,
689    ) -> Result<InitializeParams> {
690        let enable_lsp_tasks = ProjectSettings::get_global(cx)
691            .lsp
692            .get(&SERVER_NAME)
693            .is_some_and(|s| s.enable_lsp_tasks);
694
695        let mut commands = vec![
696            "rust-analyzer.showReferences",
697            "rust-analyzer.gotoLocation",
698            "rust-analyzer.triggerParameterHints",
699            "rust-analyzer.rename",
700        ];
701        if enable_lsp_tasks {
702            commands.push("rust-analyzer.runSingle");
703        }
704
705        let mut experimental = json!({
706            "commands": {
707                "commands": commands,
708            }
709        });
710        if enable_lsp_tasks {
711            experimental["runnables"] = json!({ "kinds": ["cargo", "shell"] });
712        }
713
714        if let Some(original_experimental) = &mut original.capabilities.experimental {
715            merge_json_value_into(experimental, original_experimental);
716        } else {
717            original.capabilities.experimental = Some(experimental);
718        }
719
720        Ok(original)
721    }
722
723    fn client_command(
724        &self,
725        command_name: &str,
726        arguments: &[serde_json::Value],
727    ) -> Option<ClientCommand> {
728        match command_name {
729            "rust-analyzer.showReferences" => Some(ClientCommand::ShowLocations),
730            "rust-analyzer.runSingle" => {
731                let first_arg = arguments.first()?;
732                let runnable =
733                    serde_json::from_value::<lsp_ext_command::Runnable>(first_arg.clone()).ok()?;
734                let template =
735                    lsp_ext_command::runnable_to_task_template(runnable.label, runnable.args);
736                Some(ClientCommand::ScheduleTask(template))
737            }
738            _ => None,
739        }
740    }
741}
742
743impl LspInstaller for RustLspAdapter {
744    type BinaryVersion = GitHubLspBinaryVersion;
745    async fn check_if_user_installed(
746        &self,
747        delegate: &Arc<dyn LspAdapterDelegate>,
748        _: Option<Toolchain>,
749        cx: &AsyncApp,
750    ) -> Option<LanguageServerBinary> {
751        let delegate = delegate.clone();
752        cx.background_spawn(async move {
753            let env = delegate.shell_env().await;
754            if let Some(path) = Self::rustup_rust_analyzer_for_worktree(delegate.as_ref()).await {
755                let result = delegate
756                    .try_exec(LanguageServerBinary {
757                        path: path.clone(),
758                        arguments: vec!["--help".into()],
759                        env: Some(env.clone()),
760                    })
761                    .await;
762                if result.is_ok() {
763                    log::debug!("found rust-analyzer in rustup toolchain override");
764                    return Some(LanguageServerBinary {
765                        path,
766                        env: Some(env),
767                        arguments: vec![],
768                    });
769                }
770            }
771
772            let path = delegate.which("rust-analyzer".as_ref()).await?;
773
774            // It is surprisingly common for ~/.cargo/bin/rust-analyzer to be a symlink to
775            // /usr/bin/rust-analyzer that fails when you run it; so we need to test it.
776            log::debug!("found rust-analyzer in PATH. trying to run `rust-analyzer --help`");
777            let result = delegate
778                .try_exec(LanguageServerBinary {
779                    path: path.clone(),
780                    arguments: vec!["--help".into()],
781                    env: Some(env.clone()),
782                })
783                .await;
784            if let Err(err) = result {
785                log::debug!(
786                    "failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}",
787                    path,
788                    err
789                );
790                return None;
791            }
792
793            Some(LanguageServerBinary {
794                path,
795                env: Some(env),
796                arguments: vec![],
797            })
798        })
799        .await
800    }
801
802    async fn fetch_latest_server_version(
803        &self,
804        delegate: &Arc<dyn LspAdapterDelegate>,
805        pre_release: bool,
806        _: &mut AsyncApp,
807    ) -> Result<GitHubLspBinaryVersion> {
808        let release = latest_github_release(
809            "rust-lang/rust-analyzer",
810            true,
811            pre_release,
812            delegate.http_client(),
813        )
814        .await?;
815        let asset_name = Self::build_asset_name().await;
816        let asset = release
817            .assets
818            .into_iter()
819            .find(|asset| asset.name == asset_name)
820            .with_context(|| format!("no asset found matching `{asset_name:?}`"))?;
821        Ok(GitHubLspBinaryVersion {
822            name: release.tag_name,
823            url: asset.browser_download_url,
824            digest: asset.digest,
825        })
826    }
827
828    fn fetch_server_binary(
829        &self,
830        version: GitHubLspBinaryVersion,
831        container_dir: PathBuf,
832        delegate: &Arc<dyn LspAdapterDelegate>,
833    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
834        let delegate = delegate.clone();
835
836        async move {
837            let GitHubLspBinaryVersion {
838                name,
839                url,
840                digest: expected_digest,
841            } = version;
842            let destination_path = container_dir.join(format!("rust-analyzer-{name}"));
843            let server_path = match Self::GITHUB_ASSET_KIND {
844                AssetKind::TarGz | AssetKind::TarBz2 | AssetKind::Gz => destination_path.clone(), // Tar and gzip extract in place.
845                AssetKind::Zip => destination_path.clone().join("rust-analyzer.exe"), // zip contains a .exe
846            };
847
848            let binary = LanguageServerBinary {
849                path: server_path.clone(),
850                env: None,
851                arguments: Default::default(),
852            };
853
854            let metadata_path = destination_path.with_extension("metadata");
855            let metadata = GithubBinaryMetadata::read_from_file(&metadata_path)
856                .await
857                .ok();
858            if let Some(metadata) = metadata {
859                let validity_check = async || {
860                    delegate
861                        .try_exec(LanguageServerBinary {
862                            path: server_path.clone(),
863                            arguments: vec!["--version".into()],
864                            env: None,
865                        })
866                        .await
867                        .inspect_err(|err| {
868                            log::warn!(
869                                "Unable to run {server_path:?} asset, redownloading: {err:#}",
870                            )
871                        })
872                };
873                if let (Some(actual_digest), Some(expected_digest)) =
874                    (&metadata.digest, &expected_digest)
875                {
876                    if actual_digest == expected_digest {
877                        if validity_check().await.is_ok() {
878                            return Ok(binary);
879                        }
880                    } else {
881                        log::info!(
882                            "SHA-256 mismatch for {destination_path:?} asset, downloading new asset. Expected: {expected_digest}, Got: {actual_digest}"
883                        );
884                    }
885                } else if validity_check().await.is_ok() {
886                    return Ok(binary);
887                }
888            }
889
890            download_server_binary(
891                &*delegate.http_client(),
892                &url,
893                expected_digest.as_deref(),
894                &destination_path,
895                Self::GITHUB_ASSET_KIND,
896            )
897            .await?;
898            make_file_executable(&server_path).await?;
899            remove_matching(&container_dir, |path| path != destination_path).await;
900            GithubBinaryMetadata::write_to_file(
901                &GithubBinaryMetadata {
902                    metadata_version: 1,
903                    digest: expected_digest,
904                },
905                &metadata_path,
906            )
907            .await?;
908
909            Ok(LanguageServerBinary {
910                path: server_path,
911                env: None,
912                arguments: Default::default(),
913            })
914        }
915    }
916
917    async fn cached_server_binary(
918        &self,
919        container_dir: PathBuf,
920        _: &dyn LspAdapterDelegate,
921    ) -> Option<LanguageServerBinary> {
922        get_cached_server_binary(container_dir).await
923    }
924}
925
926pub(crate) struct RustContextProvider;
927
928const RUST_PACKAGE_TASK_VARIABLE: VariableName =
929    VariableName::Custom(Cow::Borrowed("RUST_PACKAGE"));
930
931/// The bin name corresponding to the current file in Cargo.toml
932const RUST_BIN_NAME_TASK_VARIABLE: VariableName =
933    VariableName::Custom(Cow::Borrowed("RUST_BIN_NAME"));
934
935/// The bin kind (bin/example) corresponding to the current file in Cargo.toml
936const RUST_BIN_KIND_TASK_VARIABLE: VariableName =
937    VariableName::Custom(Cow::Borrowed("RUST_BIN_KIND"));
938
939/// The flag to list required features for executing a bin, if any
940const RUST_BIN_REQUIRED_FEATURES_FLAG_TASK_VARIABLE: VariableName =
941    VariableName::Custom(Cow::Borrowed("RUST_BIN_REQUIRED_FEATURES_FLAG"));
942
943/// The list of required features for executing a bin, if any
944const RUST_BIN_REQUIRED_FEATURES_TASK_VARIABLE: VariableName =
945    VariableName::Custom(Cow::Borrowed("RUST_BIN_REQUIRED_FEATURES"));
946
947const RUST_TEST_FRAGMENT_TASK_VARIABLE: VariableName =
948    VariableName::Custom(Cow::Borrowed("RUST_TEST_FRAGMENT"));
949
950const RUST_DOC_TEST_NAME_TASK_VARIABLE: VariableName =
951    VariableName::Custom(Cow::Borrowed("RUST_DOC_TEST_NAME"));
952
953const RUST_TEST_NAME_TASK_VARIABLE: VariableName =
954    VariableName::Custom(Cow::Borrowed("RUST_TEST_NAME"));
955
956const RUST_MANIFEST_DIRNAME_TASK_VARIABLE: VariableName =
957    VariableName::Custom(Cow::Borrowed("RUST_MANIFEST_DIRNAME"));
958
959impl ContextProvider for RustContextProvider {
960    fn build_context(
961        &self,
962        task_variables: &TaskVariables,
963        location: ContextLocation<'_>,
964        project_env: Option<HashMap<String, String>>,
965        _: Arc<dyn LanguageToolchainStore>,
966        cx: &mut gpui::App,
967    ) -> Task<Result<TaskVariables>> {
968        let local_abs_path = location
969            .file_location
970            .buffer
971            .read(cx)
972            .file()
973            .and_then(|file| Some(file.as_local()?.abs_path(cx)));
974
975        let mut variables = TaskVariables::default();
976
977        if let (Some(path), Some(stem)) = (&local_abs_path, task_variables.get(&VariableName::Stem))
978        {
979            let fragment = test_fragment(&variables, path, stem);
980            variables.insert(RUST_TEST_FRAGMENT_TASK_VARIABLE, fragment);
981        };
982        if let Some(test_name) =
983            task_variables.get(&VariableName::Custom(Cow::Borrowed("_test_name")))
984        {
985            variables.insert(RUST_TEST_NAME_TASK_VARIABLE, test_name.into());
986        }
987        if let Some(doc_test_name) =
988            task_variables.get(&VariableName::Custom(Cow::Borrowed("_doc_test_name")))
989        {
990            variables.insert(RUST_DOC_TEST_NAME_TASK_VARIABLE, doc_test_name.into());
991        }
992        cx.background_spawn(async move {
993            if let Some(path) = local_abs_path
994                .as_deref()
995                .and_then(|local_abs_path| local_abs_path.parent())
996                && let Some(package_name) =
997                    human_readable_package_name(path, project_env.as_ref()).await
998            {
999                variables.insert(RUST_PACKAGE_TASK_VARIABLE.clone(), package_name);
1000            }
1001            if let Some(path) = local_abs_path.as_ref()
1002                && let Some((target, manifest_path)) =
1003                    target_info_from_abs_path(path, project_env.as_ref()).await?
1004            {
1005                if let Some(target) = target {
1006                    variables.extend(TaskVariables::from_iter([
1007                        (RUST_PACKAGE_TASK_VARIABLE.clone(), target.package_name),
1008                        (RUST_BIN_NAME_TASK_VARIABLE.clone(), target.target_name),
1009                        (
1010                            RUST_BIN_KIND_TASK_VARIABLE.clone(),
1011                            target.target_kind.to_string(),
1012                        ),
1013                    ]));
1014                    if target.required_features.is_empty() {
1015                        variables.insert(RUST_BIN_REQUIRED_FEATURES_FLAG_TASK_VARIABLE, "".into());
1016                        variables.insert(RUST_BIN_REQUIRED_FEATURES_TASK_VARIABLE, "".into());
1017                    } else {
1018                        variables.insert(
1019                            RUST_BIN_REQUIRED_FEATURES_FLAG_TASK_VARIABLE.clone(),
1020                            "--features".to_string(),
1021                        );
1022                        variables.insert(
1023                            RUST_BIN_REQUIRED_FEATURES_TASK_VARIABLE.clone(),
1024                            target.required_features.join(","),
1025                        );
1026                    }
1027                }
1028                variables.extend(TaskVariables::from_iter([(
1029                    RUST_MANIFEST_DIRNAME_TASK_VARIABLE.clone(),
1030                    manifest_path.to_string_lossy().into_owned(),
1031                )]));
1032            }
1033            Ok(variables)
1034        })
1035    }
1036
1037    fn associated_tasks(
1038        &self,
1039        buffer: Option<Entity<Buffer>>,
1040        cx: &App,
1041    ) -> Task<Option<TaskTemplates>> {
1042        const DEFAULT_RUN_NAME_STR: &str = "RUST_DEFAULT_PACKAGE_RUN";
1043        const CUSTOM_TARGET_DIR: &str = "RUST_TARGET_DIR";
1044
1045        let language = LanguageName::new_static("Rust");
1046        let settings = LanguageSettings::resolve(buffer.map(|b| b.read(cx)), Some(&language), cx);
1047        let package_to_run = settings.tasks.variables.get(DEFAULT_RUN_NAME_STR).cloned();
1048        let custom_target_dir = settings.tasks.variables.get(CUSTOM_TARGET_DIR).cloned();
1049        let run_task_args = if let Some(package_to_run) = package_to_run {
1050            vec!["run".into(), "-p".into(), package_to_run]
1051        } else {
1052            vec!["run".into()]
1053        };
1054        let mut task_templates = vec![
1055            TaskTemplate {
1056                label: format!(
1057                    "Check (package: {})",
1058                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
1059                ),
1060                command: "cargo".into(),
1061                args: vec![
1062                    "check".into(),
1063                    "-p".into(),
1064                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
1065                ],
1066                cwd: Some("$ZED_DIRNAME".to_owned()),
1067                ..TaskTemplate::default()
1068            },
1069            TaskTemplate {
1070                label: "Check all targets (workspace)".into(),
1071                command: "cargo".into(),
1072                args: vec!["check".into(), "--workspace".into(), "--all-targets".into()],
1073                cwd: Some("$ZED_DIRNAME".to_owned()),
1074                ..TaskTemplate::default()
1075            },
1076            TaskTemplate {
1077                label: format!(
1078                    "Test '{}' (package: {})",
1079                    RUST_TEST_NAME_TASK_VARIABLE.template_value(),
1080                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
1081                ),
1082                command: "cargo".into(),
1083                args: vec![
1084                    "test".into(),
1085                    "-p".into(),
1086                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
1087                    "--".into(),
1088                    "--nocapture".into(),
1089                    "--include-ignored".into(),
1090                    RUST_TEST_NAME_TASK_VARIABLE.template_value(),
1091                ],
1092                tags: vec!["rust-test".to_owned()],
1093                cwd: Some(RUST_MANIFEST_DIRNAME_TASK_VARIABLE.template_value()),
1094                ..TaskTemplate::default()
1095            },
1096            TaskTemplate {
1097                label: format!(
1098                    "Doc test '{}' (package: {})",
1099                    RUST_DOC_TEST_NAME_TASK_VARIABLE.template_value(),
1100                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
1101                ),
1102                command: "cargo".into(),
1103                args: vec![
1104                    "test".into(),
1105                    "--doc".into(),
1106                    "-p".into(),
1107                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
1108                    "--".into(),
1109                    "--nocapture".into(),
1110                    "--include-ignored".into(),
1111                    RUST_DOC_TEST_NAME_TASK_VARIABLE.template_value(),
1112                ],
1113                tags: vec!["rust-doc-test".to_owned()],
1114                cwd: Some(RUST_MANIFEST_DIRNAME_TASK_VARIABLE.template_value()),
1115                ..TaskTemplate::default()
1116            },
1117            TaskTemplate {
1118                label: format!(
1119                    "Test mod '{}' (package: {})",
1120                    VariableName::Stem.template_value(),
1121                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
1122                ),
1123                command: "cargo".into(),
1124                args: vec![
1125                    "test".into(),
1126                    "-p".into(),
1127                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
1128                    "--".into(),
1129                    RUST_TEST_FRAGMENT_TASK_VARIABLE.template_value(),
1130                ],
1131                tags: vec!["rust-mod-test".to_owned()],
1132                cwd: Some(RUST_MANIFEST_DIRNAME_TASK_VARIABLE.template_value()),
1133                ..TaskTemplate::default()
1134            },
1135            TaskTemplate {
1136                label: format!(
1137                    "Run {} {} (package: {})",
1138                    RUST_BIN_KIND_TASK_VARIABLE.template_value(),
1139                    RUST_BIN_NAME_TASK_VARIABLE.template_value(),
1140                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
1141                ),
1142                command: "cargo".into(),
1143                args: vec![
1144                    "run".into(),
1145                    "-p".into(),
1146                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
1147                    format!("--{}", RUST_BIN_KIND_TASK_VARIABLE.template_value()),
1148                    RUST_BIN_NAME_TASK_VARIABLE.template_value(),
1149                    RUST_BIN_REQUIRED_FEATURES_FLAG_TASK_VARIABLE.template_value(),
1150                    RUST_BIN_REQUIRED_FEATURES_TASK_VARIABLE.template_value(),
1151                ],
1152                cwd: Some(RUST_MANIFEST_DIRNAME_TASK_VARIABLE.template_value()),
1153                tags: vec!["rust-main".to_owned()],
1154                ..TaskTemplate::default()
1155            },
1156            TaskTemplate {
1157                label: format!(
1158                    "Test (package: {})",
1159                    RUST_PACKAGE_TASK_VARIABLE.template_value()
1160                ),
1161                command: "cargo".into(),
1162                args: vec![
1163                    "test".into(),
1164                    "-p".into(),
1165                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
1166                ],
1167                cwd: Some(RUST_MANIFEST_DIRNAME_TASK_VARIABLE.template_value()),
1168                ..TaskTemplate::default()
1169            },
1170            TaskTemplate {
1171                label: "Run".into(),
1172                command: "cargo".into(),
1173                args: run_task_args,
1174                cwd: Some(RUST_MANIFEST_DIRNAME_TASK_VARIABLE.template_value()),
1175                ..TaskTemplate::default()
1176            },
1177            TaskTemplate {
1178                label: "Clean".into(),
1179                command: "cargo".into(),
1180                args: vec!["clean".into()],
1181                cwd: Some(RUST_MANIFEST_DIRNAME_TASK_VARIABLE.template_value()),
1182                ..TaskTemplate::default()
1183            },
1184        ];
1185
1186        if let Some(custom_target_dir) = custom_target_dir {
1187            task_templates = task_templates
1188                .into_iter()
1189                .map(|mut task_template| {
1190                    let mut args = task_template.args.split_off(1);
1191                    task_template.args.append(&mut vec![
1192                        "--target-dir".to_string(),
1193                        custom_target_dir.clone(),
1194                    ]);
1195                    task_template.args.append(&mut args);
1196
1197                    task_template
1198                })
1199                .collect();
1200        }
1201
1202        Task::ready(Some(TaskTemplates(task_templates)))
1203    }
1204
1205    fn lsp_task_source(&self) -> Option<LanguageServerName> {
1206        Some(SERVER_NAME)
1207    }
1208}
1209
1210/// Part of the data structure of Cargo metadata
1211#[derive(Debug, serde::Deserialize)]
1212struct CargoMetadata {
1213    packages: Vec<CargoPackage>,
1214}
1215
1216#[derive(Debug, serde::Deserialize)]
1217struct CargoPackage {
1218    id: String,
1219    targets: Vec<CargoTarget>,
1220    manifest_path: Arc<Path>,
1221}
1222
1223#[derive(Debug, serde::Deserialize)]
1224struct CargoTarget {
1225    name: String,
1226    kind: Vec<String>,
1227    src_path: String,
1228    #[serde(rename = "required-features", default)]
1229    required_features: Vec<String>,
1230}
1231
1232#[derive(Debug, PartialEq)]
1233enum TargetKind {
1234    Bin,
1235    Example,
1236}
1237
1238impl Display for TargetKind {
1239    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1240        match self {
1241            TargetKind::Bin => write!(f, "bin"),
1242            TargetKind::Example => write!(f, "example"),
1243        }
1244    }
1245}
1246
1247impl TryFrom<&str> for TargetKind {
1248    type Error = ();
1249    fn try_from(value: &str) -> Result<Self, ()> {
1250        match value {
1251            "bin" => Ok(Self::Bin),
1252            "example" => Ok(Self::Example),
1253            _ => Err(()),
1254        }
1255    }
1256}
1257/// Which package and binary target are we in?
1258#[derive(Debug, PartialEq)]
1259struct TargetInfo {
1260    package_name: String,
1261    target_name: String,
1262    target_kind: TargetKind,
1263    required_features: Vec<String>,
1264}
1265
1266async fn target_info_from_abs_path(
1267    abs_path: &Path,
1268    project_env: Option<&HashMap<String, String>>,
1269) -> Result<Option<(Option<TargetInfo>, Arc<Path>)>> {
1270    let mut command = util::command::new_command("cargo");
1271    if let Some(envs) = project_env {
1272        command.envs(envs);
1273    }
1274    let output = command
1275        .current_dir(
1276            abs_path
1277                .parent()
1278                .ok_or_else(|| anyhow::anyhow!("failed to get parent directory"))?,
1279        )
1280        .arg("metadata")
1281        .arg("--no-deps")
1282        .arg("--format-version")
1283        .arg("1")
1284        .output()
1285        .await?;
1286
1287    if !output.status.success() {
1288        let stderr_msg = String::from_utf8_lossy(&output.stderr);
1289        anyhow::bail!("Cargo metadata failed\n {stderr_msg}");
1290    }
1291
1292    let metadata: CargoMetadata = serde_json::from_slice(&output.stdout)?;
1293    Ok(target_info_from_metadata(metadata, abs_path))
1294}
1295
1296fn target_info_from_metadata(
1297    metadata: CargoMetadata,
1298    abs_path: &Path,
1299) -> Option<(Option<TargetInfo>, Arc<Path>)> {
1300    let mut manifest_path = None;
1301    for package in metadata.packages {
1302        let Some(manifest_dir_path) = package.manifest_path.parent() else {
1303            continue;
1304        };
1305
1306        let Some(path_from_manifest_dir) = abs_path.strip_prefix(manifest_dir_path).ok() else {
1307            continue;
1308        };
1309        let candidate_path_length = path_from_manifest_dir.components().count();
1310        // Pick the most specific manifest path
1311        if let Some((path, current_length)) = &mut manifest_path {
1312            if candidate_path_length > *current_length {
1313                *path = Arc::from(manifest_dir_path);
1314                *current_length = candidate_path_length;
1315            }
1316        } else {
1317            manifest_path = Some((Arc::from(manifest_dir_path), candidate_path_length));
1318        };
1319
1320        for target in package.targets {
1321            let Some(bin_kind) = target
1322                .kind
1323                .iter()
1324                .find_map(|kind| TargetKind::try_from(kind.as_ref()).ok())
1325            else {
1326                continue;
1327            };
1328            let target_path = PathBuf::from(target.src_path);
1329            if target_path == abs_path {
1330                return manifest_path.map(|(path, _)| {
1331                    (
1332                        package_name_from_pkgid(&package.id).map(|package_name| TargetInfo {
1333                            package_name: package_name.to_owned(),
1334                            target_name: target.name,
1335                            required_features: target.required_features,
1336                            target_kind: bin_kind,
1337                        }),
1338                        path,
1339                    )
1340                });
1341            }
1342        }
1343    }
1344
1345    manifest_path.map(|(path, _)| (None, path))
1346}
1347
1348async fn human_readable_package_name(
1349    package_directory: &Path,
1350    project_env: Option<&HashMap<String, String>>,
1351) -> Option<String> {
1352    let mut command = util::command::new_command("cargo");
1353    if let Some(envs) = project_env {
1354        command.envs(envs);
1355    }
1356    let pkgid = String::from_utf8(
1357        command
1358            .current_dir(package_directory)
1359            .arg("pkgid")
1360            .output()
1361            .await
1362            .log_err()?
1363            .stdout,
1364    )
1365    .ok()?;
1366    Some(package_name_from_pkgid(&pkgid)?.to_owned())
1367}
1368
1369// For providing local `cargo check -p $pkgid` task, we do not need most of the information we have returned.
1370// Output example in the root of Zed project:
1371// ```sh
1372// ❯ cargo pkgid zed
1373// path+file:///absolute/path/to/project/zed/crates/zed#0.131.0
1374// ```
1375// Another variant, if a project has a custom package name or hyphen in the name:
1376// ```
1377// path+file:///absolute/path/to/project/custom-package#my-custom-package@0.1.0
1378// ```
1379//
1380// Extracts the package name from the output according to the spec:
1381// https://doc.rust-lang.org/cargo/reference/pkgid-spec.html#specification-grammar
1382fn package_name_from_pkgid(pkgid: &str) -> Option<&str> {
1383    fn split_off_suffix(input: &str, suffix_start: char) -> &str {
1384        match input.rsplit_once(suffix_start) {
1385            Some((without_suffix, _)) => without_suffix,
1386            None => input,
1387        }
1388    }
1389
1390    let (version_prefix, version_suffix) = pkgid.trim().rsplit_once('#')?;
1391    let package_name = match version_suffix.rsplit_once('@') {
1392        Some((custom_package_name, _version)) => custom_package_name,
1393        None => {
1394            let host_and_path = split_off_suffix(version_prefix, '?');
1395            let (_, package_name) = host_and_path.rsplit_once('/')?;
1396            package_name
1397        }
1398    };
1399    Some(package_name)
1400}
1401
1402async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
1403    let binary_result = maybe!(async {
1404        let mut last = None;
1405        let mut entries = fs::read_dir(&container_dir)
1406            .await
1407            .with_context(|| format!("listing {container_dir:?}"))?;
1408        while let Some(entry) = entries.next().await {
1409            let path = entry?.path();
1410            if path.extension().is_some_and(|ext| ext == "metadata") {
1411                continue;
1412            }
1413            last = Some(path);
1414        }
1415
1416        let path = match last {
1417            Some(last) => last,
1418            None => return Ok(None),
1419        };
1420        let path = match RustLspAdapter::GITHUB_ASSET_KIND {
1421            AssetKind::TarGz | AssetKind::TarBz2 | AssetKind::Gz => path, // Tar and gzip extract in place.
1422            AssetKind::Zip => path.join("rust-analyzer.exe"),             // zip contains a .exe
1423        };
1424
1425        anyhow::Ok(Some(LanguageServerBinary {
1426            path,
1427            env: None,
1428            arguments: Vec::new(),
1429        }))
1430    })
1431    .await;
1432
1433    match binary_result {
1434        Ok(Some(binary)) => Some(binary),
1435        Ok(None) => {
1436            log::info!("No cached rust-analyzer binary found");
1437            None
1438        }
1439        Err(e) => {
1440            log::error!("Failed to look up cached rust-analyzer binary: {e:#}");
1441            None
1442        }
1443    }
1444}
1445
1446fn test_fragment(variables: &TaskVariables, path: &Path, stem: &str) -> String {
1447    let fragment = if stem == "lib" {
1448        // This isn't quite right---it runs the tests for the entire library, rather than
1449        // just for the top-level `mod tests`. But we don't really have the means here to
1450        // filter out just that module.
1451        Some("--lib".to_owned())
1452    } else if stem == "mod" {
1453        maybe!({ Some(path.parent()?.file_name()?.to_string_lossy().into_owned()) })
1454    } else if stem == "main" {
1455        if let (Some(bin_name), Some(bin_kind)) = (
1456            variables.get(&RUST_BIN_NAME_TASK_VARIABLE),
1457            variables.get(&RUST_BIN_KIND_TASK_VARIABLE),
1458        ) {
1459            Some(format!("--{bin_kind}={bin_name}"))
1460        } else {
1461            None
1462        }
1463    } else {
1464        Some(stem.to_owned())
1465    };
1466    fragment.unwrap_or_else(|| "--".to_owned())
1467}
1468
1469#[cfg(test)]
1470mod tests {
1471    use std::num::NonZeroU32;
1472
1473    use super::*;
1474    use crate::language;
1475    use gpui::{BorrowAppContext, Hsla, TestAppContext};
1476    use lsp::CompletionItemLabelDetails;
1477    use pretty_assertions::assert_eq;
1478    use settings::SettingsStore;
1479    use theme::SyntaxTheme;
1480    use util::path;
1481
1482    #[gpui::test]
1483    async fn test_process_rust_diagnostics() {
1484        let mut params = lsp::PublishDiagnosticsParams {
1485            uri: lsp::Uri::from_file_path(path!("/a")).unwrap(),
1486            version: None,
1487            diagnostics: vec![
1488                // no newlines
1489                lsp::Diagnostic {
1490                    message: "use of moved value `a`".to_string(),
1491                    ..Default::default()
1492                },
1493                // newline at the end of a code span
1494                lsp::Diagnostic {
1495                    message: "consider importing this struct: `use b::c;\n`".to_string(),
1496                    ..Default::default()
1497                },
1498                // code span starting right after a newline
1499                lsp::Diagnostic {
1500                    message: "cannot borrow `self.d` as mutable\n`self` is a `&` reference"
1501                        .to_string(),
1502                    ..Default::default()
1503                },
1504            ],
1505        };
1506        RustLspAdapter.process_diagnostics(&mut params, LanguageServerId(0));
1507
1508        assert_eq!(params.diagnostics[0].message, "use of moved value `a`");
1509
1510        // remove trailing newline from code span
1511        assert_eq!(
1512            params.diagnostics[1].message,
1513            "consider importing this struct: `use b::c;`"
1514        );
1515
1516        // do not remove newline before the start of code span
1517        assert_eq!(
1518            params.diagnostics[2].message,
1519            "cannot borrow `self.d` as mutable\n`self` is a `&` reference"
1520        );
1521    }
1522
1523    #[gpui::test]
1524    async fn test_rust_label_for_completion() {
1525        let adapter = Arc::new(RustLspAdapter);
1526        let language = language("rust", tree_sitter_rust::LANGUAGE.into());
1527        let grammar = language.grammar().unwrap();
1528        let theme = SyntaxTheme::new_test([
1529            ("type", Hsla::default()),
1530            ("keyword", Hsla::default()),
1531            ("function", Hsla::default()),
1532            ("property", Hsla::default()),
1533        ]);
1534
1535        language.set_theme(&theme);
1536
1537        let highlight_function = grammar.highlight_id_for_name("function").unwrap();
1538        let highlight_type = grammar.highlight_id_for_name("type").unwrap();
1539        let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
1540        let highlight_field = grammar.highlight_id_for_name("property").unwrap();
1541
1542        assert_eq!(
1543            adapter
1544                .label_for_completion(
1545                    &lsp::CompletionItem {
1546                        kind: Some(lsp::CompletionItemKind::FUNCTION),
1547                        label: "hello(…)".to_string(),
1548                        label_details: Some(CompletionItemLabelDetails {
1549                            detail: Some("(use crate::foo)".into()),
1550                            description: Some("fn(&mut Option<T>) -> Vec<T>".to_string())
1551                        }),
1552                        ..Default::default()
1553                    },
1554                    &language
1555                )
1556                .await,
1557            Some(CodeLabel::new(
1558                "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
1559                0..5,
1560                vec![
1561                    (0..5, highlight_function),
1562                    (7..10, highlight_keyword),
1563                    (11..17, highlight_type),
1564                    (18..19, highlight_type),
1565                    (25..28, highlight_type),
1566                    (29..30, highlight_type),
1567                ],
1568            ))
1569        );
1570        assert_eq!(
1571            adapter
1572                .label_for_completion(
1573                    &lsp::CompletionItem {
1574                        kind: Some(lsp::CompletionItemKind::FUNCTION),
1575                        label: "hello(…)".to_string(),
1576                        label_details: Some(CompletionItemLabelDetails {
1577                            detail: Some("(use crate::foo)".into()),
1578                            description: Some("async fn(&mut Option<T>) -> Vec<T>".to_string()),
1579                        }),
1580                        ..Default::default()
1581                    },
1582                    &language
1583                )
1584                .await,
1585            Some(CodeLabel::new(
1586                "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
1587                0..5,
1588                vec![
1589                    (0..5, highlight_function),
1590                    (7..10, highlight_keyword),
1591                    (11..17, highlight_type),
1592                    (18..19, highlight_type),
1593                    (25..28, highlight_type),
1594                    (29..30, highlight_type),
1595                ],
1596            ))
1597        );
1598        assert_eq!(
1599            adapter
1600                .label_for_completion(
1601                    &lsp::CompletionItem {
1602                        kind: Some(lsp::CompletionItemKind::FIELD),
1603                        label: "len".to_string(),
1604                        detail: Some("usize".to_string()),
1605                        ..Default::default()
1606                    },
1607                    &language
1608                )
1609                .await,
1610            Some(CodeLabel::new(
1611                "len: usize".to_string(),
1612                0..3,
1613                vec![(0..3, highlight_field), (5..10, highlight_type),],
1614            ))
1615        );
1616
1617        assert_eq!(
1618            adapter
1619                .label_for_completion(
1620                    &lsp::CompletionItem {
1621                        kind: Some(lsp::CompletionItemKind::FUNCTION),
1622                        label: "hello(…)".to_string(),
1623                        label_details: Some(CompletionItemLabelDetails {
1624                            detail: Some("(use crate::foo)".to_string()),
1625                            description: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
1626                        }),
1627
1628                        ..Default::default()
1629                    },
1630                    &language
1631                )
1632                .await,
1633            Some(CodeLabel::new(
1634                "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
1635                0..5,
1636                vec![
1637                    (0..5, highlight_function),
1638                    (7..10, highlight_keyword),
1639                    (11..17, highlight_type),
1640                    (18..19, highlight_type),
1641                    (25..28, highlight_type),
1642                    (29..30, highlight_type),
1643                ],
1644            ))
1645        );
1646
1647        assert_eq!(
1648            adapter
1649                .label_for_completion(
1650                    &lsp::CompletionItem {
1651                        kind: Some(lsp::CompletionItemKind::FUNCTION),
1652                        label: "hello".to_string(),
1653                        label_details: Some(CompletionItemLabelDetails {
1654                            detail: Some("(use crate::foo)".to_string()),
1655                            description: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
1656                        }),
1657                        ..Default::default()
1658                    },
1659                    &language
1660                )
1661                .await,
1662            Some(CodeLabel::new(
1663                "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
1664                0..5,
1665                vec![
1666                    (0..5, highlight_function),
1667                    (7..10, highlight_keyword),
1668                    (11..17, highlight_type),
1669                    (18..19, highlight_type),
1670                    (25..28, highlight_type),
1671                    (29..30, highlight_type),
1672                ],
1673            ))
1674        );
1675
1676        assert_eq!(
1677            adapter
1678                .label_for_completion(
1679                    &lsp::CompletionItem {
1680                        kind: Some(lsp::CompletionItemKind::METHOD),
1681                        label: "await.as_deref_mut()".to_string(),
1682                        filter_text: Some("as_deref_mut".to_string()),
1683                        label_details: Some(CompletionItemLabelDetails {
1684                            detail: None,
1685                            description: Some("fn(&mut self) -> IterMut<'_, T>".to_string()),
1686                        }),
1687                        ..Default::default()
1688                    },
1689                    &language
1690                )
1691                .await,
1692            Some(CodeLabel::new(
1693                "await.as_deref_mut(&mut self) -> IterMut<'_, T>".to_string(),
1694                6..18,
1695                vec![
1696                    (6..18, HighlightId::new(2)),
1697                    (20..23, HighlightId::new(1)),
1698                    (33..40, HighlightId::new(0)),
1699                    (45..46, HighlightId::new(0))
1700                ],
1701            ))
1702        );
1703
1704        assert_eq!(
1705            adapter
1706                .label_for_completion(
1707                    &lsp::CompletionItem {
1708                        kind: Some(lsp::CompletionItemKind::METHOD),
1709                        label: "as_deref_mut()".to_string(),
1710                        filter_text: Some("as_deref_mut".to_string()),
1711                        label_details: Some(CompletionItemLabelDetails {
1712                            detail: None,
1713                            description: Some(
1714                                "pub fn as_deref_mut(&mut self) -> IterMut<'_, T>".to_string()
1715                            ),
1716                        }),
1717                        ..Default::default()
1718                    },
1719                    &language
1720                )
1721                .await,
1722            Some(CodeLabel::new(
1723                "pub fn as_deref_mut(&mut self) -> IterMut<'_, T>".to_string(),
1724                7..19,
1725                vec![
1726                    (0..3, HighlightId::new(1)),
1727                    (4..6, HighlightId::new(1)),
1728                    (7..19, HighlightId::new(2)),
1729                    (21..24, HighlightId::new(1)),
1730                    (34..41, HighlightId::new(0)),
1731                    (46..47, HighlightId::new(0))
1732                ],
1733            ))
1734        );
1735
1736        assert_eq!(
1737            adapter
1738                .label_for_completion(
1739                    &lsp::CompletionItem {
1740                        kind: Some(lsp::CompletionItemKind::METHOD),
1741                        label: "sync_all(…)".to_string(),
1742                        filter_text: Some("sync_allfsync".to_string()),
1743                        label_details: Some(CompletionItemLabelDetails {
1744                            detail: None,
1745                            description: Some(
1746                                "pub fn sync_all(&self) -> io::Result<()>".to_string()
1747                            ),
1748                        }),
1749                        ..Default::default()
1750                    },
1751                    &language
1752                )
1753                .await,
1754            Some(CodeLabel::new(
1755                "pub fn sync_all(&self) -> io::Result<()>".to_string(),
1756                7..15,
1757                vec![
1758                    (0..3, HighlightId::new(1)),
1759                    (4..6, HighlightId::new(1)),
1760                    (7..15, HighlightId::new(2)),
1761                    (30..36, HighlightId::new(0))
1762                ],
1763            ))
1764        );
1765
1766        assert_eq!(
1767            adapter
1768                .label_for_completion(
1769                    &lsp::CompletionItem {
1770                        kind: Some(lsp::CompletionItemKind::FIELD),
1771                        label: "inner_value".to_string(),
1772                        filter_text: Some("value".to_string()),
1773                        detail: Some("String".to_string()),
1774                        ..Default::default()
1775                    },
1776                    &language,
1777                )
1778                .await,
1779            Some(CodeLabel::new(
1780                "inner_value: String".to_string(),
1781                6..11,
1782                vec![(0..11, HighlightId::new(3)), (13..19, HighlightId::new(0))],
1783            ))
1784        );
1785
1786        // Snippet with insert tabstop (empty placeholder)
1787        assert_eq!(
1788            adapter
1789                .label_for_completion(
1790                    &lsp::CompletionItem {
1791                        kind: Some(lsp::CompletionItemKind::SNIPPET),
1792                        label: "println!".to_string(),
1793                        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1794                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1795                            range: lsp::Range::default(),
1796                            new_text: "println!(\"$1\", $2)$0".to_string(),
1797                        })),
1798                        ..Default::default()
1799                    },
1800                    &language,
1801                )
1802                .await,
1803            Some(CodeLabel::new(
1804                "println!(\"\", …)".to_string(),
1805                0..8,
1806                vec![
1807                    (10..13, HighlightId::TABSTOP_INSERT_ID),
1808                    (16..19, HighlightId::TABSTOP_INSERT_ID),
1809                    (0..7, HighlightId::new(2)),
1810                    (7..8, HighlightId::new(2)),
1811                ],
1812            ))
1813        );
1814
1815        // Snippet with replace tabstop (placeholder with default text)
1816        assert_eq!(
1817            adapter
1818                .label_for_completion(
1819                    &lsp::CompletionItem {
1820                        kind: Some(lsp::CompletionItemKind::SNIPPET),
1821                        label: "vec!".to_string(),
1822                        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1823                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1824                            range: lsp::Range::default(),
1825                            new_text: "vec![${1:elem}]$0".to_string(),
1826                        })),
1827                        ..Default::default()
1828                    },
1829                    &language,
1830                )
1831                .await,
1832            Some(CodeLabel::new(
1833                "vec![elem]".to_string(),
1834                0..4,
1835                vec![
1836                    (5..9, HighlightId::TABSTOP_REPLACE_ID),
1837                    (0..3, HighlightId::new(2)),
1838                    (3..4, HighlightId::new(2)),
1839                ],
1840            ))
1841        );
1842
1843        // Snippet with tabstop appearing more than once
1844        assert_eq!(
1845            adapter
1846                .label_for_completion(
1847                    &lsp::CompletionItem {
1848                        kind: Some(lsp::CompletionItemKind::SNIPPET),
1849                        label: "if let".to_string(),
1850                        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1851                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1852                            range: lsp::Range::default(),
1853                            new_text: "if let ${1:pat} = $1 {\n    $0\n}".to_string(),
1854                        })),
1855                        ..Default::default()
1856                    },
1857                    &language,
1858                )
1859                .await,
1860            Some(CodeLabel::new(
1861                "if let pat = … {\n    \n}".to_string(),
1862                0..6,
1863                vec![
1864                    (7..10, HighlightId::TABSTOP_REPLACE_ID),
1865                    (13..16, HighlightId::TABSTOP_INSERT_ID),
1866                    (0..2, HighlightId::new(1)),
1867                    (3..6, HighlightId::new(1)),
1868                ],
1869            ))
1870        );
1871
1872        // Snippet with tabstops not in left-to-right order
1873        assert_eq!(
1874            adapter
1875                .label_for_completion(
1876                    &lsp::CompletionItem {
1877                        kind: Some(lsp::CompletionItemKind::SNIPPET),
1878                        label: "for".to_string(),
1879                        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1880                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1881                            range: lsp::Range::default(),
1882                            new_text: "for ${2:item} in ${1:iter} {\n    $0\n}".to_string(),
1883                        })),
1884                        ..Default::default()
1885                    },
1886                    &language,
1887                )
1888                .await,
1889            Some(CodeLabel::new(
1890                "for item in iter {\n    \n}".to_string(),
1891                0..3,
1892                vec![
1893                    (4..8, HighlightId::TABSTOP_REPLACE_ID),
1894                    (12..16, HighlightId::TABSTOP_REPLACE_ID),
1895                    (0..3, HighlightId::new(1)),
1896                    (9..11, HighlightId::new(1)),
1897                ],
1898            ))
1899        );
1900
1901        assert_eq!(
1902            adapter
1903                .label_for_completion(
1904                    &lsp::CompletionItem {
1905                        kind: Some(lsp::CompletionItemKind::SNIPPET),
1906                        label: "unimplemented".to_string(),
1907                        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1908                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1909                            range: lsp::Range::default(),
1910                            new_text: "unimplemented!(${1:\"$2\"})".to_string(),
1911                        })),
1912                        ..lsp::CompletionItem::default()
1913                    },
1914                    &language,
1915                )
1916                .await,
1917            Some(CodeLabel::new(
1918                "unimplemented!(\"\")".to_string(),
1919                0..13,
1920                vec![
1921                    (15..20, HighlightId::TABSTOP_REPLACE_ID),
1922                    (16..19, HighlightId::TABSTOP_INSERT_ID),
1923                    (0..13, HighlightId::new(2)),
1924                    (13..14, HighlightId::new(2)),
1925                ],
1926            ))
1927        );
1928
1929        // Postfix completion without actual tabstops (only implicit final $0)
1930        // The label should use completion.label so it can be filtered by "ref"
1931        let ref_completion = adapter
1932            .label_for_completion(
1933                &lsp::CompletionItem {
1934                    kind: Some(lsp::CompletionItemKind::SNIPPET),
1935                    label: "ref".to_string(),
1936                    filter_text: Some("ref".to_string()),
1937                    label_details: Some(CompletionItemLabelDetails {
1938                        detail: None,
1939                        description: Some("&expr".to_string()),
1940                    }),
1941                    detail: Some("&expr".to_string()),
1942                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1943                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1944                        range: lsp::Range::default(),
1945                        new_text: "&String::new()".to_string(),
1946                    })),
1947                    ..Default::default()
1948                },
1949                &language,
1950            )
1951            .await;
1952        assert!(
1953            ref_completion.is_some(),
1954            "ref postfix completion should have a label"
1955        );
1956        let ref_label = ref_completion.unwrap();
1957        let filter_text = &ref_label.text[ref_label.filter_range.clone()];
1958        assert!(
1959            filter_text.contains("ref"),
1960            "filter range text '{filter_text}' should contain 'ref' for filtering to work",
1961        );
1962
1963        // Test for correct range calculation with mixed empty and non-empty tabstops.(See https://github.com/zed-industries/zed/issues/44825)
1964        let res = adapter
1965            .label_for_completion(
1966                &lsp::CompletionItem {
1967                    kind: Some(lsp::CompletionItemKind::STRUCT),
1968                    label: "Particles".to_string(),
1969                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1970                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1971                        range: lsp::Range::default(),
1972                        new_text: "Particles { pos_x: $1, pos_y: $2, vel_x: $3, vel_y: $4, acc_x: ${5:()}, acc_y: ${6:()}, mass: $7 }$0".to_string(),
1973                    })),
1974                    ..Default::default()
1975                },
1976                &language,
1977            )
1978            .await
1979            .unwrap();
1980
1981        assert_eq!(
1982            res,
1983            CodeLabel::new(
1984                "Particles { pos_x: …, pos_y: …, vel_x: …, vel_y: …, acc_x: (), acc_y: (), mass: … }".to_string(),
1985                0..9,
1986                vec![
1987                    (19..22, HighlightId::TABSTOP_INSERT_ID),
1988                    (31..34, HighlightId::TABSTOP_INSERT_ID),
1989                    (43..46, HighlightId::TABSTOP_INSERT_ID),
1990                    (55..58, HighlightId::TABSTOP_INSERT_ID),
1991                    (67..69, HighlightId::TABSTOP_REPLACE_ID),
1992                    (78..80, HighlightId::TABSTOP_REPLACE_ID),
1993                    (88..91, HighlightId::TABSTOP_INSERT_ID),
1994                    (0..9, highlight_type),
1995                    (60..65, highlight_field),
1996                    (71..76, highlight_field),
1997                ],
1998            )
1999        );
2000    }
2001
2002    #[gpui::test]
2003    async fn test_rust_label_for_symbol() {
2004        let adapter = Arc::new(RustLspAdapter);
2005        let language = language("rust", tree_sitter_rust::LANGUAGE.into());
2006        let grammar = language.grammar().unwrap();
2007        let theme = SyntaxTheme::new_test([
2008            ("type", Hsla::default()),
2009            ("keyword", Hsla::default()),
2010            ("function", Hsla::default()),
2011            ("property", Hsla::default()),
2012        ]);
2013
2014        language.set_theme(&theme);
2015
2016        let highlight_function = grammar.highlight_id_for_name("function").unwrap();
2017        let highlight_type = grammar.highlight_id_for_name("type").unwrap();
2018        let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
2019
2020        assert_eq!(
2021            adapter
2022                .label_for_symbol(
2023                    &language::Symbol {
2024                        name: "hello".to_string(),
2025                        kind: language::SymbolKind::Function,
2026                        container_name: None,
2027                    },
2028                    &language
2029                )
2030                .await,
2031            Some(CodeLabel::new(
2032                "fn hello".to_string(),
2033                3..8,
2034                vec![(0..2, highlight_keyword), (3..8, highlight_function)],
2035            ))
2036        );
2037
2038        assert_eq!(
2039            adapter
2040                .label_for_symbol(
2041                    &language::Symbol {
2042                        name: "World".to_string(),
2043                        kind: language::SymbolKind::TypeParameter,
2044                        container_name: None,
2045                    },
2046                    &language
2047                )
2048                .await,
2049            Some(CodeLabel::new(
2050                "type World".to_string(),
2051                5..10,
2052                vec![(0..4, highlight_keyword), (5..10, highlight_type)],
2053            ))
2054        );
2055
2056        assert_eq!(
2057            adapter
2058                .label_for_symbol(
2059                    &language::Symbol {
2060                        name: "zed".to_string(),
2061                        kind: language::SymbolKind::Package,
2062                        container_name: None,
2063                    },
2064                    &language
2065                )
2066                .await,
2067            Some(CodeLabel::new(
2068                "extern crate zed".to_string(),
2069                13..16,
2070                vec![(0..6, highlight_keyword), (7..12, highlight_keyword),],
2071            ))
2072        );
2073
2074        assert_eq!(
2075            adapter
2076                .label_for_symbol(
2077                    &language::Symbol {
2078                        name: "Variant".to_string(),
2079                        kind: language::SymbolKind::EnumMember,
2080                        container_name: None,
2081                    },
2082                    &language
2083                )
2084                .await,
2085            Some(CodeLabel::new(
2086                "Variant".to_string(),
2087                0..7,
2088                vec![(0..7, highlight_type)],
2089            ))
2090        );
2091    }
2092
2093    #[gpui::test]
2094    async fn test_rust_autoindent(cx: &mut TestAppContext) {
2095        // cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
2096        cx.update(|cx| {
2097            let test_settings = SettingsStore::test(cx);
2098            cx.set_global(test_settings);
2099            cx.update_global::<SettingsStore, _>(|store, cx| {
2100                store.update_user_settings(cx, |s| {
2101                    s.project.all_languages.defaults.tab_size = NonZeroU32::new(2);
2102                });
2103            });
2104        });
2105
2106        let language = crate::language("rust", tree_sitter_rust::LANGUAGE.into());
2107
2108        cx.new(|cx| {
2109            let mut buffer = Buffer::local("", cx).with_language(language, cx);
2110
2111            // indent between braces
2112            buffer.set_text("fn a() {}", cx);
2113            let ix = buffer.len() - 1;
2114            buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
2115            assert_eq!(buffer.text(), "fn a() {\n  \n}");
2116
2117            // indent between braces, even after empty lines
2118            buffer.set_text("fn a() {\n\n\n}", cx);
2119            let ix = buffer.len() - 2;
2120            buffer.edit([(ix..ix, "\n")], Some(AutoindentMode::EachLine), cx);
2121            assert_eq!(buffer.text(), "fn a() {\n\n\n  \n}");
2122
2123            // indent a line that continues a field expression
2124            buffer.set_text("fn a() {\n  \n}", cx);
2125            let ix = buffer.len() - 2;
2126            buffer.edit([(ix..ix, "b\n.c")], Some(AutoindentMode::EachLine), cx);
2127            assert_eq!(buffer.text(), "fn a() {\n  b\n    .c\n}");
2128
2129            // indent further lines that continue the field expression, even after empty lines
2130            let ix = buffer.len() - 2;
2131            buffer.edit([(ix..ix, "\n\n.d")], Some(AutoindentMode::EachLine), cx);
2132            assert_eq!(buffer.text(), "fn a() {\n  b\n    .c\n    \n    .d\n}");
2133
2134            // dedent the line after the field expression
2135            let ix = buffer.len() - 2;
2136            buffer.edit([(ix..ix, ";\ne")], Some(AutoindentMode::EachLine), cx);
2137            assert_eq!(
2138                buffer.text(),
2139                "fn a() {\n  b\n    .c\n    \n    .d;\n  e\n}"
2140            );
2141
2142            // indent inside a struct within a call
2143            buffer.set_text("const a: B = c(D {});", cx);
2144            let ix = buffer.len() - 3;
2145            buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
2146            assert_eq!(buffer.text(), "const a: B = c(D {\n  \n});");
2147
2148            // indent further inside a nested call
2149            let ix = buffer.len() - 4;
2150            buffer.edit([(ix..ix, "e: f(\n\n)")], Some(AutoindentMode::EachLine), cx);
2151            assert_eq!(buffer.text(), "const a: B = c(D {\n  e: f(\n    \n  )\n});");
2152
2153            // keep that indent after an empty line
2154            let ix = buffer.len() - 8;
2155            buffer.edit([(ix..ix, "\n")], Some(AutoindentMode::EachLine), cx);
2156            assert_eq!(
2157                buffer.text(),
2158                "const a: B = c(D {\n  e: f(\n    \n    \n  )\n});"
2159            );
2160
2161            buffer
2162        });
2163    }
2164
2165    #[test]
2166    fn test_package_name_from_pkgid() {
2167        for (input, expected) in [
2168            (
2169                "path+file:///absolute/path/to/project/zed/crates/zed#0.131.0",
2170                "zed",
2171            ),
2172            (
2173                "path+file:///absolute/path/to/project/custom-package#my-custom-package@0.1.0",
2174                "my-custom-package",
2175            ),
2176        ] {
2177            assert_eq!(package_name_from_pkgid(input), Some(expected));
2178        }
2179    }
2180
2181    #[test]
2182    fn test_target_info_from_metadata() {
2183        for (input, absolute_path, expected) in [
2184            (
2185                r#"{"packages":[{"id":"path+file:///absolute/path/to/project/zed/crates/zed#0.131.0","manifest_path":"/path/to/zed/Cargo.toml","targets":[{"name":"zed","kind":["bin"],"src_path":"/path/to/zed/src/main.rs"}]}]}"#,
2186                "/path/to/zed/src/main.rs",
2187                Some((
2188                    Some(TargetInfo {
2189                        package_name: "zed".into(),
2190                        target_name: "zed".into(),
2191                        required_features: Vec::new(),
2192                        target_kind: TargetKind::Bin,
2193                    }),
2194                    Arc::from("/path/to/zed".as_ref()),
2195                )),
2196            ),
2197            (
2198                r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","manifest_path":"/path/to/custom-package/Cargo.toml","targets":[{"name":"my-custom-bin","kind":["bin"],"src_path":"/path/to/custom-package/src/main.rs"}]}]}"#,
2199                "/path/to/custom-package/src/main.rs",
2200                Some((
2201                    Some(TargetInfo {
2202                        package_name: "my-custom-package".into(),
2203                        target_name: "my-custom-bin".into(),
2204                        required_features: Vec::new(),
2205                        target_kind: TargetKind::Bin,
2206                    }),
2207                    Arc::from("/path/to/custom-package".as_ref()),
2208                )),
2209            ),
2210            (
2211                r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","targets":[{"name":"my-custom-bin","kind":["example"],"src_path":"/path/to/custom-package/src/main.rs"}],"manifest_path":"/path/to/custom-package/Cargo.toml"}]}"#,
2212                "/path/to/custom-package/src/main.rs",
2213                Some((
2214                    Some(TargetInfo {
2215                        package_name: "my-custom-package".into(),
2216                        target_name: "my-custom-bin".into(),
2217                        required_features: Vec::new(),
2218                        target_kind: TargetKind::Example,
2219                    }),
2220                    Arc::from("/path/to/custom-package".as_ref()),
2221                )),
2222            ),
2223            (
2224                r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","manifest_path":"/path/to/custom-package/Cargo.toml","targets":[{"name":"my-custom-bin","kind":["example"],"src_path":"/path/to/custom-package/src/main.rs","required-features":["foo","bar"]}]}]}"#,
2225                "/path/to/custom-package/src/main.rs",
2226                Some((
2227                    Some(TargetInfo {
2228                        package_name: "my-custom-package".into(),
2229                        target_name: "my-custom-bin".into(),
2230                        required_features: vec!["foo".to_owned(), "bar".to_owned()],
2231                        target_kind: TargetKind::Example,
2232                    }),
2233                    Arc::from("/path/to/custom-package".as_ref()),
2234                )),
2235            ),
2236            (
2237                r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","targets":[{"name":"my-custom-bin","kind":["example"],"src_path":"/path/to/custom-package/src/main.rs","required-features":[]}],"manifest_path":"/path/to/custom-package/Cargo.toml"}]}"#,
2238                "/path/to/custom-package/src/main.rs",
2239                Some((
2240                    Some(TargetInfo {
2241                        package_name: "my-custom-package".into(),
2242                        target_name: "my-custom-bin".into(),
2243                        required_features: vec![],
2244                        target_kind: TargetKind::Example,
2245                    }),
2246                    Arc::from("/path/to/custom-package".as_ref()),
2247                )),
2248            ),
2249            (
2250                r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","targets":[{"name":"my-custom-package","kind":["lib"],"src_path":"/path/to/custom-package/src/main.rs"}],"manifest_path":"/path/to/custom-package/Cargo.toml"}]}"#,
2251                "/path/to/custom-package/src/main.rs",
2252                Some((None, Arc::from("/path/to/custom-package".as_ref()))),
2253            ),
2254        ] {
2255            let metadata: CargoMetadata = serde_json::from_str(input).context(input).unwrap();
2256
2257            let absolute_path = Path::new(absolute_path);
2258
2259            assert_eq!(target_info_from_metadata(metadata, absolute_path), expected);
2260        }
2261    }
2262
2263    #[test]
2264    fn target_info_from_abs_path_failed() {
2265        let project_root = tempfile::tempdir().unwrap();
2266        let cargo_toml_path = project_root.path().join("Cargo.toml");
2267        let src_dir = project_root.path().join("src");
2268        let main_rs_path = src_dir.join("main.rs");
2269
2270        std::fs::create_dir_all(&src_dir).unwrap();
2271        std::fs::write(&cargo_toml_path, "invalid_toml = {[[{").unwrap();
2272        std::fs::write(&main_rs_path, "// rust").unwrap();
2273
2274        let e = smol::block_on(target_info_from_abs_path(&main_rs_path, None)).unwrap_err();
2275        assert!(e.to_string().contains("Cargo metadata failed"));
2276    }
2277
2278    #[test]
2279    fn test_rust_test_fragment() {
2280        #[track_caller]
2281        fn check(
2282            variables: impl IntoIterator<Item = (VariableName, &'static str)>,
2283            path: &str,
2284            expected: &str,
2285        ) {
2286            let path = Path::new(path);
2287            let found = test_fragment(
2288                &TaskVariables::from_iter(variables.into_iter().map(|(k, v)| (k, v.to_owned()))),
2289                path,
2290                path.file_stem().unwrap().to_str().unwrap(),
2291            );
2292            assert_eq!(expected, found);
2293        }
2294
2295        check([], "/project/src/lib.rs", "--lib");
2296        check([], "/project/src/foo/mod.rs", "foo");
2297        check(
2298            [
2299                (RUST_BIN_KIND_TASK_VARIABLE.clone(), "bin"),
2300                (RUST_BIN_NAME_TASK_VARIABLE, "x"),
2301            ],
2302            "/project/src/main.rs",
2303            "--bin=x",
2304        );
2305        check([], "/project/src/main.rs", "--");
2306    }
2307
2308    #[test]
2309    fn test_convert_rust_analyzer_schema() {
2310        let raw_schema = serde_json::json!([
2311            {
2312                "title": "Assist",
2313                "properties": {
2314                    "rust-analyzer.assist.emitMustUse": {
2315                        "markdownDescription": "Insert #[must_use] when generating `as_` methods for enum variants.",
2316                        "default": false,
2317                        "type": "boolean"
2318                    }
2319                }
2320            },
2321            {
2322                "title": "Assist",
2323                "properties": {
2324                    "rust-analyzer.assist.expressionFillDefault": {
2325                        "markdownDescription": "Placeholder expression to use for missing expressions in assists.",
2326                        "default": "todo",
2327                        "type": "string"
2328                    }
2329                }
2330            },
2331            {
2332                "title": "Cache Priming",
2333                "properties": {
2334                    "rust-analyzer.cachePriming.enable": {
2335                        "markdownDescription": "Warm up caches on project load.",
2336                        "default": true,
2337                        "type": "boolean"
2338                    }
2339                }
2340            }
2341        ]);
2342
2343        let converted = RustLspAdapter::convert_rust_analyzer_schema(&raw_schema);
2344
2345        assert_eq!(
2346            converted.get("type").and_then(|v| v.as_str()),
2347            Some("object")
2348        );
2349
2350        let properties = converted
2351            .pointer("/properties")
2352            .expect("should have properties")
2353            .as_object()
2354            .expect("properties should be object");
2355
2356        assert!(properties.contains_key("assist"));
2357        assert!(properties.contains_key("cachePriming"));
2358        assert!(!properties.contains_key("rust-analyzer"));
2359
2360        let assist_props = properties
2361            .get("assist")
2362            .expect("should have assist")
2363            .pointer("/properties")
2364            .expect("assist should have properties")
2365            .as_object()
2366            .expect("assist properties should be object");
2367
2368        assert!(assist_props.contains_key("emitMustUse"));
2369        assert!(assist_props.contains_key("expressionFillDefault"));
2370
2371        let emit_must_use = assist_props
2372            .get("emitMustUse")
2373            .expect("should have emitMustUse");
2374        assert_eq!(
2375            emit_must_use.get("type").and_then(|v| v.as_str()),
2376            Some("boolean")
2377        );
2378        assert_eq!(
2379            emit_must_use.get("default").and_then(|v| v.as_bool()),
2380            Some(false)
2381        );
2382
2383        let cache_priming_props = properties
2384            .get("cachePriming")
2385            .expect("should have cachePriming")
2386            .pointer("/properties")
2387            .expect("cachePriming should have properties")
2388            .as_object()
2389            .expect("cachePriming properties should be object");
2390
2391        assert!(cache_priming_props.contains_key("enable"));
2392    }
2393}
2394
Served at tenant.openagents/omega Member data and write actions are omitted.