rust.rs

   1use anyhow::{Context as _, Result};
   2use async_trait::async_trait;
   3use collections::HashMap;
   4use futures::StreamExt;
   5use gpui::{App, AppContext, AsyncApp, SharedString, Task};
   6use http_client::github::AssetKind;
   7use http_client::github::{GitHubLspBinaryVersion, latest_github_release};
   8use http_client::github_download::{GithubBinaryMetadata, download_server_binary};
   9pub use language::*;
  10use lsp::{InitializeParams, LanguageServerBinary};
  11use project::lsp_store::rust_analyzer_ext::CARGO_DIAGNOSTICS_SOURCE_NAME;
  12use project::project_settings::ProjectSettings;
  13use regex::Regex;
  14use serde_json::json;
  15use settings::Settings as _;
  16use smol::fs::{self};
  17use std::fmt::Display;
  18use std::ops::Range;
  19use std::{
  20    borrow::Cow,
  21    path::{Path, PathBuf},
  22    sync::{Arc, LazyLock},
  23};
  24use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};
  25use util::fs::{make_file_executable, remove_matching};
  26use util::merge_json_value_into;
  27use util::rel_path::RelPath;
  28use util::{ResultExt, maybe};
  29
  30use crate::language_settings::language_settings;
  31
  32pub struct RustLspAdapter;
  33
  34#[cfg(target_os = "macos")]
  35impl RustLspAdapter {
  36    const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz;
  37    const ARCH_SERVER_NAME: &str = "apple-darwin";
  38}
  39
  40#[cfg(target_os = "linux")]
  41impl RustLspAdapter {
  42    const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz;
  43    const ARCH_SERVER_NAME: &str = "unknown-linux";
  44}
  45
  46#[cfg(target_os = "freebsd")]
  47impl RustLspAdapter {
  48    const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz;
  49    const ARCH_SERVER_NAME: &str = "unknown-freebsd";
  50}
  51
  52#[cfg(target_os = "windows")]
  53impl RustLspAdapter {
  54    const GITHUB_ASSET_KIND: AssetKind = AssetKind::Zip;
  55    const ARCH_SERVER_NAME: &str = "pc-windows-msvc";
  56}
  57
  58const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("rust-analyzer");
  59
  60#[cfg(target_os = "linux")]
  61enum LibcType {
  62    Gnu,
  63    Musl,
  64}
  65
  66impl RustLspAdapter {
  67    #[cfg(target_os = "linux")]
  68    async fn determine_libc_type() -> LibcType {
  69        use futures::pin_mut;
  70        use smol::process::Command;
  71
  72        async fn from_ldd_version() -> Option<LibcType> {
  73            let ldd_output = Command::new("ldd").arg("--version").output().await.ok()?;
  74            let ldd_version = String::from_utf8_lossy(&ldd_output.stdout);
  75
  76            if ldd_version.contains("GNU libc") || ldd_version.contains("GLIBC") {
  77                Some(LibcType::Gnu)
  78            } else if ldd_version.contains("musl") {
  79                Some(LibcType::Musl)
  80            } else {
  81                None
  82            }
  83        }
  84
  85        if let Some(libc_type) = from_ldd_version().await {
  86            return libc_type;
  87        }
  88
  89        let Ok(dir_entries) = smol::fs::read_dir("/lib").await else {
  90            // defaulting to gnu because nix doesn't have /lib files due to not following FHS
  91            return LibcType::Gnu;
  92        };
  93        let dir_entries = dir_entries.filter_map(async move |e| e.ok());
  94        pin_mut!(dir_entries);
  95
  96        let mut has_musl = false;
  97        let mut has_gnu = false;
  98
  99        while let Some(entry) = dir_entries.next().await {
 100            let file_name = entry.file_name();
 101            let file_name = file_name.to_string_lossy();
 102            if file_name.starts_with("ld-musl-") {
 103                has_musl = true;
 104            } else if file_name.starts_with("ld-linux-") {
 105                has_gnu = true;
 106            }
 107        }
 108
 109        match (has_musl, has_gnu) {
 110            (true, _) => LibcType::Musl,
 111            (_, true) => LibcType::Gnu,
 112            _ => LibcType::Gnu,
 113        }
 114    }
 115
 116    #[cfg(target_os = "linux")]
 117    async fn build_arch_server_name_linux() -> String {
 118        let libc = match Self::determine_libc_type().await {
 119            LibcType::Musl => "musl",
 120            LibcType::Gnu => "gnu",
 121        };
 122
 123        format!("{}-{}", Self::ARCH_SERVER_NAME, libc)
 124    }
 125
 126    async fn build_asset_name() -> String {
 127        let extension = match Self::GITHUB_ASSET_KIND {
 128            AssetKind::TarGz => "tar.gz",
 129            AssetKind::Gz => "gz",
 130            AssetKind::Zip => "zip",
 131        };
 132
 133        #[cfg(target_os = "linux")]
 134        let arch_server_name = Self::build_arch_server_name_linux().await;
 135        #[cfg(not(target_os = "linux"))]
 136        let arch_server_name = Self::ARCH_SERVER_NAME.to_string();
 137
 138        format!(
 139            "{}-{}-{}.{}",
 140            SERVER_NAME,
 141            std::env::consts::ARCH,
 142            &arch_server_name,
 143            extension
 144        )
 145    }
 146}
 147
 148pub(crate) struct CargoManifestProvider;
 149
 150impl ManifestProvider for CargoManifestProvider {
 151    fn name(&self) -> ManifestName {
 152        SharedString::new_static("Cargo.toml").into()
 153    }
 154
 155    fn search(
 156        &self,
 157        ManifestQuery {
 158            path,
 159            depth,
 160            delegate,
 161        }: ManifestQuery,
 162    ) -> Option<Arc<RelPath>> {
 163        let mut outermost_cargo_toml = None;
 164        for path in path.ancestors().take(depth) {
 165            let p = path.join(RelPath::unix("Cargo.toml").unwrap());
 166            if delegate.exists(&p, Some(false)) {
 167                outermost_cargo_toml = Some(Arc::from(path));
 168            }
 169        }
 170
 171        outermost_cargo_toml
 172    }
 173}
 174
 175#[async_trait(?Send)]
 176impl LspAdapter for RustLspAdapter {
 177    fn name(&self) -> LanguageServerName {
 178        SERVER_NAME
 179    }
 180
 181    fn disk_based_diagnostic_sources(&self) -> Vec<String> {
 182        vec![CARGO_DIAGNOSTICS_SOURCE_NAME.to_owned()]
 183    }
 184
 185    fn disk_based_diagnostics_progress_token(&self) -> Option<String> {
 186        Some("rust-analyzer/flycheck".into())
 187    }
 188
 189    fn process_diagnostics(
 190        &self,
 191        params: &mut lsp::PublishDiagnosticsParams,
 192        _: LanguageServerId,
 193        _: Option<&'_ Buffer>,
 194    ) {
 195        static REGEX: LazyLock<Regex> =
 196            LazyLock::new(|| Regex::new(r"(?m)`([^`]+)\n`$").expect("Failed to create REGEX"));
 197
 198        for diagnostic in &mut params.diagnostics {
 199            for message in diagnostic
 200                .related_information
 201                .iter_mut()
 202                .flatten()
 203                .map(|info| &mut info.message)
 204                .chain([&mut diagnostic.message])
 205            {
 206                if let Cow::Owned(sanitized) = REGEX.replace_all(message, "`$1`") {
 207                    *message = sanitized;
 208                }
 209            }
 210        }
 211    }
 212
 213    fn diagnostic_message_to_markdown(&self, message: &str) -> Option<String> {
 214        static REGEX: LazyLock<Regex> =
 215            LazyLock::new(|| Regex::new(r"(?m)\n *").expect("Failed to create REGEX"));
 216        Some(REGEX.replace_all(message, "\n\n").to_string())
 217    }
 218
 219    async fn label_for_completion(
 220        &self,
 221        completion: &lsp::CompletionItem,
 222        language: &Arc<Language>,
 223    ) -> Option<CodeLabel> {
 224        // rust-analyzer calls these detail left and detail right in terms of where it expects things to be rendered
 225        // this usually contains signatures of the thing to be completed
 226        let detail_right = completion
 227            .label_details
 228            .as_ref()
 229            .and_then(|detail| detail.description.as_ref())
 230            .or(completion.detail.as_ref())
 231            .map(|detail| detail.trim());
 232        // this tends to contain alias and import information
 233        let detail_left = completion
 234            .label_details
 235            .as_ref()
 236            .and_then(|detail| detail.detail.as_deref());
 237        let mk_label = |text: String, filter_range: &dyn Fn() -> Range<usize>, runs| {
 238            let filter_range = completion
 239                .filter_text
 240                .as_deref()
 241                .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
 242                .or_else(|| {
 243                    text.find(&completion.label)
 244                        .map(|ix| ix..ix + completion.label.len())
 245                })
 246                .unwrap_or_else(filter_range);
 247
 248            CodeLabel {
 249                text,
 250                runs,
 251                filter_range,
 252            }
 253        };
 254        let mut label = match (detail_right, completion.kind) {
 255            (Some(signature), Some(lsp::CompletionItemKind::FIELD)) => {
 256                let name = &completion.label;
 257                let text = format!("{name}: {signature}");
 258                let prefix = "struct S { ";
 259                let source = Rope::from_iter([prefix, &text, " }"]);
 260                let runs =
 261                    language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
 262                mk_label(text, &|| 0..completion.label.len(), runs)
 263            }
 264            (
 265                Some(signature),
 266                Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE),
 267            ) if completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => {
 268                let name = &completion.label;
 269                let text = format!("{name}: {signature}",);
 270                let prefix = "let ";
 271                let source = Rope::from_iter([prefix, &text, " = ();"]);
 272                let runs =
 273                    language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
 274                mk_label(text, &|| 0..completion.label.len(), runs)
 275            }
 276            (
 277                function_signature,
 278                Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD),
 279            ) => {
 280                const FUNCTION_PREFIXES: [&str; 6] = [
 281                    "async fn",
 282                    "async unsafe fn",
 283                    "const fn",
 284                    "const unsafe fn",
 285                    "unsafe fn",
 286                    "fn",
 287                ];
 288                let fn_prefixed = FUNCTION_PREFIXES.iter().find_map(|&prefix| {
 289                    function_signature?
 290                        .strip_prefix(prefix)
 291                        .map(|suffix| (prefix, suffix))
 292                });
 293                let label = if let Some(label) = completion
 294                    .label
 295                    .strip_suffix("(…)")
 296                    .or_else(|| completion.label.strip_suffix("()"))
 297                {
 298                    label
 299                } else {
 300                    &completion.label
 301                };
 302
 303                static FULL_SIGNATURE_REGEX: LazyLock<Regex> =
 304                    LazyLock::new(|| Regex::new(r"fn (.?+)\(").expect("Failed to create REGEX"));
 305                if let Some((function_signature, match_)) = function_signature
 306                    .filter(|it| it.contains(&label))
 307                    .and_then(|it| Some((it, FULL_SIGNATURE_REGEX.find(it)?)))
 308                {
 309                    let source = Rope::from(function_signature);
 310                    let runs = language.highlight_text(&source, 0..function_signature.len());
 311                    mk_label(
 312                        function_signature.to_owned(),
 313                        &|| match_.range().start - 3..match_.range().end - 1,
 314                        runs,
 315                    )
 316                } else if let Some((prefix, suffix)) = fn_prefixed {
 317                    let text = format!("{label}{suffix}");
 318                    let source = Rope::from_iter([prefix, " ", &text, " {}"]);
 319                    let run_start = prefix.len() + 1;
 320                    let runs = language.highlight_text(&source, run_start..run_start + text.len());
 321                    mk_label(text, &|| 0..label.len(), runs)
 322                } else if completion
 323                    .detail
 324                    .as_ref()
 325                    .is_some_and(|detail| detail.starts_with("macro_rules! "))
 326                {
 327                    let text = completion.label.clone();
 328                    let len = text.len();
 329                    let source = Rope::from(text.as_str());
 330                    let runs = language.highlight_text(&source, 0..len);
 331                    mk_label(text, &|| 0..completion.label.len(), runs)
 332                } else if detail_left.is_none() {
 333                    return None;
 334                } else {
 335                    mk_label(
 336                        completion.label.clone(),
 337                        &|| 0..completion.label.len(),
 338                        vec![],
 339                    )
 340                }
 341            }
 342            (_, kind) => {
 343                let highlight_name = kind.and_then(|kind| match kind {
 344                    lsp::CompletionItemKind::STRUCT
 345                    | lsp::CompletionItemKind::INTERFACE
 346                    | lsp::CompletionItemKind::ENUM => Some("type"),
 347                    lsp::CompletionItemKind::ENUM_MEMBER => Some("variant"),
 348                    lsp::CompletionItemKind::KEYWORD => Some("keyword"),
 349                    lsp::CompletionItemKind::VALUE | lsp::CompletionItemKind::CONSTANT => {
 350                        Some("constant")
 351                    }
 352                    _ => None,
 353                });
 354
 355                let label = completion.label.clone();
 356                let mut runs = vec![];
 357                if let Some(highlight_name) = highlight_name {
 358                    let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name)?;
 359                    runs.push((
 360                        0..label.rfind('(').unwrap_or(completion.label.len()),
 361                        highlight_id,
 362                    ));
 363                } else if detail_left.is_none() {
 364                    return None;
 365                }
 366
 367                mk_label(label, &|| 0..completion.label.len(), runs)
 368            }
 369        };
 370
 371        if let Some(detail_left) = detail_left {
 372            label.text.push(' ');
 373            if !detail_left.starts_with('(') {
 374                label.text.push('(');
 375            }
 376            label.text.push_str(detail_left);
 377            if !detail_left.ends_with(')') {
 378                label.text.push(')');
 379            }
 380        }
 381        Some(label)
 382    }
 383
 384    async fn label_for_symbol(
 385        &self,
 386        name: &str,
 387        kind: lsp::SymbolKind,
 388        language: &Arc<Language>,
 389    ) -> Option<CodeLabel> {
 390        let (prefix, suffix) = match kind {
 391            lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => ("fn ", " () {}"),
 392            lsp::SymbolKind::STRUCT => ("struct ", " {}"),
 393            lsp::SymbolKind::ENUM => ("enum ", " {}"),
 394            lsp::SymbolKind::INTERFACE => ("trait ", " {}"),
 395            lsp::SymbolKind::CONSTANT => ("const ", ": () = ();"),
 396            lsp::SymbolKind::MODULE => ("mod ", " {}"),
 397            lsp::SymbolKind::TYPE_PARAMETER => ("type ", " {}"),
 398            _ => return None,
 399        };
 400
 401        let filter_range = prefix.len()..prefix.len() + name.len();
 402        let display_range = 0..filter_range.end;
 403        Some(CodeLabel {
 404            runs: language.highlight_text(&Rope::from_iter([prefix, name, suffix]), display_range),
 405            text: format!("{prefix}{name}"),
 406            filter_range,
 407        })
 408    }
 409
 410    fn prepare_initialize_params(
 411        &self,
 412        mut original: InitializeParams,
 413        cx: &App,
 414    ) -> Result<InitializeParams> {
 415        let enable_lsp_tasks = ProjectSettings::get_global(cx)
 416            .lsp
 417            .get(&SERVER_NAME)
 418            .is_some_and(|s| s.enable_lsp_tasks);
 419        if enable_lsp_tasks {
 420            let experimental = json!({
 421                "runnables": {
 422                    "kinds": [ "cargo", "shell" ],
 423                },
 424            });
 425            if let Some(original_experimental) = &mut original.capabilities.experimental {
 426                merge_json_value_into(experimental, original_experimental);
 427            } else {
 428                original.capabilities.experimental = Some(experimental);
 429            }
 430        }
 431
 432        Ok(original)
 433    }
 434}
 435
 436impl LspInstaller for RustLspAdapter {
 437    type BinaryVersion = GitHubLspBinaryVersion;
 438    async fn check_if_user_installed(
 439        &self,
 440        delegate: &dyn LspAdapterDelegate,
 441        _: Option<Toolchain>,
 442        _: &AsyncApp,
 443    ) -> Option<LanguageServerBinary> {
 444        let path = delegate.which("rust-analyzer".as_ref()).await?;
 445        let env = delegate.shell_env().await;
 446
 447        // It is surprisingly common for ~/.cargo/bin/rust-analyzer to be a symlink to
 448        // /usr/bin/rust-analyzer that fails when you run it; so we need to test it.
 449        log::info!("found rust-analyzer in PATH. trying to run `rust-analyzer --help`");
 450        let result = delegate
 451            .try_exec(LanguageServerBinary {
 452                path: path.clone(),
 453                arguments: vec!["--help".into()],
 454                env: Some(env.clone()),
 455            })
 456            .await;
 457        if let Err(err) = result {
 458            log::debug!(
 459                "failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}",
 460                path,
 461                err
 462            );
 463            return None;
 464        }
 465
 466        Some(LanguageServerBinary {
 467            path,
 468            env: Some(env),
 469            arguments: vec![],
 470        })
 471    }
 472
 473    async fn fetch_latest_server_version(
 474        &self,
 475        delegate: &dyn LspAdapterDelegate,
 476        pre_release: bool,
 477        _: &mut AsyncApp,
 478    ) -> Result<GitHubLspBinaryVersion> {
 479        let release = latest_github_release(
 480            "rust-lang/rust-analyzer",
 481            true,
 482            pre_release,
 483            delegate.http_client(),
 484        )
 485        .await?;
 486        let asset_name = Self::build_asset_name().await;
 487        let asset = release
 488            .assets
 489            .into_iter()
 490            .find(|asset| asset.name == asset_name)
 491            .with_context(|| format!("no asset found matching `{asset_name:?}`"))?;
 492        Ok(GitHubLspBinaryVersion {
 493            name: release.tag_name,
 494            url: asset.browser_download_url,
 495            digest: asset.digest,
 496        })
 497    }
 498
 499    async fn fetch_server_binary(
 500        &self,
 501        version: GitHubLspBinaryVersion,
 502        container_dir: PathBuf,
 503        delegate: &dyn LspAdapterDelegate,
 504    ) -> Result<LanguageServerBinary> {
 505        let GitHubLspBinaryVersion {
 506            name,
 507            url,
 508            digest: expected_digest,
 509        } = version;
 510        let destination_path = container_dir.join(format!("rust-analyzer-{name}"));
 511        let server_path = match Self::GITHUB_ASSET_KIND {
 512            AssetKind::TarGz | AssetKind::Gz => destination_path.clone(), // Tar and gzip extract in place.
 513            AssetKind::Zip => destination_path.clone().join("rust-analyzer.exe"), // zip contains a .exe
 514        };
 515
 516        let binary = LanguageServerBinary {
 517            path: server_path.clone(),
 518            env: None,
 519            arguments: Default::default(),
 520        };
 521
 522        let metadata_path = destination_path.with_extension("metadata");
 523        let metadata = GithubBinaryMetadata::read_from_file(&metadata_path)
 524            .await
 525            .ok();
 526        if let Some(metadata) = metadata {
 527            let validity_check = async || {
 528                delegate
 529                    .try_exec(LanguageServerBinary {
 530                        path: server_path.clone(),
 531                        arguments: vec!["--version".into()],
 532                        env: None,
 533                    })
 534                    .await
 535                    .inspect_err(|err| {
 536                        log::warn!("Unable to run {server_path:?} asset, redownloading: {err}",)
 537                    })
 538            };
 539            if let (Some(actual_digest), Some(expected_digest)) =
 540                (&metadata.digest, &expected_digest)
 541            {
 542                if actual_digest == expected_digest {
 543                    if validity_check().await.is_ok() {
 544                        return Ok(binary);
 545                    }
 546                } else {
 547                    log::info!(
 548                        "SHA-256 mismatch for {destination_path:?} asset, downloading new asset. Expected: {expected_digest}, Got: {actual_digest}"
 549                    );
 550                }
 551            } else if validity_check().await.is_ok() {
 552                return Ok(binary);
 553            }
 554        }
 555
 556        download_server_binary(
 557            &*delegate.http_client(),
 558            &url,
 559            expected_digest.as_deref(),
 560            &destination_path,
 561            Self::GITHUB_ASSET_KIND,
 562        )
 563        .await?;
 564        make_file_executable(&server_path).await?;
 565        remove_matching(&container_dir, |path| path != destination_path).await;
 566        GithubBinaryMetadata::write_to_file(
 567            &GithubBinaryMetadata {
 568                metadata_version: 1,
 569                digest: expected_digest,
 570            },
 571            &metadata_path,
 572        )
 573        .await?;
 574
 575        Ok(LanguageServerBinary {
 576            path: server_path,
 577            env: None,
 578            arguments: Default::default(),
 579        })
 580    }
 581
 582    async fn cached_server_binary(
 583        &self,
 584        container_dir: PathBuf,
 585        _: &dyn LspAdapterDelegate,
 586    ) -> Option<LanguageServerBinary> {
 587        get_cached_server_binary(container_dir).await
 588    }
 589}
 590
 591pub(crate) struct RustContextProvider;
 592
 593const RUST_PACKAGE_TASK_VARIABLE: VariableName =
 594    VariableName::Custom(Cow::Borrowed("RUST_PACKAGE"));
 595
 596/// The bin name corresponding to the current file in Cargo.toml
 597const RUST_BIN_NAME_TASK_VARIABLE: VariableName =
 598    VariableName::Custom(Cow::Borrowed("RUST_BIN_NAME"));
 599
 600/// The bin kind (bin/example) corresponding to the current file in Cargo.toml
 601const RUST_BIN_KIND_TASK_VARIABLE: VariableName =
 602    VariableName::Custom(Cow::Borrowed("RUST_BIN_KIND"));
 603
 604/// The flag to list required features for executing a bin, if any
 605const RUST_BIN_REQUIRED_FEATURES_FLAG_TASK_VARIABLE: VariableName =
 606    VariableName::Custom(Cow::Borrowed("RUST_BIN_REQUIRED_FEATURES_FLAG"));
 607
 608/// The list of required features for executing a bin, if any
 609const RUST_BIN_REQUIRED_FEATURES_TASK_VARIABLE: VariableName =
 610    VariableName::Custom(Cow::Borrowed("RUST_BIN_REQUIRED_FEATURES"));
 611
 612const RUST_TEST_FRAGMENT_TASK_VARIABLE: VariableName =
 613    VariableName::Custom(Cow::Borrowed("RUST_TEST_FRAGMENT"));
 614
 615const RUST_DOC_TEST_NAME_TASK_VARIABLE: VariableName =
 616    VariableName::Custom(Cow::Borrowed("RUST_DOC_TEST_NAME"));
 617
 618const RUST_TEST_NAME_TASK_VARIABLE: VariableName =
 619    VariableName::Custom(Cow::Borrowed("RUST_TEST_NAME"));
 620
 621const RUST_MANIFEST_DIRNAME_TASK_VARIABLE: VariableName =
 622    VariableName::Custom(Cow::Borrowed("RUST_MANIFEST_DIRNAME"));
 623
 624impl ContextProvider for RustContextProvider {
 625    fn build_context(
 626        &self,
 627        task_variables: &TaskVariables,
 628        location: ContextLocation<'_>,
 629        project_env: Option<HashMap<String, String>>,
 630        _: Arc<dyn LanguageToolchainStore>,
 631        cx: &mut gpui::App,
 632    ) -> Task<Result<TaskVariables>> {
 633        let local_abs_path = location
 634            .file_location
 635            .buffer
 636            .read(cx)
 637            .file()
 638            .and_then(|file| Some(file.as_local()?.abs_path(cx)));
 639
 640        let mut variables = TaskVariables::default();
 641
 642        if let (Some(path), Some(stem)) = (&local_abs_path, task_variables.get(&VariableName::Stem))
 643        {
 644            let fragment = test_fragment(&variables, path, stem);
 645            variables.insert(RUST_TEST_FRAGMENT_TASK_VARIABLE, fragment);
 646        };
 647        if let Some(test_name) =
 648            task_variables.get(&VariableName::Custom(Cow::Borrowed("_test_name")))
 649        {
 650            variables.insert(RUST_TEST_NAME_TASK_VARIABLE, test_name.into());
 651        }
 652        if let Some(doc_test_name) =
 653            task_variables.get(&VariableName::Custom(Cow::Borrowed("_doc_test_name")))
 654        {
 655            variables.insert(RUST_DOC_TEST_NAME_TASK_VARIABLE, doc_test_name.into());
 656        }
 657        cx.background_spawn(async move {
 658            if let Some(path) = local_abs_path
 659                .as_deref()
 660                .and_then(|local_abs_path| local_abs_path.parent())
 661                && let Some(package_name) =
 662                    human_readable_package_name(path, project_env.as_ref()).await
 663            {
 664                variables.insert(RUST_PACKAGE_TASK_VARIABLE.clone(), package_name);
 665            }
 666            if let Some(path) = local_abs_path.as_ref()
 667                && let Some((target, manifest_path)) =
 668                    target_info_from_abs_path(path, project_env.as_ref()).await
 669            {
 670                if let Some(target) = target {
 671                    variables.extend(TaskVariables::from_iter([
 672                        (RUST_PACKAGE_TASK_VARIABLE.clone(), target.package_name),
 673                        (RUST_BIN_NAME_TASK_VARIABLE.clone(), target.target_name),
 674                        (
 675                            RUST_BIN_KIND_TASK_VARIABLE.clone(),
 676                            target.target_kind.to_string(),
 677                        ),
 678                    ]));
 679                    if target.required_features.is_empty() {
 680                        variables.insert(RUST_BIN_REQUIRED_FEATURES_FLAG_TASK_VARIABLE, "".into());
 681                        variables.insert(RUST_BIN_REQUIRED_FEATURES_TASK_VARIABLE, "".into());
 682                    } else {
 683                        variables.insert(
 684                            RUST_BIN_REQUIRED_FEATURES_FLAG_TASK_VARIABLE.clone(),
 685                            "--features".to_string(),
 686                        );
 687                        variables.insert(
 688                            RUST_BIN_REQUIRED_FEATURES_TASK_VARIABLE.clone(),
 689                            target.required_features.join(","),
 690                        );
 691                    }
 692                }
 693                variables.extend(TaskVariables::from_iter([(
 694                    RUST_MANIFEST_DIRNAME_TASK_VARIABLE.clone(),
 695                    manifest_path.to_string_lossy().into_owned(),
 696                )]));
 697            }
 698            Ok(variables)
 699        })
 700    }
 701
 702    fn associated_tasks(
 703        &self,
 704        file: Option<Arc<dyn language::File>>,
 705        cx: &App,
 706    ) -> Task<Option<TaskTemplates>> {
 707        const DEFAULT_RUN_NAME_STR: &str = "RUST_DEFAULT_PACKAGE_RUN";
 708        const CUSTOM_TARGET_DIR: &str = "RUST_TARGET_DIR";
 709
 710        let language_sets = language_settings(Some("Rust".into()), file.as_ref(), cx);
 711        let package_to_run = language_sets
 712            .tasks
 713            .variables
 714            .get(DEFAULT_RUN_NAME_STR)
 715            .cloned();
 716        let custom_target_dir = language_sets
 717            .tasks
 718            .variables
 719            .get(CUSTOM_TARGET_DIR)
 720            .cloned();
 721        let run_task_args = if let Some(package_to_run) = package_to_run {
 722            vec!["run".into(), "-p".into(), package_to_run]
 723        } else {
 724            vec!["run".into()]
 725        };
 726        let mut task_templates = vec![
 727            TaskTemplate {
 728                label: format!(
 729                    "Check (package: {})",
 730                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 731                ),
 732                command: "cargo".into(),
 733                args: vec![
 734                    "check".into(),
 735                    "-p".into(),
 736                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 737                ],
 738                cwd: Some("$ZED_DIRNAME".to_owned()),
 739                ..TaskTemplate::default()
 740            },
 741            TaskTemplate {
 742                label: "Check all targets (workspace)".into(),
 743                command: "cargo".into(),
 744                args: vec!["check".into(), "--workspace".into(), "--all-targets".into()],
 745                cwd: Some("$ZED_DIRNAME".to_owned()),
 746                ..TaskTemplate::default()
 747            },
 748            TaskTemplate {
 749                label: format!(
 750                    "Test '{}' (package: {})",
 751                    RUST_TEST_NAME_TASK_VARIABLE.template_value(),
 752                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 753                ),
 754                command: "cargo".into(),
 755                args: vec![
 756                    "test".into(),
 757                    "-p".into(),
 758                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 759                    "--".into(),
 760                    "--nocapture".into(),
 761                    "--include-ignored".into(),
 762                    RUST_TEST_NAME_TASK_VARIABLE.template_value(),
 763                ],
 764                tags: vec!["rust-test".to_owned()],
 765                cwd: Some(RUST_MANIFEST_DIRNAME_TASK_VARIABLE.template_value()),
 766                ..TaskTemplate::default()
 767            },
 768            TaskTemplate {
 769                label: format!(
 770                    "Doc test '{}' (package: {})",
 771                    RUST_DOC_TEST_NAME_TASK_VARIABLE.template_value(),
 772                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 773                ),
 774                command: "cargo".into(),
 775                args: vec![
 776                    "test".into(),
 777                    "--doc".into(),
 778                    "-p".into(),
 779                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 780                    "--".into(),
 781                    "--nocapture".into(),
 782                    "--include-ignored".into(),
 783                    RUST_DOC_TEST_NAME_TASK_VARIABLE.template_value(),
 784                ],
 785                tags: vec!["rust-doc-test".to_owned()],
 786                cwd: Some(RUST_MANIFEST_DIRNAME_TASK_VARIABLE.template_value()),
 787                ..TaskTemplate::default()
 788            },
 789            TaskTemplate {
 790                label: format!(
 791                    "Test mod '{}' (package: {})",
 792                    VariableName::Stem.template_value(),
 793                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 794                ),
 795                command: "cargo".into(),
 796                args: vec![
 797                    "test".into(),
 798                    "-p".into(),
 799                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 800                    "--".into(),
 801                    RUST_TEST_FRAGMENT_TASK_VARIABLE.template_value(),
 802                ],
 803                tags: vec!["rust-mod-test".to_owned()],
 804                cwd: Some(RUST_MANIFEST_DIRNAME_TASK_VARIABLE.template_value()),
 805                ..TaskTemplate::default()
 806            },
 807            TaskTemplate {
 808                label: format!(
 809                    "Run {} {} (package: {})",
 810                    RUST_BIN_KIND_TASK_VARIABLE.template_value(),
 811                    RUST_BIN_NAME_TASK_VARIABLE.template_value(),
 812                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 813                ),
 814                command: "cargo".into(),
 815                args: vec![
 816                    "run".into(),
 817                    "-p".into(),
 818                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 819                    format!("--{}", RUST_BIN_KIND_TASK_VARIABLE.template_value()),
 820                    RUST_BIN_NAME_TASK_VARIABLE.template_value(),
 821                    RUST_BIN_REQUIRED_FEATURES_FLAG_TASK_VARIABLE.template_value(),
 822                    RUST_BIN_REQUIRED_FEATURES_TASK_VARIABLE.template_value(),
 823                ],
 824                cwd: Some("$ZED_DIRNAME".to_owned()),
 825                tags: vec!["rust-main".to_owned()],
 826                ..TaskTemplate::default()
 827            },
 828            TaskTemplate {
 829                label: format!(
 830                    "Test (package: {})",
 831                    RUST_PACKAGE_TASK_VARIABLE.template_value()
 832                ),
 833                command: "cargo".into(),
 834                args: vec![
 835                    "test".into(),
 836                    "-p".into(),
 837                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 838                ],
 839                cwd: Some(RUST_MANIFEST_DIRNAME_TASK_VARIABLE.template_value()),
 840                ..TaskTemplate::default()
 841            },
 842            TaskTemplate {
 843                label: "Run".into(),
 844                command: "cargo".into(),
 845                args: run_task_args,
 846                cwd: Some("$ZED_DIRNAME".to_owned()),
 847                ..TaskTemplate::default()
 848            },
 849            TaskTemplate {
 850                label: "Clean".into(),
 851                command: "cargo".into(),
 852                args: vec!["clean".into()],
 853                cwd: Some("$ZED_DIRNAME".to_owned()),
 854                ..TaskTemplate::default()
 855            },
 856        ];
 857
 858        if let Some(custom_target_dir) = custom_target_dir {
 859            task_templates = task_templates
 860                .into_iter()
 861                .map(|mut task_template| {
 862                    let mut args = task_template.args.split_off(1);
 863                    task_template.args.append(&mut vec![
 864                        "--target-dir".to_string(),
 865                        custom_target_dir.clone(),
 866                    ]);
 867                    task_template.args.append(&mut args);
 868
 869                    task_template
 870                })
 871                .collect();
 872        }
 873
 874        Task::ready(Some(TaskTemplates(task_templates)))
 875    }
 876
 877    fn lsp_task_source(&self) -> Option<LanguageServerName> {
 878        Some(SERVER_NAME)
 879    }
 880}
 881
 882/// Part of the data structure of Cargo metadata
 883#[derive(Debug, serde::Deserialize)]
 884struct CargoMetadata {
 885    packages: Vec<CargoPackage>,
 886}
 887
 888#[derive(Debug, serde::Deserialize)]
 889struct CargoPackage {
 890    id: String,
 891    targets: Vec<CargoTarget>,
 892    manifest_path: Arc<Path>,
 893}
 894
 895#[derive(Debug, serde::Deserialize)]
 896struct CargoTarget {
 897    name: String,
 898    kind: Vec<String>,
 899    src_path: String,
 900    #[serde(rename = "required-features", default)]
 901    required_features: Vec<String>,
 902}
 903
 904#[derive(Debug, PartialEq)]
 905enum TargetKind {
 906    Bin,
 907    Example,
 908}
 909
 910impl Display for TargetKind {
 911    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 912        match self {
 913            TargetKind::Bin => write!(f, "bin"),
 914            TargetKind::Example => write!(f, "example"),
 915        }
 916    }
 917}
 918
 919impl TryFrom<&str> for TargetKind {
 920    type Error = ();
 921    fn try_from(value: &str) -> Result<Self, ()> {
 922        match value {
 923            "bin" => Ok(Self::Bin),
 924            "example" => Ok(Self::Example),
 925            _ => Err(()),
 926        }
 927    }
 928}
 929/// Which package and binary target are we in?
 930#[derive(Debug, PartialEq)]
 931struct TargetInfo {
 932    package_name: String,
 933    target_name: String,
 934    target_kind: TargetKind,
 935    required_features: Vec<String>,
 936}
 937
 938async fn target_info_from_abs_path(
 939    abs_path: &Path,
 940    project_env: Option<&HashMap<String, String>>,
 941) -> Option<(Option<TargetInfo>, Arc<Path>)> {
 942    let mut command = util::command::new_smol_command("cargo");
 943    if let Some(envs) = project_env {
 944        command.envs(envs);
 945    }
 946    let output = command
 947        .current_dir(abs_path.parent()?)
 948        .arg("metadata")
 949        .arg("--no-deps")
 950        .arg("--format-version")
 951        .arg("1")
 952        .output()
 953        .await
 954        .log_err()?
 955        .stdout;
 956
 957    let metadata: CargoMetadata = serde_json::from_slice(&output).log_err()?;
 958    target_info_from_metadata(metadata, abs_path)
 959}
 960
 961fn target_info_from_metadata(
 962    metadata: CargoMetadata,
 963    abs_path: &Path,
 964) -> Option<(Option<TargetInfo>, Arc<Path>)> {
 965    let mut manifest_path = None;
 966    for package in metadata.packages {
 967        let Some(manifest_dir_path) = package.manifest_path.parent() else {
 968            continue;
 969        };
 970
 971        let Some(path_from_manifest_dir) = abs_path.strip_prefix(manifest_dir_path).ok() else {
 972            continue;
 973        };
 974        let candidate_path_length = path_from_manifest_dir.components().count();
 975        // Pick the most specific manifest path
 976        if let Some((path, current_length)) = &mut manifest_path {
 977            if candidate_path_length > *current_length {
 978                *path = Arc::from(manifest_dir_path);
 979                *current_length = candidate_path_length;
 980            }
 981        } else {
 982            manifest_path = Some((Arc::from(manifest_dir_path), candidate_path_length));
 983        };
 984
 985        for target in package.targets {
 986            let Some(bin_kind) = target
 987                .kind
 988                .iter()
 989                .find_map(|kind| TargetKind::try_from(kind.as_ref()).ok())
 990            else {
 991                continue;
 992            };
 993            let target_path = PathBuf::from(target.src_path);
 994            if target_path == abs_path {
 995                return manifest_path.map(|(path, _)| {
 996                    (
 997                        package_name_from_pkgid(&package.id).map(|package_name| TargetInfo {
 998                            package_name: package_name.to_owned(),
 999                            target_name: target.name,
1000                            required_features: target.required_features,
1001                            target_kind: bin_kind,
1002                        }),
1003                        path,
1004                    )
1005                });
1006            }
1007        }
1008    }
1009
1010    manifest_path.map(|(path, _)| (None, path))
1011}
1012
1013async fn human_readable_package_name(
1014    package_directory: &Path,
1015    project_env: Option<&HashMap<String, String>>,
1016) -> Option<String> {
1017    let mut command = util::command::new_smol_command("cargo");
1018    if let Some(envs) = project_env {
1019        command.envs(envs);
1020    }
1021    let pkgid = String::from_utf8(
1022        command
1023            .current_dir(package_directory)
1024            .arg("pkgid")
1025            .output()
1026            .await
1027            .log_err()?
1028            .stdout,
1029    )
1030    .ok()?;
1031    Some(package_name_from_pkgid(&pkgid)?.to_owned())
1032}
1033
1034// For providing local `cargo check -p $pkgid` task, we do not need most of the information we have returned.
1035// Output example in the root of Zed project:
1036// ```sh
1037// ❯ cargo pkgid zed
1038// path+file:///absolute/path/to/project/zed/crates/zed#0.131.0
1039// ```
1040// Another variant, if a project has a custom package name or hyphen in the name:
1041// ```
1042// path+file:///absolute/path/to/project/custom-package#my-custom-package@0.1.0
1043// ```
1044//
1045// Extracts the package name from the output according to the spec:
1046// https://doc.rust-lang.org/cargo/reference/pkgid-spec.html#specification-grammar
1047fn package_name_from_pkgid(pkgid: &str) -> Option<&str> {
1048    fn split_off_suffix(input: &str, suffix_start: char) -> &str {
1049        match input.rsplit_once(suffix_start) {
1050            Some((without_suffix, _)) => without_suffix,
1051            None => input,
1052        }
1053    }
1054
1055    let (version_prefix, version_suffix) = pkgid.trim().rsplit_once('#')?;
1056    let package_name = match version_suffix.rsplit_once('@') {
1057        Some((custom_package_name, _version)) => custom_package_name,
1058        None => {
1059            let host_and_path = split_off_suffix(version_prefix, '?');
1060            let (_, package_name) = host_and_path.rsplit_once('/')?;
1061            package_name
1062        }
1063    };
1064    Some(package_name)
1065}
1066
1067async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
1068    maybe!(async {
1069        let mut last = None;
1070        let mut entries = fs::read_dir(&container_dir).await?;
1071        while let Some(entry) = entries.next().await {
1072            let path = entry?.path();
1073            if path.extension().is_some_and(|ext| ext == "metadata") {
1074                continue;
1075            }
1076            last = Some(path);
1077        }
1078
1079        let path = last.context("no cached binary")?;
1080        let path = match RustLspAdapter::GITHUB_ASSET_KIND {
1081            AssetKind::TarGz | AssetKind::Gz => path, // Tar and gzip extract in place.
1082            AssetKind::Zip => path.join("rust-analyzer.exe"), // zip contains a .exe
1083        };
1084
1085        anyhow::Ok(LanguageServerBinary {
1086            path,
1087            env: None,
1088            arguments: Default::default(),
1089        })
1090    })
1091    .await
1092    .log_err()
1093}
1094
1095fn test_fragment(variables: &TaskVariables, path: &Path, stem: &str) -> String {
1096    let fragment = if stem == "lib" {
1097        // This isn't quite right---it runs the tests for the entire library, rather than
1098        // just for the top-level `mod tests`. But we don't really have the means here to
1099        // filter out just that module.
1100        Some("--lib".to_owned())
1101    } else if stem == "mod" {
1102        maybe!({ Some(path.parent()?.file_name()?.to_string_lossy().into_owned()) })
1103    } else if stem == "main" {
1104        if let (Some(bin_name), Some(bin_kind)) = (
1105            variables.get(&RUST_BIN_NAME_TASK_VARIABLE),
1106            variables.get(&RUST_BIN_KIND_TASK_VARIABLE),
1107        ) {
1108            Some(format!("--{bin_kind}={bin_name}"))
1109        } else {
1110            None
1111        }
1112    } else {
1113        Some(stem.to_owned())
1114    };
1115    fragment.unwrap_or_else(|| "--".to_owned())
1116}
1117
1118#[cfg(test)]
1119mod tests {
1120    use std::num::NonZeroU32;
1121
1122    use super::*;
1123    use crate::language;
1124    use gpui::{BorrowAppContext, Hsla, TestAppContext};
1125    use lsp::CompletionItemLabelDetails;
1126    use settings::SettingsStore;
1127    use theme::SyntaxTheme;
1128    use util::path;
1129
1130    #[gpui::test]
1131    async fn test_process_rust_diagnostics() {
1132        let mut params = lsp::PublishDiagnosticsParams {
1133            uri: lsp::Uri::from_file_path(path!("/a")).unwrap(),
1134            version: None,
1135            diagnostics: vec![
1136                // no newlines
1137                lsp::Diagnostic {
1138                    message: "use of moved value `a`".to_string(),
1139                    ..Default::default()
1140                },
1141                // newline at the end of a code span
1142                lsp::Diagnostic {
1143                    message: "consider importing this struct: `use b::c;\n`".to_string(),
1144                    ..Default::default()
1145                },
1146                // code span starting right after a newline
1147                lsp::Diagnostic {
1148                    message: "cannot borrow `self.d` as mutable\n`self` is a `&` reference"
1149                        .to_string(),
1150                    ..Default::default()
1151                },
1152            ],
1153        };
1154        RustLspAdapter.process_diagnostics(&mut params, LanguageServerId(0), None);
1155
1156        assert_eq!(params.diagnostics[0].message, "use of moved value `a`");
1157
1158        // remove trailing newline from code span
1159        assert_eq!(
1160            params.diagnostics[1].message,
1161            "consider importing this struct: `use b::c;`"
1162        );
1163
1164        // do not remove newline before the start of code span
1165        assert_eq!(
1166            params.diagnostics[2].message,
1167            "cannot borrow `self.d` as mutable\n`self` is a `&` reference"
1168        );
1169    }
1170
1171    #[gpui::test]
1172    async fn test_rust_label_for_completion() {
1173        let adapter = Arc::new(RustLspAdapter);
1174        let language = language("rust", tree_sitter_rust::LANGUAGE.into());
1175        let grammar = language.grammar().unwrap();
1176        let theme = SyntaxTheme::new_test([
1177            ("type", Hsla::default()),
1178            ("keyword", Hsla::default()),
1179            ("function", Hsla::default()),
1180            ("property", Hsla::default()),
1181        ]);
1182
1183        language.set_theme(&theme);
1184
1185        let highlight_function = grammar.highlight_id_for_name("function").unwrap();
1186        let highlight_type = grammar.highlight_id_for_name("type").unwrap();
1187        let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
1188        let highlight_field = grammar.highlight_id_for_name("property").unwrap();
1189
1190        assert_eq!(
1191            adapter
1192                .label_for_completion(
1193                    &lsp::CompletionItem {
1194                        kind: Some(lsp::CompletionItemKind::FUNCTION),
1195                        label: "hello(…)".to_string(),
1196                        label_details: Some(CompletionItemLabelDetails {
1197                            detail: Some("(use crate::foo)".into()),
1198                            description: Some("fn(&mut Option<T>) -> Vec<T>".to_string())
1199                        }),
1200                        ..Default::default()
1201                    },
1202                    &language
1203                )
1204                .await,
1205            Some(CodeLabel {
1206                text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
1207                filter_range: 0..5,
1208                runs: vec![
1209                    (0..5, highlight_function),
1210                    (7..10, highlight_keyword),
1211                    (11..17, highlight_type),
1212                    (18..19, highlight_type),
1213                    (25..28, highlight_type),
1214                    (29..30, highlight_type),
1215                ],
1216            })
1217        );
1218        assert_eq!(
1219            adapter
1220                .label_for_completion(
1221                    &lsp::CompletionItem {
1222                        kind: Some(lsp::CompletionItemKind::FUNCTION),
1223                        label: "hello(…)".to_string(),
1224                        label_details: Some(CompletionItemLabelDetails {
1225                            detail: Some("(use crate::foo)".into()),
1226                            description: Some("async fn(&mut Option<T>) -> Vec<T>".to_string()),
1227                        }),
1228                        ..Default::default()
1229                    },
1230                    &language
1231                )
1232                .await,
1233            Some(CodeLabel {
1234                text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
1235                filter_range: 0..5,
1236                runs: vec![
1237                    (0..5, highlight_function),
1238                    (7..10, highlight_keyword),
1239                    (11..17, highlight_type),
1240                    (18..19, highlight_type),
1241                    (25..28, highlight_type),
1242                    (29..30, highlight_type),
1243                ],
1244            })
1245        );
1246        assert_eq!(
1247            adapter
1248                .label_for_completion(
1249                    &lsp::CompletionItem {
1250                        kind: Some(lsp::CompletionItemKind::FIELD),
1251                        label: "len".to_string(),
1252                        detail: Some("usize".to_string()),
1253                        ..Default::default()
1254                    },
1255                    &language
1256                )
1257                .await,
1258            Some(CodeLabel {
1259                text: "len: usize".to_string(),
1260                filter_range: 0..3,
1261                runs: vec![(0..3, highlight_field), (5..10, highlight_type),],
1262            })
1263        );
1264
1265        assert_eq!(
1266            adapter
1267                .label_for_completion(
1268                    &lsp::CompletionItem {
1269                        kind: Some(lsp::CompletionItemKind::FUNCTION),
1270                        label: "hello(…)".to_string(),
1271                        label_details: Some(CompletionItemLabelDetails {
1272                            detail: Some("(use crate::foo)".to_string()),
1273                            description: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
1274                        }),
1275
1276                        ..Default::default()
1277                    },
1278                    &language
1279                )
1280                .await,
1281            Some(CodeLabel {
1282                text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
1283                filter_range: 0..5,
1284                runs: vec![
1285                    (0..5, highlight_function),
1286                    (7..10, highlight_keyword),
1287                    (11..17, highlight_type),
1288                    (18..19, highlight_type),
1289                    (25..28, highlight_type),
1290                    (29..30, highlight_type),
1291                ],
1292            })
1293        );
1294
1295        assert_eq!(
1296            adapter
1297                .label_for_completion(
1298                    &lsp::CompletionItem {
1299                        kind: Some(lsp::CompletionItemKind::FUNCTION),
1300                        label: "hello".to_string(),
1301                        label_details: Some(CompletionItemLabelDetails {
1302                            detail: Some("(use crate::foo)".to_string()),
1303                            description: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
1304                        }),
1305                        ..Default::default()
1306                    },
1307                    &language
1308                )
1309                .await,
1310            Some(CodeLabel {
1311                text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
1312                filter_range: 0..5,
1313                runs: vec![
1314                    (0..5, highlight_function),
1315                    (7..10, highlight_keyword),
1316                    (11..17, highlight_type),
1317                    (18..19, highlight_type),
1318                    (25..28, highlight_type),
1319                    (29..30, highlight_type),
1320                ],
1321            })
1322        );
1323
1324        assert_eq!(
1325            adapter
1326                .label_for_completion(
1327                    &lsp::CompletionItem {
1328                        kind: Some(lsp::CompletionItemKind::METHOD),
1329                        label: "await.as_deref_mut()".to_string(),
1330                        filter_text: Some("as_deref_mut".to_string()),
1331                        label_details: Some(CompletionItemLabelDetails {
1332                            detail: None,
1333                            description: Some("fn(&mut self) -> IterMut<'_, T>".to_string()),
1334                        }),
1335                        ..Default::default()
1336                    },
1337                    &language
1338                )
1339                .await,
1340            Some(CodeLabel {
1341                text: "await.as_deref_mut(&mut self) -> IterMut<'_, T>".to_string(),
1342                filter_range: 6..18,
1343                runs: vec![
1344                    (6..18, HighlightId(2)),
1345                    (20..23, HighlightId(1)),
1346                    (33..40, HighlightId(0)),
1347                    (45..46, HighlightId(0))
1348                ],
1349            })
1350        );
1351
1352        assert_eq!(
1353            adapter
1354                .label_for_completion(
1355                    &lsp::CompletionItem {
1356                        kind: Some(lsp::CompletionItemKind::METHOD),
1357                        label: "as_deref_mut()".to_string(),
1358                        filter_text: Some("as_deref_mut".to_string()),
1359                        label_details: Some(CompletionItemLabelDetails {
1360                            detail: None,
1361                            description: Some(
1362                                "pub fn as_deref_mut(&mut self) -> IterMut<'_, T>".to_string()
1363                            ),
1364                        }),
1365                        ..Default::default()
1366                    },
1367                    &language
1368                )
1369                .await,
1370            Some(CodeLabel {
1371                text: "pub fn as_deref_mut(&mut self) -> IterMut<'_, T>".to_string(),
1372                filter_range: 7..19,
1373                runs: vec![
1374                    (0..3, HighlightId(1)),
1375                    (4..6, HighlightId(1)),
1376                    (7..19, HighlightId(2)),
1377                    (21..24, HighlightId(1)),
1378                    (34..41, HighlightId(0)),
1379                    (46..47, HighlightId(0))
1380                ],
1381            })
1382        );
1383
1384        assert_eq!(
1385            adapter
1386                .label_for_completion(
1387                    &lsp::CompletionItem {
1388                        kind: Some(lsp::CompletionItemKind::FIELD),
1389                        label: "inner_value".to_string(),
1390                        filter_text: Some("value".to_string()),
1391                        detail: Some("String".to_string()),
1392                        ..Default::default()
1393                    },
1394                    &language,
1395                )
1396                .await,
1397            Some(CodeLabel {
1398                text: "inner_value: String".to_string(),
1399                filter_range: 6..11,
1400                runs: vec![(0..11, HighlightId(3)), (13..19, HighlightId(0))],
1401            })
1402        );
1403    }
1404
1405    #[gpui::test]
1406    async fn test_rust_label_for_symbol() {
1407        let adapter = Arc::new(RustLspAdapter);
1408        let language = language("rust", tree_sitter_rust::LANGUAGE.into());
1409        let grammar = language.grammar().unwrap();
1410        let theme = SyntaxTheme::new_test([
1411            ("type", Hsla::default()),
1412            ("keyword", Hsla::default()),
1413            ("function", Hsla::default()),
1414            ("property", Hsla::default()),
1415        ]);
1416
1417        language.set_theme(&theme);
1418
1419        let highlight_function = grammar.highlight_id_for_name("function").unwrap();
1420        let highlight_type = grammar.highlight_id_for_name("type").unwrap();
1421        let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
1422
1423        assert_eq!(
1424            adapter
1425                .label_for_symbol("hello", lsp::SymbolKind::FUNCTION, &language)
1426                .await,
1427            Some(CodeLabel {
1428                text: "fn hello".to_string(),
1429                filter_range: 3..8,
1430                runs: vec![(0..2, highlight_keyword), (3..8, highlight_function)],
1431            })
1432        );
1433
1434        assert_eq!(
1435            adapter
1436                .label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER, &language)
1437                .await,
1438            Some(CodeLabel {
1439                text: "type World".to_string(),
1440                filter_range: 5..10,
1441                runs: vec![(0..4, highlight_keyword), (5..10, highlight_type)],
1442            })
1443        );
1444    }
1445
1446    #[gpui::test]
1447    async fn test_rust_autoindent(cx: &mut TestAppContext) {
1448        // cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
1449        cx.update(|cx| {
1450            let test_settings = SettingsStore::test(cx);
1451            cx.set_global(test_settings);
1452            language::init(cx);
1453            cx.update_global::<SettingsStore, _>(|store, cx| {
1454                store.update_user_settings(cx, |s| {
1455                    s.project.all_languages.defaults.tab_size = NonZeroU32::new(2);
1456                });
1457            });
1458        });
1459
1460        let language = crate::language("rust", tree_sitter_rust::LANGUAGE.into());
1461
1462        cx.new(|cx| {
1463            let mut buffer = Buffer::local("", cx).with_language(language, cx);
1464
1465            // indent between braces
1466            buffer.set_text("fn a() {}", cx);
1467            let ix = buffer.len() - 1;
1468            buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
1469            assert_eq!(buffer.text(), "fn a() {\n  \n}");
1470
1471            // indent between braces, even after empty lines
1472            buffer.set_text("fn a() {\n\n\n}", cx);
1473            let ix = buffer.len() - 2;
1474            buffer.edit([(ix..ix, "\n")], Some(AutoindentMode::EachLine), cx);
1475            assert_eq!(buffer.text(), "fn a() {\n\n\n  \n}");
1476
1477            // indent a line that continues a field expression
1478            buffer.set_text("fn a() {\n  \n}", cx);
1479            let ix = buffer.len() - 2;
1480            buffer.edit([(ix..ix, "b\n.c")], Some(AutoindentMode::EachLine), cx);
1481            assert_eq!(buffer.text(), "fn a() {\n  b\n    .c\n}");
1482
1483            // indent further lines that continue the field expression, even after empty lines
1484            let ix = buffer.len() - 2;
1485            buffer.edit([(ix..ix, "\n\n.d")], Some(AutoindentMode::EachLine), cx);
1486            assert_eq!(buffer.text(), "fn a() {\n  b\n    .c\n    \n    .d\n}");
1487
1488            // dedent the line after the field expression
1489            let ix = buffer.len() - 2;
1490            buffer.edit([(ix..ix, ";\ne")], Some(AutoindentMode::EachLine), cx);
1491            assert_eq!(
1492                buffer.text(),
1493                "fn a() {\n  b\n    .c\n    \n    .d;\n  e\n}"
1494            );
1495
1496            // indent inside a struct within a call
1497            buffer.set_text("const a: B = c(D {});", cx);
1498            let ix = buffer.len() - 3;
1499            buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
1500            assert_eq!(buffer.text(), "const a: B = c(D {\n  \n});");
1501
1502            // indent further inside a nested call
1503            let ix = buffer.len() - 4;
1504            buffer.edit([(ix..ix, "e: f(\n\n)")], Some(AutoindentMode::EachLine), cx);
1505            assert_eq!(buffer.text(), "const a: B = c(D {\n  e: f(\n    \n  )\n});");
1506
1507            // keep that indent after an empty line
1508            let ix = buffer.len() - 8;
1509            buffer.edit([(ix..ix, "\n")], Some(AutoindentMode::EachLine), cx);
1510            assert_eq!(
1511                buffer.text(),
1512                "const a: B = c(D {\n  e: f(\n    \n    \n  )\n});"
1513            );
1514
1515            buffer
1516        });
1517    }
1518
1519    #[test]
1520    fn test_package_name_from_pkgid() {
1521        for (input, expected) in [
1522            (
1523                "path+file:///absolute/path/to/project/zed/crates/zed#0.131.0",
1524                "zed",
1525            ),
1526            (
1527                "path+file:///absolute/path/to/project/custom-package#my-custom-package@0.1.0",
1528                "my-custom-package",
1529            ),
1530        ] {
1531            assert_eq!(package_name_from_pkgid(input), Some(expected));
1532        }
1533    }
1534
1535    #[test]
1536    fn test_target_info_from_metadata() {
1537        for (input, absolute_path, expected) in [
1538            (
1539                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"}]}]}"#,
1540                "/path/to/zed/src/main.rs",
1541                Some((
1542                    Some(TargetInfo {
1543                        package_name: "zed".into(),
1544                        target_name: "zed".into(),
1545                        required_features: Vec::new(),
1546                        target_kind: TargetKind::Bin,
1547                    }),
1548                    Arc::from("/path/to/zed".as_ref()),
1549                )),
1550            ),
1551            (
1552                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"}]}]}"#,
1553                "/path/to/custom-package/src/main.rs",
1554                Some((
1555                    Some(TargetInfo {
1556                        package_name: "my-custom-package".into(),
1557                        target_name: "my-custom-bin".into(),
1558                        required_features: Vec::new(),
1559                        target_kind: TargetKind::Bin,
1560                    }),
1561                    Arc::from("/path/to/custom-package".as_ref()),
1562                )),
1563            ),
1564            (
1565                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"}]}"#,
1566                "/path/to/custom-package/src/main.rs",
1567                Some((
1568                    Some(TargetInfo {
1569                        package_name: "my-custom-package".into(),
1570                        target_name: "my-custom-bin".into(),
1571                        required_features: Vec::new(),
1572                        target_kind: TargetKind::Example,
1573                    }),
1574                    Arc::from("/path/to/custom-package".as_ref()),
1575                )),
1576            ),
1577            (
1578                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"]}]}]}"#,
1579                "/path/to/custom-package/src/main.rs",
1580                Some((
1581                    Some(TargetInfo {
1582                        package_name: "my-custom-package".into(),
1583                        target_name: "my-custom-bin".into(),
1584                        required_features: vec!["foo".to_owned(), "bar".to_owned()],
1585                        target_kind: TargetKind::Example,
1586                    }),
1587                    Arc::from("/path/to/custom-package".as_ref()),
1588                )),
1589            ),
1590            (
1591                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"}]}"#,
1592                "/path/to/custom-package/src/main.rs",
1593                Some((
1594                    Some(TargetInfo {
1595                        package_name: "my-custom-package".into(),
1596                        target_name: "my-custom-bin".into(),
1597                        required_features: vec![],
1598                        target_kind: TargetKind::Example,
1599                    }),
1600                    Arc::from("/path/to/custom-package".as_ref()),
1601                )),
1602            ),
1603            (
1604                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"}]}"#,
1605                "/path/to/custom-package/src/main.rs",
1606                Some((None, Arc::from("/path/to/custom-package".as_ref()))),
1607            ),
1608        ] {
1609            let metadata: CargoMetadata = serde_json::from_str(input).context(input).unwrap();
1610
1611            let absolute_path = Path::new(absolute_path);
1612
1613            assert_eq!(target_info_from_metadata(metadata, absolute_path), expected);
1614        }
1615    }
1616
1617    #[test]
1618    fn test_rust_test_fragment() {
1619        #[track_caller]
1620        fn check(
1621            variables: impl IntoIterator<Item = (VariableName, &'static str)>,
1622            path: &str,
1623            expected: &str,
1624        ) {
1625            let path = Path::new(path);
1626            let found = test_fragment(
1627                &TaskVariables::from_iter(variables.into_iter().map(|(k, v)| (k, v.to_owned()))),
1628                path,
1629                path.file_stem().unwrap().to_str().unwrap(),
1630            );
1631            assert_eq!(expected, found);
1632        }
1633
1634        check([], "/project/src/lib.rs", "--lib");
1635        check([], "/project/src/foo/mod.rs", "foo");
1636        check(
1637            [
1638                (RUST_BIN_KIND_TASK_VARIABLE.clone(), "bin"),
1639                (RUST_BIN_NAME_TASK_VARIABLE, "x"),
1640            ],
1641            "/project/src/main.rs",
1642            "--bin=x",
1643        );
1644        check([], "/project/src/main.rs", "--");
1645    }
1646}