use std::collections::HashMap;
use std::path::PathBuf;
use crossbeam_channel::Sender;
use lsp_server::{ErrorCode, Message, Notification, Request, RequestId, Response};
use lsp_types::notification::{
DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument,
Notification as NotificationTrait, PublishDiagnostics,
};
use lsp_types::request::{
DocumentSymbolRequest, FoldingRangeRequest, Formatting, RangeFormatting,
Request as RequestTrait, SelectionRangeRequest, SemanticTokensFullRequest,
};
use lsp_types::{
Diagnostic, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
DocumentFormattingParams, DocumentRangeFormattingParams, DocumentSymbolParams,
FoldingRangeParams, PublishDiagnosticsParams, SelectionRangeParams, SemanticTokensParams, Uri,
};
use crate::formatter::FormatStyle;
use crate::text::{PositionEncoding, apply_content_changes};
use super::analysis_thread::AnalysisRequest;
use super::read_jobs::ReadJob;
use super::uri;
#[derive(Debug, Clone)]
struct Document {
text: String,
version: i32,
}
pub(crate) enum Outbound {
Diagnostics {
uri: Uri,
version: i32,
diags: Vec<Diagnostic>,
},
}
pub(crate) struct GlobalState {
documents: HashMap<Uri, Document>,
sender: Sender<Message>,
analysis_tx: Sender<AnalysisRequest>,
read_tx: Sender<ReadJob>,
encoding: PositionEncoding,
}
impl GlobalState {
pub(crate) fn new(
sender: Sender<Message>,
analysis_tx: Sender<AnalysisRequest>,
read_tx: Sender<ReadJob>,
encoding: PositionEncoding,
) -> Self {
Self {
documents: HashMap::new(),
sender,
analysis_tx,
read_tx,
encoding,
}
}
pub(crate) fn on_request(&mut self, req: Request) {
match req.method.as_str() {
Formatting::METHOD => self.on_formatting(req),
RangeFormatting::METHOD => self.on_range_formatting(req),
DocumentSymbolRequest::METHOD => self.on_document_symbols(req),
FoldingRangeRequest::METHOD => self.on_folding_ranges(req),
SelectionRangeRequest::METHOD => self.on_selection_ranges(req),
SemanticTokensFullRequest::METHOD => self.on_semantic_tokens_full(req),
_ => {
let resp = Response::new_err(
req.id,
ErrorCode::MethodNotFound as i32,
format!("unhandled method: {}", req.method),
);
let _ = self.sender.send(Message::Response(resp));
}
}
}
fn on_formatting(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<DocumentFormattingParams>(Formatting::METHOD) else {
self.respond_err(id, "invalid formatting params");
return;
};
let uri = params.text_document.uri;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
self.dispatch_read(ReadJob::Format {
id,
path: path_for(&uri),
text,
style: FormatStyle::default(),
sender: self.sender.clone(),
});
}
fn on_range_formatting(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<DocumentRangeFormattingParams>(RangeFormatting::METHOD)
else {
self.respond_err(id, "invalid rangeFormatting params");
return;
};
let uri = params.text_document.uri;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
self.dispatch_read(ReadJob::FormatRange {
id,
path: path_for(&uri),
text,
range: params.range,
style: FormatStyle::default(),
sender: self.sender.clone(),
});
}
fn on_document_symbols(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<DocumentSymbolParams>(DocumentSymbolRequest::METHOD)
else {
self.respond_err(id, "invalid documentSymbol params");
return;
};
let uri = params.text_document.uri;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
self.dispatch_read(ReadJob::DocumentSymbols {
id,
path: path_for(&uri),
text,
sender: self.sender.clone(),
});
}
fn on_folding_ranges(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<FoldingRangeParams>(FoldingRangeRequest::METHOD) else {
self.respond_err(id, "invalid foldingRange params");
return;
};
let uri = params.text_document.uri;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
self.dispatch_read(ReadJob::FoldingRanges {
id,
path: path_for(&uri),
text,
sender: self.sender.clone(),
});
}
fn on_selection_ranges(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<SelectionRangeParams>(SelectionRangeRequest::METHOD)
else {
self.respond_err(id, "invalid selectionRange params");
return;
};
let uri = params.text_document.uri;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
self.dispatch_read(ReadJob::SelectionRanges {
id,
path: path_for(&uri),
text,
positions: params.positions,
sender: self.sender.clone(),
});
}
fn on_semantic_tokens_full(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) =
req.extract::<SemanticTokensParams>(SemanticTokensFullRequest::METHOD)
else {
self.respond_err(id, "invalid semanticTokens params");
return;
};
let uri = params.text_document.uri;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
self.dispatch_read(ReadJob::SemanticTokensFull {
id,
path: path_for(&uri),
text,
sender: self.sender.clone(),
});
}
fn dispatch_read(&self, job: ReadJob) {
if let Err(crossbeam_channel::SendError(job)) = self.read_tx.send(job) {
let (id, sender) = job.into_reply_parts();
let _ = sender.send(Message::Response(Response::new_ok(
id,
serde_json::Value::Null,
)));
}
}
pub(crate) fn on_notification(&mut self, note: Notification) {
match note.method.as_str() {
DidOpenTextDocument::METHOD => {
if let Ok(params) =
note.extract::<DidOpenTextDocumentParams>(DidOpenTextDocument::METHOD)
{
let uri = params.text_document.uri;
self.documents.insert(
uri.clone(),
Document {
text: params.text_document.text,
version: params.text_document.version,
},
);
self.send_analysis(uri);
}
}
DidChangeTextDocument::METHOD => {
if let Ok(params) =
note.extract::<DidChangeTextDocumentParams>(DidChangeTextDocument::METHOD)
{
let uri = params.text_document.uri;
let Some(doc) = self.documents.get_mut(&uri) else {
return;
};
apply_content_changes(&mut doc.text, params.content_changes, self.encoding);
doc.version = params.text_document.version;
self.send_analysis(uri);
}
}
DidCloseTextDocument::METHOD => {
if let Ok(params) =
note.extract::<DidCloseTextDocumentParams>(DidCloseTextDocument::METHOD)
{
let uri = params.text_document.uri;
self.documents.remove(&uri);
self.publish(uri, Vec::new(), None);
}
}
_ => {}
}
}
pub(crate) fn on_outbound(&mut self, outbound: Outbound) {
match outbound {
Outbound::Diagnostics {
uri,
version,
diags,
} => {
if !matches!(self.documents.get(&uri), Some(d) if d.version == version) {
return;
}
self.publish(uri, diags, Some(version));
}
}
}
fn send_analysis(&mut self, uri: Uri) {
let Some(doc) = self.documents.get(&uri) else {
return;
};
let _ = self.analysis_tx.send(AnalysisRequest {
path: path_for(&uri),
text: doc.text.clone(),
version: doc.version,
uri,
});
}
fn publish(&self, uri: Uri, diagnostics: Vec<Diagnostic>, version: Option<i32>) {
let params = PublishDiagnosticsParams {
uri,
diagnostics,
version,
};
let note = Notification::new(PublishDiagnostics::METHOD.to_string(), params);
let _ = self.sender.send(Message::Notification(note));
}
fn respond_ok(&self, id: RequestId, value: serde_json::Value) {
let _ = self
.sender
.send(Message::Response(Response::new_ok(id, value)));
}
fn respond_err(&self, id: RequestId, message: &str) {
let resp = Response::new_err(id, ErrorCode::InvalidParams as i32, message.to_string());
let _ = self.sender.send(Message::Response(resp));
}
}
fn path_for(uri: &Uri) -> PathBuf {
uri::to_path(uri).unwrap_or_else(|| PathBuf::from("untitled.jl"))
}