Skip to main content

lsp_max_ast/
lib.rs

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