Skip to main content

lsp_max_ast/
lib.rs

1use dashmap::DashMap;
2use lsp_max_ast_core::document::Document;
3use lsp_types_max::{
4    Diagnostic, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
5    DocumentUri,
6};
7use parking_lot::Mutex;
8use tree_sitter::Parser;
9
10/// The `AutoLspAdapter` acts as the formal bridge between the `lsp-max`
11/// execution engine and the incremental AST generation from `lsp-max-ast-core`.
12///
13/// It strictly adheres to the architectural mandate by cleanly separating the
14/// transport/JSON-RPC layer (`lsp-max`) from the formal grammar
15/// parsing layer (`lsp-max-ast-core`).
16pub struct AutoLspAdapter {
17    /// Incremental text and syntax tree store.
18    documents: DashMap<DocumentUri, Mutex<Document>>,
19}
20
21impl Default for AutoLspAdapter {
22    fn default() -> Self {
23        Self::new_default()
24    }
25}
26
27impl AutoLspAdapter {
28    /// Creates a new `AutoLspAdapter`.
29    pub fn new_default() -> Self {
30        Self {
31            documents: DashMap::new(),
32        }
33    }
34
35    /// Handles a document open event, injecting the initial state into the incremental database.
36    pub fn handle_did_open(
37        &self,
38        params: DidOpenTextDocumentParams,
39        language: tree_sitter::Language,
40    ) {
41        let uri = params.text_document.uri;
42        let text = params.text_document.text;
43
44        let mut parser = Parser::new();
45        parser
46            .set_language(&language)
47            .expect("Error loading grammar");
48
49        if let Some(tree) = parser.parse(&text, None) {
50            let doc = Document::new(text, tree, None);
51            self.documents.insert(uri, Mutex::new(doc));
52        }
53    }
54
55    /// Handles a document change event, applying incremental diffs to the AST database.
56    pub fn handle_did_change(
57        &self,
58        params: DidChangeTextDocumentParams,
59        language: tree_sitter::Language,
60    ) {
61        let uri = params.text_document.uri;
62        if let Some(doc_ref) = self.documents.get(&uri) {
63            let mut doc = doc_ref.lock();
64            let mut parser = Parser::new();
65            parser
66                .set_language(&language)
67                .expect("Error loading grammar");
68
69            let _ = doc.update(&mut parser, &params.content_changes);
70        }
71    }
72
73    /// Handles a document close event, cleaning up memory.
74    pub fn handle_did_close(&self, params: DidCloseTextDocumentParams) {
75        self.documents.remove(&params.text_document.uri);
76    }
77
78    /// Analyzes the document and returns a set of diagnostics derived from the AST.
79    pub fn pull_diagnostics(&self, uri: &DocumentUri) -> Vec<Diagnostic> {
80        let mut diags = Vec::new();
81        if let Some(doc_ref) = self.documents.get(uri) {
82            let doc = doc_ref.lock();
83
84            // Perform a genuine depth-first traversal of the syntax tree
85            // to extract structural and syntax errors identified by Tree-sitter.
86            let mut queue = vec![doc.tree.root_node()];
87            while let Some(node) = queue.pop() {
88                if node.is_error() || node.is_missing() {
89                    let range = doc.denormalize_range(&node.range()).unwrap_or_default();
90                    diags.push(Diagnostic {
91                        range,
92                        severity: Some(lsp_types_max::DiagnosticSeverity::ERROR),
93                        code: Some(lsp_types_max::NumberOrString::String(
94                            "AST_ERROR".to_string(),
95                        )),
96                        source: Some("lsp-max-ast".to_string()),
97                        message: "Syntax error detected by formal parser.".to_string(),
98                        ..Default::default()
99                    });
100                }
101
102                // Enqueue children
103                for i in 0..node.child_count() as u32 {
104                    if let Some(child) = node.child(i) {
105                        queue.push(child);
106                    }
107                }
108            }
109        }
110        diags
111    }
112
113    /// Provides read-access to a managed document for semantic token and symbol generation.
114    pub fn get_document<F, R>(&self, uri: &DocumentUri, f: F) -> Option<R>
115    where
116        F: FnOnce(&Document) -> R,
117    {
118        self.documents.get(uri).map(|doc_ref| f(&doc_ref.lock()))
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_adapter_initialization() {
128        let adapter = AutoLspAdapter::new_default();
129        assert!(adapter.documents.is_empty());
130    }
131}