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, DidChangeWatchedFiles, DidCloseTextDocument, DidOpenTextDocument,
DidSaveTextDocument, Notification as NotificationTrait, PublishDiagnostics,
};
use lsp_types::request::{
CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
CodeActionRequest, Completion, DocumentDiagnosticRequest, DocumentHighlightRequest,
DocumentLinkRequest, DocumentSymbolRequest, FoldingRangeRequest, Formatting, GotoDefinition,
HoverRequest, PrepareRenameRequest, RangeFormatting, References, RegisterCapability, Rename,
Request as RequestTrait, ResolveCompletionItem, SelectionRangeRequest,
SemanticTokensFullRequest, SignatureHelpRequest, TypeHierarchyPrepare, TypeHierarchySubtypes,
TypeHierarchySupertypes, WorkspaceDiagnosticRefresh, WorkspaceSymbolRequest,
};
use lsp_types::{
CallHierarchyIncomingCallsParams, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
CodeActionParams, CompletionItem, CompletionParams, Diagnostic, DidChangeTextDocumentParams,
DidChangeWatchedFilesParams, DidChangeWatchedFilesRegistrationOptions,
DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams,
DocumentDiagnosticParams, DocumentFormattingParams, DocumentHighlightParams,
DocumentLinkParams, DocumentRangeFormattingParams, DocumentSymbolParams, FileSystemWatcher,
FoldingRangeParams, GlobPattern, GotoDefinitionParams, HoverParams, PublishDiagnosticsParams,
ReferenceParams, Registration, RegistrationParams, RenameParams, SelectionRangeParams,
SemanticTokensParams, SignatureHelpParams, TextDocumentPositionParams,
TypeHierarchyPrepareParams, TypeHierarchySubtypesParams, TypeHierarchySupertypesParams, Uri,
WorkspaceSymbolParams,
};
use crate::environment::is_environment_file;
use crate::formatter::FormatStyle;
use crate::text::{PositionEncoding, apply_content_changes};
use super::analysis_thread::AnalysisRequest;
use super::read_jobs::ReadJob;
use super::server::HarvestSignal;
use super::uri;
#[derive(Debug, Clone)]
struct Document {
text: String,
version: i32,
}
pub(crate) enum Outbound {
Diagnostics {
uri: Uri,
version: i32,
diags: Vec<Diagnostic>,
},
ProjectDiagnostics { uri: Uri, diags: Vec<Diagnostic> },
DiagnosticsRefresh,
}
pub(crate) struct GlobalState {
documents: HashMap<Uri, Document>,
sender: Sender<Message>,
analysis_tx: Sender<AnalysisRequest>,
read_tx: Sender<ReadJob>,
harvest_tx: Sender<HarvestSignal>,
sync_tx: Sender<PathBuf>,
encoding: PositionEncoding,
pull_diagnostics: bool,
diagnostic_refresh: bool,
refresh_seq: u64,
parse_diags: HashMap<Uri, Vec<Diagnostic>>,
graph_diags: HashMap<Uri, Vec<Diagnostic>>,
}
impl GlobalState {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
sender: Sender<Message>,
analysis_tx: Sender<AnalysisRequest>,
read_tx: Sender<ReadJob>,
harvest_tx: Sender<HarvestSignal>,
sync_tx: Sender<PathBuf>,
encoding: PositionEncoding,
pull_diagnostics: bool,
diagnostic_refresh: bool,
) -> Self {
Self {
documents: HashMap::new(),
parse_diags: HashMap::new(),
graph_diags: HashMap::new(),
sender,
analysis_tx,
read_tx,
harvest_tx,
sync_tx,
encoding,
pull_diagnostics,
diagnostic_refresh,
refresh_seq: 0,
}
}
pub(crate) fn on_request(&mut self, req: Request) {
match req.method.as_str() {
CodeActionRequest::METHOD => self.on_code_action(req),
DocumentDiagnosticRequest::METHOD => self.on_document_diagnostic(req),
Formatting::METHOD => self.on_formatting(req),
RangeFormatting::METHOD => self.on_range_formatting(req),
DocumentSymbolRequest::METHOD => self.on_document_symbols(req),
WorkspaceSymbolRequest::METHOD => self.on_workspace_symbols(req),
FoldingRangeRequest::METHOD => self.on_folding_ranges(req),
DocumentLinkRequest::METHOD => self.on_document_links(req),
SelectionRangeRequest::METHOD => self.on_selection_ranges(req),
SemanticTokensFullRequest::METHOD => self.on_semantic_tokens_full(req),
Completion::METHOD => self.on_completion(req),
ResolveCompletionItem::METHOD => self.on_completion_resolve(req),
HoverRequest::METHOD => self.on_hover(req),
SignatureHelpRequest::METHOD => self.on_signature_help(req),
GotoDefinition::METHOD => self.on_definition(req),
References::METHOD => self.on_references(req),
DocumentHighlightRequest::METHOD => self.on_document_highlight(req),
PrepareRenameRequest::METHOD => self.on_prepare_rename(req),
Rename::METHOD => self.on_rename(req),
CallHierarchyPrepare::METHOD => self.on_prepare_call_hierarchy(req),
CallHierarchyIncomingCalls::METHOD => self.on_call_hierarchy_incoming(req),
CallHierarchyOutgoingCalls::METHOD => self.on_call_hierarchy_outgoing(req),
TypeHierarchyPrepare::METHOD => self.on_prepare_type_hierarchy(req),
TypeHierarchySupertypes::METHOD => self.on_type_hierarchy_supertypes(req),
TypeHierarchySubtypes::METHOD => self.on_type_hierarchy_subtypes(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_document_diagnostic(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) =
req.extract::<DocumentDiagnosticParams>(DocumentDiagnosticRequest::METHOD)
else {
self.respond_err(id, "invalid documentDiagnostic params");
return;
};
let uri = params.text_document.uri;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
let empty = serde_json::to_value(super::read_jobs::full_report(Vec::new()))
.expect("empty diagnostic report serializes");
self.respond_ok(id, empty);
return;
};
self.dispatch_read(ReadJob::DocumentDiagnostic {
id,
path: path_for(&uri),
text,
sender: self.sender.clone(),
});
}
fn on_code_action(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<CodeActionParams>(CodeActionRequest::METHOD) else {
self.respond_err(id, "invalid codeAction 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::CodeAction {
id,
path: path_for(&uri),
text,
range: params.range,
uri,
sender: self.sender.clone(),
});
}
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_workspace_symbols(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<WorkspaceSymbolParams>(WorkspaceSymbolRequest::METHOD)
else {
self.respond_err(id, "invalid workspaceSymbol params");
return;
};
self.dispatch_read(ReadJob::WorkspaceSymbols {
id,
query: params.query,
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_document_links(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<DocumentLinkParams>(DocumentLinkRequest::METHOD) else {
self.respond_err(id, "invalid documentLink 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::DocumentLinks {
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 on_completion(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<CompletionParams>(Completion::METHOD) else {
self.respond_err(id, "invalid completion params");
return;
};
let uri = params.text_document_position.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::Completion {
id,
path: path_for(&uri),
text,
position: params.text_document_position.position,
sender: self.sender.clone(),
});
}
fn on_hover(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<HoverParams>(HoverRequest::METHOD) else {
self.respond_err(id, "invalid hover params");
return;
};
let uri = params.text_document_position_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::Hover {
id,
path: path_for(&uri),
text,
position: params.text_document_position_params.position,
sender: self.sender.clone(),
});
}
fn on_signature_help(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<SignatureHelpParams>(SignatureHelpRequest::METHOD)
else {
self.respond_err(id, "invalid signatureHelp params");
return;
};
let uri = params.text_document_position_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::SignatureHelp {
id,
path: path_for(&uri),
text,
position: params.text_document_position_params.position,
sender: self.sender.clone(),
});
}
fn on_definition(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<GotoDefinitionParams>(GotoDefinition::METHOD) else {
self.respond_err(id, "invalid definition params");
return;
};
let uri = params.text_document_position_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::Definition {
id,
path: path_for(&uri),
position: params.text_document_position_params.position,
uri,
text,
sender: self.sender.clone(),
});
}
fn on_references(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<ReferenceParams>(References::METHOD) else {
self.respond_err(id, "invalid references params");
return;
};
let uri = params.text_document_position.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::References {
id,
path: path_for(&uri),
position: params.text_document_position.position,
include_declaration: params.context.include_declaration,
uri,
text,
sender: self.sender.clone(),
});
}
fn on_document_highlight(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) =
req.extract::<DocumentHighlightParams>(DocumentHighlightRequest::METHOD)
else {
self.respond_err(id, "invalid documentHighlight params");
return;
};
let uri = params.text_document_position_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::DocumentHighlight {
id,
path: path_for(&uri),
position: params.text_document_position_params.position,
text,
sender: self.sender.clone(),
});
}
fn on_prepare_rename(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) =
req.extract::<TextDocumentPositionParams>(PrepareRenameRequest::METHOD)
else {
self.respond_err(id, "invalid prepareRename 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::PrepareRename {
id,
path: path_for(&uri),
position: params.position,
text,
sender: self.sender.clone(),
});
}
fn on_rename(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<RenameParams>(Rename::METHOD) else {
self.respond_err(id, "invalid rename params");
return;
};
let uri = params.text_document_position.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::Rename {
id,
path: path_for(&uri),
position: params.text_document_position.position,
new_name: params.new_name,
uri,
text,
sender: self.sender.clone(),
});
}
fn on_prepare_call_hierarchy(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) =
req.extract::<CallHierarchyPrepareParams>(CallHierarchyPrepare::METHOD)
else {
self.respond_err(id, "invalid prepareCallHierarchy params");
return;
};
let uri = params.text_document_position_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::PrepareCallHierarchy {
id,
path: path_for(&uri),
position: params.text_document_position_params.position,
uri,
text,
sender: self.sender.clone(),
});
}
fn on_call_hierarchy_incoming(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) =
req.extract::<CallHierarchyIncomingCallsParams>(CallHierarchyIncomingCalls::METHOD)
else {
self.respond_err(id, "invalid incomingCalls params");
return;
};
self.dispatch_read(ReadJob::CallHierarchyIncoming {
id,
item: Box::new(params.item),
sender: self.sender.clone(),
});
}
fn on_call_hierarchy_outgoing(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) =
req.extract::<CallHierarchyOutgoingCallsParams>(CallHierarchyOutgoingCalls::METHOD)
else {
self.respond_err(id, "invalid outgoingCalls params");
return;
};
self.dispatch_read(ReadJob::CallHierarchyOutgoing {
id,
item: Box::new(params.item),
sender: self.sender.clone(),
});
}
fn on_prepare_type_hierarchy(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) =
req.extract::<TypeHierarchyPrepareParams>(TypeHierarchyPrepare::METHOD)
else {
self.respond_err(id, "invalid prepareTypeHierarchy params");
return;
};
let uri = params.text_document_position_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::PrepareTypeHierarchy {
id,
path: path_for(&uri),
position: params.text_document_position_params.position,
uri,
text,
sender: self.sender.clone(),
});
}
fn on_type_hierarchy_supertypes(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) =
req.extract::<TypeHierarchySupertypesParams>(TypeHierarchySupertypes::METHOD)
else {
self.respond_err(id, "invalid supertypes params");
return;
};
self.dispatch_read(ReadJob::TypeHierarchySupertypes {
id,
item: Box::new(params.item),
sender: self.sender.clone(),
});
}
fn on_type_hierarchy_subtypes(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) =
req.extract::<TypeHierarchySubtypesParams>(TypeHierarchySubtypes::METHOD)
else {
self.respond_err(id, "invalid subtypes params");
return;
};
self.dispatch_read(ReadJob::TypeHierarchySubtypes {
id,
item: Box::new(params.item),
sender: self.sender.clone(),
});
}
fn on_completion_resolve(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, item)) = req.extract::<CompletionItem>(ResolveCompletionItem::METHOD) else {
self.respond_err(id, "invalid completionItem/resolve params");
return;
};
self.dispatch_read(ReadJob::CompletionResolve {
id,
item: Box::new(item),
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,
},
);
if self.pull_diagnostics && self.graph_diags.contains_key(&uri) {
self.publish(uri.clone(), Vec::new(), None);
}
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);
}
}
DidSaveTextDocument::METHOD => {
if let Ok(params) =
note.extract::<DidSaveTextDocumentParams>(DidSaveTextDocument::METHOD)
{
if let Some(path) = uri::to_path(¶ms.text_document.uri) {
let _ = self.harvest_tx.send(harvest_signal(path));
}
}
}
DidChangeWatchedFiles::METHOD => {
if let Ok(params) =
note.extract::<DidChangeWatchedFilesParams>(DidChangeWatchedFiles::METHOD)
{
self.on_watched_files(params);
}
}
DidCloseTextDocument::METHOD => {
if let Ok(params) =
note.extract::<DidCloseTextDocumentParams>(DidCloseTextDocument::METHOD)
{
let uri = params.text_document.uri;
self.documents.remove(&uri);
if let Some(path) = uri::to_path(&uri) {
let _ = self.sync_tx.send(path);
}
self.parse_diags.remove(&uri);
self.publish_merged(uri, None);
}
}
_ => {}
}
}
fn on_watched_files(&mut self, params: DidChangeWatchedFilesParams) {
let environment_changed = params
.changes
.iter()
.filter_map(|event| uri::to_path(&event.uri))
.any(|path| is_environment_file(&path));
for event in ¶ms.changes {
let Some(path) = uri::to_path(&event.uri) else {
continue;
};
if is_environment_file(&path) || path.extension().is_none_or(|ext| ext != "jl") {
continue;
}
if !self.documents.contains_key(&event.uri) {
let _ = self.sync_tx.send(path.clone());
}
if !environment_changed {
let _ = self.harvest_tx.send(HarvestSignal::Source(path));
}
}
if environment_changed {
let _ = self.harvest_tx.send(HarvestSignal::Environment);
}
}
pub(crate) fn register_file_watchers(&self) {
let watchers = [
"**/*.jl",
"**/Project.toml",
"**/JuliaProject.toml",
"**/Manifest.toml",
"**/JuliaManifest.toml",
"**/Manifest-v*.toml",
]
.into_iter()
.map(|glob| FileSystemWatcher {
glob_pattern: GlobPattern::String(glob.to_string()),
kind: None,
})
.collect();
let params = RegistrationParams {
registrations: vec![Registration {
id: "fatou-watched-files".to_string(),
method: DidChangeWatchedFiles::METHOD.to_string(),
register_options: Some(
serde_json::to_value(DidChangeWatchedFilesRegistrationOptions { watchers })
.expect("watcher registration options serialize"),
),
}],
};
let _ = self.sender.send(Message::Request(Request {
id: RequestId::from("fatou-register-watched-files".to_string()),
method: RegisterCapability::METHOD.to_string(),
params: serde_json::to_value(params).expect("registration params serialize"),
}));
}
pub(crate) fn on_outbound(&mut self, outbound: Outbound) {
match outbound {
Outbound::Diagnostics {
uri,
version,
diags,
} => {
if self.pull_diagnostics {
return;
}
if !matches!(self.documents.get(&uri), Some(d) if d.version == version) {
return;
}
self.parse_diags.insert(uri.clone(), diags);
self.publish_merged(uri, Some(version));
}
Outbound::ProjectDiagnostics { uri, diags } => {
if diags.is_empty() {
self.graph_diags.remove(&uri);
} else {
self.graph_diags.insert(uri.clone(), diags);
}
if self.pull_diagnostics && self.documents.contains_key(&uri) {
return;
}
let version = self.documents.get(&uri).map(|d| d.version);
self.publish_merged(uri, version);
}
Outbound::DiagnosticsRefresh => {
if !(self.pull_diagnostics && self.diagnostic_refresh) {
return;
}
self.refresh_seq += 1;
let _ = self.sender.send(Message::Request(Request {
id: RequestId::from(format!("fatou-diagnostic-refresh-{}", self.refresh_seq)),
method: WorkspaceDiagnosticRefresh::METHOD.to_string(),
params: serde_json::Value::Null,
}));
}
}
}
fn publish_merged(&self, uri: Uri, version: Option<i32>) {
let mut diagnostics = self.parse_diags.get(&uri).cloned().unwrap_or_default();
if let Some(graph) = self.graph_diags.get(&uri) {
diagnostics.extend(graph.iter().cloned());
}
self.publish(uri, diagnostics, 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"))
}
fn harvest_signal(path: PathBuf) -> HarvestSignal {
if is_environment_file(&path) {
HarvestSignal::Environment
} else {
HarvestSignal::Source(path)
}
}