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