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