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