use anyhow::{Context as _, Result};use async_compression::futures::bufread::GzipDecoder;use async_trait::async_trait;use collections::HashMap;use futures::{StreamExt, io::BufReader};use gpui::{App, AppContext, AsyncApp, SharedString, Task};use http_client::github::AssetKind;use http_client::github::{GitHubLspBinaryVersion, latest_github_release};pub use language::*;use lsp::{InitializeParams, LanguageServerBinary};use project::Fs;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::{ any::Any, borrow::Cow, path::{Path, PathBuf}, sync::{Arc, LazyLock},};use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};use util::archive::extract_zip;use util::merge_json_value_into;use util::{ ResultExt, fs::{make_file_executable, remove_matching}, 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-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";} const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("rust-analyzer"); impl RustLspAdapter { fn build_asset_name() -> String { let extension = match Self::GITHUB_ASSET_KIND { AssetKind::TarGz => "tar.gz", AssetKind::Gz => "gz", AssetKind::Zip => "zip", }; format!( "{}-{}-{}.{}", 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 { 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::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, ) -> 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 => { extract_zip(&destination_path, response.body_mut()) .await .with_context(|| { format!("unzipping {} to {:?}", version.url, destination_path) })?; } }; // todo("windows") make_file_executable(&server_path).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![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> { 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()); let filter_range = completion .filter_text .as_deref() .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len())) .unwrap_or(0..name.len()); return Some(CodeLabel { text, runs, filter_range, }); } ( 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()); let filter_range = completion .filter_text .as_deref() .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len())) .unwrap_or(0..name.len()); return Some(CodeLabel { text, runs, filter_range, }); } ( 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); } let filter_range = completion .filter_text .as_deref() .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len())) .unwrap_or(0..completion.label.find('(').unwrap_or(text.len())); return Some(CodeLabel { filter_range, text, runs, }); } else if completion .detail .as_ref() .map_or(false, |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); let filter_range = completion .filter_text .as_deref() .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len())) .unwrap_or(0..len); return Some(CodeLabel { filter_range, text, 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, completion.filter_text.as_deref()); 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,Lines 1–500 of 1527. Load more
use anyhow::{Context as _, Result}; use async_compression::futures::bufread::GzipDecoder; use async_trait::async_trait; use collections::HashMap; use futures::{StreamExt, io::BufReader}; use gpui::{App, AppContext, AsyncApp, SharedString, Task}; use http_client::github::AssetKind; use http_client::github::{GitHubLspBinaryVersion, latest_github_release}; pub use language::*; use lsp::{InitializeParams, LanguageServerBinary}; use project::Fs; 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::{ any::Any, borrow::Cow, path::{Path, PathBuf}, sync::{Arc, LazyLock}, }; use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName}; use util::archive::extract_zip; use util::merge_json_value_into; use util::{ ResultExt, fs::{make_file_executable, remove_matching}, 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-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"; } const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("rust-analyzer"); impl RustLspAdapter { fn build_asset_name() -> String { let extension = match Self::GITHUB_ASSET_KIND { AssetKind::TarGz => "tar.gz", AssetKind::Gz => "gz", AssetKind::Zip => "zip", }; format!( "{}-{}-{}.{}", 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 { 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::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, ) -> 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 => { extract_zip(&destination_path, response.body_mut()) .await .with_context(|| { format!("unzipping {} to {:?}", version.url, destination_path) })?; } }; // todo("windows") make_file_executable(&server_path).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![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> { 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()); let filter_range = completion .filter_text .as_deref() .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len())) .unwrap_or(0..name.len()); return Some(CodeLabel { text, runs, filter_range, }); } ( 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()); let filter_range = completion .filter_text .as_deref() .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len())) .unwrap_or(0..name.len()); return Some(CodeLabel { text, runs, filter_range, }); } ( 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); } let filter_range = completion .filter_text .as_deref() .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len())) .unwrap_or(0..completion.label.find('(').unwrap_or(text.len())); return Some(CodeLabel { filter_range, text, runs, }); } else if completion .detail .as_ref() .map_or(false, |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); let filter_range = completion .filter_text .as_deref() .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len())) .unwrap_or(0..len); return Some(CodeLabel { filter_range, text, 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, completion.filter_text.as_deref()); 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,