1use std::collections::HashMap;
2
3use tree_sitter::{Query, QueryCursor, Tree};
4
5use lsp_types::{
6 DidChangeTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbolParams,
7 DocumentSymbolResponse, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents,
8 HoverParams, Location, MarkupContent, MarkupKind, ReferenceParams, SymbolInformation,
9 SymbolKind, Url,
10};
11
12use crate::grammar::parsing::{get_quickbms_language, parse, PointLike, RangeLike};
13
14pub struct ServerState {
15 files: HashMap<Url, (String, Tree)>,
16 keyword_docs: HashMap<String, String>,
17}
18
19impl ServerState {
20 pub fn new() -> ServerState {
21 ServerState {
22 files: HashMap::new(),
23 keyword_docs: get_keyword_docs(),
24 }
25 }
26
27 pub fn did_open(&mut self, request: &DidOpenTextDocumentParams) {
28 let text_document = &request.text_document;
29
30 let tree = parse(&text_document.text);
31
32 match tree {
33 Some(t) => {
34 self.files
35 .insert(text_document.uri.clone(), (text_document.text.clone(), t));
36 }
37 None => eprintln!("Parsing failed due to timeout or cancellation flag."),
38 }
39 }
40
41 pub fn did_change(&mut self, request: &DidChangeTextDocumentParams) {
42 let text_document = &request.text_document;
43
44 let text = request.content_changes[0].text.clone();
45 let tree = parse(&text);
46
47 match tree {
48 Some(t) => {
49 self.files.insert(text_document.uri.clone(), (text, t));
50 }
51 None => eprintln!("Parsing failed due to timeout or cancellation flag."),
52 }
53 }
54
55 pub fn document_symbol(
56 &self,
57 request: &DocumentSymbolParams,
58 ) -> Option<DocumentSymbolResponse> {
59 let url = &request.text_document.uri;
60
61 let (source, tree) = self.files.get(url).unwrap();
62
63 let mut functions = vec![];
65 let query = Query::new(
66 get_quickbms_language(),
67 r#"(function_declaration) @declaration"#,
68 )
69 .unwrap();
70
71 let mut query_cursor = QueryCursor::new();
72 let text_callback = |node: tree_sitter::Node| format!("{:?}", node); let matches = query_cursor.captures(&query, tree.root_node(), text_callback);
75 for (m, _) in matches {
76 let function_declaration = m.captures[0].node;
77
78 let func_name = function_declaration
79 .child_by_field_name("name")
80 .unwrap()
81 .utf8_text(source.as_bytes())
82 .unwrap()
83 .to_string();
84
85 let location = function_declaration.range().to_location(&url);
86
87 functions.push((func_name, location));
88 }
89
90 let symbols: Vec<SymbolInformation> = functions
92 .iter()
93 .map(|(name, location)| SymbolInformation {
94 name: name.clone(),
95 kind: SymbolKind::Function,
96 tags: None,
97 deprecated: None,
98 location: location.clone(),
99 container_name: None,
100 })
101 .collect();
102
103 Some(DocumentSymbolResponse::Flat(symbols))
104 }
105
106 pub fn hover(&self, request: &HoverParams) -> Option<Hover> {
107 let text_document_position_params = &request.text_document_position_params;
108 let url = &text_document_position_params.text_document.uri;
109 let point = text_document_position_params.position.to_point();
110
111 let (_source, tree) = self.files.get(url).unwrap();
112
113 let node = tree
114 .root_node()
115 .named_descendant_for_point_range(point, point)
116 .unwrap();
117
118 if let Some(docs) = self.keyword_docs.get(node.kind()) {
120 return Some(Hover {
121 contents: HoverContents::Markup(MarkupContent {
122 kind: MarkupKind::PlainText,
123 value: docs.to_string(),
124 }),
125 range: None,
126 });
127 }
128
129 None
130 }
131
132 pub fn goto_definition(
133 &self,
134 request: &GotoDefinitionParams,
135 ) -> Option<GotoDefinitionResponse> {
136 let text_document_position_params = &request.text_document_position_params;
137 let url = &text_document_position_params.text_document.uri;
138 let point = text_document_position_params.position.to_point();
139
140 let (source, tree) = self.files.get(url).unwrap();
141
142 let node = tree
143 .root_node()
144 .named_descendant_for_point_range(point, point)
145 .unwrap();
146
147 let parent = node.parent().unwrap();
148
149 if parent.kind() == "function_call_statement" {
151 let function_name_lc = node.utf8_text(source.as_bytes()).unwrap().to_lowercase();
152
153 let query = Query::new(
154 get_quickbms_language(),
155 r#"(function_declaration) @declaration"#,
156 )
157 .unwrap();
158
159 let mut query_cursor = QueryCursor::new();
160 let text_callback = |node: tree_sitter::Node| format!("{:?}", node); let matches = query_cursor.captures(&query, tree.root_node(), text_callback);
163 for (m, _) in matches {
164 let function_declaration = m.captures[0].node;
165
166 let decl_func_name_lc = function_declaration
167 .child_by_field_name("name")
168 .unwrap()
169 .utf8_text(source.as_bytes())
170 .unwrap()
171 .to_lowercase();
172 if decl_func_name_lc == function_name_lc {
173 return Some(GotoDefinitionResponse::Scalar(
174 function_declaration.range().to_location(&url),
175 ));
176 }
177 }
178 }
179
180 None
181 }
182
183 pub fn goto_references(&self, request: &ReferenceParams) -> Option<Vec<Location>> {
184 let text_document_position = &request.text_document_position;
185 let url = &text_document_position.text_document.uri;
186 let point = text_document_position.position.to_point();
187
188 let (source, tree) = self.files.get(url).unwrap();
189
190 let node = tree
191 .root_node()
192 .named_descendant_for_point_range(point, point)
193 .unwrap();
194
195 let parent = node.parent().unwrap();
196
197 if parent.kind() == "function_call_statement" || parent.kind() == "function_declaration" {
199 let function_name_lc = node.utf8_text(source.as_bytes()).unwrap().to_lowercase();
200 let mut function_references = vec![];
201
202 let query = Query::new(
204 get_quickbms_language(),
205 r#"(function_declaration) @declaration"#,
206 )
207 .unwrap();
208
209 let mut query_cursor = QueryCursor::new();
210 let text_callback = |node: tree_sitter::Node| format!("{:?}", node); let matches = query_cursor.captures(&query, tree.root_node(), text_callback);
213 for (m, _) in matches {
214 let function_declaration = m.captures[0].node;
215
216 let decl_name = function_declaration.child_by_field_name("name").unwrap();
217 let decl_func_name_lc = decl_name
218 .utf8_text(source.as_bytes())
219 .unwrap()
220 .to_lowercase();
221 if decl_func_name_lc == function_name_lc {
222 function_references.push(decl_name.range().to_location(&url));
223 }
224 }
225
226 let query = Query::new(
228 get_quickbms_language(),
229 r#"(function_call_statement) @call"#,
230 )
231 .unwrap();
232
233 let mut query_cursor = QueryCursor::new();
234 let text_callback = |node: tree_sitter::Node| format!("{:?}", node); let matches = query_cursor.captures(&query, tree.root_node(), text_callback);
237 for (m, _) in matches {
238 let function_call = m.captures[0].node;
239
240 let call_name = function_call.child_by_field_name("name").unwrap();
241 let call_func_name_lc = call_name
242 .utf8_text(source.as_bytes())
243 .unwrap()
244 .to_lowercase();
245 if call_func_name_lc == function_name_lc {
246 function_references.push(call_name.range().to_location(&url));
247 }
248 }
249
250 return Some(function_references);
252 }
253
254 None
255 }
256}
257
258pub fn get_keyword_docs() -> HashMap<String, String> {
259 [
260 (
261 "print".to_string(),
262 include_str!("keyword_docs/print.txt").to_string(),
263 ),
264 (
265 "set".to_string(),
266 include_str!("keyword_docs/set.txt").to_string(),
267 ),
268 (
269 "startfunction".to_string(),
270 include_str!("keyword_docs/functions.txt").to_string(),
271 ),
272 (
273 "endfunction".to_string(),
274 include_str!("keyword_docs/functions.txt").to_string(),
275 ),
276 (
277 "callfunction".to_string(),
278 include_str!("keyword_docs/functions.txt").to_string(),
279 ),
280 (
281 "endian".to_string(),
282 include_str!("keyword_docs/endian.txt").to_string(),
283 ),
284 (
285 "idstring".to_string(),
286 include_str!("keyword_docs/idstring.txt").to_string(),
287 ),
288 (
289 "if".to_string(),
290 include_str!("keyword_docs/if.txt").to_string(),
291 ),
292 (
293 "elif".to_string(),
294 include_str!("keyword_docs/if.txt").to_string(),
295 ),
296 (
297 "else".to_string(),
298 include_str!("keyword_docs/if.txt").to_string(),
299 ),
300 (
301 "endif".to_string(),
302 include_str!("keyword_docs/if.txt").to_string(),
303 ),
304 ]
305 .iter()
306 .cloned()
307 .collect()
308}