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