rust.rs

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