Lines
0 %
Functions
use std::sync::Arc;
use std::sync::RwLock;
use conjure_cp_core::context::Context;
use conjure_cp_essence_parser::RecoverableParseError;
use conjure_cp_essence_parser::diagnostics::diagnostics_api::Diagnostic;
use conjure_cp_essence_parser::diagnostics::error_detection::collect_errors::error_to_diagnostic;
use conjure_cp_essence_parser::parse_essence_with_context_and_map;
use conjure_cp_essence_parser::util::get_tree;
use tower_lsp::{lsp_types::Diagnostic as LspDiagnostic, lsp_types::*};
use conjure_cp_essence_parser::diagnostics::diagnostics_api::Diagnostic as ParserDiagnostic;
use conjure_cp_essence_parser::diagnostics::diagnostics_api::Position as ParserPosition;
use conjure_cp_essence_parser::diagnostics::diagnostics_api::Range as ParserRange;
use conjure_cp_essence_parser::diagnostics::diagnostics_api::Severity as ParserSeverity;
use conjure_cp_essence_parser::diagnostics::diagnostics_api::get_diagnostics;
use tower_lsp::lsp_types::Position as LspPosition;
use tower_lsp::lsp_types::Range as LspRange;
use tree_sitter::Point;
use tree_sitter::Tree;
use crate::handlers::cache::CacheCont;
use crate::server::Backend;
impl Backend {
pub async fn handle_did_open(&self, params: DidOpenTextDocumentParams) {
//on open, check whether cache has existing entry, if not, add to cache
let uri = params.text_document.uri;
let text = params.text_document.text.clone();
let lsp_cache = &self.lsp_cache;
//basically look to see if in cache and if not in cache, fetch from source
//the closure? only runs on a cache miss
let cache_content = lsp_cache
.get_with(uri.clone(), async {
let (cst_tree, _) = get_tree(&text).unwrap();
let context = Arc::new(RwLock::new(Context::default()));
let mut errors: Vec<RecoverableParseError> = Vec::new();
let parsed = parse_essence_with_context_and_map(
&text,
context,
&mut errors,
Some(&cst_tree),
);
match parsed {
Ok((Some(ast_model), source_map)) => CacheCont {
sourcemap: Some(source_map),
ast: Some(ast_model),
errors,
cst: Some(cst_tree),
contents: text.clone(),
version: params.text_document.version,
},
Ok((None, source_map)) => CacheCont {
ast: None,
Err(fatal) => CacheCont {
sourcemap: None,
errors: vec![RecoverableParseError::new(fatal.to_string(), None)],
} //this inserts the cache created above into the cache
})
.await;
self.client
.log_message(MessageType::INFO, "Did open document")
//diagnostic stuff here
self.handle_diagnostics(&uri.clone(), cache_content).await;
}
pub async fn handle_did_save(&self, params: DidSaveTextDocumentParams) {
//if save, do not update existing entry,simply access from cache
if let Some(lsp_cont) = lsp_cache.get(&uri).await {
//CANNOT USE PRINTLNs AS THIS WILL BREAK CONNECTION WITH SERVER
//check versioning? might modify for dirty clean later cos i dont fw current situ
.log_message(MessageType::INFO, "Did save document")
self.handle_diagnostics(&uri, lsp_cont).await;
pub async fn handle_did_change(&self, params: DidChangeTextDocumentParams) {
//on change, take change and range of change
//modify existing document given uri and cache content to update the document version in cache
//TODO: check whether changes are purely whitespace
//if changes are purely whitespace, check whether they
.log_message(MessageType::INFO, "in document change")
if let Some(change) = params.content_changes.first()
&& let Some(cache_conts) = lsp_cache.get(&uri).await
{
let mut new_text = cache_conts.contents.clone();
if let Some(lsp_range) = change.range {
//convert range for string conversion here
let start_byte = position_to_byte(&cache_conts.contents, lsp_range.start);
let end_byte = position_to_byte(&cache_conts.contents, lsp_range.end);
new_text.replace_range(start_byte..end_byte, &change.text);
} else {
new_text = change.text.clone();
let new_tree: Tree = if let Some(lsp_range) = change.range {
let old_end_byte = position_to_byte(&cache_conts.contents, lsp_range.end);
let new_end_byte = start_byte + change.text.len();
let start_position = position_to_treesitter_point(lsp_range.start);
let old_end_position = position_to_treesitter_point(lsp_range.end);
let new_end_position = calculate_new_end_position(&change.text, lsp_range.start);
.log_message(MessageType::INFO, "before edit")
if let Some(ref mut old_cst) = cache_conts.cst.clone() {
old_cst.edit(&tree_sitter::InputEdit {
start_byte,
old_end_byte,
new_end_byte,
start_position,
old_end_position,
new_end_position,
});
// parse the new text with the edited tree as a starting point for incremental parsing
// TODO: handle _FRAGMENT_EXPRESSION like get_tree does
// maybe make separate helper for that or something
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&tree_sitter_essence::LANGUAGE.into())
.unwrap();
tree_sitter::Parser::parse(&mut parser, &new_text, Some(old_cst)).unwrap()
// if cst was None due to a failure to parse,
// we should re-parse the entire new text instead of trying to edit a non-existent tree
// there could be a better way to handle this, but for now this is a safe fallback
get_tree(&new_text).unwrap().0
};
.log_message(MessageType::INFO, new_text.clone())
&new_text,
Some(&new_tree),
let new_cache_conts = match parsed {
Ok((Some(ast_model), source_map)) => {
.log_message(MessageType::LOG, "THIS ONE INSTEAD")
CacheCont {
cst: Some(new_tree),
contents: new_text.clone(),
Ok((None, source_map)) => {
.log_message(MessageType::LOG, "jshdhshshshhs")
lsp_cache.insert(uri.clone(), new_cache_conts.clone()).await;
self.handle_diagnostics(&uri, new_cache_conts).await;
pub async fn handle_diagnostics(&self, uri: &Url, cache_conts: CacheCont) {
// Get syntactic diagnostics from CST
let syntactic_diagnostics = if let Some(ref cst) = cache_conts.cst {
get_diagnostics(&cache_conts.contents, cst)
Vec::new()
// Get semantic diagnostics from errors
let semantic_diagnostics: Vec<Diagnostic> = cache_conts
.errors
.into_iter()
.map(|err| error_to_diagnostic(&err))
.collect();
// Combine all diagnostics
let mut diagnostics = syntactic_diagnostics;
diagnostics.extend(semantic_diagnostics);
// Convert to LSP format
let lsp_diagnostics = convert_diagnostics(diagnostics);
// Publish diagnostics - this should be the ONLY call
.publish_diagnostics(uri.clone(), lsp_diagnostics, None)
// convert diagnostics from cp-essence-parser to LSP diagnostics
pub fn convert_diagnostics(diagnostics: Vec<ParserDiagnostic>) -> Vec<LspDiagnostic> {
// map each ParserDiagnostic to LspDiagnostic
diagnostics
.map(|diag| {
LspDiagnostic {
range: parser_to_lsp_range(diag.range),
severity: match diag.severity {
ParserSeverity::Error => Some(tower_lsp::lsp_types::DiagnosticSeverity::ERROR),
ParserSeverity::Warn => Some(tower_lsp::lsp_types::DiagnosticSeverity::WARNING),
ParserSeverity::Info => {
Some(tower_lsp::lsp_types::DiagnosticSeverity::INFORMATION)
ParserSeverity::Hint => Some(tower_lsp::lsp_types::DiagnosticSeverity::HINT),
code: None, // for now
code_description: None, // also for now
source: Some(diag.source.to_string()),
message: diag.message,
related_information: None,
tags: None,
data: None,
.collect()
// playing that position converts properly
pub fn parser_to_lsp_range(range: ParserRange) -> LspRange {
LspRange {
start: parser_to_lsp_position(range.start),
end: parser_to_lsp_position(range.end),
pub fn parser_to_lsp_position(position: ParserPosition) -> LspPosition {
LspPosition {
line: position.line,
character: position.character,
//need to convert from character and line to byte value in a file
pub fn position_to_byte(text: &str, position: Position) -> usize {
//as_bytes converts a string into bytes which I could do with text but the issue is finding
//the position from that point???
let mut byte_offset = 0;
//go through every line
for (line_idx, line) in text.lines().enumerate() {
if line_idx < position.line as usize {
byte_offset += line.len() + 1; // +1 for newline
//can directly convert character as it's a byte offset alr
byte_offset += position.character as usize;
break;
byte_offset
//need to convert from character and line to row and line
//this allows for incremental editing of treesitter
fn position_to_treesitter_point(position: Position) -> Point {
Point::new(position.line as usize, position.character as usize)
fn calculate_new_end_position(text: &str, start: Position) -> Point {
let mut row = start.line as usize;
let mut column = start.character as usize;
for ch in text.chars() {
if ch == '\n' {
row += 1;
column = 0;
column += 1;
Point::new(row, column)