texlab/
component_db.rs

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});