#![deny(warnings)]
#![deny(missing_docs)]
#![doc(html_logo_url = "https://raw.githubusercontent.com/yslide/slide/master/assets/logo.png")]
use libslide::*;
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard};
use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::*;
use tower_lsp::{Client, LanguageServer, LspService, Server};
use std::cell::RefCell;
use std::collections::HashMap;
use std::ops::Deref;
mod ast;
mod services;
mod shims;
use shims::convert_diagnostics;
#[cfg(test)]
mod tests;
pub(crate) struct ProgramInfo {
source: String,
uri: Url,
original: StmtList,
#[allow(unused)]
simplified: StmtList,
}
type DocumentRegistry = HashMap<Url, ProgramInfo>;
pub struct SlideLS {
client: Client,
document_registry: Mutex<RefCell<DocumentRegistry>>,
context: Mutex<RefCell<ProgramContext>>,
client_caps: Mutex<RefCell<ClientCapabilities>>,
}
impl SlideLS {
pub fn new(client: Client) -> Self {
Self {
client,
document_registry: Default::default(),
context: Default::default(),
client_caps: Default::default(),
}
}
pub fn capabilities() -> ServerCapabilities {
let text_document_sync = Some(TextDocumentSyncCapability::Options(
TextDocumentSyncOptions {
open_close: Some(true),
change: Some(TextDocumentSyncKind::Full),
..TextDocumentSyncOptions::default()
},
));
let definition_provider = Some(true);
let hover_provider = Some(HoverProviderCapability::Simple(true));
let references_provider = Some(true);
let document_highlight_provider = Some(true);
ServerCapabilities {
definition_provider,
text_document_sync,
hover_provider,
references_provider,
document_highlight_provider,
..ServerCapabilities::default()
}
}
async fn change(&self, doc: Url, text: String, version: Option<i64>) {
let ScanResult {
tokens,
diagnostics: scan_diags,
} = scan(&*text);
let ParseResult {
program,
diagnostics: parse_diags,
} = parse_statements(tokens, &text);
let lint_diags = lint_stmt(&program, &text);
let EvaluationResult {
simplified,
diagnostics: eval_diags,
} = evaluate(program.clone(), &self.context().deref()).expect("Evaluation failed.");
let diags = [scan_diags, parse_diags, lint_diags, eval_diags]
.iter()
.flat_map(|diags| convert_diagnostics(diags, "slide", &doc, &text))
.collect();
self.client
.publish_diagnostics(doc.clone(), diags, version)
.await;
self.doc_registry().get_mut().insert(
doc.clone(),
ProgramInfo {
source: text,
uri: doc,
original: program,
simplified,
},
);
}
fn close(&self, doc: &Url) {
self.doc_registry().get_mut().remove(doc);
}
fn doc_registry(&self) -> MutexGuard<RefCell<DocumentRegistry>> {
self.document_registry.lock()
}
fn get_program_info(&self, doc: &Url) -> MappedMutexGuard<ProgramInfo> {
MutexGuard::map(self.doc_registry(), |dr| dr.get_mut().get_mut(doc).unwrap())
}
fn context(&self) -> MappedMutexGuard<ProgramContext> {
MutexGuard::map(self.context.lock(), |pc| pc.get_mut())
}
fn client_caps(&self) -> MappedMutexGuard<ClientCapabilities> {
MutexGuard::map(self.client_caps.lock(), |pc| pc.get_mut())
}
}
#[tower_lsp::async_trait]
impl LanguageServer for SlideLS {
async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
let context = ProgramContext::default().lint(true);
self.context.lock().replace(context);
self.client_caps.lock().replace(params.capabilities);
Ok(InitializeResult {
capabilities: SlideLS::capabilities(),
..InitializeResult::default()
})
}
async fn initialized(&self, _params: InitializedParams) {
self.client
.log_message(MessageType::Info, "Slide language server initialized.")
.await;
}
async fn shutdown(&self) -> Result<()> {
Ok(())
}
async fn did_open(&self, params: DidOpenTextDocumentParams) {
let TextDocumentItem {
uri, text, version, ..
} = params.text_document;
self.change(uri, text, Some(version)).await;
}
async fn did_change(&self, params: DidChangeTextDocumentParams) {
let VersionedTextDocumentIdentifier { uri, version, .. } = params.text_document;
let TextDocumentContentChangeEvent { text, .. } =
params.content_changes.into_iter().next().unwrap();
self.change(uri, text, version).await;
}
async fn did_close(&self, params: DidCloseTextDocumentParams) {
let TextDocumentIdentifier { uri } = params.text_document;
self.close(&uri);
}
async fn goto_definition(
&self,
params: GotoDefinitionParams,
) -> Result<Option<GotoDefinitionResponse>> {
let TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri },
position,
} = params.text_document_position_params;
let program_info = self.get_program_info(&uri);
let supports_link = self
.client_caps()
.text_document
.as_ref()
.and_then(|td| td.definition)
.and_then(|def| def.link_support)
.unwrap_or(false);
let definitions = services::get_definitions(position, program_info.deref(), supports_link);
Ok(definitions)
}
async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
let TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri },
position,
} = params.text_document_position_params;
let program_info = self.get_program_info(&uri);
let context = self.context();
let hover = services::get_hover_info(position, program_info.deref(), context.deref());
Ok(hover)
}
async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
let ReferenceParams {
text_document_position:
TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri },
position,
},
context: ReferenceContext {
include_declaration,
},
..
} = params;
let program_info = self.get_program_info(&uri);
let references =
services::get_references(position, include_declaration, program_info.deref());
Ok(references)
}
async fn document_highlight(
&self,
params: DocumentHighlightParams,
) -> Result<Option<Vec<DocumentHighlight>>> {
let TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri },
position,
} = params.text_document_position_params;
let program_info = self.get_program_info(&uri);
let highlights = services::get_semantic_highlights(position, program_info.deref());
Ok(highlights)
}
}
#[tokio::main]
async fn main() {
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
let (service, messages) = LspService::new(SlideLS::new);
Server::new(stdin, stdout)
.interleave(messages)
.serve(service)
.await;
}