summaryrefslogtreecommitdiff
path: root/support/texlab/src/util/components.rs
blob: 022e7efbc88fe3b5310ffd31b75492694556a381 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use std::io::Read;

use flate2::read::GzDecoder;
use itertools::Itertools;
use lsp_types::{MarkupContent, MarkupKind};
use once_cell::sync::Lazy;
use serde::Deserialize;
use smol_str::SmolStr;

use crate::{
    db::{analysis::TexLinkKind, Document, Workspace},
    Db,
};

#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ComponentDatabase {
    pub components: Vec<Component>,
    pub metadata: Vec<ComponentMetadata>,
}

impl ComponentDatabase {
    pub fn find(&self, name: &str) -> Option<&Component> {
        self.components.iter().find(|component| {
            component
                .file_names
                .iter()
                .any(|file_name| file_name == name)
        })
    }

    pub fn linked_components(&self, db: &dyn Db, child: Document) -> Vec<&Component> {
        Workspace::get(db)
            .related(db, child)
            .iter()
            .filter_map(|document| document.parse(db).as_tex())
            .flat_map(|data| data.analyze(db).links(db))
            .filter_map(|link| match link.kind(db) {
                TexLinkKind::Sty => Some(format!("{}.sty", link.path(db).text(db))),
                TexLinkKind::Cls => Some(format!("{}.cls", link.path(db).text(db))),
                _ => None,
            })
            .filter_map(|name| self.find(&name))
            .chain(std::iter::once(self.kernel()))
            .flat_map(|comp| {
                comp.references
                    .iter()
                    .filter_map(|name| self.find(name))
                    .chain(std::iter::once(comp))
            })
            .unique_by(|comp| &comp.file_names)
            .collect()
    }

    pub fn kernel(&self) -> &Component {
        self.components
            .iter()
            .find(|component| component.file_names.is_empty())
            .unwrap()
    }

    pub fn documentation(&self, name: &str) -> Option<MarkupContent> {
        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, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Component {
    pub file_names: Vec<SmolStr>,
    pub references: Vec<SmolStr>,
    pub commands: Vec<ComponentCommand>,
    pub environments: Vec<SmolStr>,
}

#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ComponentCommand {
    pub name: SmolStr,
    pub image: Option<String>,
    pub glyph: Option<SmolStr>,
    pub parameters: Vec<ComponentParameter>,
}

#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ComponentParameter(pub Vec<ComponentArgument>);

#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ComponentArgument {
    pub name: SmolStr,
    pub image: Option<String>,
}

#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ComponentMetadata {
    pub name: String,
    pub caption: Option<String>,
    pub description: Option<String>,
}

const JSON_GZ: &[u8] = include_bytes!("../../data/components.json.gz");

pub static COMPONENT_DATABASE: Lazy<ComponentDatabase> = 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()
});