use anyhow::{Context as _, Result};use async_trait::async_trait;use collections::HashMap;use futures::StreamExt;use gpui::{App, AppContext, AsyncApp, SharedString, Task};use http_client::github::AssetKind;use http_client::github::{GitHubLspBinaryVersion, latest_github_release};use http_client::github_download::{GithubBinaryMetadata, download_server_binary};pub use language::*;use lsp::{InitializeParams, LanguageServerBinary};use project::lsp_store::rust_analyzer_ext::CARGO_DIAGNOSTICS_SOURCE_NAME;use project::project_settings::ProjectSettings;use regex::Regex;use serde_json::json;use settings::Settings as _;use smol::fs::{self};use std::fmt::Display;use std::ops::Range;use std::{ borrow::Cow, path::{Path, PathBuf}, sync::{Arc, LazyLock},};use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};use util::fs::{make_file_executable, remove_matching};use util::merge_json_value_into;use util::rel_path::RelPath;use util::{ResultExt, maybe}; use crate::language_settings::language_settings; pub struct RustLspAdapter; #[cfg(target_os = "macos")]impl RustLspAdapter { const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz; const ARCH_SERVER_NAME: &str = "apple-darwin";} #[cfg(target_os = "linux")]impl RustLspAdapter { const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz; const ARCH_SERVER_NAME: &str = "unknown-linux";} #[cfg(target_os = "freebsd")]impl RustLspAdapter { const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz; const ARCH_SERVER_NAME: &str = "unknown-freebsd";} #[cfg(target_os = "windows")]impl RustLspAdapter { const GITHUB_ASSET_KIND: AssetKind = AssetKind::Zip; const ARCH_SERVER_NAME: &str = "pc-windows-msvc";} const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("rust-analyzer"); #[cfg(target_os = "linux")]enum LibcType { Gnu, Musl,} impl RustLspAdapter { #[cfg(target_os = "linux")] async fn determine_libc_type() -> LibcType { use futures::pin_mut; async fn from_ldd_version() -> Option<LibcType> { use util::command::new_smol_command; let ldd_output = new_smol_command("ldd") .arg("--version") .output() .await .ok()?; let ldd_version = String::from_utf8_lossy(&ldd_output.stdout); if ldd_version.contains("GNU libc") || ldd_version.contains("GLIBC") { Some(LibcType::Gnu) } else if ldd_version.contains("musl") { Some(LibcType::Musl) } else { None } } if let Some(libc_type) = from_ldd_version().await { return libc_type; } let Ok(dir_entries) = smol::fs::read_dir("/lib").await else { // defaulting to gnu because nix doesn't have /lib files due to not following FHS return LibcType::Gnu; }; let dir_entries = dir_entries.filter_map(async move |e| e.ok()); pin_mut!(dir_entries); let mut has_musl = false; let mut has_gnu = false; while let Some(entry) = dir_entries.next().await { let file_name = entry.file_name(); let file_name = file_name.to_string_lossy(); if file_name.starts_with("ld-musl-") { has_musl = true; } else if file_name.starts_with("ld-linux-") { has_gnu = true; } } match (has_musl, has_gnu) { (true, _) => LibcType::Musl, (_, true) => LibcType::Gnu, _ => LibcType::Gnu, } } #[cfg(target_os = "linux")] async fn build_arch_server_name_linux() -> String { let libc = match Self::determine_libc_type().await { LibcType::Musl => "musl", LibcType::Gnu => "gnu", }; format!("{}-{}", Self::ARCH_SERVER_NAME, libc) } async fn build_asset_name() -> String { let extension = match Self::GITHUB_ASSET_KIND { AssetKind::TarGz => "tar.gz", AssetKind::Gz => "gz", AssetKind::Zip => "zip", }; #[cfg(target_os = "linux")] let arch_server_name = Self::build_arch_server_name_linux().await; #[cfg(not(target_os = "linux"))] let arch_server_name = Self::ARCH_SERVER_NAME.to_string(); format!( "{}-{}-{}.{}", SERVER_NAME, std::env::consts::ARCH, &arch_server_name, extension ) }} pub(crate) struct CargoManifestProvider; impl ManifestProvider for CargoManifestProvider { fn name(&self) -> ManifestName { SharedString::new_static("Cargo.toml").into() } fn search( &self, ManifestQuery { path, depth, delegate, }: ManifestQuery, ) -> Option<Arc<RelPath>> { let mut outermost_cargo_toml = None; for path in path.ancestors().take(depth) { let p = path.join(RelPath::unix("Cargo.toml").unwrap()); if delegate.exists(&p, Some(false)) { outermost_cargo_toml = Some(Arc::from(path)); } } outermost_cargo_toml }} #[async_trait(?Send)]impl LspAdapter for RustLspAdapter { fn name(&self) -> LanguageServerName { SERVER_NAME } fn disk_based_diagnostic_sources(&self) -> Vec<String> { vec![CARGO_DIAGNOSTICS_SOURCE_NAME.to_owned()] } fn disk_based_diagnostics_progress_token(&self) -> Option<String> { Some("rust-analyzer/flycheck".into()) } fn process_diagnostics( &self, params: &mut lsp::PublishDiagnosticsParams, _: LanguageServerId, _: Option<&'_ Buffer>, ) { static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?m)`([^`]+)\n`$").expect("Failed to create REGEX")); for diagnostic in &mut params.diagnostics { for message in diagnostic .related_information .iter_mut() .flatten() .map(|info| &mut info.message) .chain([&mut diagnostic.message]) { if let Cow::Owned(sanitized) = REGEX.replace_all(message, "`$1`") { *message = sanitized; } } } } fn diagnostic_message_to_markdown(&self, message: &str) -> Option<String> { static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?m)\n *").expect("Failed to create REGEX")); Some(REGEX.replace_all(message, "\n\n").to_string()) } async fn label_for_completion( &self, completion: &lsp::CompletionItem, language: &Arc<Language>, ) -> Option<CodeLabel> { // rust-analyzer calls these detail left and detail right in terms of where it expects things to be rendered // this usually contains signatures of the thing to be completed let detail_right = completion .label_details .as_ref() .and_then(|detail| detail.description.as_ref()) .or(completion.detail.as_ref()) .map(|detail| detail.trim()); // this tends to contain alias and import information let detail_left = completion .label_details .as_ref() .and_then(|detail| detail.detail.as_deref()); let mk_label = |text: String, filter_range: &dyn Fn() -> Range<usize>, runs| { let filter_range = completion .filter_text .as_deref() .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len())) .or_else(|| { text.find(&completion.label) .map(|ix| ix..ix + completion.label.len()) }) .unwrap_or_else(filter_range); CodeLabel::new(text, filter_range, runs) }; let mut label = match (detail_right, completion.kind) { (Some(signature), Some(lsp::CompletionItemKind::FIELD)) => { let name = &completion.label; let text = format!("{name}: {signature}"); let prefix = "struct S { "; let source = Rope::from_iter([prefix, &text, " }"]); let runs = language.highlight_text(&source, prefix.len()..prefix.len() + text.len()); mk_label(text, &|| 0..completion.label.len(), runs) } ( Some(signature), Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE), ) if completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => { let name = &completion.label; let text = format!("{name}: {signature}",); let prefix = "let "; let source = Rope::from_iter([prefix, &text, " = ();"]); let runs = language.highlight_text(&source, prefix.len()..prefix.len() + text.len()); mk_label(text, &|| 0..completion.label.len(), runs) } ( function_signature, Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD), ) => { const FUNCTION_PREFIXES: [&str; 6] = [ "async fn", "async unsafe fn", "const fn", "const unsafe fn", "unsafe fn", "fn", ]; let fn_prefixed = FUNCTION_PREFIXES.iter().find_map(|&prefix| { function_signature? .strip_prefix(prefix) .map(|suffix| (prefix, suffix)) }); let label = if let Some(label) = completion .label .strip_suffix("(…)") .or_else(|| completion.label.strip_suffix("()")) { label } else { &completion.label }; static FULL_SIGNATURE_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"fn (.?+)\(").expect("Failed to create REGEX")); if let Some((function_signature, match_)) = function_signature .filter(|it| it.contains(&label)) .and_then(|it| Some((it, FULL_SIGNATURE_REGEX.find(it)?))) { let source = Rope::from(function_signature); let runs = language.highlight_text(&source, 0..function_signature.len()); mk_label( function_signature.to_owned(), &|| match_.range().start - 3..match_.range().end - 1, runs, ) } else if let Some((prefix, suffix)) = fn_prefixed { let text = format!("{label}{suffix}"); let source = Rope::from_iter([prefix, " ", &text, " {}"]); let run_start = prefix.len() + 1; let runs = language.highlight_text(&source, run_start..run_start + text.len()); mk_label(text, &|| 0..label.len(), runs) } else if completion .detail .as_ref() .is_some_and(|detail| detail.starts_with("macro_rules! ")) { let text = completion.label.clone(); let len = text.len(); let source = Rope::from(text.as_str()); let runs = language.highlight_text(&source, 0..len); mk_label(text, &|| 0..completion.label.len(), runs) } else if detail_left.is_none() { return None; } else { mk_label( completion.label.clone(), &|| 0..completion.label.len(), vec![], ) } } (_, kind) => { let highlight_name = kind.and_then(|kind| match kind { lsp::CompletionItemKind::STRUCT | lsp::CompletionItemKind::INTERFACE | lsp::CompletionItemKind::ENUM => Some("type"), lsp::CompletionItemKind::ENUM_MEMBER => Some("variant"), lsp::CompletionItemKind::KEYWORD => Some("keyword"), lsp::CompletionItemKind::VALUE | lsp::CompletionItemKind::CONSTANT => { Some("constant") } _ => None, }); let label = completion.label.clone(); let mut runs = vec![]; if let Some(highlight_name) = highlight_name { let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name)?; runs.push(( 0..label.rfind('(').unwrap_or(completion.label.len()), highlight_id, )); } else if detail_left.is_none() { return None; } mk_label(label, &|| 0..completion.label.len(), runs) } }; if let Some(detail_left) = detail_left { label.text.push(' '); if !detail_left.starts_with('(') { label.text.push('('); } label.text.push_str(detail_left); if !detail_left.ends_with(')') { label.text.push(')'); } } Some(label) } async fn label_for_symbol( &self, name: &str, kind: lsp::SymbolKind, language: &Arc<Language>, ) -> Option<CodeLabel> { let (prefix, suffix) = match kind { lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => ("fn ", " () {}"), lsp::SymbolKind::STRUCT => ("struct ", " {}"), lsp::SymbolKind::ENUM => ("enum ", " {}"), lsp::SymbolKind::INTERFACE => ("trait ", " {}"), lsp::SymbolKind::CONSTANT => ("const ", ": () = ();"), lsp::SymbolKind::MODULE => ("mod ", " {}"), lsp::SymbolKind::TYPE_PARAMETER => ("type ", " {}"), _ => return None, }; let filter_range = prefix.len()..prefix.len() + name.len(); let display_range = 0..filter_range.end; Some(CodeLabel::new( format!("{prefix}{name}"), filter_range, language.highlight_text(&Rope::from_iter([prefix, name, suffix]), display_range), )) } fn prepare_initialize_params( &self, mut original: InitializeParams, cx: &App, ) -> Result<InitializeParams> { let enable_lsp_tasks = ProjectSettings::get_global(cx) .lsp .get(&SERVER_NAME) .is_some_and(|s| s.enable_lsp_tasks); if enable_lsp_tasks { let experimental = json!({ "runnables": { "kinds": [ "cargo", "shell" ], }, }); if let Some(original_experimental) = &mut original.capabilities.experimental { merge_json_value_into(experimental, original_experimental); } else { original.capabilities.experimental = Some(experimental); } } Ok(original) }} impl LspInstaller for RustLspAdapter { type BinaryVersion = GitHubLspBinaryVersion; async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, _: Option<Toolchain>, _: &AsyncApp, ) -> Option<LanguageServerBinary> { let path = delegate.which("rust-analyzer".as_ref()).await?; let env = delegate.shell_env().await; // It is surprisingly common for ~/.cargo/bin/rust-analyzer to be a symlink to // /usr/bin/rust-analyzer that fails when you run it; so we need to test it. log::debug!("found rust-analyzer in PATH. trying to run `rust-analyzer --help`"); let result = delegate .try_exec(LanguageServerBinary { path: path.clone(), arguments: vec!["--help".into()], env: Some(env.clone()), }) .await; if let Err(err) = result { log::debug!( "failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}", path, err ); return None; } Some(LanguageServerBinary { path, env: Some(env), arguments: vec![], }) } async fn fetch_latest_server_version( &self, delegate: &dyn LspAdapterDelegate, pre_release: bool, _: &mut AsyncApp, ) -> Result<GitHubLspBinaryVersion> { let release = latest_github_release( "rust-lang/rust-analyzer", true, pre_release, delegate.http_client(), ) .await?; let asset_name = Self::build_asset_name().await; let asset = release .assets .into_iter() .find(|asset| asset.name == asset_name) .with_context(|| format!("no asset found matching `{asset_name:?}`"))?; Ok(GitHubLspBinaryVersion { name: release.tag_name, url: asset.browser_download_url, digest: asset.digest, }) } async fn fetch_server_binary(Lines 1–500 of 1646. Load more
use anyhow::{Context as _, Result}; use async_trait::async_trait; use collections::HashMap; use futures::StreamExt; use gpui::{App, AppContext, AsyncApp, SharedString, Task}; use http_client::github::AssetKind; use http_client::github::{GitHubLspBinaryVersion, latest_github_release}; use http_client::github_download::{GithubBinaryMetadata, download_server_binary}; pub use language::*; use lsp::{InitializeParams, LanguageServerBinary}; use project::lsp_store::rust_analyzer_ext::CARGO_DIAGNOSTICS_SOURCE_NAME; use project::project_settings::ProjectSettings; use regex::Regex; use serde_json::json; use settings::Settings as _; use smol::fs::{self}; use std::fmt::Display; use std::ops::Range; use std::{ borrow::Cow, path::{Path, PathBuf}, sync::{Arc, LazyLock}, }; use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName}; use util::fs::{make_file_executable, remove_matching}; use util::merge_json_value_into; use util::rel_path::RelPath; use util::{ResultExt, maybe}; use crate::language_settings::language_settings; pub struct RustLspAdapter; #[cfg(target_os = "macos")] impl RustLspAdapter { const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz; const ARCH_SERVER_NAME: &str = "apple-darwin"; } #[cfg(target_os = "linux")] impl RustLspAdapter { const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz; const ARCH_SERVER_NAME: &str = "unknown-linux"; } #[cfg(target_os = "freebsd")] impl RustLspAdapter { const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz; const ARCH_SERVER_NAME: &str = "unknown-freebsd"; } #[cfg(target_os = "windows")] impl RustLspAdapter { const GITHUB_ASSET_KIND: AssetKind = AssetKind::Zip; const ARCH_SERVER_NAME: &str = "pc-windows-msvc"; } const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("rust-analyzer"); #[cfg(target_os = "linux")] enum LibcType { Gnu, Musl, } impl RustLspAdapter { #[cfg(target_os = "linux")] async fn determine_libc_type() -> LibcType { use futures::pin_mut; async fn from_ldd_version() -> Option<LibcType> { use util::command::new_smol_command; let ldd_output = new_smol_command("ldd") .arg("--version") .output() .await .ok()?; let ldd_version = String::from_utf8_lossy(&ldd_output.stdout); if ldd_version.contains("GNU libc") || ldd_version.contains("GLIBC") { Some(LibcType::Gnu) } else if ldd_version.contains("musl") { Some(LibcType::Musl) } else { None } } if let Some(libc_type) = from_ldd_version().await { return libc_type; } let Ok(dir_entries) = smol::fs::read_dir("/lib").await else { // defaulting to gnu because nix doesn't have /lib files due to not following FHS return LibcType::Gnu; }; let dir_entries = dir_entries.filter_map(async move |e| e.ok()); pin_mut!(dir_entries); let mut has_musl = false; let mut has_gnu = false; while let Some(entry) = dir_entries.next().await { let file_name = entry.file_name(); let file_name = file_name.to_string_lossy(); if file_name.starts_with("ld-musl-") { has_musl = true; } else if file_name.starts_with("ld-linux-") { has_gnu = true; } } match (has_musl, has_gnu) { (true, _) => LibcType::Musl, (_, true) => LibcType::Gnu, _ => LibcType::Gnu, } } #[cfg(target_os = "linux")] async fn build_arch_server_name_linux() -> String { let libc = match Self::determine_libc_type().await { LibcType::Musl => "musl", LibcType::Gnu => "gnu", }; format!("{}-{}", Self::ARCH_SERVER_NAME, libc) } async fn build_asset_name() -> String { let extension = match Self::GITHUB_ASSET_KIND { AssetKind::TarGz => "tar.gz", AssetKind::Gz => "gz", AssetKind::Zip => "zip", }; #[cfg(target_os = "linux")] let arch_server_name = Self::build_arch_server_name_linux().await; #[cfg(not(target_os = "linux"))] let arch_server_name = Self::ARCH_SERVER_NAME.to_string(); format!( "{}-{}-{}.{}", SERVER_NAME, std::env::consts::ARCH, &arch_server_name, extension ) } } pub(crate) struct CargoManifestProvider; impl ManifestProvider for CargoManifestProvider { fn name(&self) -> ManifestName { SharedString::new_static("Cargo.toml").into() } fn search( &self, ManifestQuery { path, depth, delegate, }: ManifestQuery, ) -> Option<Arc<RelPath>> { let mut outermost_cargo_toml = None; for path in path.ancestors().take(depth) { let p = path.join(RelPath::unix("Cargo.toml").unwrap()); if delegate.exists(&p, Some(false)) { outermost_cargo_toml = Some(Arc::from(path)); } } outermost_cargo_toml } } #[async_trait(?Send)] impl LspAdapter for RustLspAdapter { fn name(&self) -> LanguageServerName { SERVER_NAME } fn disk_based_diagnostic_sources(&self) -> Vec<String> { vec![CARGO_DIAGNOSTICS_SOURCE_NAME.to_owned()] } fn disk_based_diagnostics_progress_token(&self) -> Option<String> { Some("rust-analyzer/flycheck".into()) } fn process_diagnostics( &self, params: &mut lsp::PublishDiagnosticsParams, _: LanguageServerId, _: Option<&'_ Buffer>, ) { static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?m)`([^`]+)\n`$").expect("Failed to create REGEX")); for diagnostic in &mut params.diagnostics { for message in diagnostic .related_information .iter_mut() .flatten() .map(|info| &mut info.message) .chain([&mut diagnostic.message]) { if let Cow::Owned(sanitized) = REGEX.replace_all(message, "`$1`") { *message = sanitized; } } } } fn diagnostic_message_to_markdown(&self, message: &str) -> Option<String> { static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?m)\n *").expect("Failed to create REGEX")); Some(REGEX.replace_all(message, "\n\n").to_string()) } async fn label_for_completion( &self, completion: &lsp::CompletionItem, language: &Arc<Language>, ) -> Option<CodeLabel> { // rust-analyzer calls these detail left and detail right in terms of where it expects things to be rendered // this usually contains signatures of the thing to be completed let detail_right = completion .label_details .as_ref() .and_then(|detail| detail.description.as_ref()) .or(completion.detail.as_ref()) .map(|detail| detail.trim()); // this tends to contain alias and import information let detail_left = completion .label_details .as_ref() .and_then(|detail| detail.detail.as_deref()); let mk_label = |text: String, filter_range: &dyn Fn() -> Range<usize>, runs| { let filter_range = completion .filter_text .as_deref() .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len())) .or_else(|| { text.find(&completion.label) .map(|ix| ix..ix + completion.label.len()) }) .unwrap_or_else(filter_range); CodeLabel::new(text, filter_range, runs) }; let mut label = match (detail_right, completion.kind) { (Some(signature), Some(lsp::CompletionItemKind::FIELD)) => { let name = &completion.label; let text = format!("{name}: {signature}"); let prefix = "struct S { "; let source = Rope::from_iter([prefix, &text, " }"]); let runs = language.highlight_text(&source, prefix.len()..prefix.len() + text.len()); mk_label(text, &|| 0..completion.label.len(), runs) } ( Some(signature), Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE), ) if completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => { let name = &completion.label; let text = format!("{name}: {signature}",); let prefix = "let "; let source = Rope::from_iter([prefix, &text, " = ();"]); let runs = language.highlight_text(&source, prefix.len()..prefix.len() + text.len()); mk_label(text, &|| 0..completion.label.len(), runs) } ( function_signature, Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD), ) => { const FUNCTION_PREFIXES: [&str; 6] = [ "async fn", "async unsafe fn", "const fn", "const unsafe fn", "unsafe fn", "fn", ]; let fn_prefixed = FUNCTION_PREFIXES.iter().find_map(|&prefix| { function_signature? .strip_prefix(prefix) .map(|suffix| (prefix, suffix)) }); let label = if let Some(label) = completion .label .strip_suffix("(…)") .or_else(|| completion.label.strip_suffix("()")) { label } else { &completion.label }; static FULL_SIGNATURE_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"fn (.?+)\(").expect("Failed to create REGEX")); if let Some((function_signature, match_)) = function_signature .filter(|it| it.contains(&label)) .and_then(|it| Some((it, FULL_SIGNATURE_REGEX.find(it)?))) { let source = Rope::from(function_signature); let runs = language.highlight_text(&source, 0..function_signature.len()); mk_label( function_signature.to_owned(), &|| match_.range().start - 3..match_.range().end - 1, runs, ) } else if let Some((prefix, suffix)) = fn_prefixed { let text = format!("{label}{suffix}"); let source = Rope::from_iter([prefix, " ", &text, " {}"]); let run_start = prefix.len() + 1; let runs = language.highlight_text(&source, run_start..run_start + text.len()); mk_label(text, &|| 0..label.len(), runs) } else if completion .detail .as_ref() .is_some_and(|detail| detail.starts_with("macro_rules! ")) { let text = completion.label.clone(); let len = text.len(); let source = Rope::from(text.as_str()); let runs = language.highlight_text(&source, 0..len); mk_label(text, &|| 0..completion.label.len(), runs) } else if detail_left.is_none() { return None; } else { mk_label( completion.label.clone(), &|| 0..completion.label.len(), vec![], ) } } (_, kind) => { let highlight_name = kind.and_then(|kind| match kind { lsp::CompletionItemKind::STRUCT | lsp::CompletionItemKind::INTERFACE | lsp::CompletionItemKind::ENUM => Some("type"), lsp::CompletionItemKind::ENUM_MEMBER => Some("variant"), lsp::CompletionItemKind::KEYWORD => Some("keyword"), lsp::CompletionItemKind::VALUE | lsp::CompletionItemKind::CONSTANT => { Some("constant") } _ => None, }); let label = completion.label.clone(); let mut runs = vec![]; if let Some(highlight_name) = highlight_name { let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name)?; runs.push(( 0..label.rfind('(').unwrap_or(completion.label.len()), highlight_id, )); } else if detail_left.is_none() { return None; } mk_label(label, &|| 0..completion.label.len(), runs) } }; if let Some(detail_left) = detail_left { label.text.push(' '); if !detail_left.starts_with('(') { label.text.push('('); } label.text.push_str(detail_left); if !detail_left.ends_with(')') { label.text.push(')'); } } Some(label) } async fn label_for_symbol( &self, name: &str, kind: lsp::SymbolKind, language: &Arc<Language>, ) -> Option<CodeLabel> { let (prefix, suffix) = match kind { lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => ("fn ", " () {}"), lsp::SymbolKind::STRUCT => ("struct ", " {}"), lsp::SymbolKind::ENUM => ("enum ", " {}"), lsp::SymbolKind::INTERFACE => ("trait ", " {}"), lsp::SymbolKind::CONSTANT => ("const ", ": () = ();"), lsp::SymbolKind::MODULE => ("mod ", " {}"), lsp::SymbolKind::TYPE_PARAMETER => ("type ", " {}"), _ => return None, }; let filter_range = prefix.len()..prefix.len() + name.len(); let display_range = 0..filter_range.end; Some(CodeLabel::new( format!("{prefix}{name}"), filter_range, language.highlight_text(&Rope::from_iter([prefix, name, suffix]), display_range), )) } fn prepare_initialize_params( &self, mut original: InitializeParams, cx: &App, ) -> Result<InitializeParams> { let enable_lsp_tasks = ProjectSettings::get_global(cx) .lsp .get(&SERVER_NAME) .is_some_and(|s| s.enable_lsp_tasks); if enable_lsp_tasks { let experimental = json!({ "runnables": { "kinds": [ "cargo", "shell" ], }, }); if let Some(original_experimental) = &mut original.capabilities.experimental { merge_json_value_into(experimental, original_experimental); } else { original.capabilities.experimental = Some(experimental); } } Ok(original) } } impl LspInstaller for RustLspAdapter { type BinaryVersion = GitHubLspBinaryVersion; async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, _: Option<Toolchain>, _: &AsyncApp, ) -> Option<LanguageServerBinary> { let path = delegate.which("rust-analyzer".as_ref()).await?; let env = delegate.shell_env().await; // It is surprisingly common for ~/.cargo/bin/rust-analyzer to be a symlink to // /usr/bin/rust-analyzer that fails when you run it; so we need to test it. log::debug!("found rust-analyzer in PATH. trying to run `rust-analyzer --help`"); let result = delegate .try_exec(LanguageServerBinary { path: path.clone(), arguments: vec!["--help".into()], env: Some(env.clone()), }) .await; if let Err(err) = result { log::debug!( "failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}", path, err ); return None; } Some(LanguageServerBinary { path, env: Some(env), arguments: vec![], }) } async fn fetch_latest_server_version( &self, delegate: &dyn LspAdapterDelegate, pre_release: bool, _: &mut AsyncApp, ) -> Result<GitHubLspBinaryVersion> { let release = latest_github_release( "rust-lang/rust-analyzer", true, pre_release, delegate.http_client(), ) .await?; let asset_name = Self::build_asset_name().await; let asset = release .assets .into_iter() .find(|asset| asset.name == asset_name) .with_context(|| format!("no asset found matching `{asset_name:?}`"))?; Ok(GitHubLspBinaryVersion { name: release.tag_name, url: asset.browser_download_url, digest: asset.digest, }) } async fn fetch_server_binary(