quickbms_lsp/server/
state.rs

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        // Find function definitions
64        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); // TODO: placeholder
73
74        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        // Return symbols
91        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        // Handle if the user is pointing at a keyword
119        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        // Handle if the user is pointing at a function call
150        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); // TODO: placeholder
161
162            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        // Handle if the user is pointing at a mention of a function from either a call or definition
198        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            // Find the definition
203            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); // TODO: placeholder
211
212            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            // Find the calls
227            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); // TODO: placeholder
235
236            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 the references we found
251            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}