1use std::io::Read;
2
3use flate2::read::GzDecoder;
4use itertools::Itertools;
5use lsp_types::{MarkupContent, MarkupKind};
6use once_cell::sync::Lazy;
7use serde::{Deserialize, Serialize};
8use smol_str::SmolStr;
9
10use crate::{syntax::latex::ExplicitLink, Workspace};
11
12#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct ComponentDatabase {
15 pub components: Vec<Component>,
16 pub metadata: Vec<ComponentMetadata>,
17}
18
19impl ComponentDatabase {
20 #[must_use]
21 pub fn find(&self, name: &str) -> Option<&Component> {
22 self.components.iter().find(|component| {
23 component
24 .file_names
25 .iter()
26 .any(|file_name| file_name == name)
27 })
28 }
29
30 #[must_use]
31 pub fn find_no_ext(&self, name: &str) -> Option<&Component> {
32 self.components.iter().find(|component| {
33 component
34 .file_names
35 .iter()
36 .any(|file_name| &file_name[0..file_name.len() - 4] == name)
37 })
38 }
39
40 #[must_use]
41 pub fn linked_components(&self, workspace: &Workspace) -> Vec<&Component> {
42 let mut start_components = vec![self.kernel()];
43 for document in workspace.iter() {
44 if let Some(data) = document.data().as_latex() {
45 data.extras
46 .explicit_links
47 .iter()
48 .filter_map(ExplicitLink::as_component_name)
49 .filter_map(|name| self.find(&name))
50 .for_each(|component| start_components.push(component));
51 }
52 }
53
54 let mut all_components = Vec::new();
55 for component in start_components {
56 all_components.push(component);
57 component
58 .references
59 .iter()
60 .filter_map(|file| self.find(file))
61 .for_each(|component| all_components.push(component));
62 }
63
64 all_components
65 .into_iter()
66 .unique_by(|component| &component.file_names)
67 .collect()
68 }
69
70 #[must_use]
71 pub fn contains(&self, short_name: &str) -> bool {
72 let sty = format!("{}.sty", short_name);
73 let cls = format!("{}.cls", short_name);
74 self.find(&sty).is_some() || self.find(&cls).is_some()
75 }
76
77 #[must_use]
78 pub fn kernel(&self) -> &Component {
79 self.components
80 .iter()
81 .find(|component| component.file_names.is_empty())
82 .unwrap()
83 }
84
85 #[must_use]
86 pub fn exists(&self, file_name: &str) -> bool {
87 self.components
88 .iter()
89 .any(|component| component.file_names.iter().any(|f| f == file_name))
90 }
91
92 #[must_use]
93 pub fn documentation(&self, name: &str) -> Option<MarkupContent> {
94 let metadata = self
95 .metadata
96 .iter()
97 .find(|metadata| metadata.name == name)?;
98
99 let desc = metadata.description.clone()?;
100 Some(MarkupContent {
101 kind: MarkupKind::PlainText,
102 value: desc,
103 })
104 }
105}
106
107#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct Component {
110 pub file_names: Vec<SmolStr>,
111 pub references: Vec<SmolStr>,
112 pub commands: Vec<ComponentCommand>,
113 pub environments: Vec<SmolStr>,
114}
115
116#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct ComponentCommand {
119 pub name: SmolStr,
120 pub image: Option<String>,
121 pub glyph: Option<SmolStr>,
122 pub parameters: Vec<ComponentParameter>,
123}
124
125#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
126#[serde(rename_all = "camelCase")]
127pub struct ComponentParameter(pub Vec<ComponentArgument>);
128
129#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct ComponentArgument {
132 pub name: SmolStr,
133 pub image: Option<String>,
134}
135
136#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
137#[serde(rename_all = "camelCase")]
138pub struct ComponentMetadata {
139 pub name: String,
140 pub caption: Option<String>,
141 pub description: Option<String>,
142}
143
144const JSON_GZ: &[u8] = include_bytes!("../data/components.json.gz");
145
146pub static COMPONENT_DATABASE: Lazy<ComponentDatabase> = Lazy::new(|| {
147 let mut decoder = GzDecoder::new(JSON_GZ);
148 let mut buf = String::new();
149 decoder.read_to_string(&mut buf).unwrap();
150 serde_json::from_str(&buf).unwrap()
151});