use anyhow::{anyhow, bail, Context, Result};use async_compression::futures::bufread::GzipDecoder;use async_trait::async_trait;use collections::HashMap;use futures::{io::BufReader, StreamExt};use gpui::{AppContext, AsyncAppContext};use http_client::github::{latest_github_release, GitHubLspBinaryVersion};pub use language::*;use language_settings::all_language_settings;use lsp::LanguageServerBinary;use project::{lsp_store::language_server_settings, project_settings::BinarySettings};use regex::Regex;use smol::fs::{self, File};use std::{ any::Any, borrow::Cow, env::consts, path::{Path, PathBuf}, sync::Arc, sync::LazyLock,};use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};use util::{fs::remove_matching, maybe, ResultExt}; pub struct RustLspAdapter; impl RustLspAdapter { const SERVER_NAME: &'static str = "rust-analyzer";} #[async_trait(?Send)]impl LspAdapter for RustLspAdapter { fn name(&self) -> LanguageServerName { LanguageServerName(Self::SERVER_NAME.into()) } async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, cx: &AsyncAppContext, ) -> Option<LanguageServerBinary> { let configured_binary = cx .update(|cx| { language_server_settings(delegate, Self::SERVER_NAME, cx) .and_then(|s| s.binary.clone()) }) .ok()?; let (path, env, arguments) = match configured_binary { // If nothing is configured, or path_lookup explicitly enabled, // we lookup the binary in the path. None | Some(BinarySettings { path: None, path_lookup: Some(true), .. }) | Some(BinarySettings { path: None, path_lookup: None, .. }) => { let path = delegate.which(Self::SERVER_NAME.as_ref()).await; let env = delegate.shell_env().await; if let Some(path) = path { // 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`"); match delegate .try_exec(LanguageServerBinary { path: path.clone(), arguments: vec!["--help".into()], env: Some(env.clone()), }) .await { Ok(()) => (Some(path), Some(env), None), Err(err) => { log::error!("failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}", path, err); (None, None, None) } } } else { (None, None, None) } } // Otherwise, we use the configured binary. Some(BinarySettings { path: Some(path), arguments, path_lookup, }) => { if path_lookup.is_some() { log::warn!("Both `path` and `path_lookup` are set, ignoring `path_lookup`"); } (Some(path.into()), None, arguments) } _ => (None, None, None), }; path.map(|path| LanguageServerBinary { path, env, arguments: arguments .unwrap_or_default() .iter() .map(|arg| arg.into()) .collect(), }) } 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 os = match consts::OS { "macos" => "apple-darwin", "linux" => "unknown-linux-gnu", "windows" => "pc-windows-msvc", other => bail!("Running on unsupported os: {other}"), }; let asset_name = format!("rust-analyzer-{}-{os}.gz", consts::ARCH); 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)); if fs::metadata(&destination_path).await.is_err() { let mut response = delegate .http_client() .get(&version.url, Default::default(), true) .await .map_err(|err| anyhow!("error downloading release: {}", err))?; let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); let mut file = File::create(&destination_path).await?; futures::io::copy(decompressed_bytes, &mut file).await?; // todo("windows") #[cfg(not(windows))] { fs::set_permissions( &destination_path, <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755), ) .await?; } remove_matching(&container_dir, |entry| entry != destination_path).await; } Ok(LanguageServerBinary { path: destination_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 } async fn installation_test_binary( &self, container_dir: PathBuf, ) -> Option<LanguageServerBinary> { get_cached_server_binary(container_dir) .await .map(|mut binary| { binary.arguments = vec!["--help".into()]; binary }) } 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) { 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(ToOwned::to_owned); let function_signature = completion .label_details .as_ref() .and_then(|detail| detail.description.as_ref()) .or(completion.detail.as_ref()) .map(ToOwned::to_owned); match completion.kind { Some(lsp::CompletionItemKind::FIELD) if detail.is_some() => { let name = &completion.label; let text = format!("{}: {}", name, detail.unwrap()); let source = Rope::from(format!("struct S {{ {} }}", text).as_str()); let runs = language.highlight_text(&source, 11..11 + text.len()); return Some(CodeLabel { text, runs, filter_range: 0..name.len(), }); } Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE) if detail.is_some() && completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => { let name = &completion.label; let text = format!( "{}: {}", name, completion.detail.as_ref().or(detail.as_ref()).unwrap() ); let source = Rope::from(format!("let {} = ();", text).as_str()); let runs = language.highlight_text(&source, 4..4 + text.len()); return Some(CodeLabel { text, runs, filter_range: 0..name.len(), }); } Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD) if detail.is_some() => { static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("\\(…?\\)").unwrap()); let detail = detail.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).as_str()); let run_start = prefix.len() + 1; let runs = language.highlight_text(&source, run_start..run_start + text.len()); if detail.starts_with(" (") { 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(" (")) { use std::fmt::Write; write!(label, "{detail}").ok()?; } 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")); const RUST_MAIN_FUNCTION_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("_rust_main_function_end")); impl ContextProvider for RustContextProvider { fn build_context( &self, task_variables: &TaskVariables, location: &Location, project_env: Option<&HashMap<String, String>>, cx: &mut gpui::AppContext, ) -> Result<TaskVariables> { let local_abs_path = location .buffer .read(cx) .file() .and_then(|file| Some(file.as_local()?.abs_path(cx))); let local_abs_path = local_abs_path.as_deref(); let is_main_function = task_variables .get(&RUST_MAIN_FUNCTION_TASK_VARIABLE) .is_some(); if is_main_function { if let Some((package_name, bin_name)) = local_abs_path .and_then(|path| package_name_and_bin_name_from_abs_path(path, project_env)) { return Ok(TaskVariables::from_iter([ (RUST_PACKAGE_TASK_VARIABLE.clone(), package_name), (RUST_BIN_NAME_TASK_VARIABLE.clone(), bin_name), ])); } } if let Some(package_name) = local_abs_path .and_then(|local_abs_path| local_abs_path.parent()) .and_then(|path| human_readable_package_name(path, project_env)) { return Ok(TaskVariables::from_iter([( RUST_PACKAGE_TASK_VARIABLE.clone(), package_name, )])); } Ok(TaskVariables::default()) } fn associated_tasks( &self, file: Option<Arc<dyn language::File>>, cx: &AppContext, ) -> Option<TaskTemplates> { const DEFAULT_RUN_NAME_STR: &str = "RUST_DEFAULT_PACKAGE_RUN"; let package_to_run = all_language_settings(file.as_ref(), cx) .language(Some(&"Rust".into())) .tasks .variables .get(DEFAULT_RUN_NAME_STR); let run_task_args = if let Some(package_to_run) = package_to_run { vec!["run".into(), "-p".into(), package_to_run.clone()] } else { vec!["run".into()] }; Some(TaskTemplates(vec![ TaskTemplate { label: format!( "cargo check -p {}", RUST_PACKAGE_TASK_VARIABLE.template_value(), ), command: "cargo".into(), args: vec![Lines 1–500 of 1078. Load more
use anyhow::{anyhow, bail, Context, Result}; use async_compression::futures::bufread::GzipDecoder; use async_trait::async_trait; use collections::HashMap; use futures::{io::BufReader, StreamExt}; use gpui::{AppContext, AsyncAppContext}; use http_client::github::{latest_github_release, GitHubLspBinaryVersion}; pub use language::*; use language_settings::all_language_settings; use lsp::LanguageServerBinary; use project::{lsp_store::language_server_settings, project_settings::BinarySettings}; use regex::Regex; use smol::fs::{self, File}; use std::{ any::Any, borrow::Cow, env::consts, path::{Path, PathBuf}, sync::Arc, sync::LazyLock, }; use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName}; use util::{fs::remove_matching, maybe, ResultExt}; pub struct RustLspAdapter; impl RustLspAdapter { const SERVER_NAME: &'static str = "rust-analyzer"; } #[async_trait(?Send)] impl LspAdapter for RustLspAdapter { fn name(&self) -> LanguageServerName { LanguageServerName(Self::SERVER_NAME.into()) } async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, cx: &AsyncAppContext, ) -> Option<LanguageServerBinary> { let configured_binary = cx .update(|cx| { language_server_settings(delegate, Self::SERVER_NAME, cx) .and_then(|s| s.binary.clone()) }) .ok()?; let (path, env, arguments) = match configured_binary { // If nothing is configured, or path_lookup explicitly enabled, // we lookup the binary in the path. None | Some(BinarySettings { path: None, path_lookup: Some(true), .. }) | Some(BinarySettings { path: None, path_lookup: None, .. }) => { let path = delegate.which(Self::SERVER_NAME.as_ref()).await; let env = delegate.shell_env().await; if let Some(path) = path { // 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`"); match delegate .try_exec(LanguageServerBinary { path: path.clone(), arguments: vec!["--help".into()], env: Some(env.clone()), }) .await { Ok(()) => (Some(path), Some(env), None), Err(err) => { log::error!("failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}", path, err); (None, None, None) } } } else { (None, None, None) } } // Otherwise, we use the configured binary. Some(BinarySettings { path: Some(path), arguments, path_lookup, }) => { if path_lookup.is_some() { log::warn!("Both `path` and `path_lookup` are set, ignoring `path_lookup`"); } (Some(path.into()), None, arguments) } _ => (None, None, None), }; path.map(|path| LanguageServerBinary { path, env, arguments: arguments .unwrap_or_default() .iter() .map(|arg| arg.into()) .collect(), }) } 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 os = match consts::OS { "macos" => "apple-darwin", "linux" => "unknown-linux-gnu", "windows" => "pc-windows-msvc", other => bail!("Running on unsupported os: {other}"), }; let asset_name = format!("rust-analyzer-{}-{os}.gz", consts::ARCH); 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)); if fs::metadata(&destination_path).await.is_err() { let mut response = delegate .http_client() .get(&version.url, Default::default(), true) .await .map_err(|err| anyhow!("error downloading release: {}", err))?; let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); let mut file = File::create(&destination_path).await?; futures::io::copy(decompressed_bytes, &mut file).await?; // todo("windows") #[cfg(not(windows))] { fs::set_permissions( &destination_path, <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755), ) .await?; } remove_matching(&container_dir, |entry| entry != destination_path).await; } Ok(LanguageServerBinary { path: destination_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 } async fn installation_test_binary( &self, container_dir: PathBuf, ) -> Option<LanguageServerBinary> { get_cached_server_binary(container_dir) .await .map(|mut binary| { binary.arguments = vec!["--help".into()]; binary }) } 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) { 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(ToOwned::to_owned); let function_signature = completion .label_details .as_ref() .and_then(|detail| detail.description.as_ref()) .or(completion.detail.as_ref()) .map(ToOwned::to_owned); match completion.kind { Some(lsp::CompletionItemKind::FIELD) if detail.is_some() => { let name = &completion.label; let text = format!("{}: {}", name, detail.unwrap()); let source = Rope::from(format!("struct S {{ {} }}", text).as_str()); let runs = language.highlight_text(&source, 11..11 + text.len()); return Some(CodeLabel { text, runs, filter_range: 0..name.len(), }); } Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE) if detail.is_some() && completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => { let name = &completion.label; let text = format!( "{}: {}", name, completion.detail.as_ref().or(detail.as_ref()).unwrap() ); let source = Rope::from(format!("let {} = ();", text).as_str()); let runs = language.highlight_text(&source, 4..4 + text.len()); return Some(CodeLabel { text, runs, filter_range: 0..name.len(), }); } Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD) if detail.is_some() => { static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("\\(…?\\)").unwrap()); let detail = detail.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).as_str()); let run_start = prefix.len() + 1; let runs = language.highlight_text(&source, run_start..run_start + text.len()); if detail.starts_with(" (") { 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(" (")) { use std::fmt::Write; write!(label, "{detail}").ok()?; } 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")); const RUST_MAIN_FUNCTION_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("_rust_main_function_end")); impl ContextProvider for RustContextProvider { fn build_context( &self, task_variables: &TaskVariables, location: &Location, project_env: Option<&HashMap<String, String>>, cx: &mut gpui::AppContext, ) -> Result<TaskVariables> { let local_abs_path = location .buffer .read(cx) .file() .and_then(|file| Some(file.as_local()?.abs_path(cx))); let local_abs_path = local_abs_path.as_deref(); let is_main_function = task_variables .get(&RUST_MAIN_FUNCTION_TASK_VARIABLE) .is_some(); if is_main_function { if let Some((package_name, bin_name)) = local_abs_path .and_then(|path| package_name_and_bin_name_from_abs_path(path, project_env)) { return Ok(TaskVariables::from_iter([ (RUST_PACKAGE_TASK_VARIABLE.clone(), package_name), (RUST_BIN_NAME_TASK_VARIABLE.clone(), bin_name), ])); } } if let Some(package_name) = local_abs_path .and_then(|local_abs_path| local_abs_path.parent()) .and_then(|path| human_readable_package_name(path, project_env)) { return Ok(TaskVariables::from_iter([( RUST_PACKAGE_TASK_VARIABLE.clone(), package_name, )])); } Ok(TaskVariables::default()) } fn associated_tasks( &self, file: Option<Arc<dyn language::File>>, cx: &AppContext, ) -> Option<TaskTemplates> { const DEFAULT_RUN_NAME_STR: &str = "RUST_DEFAULT_PACKAGE_RUN"; let package_to_run = all_language_settings(file.as_ref(), cx) .language(Some(&"Rust".into())) .tasks .variables .get(DEFAULT_RUN_NAME_STR); let run_task_args = if let Some(package_to_run) = package_to_run { vec!["run".into(), "-p".into(), package_to_run.clone()] } else { vec!["run".into()] }; Some(TaskTemplates(vec![ TaskTemplate { label: format!( "cargo check -p {}", RUST_PACKAGE_TASK_VARIABLE.template_value(), ), command: "cargo".into(), args: vec![