#![feature(char_indices_offset)] mod document; mod compiler; mod parser; mod elements; mod lua; mod cache; mod lsp; use std::collections::HashMap; use std::rc::Rc; use std::sync::Arc; use dashmap::DashMap; use document::variable::Variable; use tower_lsp::jsonrpc::Result; use tower_lsp::lsp_types::*; use tower_lsp::{Client, LanguageServer, LspService, Server}; #[derive(Debug)] struct Backend { client: Client, document_map: DashMap, //variables: DashMap>>, } #[derive(Debug)] struct TextDocumentItem { uri: Url, text: String, version: i32, } impl Backend { async fn on_change(&self, params: TextDocumentItem) { self.document_map .insert(params.uri.to_string(), params.text.clone()); let ParserResult { ast, parse_errors, semantic_tokens, } = parse(¶ms.text); let diagnostics = parse_errors .into_iter() .filter_map(|item| { let (message, span) = match item.reason() { chumsky::error::SimpleReason::Unclosed { span, delimiter } => { (format!("Unclosed delimiter {}", delimiter), span.clone()) } chumsky::error::SimpleReason::Unexpected => ( format!( "{}, expected {}", if item.found().is_some() { "Unexpected token in input" } else { "Unexpected end of input" }, if item.expected().len() == 0 { "something else".to_string() } else { item.expected() .map(|expected| match expected { Some(expected) => expected.to_string(), None => "end of input".to_string(), }) .collect::>() .join(", ") } ), item.span(), ), chumsky::error::SimpleReason::Custom(msg) => (msg.to_string(), item.span()), }; || -> Option { // let start_line = rope.try_char_to_line(span.start)?; // let first_char = rope.try_line_to_char(start_line)?; // let start_column = span.start - first_char; let start_position = offset_to_position(span.start, &rope)?; let end_position = offset_to_position(span.end, &rope)?; // let end_line = rope.try_char_to_line(span.end)?; // let first_char = rope.try_line_to_char(end_line)?; // let end_column = span.end - first_char; Some(Diagnostic::new_simple( Range::new(start_position, end_position), message, )) }() }) .collect::>(); self.client .publish_diagnostics(params.uri.clone(), diagnostics, Some(params.version)) .await; if let Some(ast) = ast { self.ast_map.insert(params.uri.to_string(), ast); } // self.client // .log_message(MessageType::INFO, &format!("{:?}", semantic_tokens)) // .await; self.semantic_token_map .insert(params.uri.to_string(), semantic_tokens); } } #[tower_lsp::async_trait] impl LanguageServer for Backend { async fn initialize(&self, _: InitializeParams) -> Result { Ok(InitializeResult { server_info: None, capabilities: ServerCapabilities { text_document_sync: Some(TextDocumentSyncCapability::Kind( TextDocumentSyncKind::FULL, )), completion_provider: Some(CompletionOptions { resolve_provider: Some(false), trigger_characters: Some(vec!["%".to_string()]), work_done_progress_options: Default::default(), all_commit_characters: None, completion_item: None, }), semantic_tokens_provider: Some( SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions( SemanticTokensRegistrationOptions { text_document_registration_options: { TextDocumentRegistrationOptions { document_selector: Some(vec![DocumentFilter { language: Some("nml".to_string()), scheme: Some("file".to_string()), pattern: None, }]), } }, semantic_tokens_options: SemanticTokensOptions { work_done_progress_options: WorkDoneProgressOptions::default(), legend: SemanticTokensLegend { token_types: vec![SemanticTokenType::COMMENT, SemanticTokenType::MACRO], token_modifiers: vec![], }, range: None, //Some(true), full: Some(SemanticTokensFullOptions::Bool(true)), }, static_registration_options: StaticRegistrationOptions::default(), }, ), ), ..ServerCapabilities::default() }, }) } async fn initialized(&self, _: InitializedParams) { self.client .log_message(MessageType::INFO, "server initialized!") .await; } async fn shutdown(&self) -> Result<()> { Ok(()) } async fn did_open(&self, params: DidOpenTextDocumentParams) { self.client .log_message(MessageType::INFO, "file opened!") .await; self.on_change(TextDocumentItem { uri: params.text_document.uri, text: params.text_document.text, version: params.text_document.version, }) .await } async fn did_change(&self, mut params: DidChangeTextDocumentParams) { self.on_change(TextDocumentItem { uri: params.text_document.uri, text: std::mem::take(&mut params.content_changes[0].text), version: params.text_document.version, }) .await } async fn completion(&self, params: CompletionParams) -> Result> { let uri = params.text_document_position.text_document.uri; let position = params.text_document_position.position; let completions = || -> Option> { let mut ret = Vec::with_capacity(0); Some(ret) }(); Ok(completions.map(CompletionResponse::Array)) } async fn semantic_tokens_full( &self, params: SemanticTokensParams, ) -> Result> { let uri = params.text_document.uri.to_string(); self.client .log_message(MessageType::LOG, "semantic_token_full") .await; let semantic_tokens = || -> Option> { let semantic_tokens = vec![ SemanticToken { delta_line: 1, delta_start: 2, length: 5, token_type: 1, token_modifiers_bitset: 0, } ]; Some(semantic_tokens) }(); if let Some(semantic_token) = semantic_tokens { return Ok(Some(SemanticTokensResult::Tokens(SemanticTokens { result_id: None, data: semantic_token, }))); } Ok(None) } } #[tokio::main] async fn main() { let stdin = tokio::io::stdin(); let stdout = tokio::io::stdout(); let (service, socket) = LspService::new( |client| Backend { client }); Server::new(stdin, stdout, socket).serve(service).await; }