use anyhow::{Context as _, Result};use async_trait::async_trait;use collections::HashMap;use futures::StreamExt;use futures::lock::OwnedMutexGuard;use gpui::{App, AppContext, AsyncApp, Entity, 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, LanguageServerBinaryOptions};use project::lsp_store::lsp_ext_command;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::{SemanticTokenRules, Settings as _};use smallvec::SmallVec;use smol::fs::{self};use std::cmp::Reverse;use std::fmt::Display;use std::future::Future;use std::ops::Range;use std::{ borrow::Cow, path::{Path, PathBuf}, sync::{Arc, LazyLock},};use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};use util::command::{Stdio, new_command};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::LanguageSettings; pub(crate) fn semantic_token_rules() -> SemanticTokenRules { let content = grammars::get_file("rust/semantic_token_rules.json") .expect("missing rust/semantic_token_rules.json"); let json = std::str::from_utf8(&content.data).expect("invalid utf-8 in semantic_token_rules"); settings::parse_json_with_comments::<SemanticTokenRules>(json) .expect("failed to parse rust semantic_token_rules.json")} 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 { fn convert_rust_analyzer_schema(raw_schema: &serde_json::Value) -> serde_json::Value { let Some(schema_array) = raw_schema.as_array() else { return raw_schema.clone(); }; let mut root_properties = serde_json::Map::new(); for item in schema_array { if let Some(props) = item.get("properties").and_then(|p| p.as_object()) { for (key, value) in props { let parts: Vec<&str> = key.split('.').collect(); if parts.is_empty() { continue; } let parts_to_process = if parts.first() == Some(&"rust-analyzer") { &parts[1..] } else { &parts[..] }; if parts_to_process.is_empty() { continue; } let mut current = &mut root_properties; for (i, part) in parts_to_process.iter().enumerate() { let is_last = i == parts_to_process.len() - 1; if is_last { current.insert(part.to_string(), value.clone()); } else { let next_current = current .entry(part.to_string()) .or_insert_with(|| { serde_json::json!({ "type": "object", "properties": {} }) }) .as_object_mut() .expect("should be an object") .entry("properties") .or_insert_with(|| serde_json::json!({})) .as_object_mut() .expect("properties should be an object"); current = next_current; } } } } } serde_json::json!({ "type": "object", "properties": root_properties }) } #[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_command; let ldd_output = new_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 rustup_rust_analyzer_for_worktree( delegate: &dyn LspAdapterDelegate, ) -> Option<PathBuf> { if !Self::workspace_has_rust_toolchain_override(delegate).await { return None; } let rustup = delegate.which("rustup".as_ref()).await?; let env = delegate.shell_env().await; let worktree_root = delegate.worktree_root_path(); let output = new_command(rustup) .args(["which", "rust-analyzer"]) .envs(env.iter()) .current_dir(worktree_root) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .output() .await; let output = match output { Ok(output) if output.status.success() => output, Ok(output) => { log::debug!( "failed to locate rust-analyzer through rustup in {worktree_root:?}: {}", String::from_utf8_lossy(&output.stderr) ); return None; } Err(err) => { log::debug!( "failed to run `rustup which rust-analyzer` in {worktree_root:?}: {err:#}" ); return None; } }; let path = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim()); Some(path).filter(|p| !p.as_os_str().is_empty()) } async fn workspace_has_rust_toolchain_override(delegate: &dyn LspAdapterDelegate) -> bool { for file_name in ["rust-toolchain.toml", "rust-toolchain"] { if fs::metadata(delegate.resolve_relative_path(PathBuf::from(file_name))) .await .is_ok() { return true; } } false } async fn build_asset_name() -> String { let extension = match Self::GITHUB_ASSET_KIND { AssetKind::TarGz => "tar.gz", AssetKind::TarBz2 => "tar.bz2", 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::from_unix_str("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) { 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 mut 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 mut label; let mut runs = vec![]; if completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET) && let Some( lsp::CompletionTextEdit::InsertAndReplace(lsp::InsertReplaceEdit { new_text, .. }) | lsp::CompletionTextEdit::Edit(lsp::TextEdit { new_text, .. }), ) = completion.text_edit.as_ref() && let Ok(mut snippet) = snippet::Snippet::parse(new_text) && snippet.tabstops.len() > 1 { label = String::new(); // we never display the final tabstop snippet.tabstops.remove(snippet.tabstops.len() - 1); let mut text_pos = 0; let mut all_stop_ranges = snippet .tabstops .into_iter() .flat_map(|stop| stop.ranges) .collect::<SmallVec<[_; 8]>>(); all_stop_ranges.sort_unstable_by_key(|a| (a.start, Reverse(a.end))); // Placeholders may nest, e.g. `$2` inside `${1:"$2"}` struct OpenPlaceholder { snippet_text_end: usize, label_run_start: usize, } let mut open_placeholders = SmallVec::<[OpenPlaceholder; 4]>::new(); for range in &all_stop_ranges { let start_pos = range.start as usize;Lines 1–500 of 2393. Load more
use anyhow::{Context as _, Result}; use async_trait::async_trait; use collections::HashMap; use futures::StreamExt; use futures::lock::OwnedMutexGuard; use gpui::{App, AppContext, AsyncApp, Entity, 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, LanguageServerBinaryOptions}; use project::lsp_store::lsp_ext_command; 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::{SemanticTokenRules, Settings as _}; use smallvec::SmallVec; use smol::fs::{self}; use std::cmp::Reverse; use std::fmt::Display; use std::future::Future; use std::ops::Range; use std::{ borrow::Cow, path::{Path, PathBuf}, sync::{Arc, LazyLock}, }; use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName}; use util::command::{Stdio, new_command}; 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::LanguageSettings; pub(crate) fn semantic_token_rules() -> SemanticTokenRules { let content = grammars::get_file("rust/semantic_token_rules.json") .expect("missing rust/semantic_token_rules.json"); let json = std::str::from_utf8(&content.data).expect("invalid utf-8 in semantic_token_rules"); settings::parse_json_with_comments::<SemanticTokenRules>(json) .expect("failed to parse rust semantic_token_rules.json") } 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 { fn convert_rust_analyzer_schema(raw_schema: &serde_json::Value) -> serde_json::Value { let Some(schema_array) = raw_schema.as_array() else { return raw_schema.clone(); }; let mut root_properties = serde_json::Map::new(); for item in schema_array { if let Some(props) = item.get("properties").and_then(|p| p.as_object()) { for (key, value) in props { let parts: Vec<&str> = key.split('.').collect(); if parts.is_empty() { continue; } let parts_to_process = if parts.first() == Some(&"rust-analyzer") { &parts[1..] } else { &parts[..] }; if parts_to_process.is_empty() { continue; } let mut current = &mut root_properties; for (i, part) in parts_to_process.iter().enumerate() { let is_last = i == parts_to_process.len() - 1; if is_last { current.insert(part.to_string(), value.clone()); } else { let next_current = current .entry(part.to_string()) .or_insert_with(|| { serde_json::json!({ "type": "object", "properties": {} }) }) .as_object_mut() .expect("should be an object") .entry("properties") .or_insert_with(|| serde_json::json!({})) .as_object_mut() .expect("properties should be an object"); current = next_current; } } } } } serde_json::json!({ "type": "object", "properties": root_properties }) } #[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_command; let ldd_output = new_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 rustup_rust_analyzer_for_worktree( delegate: &dyn LspAdapterDelegate, ) -> Option<PathBuf> { if !Self::workspace_has_rust_toolchain_override(delegate).await { return None; } let rustup = delegate.which("rustup".as_ref()).await?; let env = delegate.shell_env().await; let worktree_root = delegate.worktree_root_path(); let output = new_command(rustup) .args(["which", "rust-analyzer"]) .envs(env.iter()) .current_dir(worktree_root) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .output() .await; let output = match output { Ok(output) if output.status.success() => output, Ok(output) => { log::debug!( "failed to locate rust-analyzer through rustup in {worktree_root:?}: {}", String::from_utf8_lossy(&output.stderr) ); return None; } Err(err) => { log::debug!( "failed to run `rustup which rust-analyzer` in {worktree_root:?}: {err:#}" ); return None; } }; let path = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim()); Some(path).filter(|p| !p.as_os_str().is_empty()) } async fn workspace_has_rust_toolchain_override(delegate: &dyn LspAdapterDelegate) -> bool { for file_name in ["rust-toolchain.toml", "rust-toolchain"] { if fs::metadata(delegate.resolve_relative_path(PathBuf::from(file_name))) .await .is_ok() { return true; } } false } async fn build_asset_name() -> String { let extension = match Self::GITHUB_ASSET_KIND { AssetKind::TarGz => "tar.gz", AssetKind::TarBz2 => "tar.bz2", 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::from_unix_str("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) { 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 mut 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 mut label; let mut runs = vec![]; if completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET) && let Some( lsp::CompletionTextEdit::InsertAndReplace(lsp::InsertReplaceEdit { new_text, .. }) | lsp::CompletionTextEdit::Edit(lsp::TextEdit { new_text, .. }), ) = completion.text_edit.as_ref() && let Ok(mut snippet) = snippet::Snippet::parse(new_text) && snippet.tabstops.len() > 1 { label = String::new(); // we never display the final tabstop snippet.tabstops.remove(snippet.tabstops.len() - 1); let mut text_pos = 0; let mut all_stop_ranges = snippet .tabstops .into_iter() .flat_map(|stop| stop.ranges) .collect::<SmallVec<[_; 8]>>(); all_stop_ranges.sort_unstable_by_key(|a| (a.start, Reverse(a.end))); // Placeholders may nest, e.g. `$2` inside `${1:"$2"}` struct OpenPlaceholder { snippet_text_end: usize, label_run_start: usize, } let mut open_placeholders = SmallVec::<[OpenPlaceholder; 4]>::new(); for range in &all_stop_ranges { let start_pos = range.start as usize;