use std::collections::HashMap;
use std::error::Error;
use lsp_server::{Connection, ExtractError, Message, Notification, Request, RequestId, Response};
use lsp_types::notification::{
DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, Notification as _,
PublishDiagnostics,
};
use lsp_types::request::Formatting;
use lsp_types::{
Diagnostic, DiagnosticSeverity, DocumentFormattingParams, OneOf, Position,
PublishDiagnosticsParams, Range, ServerCapabilities, TextDocumentSyncCapability,
TextDocumentSyncKind, TextEdit, Uri,
};
use crate::formatter::{FormatStyle, format_with_style};
use crate::parser::parse;
use crate::text::LineIndex;
type LspResult<T> = Result<T, Box<dyn Error + Sync + Send>>;
pub fn run() -> LspResult<()> {
let (connection, io_threads) = Connection::stdio();
serve(&connection)?;
io_threads.join()?;
Ok(())
}
pub fn serve(connection: &Connection) -> LspResult<()> {
let capabilities = ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)),
document_formatting_provider: Some(OneOf::Left(true)),
..Default::default()
};
connection.initialize(serde_json::to_value(capabilities)?)?;
main_loop(connection)
}
fn main_loop(connection: &Connection) -> LspResult<()> {
let mut documents: HashMap<String, String> = HashMap::new();
for message in &connection.receiver {
match message {
Message::Request(req) => {
if connection.handle_shutdown(&req)? {
return Ok(());
}
handle_request(connection, &documents, req)?;
}
Message::Notification(note) => {
handle_notification(connection, &mut documents, note)?;
}
Message::Response(_) => {}
}
}
Ok(())
}
fn handle_request(
connection: &Connection,
documents: &HashMap<String, String>,
req: Request,
) -> LspResult<()> {
match cast::<Formatting>(req) {
Ok((id, params)) => {
let edits = format_edits(documents, ¶ms);
respond(connection, id, &edits)?;
Ok(())
}
Err(ExtractError::MethodMismatch(req)) => {
respond::<Option<serde_json::Value>>(connection, req.id, &None)?;
Ok(())
}
Err(ExtractError::JsonError { method, error }) => {
Err(format!("malformed `{method}` request: {error}").into())
}
}
}
fn handle_notification(
connection: &Connection,
documents: &mut HashMap<String, String>,
note: Notification,
) -> LspResult<()> {
match note.method.as_str() {
DidOpenTextDocument::METHOD => {
let params: lsp_types::DidOpenTextDocumentParams = serde_json::from_value(note.params)?;
let uri = params.text_document.uri.clone();
let text = params.text_document.text;
publish_diagnostics(connection, &uri, &text)?;
documents.insert(uri_key(&uri), text);
}
DidChangeTextDocument::METHOD => {
let params: lsp_types::DidChangeTextDocumentParams =
serde_json::from_value(note.params)?;
if let Some(change) = params.content_changes.into_iter().next_back() {
let uri = params.text_document.uri.clone();
publish_diagnostics(connection, &uri, &change.text)?;
documents.insert(uri_key(&uri), change.text);
}
}
DidCloseTextDocument::METHOD => {
let params: lsp_types::DidCloseTextDocumentParams =
serde_json::from_value(note.params)?;
documents.remove(&uri_key(¶ms.text_document.uri));
}
_ => {}
}
Ok(())
}
fn format_edits(
documents: &HashMap<String, String>,
params: &DocumentFormattingParams,
) -> Option<Vec<TextEdit>> {
let text = documents.get(&uri_key(¶ms.text_document.uri))?;
let style = FormatStyle::default();
let formatted = format_with_style(text, style).ok()?;
if formatted == *text {
return Some(Vec::new());
}
let line_index = LineIndex::new(text);
let end = line_index.byte_to_position(text.len());
Some(vec![TextEdit {
range: Range::new(Position::new(0, 0), end),
new_text: formatted,
}])
}
fn publish_diagnostics(connection: &Connection, uri: &Uri, text: &str) -> LspResult<()> {
let parsed = parse(text);
let line_index = LineIndex::new(text);
let diagnostics: Vec<Diagnostic> = parsed
.diagnostics
.iter()
.map(|diag| Diagnostic {
range: Range::new(
line_index.byte_to_position(diag.start),
line_index.byte_to_position(diag.end),
),
severity: Some(DiagnosticSeverity::ERROR),
source: Some("fatou".to_string()),
message: diag.message.clone(),
..Default::default()
})
.collect();
let params = PublishDiagnosticsParams {
uri: uri.clone(),
diagnostics,
version: None,
};
connection.sender.send(Message::Notification(Notification {
method: PublishDiagnostics::METHOD.to_string(),
params: serde_json::to_value(params)?,
}))?;
Ok(())
}
fn respond<T: serde::Serialize>(
connection: &Connection,
id: RequestId,
result: &T,
) -> LspResult<()> {
let response = Response {
id,
result: Some(serde_json::to_value(result)?),
error: None,
};
connection.sender.send(Message::Response(response))?;
Ok(())
}
fn cast<R>(req: Request) -> Result<(RequestId, R::Params), ExtractError<Request>>
where
R: lsp_types::request::Request,
R::Params: serde::de::DeserializeOwned,
{
req.extract(R::METHOD)
}
fn uri_key(uri: &Uri) -> String {
uri.as_str().to_string()
}