html_languageservice/language_facts/
data_provider.rs

1use std::collections::HashMap;
2
3use lsp_types::{MarkupContent, MarkupKind};
4
5use crate::{
6    html_data::{Description, HTMLDataV1, IAttributeData, IReference, ITagData, IValueData},
7    utils::markup,
8};
9
10/// Built-in data provider that provides information for `HTMLDataManager`
11pub struct HTMLDataProvider {
12    id: String,
13    tags: Vec<ITagData>,
14    tag_map: HashMap<String, usize>,
15    global_attributes: Vec<IAttributeData>,
16    value_set_map: HashMap<String, Vec<IValueData>>,
17}
18
19/// To implement that the data provider can provide information to the `HTMLDataManager`
20pub trait IHTMLDataProvider: Send + Sync {
21    /// The ID of the data provider, which cannot be duplicated,
22    /// note that the ID of the built-in data provider is "html5"
23    fn get_id(&self) -> &str;
24    fn is_applicable(&self, language_id: &str) -> bool;
25    fn provide_tags(&self) -> &Vec<ITagData>;
26    fn provide_attributes(&self, tag: &str) -> Vec<&IAttributeData>;
27    fn provide_values(&self, tag: &str, attribute: &str) -> Vec<&IValueData>;
28}
29
30impl HTMLDataProvider {
31    pub fn new(id: String, custom_data: HTMLDataV1) -> HTMLDataProvider {
32        let mut tag_map = HashMap::new();
33        if let Some(tags) = &custom_data.tags {
34            for (i, tag) in tags.iter().enumerate() {
35                tag_map.insert(tag.name.clone(), i);
36            }
37        }
38
39        let mut value_set_map = HashMap::new();
40
41        if let Some(value_sets) = custom_data.value_sets {
42            for vs in value_sets {
43                value_set_map.insert(vs.name, vs.values);
44            }
45        }
46
47        HTMLDataProvider {
48            id,
49            tags: custom_data.tags.unwrap_or_default(),
50            tag_map,
51            global_attributes: custom_data.global_attributes.unwrap_or_default(),
52            value_set_map,
53        }
54    }
55}
56
57impl IHTMLDataProvider for HTMLDataProvider {
58    fn get_id(&self) -> &str {
59        &self.id
60    }
61
62    fn is_applicable(&self, _language_id: &str) -> bool {
63        true
64    }
65
66    fn provide_tags(&self) -> &Vec<ITagData> {
67        &self.tags
68    }
69
70    fn provide_attributes(&self, tag: &str) -> Vec<&IAttributeData> {
71        let mut attributes = vec![];
72
73        let tag_entry_index = self.tag_map.get(&tag.to_lowercase());
74        if let Some(tag_entry_index) = tag_entry_index {
75            let tag_entry = &self.tags[*tag_entry_index];
76            for attribute in &tag_entry.attributes {
77                attributes.push(attribute);
78            }
79        }
80        for attribute in &self.global_attributes {
81            attributes.push(&attribute);
82        }
83
84        attributes
85    }
86
87    fn provide_values(&self, tag: &str, attribute: &str) -> Vec<&IValueData> {
88        let mut values = vec![];
89
90        let attribute = attribute.to_lowercase();
91
92        let tag_entry = self.tag_map.get(&tag.to_lowercase());
93        if let Some(tag_entry_index) = tag_entry {
94            let tag_entry = &self.tags[*tag_entry_index];
95            for a in &tag_entry.attributes {
96                if a.name.to_lowercase() == attribute {
97                    if let Some(a_values) = &a.values {
98                        for value in a_values {
99                            values.push(value);
100                        }
101                    }
102                    if let Some(value_set) = &a.value_set {
103                        if let Some(set) = &self.value_set_map.get(value_set) {
104                            for v in *set {
105                                values.push(v);
106                            }
107                        }
108                    }
109                }
110            }
111        }
112        for a in &self.global_attributes {
113            if a.name.to_lowercase() == attribute {
114                if let Some(a_values) = &a.values {
115                    for value in a_values {
116                        values.push(value);
117                    }
118                }
119                if let Some(value_set) = &a.value_set {
120                    if let Some(set) = &self.value_set_map.get(value_set) {
121                        for v in *set {
122                            values.push(v);
123                        }
124                    }
125                }
126            }
127        }
128
129        values
130    }
131}
132
133/// Generate Documentation used in hover/complete From documentation and references
134pub fn generate_documentation(
135    item: GenerateDocumentationItem,
136    setting: GenerateDocumentationSetting,
137) -> Option<MarkupContent> {
138    let mut result = MarkupContent {
139        kind: if setting.does_support_markdown {
140            MarkupKind::Markdown
141        } else {
142            MarkupKind::PlainText
143        },
144        value: String::new(),
145    };
146
147    if item.description.is_some() && setting.documentation {
148        let normalized_description = markup::normalize_markup_content(item.description.unwrap());
149        result.value += &normalized_description.value;
150    }
151
152    if item.references.as_deref().is_some_and(|r| r.len() > 0) && setting.references {
153        if result.value.len() > 0 {
154            result.value += "\n\n";
155        }
156        let references = item.references.unwrap();
157        if setting.does_support_markdown {
158            result.value += &references
159                .iter()
160                .map(|r| format!("[{}]({})", r.name, r.url))
161                .collect::<Vec<String>>()
162                .join(" | ");
163        } else {
164            result.value += &references
165                .iter()
166                .map(|r| format!("{}: {}", r.name, r.url))
167                .collect::<Vec<String>>()
168                .join("\n");
169        }
170    }
171
172    if result.value.len() > 0 {
173        Some(result)
174    } else {
175        None
176    }
177}
178
179pub struct GenerateDocumentationItem {
180    pub description: Option<Description>,
181    pub references: Option<Vec<IReference>>,
182}
183
184pub struct GenerateDocumentationSetting {
185    pub documentation: bool,
186    pub references: bool,
187    pub does_support_markdown: bool,
188}