use std::{collections::HashMap, ops::ControlFlow};
use async_lsp::{
lsp_types::{
CodeAction, CodeActionOrCommand, CodeActionParams, CodeActionProviderCapability,
CodeActionResponse, CompletionOptions, CompletionParams, CompletionResponse,
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
InitializeParams, InitializeResult, ServerCapabilities, TextDocumentSyncCapability,
TextDocumentSyncKind, Url, WorkspaceEdit,
},
ClientSocket, LanguageServer, ResponseError,
};
use futures::future::BoxFuture;
use crate::{code_action::CodeActionEngine, completion::CompletionEngine};
pub struct DispositionLanguageServer {
#[allow(dead_code)]
client: ClientSocket,
documents: HashMap<Url, String>,
}
impl DispositionLanguageServer {
pub fn new(client: ClientSocket) -> Self {
Self {
client,
documents: HashMap::new(),
}
}
}
impl LanguageServer for DispositionLanguageServer {
type Error = ResponseError;
type NotifyResult = ControlFlow<async_lsp::Result<()>>;
fn initialize(
&mut self,
_params: InitializeParams,
) -> BoxFuture<'static, Result<InitializeResult, Self::Error>> {
Box::pin(async move {
Ok(InitializeResult {
capabilities: ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Kind(
TextDocumentSyncKind::FULL,
)),
completion_provider: Some(CompletionOptions {
trigger_characters: Some(vec![
":".to_string(),
" ".to_string(),
"-".to_string(),
]),
..CompletionOptions::default()
}),
code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
..ServerCapabilities::default()
},
server_info: None,
})
})
}
fn completion(
&mut self,
params: CompletionParams,
) -> BoxFuture<'static, Result<Option<CompletionResponse>, Self::Error>> {
let uri = params.text_document_position.text_document.uri;
let position = params.text_document_position.position;
let items = self
.documents
.get(&uri)
.map(|text| CompletionEngine::completions(text, position.line, position.character))
.unwrap_or_default();
Box::pin(async move { Ok(Some(CompletionResponse::Array(items))) })
}
fn code_action(
&mut self,
params: CodeActionParams,
) -> BoxFuture<'static, Result<Option<CodeActionResponse>, Self::Error>> {
let uri = params.text_document.uri;
let line = params.range.start.line;
let actions = self
.documents
.get(&uri)
.map(|text| CodeActionEngine::list_conversions(text, line))
.unwrap_or_default()
.into_iter()
.map(|conversion| {
CodeActionOrCommand::CodeAction(CodeAction {
title: conversion.title,
kind: Some(async_lsp::lsp_types::CodeActionKind::REFACTOR_REWRITE),
edit: Some(WorkspaceEdit {
changes: Some([(uri.clone(), vec![conversion.edit])].into_iter().collect()),
..WorkspaceEdit::default()
}),
..CodeAction::default()
})
})
.collect::<CodeActionResponse>();
Box::pin(async move { Ok(Some(actions)) })
}
fn did_open(&mut self, params: DidOpenTextDocumentParams) -> Self::NotifyResult {
self.documents
.insert(params.text_document.uri, params.text_document.text);
ControlFlow::Continue(())
}
fn did_change(&mut self, params: DidChangeTextDocumentParams) -> Self::NotifyResult {
if let Some(change) = params.content_changes.into_iter().next_back() {
self.documents.insert(params.text_document.uri, change.text);
}
ControlFlow::Continue(())
}
fn did_close(&mut self, params: DidCloseTextDocumentParams) -> Self::NotifyResult {
self.documents.remove(¶ms.text_document.uri);
ControlFlow::Continue(())
}
}