rust.rs

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