use anyhow::{anyhow, Context as _, Result};use async_compression::futures::bufread::GzipDecoder;use async_trait::async_trait;use collections::HashMap;use futures::{io::BufReader, StreamExt};use gpui::{App, AsyncApp, SharedString, Task};use http_client::github::AssetKind;use http_client::github::{latest_github_release, GitHubLspBinaryVersion};pub use language::*;use lsp::LanguageServerBinary;use regex::Regex;use smol::fs::{self};use std::fmt::Display;use std::{ any::Any, borrow::Cow, path::{Path, PathBuf}, sync::{Arc, LazyLock},};use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};use util::{fs::remove_matching, maybe, ResultExt}; 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-gnu";} #[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";} impl RustLspAdapter { const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("rust-analyzer"); fn build_asset_name() -> String { let extension = match Self::GITHUB_ASSET_KIND { AssetKind::TarGz => "tar.gz", AssetKind::Gz => "gz", AssetKind::Zip => "zip", }; format!( "{}-{}-{}.{}", Self::SERVER_NAME, std::env::consts::ARCH, Self::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<Path>> { let mut outermost_cargo_toml = None; for path in path.ancestors().take(depth) { let p = path.join("Cargo.toml"); 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 { Self::SERVER_NAME.clone() } fn manifest_name(&self) -> Option<ManifestName> { Some(SharedString::new_static("Cargo.toml").into()) } async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, _: Arc<dyn LanguageToolchainStore>, _: &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::info!("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::error!( "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, ) -> Result<Box<dyn 'static + Send + Any>> { let release = latest_github_release( "rust-lang/rust-analyzer", true, false, delegate.http_client(), ) .await?; let asset_name = Self::build_asset_name(); let asset = release .assets .iter() .find(|asset| asset.name == asset_name) .with_context(|| format!("no asset found matching `{asset_name:?}`"))?; Ok(Box::new(GitHubLspBinaryVersion { name: release.tag_name, url: asset.browser_download_url.clone(), })) } async fn fetch_server_binary( &self, version: Box<dyn 'static + Send + Any>, container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Result<LanguageServerBinary> { let version = version.downcast::<GitHubLspBinaryVersion>().unwrap(); let destination_path = container_dir.join(format!("rust-analyzer-{}", version.name)); let server_path = match Self::GITHUB_ASSET_KIND { AssetKind::TarGz | AssetKind::Gz => destination_path.clone(), // Tar and gzip extract in place. AssetKind::Zip => destination_path.clone().join("rust-analyzer.exe"), // zip contains a .exe }; if fs::metadata(&server_path).await.is_err() { remove_matching(&container_dir, |entry| entry != destination_path).await; let mut response = delegate .http_client() .get(&version.url, Default::default(), true) .await .with_context(|| format!("downloading release from {}", version.url))?; match Self::GITHUB_ASSET_KIND { AssetKind::TarGz => { let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); let archive = async_tar::Archive::new(decompressed_bytes); archive.unpack(&destination_path).await.with_context(|| { format!("extracting {} to {:?}", version.url, destination_path) })?; } AssetKind::Gz => { let mut decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); let mut file = fs::File::create(&destination_path).await.with_context(|| { format!( "creating a file {:?} for a download from {}", destination_path, version.url, ) })?; futures::io::copy(&mut decompressed_bytes, &mut file) .await .with_context(|| { format!("extracting {} to {:?}", version.url, destination_path) })?; } AssetKind::Zip => { node_runtime::extract_zip( &destination_path, BufReader::new(response.body_mut()), ) .await .with_context(|| { format!("unzipping {} to {:?}", version.url, destination_path) })?; } }; // todo("windows") #[cfg(not(windows))] { fs::set_permissions( &server_path, <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755), ) .await?; } } Ok(LanguageServerBinary { path: server_path, env: None, arguments: Default::default(), }) } async fn cached_server_binary( &self, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option<LanguageServerBinary> { get_cached_server_binary(container_dir).await } fn disk_based_diagnostic_sources(&self) -> Vec<String> { vec!["rustc".into()] } 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; } } } } async fn label_for_completion( &self, completion: &lsp::CompletionItem, language: &Arc<Language>, ) -> Option<CodeLabel> { let detail = completion .label_details .as_ref() .and_then(|detail| detail.detail.as_ref()) .or(completion.detail.as_ref()) .map(|detail| detail.trim()); let function_signature = completion .label_details .as_ref() .and_then(|detail| detail.description.as_deref()) .or(completion.detail.as_deref()); match (detail, completion.kind) { (Some(detail), Some(lsp::CompletionItemKind::FIELD)) => { let name = &completion.label; let text = format!("{name}: {detail}"); let prefix = "struct S { "; let source = Rope::from(format!("{prefix}{text} }}")); let runs = language.highlight_text(&source, prefix.len()..prefix.len() + text.len()); return Some(CodeLabel { text, runs, filter_range: 0..name.len(), }); } ( Some(detail), Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE), ) if completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => { let name = &completion.label; let text = format!( "{}: {}", name, completion.detail.as_deref().unwrap_or(detail) ); let prefix = "let "; let source = Rope::from(format!("{prefix}{text} = ();")); let runs = language.highlight_text(&source, prefix.len()..prefix.len() + text.len()); return Some(CodeLabel { text, runs, filter_range: 0..name.len(), }); } ( Some(detail), Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD), ) => { static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("\\(…?\\)").unwrap()); const FUNCTION_PREFIXES: [&str; 6] = [ "async fn", "async unsafe fn", "const fn", "const unsafe fn", "unsafe fn", "fn", ]; // Is it function `async`? let fn_keyword = FUNCTION_PREFIXES.iter().find_map(|prefix| { function_signature.as_ref().and_then(|signature| { signature .strip_prefix(*prefix) .map(|suffix| (*prefix, suffix)) }) }); // fn keyword should be followed by opening parenthesis. if let Some((prefix, suffix)) = fn_keyword { let mut text = REGEX.replace(&completion.label, suffix).to_string(); let source = Rope::from(format!("{prefix} {text} {{}}")); let run_start = prefix.len() + 1; let runs = language.highlight_text(&source, run_start..run_start + text.len()); if detail.starts_with("(") { text.push(' '); text.push_str(&detail); } return Some(CodeLabel { filter_range: 0..completion.label.find('(').unwrap_or(text.len()), text, runs, }); } else if completion .detail .as_ref() .map_or(false, |detail| detail.starts_with("macro_rules! ")) { let source = Rope::from(completion.label.as_str()); let runs = language.highlight_text(&source, 0..completion.label.len()); return Some(CodeLabel { filter_range: 0..completion.label.len(), text: completion.label.clone(), runs, }); } } (_, Some(kind)) => { let highlight_name = 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 mut label = completion.label.clone(); if let Some(detail) = detail.filter(|detail| detail.starts_with("(")) { label.push(' '); label.push_str(detail); } let mut label = CodeLabel::plain(label, None); if let Some(highlight_name) = highlight_name { let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name)?; label.runs.push(( 0..label.text.rfind('(').unwrap_or(completion.label.len()), highlight_id, )); } return Some(label); } _ => {} } None } async fn label_for_symbol( &self, name: &str, kind: lsp::SymbolKind, language: &Arc<Language>, ) -> Option<CodeLabel> { let (text, filter_range, display_range) = match kind { lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => { let text = format!("fn {} () {{}}", name); let filter_range = 3..3 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::STRUCT => { let text = format!("struct {} {{}}", name); let filter_range = 7..7 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::ENUM => { let text = format!("enum {} {{}}", name); let filter_range = 5..5 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::INTERFACE => { let text = format!("trait {} {{}}", name); let filter_range = 6..6 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::CONSTANT => { let text = format!("const {}: () = ();", name); let filter_range = 6..6 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::MODULE => { let text = format!("mod {} {{}}", name); let filter_range = 4..4 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::TYPE_PARAMETER => { let text = format!("type {} {{}}", name); let filter_range = 5..5 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } _ => return None, }; Some(CodeLabel { runs: language.highlight_text(&text.as_str().into(), display_range.clone()), text: text[display_range].to_string(), filter_range, }) }} pub(crate) struct RustContextProvider; const RUST_PACKAGE_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("RUST_PACKAGE")); /// The bin name corresponding to the current file in Cargo.tomlconst RUST_BIN_NAME_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("RUST_BIN_NAME")); /// The bin kind (bin/example) corresponding to the current file in Cargo.tomlconst RUST_BIN_KIND_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("RUST_BIN_KIND")); const RUST_TEST_FRAGMENT_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("RUST_TEST_FRAGMENT")); const RUST_DOC_TEST_NAME_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("RUST_DOC_TEST_NAME")); const RUST_TEST_NAME_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("RUST_TEST_NAME")); impl ContextProvider for RustContextProvider {Lines 1–500 of 1315. Load more
use anyhow::{anyhow, Context as _, Result}; use async_compression::futures::bufread::GzipDecoder; use async_trait::async_trait; use collections::HashMap; use futures::{io::BufReader, StreamExt}; use gpui::{App, AsyncApp, SharedString, Task}; use http_client::github::AssetKind; use http_client::github::{latest_github_release, GitHubLspBinaryVersion}; pub use language::*; use lsp::LanguageServerBinary; use regex::Regex; use smol::fs::{self}; use std::fmt::Display; use std::{ any::Any, borrow::Cow, path::{Path, PathBuf}, sync::{Arc, LazyLock}, }; use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName}; use util::{fs::remove_matching, maybe, ResultExt}; 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-gnu"; } #[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"; } impl RustLspAdapter { const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("rust-analyzer"); fn build_asset_name() -> String { let extension = match Self::GITHUB_ASSET_KIND { AssetKind::TarGz => "tar.gz", AssetKind::Gz => "gz", AssetKind::Zip => "zip", }; format!( "{}-{}-{}.{}", Self::SERVER_NAME, std::env::consts::ARCH, Self::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<Path>> { let mut outermost_cargo_toml = None; for path in path.ancestors().take(depth) { let p = path.join("Cargo.toml"); 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 { Self::SERVER_NAME.clone() } fn manifest_name(&self) -> Option<ManifestName> { Some(SharedString::new_static("Cargo.toml").into()) } async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, _: Arc<dyn LanguageToolchainStore>, _: &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::info!("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::error!( "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, ) -> Result<Box<dyn 'static + Send + Any>> { let release = latest_github_release( "rust-lang/rust-analyzer", true, false, delegate.http_client(), ) .await?; let asset_name = Self::build_asset_name(); let asset = release .assets .iter() .find(|asset| asset.name == asset_name) .with_context(|| format!("no asset found matching `{asset_name:?}`"))?; Ok(Box::new(GitHubLspBinaryVersion { name: release.tag_name, url: asset.browser_download_url.clone(), })) } async fn fetch_server_binary( &self, version: Box<dyn 'static + Send + Any>, container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Result<LanguageServerBinary> { let version = version.downcast::<GitHubLspBinaryVersion>().unwrap(); let destination_path = container_dir.join(format!("rust-analyzer-{}", version.name)); let server_path = match Self::GITHUB_ASSET_KIND { AssetKind::TarGz | AssetKind::Gz => destination_path.clone(), // Tar and gzip extract in place. AssetKind::Zip => destination_path.clone().join("rust-analyzer.exe"), // zip contains a .exe }; if fs::metadata(&server_path).await.is_err() { remove_matching(&container_dir, |entry| entry != destination_path).await; let mut response = delegate .http_client() .get(&version.url, Default::default(), true) .await .with_context(|| format!("downloading release from {}", version.url))?; match Self::GITHUB_ASSET_KIND { AssetKind::TarGz => { let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); let archive = async_tar::Archive::new(decompressed_bytes); archive.unpack(&destination_path).await.with_context(|| { format!("extracting {} to {:?}", version.url, destination_path) })?; } AssetKind::Gz => { let mut decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); let mut file = fs::File::create(&destination_path).await.with_context(|| { format!( "creating a file {:?} for a download from {}", destination_path, version.url, ) })?; futures::io::copy(&mut decompressed_bytes, &mut file) .await .with_context(|| { format!("extracting {} to {:?}", version.url, destination_path) })?; } AssetKind::Zip => { node_runtime::extract_zip( &destination_path, BufReader::new(response.body_mut()), ) .await .with_context(|| { format!("unzipping {} to {:?}", version.url, destination_path) })?; } }; // todo("windows") #[cfg(not(windows))] { fs::set_permissions( &server_path, <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755), ) .await?; } } Ok(LanguageServerBinary { path: server_path, env: None, arguments: Default::default(), }) } async fn cached_server_binary( &self, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option<LanguageServerBinary> { get_cached_server_binary(container_dir).await } fn disk_based_diagnostic_sources(&self) -> Vec<String> { vec!["rustc".into()] } 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; } } } } async fn label_for_completion( &self, completion: &lsp::CompletionItem, language: &Arc<Language>, ) -> Option<CodeLabel> { let detail = completion .label_details .as_ref() .and_then(|detail| detail.detail.as_ref()) .or(completion.detail.as_ref()) .map(|detail| detail.trim()); let function_signature = completion .label_details .as_ref() .and_then(|detail| detail.description.as_deref()) .or(completion.detail.as_deref()); match (detail, completion.kind) { (Some(detail), Some(lsp::CompletionItemKind::FIELD)) => { let name = &completion.label; let text = format!("{name}: {detail}"); let prefix = "struct S { "; let source = Rope::from(format!("{prefix}{text} }}")); let runs = language.highlight_text(&source, prefix.len()..prefix.len() + text.len()); return Some(CodeLabel { text, runs, filter_range: 0..name.len(), }); } ( Some(detail), Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE), ) if completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => { let name = &completion.label; let text = format!( "{}: {}", name, completion.detail.as_deref().unwrap_or(detail) ); let prefix = "let "; let source = Rope::from(format!("{prefix}{text} = ();")); let runs = language.highlight_text(&source, prefix.len()..prefix.len() + text.len()); return Some(CodeLabel { text, runs, filter_range: 0..name.len(), }); } ( Some(detail), Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD), ) => { static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("\\(…?\\)").unwrap()); const FUNCTION_PREFIXES: [&str; 6] = [ "async fn", "async unsafe fn", "const fn", "const unsafe fn", "unsafe fn", "fn", ]; // Is it function `async`? let fn_keyword = FUNCTION_PREFIXES.iter().find_map(|prefix| { function_signature.as_ref().and_then(|signature| { signature .strip_prefix(*prefix) .map(|suffix| (*prefix, suffix)) }) }); // fn keyword should be followed by opening parenthesis. if let Some((prefix, suffix)) = fn_keyword { let mut text = REGEX.replace(&completion.label, suffix).to_string(); let source = Rope::from(format!("{prefix} {text} {{}}")); let run_start = prefix.len() + 1; let runs = language.highlight_text(&source, run_start..run_start + text.len()); if detail.starts_with("(") { text.push(' '); text.push_str(&detail); } return Some(CodeLabel { filter_range: 0..completion.label.find('(').unwrap_or(text.len()), text, runs, }); } else if completion .detail .as_ref() .map_or(false, |detail| detail.starts_with("macro_rules! ")) { let source = Rope::from(completion.label.as_str()); let runs = language.highlight_text(&source, 0..completion.label.len()); return Some(CodeLabel { filter_range: 0..completion.label.len(), text: completion.label.clone(), runs, }); } } (_, Some(kind)) => { let highlight_name = 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 mut label = completion.label.clone(); if let Some(detail) = detail.filter(|detail| detail.starts_with("(")) { label.push(' '); label.push_str(detail); } let mut label = CodeLabel::plain(label, None); if let Some(highlight_name) = highlight_name { let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name)?; label.runs.push(( 0..label.text.rfind('(').unwrap_or(completion.label.len()), highlight_id, )); } return Some(label); } _ => {} } None } async fn label_for_symbol( &self, name: &str, kind: lsp::SymbolKind, language: &Arc<Language>, ) -> Option<CodeLabel> { let (text, filter_range, display_range) = match kind { lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => { let text = format!("fn {} () {{}}", name); let filter_range = 3..3 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::STRUCT => { let text = format!("struct {} {{}}", name); let filter_range = 7..7 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::ENUM => { let text = format!("enum {} {{}}", name); let filter_range = 5..5 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::INTERFACE => { let text = format!("trait {} {{}}", name); let filter_range = 6..6 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::CONSTANT => { let text = format!("const {}: () = ();", name); let filter_range = 6..6 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::MODULE => { let text = format!("mod {} {{}}", name); let filter_range = 4..4 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::TYPE_PARAMETER => { let text = format!("type {} {{}}", name); let filter_range = 5..5 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } _ => return None, }; Some(CodeLabel { runs: language.highlight_text(&text.as_str().into(), display_range.clone()), text: text[display_range].to_string(), filter_range, }) } } pub(crate) struct RustContextProvider; const RUST_PACKAGE_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("RUST_PACKAGE")); /// The bin name corresponding to the current file in Cargo.toml const RUST_BIN_NAME_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("RUST_BIN_NAME")); /// The bin kind (bin/example) corresponding to the current file in Cargo.toml const RUST_BIN_KIND_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("RUST_BIN_KIND")); const RUST_TEST_FRAGMENT_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("RUST_TEST_FRAGMENT")); const RUST_DOC_TEST_NAME_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("RUST_DOC_TEST_NAME")); const RUST_TEST_NAME_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("RUST_TEST_NAME")); impl ContextProvider for RustContextProvider {