html_languageservice/language_facts/
data_provider.rs

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