rust.rs

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