rust.rs

   1use anyhow::{anyhow, bail, Context, Result};
   2use async_compression::futures::bufread::GzipDecoder;
   3use async_trait::async_trait;
   4use futures::{io::BufReader, StreamExt};
   5use gpui::{AppContext, AsyncAppContext};
   6use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
   7pub use language::*;
   8use language_settings::all_language_settings;
   9use lsp::LanguageServerBinary;
  10use project::{lsp_store::language_server_settings, project_settings::BinarySettings};
  11use regex::Regex;
  12use smol::fs::{self, File};
  13use std::{
  14    any::Any,
  15    borrow::Cow,
  16    env::consts,
  17    path::{Path, PathBuf},
  18    sync::Arc,
  19    sync::LazyLock,
  20};
  21use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};
  22use util::{fs::remove_matching, maybe, ResultExt};
  23
  24pub struct RustLspAdapter;
  25
  26impl RustLspAdapter {
  27    const SERVER_NAME: &'static str = "rust-analyzer";
  28}
  29
  30#[async_trait(?Send)]
  31impl LspAdapter for RustLspAdapter {
  32    fn name(&self) -> LanguageServerName {
  33        LanguageServerName(Self::SERVER_NAME.into())
  34    }
  35
  36    async fn check_if_user_installed(
  37        &self,
  38        delegate: &dyn LspAdapterDelegate,
  39        cx: &AsyncAppContext,
  40    ) -> Option<LanguageServerBinary> {
  41        let configured_binary = cx
  42            .update(|cx| {
  43                language_server_settings(delegate, Self::SERVER_NAME, cx)
  44                    .and_then(|s| s.binary.clone())
  45            })
  46            .ok()?;
  47
  48        let (path, env, arguments) = match configured_binary {
  49            // If nothing is configured, or path_lookup explicitly enabled,
  50            // we lookup the binary in the path.
  51            None
  52            | Some(BinarySettings {
  53                path: None,
  54                path_lookup: Some(true),
  55                ..
  56            })
  57            | Some(BinarySettings {
  58                path: None,
  59                path_lookup: None,
  60                ..
  61            }) => {
  62                let path = delegate.which(Self::SERVER_NAME.as_ref()).await;
  63                let env = delegate.shell_env().await;
  64                (path, Some(env), None)
  65            }
  66            // Otherwise, we use the configured binary.
  67            Some(BinarySettings {
  68                path: Some(path),
  69                arguments,
  70                path_lookup,
  71            }) => {
  72                if path_lookup.is_some() {
  73                    log::warn!("Both `path` and `path_lookup` are set, ignoring `path_lookup`");
  74                }
  75                (Some(path.into()), None, arguments)
  76            }
  77
  78            _ => (None, None, None),
  79        };
  80
  81        path.map(|path| LanguageServerBinary {
  82            path,
  83            env,
  84            arguments: arguments
  85                .unwrap_or_default()
  86                .iter()
  87                .map(|arg| arg.into())
  88                .collect(),
  89        })
  90    }
  91
  92    async fn fetch_latest_server_version(
  93        &self,
  94        delegate: &dyn LspAdapterDelegate,
  95    ) -> Result<Box<dyn 'static + Send + Any>> {
  96        let release = latest_github_release(
  97            "rust-lang/rust-analyzer",
  98            true,
  99            false,
 100            delegate.http_client(),
 101        )
 102        .await?;
 103        let os = match consts::OS {
 104            "macos" => "apple-darwin",
 105            "linux" => "unknown-linux-gnu",
 106            "windows" => "pc-windows-msvc",
 107            other => bail!("Running on unsupported os: {other}"),
 108        };
 109        let asset_name = format!("rust-analyzer-{}-{os}.gz", consts::ARCH);
 110        let asset = release
 111            .assets
 112            .iter()
 113            .find(|asset| asset.name == asset_name)
 114            .with_context(|| format!("no asset found matching `{asset_name:?}`"))?;
 115        Ok(Box::new(GitHubLspBinaryVersion {
 116            name: release.tag_name,
 117            url: asset.browser_download_url.clone(),
 118        }))
 119    }
 120
 121    async fn fetch_server_binary(
 122        &self,
 123        version: Box<dyn 'static + Send + Any>,
 124        container_dir: PathBuf,
 125        delegate: &dyn LspAdapterDelegate,
 126    ) -> Result<LanguageServerBinary> {
 127        let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
 128        let destination_path = container_dir.join(format!("rust-analyzer-{}", version.name));
 129
 130        if fs::metadata(&destination_path).await.is_err() {
 131            let mut response = delegate
 132                .http_client()
 133                .get(&version.url, Default::default(), true)
 134                .await
 135                .map_err(|err| anyhow!("error downloading release: {}", err))?;
 136            let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
 137            let mut file = File::create(&destination_path).await?;
 138            futures::io::copy(decompressed_bytes, &mut file).await?;
 139            // todo("windows")
 140            #[cfg(not(windows))]
 141            {
 142                fs::set_permissions(
 143                    &destination_path,
 144                    <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
 145                )
 146                .await?;
 147            }
 148
 149            remove_matching(&container_dir, |entry| entry != destination_path).await;
 150        }
 151
 152        Ok(LanguageServerBinary {
 153            path: destination_path,
 154            env: None,
 155            arguments: Default::default(),
 156        })
 157    }
 158
 159    async fn cached_server_binary(
 160        &self,
 161        container_dir: PathBuf,
 162        _: &dyn LspAdapterDelegate,
 163    ) -> Option<LanguageServerBinary> {
 164        get_cached_server_binary(container_dir).await
 165    }
 166
 167    async fn installation_test_binary(
 168        &self,
 169        container_dir: PathBuf,
 170    ) -> Option<LanguageServerBinary> {
 171        get_cached_server_binary(container_dir)
 172            .await
 173            .map(|mut binary| {
 174                binary.arguments = vec!["--help".into()];
 175                binary
 176            })
 177    }
 178
 179    fn disk_based_diagnostic_sources(&self) -> Vec<String> {
 180        vec!["rustc".into()]
 181    }
 182
 183    fn disk_based_diagnostics_progress_token(&self) -> Option<String> {
 184        Some("rust-analyzer/flycheck".into())
 185    }
 186
 187    fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
 188        static REGEX: LazyLock<Regex> =
 189            LazyLock::new(|| Regex::new(r"(?m)`([^`]+)\n`$").expect("Failed to create REGEX"));
 190
 191        for diagnostic in &mut params.diagnostics {
 192            for message in diagnostic
 193                .related_information
 194                .iter_mut()
 195                .flatten()
 196                .map(|info| &mut info.message)
 197                .chain([&mut diagnostic.message])
 198            {
 199                if let Cow::Owned(sanitized) = REGEX.replace_all(message, "`$1`") {
 200                    *message = sanitized;
 201                }
 202            }
 203        }
 204    }
 205
 206    async fn label_for_completion(
 207        &self,
 208        completion: &lsp::CompletionItem,
 209        language: &Arc<Language>,
 210    ) -> Option<CodeLabel> {
 211        let detail = completion
 212            .label_details
 213            .as_ref()
 214            .and_then(|detail| detail.detail.as_ref())
 215            .or(completion.detail.as_ref())
 216            .map(ToOwned::to_owned);
 217        let function_signature = completion
 218            .label_details
 219            .as_ref()
 220            .and_then(|detail| detail.description.as_ref())
 221            .or(completion.detail.as_ref())
 222            .map(ToOwned::to_owned);
 223        match completion.kind {
 224            Some(lsp::CompletionItemKind::FIELD) if detail.is_some() => {
 225                let name = &completion.label;
 226                let text = format!("{}: {}", name, detail.unwrap());
 227                let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
 228                let runs = language.highlight_text(&source, 11..11 + text.len());
 229                return Some(CodeLabel {
 230                    text,
 231                    runs,
 232                    filter_range: 0..name.len(),
 233                });
 234            }
 235            Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE)
 236                if detail.is_some()
 237                    && completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) =>
 238            {
 239                let name = &completion.label;
 240                let text = format!(
 241                    "{}: {}",
 242                    name,
 243                    completion.detail.as_ref().or(detail.as_ref()).unwrap()
 244                );
 245                let source = Rope::from(format!("let {} = ();", text).as_str());
 246                let runs = language.highlight_text(&source, 4..4 + text.len());
 247                return Some(CodeLabel {
 248                    text,
 249                    runs,
 250                    filter_range: 0..name.len(),
 251                });
 252            }
 253            Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD)
 254                if detail.is_some() =>
 255            {
 256                static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("\\(…?\\)").unwrap());
 257
 258                let detail = detail.unwrap();
 259                const FUNCTION_PREFIXES: [&str; 6] = [
 260                    "async fn",
 261                    "async unsafe fn",
 262                    "const fn",
 263                    "const unsafe fn",
 264                    "unsafe fn",
 265                    "fn",
 266                ];
 267                // Is it function `async`?
 268                let fn_keyword = FUNCTION_PREFIXES.iter().find_map(|prefix| {
 269                    function_signature.as_ref().and_then(|signature| {
 270                        signature
 271                            .strip_prefix(*prefix)
 272                            .map(|suffix| (*prefix, suffix))
 273                    })
 274                });
 275                // fn keyword should be followed by opening parenthesis.
 276                if let Some((prefix, suffix)) = fn_keyword {
 277                    let mut text = REGEX.replace(&completion.label, suffix).to_string();
 278                    let source = Rope::from(format!("{prefix} {} {{}}", text).as_str());
 279                    let run_start = prefix.len() + 1;
 280                    let runs = language.highlight_text(&source, run_start..run_start + text.len());
 281                    if detail.starts_with(" (") {
 282                        text.push_str(&detail);
 283                    }
 284
 285                    return Some(CodeLabel {
 286                        filter_range: 0..completion.label.find('(').unwrap_or(text.len()),
 287                        text,
 288                        runs,
 289                    });
 290                } else if completion
 291                    .detail
 292                    .as_ref()
 293                    .map_or(false, |detail| detail.starts_with("macro_rules! "))
 294                {
 295                    let source = Rope::from(completion.label.as_str());
 296                    let runs = language.highlight_text(&source, 0..completion.label.len());
 297
 298                    return Some(CodeLabel {
 299                        filter_range: 0..completion.label.len(),
 300                        text: completion.label.clone(),
 301                        runs,
 302                    });
 303                }
 304            }
 305            Some(kind) => {
 306                let highlight_name = match kind {
 307                    lsp::CompletionItemKind::STRUCT
 308                    | lsp::CompletionItemKind::INTERFACE
 309                    | lsp::CompletionItemKind::ENUM => Some("type"),
 310                    lsp::CompletionItemKind::ENUM_MEMBER => Some("variant"),
 311                    lsp::CompletionItemKind::KEYWORD => Some("keyword"),
 312                    lsp::CompletionItemKind::VALUE | lsp::CompletionItemKind::CONSTANT => {
 313                        Some("constant")
 314                    }
 315                    _ => None,
 316                };
 317
 318                let mut label = completion.label.clone();
 319                if let Some(detail) = detail.filter(|detail| detail.starts_with(" (")) {
 320                    use std::fmt::Write;
 321                    write!(label, "{detail}").ok()?;
 322                }
 323                let mut label = CodeLabel::plain(label, None);
 324                if let Some(highlight_name) = highlight_name {
 325                    let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name)?;
 326                    label.runs.push((
 327                        0..label.text.rfind('(').unwrap_or(completion.label.len()),
 328                        highlight_id,
 329                    ));
 330                }
 331
 332                return Some(label);
 333            }
 334            _ => {}
 335        }
 336        None
 337    }
 338
 339    async fn label_for_symbol(
 340        &self,
 341        name: &str,
 342        kind: lsp::SymbolKind,
 343        language: &Arc<Language>,
 344    ) -> Option<CodeLabel> {
 345        let (text, filter_range, display_range) = match kind {
 346            lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
 347                let text = format!("fn {} () {{}}", name);
 348                let filter_range = 3..3 + name.len();
 349                let display_range = 0..filter_range.end;
 350                (text, filter_range, display_range)
 351            }
 352            lsp::SymbolKind::STRUCT => {
 353                let text = format!("struct {} {{}}", name);
 354                let filter_range = 7..7 + name.len();
 355                let display_range = 0..filter_range.end;
 356                (text, filter_range, display_range)
 357            }
 358            lsp::SymbolKind::ENUM => {
 359                let text = format!("enum {} {{}}", name);
 360                let filter_range = 5..5 + name.len();
 361                let display_range = 0..filter_range.end;
 362                (text, filter_range, display_range)
 363            }
 364            lsp::SymbolKind::INTERFACE => {
 365                let text = format!("trait {} {{}}", name);
 366                let filter_range = 6..6 + name.len();
 367                let display_range = 0..filter_range.end;
 368                (text, filter_range, display_range)
 369            }
 370            lsp::SymbolKind::CONSTANT => {
 371                let text = format!("const {}: () = ();", name);
 372                let filter_range = 6..6 + name.len();
 373                let display_range = 0..filter_range.end;
 374                (text, filter_range, display_range)
 375            }
 376            lsp::SymbolKind::MODULE => {
 377                let text = format!("mod {} {{}}", name);
 378                let filter_range = 4..4 + name.len();
 379                let display_range = 0..filter_range.end;
 380                (text, filter_range, display_range)
 381            }
 382            lsp::SymbolKind::TYPE_PARAMETER => {
 383                let text = format!("type {} {{}}", name);
 384                let filter_range = 5..5 + name.len();
 385                let display_range = 0..filter_range.end;
 386                (text, filter_range, display_range)
 387            }
 388            _ => return None,
 389        };
 390
 391        Some(CodeLabel {
 392            runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
 393            text: text[display_range].to_string(),
 394            filter_range,
 395        })
 396    }
 397}
 398
 399pub(crate) struct RustContextProvider;
 400
 401const RUST_PACKAGE_TASK_VARIABLE: VariableName =
 402    VariableName::Custom(Cow::Borrowed("RUST_PACKAGE"));
 403
 404/// The bin name corresponding to the current file in Cargo.toml
 405const RUST_BIN_NAME_TASK_VARIABLE: VariableName =
 406    VariableName::Custom(Cow::Borrowed("RUST_BIN_NAME"));
 407
 408const RUST_MAIN_FUNCTION_TASK_VARIABLE: VariableName =
 409    VariableName::Custom(Cow::Borrowed("_rust_main_function_end"));
 410
 411impl ContextProvider for RustContextProvider {
 412    fn build_context(
 413        &self,
 414        task_variables: &TaskVariables,
 415        location: &Location,
 416        cx: &mut gpui::AppContext,
 417    ) -> Result<TaskVariables> {
 418        let local_abs_path = location
 419            .buffer
 420            .read(cx)
 421            .file()
 422            .and_then(|file| Some(file.as_local()?.abs_path(cx)));
 423
 424        let local_abs_path = local_abs_path.as_deref();
 425
 426        let is_main_function = task_variables
 427            .get(&RUST_MAIN_FUNCTION_TASK_VARIABLE)
 428            .is_some();
 429
 430        if is_main_function {
 431            if let Some((package_name, bin_name)) =
 432                local_abs_path.and_then(package_name_and_bin_name_from_abs_path)
 433            {
 434                return Ok(TaskVariables::from_iter([
 435                    (RUST_PACKAGE_TASK_VARIABLE.clone(), package_name),
 436                    (RUST_BIN_NAME_TASK_VARIABLE.clone(), bin_name),
 437                ]));
 438            }
 439        }
 440
 441        if let Some(package_name) = local_abs_path
 442            .and_then(|local_abs_path| local_abs_path.parent())
 443            .and_then(human_readable_package_name)
 444        {
 445            return Ok(TaskVariables::from_iter([(
 446                RUST_PACKAGE_TASK_VARIABLE.clone(),
 447                package_name,
 448            )]));
 449        }
 450
 451        Ok(TaskVariables::default())
 452    }
 453
 454    fn associated_tasks(
 455        &self,
 456        file: Option<Arc<dyn language::File>>,
 457        cx: &AppContext,
 458    ) -> Option<TaskTemplates> {
 459        const DEFAULT_RUN_NAME_STR: &str = "RUST_DEFAULT_PACKAGE_RUN";
 460        let package_to_run = all_language_settings(file.as_ref(), cx)
 461            .language(Some(&"Rust".into()))
 462            .tasks
 463            .variables
 464            .get(DEFAULT_RUN_NAME_STR);
 465        let run_task_args = if let Some(package_to_run) = package_to_run {
 466            vec!["run".into(), "-p".into(), package_to_run.clone()]
 467        } else {
 468            vec!["run".into()]
 469        };
 470        Some(TaskTemplates(vec![
 471            TaskTemplate {
 472                label: format!(
 473                    "cargo check -p {}",
 474                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 475                ),
 476                command: "cargo".into(),
 477                args: vec![
 478                    "check".into(),
 479                    "-p".into(),
 480                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 481                ],
 482                cwd: Some("$ZED_DIRNAME".to_owned()),
 483                ..TaskTemplate::default()
 484            },
 485            TaskTemplate {
 486                label: "cargo check --workspace --all-targets".into(),
 487                command: "cargo".into(),
 488                args: vec!["check".into(), "--workspace".into(), "--all-targets".into()],
 489                cwd: Some("$ZED_DIRNAME".to_owned()),
 490                ..TaskTemplate::default()
 491            },
 492            TaskTemplate {
 493                label: format!(
 494                    "cargo test -p {} {} -- --nocapture",
 495                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 496                    VariableName::Symbol.template_value(),
 497                ),
 498                command: "cargo".into(),
 499                args: vec![
 500                    "test".into(),
 501                    "-p".into(),
 502                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 503                    VariableName::Symbol.template_value(),
 504                    "--".into(),
 505                    "--nocapture".into(),
 506                ],
 507                tags: vec!["rust-test".to_owned()],
 508                cwd: Some("$ZED_DIRNAME".to_owned()),
 509                ..TaskTemplate::default()
 510            },
 511            TaskTemplate {
 512                label: format!(
 513                    "cargo test -p {} {}",
 514                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 515                    VariableName::Stem.template_value(),
 516                ),
 517                command: "cargo".into(),
 518                args: vec![
 519                    "test".into(),
 520                    "-p".into(),
 521                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 522                    VariableName::Stem.template_value(),
 523                ],
 524                tags: vec!["rust-mod-test".to_owned()],
 525                cwd: Some("$ZED_DIRNAME".to_owned()),
 526                ..TaskTemplate::default()
 527            },
 528            TaskTemplate {
 529                label: format!(
 530                    "cargo run -p {} --bin {}",
 531                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 532                    RUST_BIN_NAME_TASK_VARIABLE.template_value(),
 533                ),
 534                command: "cargo".into(),
 535                args: vec![
 536                    "run".into(),
 537                    "-p".into(),
 538                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 539                    "--bin".into(),
 540                    RUST_BIN_NAME_TASK_VARIABLE.template_value(),
 541                ],
 542                cwd: Some("$ZED_DIRNAME".to_owned()),
 543                tags: vec!["rust-main".to_owned()],
 544                ..TaskTemplate::default()
 545            },
 546            TaskTemplate {
 547                label: format!(
 548                    "cargo test -p {}",
 549                    RUST_PACKAGE_TASK_VARIABLE.template_value()
 550                ),
 551                command: "cargo".into(),
 552                args: vec![
 553                    "test".into(),
 554                    "-p".into(),
 555                    RUST_PACKAGE_TASK_VARIABLE.template_value(),
 556                ],
 557                cwd: Some("$ZED_DIRNAME".to_owned()),
 558                ..TaskTemplate::default()
 559            },
 560            TaskTemplate {
 561                label: "cargo run".into(),
 562                command: "cargo".into(),
 563                args: run_task_args,
 564                cwd: Some("$ZED_DIRNAME".to_owned()),
 565                ..TaskTemplate::default()
 566            },
 567            TaskTemplate {
 568                label: "cargo clean".into(),
 569                command: "cargo".into(),
 570                args: vec!["clean".into()],
 571                cwd: Some("$ZED_DIRNAME".to_owned()),
 572                ..TaskTemplate::default()
 573            },
 574        ]))
 575    }
 576}
 577
 578/// Part of the data structure of Cargo metadata
 579#[derive(serde::Deserialize)]
 580struct CargoMetadata {
 581    packages: Vec<CargoPackage>,
 582}
 583
 584#[derive(serde::Deserialize)]
 585struct CargoPackage {
 586    id: String,
 587    targets: Vec<CargoTarget>,
 588}
 589
 590#[derive(serde::Deserialize)]
 591struct CargoTarget {
 592    name: String,
 593    kind: Vec<String>,
 594    src_path: String,
 595}
 596
 597fn package_name_and_bin_name_from_abs_path(abs_path: &Path) -> Option<(String, String)> {
 598    let output = std::process::Command::new("cargo")
 599        .current_dir(abs_path.parent()?)
 600        .arg("metadata")
 601        .arg("--no-deps")
 602        .arg("--format-version")
 603        .arg("1")
 604        .output()
 605        .log_err()?
 606        .stdout;
 607
 608    let metadata: CargoMetadata = serde_json::from_slice(&output).log_err()?;
 609
 610    retrieve_package_id_and_bin_name_from_metadata(metadata, abs_path).and_then(
 611        |(package_id, bin_name)| {
 612            let package_name = package_name_from_pkgid(&package_id);
 613
 614            package_name.map(|package_name| (package_name.to_owned(), bin_name))
 615        },
 616    )
 617}
 618
 619fn retrieve_package_id_and_bin_name_from_metadata(
 620    metadata: CargoMetadata,
 621    abs_path: &Path,
 622) -> Option<(String, String)> {
 623    for package in metadata.packages {
 624        for target in package.targets {
 625            let is_bin = target.kind.iter().any(|kind| kind == "bin");
 626            let target_path = PathBuf::from(target.src_path);
 627            if target_path == abs_path && is_bin {
 628                return Some((package.id, target.name));
 629            }
 630        }
 631    }
 632
 633    None
 634}
 635
 636fn human_readable_package_name(package_directory: &Path) -> Option<String> {
 637    let pkgid = String::from_utf8(
 638        std::process::Command::new("cargo")
 639            .current_dir(package_directory)
 640            .arg("pkgid")
 641            .output()
 642            .log_err()?
 643            .stdout,
 644    )
 645    .ok()?;
 646    Some(package_name_from_pkgid(&pkgid)?.to_owned())
 647}
 648
 649// For providing local `cargo check -p $pkgid` task, we do not need most of the information we have returned.
 650// Output example in the root of Zed project:
 651// ```sh
 652// ❯ cargo pkgid zed
 653// path+file:///absolute/path/to/project/zed/crates/zed#0.131.0
 654// ```
 655// Another variant, if a project has a custom package name or hyphen in the name:
 656// ```
 657// path+file:///absolute/path/to/project/custom-package#my-custom-package@0.1.0
 658// ```
 659//
 660// Extracts the package name from the output according to the spec:
 661// https://doc.rust-lang.org/cargo/reference/pkgid-spec.html#specification-grammar
 662fn package_name_from_pkgid(pkgid: &str) -> Option<&str> {
 663    fn split_off_suffix(input: &str, suffix_start: char) -> &str {
 664        match input.rsplit_once(suffix_start) {
 665            Some((without_suffix, _)) => without_suffix,
 666            None => input,
 667        }
 668    }
 669
 670    let (version_prefix, version_suffix) = pkgid.trim().rsplit_once('#')?;
 671    let package_name = match version_suffix.rsplit_once('@') {
 672        Some((custom_package_name, _version)) => custom_package_name,
 673        None => {
 674            let host_and_path = split_off_suffix(version_prefix, '?');
 675            let (_, package_name) = host_and_path.rsplit_once('/')?;
 676            package_name
 677        }
 678    };
 679    Some(package_name)
 680}
 681
 682async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
 683    maybe!(async {
 684        let mut last = None;
 685        let mut entries = fs::read_dir(&container_dir).await?;
 686        while let Some(entry) = entries.next().await {
 687            last = Some(entry?.path());
 688        }
 689
 690        anyhow::Ok(LanguageServerBinary {
 691            path: last.ok_or_else(|| anyhow!("no cached binary"))?,
 692            env: None,
 693            arguments: Default::default(),
 694        })
 695    })
 696    .await
 697    .log_err()
 698}
 699
 700#[cfg(test)]
 701mod tests {
 702    use std::num::NonZeroU32;
 703
 704    use super::*;
 705    use crate::language;
 706    use gpui::{BorrowAppContext, Context, Hsla, TestAppContext};
 707    use language::language_settings::AllLanguageSettings;
 708    use lsp::CompletionItemLabelDetails;
 709    use settings::SettingsStore;
 710    use theme::SyntaxTheme;
 711
 712    #[gpui::test]
 713    async fn test_process_rust_diagnostics() {
 714        let mut params = lsp::PublishDiagnosticsParams {
 715            uri: lsp::Url::from_file_path("/a").unwrap(),
 716            version: None,
 717            diagnostics: vec![
 718                // no newlines
 719                lsp::Diagnostic {
 720                    message: "use of moved value `a`".to_string(),
 721                    ..Default::default()
 722                },
 723                // newline at the end of a code span
 724                lsp::Diagnostic {
 725                    message: "consider importing this struct: `use b::c;\n`".to_string(),
 726                    ..Default::default()
 727                },
 728                // code span starting right after a newline
 729                lsp::Diagnostic {
 730                    message: "cannot borrow `self.d` as mutable\n`self` is a `&` reference"
 731                        .to_string(),
 732                    ..Default::default()
 733                },
 734            ],
 735        };
 736        RustLspAdapter.process_diagnostics(&mut params);
 737
 738        assert_eq!(params.diagnostics[0].message, "use of moved value `a`");
 739
 740        // remove trailing newline from code span
 741        assert_eq!(
 742            params.diagnostics[1].message,
 743            "consider importing this struct: `use b::c;`"
 744        );
 745
 746        // do not remove newline before the start of code span
 747        assert_eq!(
 748            params.diagnostics[2].message,
 749            "cannot borrow `self.d` as mutable\n`self` is a `&` reference"
 750        );
 751    }
 752
 753    #[gpui::test]
 754    async fn test_rust_label_for_completion() {
 755        let adapter = Arc::new(RustLspAdapter);
 756        let language = language("rust", tree_sitter_rust::LANGUAGE.into());
 757        let grammar = language.grammar().unwrap();
 758        let theme = SyntaxTheme::new_test([
 759            ("type", Hsla::default()),
 760            ("keyword", Hsla::default()),
 761            ("function", Hsla::default()),
 762            ("property", Hsla::default()),
 763        ]);
 764
 765        language.set_theme(&theme);
 766
 767        let highlight_function = grammar.highlight_id_for_name("function").unwrap();
 768        let highlight_type = grammar.highlight_id_for_name("type").unwrap();
 769        let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
 770        let highlight_field = grammar.highlight_id_for_name("property").unwrap();
 771
 772        assert_eq!(
 773            adapter
 774                .label_for_completion(
 775                    &lsp::CompletionItem {
 776                        kind: Some(lsp::CompletionItemKind::FUNCTION),
 777                        label: "hello(…)".to_string(),
 778                        label_details: Some(CompletionItemLabelDetails {
 779                            detail: Some(" (use crate::foo)".into()),
 780                            description: Some("fn(&mut Option<T>) -> Vec<T>".to_string())
 781                        }),
 782                        ..Default::default()
 783                    },
 784                    &language
 785                )
 786                .await,
 787            Some(CodeLabel {
 788                text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
 789                filter_range: 0..5,
 790                runs: vec![
 791                    (0..5, highlight_function),
 792                    (7..10, highlight_keyword),
 793                    (11..17, highlight_type),
 794                    (18..19, highlight_type),
 795                    (25..28, highlight_type),
 796                    (29..30, highlight_type),
 797                ],
 798            })
 799        );
 800        assert_eq!(
 801            adapter
 802                .label_for_completion(
 803                    &lsp::CompletionItem {
 804                        kind: Some(lsp::CompletionItemKind::FUNCTION),
 805                        label: "hello(…)".to_string(),
 806                        label_details: Some(CompletionItemLabelDetails {
 807                            detail: Some(" (use crate::foo)".into()),
 808                            description: Some("async fn(&mut Option<T>) -> Vec<T>".to_string()),
 809                        }),
 810                        ..Default::default()
 811                    },
 812                    &language
 813                )
 814                .await,
 815            Some(CodeLabel {
 816                text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
 817                filter_range: 0..5,
 818                runs: vec![
 819                    (0..5, highlight_function),
 820                    (7..10, highlight_keyword),
 821                    (11..17, highlight_type),
 822                    (18..19, highlight_type),
 823                    (25..28, highlight_type),
 824                    (29..30, highlight_type),
 825                ],
 826            })
 827        );
 828        assert_eq!(
 829            adapter
 830                .label_for_completion(
 831                    &lsp::CompletionItem {
 832                        kind: Some(lsp::CompletionItemKind::FIELD),
 833                        label: "len".to_string(),
 834                        detail: Some("usize".to_string()),
 835                        ..Default::default()
 836                    },
 837                    &language
 838                )
 839                .await,
 840            Some(CodeLabel {
 841                text: "len: usize".to_string(),
 842                filter_range: 0..3,
 843                runs: vec![(0..3, highlight_field), (5..10, highlight_type),],
 844            })
 845        );
 846
 847        assert_eq!(
 848            adapter
 849                .label_for_completion(
 850                    &lsp::CompletionItem {
 851                        kind: Some(lsp::CompletionItemKind::FUNCTION),
 852                        label: "hello(…)".to_string(),
 853                        label_details: Some(CompletionItemLabelDetails {
 854                            detail: Some(" (use crate::foo)".to_string()),
 855                            description: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
 856                        }),
 857
 858                        ..Default::default()
 859                    },
 860                    &language
 861                )
 862                .await,
 863            Some(CodeLabel {
 864                text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
 865                filter_range: 0..5,
 866                runs: vec![
 867                    (0..5, highlight_function),
 868                    (7..10, highlight_keyword),
 869                    (11..17, highlight_type),
 870                    (18..19, highlight_type),
 871                    (25..28, highlight_type),
 872                    (29..30, highlight_type),
 873                ],
 874            })
 875        );
 876    }
 877
 878    #[gpui::test]
 879    async fn test_rust_label_for_symbol() {
 880        let adapter = Arc::new(RustLspAdapter);
 881        let language = language("rust", tree_sitter_rust::LANGUAGE.into());
 882        let grammar = language.grammar().unwrap();
 883        let theme = SyntaxTheme::new_test([
 884            ("type", Hsla::default()),
 885            ("keyword", Hsla::default()),
 886            ("function", Hsla::default()),
 887            ("property", Hsla::default()),
 888        ]);
 889
 890        language.set_theme(&theme);
 891
 892        let highlight_function = grammar.highlight_id_for_name("function").unwrap();
 893        let highlight_type = grammar.highlight_id_for_name("type").unwrap();
 894        let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
 895
 896        assert_eq!(
 897            adapter
 898                .label_for_symbol("hello", lsp::SymbolKind::FUNCTION, &language)
 899                .await,
 900            Some(CodeLabel {
 901                text: "fn hello".to_string(),
 902                filter_range: 3..8,
 903                runs: vec![(0..2, highlight_keyword), (3..8, highlight_function)],
 904            })
 905        );
 906
 907        assert_eq!(
 908            adapter
 909                .label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER, &language)
 910                .await,
 911            Some(CodeLabel {
 912                text: "type World".to_string(),
 913                filter_range: 5..10,
 914                runs: vec![(0..4, highlight_keyword), (5..10, highlight_type)],
 915            })
 916        );
 917    }
 918
 919    #[gpui::test]
 920    async fn test_rust_autoindent(cx: &mut TestAppContext) {
 921        // cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
 922        cx.update(|cx| {
 923            let test_settings = SettingsStore::test(cx);
 924            cx.set_global(test_settings);
 925            language::init(cx);
 926            cx.update_global::<SettingsStore, _>(|store, cx| {
 927                store.update_user_settings::<AllLanguageSettings>(cx, |s| {
 928                    s.defaults.tab_size = NonZeroU32::new(2);
 929                });
 930            });
 931        });
 932
 933        let language = crate::language("rust", tree_sitter_rust::LANGUAGE.into());
 934
 935        cx.new_model(|cx| {
 936            let mut buffer = Buffer::local("", cx).with_language(language, cx);
 937
 938            // indent between braces
 939            buffer.set_text("fn a() {}", cx);
 940            let ix = buffer.len() - 1;
 941            buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
 942            assert_eq!(buffer.text(), "fn a() {\n  \n}");
 943
 944            // indent between braces, even after empty lines
 945            buffer.set_text("fn a() {\n\n\n}", cx);
 946            let ix = buffer.len() - 2;
 947            buffer.edit([(ix..ix, "\n")], Some(AutoindentMode::EachLine), cx);
 948            assert_eq!(buffer.text(), "fn a() {\n\n\n  \n}");
 949
 950            // indent a line that continues a field expression
 951            buffer.set_text("fn a() {\n  \n}", cx);
 952            let ix = buffer.len() - 2;
 953            buffer.edit([(ix..ix, "b\n.c")], Some(AutoindentMode::EachLine), cx);
 954            assert_eq!(buffer.text(), "fn a() {\n  b\n    .c\n}");
 955
 956            // indent further lines that continue the field expression, even after empty lines
 957            let ix = buffer.len() - 2;
 958            buffer.edit([(ix..ix, "\n\n.d")], Some(AutoindentMode::EachLine), cx);
 959            assert_eq!(buffer.text(), "fn a() {\n  b\n    .c\n    \n    .d\n}");
 960
 961            // dedent the line after the field expression
 962            let ix = buffer.len() - 2;
 963            buffer.edit([(ix..ix, ";\ne")], Some(AutoindentMode::EachLine), cx);
 964            assert_eq!(
 965                buffer.text(),
 966                "fn a() {\n  b\n    .c\n    \n    .d;\n  e\n}"
 967            );
 968
 969            // indent inside a struct within a call
 970            buffer.set_text("const a: B = c(D {});", cx);
 971            let ix = buffer.len() - 3;
 972            buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
 973            assert_eq!(buffer.text(), "const a: B = c(D {\n  \n});");
 974
 975            // indent further inside a nested call
 976            let ix = buffer.len() - 4;
 977            buffer.edit([(ix..ix, "e: f(\n\n)")], Some(AutoindentMode::EachLine), cx);
 978            assert_eq!(buffer.text(), "const a: B = c(D {\n  e: f(\n    \n  )\n});");
 979
 980            // keep that indent after an empty line
 981            let ix = buffer.len() - 8;
 982            buffer.edit([(ix..ix, "\n")], Some(AutoindentMode::EachLine), cx);
 983            assert_eq!(
 984                buffer.text(),
 985                "const a: B = c(D {\n  e: f(\n    \n    \n  )\n});"
 986            );
 987
 988            buffer
 989        });
 990    }
 991
 992    #[test]
 993    fn test_package_name_from_pkgid() {
 994        for (input, expected) in [
 995            (
 996                "path+file:///absolute/path/to/project/zed/crates/zed#0.131.0",
 997                "zed",
 998            ),
 999            (
1000                "path+file:///absolute/path/to/project/custom-package#my-custom-package@0.1.0",
1001                "my-custom-package",
1002            ),
1003        ] {
1004            assert_eq!(package_name_from_pkgid(input), Some(expected));
1005        }
1006    }
1007
1008    #[test]
1009    fn test_retrieve_package_id_and_bin_name_from_metadata() {
1010        for (input, absolute_path, expected) in [
1011            (
1012                r#"{"packages":[{"id":"path+file:///path/to/zed/crates/zed#0.131.0","targets":[{"name":"zed","kind":["bin"],"src_path":"/path/to/zed/src/main.rs"}]}]}"#,
1013                "/path/to/zed/src/main.rs",
1014                Some(("path+file:///path/to/zed/crates/zed#0.131.0", "zed")),
1015            ),
1016            (
1017                r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","targets":[{"name":"my-custom-bin","kind":["bin"],"src_path":"/path/to/custom-package/src/main.rs"}]}]}"#,
1018                "/path/to/custom-package/src/main.rs",
1019                Some((
1020                    "path+file:///path/to/custom-package#my-custom-package@0.1.0",
1021                    "my-custom-bin",
1022                )),
1023            ),
1024            (
1025                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"}]}]}"#,
1026                "/path/to/custom-package/src/main.rs",
1027                None,
1028            ),
1029        ] {
1030            let metadata: CargoMetadata = serde_json::from_str(input).unwrap();
1031
1032            let absolute_path = Path::new(absolute_path);
1033
1034            assert_eq!(
1035                retrieve_package_id_and_bin_name_from_metadata(metadata, absolute_path),
1036                expected.map(|(pkgid, bin)| (pkgid.to_owned(), bin.to_owned()))
1037            );
1038        }
1039    }
1040}