1use std::collections::HashMap;
2
3use tree_sitter::{Node, Point, Query, QueryCursor};
4
5use crate::{
6 htmx_tags::{get_tag, get_tags, Tag},
7 init_hx::LangType,
8 position::{CaptureDetails, Position, PositionDefinition, QueryType},
9 queries::{
10 HX_ANY_HTML, HX_GO_TAGS, HX_HTML, HX_JS_TAGS, HX_NAME, HX_PYTHON_TAGS, HX_RUST_TAGS,
11 HX_VALUE,
12 },
13};
14
15pub struct Queries {
18 pub html: HTMLQueries,
20 pub javascript: Query,
22 pub backend: Query,
24}
25
26impl Clone for Queries {
27 fn clone(&self) -> Self {
28 Self::default()
29 }
30}
31
32impl Default for Queries {
33 fn default() -> Self {
34 Self {
35 html: HTMLQueries::default(),
36 javascript: Query::new(tree_sitter_javascript::language(), HX_JS_TAGS).unwrap(),
37 backend: Query::new(tree_sitter_rust::language(), HX_RUST_TAGS).unwrap(),
38 }
39 }
40}
41
42impl Queries {
43 pub fn get(&self, query: HtmxQuery) -> &Query {
45 match query {
46 HtmxQuery::Html(html) => self.html.get(html),
47 HtmxQuery::JavaScript => &self.javascript,
48 HtmxQuery::Backend => &self.backend,
49 }
50 }
51
52 pub fn change_backend(&mut self, lang: &str) -> Option<()> {
54 let lang = match lang {
55 "python" => Some((tree_sitter_python::language(), HX_PYTHON_TAGS)),
56 "go" => Some((tree_sitter_go::language(), HX_GO_TAGS)),
57 _ => None,
58 };
59 if let Some(lang) = lang {
60 self.backend = Query::new(lang.0, lang.1).unwrap();
61 }
62 None
63 }
64}
65
66pub struct HTMLQueries {
71 lsp: Query,
72 name: Query,
73 value: Query,
74}
75
76impl Default for HTMLQueries {
77 fn default() -> Self {
78 let lsp = Query::new(tree_sitter_html::language(), HX_HTML).unwrap();
79 let name = Query::new(tree_sitter_html::language(), HX_NAME).unwrap();
80 let value = Query::new(tree_sitter_html::language(), HX_VALUE).unwrap();
81 Self { lsp, name, value }
82 }
83}
84
85impl Clone for HTMLQueries {
86 fn clone(&self) -> Self {
87 Self::default()
88 }
89}
90
91impl HTMLQueries {
92 pub fn get(&self, query: HTMLQuery) -> &Query {
93 match query {
94 HTMLQuery::Lsp => &self.lsp,
95 HTMLQuery::Name => &self.name,
96 HTMLQuery::Value => &self.value,
97 }
98 }
99
100 pub fn get_by_attribute_name(name: &str) -> Query {
102 Query::new(
103 tree_sitter_html::language(),
104 &HX_ANY_HTML.replace("NAME", name),
105 )
106 .unwrap()
107 }
108}
109
110pub enum HTMLQuery {
112 Lsp,
113 Name,
114 Value,
115}
116
117pub enum HtmxQuery {
119 Html(HTMLQuery),
120 JavaScript,
121 Backend,
122}
123
124impl TryFrom<LangType> for HtmxQuery {
125 type Error = ();
126
127 fn try_from(value: LangType) -> Result<Self, Self::Error> {
128 match value {
129 LangType::Template => Err(()),
130 LangType::JavaScript => Ok(HtmxQuery::JavaScript),
131 LangType::Backend => Ok(HtmxQuery::Backend),
132 }
133 }
134}
135
136pub fn query_props(
138 node: Node<'_>,
139 source: &str,
140 trigger_point: Point,
141 query: &Query,
142 all: bool,
143) -> HashMap<String, CaptureDetails> {
144 let mut cursor_qry = QueryCursor::new();
145 let capture_names = query.capture_names();
146 let matches = cursor_qry.matches(query, node, source.as_bytes());
147
148 let mut cnt = 0;
149 matches
150 .into_iter()
151 .flat_map(|m| {
152 m.captures
153 .iter()
154 .filter(|capture| all || capture.node.start_position() <= trigger_point)
155 })
156 .fold(HashMap::new(), |mut acc, capture| {
157 let key = capture_names[capture.index as usize].to_owned();
158 let value = if let Ok(capture_value) = capture.node.utf8_text(source.as_bytes()) {
159 capture_value.to_owned()
160 } else {
161 "".to_owned()
162 };
163 if key == "hx_comment" {
164 cnt += 1;
165 }
166 let key = {
167 if all {
168 format!("{}{cnt}", key)
169 } else {
170 key
171 }
172 };
173
174 acc.insert(
175 key,
176 CaptureDetails {
177 value,
178 end_position: capture.node.end_position(),
179 start_position: capture.node.start_position(),
180 },
181 );
182
183 acc
184 })
185}
186
187pub fn query_name(
189 element: Node<'_>,
190 source: &str,
191 trigger_point: Point,
192 query_type: &QueryType,
193 query: &Query,
194) -> Option<Position> {
195 let props = query_props(element, source, trigger_point, query, false);
196 let attr_name = props.get("attr_name")?;
197 if let Some(unfinished_tag) = props.get("unfinished_tag") {
198 if query_type == &QueryType::Hover {
199 let complete_match = props.get("complete_match");
200 if complete_match.is_some() && trigger_point <= attr_name.end_position {
201 return Some(Position::AttributeName(attr_name.value.to_string()));
202 }
203 return None;
204 } else if query_type == &QueryType::Completion
205 && trigger_point > unfinished_tag.end_position
206 {
207 return Some(Position::AttributeName(String::from("--")));
208 } else if let Some(_capture) = props.get("equal_error") {
209 if query_type == &QueryType::Completion {
210 return None;
211 }
212 }
213 }
214
215 Some(Position::AttributeName(attr_name.value.to_string()))
216}
217
218pub fn query_value(
220 element: Node<'_>,
221 source: &str,
222 trigger_point: Point,
223 query_type: &QueryType,
224 query: &Query,
225) -> Option<Position> {
226 let props = query_props(element, source, trigger_point, query, false);
227
228 let attr_name = props.get("attr_name")?;
229 let mut value = String::new();
230 let mut definition = None;
231 let hovered_name = trigger_point < attr_name.end_position && query_type == &QueryType::Hover;
232 if hovered_name {
233 return Some(Position::AttributeName(attr_name.value.to_string()));
234 } else if props.get("open_quote_error").is_some() || props.get("empty_attribute").is_some() {
235 if query_type == &QueryType::Completion {
236 if let Some(quoted) = props.get("quoted_attr_value") {
237 if trigger_point >= quoted.end_position {
238 return None;
239 }
240 }
241 }
242 return Some(Position::AttributeValue {
243 name: attr_name.value.to_owned(),
244 value: "".to_string(),
245 definition: None,
246 });
247 }
248
249 if let Some(error_char) = props.get("error_char") {
250 if error_char.value == "=" {
251 return None;
252 }
253 };
254
255 if let Some(capture) = props.get("non_empty_attribute") {
256 if trigger_point >= capture.end_position {
257 return None;
258 }
259 if query_type == &QueryType::Hover || query_type == &QueryType::Definition {
260 let mut start = 0;
261 let _ = props.get("attr_value").is_some_and(|s| {
262 value = s.value.to_string();
263 start = s.start_position.column;
264 true
265 });
266 if query_type == &QueryType::Definition {
267 definition = Some(PositionDefinition::new(start, trigger_point));
269 }
270 }
271 }
272
273 Some(Position::AttributeValue {
274 name: attr_name.value.to_owned(),
275 value,
276 definition,
277 })
278}
279
280pub fn query_tag(
282 element: Node<'_>,
283 source: &str,
284 trigger_point: Point,
285 _query_type: &QueryType,
286 query: &Query,
287 full: bool,
288) -> Vec<Tag> {
289 let comments = query_props(element, source, trigger_point, query, full);
290 let mut tags = vec![];
291 for comment in comments {
292 if let Some(mut tag) = get_tag(&comment.1.value) {
293 tag.start.row = comment.1.start_position.row;
294 tag.end.row = comment.1.start_position.row;
295 tags.push(tag);
296 }
297 }
298 tags
299}
300
301#[allow(clippy::too_many_arguments)]
303pub fn query_htmx_lsp(
304 element: Node<'_>,
305 source: &str,
306 trigger_point: Point,
307 _query_type: &QueryType,
308 query: &Query,
309 tag_name: &str,
310 references: &mut Vec<Tag>,
311 file: usize,
312) {
313 let lsp_names = query_props(element, source, trigger_point, query, true);
314 for capture in lsp_names {
315 if capture.0.starts_with("attr_value") {
316 let value = capture.1.value;
317 let tags = get_tags(
318 &value,
319 capture.1.start_position.column,
320 capture.1.start_position.row,
321 );
322 if let Some(tags) = tags {
323 let tag = tags.iter().find(|item| item.name == tag_name);
324 if let Some(tag) = tag {
325 let mut tag = tag.clone();
326 tag.file = file;
327 references.push(tag);
328 }
329 }
330 }
333 }
334}
335
336pub fn find_hx_lsp(
338 element: Node<'_>,
339 source: String,
340 trigger_point: Point,
341 query: &Query,
342) -> Option<CaptureDetails> {
343 let props = query_props(element, &source, trigger_point, query, false);
344 if props.get("attr_name").is_some() {
345 let value = props.get("attr_value")?;
346 return Some(value.clone());
347 }
348 None
349}