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