html_languageservice/language_facts/
data_provider.rs1use 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#[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
23pub trait IHTMLDataProvider: Send + Sync {
25 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
176pub 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}