use anyhow::{anyhow, bail, Context, Result};use async_compression::futures::bufread::GzipDecoder;use async_trait::async_trait;use futures::{io::BufReader, StreamExt};use gpui::{AppContext, AsyncAppContext};use http::github::{latest_github_release, GitHubLspBinaryVersion};pub use language::*;use language_settings::all_language_settings;use lazy_static::lazy_static;use lsp::LanguageServerBinary;use project::project_settings::{BinarySettings, ProjectSettings};use regex::Regex;use settings::Settings;use smol::fs::{self, File};use std::{ any::Any, borrow::Cow, env::consts, path::{Path, PathBuf}, sync::Arc,};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| { ProjectSettings::get_global(cx) .lsp .get(Self::SERVER_NAME) .and_then(|s| s.binary.clone()) }); match configured_binary { Ok(Some(BinarySettings { path, arguments, path_lookup, })) => { let (path, env) = match (path, path_lookup) { (Some(path), lookup) => { if lookup.is_some() { log::warn!( "Both `path` and `path_lookup` are set, ignoring `path_lookup`" ); } (Some(path.into()), None) } (None, Some(true)) => { let path = delegate.which(Self::SERVER_NAME.as_ref()).await?; let env = delegate.shell_env().await; (Some(path), Some(env)) } (None, Some(false)) | (None, None) => (None, None), }; path.map(|path| LanguageServerBinary { path, arguments: arguments .unwrap_or_default() .iter() .map(|arg| arg.into()) .collect(), env, }) } _ => None, } } 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) { lazy_static! { static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`$").unwrap(); } 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); 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, detail.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() => { lazy_static! { static ref REGEX: Regex = Regex::new("\\(…?\\)").unwrap(); } let detail = detail.unwrap(); const FUNCTION_PREFIXES: [&'static str; 2] = ["async fn", "fn"]; let prefix = FUNCTION_PREFIXES .iter() .find_map(|prefix| detail.strip_prefix(*prefix).map(|suffix| (prefix, suffix))); // fn keyword should be followed by opening parenthesis. if let Some((prefix, suffix)) = prefix { if suffix.starts_with('(') { let 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()); return Some(CodeLabel { filter_range: 0..completion.label.find('(').unwrap_or(text.len()), 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 highlight_id = language.grammar()?.highlight_id_for_name(highlight_name?)?; 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); 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, 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(|local_abs_path| package_name_and_bin_name_from_abs_path(local_abs_path)) { 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(human_readable_package_name) { 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: &'static str = "RUST_DEFAULT_PACKAGE_RUN"; let package_to_run = all_language_settings(file.as_ref(), cx) .language(Some("Rust")) .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![ "check".into(), "-p".into(), RUST_PACKAGE_TASK_VARIABLE.template_value(), ], cwd: Some("$ZED_DIRNAME".to_owned()), ..TaskTemplate::default() }, TaskTemplate { label: "cargo check --workspace --all-targets".into(), command: "cargo".into(), args: vec!["check".into(), "--workspace".into(), "--all-targets".into()], cwd: Some("$ZED_DIRNAME".to_owned()), ..TaskTemplate::default() }, TaskTemplate { label: format!( "cargo test -p {} {} -- --nocapture", RUST_PACKAGE_TASK_VARIABLE.template_value(), VariableName::Symbol.template_value(), ), command: "cargo".into(), args: vec![ "test".into(), "-p".into(), RUST_PACKAGE_TASK_VARIABLE.template_value(), VariableName::Symbol.template_value(), "--".into(), "--nocapture".into(), ], tags: vec!["rust-test".to_owned()], cwd: Some("$ZED_DIRNAME".to_owned()), ..TaskTemplate::default() }, TaskTemplate { label: format!( "cargo test -p {} {}", RUST_PACKAGE_TASK_VARIABLE.template_value(), VariableName::Stem.template_value(), ), command: "cargo".into(), args: vec![ "test".into(), "-p".into(), RUST_PACKAGE_TASK_VARIABLE.template_value(), VariableName::Stem.template_value(), ], tags: vec!["rust-mod-test".to_owned()], cwd: Some("$ZED_DIRNAME".to_owned()), ..TaskTemplate::default() }, TaskTemplate { label: format!( "cargo run -p {} --bin {}", RUST_PACKAGE_TASK_VARIABLE.template_value(), RUST_BIN_NAME_TASK_VARIABLE.template_value(), ), command: "cargo".into(), args: vec![ "run".into(), "-p".into(), RUST_PACKAGE_TASK_VARIABLE.template_value(), "--bin".into(), RUST_BIN_NAME_TASK_VARIABLE.template_value(), ], cwd: Some("$ZED_DIRNAME".to_owned()), tags: vec!["rust-main".to_owned()], ..TaskTemplate::default() },Lines 1–500 of 985. Load more
use anyhow::{anyhow, bail, Context, Result}; use async_compression::futures::bufread::GzipDecoder; use async_trait::async_trait; use futures::{io::BufReader, StreamExt}; use gpui::{AppContext, AsyncAppContext}; use http::github::{latest_github_release, GitHubLspBinaryVersion}; pub use language::*; use language_settings::all_language_settings; use lazy_static::lazy_static; use lsp::LanguageServerBinary; use project::project_settings::{BinarySettings, ProjectSettings}; use regex::Regex; use settings::Settings; use smol::fs::{self, File}; use std::{ any::Any, borrow::Cow, env::consts, path::{Path, PathBuf}, sync::Arc, }; 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| { ProjectSettings::get_global(cx) .lsp .get(Self::SERVER_NAME) .and_then(|s| s.binary.clone()) }); match configured_binary { Ok(Some(BinarySettings { path, arguments, path_lookup, })) => { let (path, env) = match (path, path_lookup) { (Some(path), lookup) => { if lookup.is_some() { log::warn!( "Both `path` and `path_lookup` are set, ignoring `path_lookup`" ); } (Some(path.into()), None) } (None, Some(true)) => { let path = delegate.which(Self::SERVER_NAME.as_ref()).await?; let env = delegate.shell_env().await; (Some(path), Some(env)) } (None, Some(false)) | (None, None) => (None, None), }; path.map(|path| LanguageServerBinary { path, arguments: arguments .unwrap_or_default() .iter() .map(|arg| arg.into()) .collect(), env, }) } _ => None, } } 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) { lazy_static! { static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`$").unwrap(); } 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); 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, detail.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() => { lazy_static! { static ref REGEX: Regex = Regex::new("\\(…?\\)").unwrap(); } let detail = detail.unwrap(); const FUNCTION_PREFIXES: [&'static str; 2] = ["async fn", "fn"]; let prefix = FUNCTION_PREFIXES .iter() .find_map(|prefix| detail.strip_prefix(*prefix).map(|suffix| (prefix, suffix))); // fn keyword should be followed by opening parenthesis. if let Some((prefix, suffix)) = prefix { if suffix.starts_with('(') { let 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()); return Some(CodeLabel { filter_range: 0..completion.label.find('(').unwrap_or(text.len()), 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 highlight_id = language.grammar()?.highlight_id_for_name(highlight_name?)?; 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); 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, 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(|local_abs_path| package_name_and_bin_name_from_abs_path(local_abs_path)) { 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(human_readable_package_name) { 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: &'static str = "RUST_DEFAULT_PACKAGE_RUN"; let package_to_run = all_language_settings(file.as_ref(), cx) .language(Some("Rust")) .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![ "check".into(), "-p".into(), RUST_PACKAGE_TASK_VARIABLE.template_value(), ], cwd: Some("$ZED_DIRNAME".to_owned()), ..TaskTemplate::default() }, TaskTemplate { label: "cargo check --workspace --all-targets".into(), command: "cargo".into(), args: vec!["check".into(), "--workspace".into(), "--all-targets".into()], cwd: Some("$ZED_DIRNAME".to_owned()), ..TaskTemplate::default() }, TaskTemplate { label: format!( "cargo test -p {} {} -- --nocapture", RUST_PACKAGE_TASK_VARIABLE.template_value(), VariableName::Symbol.template_value(), ), command: "cargo".into(), args: vec![ "test".into(), "-p".into(), RUST_PACKAGE_TASK_VARIABLE.template_value(), VariableName::Symbol.template_value(), "--".into(), "--nocapture".into(), ], tags: vec!["rust-test".to_owned()], cwd: Some("$ZED_DIRNAME".to_owned()), ..TaskTemplate::default() }, TaskTemplate { label: format!( "cargo test -p {} {}", RUST_PACKAGE_TASK_VARIABLE.template_value(), VariableName::Stem.template_value(), ), command: "cargo".into(), args: vec![ "test".into(), "-p".into(), RUST_PACKAGE_TASK_VARIABLE.template_value(), VariableName::Stem.template_value(), ], tags: vec!["rust-mod-test".to_owned()], cwd: Some("$ZED_DIRNAME".to_owned()), ..TaskTemplate::default() }, TaskTemplate { label: format!( "cargo run -p {} --bin {}", RUST_PACKAGE_TASK_VARIABLE.template_value(), RUST_BIN_NAME_TASK_VARIABLE.template_value(), ), command: "cargo".into(), args: vec![ "run".into(), "-p".into(), RUST_PACKAGE_TASK_VARIABLE.template_value(), "--bin".into(), RUST_BIN_NAME_TASK_VARIABLE.template_value(), ], cwd: Some("$ZED_DIRNAME".to_owned()), tags: vec!["rust-main".to_owned()], ..TaskTemplate::default() },