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