rust.rs

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