use std::io::Read; use flate2::read::GzDecoder; use itertools::Itertools; use lsp_types::{MarkupContent, MarkupKind}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use smol_str::SmolStr; use crate::{syntax::latex::ExplicitLink, Workspace}; #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ComponentDatabase { pub components: Vec, pub metadata: Vec, } impl ComponentDatabase { #[must_use] pub fn find(&self, name: &str) -> Option<&Component> { self.components.iter().find(|component| { component .file_names .iter() .any(|file_name| file_name == name) }) } #[must_use] pub fn find_no_ext(&self, name: &str) -> Option<&Component> { self.components.iter().find(|component| { component .file_names .iter() .any(|file_name| &file_name[0..file_name.len() - 4] == name) }) } #[must_use] pub fn linked_components(&self, workspace: &Workspace) -> Vec<&Component> { let mut start_components = vec![self.kernel()]; for document in workspace.documents_by_uri.values() { if let Some(data) = document.data.as_latex() { data.extras .explicit_links .iter() .filter_map(ExplicitLink::as_component_name) .filter_map(|name| self.find(&name)) .for_each(|component| start_components.push(component)); } } let mut all_components = Vec::new(); for component in start_components { all_components.push(component); component .references .iter() .filter_map(|file| self.find(file)) .for_each(|component| all_components.push(component)); } all_components .into_iter() .unique_by(|component| &component.file_names) .collect() } #[must_use] pub fn contains(&self, short_name: &str) -> bool { let sty = format!("{}.sty", short_name); let cls = format!("{}.cls", short_name); self.find(&sty).is_some() || self.find(&cls).is_some() } #[must_use] pub fn kernel(&self) -> &Component { self.components .iter() .find(|component| component.file_names.is_empty()) .unwrap() } #[must_use] pub fn exists(&self, file_name: &str) -> bool { self.components .iter() .any(|component| component.file_names.iter().any(|f| f == file_name)) } #[must_use] pub fn documentation(&self, name: &str) -> Option { let metadata = self .metadata .iter() .find(|metadata| metadata.name == name)?; let desc = metadata.description.clone()?; Some(MarkupContent { kind: MarkupKind::PlainText, value: desc, }) } } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Component { pub file_names: Vec, pub references: Vec, pub commands: Vec, pub environments: Vec, } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ComponentCommand { pub name: SmolStr, pub image: Option, pub glyph: Option, pub parameters: Vec, } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ComponentParameter(pub Vec); #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ComponentArgument { pub name: SmolStr, pub image: Option, } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ComponentMetadata { pub name: String, pub caption: Option, pub description: Option, } const JSON_GZ: &[u8] = include_bytes!("../data/components.json.gz"); pub static COMPONENT_DATABASE: Lazy = Lazy::new(|| { let mut decoder = GzDecoder::new(JSON_GZ); let mut buf = String::new(); decoder.read_to_string(&mut buf).unwrap(); serde_json::from_str(&buf).unwrap() });