1
use std::sync::Arc;
2
use std::sync::RwLock;
3

            
4
use conjure_cp_core::context::Context;
5
use conjure_cp_essence_parser::RecoverableParseError;
6
use conjure_cp_essence_parser::diagnostics::diagnostics_api::Diagnostic;
7
use conjure_cp_essence_parser::diagnostics::error_detection::collect_errors::error_to_diagnostic;
8
use conjure_cp_essence_parser::parse_essence_with_context_and_map;
9
use conjure_cp_essence_parser::util::get_tree;
10
use tower_lsp::{lsp_types::Diagnostic as LspDiagnostic, lsp_types::*};
11

            
12
use conjure_cp_essence_parser::diagnostics::diagnostics_api::Diagnostic as ParserDiagnostic;
13
use conjure_cp_essence_parser::diagnostics::diagnostics_api::Position as ParserPosition;
14
use conjure_cp_essence_parser::diagnostics::diagnostics_api::Range as ParserRange;
15
use conjure_cp_essence_parser::diagnostics::diagnostics_api::Severity as ParserSeverity;
16
use conjure_cp_essence_parser::diagnostics::diagnostics_api::get_diagnostics;
17
use tower_lsp::lsp_types::Position as LspPosition;
18
use tower_lsp::lsp_types::Range as LspRange;
19

            
20
use tree_sitter::Point;
21
use tree_sitter::Tree;
22

            
23
use crate::handlers::cache::CacheCont;
24
use crate::server::Backend;
25

            
26
impl Backend {
27
    pub async fn handle_did_open(&self, params: DidOpenTextDocumentParams) {
28
        //on open, check whether cache has existing entry, if not, add to cache
29

            
30
        let uri = params.text_document.uri;
31
        let text = params.text_document.text.clone();
32

            
33
        let lsp_cache = &self.lsp_cache;
34
        //basically look to see if in cache and if not in cache, fetch from source
35
        //the closure? only runs on a cache miss
36
        let cache_content = lsp_cache
37
            .get_with(uri.clone(), async {
38
                let (cst_tree, _) = get_tree(&text).unwrap();
39

            
40
                let context = Arc::new(RwLock::new(Context::default()));
41
                let mut errors: Vec<RecoverableParseError> = Vec::new();
42

            
43
                let parsed = parse_essence_with_context_and_map(
44
                    &text,
45
                    context,
46
                    &mut errors,
47
                    Some(&cst_tree),
48
                );
49

            
50
                match parsed {
51
                    Ok((Some(ast_model), source_map)) => CacheCont {
52
                        sourcemap: Some(source_map),
53
                        ast: Some(ast_model),
54
                        errors,
55
                        cst: Some(cst_tree),
56
                        contents: text.clone(),
57
                        version: params.text_document.version,
58
                    },
59
                    Ok((None, source_map)) => CacheCont {
60
                        sourcemap: Some(source_map),
61
                        ast: None,
62
                        errors,
63
                        cst: Some(cst_tree),
64
                        contents: text.clone(),
65
                        version: params.text_document.version,
66
                    },
67
                    Err(fatal) => CacheCont {
68
                        sourcemap: None,
69
                        ast: None,
70
                        errors: vec![RecoverableParseError::new(fatal.to_string(), None)],
71
                        cst: Some(cst_tree),
72
                        contents: text.clone(),
73
                        version: params.text_document.version,
74
                    },
75
                } //this inserts the cache created above into the cache
76
            })
77
            .await;
78

            
79
        self.client
80
            .log_message(MessageType::INFO, "Did open document")
81
            .await;
82

            
83
        //diagnostic stuff here
84
        self.handle_diagnostics(&uri.clone(), cache_content).await;
85
    }
86
    pub async fn handle_did_save(&self, params: DidSaveTextDocumentParams) {
87
        //if save, do not update existing entry,simply access from cache
88
        let uri = params.text_document.uri;
89
        let lsp_cache = &self.lsp_cache;
90

            
91
        if let Some(lsp_cont) = lsp_cache.get(&uri).await {
92
            //CANNOT USE PRINTLNs AS THIS WILL BREAK CONNECTION WITH SERVER
93

            
94
            //check versioning? might modify for dirty clean later cos i dont fw current situ
95

            
96
            self.client
97
                .log_message(MessageType::INFO, "Did save document")
98
                .await;
99
            self.handle_diagnostics(&uri, lsp_cont).await;
100
        }
101
    }
102
    pub async fn handle_did_change(&self, params: DidChangeTextDocumentParams) {
103
        //on change, take change and range of change
104
        //modify existing document given uri and cache content to update the document version in cache
105
        //TODO: check whether changes are purely whitespace
106
        //if changes are purely whitespace, check whether they
107

            
108
        let uri = params.text_document.uri;
109
        let lsp_cache = &self.lsp_cache;
110

            
111
        self.client
112
            .log_message(MessageType::INFO, "in document change")
113
            .await;
114

            
115
        if let Some(change) = params.content_changes.first()
116
            && let Some(cache_conts) = lsp_cache.get(&uri).await
117
        {
118
            let mut new_text = cache_conts.contents.clone();
119
            if let Some(lsp_range) = change.range {
120
                //convert range for string conversion here
121

            
122
                let start_byte = position_to_byte(&cache_conts.contents, lsp_range.start);
123
                let end_byte = position_to_byte(&cache_conts.contents, lsp_range.end);
124
                new_text.replace_range(start_byte..end_byte, &change.text);
125
            } else {
126
                new_text = change.text.clone();
127
            }
128

            
129
            let new_tree: Tree = if let Some(lsp_range) = change.range {
130
                let start_byte = position_to_byte(&cache_conts.contents, lsp_range.start);
131
                let old_end_byte = position_to_byte(&cache_conts.contents, lsp_range.end);
132
                let new_end_byte = start_byte + change.text.len();
133

            
134
                let start_position = position_to_treesitter_point(lsp_range.start);
135
                let old_end_position = position_to_treesitter_point(lsp_range.end);
136
                let new_end_position = calculate_new_end_position(&change.text, lsp_range.start);
137

            
138
                self.client
139
                    .log_message(MessageType::INFO, "before edit")
140
                    .await;
141
                if let Some(ref mut old_cst) = cache_conts.cst.clone() {
142
                    old_cst.edit(&tree_sitter::InputEdit {
143
                        start_byte,
144
                        old_end_byte,
145
                        new_end_byte,
146
                        start_position,
147
                        old_end_position,
148
                        new_end_position,
149
                    });
150

            
151
                    // parse the new text with the edited tree as a starting point for incremental parsing
152
                    // TODO: handle _FRAGMENT_EXPRESSION like get_tree does
153
                    // maybe make separate helper for that or something
154
                    let mut parser = tree_sitter::Parser::new();
155
                    parser
156
                        .set_language(&tree_sitter_essence::LANGUAGE.into())
157
                        .unwrap();
158
                    tree_sitter::Parser::parse(&mut parser, &new_text, Some(old_cst)).unwrap()
159
                } else {
160
                    // if cst was None due to a failure to parse,
161
                    // we should re-parse the entire new text instead of trying to edit a non-existent tree
162
                    // there could be a better way to handle this, but for now this is a safe fallback
163
                    get_tree(&new_text).unwrap().0
164
                }
165
            } else {
166
                get_tree(&new_text).unwrap().0
167
            };
168

            
169
            let context = Arc::new(RwLock::new(Context::default()));
170
            let mut errors: Vec<RecoverableParseError> = Vec::new();
171

            
172
            self.client
173
                .log_message(MessageType::INFO, new_text.clone())
174
                .await;
175

            
176
            let parsed = parse_essence_with_context_and_map(
177
                &new_text,
178
                context,
179
                &mut errors,
180
                Some(&new_tree),
181
            );
182

            
183
            let new_cache_conts = match parsed {
184
                Ok((Some(ast_model), source_map)) => {
185
                    self.client
186
                        .log_message(MessageType::LOG, "THIS ONE INSTEAD")
187
                        .await;
188
                    CacheCont {
189
                        sourcemap: Some(source_map),
190
                        ast: Some(ast_model),
191
                        errors,
192
                        cst: Some(new_tree),
193
                        contents: new_text.clone(),
194
                        version: params.text_document.version,
195
                    }
196
                }
197
                Ok((None, source_map)) => {
198
                    self.client
199
                        .log_message(MessageType::LOG, "jshdhshshshhs")
200
                        .await;
201
                    CacheCont {
202
                        sourcemap: Some(source_map),
203
                        ast: None,
204
                        errors,
205
                        cst: Some(new_tree),
206
                        contents: new_text.clone(),
207
                        version: params.text_document.version,
208
                    }
209
                }
210
                Err(fatal) => CacheCont {
211
                    sourcemap: None,
212
                    ast: None,
213
                    errors: vec![RecoverableParseError::new(fatal.to_string(), None)],
214
                    cst: Some(new_tree),
215
                    contents: new_text.clone(),
216
                    version: params.text_document.version,
217
                },
218
            };
219

            
220
            lsp_cache.insert(uri.clone(), new_cache_conts.clone()).await;
221

            
222
            self.handle_diagnostics(&uri, new_cache_conts).await;
223
        }
224
    }
225

            
226
    pub async fn handle_diagnostics(&self, uri: &Url, cache_conts: CacheCont) {
227
        // Get syntactic diagnostics from CST
228
        let syntactic_diagnostics = if let Some(ref cst) = cache_conts.cst {
229
            get_diagnostics(&cache_conts.contents, cst)
230
        } else {
231
            Vec::new()
232
        };
233

            
234
        // Get semantic diagnostics from errors
235
        let semantic_diagnostics: Vec<Diagnostic> = cache_conts
236
            .errors
237
            .into_iter()
238
            .map(|err| error_to_diagnostic(&err))
239
            .collect();
240

            
241
        // Combine all diagnostics
242
        let mut diagnostics = syntactic_diagnostics;
243
        diagnostics.extend(semantic_diagnostics);
244

            
245
        // Convert to LSP format
246
        let lsp_diagnostics = convert_diagnostics(diagnostics);
247

            
248
        // Publish diagnostics - this should be the ONLY call
249
        self.client
250
            .publish_diagnostics(uri.clone(), lsp_diagnostics, None)
251
            .await;
252
    }
253
}
254
// convert diagnostics from cp-essence-parser to LSP diagnostics
255
pub fn convert_diagnostics(diagnostics: Vec<ParserDiagnostic>) -> Vec<LspDiagnostic> {
256
    // map each ParserDiagnostic to LspDiagnostic
257
    diagnostics
258
        .into_iter()
259
        .map(|diag| {
260
            LspDiagnostic {
261
                range: parser_to_lsp_range(diag.range),
262
                severity: match diag.severity {
263
                    ParserSeverity::Error => Some(tower_lsp::lsp_types::DiagnosticSeverity::ERROR),
264
                    ParserSeverity::Warn => Some(tower_lsp::lsp_types::DiagnosticSeverity::WARNING),
265
                    ParserSeverity::Info => {
266
                        Some(tower_lsp::lsp_types::DiagnosticSeverity::INFORMATION)
267
                    }
268
                    ParserSeverity::Hint => Some(tower_lsp::lsp_types::DiagnosticSeverity::HINT),
269
                },
270
                code: None,             // for now
271
                code_description: None, // also for now
272
                source: Some(diag.source.to_string()),
273
                message: diag.message,
274
                related_information: None,
275
                tags: None,
276
                data: None,
277
            }
278
        })
279
        .collect()
280
}
281

            
282
// playing that position converts properly
283
pub fn parser_to_lsp_range(range: ParserRange) -> LspRange {
284
    LspRange {
285
        start: parser_to_lsp_position(range.start),
286
        end: parser_to_lsp_position(range.end),
287
    }
288
}
289

            
290
pub fn parser_to_lsp_position(position: ParserPosition) -> LspPosition {
291
    LspPosition {
292
        line: position.line,
293
        character: position.character,
294
    }
295
}
296

            
297
//need to convert from character and line to byte value in a file
298
pub fn position_to_byte(text: &str, position: Position) -> usize {
299
    //as_bytes converts a string into bytes which I could do with text but the issue is finding
300
    //the position from that point???
301
    let mut byte_offset = 0;
302
    //go through every line
303
    for (line_idx, line) in text.lines().enumerate() {
304
        if line_idx < position.line as usize {
305
            byte_offset += line.len() + 1; // +1 for newline
306
        } else {
307
            //can directly convert character as it's a byte offset alr
308
            byte_offset += position.character as usize;
309
            break;
310
        }
311
    }
312
    byte_offset
313
}
314

            
315
//need to convert from character and line to row and line
316
//this allows for incremental editing of treesitter
317
fn position_to_treesitter_point(position: Position) -> Point {
318
    Point::new(position.line as usize, position.character as usize)
319
}
320

            
321
fn calculate_new_end_position(text: &str, start: Position) -> Point {
322
    let mut row = start.line as usize;
323
    let mut column = start.character as usize;
324
    for ch in text.chars() {
325
        if ch == '\n' {
326
            row += 1;
327
            column = 0;
328
        } else {
329
            column += 1;
330
        }
331
    }
332

            
333
    Point::new(row, column)
334
}