1use crate::handlers::call_hierarchy::{
9 handle_incoming_calls, handle_outgoing_calls, handle_prepare_call_hierarchy,
10};
11use crate::handlers::code_actions::{handle_code_action_resolve, handle_code_actions};
12use crate::handlers::code_lens::{handle_code_lens, handle_code_lens_resolve};
13use crate::handlers::completion::handle_completion;
14use crate::handlers::completion_resolve::handle_completion_resolve;
15use crate::handlers::declaration::handle_goto_declaration;
16use crate::handlers::definition::handle_goto_definition;
17use crate::handlers::diagnostics::all_diagnostics;
18use crate::handlers::document_color::{handle_color_presentation, handle_document_color};
19use crate::handlers::document_highlight::handle_document_highlight;
20use crate::handlers::document_links::{handle_document_link_resolve, handle_document_links};
21use crate::handlers::execute_command::handle_execute_command;
22use crate::handlers::folding::handle_folding_ranges;
23use crate::handlers::formatting::handle_formatting;
24use crate::handlers::hover::handle_hover;
25use crate::handlers::inlay_hints::{handle_inlay_hint_resolve, handle_inlay_hints};
26use crate::handlers::linked_editing::handle_linked_editing_range;
27use crate::handlers::on_type_formatting::handle_on_type_formatting;
28use crate::handlers::range_formatting::handle_range_formatting;
29use crate::handlers::references::handle_references;
30use crate::handlers::rename::{handle_prepare_rename, handle_rename};
31use crate::handlers::selection_range::handle_selection_range;
32use crate::handlers::semantic_tokens::{
33 handle_semantic_tokens, handle_semantic_tokens_delta, handle_semantic_tokens_range,
34};
35use crate::handlers::signature_help::handle_signature_help;
36use crate::handlers::symbols::handle_document_symbols;
37use crate::handlers::type_hierarchy::{
38 handle_prepare_type_hierarchy, handle_subtypes, handle_supertypes,
39};
40use crate::handlers::workspace_symbols::handle_workspace_symbols;
41use crate::ledger_state::{SharedLedgerState, new_shared_ledger_state};
42use crate::uri_to_path;
43use crate::vfs::Vfs;
44use crossbeam_channel::{Receiver, Sender};
45use lsp_types::notification::{
46 DidChangeTextDocument, DidChangeWatchedFiles, DidCloseTextDocument, DidOpenTextDocument,
47 Notification, PublishDiagnostics,
48};
49use lsp_types::request::{
50 CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
51 CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, CodeLensResolve,
52 ColorPresentationRequest, Completion, DocumentColor, DocumentHighlightRequest,
53 DocumentLinkRequest, DocumentLinkResolve, DocumentSymbolRequest, ExecuteCommand,
54 FoldingRangeRequest, Formatting, GotoDeclaration, GotoDefinition, HoverRequest, Initialize,
55 InlayHintRequest, InlayHintResolveRequest, LinkedEditingRange, OnTypeFormatting,
56 PrepareRenameRequest, RangeFormatting, References, Rename, Request, ResolveCompletionItem,
57 SelectionRangeRequest, SemanticTokensFullDeltaRequest, SemanticTokensFullRequest,
58 SemanticTokensRangeRequest, Shutdown, SignatureHelpRequest, TypeHierarchyPrepare,
59 TypeHierarchySubtypes, TypeHierarchySupertypes, WorkspaceSymbolRequest,
60};
61use lsp_types::{
62 CallHierarchyIncomingCallsParams, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
63 CodeAction, CodeActionParams, CodeLens, CodeLensParams, ColorPresentationParams,
64 CompletionItem, CompletionParams, DocumentColorParams, DocumentFormattingParams,
65 DocumentHighlightParams, DocumentLink, DocumentLinkParams, DocumentOnTypeFormattingParams,
66 DocumentRangeFormattingParams, DocumentSymbolParams, ExecuteCommandParams, FoldingRangeParams,
67 GotoDefinitionParams, HoverParams, InitializeParams, InlayHint, InlayHintParams,
68 LinkedEditingRangeParams, PublishDiagnosticsParams, ReferenceParams, RenameParams,
69 SelectionRangeParams, SemanticTokensDeltaParams, SemanticTokensParams,
70 SemanticTokensRangeParams, SignatureHelpParams, TextDocumentPositionParams,
71 TypeHierarchyPrepareParams, TypeHierarchySubtypesParams, TypeHierarchySupertypesParams, Uri,
72 WorkspaceSymbolParams,
73};
74use parking_lot::RwLock;
75use rustledger_core::Directive;
76use rustledger_parser::{ParseResult, Spanned, parse};
77use std::collections::HashMap;
78use std::path::PathBuf;
79use std::sync::Arc;
80
81#[derive(Debug)]
83pub enum Event {
84 Message(Message),
86 Task(TaskResult),
88}
89
90#[derive(Debug)]
92pub enum Message {
93 Request(lsp_server::Request),
95 Notification(lsp_server::Notification),
97 Response(lsp_server::Response),
99}
100
101#[derive(Debug)]
103pub struct TaskResult {
104 pub request_id: lsp_server::RequestId,
106 pub result: Result<serde_json::Value, String>,
108 pub content_modified: bool,
113}
114
115const CONTENT_MODIFIED: i32 = -32801;
118
119type BackgroundJob = Box<dyn FnOnce() + Send>;
121
122#[derive(Debug)]
133enum DispatchError {
134 MethodNotFound(String),
137 DuplicateInitialize,
144 Handler(String),
148}
149
150impl DispatchError {
151 fn error_code(&self) -> lsp_server::ErrorCode {
153 match self {
154 Self::MethodNotFound(_) => lsp_server::ErrorCode::MethodNotFound,
155 Self::DuplicateInitialize => lsp_server::ErrorCode::InvalidRequest,
156 Self::Handler(_) => lsp_server::ErrorCode::InternalError,
157 }
158 }
159}
160
161impl std::fmt::Display for DispatchError {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 match self {
164 Self::MethodNotFound(method) => write!(f, "Unhandled request: {method}"),
165 Self::DuplicateInitialize => write!(
166 f,
167 "initialize must be sent exactly once per LSP connection (LSP 3.17 \
168 §Lifecycle); this connection has already been initialized via \
169 server.rs::start_stdio."
170 ),
171 Self::Handler(msg) => f.write_str(msg),
172 }
173 }
174}
175
176pub struct MainLoopState {
178 pub vfs: Arc<RwLock<Vfs>>,
180 pub sender: Sender<lsp_server::Message>,
182 pub diagnostics: HashMap<Uri, Vec<lsp_types::Diagnostic>>,
184 pub cross_file_diag_uris: std::collections::HashSet<Uri>,
190 pub shutdown_requested: bool,
192 pub pending_exit_code: Option<i32>,
206 pub position_encoding: crate::handlers::utils::PositionEncoding,
210 pub ledger_state: SharedLedgerState,
212 pub journal_file: Option<PathBuf>,
214 pub task_sender: Sender<TaskResult>,
216 pub task_receiver: Receiver<TaskResult>,
218 pub job_sender: Sender<BackgroundJob>,
220 revision: Arc<std::sync::atomic::AtomicU64>,
232 exit_action: Option<Box<dyn FnOnce(i32) + Send>>,
242}
243
244fn empty_parse_result() -> Arc<ParseResult> {
246 Arc::new(parse(""))
247}
248
249impl MainLoopState {
250 pub fn new(sender: Sender<lsp_server::Message>, journal_file: Option<PathBuf>) -> Self {
252 let ledger_state = new_shared_ledger_state();
253
254 if let Some(ref path) = journal_file {
256 let mut state = ledger_state.write();
257 if let Err(e) = state.load(path) {
258 tracing::error!("Failed to load journal file: {e}");
259 }
260 }
261
262 let (task_sender, task_receiver) = crossbeam_channel::unbounded();
263 let (job_sender, job_receiver) = crossbeam_channel::unbounded::<BackgroundJob>();
264
265 std::thread::Builder::new()
270 .name("lsp-worker".into())
271 .spawn(move || {
272 for job in job_receiver {
273 job();
274 }
275 })
276 .expect("failed to spawn LSP worker thread");
277
278 Self {
279 vfs: Arc::new(RwLock::new(Vfs::new())),
280 cross_file_diag_uris: std::collections::HashSet::new(),
281 sender,
282 diagnostics: HashMap::new(),
283 shutdown_requested: false,
284 pending_exit_code: None,
285 position_encoding: crate::handlers::utils::PositionEncoding::Utf16,
290 ledger_state,
291 journal_file,
292 task_sender,
293 task_receiver,
294 job_sender,
295 revision: Arc::new(std::sync::atomic::AtomicU64::new(0)),
296 exit_action: Some(Box::new(|_code| {})),
305 }
306 }
307
308 fn bump_revision(&self) -> u64 {
312 self.revision
313 .fetch_add(1, std::sync::atomic::Ordering::SeqCst)
314 + 1
315 }
316
317 #[must_use]
323 pub fn with_exit_action<F>(mut self, action: F) -> Self
324 where
325 F: FnOnce(i32) + Send + 'static,
326 {
327 self.exit_action = Some(Box::new(action));
328 self
329 }
330
331 pub fn reload_journal(&mut self) {
333 if let Some(ref path) = self.journal_file {
334 let mut state = self.ledger_state.write();
335 if let Err(e) = state.load(path) {
336 tracing::error!("Failed to reload journal file: {e}");
337 }
338 }
339 }
340
341 fn get_document_data(&self, uri: &Uri) -> (String, Arc<ParseResult>) {
344 if let Some(path) = uri_to_path(uri)
345 && let Some((text, parse_result)) = self.vfs.write().get_document_data(&path)
346 {
347 return (text, parse_result);
348 }
349 (String::new(), empty_parse_result())
350 }
351
352 pub fn handle_event(&mut self, event: Event) {
354 match event {
355 Event::Message(msg) => self.handle_message(msg),
356 Event::Task(task_result) => {
357 let response = if task_result.content_modified {
358 lsp_server::Response::new_err(
359 task_result.request_id,
360 CONTENT_MODIFIED,
361 "document changed while the request was in flight; \
362 re-request for up-to-date results"
363 .to_string(),
364 )
365 } else {
366 match task_result.result {
367 Ok(value) => lsp_server::Response::new_ok(task_result.request_id, value),
368 Err(msg) => lsp_server::Response::new_err(
369 task_result.request_id,
370 lsp_server::ErrorCode::InternalError as i32,
371 msg,
372 ),
373 }
374 };
375 self.send(lsp_server::Message::Response(response));
376 }
377 }
378 }
379
380 fn dispatch_async(
390 &self,
391 request_id: lsp_server::RequestId,
392 handler: impl FnOnce() -> Result<serde_json::Value, String> + Send + 'static,
393 ) {
394 self.dispatch_async_inner(request_id, handler, true);
395 }
396
397 fn dispatch_async_unconditional(
412 &self,
413 request_id: lsp_server::RequestId,
414 handler: impl FnOnce() -> Result<serde_json::Value, String> + Send + 'static,
415 ) {
416 self.dispatch_async_inner(request_id, handler, false);
417 }
418
419 fn dispatch_async_inner(
420 &self,
421 request_id: lsp_server::RequestId,
422 handler: impl FnOnce() -> Result<serde_json::Value, String> + Send + 'static,
423 check_staleness: bool,
424 ) {
425 let task_sender = self.task_sender.clone();
426 let revision_arc = self.revision.clone();
433 let dispatch_revision = if check_staleness {
434 Some(revision_arc.load(std::sync::atomic::Ordering::SeqCst))
435 } else {
436 None
437 };
438
439 let _ = self.job_sender.send(Box::new(move || {
440 let result = handler();
441
442 if let Some(rev) = dispatch_revision
448 && revision_arc.load(std::sync::atomic::Ordering::SeqCst) != rev
449 {
450 tracing::debug!(
451 "Result for request {:?} is stale (revision changed); replying ContentModified",
452 request_id
453 );
454 let _ = task_sender.send(TaskResult {
458 request_id,
459 result: Err("stale result (content modified)".to_string()),
460 content_modified: true,
461 });
462 return;
463 }
464
465 let _ = task_sender.send(TaskResult {
467 request_id,
468 result,
469 content_modified: false,
470 });
471 }));
472 }
473
474 fn try_dispatch_async(&self, req: &lsp_server::Request) -> bool {
484 match req.method.as_str() {
485 CodeLensResolve::METHOD => {
497 let id = req.id.clone();
498 let lens: CodeLens = match serde_json::from_value(req.params.clone()) {
499 Ok(l) => l,
500 Err(e) => {
501 self.dispatch_async_unconditional(id, move || Err(e.to_string()));
504 return true;
505 }
506 };
507
508 self.dispatch_async_unconditional(id, move || {
509 let resolved = handle_code_lens_resolve(lens);
510 serde_json::to_value(resolved).map_err(|e| e.to_string())
511 });
512 true
513 }
514 SemanticTokensFullRequest::METHOD => {
516 let id = req.id.clone();
517 let params: SemanticTokensParams = match serde_json::from_value(req.params.clone())
518 {
519 Ok(p) => p,
520 Err(e) => {
521 self.dispatch_async(id, move || Err(e.to_string()));
522 return true;
523 }
524 };
525
526 let uri = ¶ms.text_document.uri;
528 let (text, parse_result) = self.get_document_data(uri);
529 let encoding = self.position_encoding;
533
534 self.dispatch_async(id, move || {
535 let response = handle_semantic_tokens(¶ms, &text, &parse_result, encoding);
536 serde_json::to_value(response).map_err(|e| e.to_string())
537 });
538 true
539 }
540 _ => false,
541 }
542 }
543
544 fn handle_message(&mut self, msg: Message) {
546 match msg {
547 Message::Request(req) => self.handle_request(req),
548 Message::Notification(notif) => self.handle_notification(notif),
549 Message::Response(_resp) => {
550 }
552 }
553 }
554
555 fn handle_request(&mut self, req: lsp_server::Request) {
561 let id = req.id.clone();
562
563 if self.try_dispatch_async(&req) {
566 return; }
568
569 let response = match self.dispatch_sync(req) {
573 Ok(value) => lsp_server::Response::new_ok(id, value),
574 Err(err) => lsp_server::Response::new_err(id, err.error_code() as i32, err.to_string()),
575 };
576
577 self.send(lsp_server::Message::Response(response));
578 }
579
580 fn dispatch_sync(
589 &mut self,
590 req: lsp_server::Request,
591 ) -> Result<serde_json::Value, DispatchError> {
592 if req.method == Initialize::METHOD {
597 let _params: InitializeParams = serde_json::from_value(req.params)
598 .map_err(|e| DispatchError::Handler(e.to_string()))?;
599 return Err(DispatchError::DuplicateInitialize);
600 }
601
602 let method = req.method.clone();
603 let inner: Result<serde_json::Value, String> = match method.as_str() {
604 Shutdown::METHOD => {
605 self.shutdown_requested = true;
606 Ok(serde_json::Value::Null)
607 }
608 Completion::METHOD => self.handle_completion_request(req),
609 GotoDefinition::METHOD => self.handle_goto_definition_request(req),
610 References::METHOD => self.handle_references_request(req),
611 HoverRequest::METHOD => self.handle_hover_request(req),
612 DocumentSymbolRequest::METHOD => self.handle_document_symbols_request(req),
613 SemanticTokensFullDeltaRequest::METHOD => {
614 self.handle_semantic_tokens_delta_request(req)
615 }
616 SemanticTokensRangeRequest::METHOD => self.handle_semantic_tokens_range_request(req),
617 CodeActionRequest::METHOD => self.handle_code_action_request(req),
618 CodeActionResolveRequest::METHOD => self.handle_code_action_resolve_request(req),
619 WorkspaceSymbolRequest::METHOD => self.handle_workspace_symbol_request(req),
620 PrepareRenameRequest::METHOD => self.handle_prepare_rename_request(req),
621 Rename::METHOD => self.handle_rename_request(req),
622 Formatting::METHOD => self.handle_formatting_request(req),
623 RangeFormatting::METHOD => self.handle_range_formatting_request(req),
624 DocumentLinkRequest::METHOD => self.handle_document_link_request(req),
625 DocumentLinkResolve::METHOD => self.handle_document_link_resolve_request(req),
626 InlayHintRequest::METHOD => self.handle_inlay_hint_request(req),
627 InlayHintResolveRequest::METHOD => self.handle_inlay_hint_resolve_request(req),
628 SelectionRangeRequest::METHOD => self.handle_selection_range_request(req),
629 FoldingRangeRequest::METHOD => self.handle_folding_range_request(req),
630 TypeHierarchyPrepare::METHOD => self.handle_prepare_type_hierarchy_request(req),
631 TypeHierarchySupertypes::METHOD => self.handle_type_hierarchy_supertypes_request(req),
632 TypeHierarchySubtypes::METHOD => self.handle_type_hierarchy_subtypes_request(req),
633 DocumentHighlightRequest::METHOD => self.handle_document_highlight_request(req),
634 LinkedEditingRange::METHOD => self.handle_linked_editing_range_request(req),
635 OnTypeFormatting::METHOD => self.handle_on_type_formatting_request(req),
636 CodeLensRequest::METHOD => self.handle_code_lens_request(req),
637 DocumentColor::METHOD => self.handle_document_color_request(req),
638 ColorPresentationRequest::METHOD => self.handle_color_presentation_request(req),
639 GotoDeclaration::METHOD => self.handle_goto_declaration_request(req),
640 CallHierarchyPrepare::METHOD => self.handle_prepare_call_hierarchy_request(req),
641 CallHierarchyIncomingCalls::METHOD => self.handle_incoming_calls_request(req),
642 CallHierarchyOutgoingCalls::METHOD => self.handle_outgoing_calls_request(req),
643 SignatureHelpRequest::METHOD => self.handle_signature_help_request(req),
644 ExecuteCommand::METHOD => self.handle_execute_command_request(req),
645 ResolveCompletionItem::METHOD => self.handle_completion_resolve_request(req),
646 _ => {
647 tracing::warn!("Unhandled request: {method}");
648 return Err(DispatchError::MethodNotFound(method));
649 }
650 };
651 inner.map_err(DispatchError::Handler)
652 }
653
654 fn handle_completion_request(
656 &self,
657 req: lsp_server::Request,
658 ) -> Result<serde_json::Value, String> {
659 let params: CompletionParams =
660 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
661
662 let uri = ¶ms.text_document_position.text_document.uri;
663 let (text, parse_result) = self.get_document_data(uri);
664
665 let ledger_guard = self.ledger_state.read();
667 let ledger_state = if ledger_guard.ledger().is_some() {
668 Some(&*ledger_guard)
669 } else {
670 None
671 };
672
673 let response = handle_completion(
674 ¶ms,
675 &text,
676 &parse_result,
677 ledger_state,
678 self.position_encoding,
679 );
680
681 serde_json::to_value(response).map_err(|e| e.to_string())
682 }
683
684 fn handle_goto_definition_request(
686 &self,
687 req: lsp_server::Request,
688 ) -> Result<serde_json::Value, String> {
689 let params: GotoDefinitionParams =
690 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
691
692 let uri = ¶ms.text_document_position_params.text_document.uri;
693 let (text, parse_result) = self.get_document_data(uri);
694
695 let ledger_guard = self.ledger_state.read();
697 let ledger_state = if ledger_guard.ledger().is_some() {
698 Some(&*ledger_guard)
699 } else {
700 None
701 };
702
703 let response = handle_goto_definition(
704 ¶ms,
705 &text,
706 &parse_result,
707 ledger_state,
708 uri,
709 self.position_encoding,
710 );
711
712 serde_json::to_value(response).map_err(|e| e.to_string())
713 }
714
715 fn handle_references_request(
717 &self,
718 req: lsp_server::Request,
719 ) -> Result<serde_json::Value, String> {
720 let params: ReferenceParams =
721 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
722
723 let uri = ¶ms.text_document_position.text_document.uri;
724 let (text, parse_result) = self.get_document_data(uri);
725
726 let current_canonical = uri_to_path(uri).and_then(|p| p.canonicalize().ok());
730 let other_files = self.other_ledger_files(current_canonical.as_deref());
731
732 let response = handle_references(
733 ¶ms,
734 &text,
735 &parse_result,
736 &other_files,
737 uri,
738 self.position_encoding,
739 );
740
741 serde_json::to_value(response).map_err(|e| e.to_string())
742 }
743
744 fn handle_hover_request(&self, req: lsp_server::Request) -> Result<serde_json::Value, String> {
746 let params: HoverParams = serde_json::from_value(req.params).map_err(|e| e.to_string())?;
747
748 let uri = ¶ms.text_document_position_params.text_document.uri;
749 let (text, parse_result) = self.get_document_data(uri);
750
751 let ledger_guard = self.ledger_state.read();
753 let ledger_state = if ledger_guard.ledger().is_some() {
754 Some(&*ledger_guard)
755 } else {
756 None
757 };
758
759 let response = handle_hover(
760 ¶ms,
761 &text,
762 &parse_result,
763 ledger_state,
764 self.position_encoding,
765 );
766
767 serde_json::to_value(response).map_err(|e| e.to_string())
768 }
769
770 fn handle_document_symbols_request(
772 &self,
773 req: lsp_server::Request,
774 ) -> Result<serde_json::Value, String> {
775 let params: DocumentSymbolParams =
776 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
777
778 let uri = ¶ms.text_document.uri;
779 let (text, parse_result) = self.get_document_data(uri);
780
781 let response =
782 handle_document_symbols(¶ms, &text, &parse_result, self.position_encoding);
783
784 serde_json::to_value(response).map_err(|e| e.to_string())
785 }
786
787 fn handle_semantic_tokens_delta_request(
789 &self,
790 req: lsp_server::Request,
791 ) -> Result<serde_json::Value, String> {
792 let params: SemanticTokensDeltaParams =
793 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
794
795 let uri = ¶ms.text_document.uri;
796 let (text, parse_result) = self.get_document_data(uri);
797
798 let response = handle_semantic_tokens_delta(
802 ¶ms,
803 &text,
804 &parse_result,
805 None,
806 self.position_encoding,
807 );
808
809 serde_json::to_value(response).map_err(|e| e.to_string())
810 }
811
812 fn handle_semantic_tokens_range_request(
814 &self,
815 req: lsp_server::Request,
816 ) -> Result<serde_json::Value, String> {
817 let params: SemanticTokensRangeParams =
818 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
819
820 let uri = ¶ms.text_document.uri;
821 let (text, parse_result) = self.get_document_data(uri);
822
823 let response =
824 handle_semantic_tokens_range(¶ms, &text, &parse_result, self.position_encoding);
825
826 serde_json::to_value(response).map_err(|e| e.to_string())
827 }
828
829 fn handle_code_action_request(
831 &self,
832 req: lsp_server::Request,
833 ) -> Result<serde_json::Value, String> {
834 let params: CodeActionParams =
835 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
836
837 let uri = ¶ms.text_document.uri;
838 let (text, parse_result) = self.get_document_data(uri);
839
840 let response = handle_code_actions(¶ms, &text, &parse_result, self.position_encoding);
841
842 serde_json::to_value(response).map_err(|e| e.to_string())
843 }
844
845 fn handle_code_action_resolve_request(
847 &self,
848 req: lsp_server::Request,
849 ) -> Result<serde_json::Value, String> {
850 let action: CodeAction = serde_json::from_value(req.params).map_err(|e| e.to_string())?;
851
852 let uri: Uri = if let Some(data) = &action.data {
854 data.get("uri")
855 .and_then(|v| v.as_str())
856 .and_then(|s| s.parse().ok())
857 .unwrap_or_else(|| "file:///unknown".parse().unwrap())
858 } else {
859 "file:///unknown".parse().unwrap()
860 };
861
862 let (text, parse_result) = self.get_document_data(&uri);
863
864 let resolved =
865 handle_code_action_resolve(action, &text, &parse_result, &uri, self.position_encoding);
866
867 serde_json::to_value(resolved).map_err(|e| e.to_string())
868 }
869
870 fn handle_workspace_symbol_request(
872 &self,
873 req: lsp_server::Request,
874 ) -> Result<serde_json::Value, String> {
875 let params: WorkspaceSymbolParams =
876 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
877
878 let mut documents: Vec<(Uri, String, Arc<ParseResult>)> = {
880 let mut vfs = self.vfs.write();
881 vfs.iter_with_parse()
882 .map(|(path, content, parse_result)| {
883 let uri_str = format!("file://{}", path.display());
884 let uri: Uri = uri_str
885 .parse()
886 .unwrap_or_else(|_| "file:///".parse().unwrap());
887 (uri, content, parse_result)
888 })
889 .collect()
890 };
891
892 let open_canonical: std::collections::HashSet<PathBuf> = self
898 .vfs
899 .read()
900 .paths()
901 .filter_map(|p| p.canonicalize().ok())
902 .collect();
903 let unopened: Vec<(Uri, String)> = {
907 let ledger_guard = self.ledger_state.read();
908 ledger_guard.ledger().map_or_else(Vec::new, |ledger| {
909 ledger
910 .source_map
911 .files()
912 .iter()
913 .filter(|f| {
914 f.path
915 .canonicalize()
916 .ok()
917 .is_none_or(|c| !open_canonical.contains(&c))
918 })
919 .filter_map(|f| {
920 match format!("file://{}", f.path.display()).parse::<Uri>() {
921 Ok(uri) => Some((uri, f.source.to_string())),
922 Err(_) => {
923 tracing::warn!(
924 "workspace/symbol: skipping ledger file with a non-file:// URI: {}",
925 f.path.display()
926 );
927 None
928 }
929 }
930 })
931 .collect()
932 })
933 };
934 for (uri, source) in unopened {
935 let parsed = Arc::new(parse(&source));
936 documents.push((uri, source, parsed));
937 }
938
939 let response = handle_workspace_symbols(¶ms, &documents, self.position_encoding);
940
941 serde_json::to_value(response).map_err(|e| e.to_string())
942 }
943
944 fn handle_prepare_rename_request(
946 &self,
947 req: lsp_server::Request,
948 ) -> Result<serde_json::Value, String> {
949 let params: TextDocumentPositionParams =
950 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
951
952 let uri = ¶ms.text_document.uri;
953 let (text, parse_result) = self.get_document_data(uri);
954
955 let response = handle_prepare_rename(¶ms, &text, &parse_result, self.position_encoding);
956
957 serde_json::to_value(response).map_err(|e| e.to_string())
958 }
959
960 fn handle_rename_request(&self, req: lsp_server::Request) -> Result<serde_json::Value, String> {
962 let params: RenameParams = serde_json::from_value(req.params).map_err(|e| e.to_string())?;
963
964 let uri = ¶ms.text_document_position.text_document.uri;
965 let (text, parse_result) = self.get_document_data(uri);
966
967 let current_canonical = uri_to_path(uri).and_then(|p| p.canonicalize().ok());
973 let other_files = self.other_ledger_files(current_canonical.as_deref());
974
975 let response = handle_rename(
976 ¶ms,
977 &text,
978 &parse_result,
979 &other_files,
980 self.position_encoding,
981 );
982
983 serde_json::to_value(response).map_err(|e| e.to_string())
984 }
985
986 fn other_ledger_files(
992 &self,
993 current_canonical: Option<&std::path::Path>,
994 ) -> Vec<(Uri, String)> {
995 let open: std::collections::HashMap<PathBuf, String> = {
997 let vfs = self.vfs.read();
998 vfs.paths()
999 .filter_map(|p| Some((p.canonicalize().ok()?, vfs.get_content(p)?)))
1000 .collect()
1001 };
1002
1003 let ledger_guard = self.ledger_state.read();
1004 let mut out = Vec::new();
1005 if let Some(ledger) = ledger_guard.ledger() {
1006 for f in ledger.source_map.files() {
1007 let canon = f.path.canonicalize().ok();
1008 if canon.as_deref() == current_canonical {
1009 continue;
1010 }
1011 let Ok(uri) = format!("file://{}", f.path.display()).parse::<Uri>() else {
1012 tracing::warn!(
1013 "rename: skipping ledger file with a non-file:// URI: {}",
1014 f.path.display()
1015 );
1016 continue;
1017 };
1018 let source = canon
1021 .as_ref()
1022 .and_then(|c| open.get(c).cloned())
1023 .unwrap_or_else(|| f.source.to_string());
1024 out.push((uri, source));
1025 }
1026 }
1027 out
1028 }
1029
1030 fn handle_formatting_request(
1032 &self,
1033 req: lsp_server::Request,
1034 ) -> Result<serde_json::Value, String> {
1035 let params: DocumentFormattingParams =
1036 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1037
1038 let uri = ¶ms.text_document.uri;
1039 let (text, parse_result) = self.get_document_data(uri);
1040
1041 let response = handle_formatting(¶ms, &text, &parse_result, self.position_encoding);
1042
1043 serde_json::to_value(response).map_err(|e| e.to_string())
1044 }
1045
1046 fn handle_folding_range_request(
1048 &self,
1049 req: lsp_server::Request,
1050 ) -> Result<serde_json::Value, String> {
1051 let params: FoldingRangeParams =
1052 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1053
1054 let uri = ¶ms.text_document.uri;
1055 let (text, parse_result) = self.get_document_data(uri);
1056
1057 let response = handle_folding_ranges(¶ms, &text, &parse_result, self.position_encoding);
1058
1059 serde_json::to_value(response).map_err(|e| e.to_string())
1060 }
1061
1062 fn handle_range_formatting_request(
1064 &self,
1065 req: lsp_server::Request,
1066 ) -> Result<serde_json::Value, String> {
1067 let params: DocumentRangeFormattingParams =
1068 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1069
1070 let uri = ¶ms.text_document.uri;
1071 let (text, parse_result) = self.get_document_data(uri);
1072
1073 let response =
1074 handle_range_formatting(¶ms, &text, &parse_result, self.position_encoding);
1075
1076 serde_json::to_value(response).map_err(|e| e.to_string())
1077 }
1078
1079 fn handle_document_link_request(
1081 &self,
1082 req: lsp_server::Request,
1083 ) -> Result<serde_json::Value, String> {
1084 let params: DocumentLinkParams =
1085 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1086
1087 let uri = ¶ms.text_document.uri;
1088 let (text, parse_result) = self.get_document_data(uri);
1089
1090 let response = handle_document_links(¶ms, &text, &parse_result, self.position_encoding);
1091
1092 serde_json::to_value(response).map_err(|e| e.to_string())
1093 }
1094
1095 fn handle_document_link_resolve_request(
1097 &self,
1098 req: lsp_server::Request,
1099 ) -> Result<serde_json::Value, String> {
1100 let link: DocumentLink = serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1101
1102 let resolved = handle_document_link_resolve(link);
1103
1104 serde_json::to_value(resolved).map_err(|e| e.to_string())
1105 }
1106
1107 fn handle_inlay_hint_request(
1109 &self,
1110 req: lsp_server::Request,
1111 ) -> Result<serde_json::Value, String> {
1112 let params: InlayHintParams =
1113 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1114
1115 let uri = ¶ms.text_document.uri;
1116 let (text, parse_result) = self.get_document_data(uri);
1117
1118 let response = handle_inlay_hints(¶ms, &text, &parse_result, self.position_encoding);
1119
1120 serde_json::to_value(response).map_err(|e| e.to_string())
1121 }
1122
1123 fn handle_inlay_hint_resolve_request(
1125 &self,
1126 req: lsp_server::Request,
1127 ) -> Result<serde_json::Value, String> {
1128 let hint: InlayHint = serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1129
1130 let uri: Uri = if let Some(data) = &hint.data {
1132 data.get("uri")
1133 .and_then(|v| v.as_str())
1134 .and_then(|s| s.parse().ok())
1135 .unwrap_or_else(|| "file:///unknown".parse().unwrap())
1136 } else {
1137 "file:///unknown".parse().unwrap()
1138 };
1139
1140 let (_text, parse_result) = self.get_document_data(&uri);
1141 let ledger_guard = self.ledger_state.read();
1144 let resolved = handle_inlay_hint_resolve(hint, &parse_result, ledger_guard.ledger());
1145
1146 serde_json::to_value(resolved).map_err(|e| e.to_string())
1147 }
1148
1149 fn handle_selection_range_request(
1151 &self,
1152 req: lsp_server::Request,
1153 ) -> Result<serde_json::Value, String> {
1154 let params: SelectionRangeParams =
1155 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1156
1157 let uri = ¶ms.text_document.uri;
1158 let (text, parse_result) = self.get_document_data(uri);
1159
1160 let response =
1163 handle_selection_range(¶ms, &text, &parse_result, self.position_encoding);
1164
1165 serde_json::to_value(response).map_err(|e| e.to_string())
1166 }
1167
1168 fn handle_prepare_type_hierarchy_request(
1170 &self,
1171 req: lsp_server::Request,
1172 ) -> Result<serde_json::Value, String> {
1173 let params: TypeHierarchyPrepareParams =
1174 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1175
1176 let uri = ¶ms.text_document_position_params.text_document.uri;
1177 let (text, parse_result) = self.get_document_data(uri);
1178
1179 let response = handle_prepare_type_hierarchy(
1180 ¶ms,
1181 &text,
1182 &parse_result,
1183 uri,
1184 self.position_encoding,
1185 );
1186
1187 serde_json::to_value(response).map_err(|e| e.to_string())
1188 }
1189
1190 fn handle_type_hierarchy_supertypes_request(
1192 &self,
1193 req: lsp_server::Request,
1194 ) -> Result<serde_json::Value, String> {
1195 let params: TypeHierarchySupertypesParams =
1196 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1197
1198 let uri = ¶ms.item.uri;
1199 let (text, parse_result) = self.get_document_data(uri);
1200
1201 let response =
1202 handle_supertypes(¶ms, &text, &parse_result, uri, self.position_encoding);
1203
1204 serde_json::to_value(response).map_err(|e| e.to_string())
1205 }
1206
1207 fn handle_type_hierarchy_subtypes_request(
1209 &self,
1210 req: lsp_server::Request,
1211 ) -> Result<serde_json::Value, String> {
1212 let params: TypeHierarchySubtypesParams =
1213 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1214
1215 let uri = ¶ms.item.uri;
1216 let (text, parse_result) = self.get_document_data(uri);
1217
1218 let response = handle_subtypes(¶ms, &text, &parse_result, uri, self.position_encoding);
1219
1220 serde_json::to_value(response).map_err(|e| e.to_string())
1221 }
1222
1223 fn handle_document_highlight_request(
1225 &self,
1226 req: lsp_server::Request,
1227 ) -> Result<serde_json::Value, String> {
1228 let params: DocumentHighlightParams =
1229 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1230
1231 let uri = ¶ms.text_document_position_params.text_document.uri;
1232 let (text, parse_result) = self.get_document_data(uri);
1233
1234 let response =
1235 handle_document_highlight(¶ms, &text, &parse_result, self.position_encoding);
1236
1237 serde_json::to_value(response).map_err(|e| e.to_string())
1238 }
1239
1240 fn handle_linked_editing_range_request(
1242 &self,
1243 req: lsp_server::Request,
1244 ) -> Result<serde_json::Value, String> {
1245 let params: LinkedEditingRangeParams =
1246 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1247
1248 let uri = ¶ms.text_document_position_params.text_document.uri;
1249 let (text, parse_result) = self.get_document_data(uri);
1250
1251 let response =
1252 handle_linked_editing_range(¶ms, &text, &parse_result, self.position_encoding);
1253
1254 serde_json::to_value(response).map_err(|e| e.to_string())
1255 }
1256
1257 fn handle_on_type_formatting_request(
1259 &self,
1260 req: lsp_server::Request,
1261 ) -> Result<serde_json::Value, String> {
1262 let params: DocumentOnTypeFormattingParams =
1263 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1264
1265 let uri = ¶ms.text_document_position.text_document.uri;
1266
1267 let text = if let Some(path) = uri_to_path(uri) {
1269 self.vfs.read().get_content(&path).unwrap_or_default()
1270 } else {
1271 String::new()
1272 };
1273
1274 let response = handle_on_type_formatting(¶ms, &text, self.position_encoding);
1275
1276 serde_json::to_value(response).map_err(|e| e.to_string())
1277 }
1278
1279 fn handle_code_lens_request(
1281 &self,
1282 req: lsp_server::Request,
1283 ) -> Result<serde_json::Value, String> {
1284 let params: CodeLensParams =
1285 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1286
1287 let uri = ¶ms.text_document.uri;
1288 let (text, parse_result) = self.get_document_data(uri);
1289
1290 let cached_diagnostics = self.diagnostics.get(uri).map(Vec::as_slice);
1301
1302 let response = handle_code_lens(
1303 ¶ms,
1304 &text,
1305 &parse_result,
1306 cached_diagnostics,
1307 self.position_encoding,
1308 );
1309
1310 serde_json::to_value(response).map_err(|e| e.to_string())
1311 }
1312
1313 fn handle_document_color_request(
1315 &self,
1316 req: lsp_server::Request,
1317 ) -> Result<serde_json::Value, String> {
1318 let params: DocumentColorParams =
1319 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1320
1321 let uri = ¶ms.text_document.uri;
1322 let (text, parse_result) = self.get_document_data(uri);
1323
1324 let response = handle_document_color(¶ms, &text, &parse_result, self.position_encoding);
1325
1326 serde_json::to_value(response).map_err(|e| e.to_string())
1327 }
1328
1329 fn handle_color_presentation_request(
1331 &self,
1332 req: lsp_server::Request,
1333 ) -> Result<serde_json::Value, String> {
1334 let params: ColorPresentationParams =
1335 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1336
1337 let response = handle_color_presentation(¶ms);
1339
1340 serde_json::to_value(response).map_err(|e| e.to_string())
1341 }
1342
1343 fn handle_goto_declaration_request(
1345 &self,
1346 req: lsp_server::Request,
1347 ) -> Result<serde_json::Value, String> {
1348 let params: GotoDefinitionParams =
1349 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1350
1351 let uri = ¶ms.text_document_position_params.text_document.uri;
1352 let (text, parse_result) = self.get_document_data(uri);
1353
1354 let ledger_guard = self.ledger_state.read();
1356 let ledger_state = if ledger_guard.ledger().is_some() {
1357 Some(&*ledger_guard)
1358 } else {
1359 None
1360 };
1361
1362 let response = handle_goto_declaration(
1364 ¶ms,
1365 &text,
1366 &parse_result,
1367 ledger_state,
1368 uri,
1369 self.position_encoding,
1370 );
1371
1372 serde_json::to_value(response).map_err(|e| e.to_string())
1373 }
1374
1375 fn handle_prepare_call_hierarchy_request(
1377 &self,
1378 req: lsp_server::Request,
1379 ) -> Result<serde_json::Value, String> {
1380 let params: CallHierarchyPrepareParams =
1381 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1382
1383 let uri = ¶ms.text_document_position_params.text_document.uri;
1384 let (text, parse_result) = self.get_document_data(uri);
1385
1386 let response = handle_prepare_call_hierarchy(
1387 ¶ms,
1388 &text,
1389 &parse_result,
1390 uri,
1391 self.position_encoding,
1392 );
1393
1394 serde_json::to_value(response).map_err(|e| e.to_string())
1395 }
1396
1397 fn handle_incoming_calls_request(
1399 &self,
1400 req: lsp_server::Request,
1401 ) -> Result<serde_json::Value, String> {
1402 let params: CallHierarchyIncomingCallsParams =
1403 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1404
1405 let uri = ¶ms.item.uri;
1406 let (text, parse_result) = self.get_document_data(uri);
1407
1408 let response =
1409 handle_incoming_calls(¶ms, &text, &parse_result, uri, self.position_encoding);
1410
1411 serde_json::to_value(response).map_err(|e| e.to_string())
1412 }
1413
1414 fn handle_outgoing_calls_request(
1416 &self,
1417 req: lsp_server::Request,
1418 ) -> Result<serde_json::Value, String> {
1419 let params: CallHierarchyOutgoingCallsParams =
1420 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1421
1422 let uri = ¶ms.item.uri;
1423 let (text, parse_result) = self.get_document_data(uri);
1424
1425 let response =
1426 handle_outgoing_calls(¶ms, &text, &parse_result, uri, self.position_encoding);
1427
1428 serde_json::to_value(response).map_err(|e| e.to_string())
1429 }
1430
1431 fn handle_signature_help_request(
1433 &self,
1434 req: lsp_server::Request,
1435 ) -> Result<serde_json::Value, String> {
1436 let params: SignatureHelpParams =
1437 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1438
1439 let uri = ¶ms.text_document_position_params.text_document.uri;
1440
1441 let text = if let Some(path) = uri_to_path(uri) {
1443 self.vfs.read().get_content(&path).unwrap_or_default()
1444 } else {
1445 String::new()
1446 };
1447
1448 let response = handle_signature_help(¶ms, &text, self.position_encoding);
1450
1451 serde_json::to_value(response).map_err(|e| e.to_string())
1452 }
1453
1454 fn handle_execute_command_request(
1456 &self,
1457 req: lsp_server::Request,
1458 ) -> Result<serde_json::Value, String> {
1459 let params: ExecuteCommandParams =
1460 serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1461
1462 let uri_from_args: Option<Uri> = params
1464 .arguments
1465 .first()
1466 .and_then(|arg| arg.get("uri"))
1467 .and_then(|v| v.as_str())
1468 .and_then(|s| s.parse().ok());
1469
1470 if let Some(uri) = uri_from_args {
1471 let (text, parse_result) = self.get_document_data(&uri);
1472 let result =
1473 handle_execute_command(¶ms, &text, &parse_result, &uri, self.position_encoding);
1474 self.send_show_message(result.show_message);
1475 return Ok(result.response.unwrap_or(serde_json::Value::Null));
1476 }
1477
1478 let first_path = self.vfs.read().paths().next().cloned();
1480 let path = match first_path {
1481 Some(p) => p,
1482 None => {
1483 return Ok(serde_json::json!({
1484 "error": "No document open"
1485 }));
1486 }
1487 };
1488
1489 #[cfg(not(windows))]
1491 let uri: Uri = format!("file://{}", path.display())
1492 .parse()
1493 .map_err(|e| format!("{:?}", e))?;
1494 #[cfg(windows)]
1495 let uri: Uri = format!("file:///{}", path.display())
1496 .parse()
1497 .map_err(|e| format!("{:?}", e))?;
1498
1499 let (text, parse_result) = self.get_document_data(&uri);
1500 let result =
1501 handle_execute_command(¶ms, &text, &parse_result, &uri, self.position_encoding);
1502 self.send_show_message(result.show_message);
1503 Ok(result.response.unwrap_or(serde_json::Value::Null))
1504 }
1505
1506 fn send_show_message(&self, params: Option<lsp_types::ShowMessageParams>) {
1508 let Some(params) = params else { return };
1509 let notif = lsp_server::Notification::new(
1510 <lsp_types::notification::ShowMessage as lsp_types::notification::Notification>::METHOD
1511 .to_string(),
1512 params,
1513 );
1514 self.send(lsp_server::Message::Notification(notif));
1515 }
1516
1517 fn handle_completion_resolve_request(
1519 &self,
1520 req: lsp_server::Request,
1521 ) -> Result<serde_json::Value, String> {
1522 let item: CompletionItem = serde_json::from_value(req.params).map_err(|e| e.to_string())?;
1523
1524 let uri: Uri = if let Some(data) = &item.data {
1526 data.get("uri")
1527 .and_then(|v| v.as_str())
1528 .and_then(|s| s.parse().ok())
1529 .unwrap_or_else(|| "file:///unknown".parse().unwrap())
1530 } else {
1531 "file:///unknown".parse().unwrap()
1532 };
1533
1534 let (_text, parse_result) = self.get_document_data(&uri);
1535
1536 let ledger_guard = self.ledger_state.read();
1545 let directives = ledger_guard
1546 .directives()
1547 .unwrap_or_else(|| parse_result.directives.as_slice());
1548 let resolved = handle_completion_resolve(item, directives);
1549
1550 serde_json::to_value(resolved).map_err(|e| e.to_string())
1551 }
1552
1553 fn handle_notification(&mut self, notif: lsp_server::Notification) {
1555 match notif.method.as_str() {
1557 DidOpenTextDocument::METHOD => {
1558 if let Ok(params) =
1559 serde_json::from_value::<lsp_types::DidOpenTextDocumentParams>(notif.params)
1560 {
1561 self.on_did_open(params);
1562 }
1563 }
1564 DidChangeTextDocument::METHOD => {
1565 if let Ok(params) =
1566 serde_json::from_value::<lsp_types::DidChangeTextDocumentParams>(notif.params)
1567 {
1568 self.on_did_change(params);
1569 }
1570 }
1571 DidCloseTextDocument::METHOD => {
1572 if let Ok(params) =
1573 serde_json::from_value::<lsp_types::DidCloseTextDocumentParams>(notif.params)
1574 {
1575 self.on_did_close(params);
1576 }
1577 }
1578 DidChangeWatchedFiles::METHOD => {
1579 if let Ok(params) =
1580 serde_json::from_value::<lsp_types::DidChangeWatchedFilesParams>(notif.params)
1581 {
1582 self.on_did_change_watched_files(params);
1583 }
1584 }
1585 "initialized" => {
1586 tracing::info!("Client initialized");
1587 self.register_file_watchers();
1589 }
1590 "exit" => {
1591 tracing::info!("Exit notification received");
1592 let code = if self.shutdown_requested { 0 } else { 1 };
1593 self.pending_exit_code = Some(code);
1597 if let Some(action) = self.exit_action.take() {
1602 action(code);
1603 }
1604 }
1605 _ => {
1606 tracing::debug!("Unhandled notification: {}", notif.method);
1607 }
1608 }
1609 }
1610
1611 fn on_did_open(&mut self, params: lsp_types::DidOpenTextDocumentParams) {
1613 let uri = params.text_document.uri;
1614 let text = params.text_document.text;
1615 let version = params.text_document.version;
1616
1617 tracing::info!("Document opened: {}", uri.as_str());
1618
1619 if let Some(path) = uri_to_path(&uri) {
1621 self.vfs.write().open(path, text.clone(), version);
1622 }
1623
1624 self.bump_revision();
1626
1627 self.publish_diagnostics(&uri, &text);
1629 }
1630
1631 fn on_did_change(&mut self, params: lsp_types::DidChangeTextDocumentParams) {
1633 let uri = params.text_document.uri;
1634 let version = params.text_document.version;
1635
1636 if let Some(change) = params.content_changes.into_iter().last() {
1638 let text = change.text;
1639
1640 tracing::debug!("Document changed: {}", uri.as_str());
1641
1642 if let Some(path) = uri_to_path(&uri) {
1644 self.vfs.write().update(&path, text.clone(), version);
1645 }
1646
1647 self.bump_revision();
1649
1650 self.publish_diagnostics(&uri, &text);
1652 }
1653 }
1654
1655 fn on_did_close(&mut self, params: lsp_types::DidCloseTextDocumentParams) {
1657 let uri = params.text_document.uri;
1658
1659 tracing::info!("Document closed: {}", uri.as_str());
1660
1661 if let Some(path) = uri_to_path(&uri) {
1663 self.vfs.write().close(&path);
1664 }
1665
1666 self.diagnostics.remove(&uri);
1668 self.send_diagnostics(&uri, vec![]);
1669 }
1670
1671 fn on_did_change_watched_files(&mut self, params: lsp_types::DidChangeWatchedFilesParams) {
1673 tracing::info!("Watched files changed: {} files", params.changes.len());
1674
1675 let mut should_reload_journal = false;
1676 let mut should_revalidate = false;
1677
1678 for change in params.changes {
1679 tracing::debug!("File {:?}: {:?}", change.uri.as_str(), change.typ);
1680
1681 if let Some(path) = uri_to_path(&change.uri) {
1683 let ledger_guard = self.ledger_state.read();
1684 if ledger_guard.contains_file(&path) {
1685 should_reload_journal = true;
1686 }
1687 }
1688
1689 if change.uri.as_str().ends_with(".beancount") || change.uri.as_str().ends_with(".bean")
1691 {
1692 should_revalidate = true;
1693 }
1694 }
1695
1696 if should_reload_journal {
1698 tracing::info!("Reloading journal due to external file changes");
1699 self.reload_journal();
1700 }
1701
1702 if should_revalidate {
1704 self.revalidate_open_documents();
1705 }
1706 }
1707
1708 fn revalidate_open_documents(&mut self) {
1710 let paths: Vec<_> = self.vfs.read().paths().cloned().collect();
1711
1712 let documents: Vec<_> = paths
1714 .into_iter()
1715 .filter_map(|path| {
1716 let content = self.vfs.read().get_content(&path)?;
1717 let uri_str = format!("file://{}", path.display());
1718 let uri = uri_str.parse::<Uri>().ok()?;
1719 Some((uri, content))
1720 })
1721 .collect();
1722
1723 for (uri, content) in documents {
1725 tracing::debug!("Revalidating: {}", uri.as_str());
1726 self.publish_diagnostics(&uri, &content);
1727 }
1728 }
1729
1730 fn register_file_watchers(&self) {
1732 let watchers = vec![
1734 lsp_types::FileSystemWatcher {
1735 glob_pattern: lsp_types::GlobPattern::String("**/*.beancount".to_string()),
1736 kind: Some(lsp_types::WatchKind::all()),
1737 },
1738 lsp_types::FileSystemWatcher {
1739 glob_pattern: lsp_types::GlobPattern::String("**/*.bean".to_string()),
1740 kind: Some(lsp_types::WatchKind::all()),
1741 },
1742 ];
1743
1744 let registration = lsp_types::Registration {
1745 id: "file-watcher".to_string(),
1746 method: "workspace/didChangeWatchedFiles".to_string(),
1747 register_options: Some(
1748 serde_json::to_value(lsp_types::DidChangeWatchedFilesRegistrationOptions {
1749 watchers,
1750 })
1751 .unwrap_or_default(),
1752 ),
1753 };
1754
1755 let params = lsp_types::RegistrationParams {
1756 registrations: vec![registration],
1757 };
1758
1759 let request = lsp_server::Request::new(
1761 lsp_server::RequestId::from("register-file-watchers".to_string()),
1762 "client/registerCapability".to_string(),
1763 params,
1764 );
1765
1766 self.send(lsp_server::Message::Request(request));
1767 tracing::info!("Registered file watchers for *.beancount and *.bean files");
1768 }
1769
1770 fn publish_diagnostics(&mut self, uri: &Uri, text: &str) {
1783 let result = parse(text);
1785
1786 let current_canonical_path = uri_to_path(uri).and_then(|p| p.canonicalize().ok());
1790
1791 let other_buffer_parses: Vec<(PathBuf, Arc<ParseResult>)> = {
1805 let mut vfs = self.vfs.write();
1806 vfs.iter_with_parse()
1807 .filter_map(|(path, _text, parsed)| {
1808 let canonical = path.canonicalize().ok()?;
1809 if Some(&canonical) == current_canonical_path.as_ref() {
1810 return None;
1811 }
1812 if !parsed.errors.is_empty() {
1813 return None;
1814 }
1815 Some((canonical, parsed))
1816 })
1817 .collect()
1818 };
1819
1820 let ledger_guard = self.ledger_state.read();
1822 let (ledger_state, current_file_id) = if ledger_guard.ledger().is_some() {
1823 let file_id = current_canonical_path.as_ref().and_then(|canonical| {
1827 ledger_guard.ledger().and_then(|ledger| {
1828 ledger.source_map.files().iter().find_map(|f| {
1829 f.path
1830 .canonicalize()
1831 .ok()
1832 .filter(|canonical_f| canonical_f == canonical)
1833 .map(|_| f.id as u16)
1834 })
1835 })
1836 });
1837 (Some(&*ledger_guard), file_id)
1838 } else {
1839 (None, None)
1840 };
1841
1842 let other_buffer_overlays: Vec<(u16, &[Spanned<Directive>])> =
1846 if let Some(ls) = ledger_state {
1847 let ledger = ls.ledger().expect("ledger_state.ledger() checked above");
1848 other_buffer_parses
1849 .iter()
1850 .filter_map(|(canonical, parsed)| {
1851 let fid = ledger.source_map.files().iter().find_map(|f| {
1852 f.path
1853 .canonicalize()
1854 .ok()
1855 .filter(|canonical_f| canonical_f == canonical)
1856 .map(|_| f.id as u16)
1857 })?;
1858 Some((fid, parsed.directives.as_slice()))
1859 })
1860 .collect()
1861 } else {
1862 Vec::new()
1863 };
1864
1865 let diagnostics = all_diagnostics(
1867 &result,
1868 text,
1869 ledger_state,
1870 current_file_id,
1871 current_canonical_path.as_deref(),
1872 &other_buffer_overlays,
1873 self.position_encoding,
1874 );
1875
1876 let cross_file = self.compute_unopened_ledger_diagnostics(
1882 ledger_state,
1883 current_canonical_path.as_deref(),
1884 current_file_id,
1885 &result,
1886 &other_buffer_overlays,
1887 );
1888 drop(ledger_guard); tracing::debug!(
1891 "Publishing {} diagnostics for {} (file_id: {:?})",
1892 diagnostics.len(),
1893 uri.as_str(),
1894 current_file_id
1895 );
1896
1897 self.diagnostics.insert(uri.clone(), diagnostics.clone());
1899 self.send_diagnostics(uri, diagnostics);
1900
1901 self.publish_cross_file_diagnostics(cross_file);
1903 }
1904
1905 fn compute_unopened_ledger_diagnostics(
1916 &self,
1917 ledger_state: Option<&crate::ledger_state::LedgerState>,
1918 current_canonical: Option<&std::path::Path>,
1919 current_file_id: Option<u16>,
1920 current_parse: &ParseResult,
1921 other_buffer_overlays: &[(u16, &[Spanned<Directive>])],
1922 ) -> Vec<(Uri, Vec<lsp_types::Diagnostic>)> {
1923 let Some(ls) = ledger_state else {
1924 return Vec::new();
1925 };
1926 let Some(ledger) = ls.ledger() else {
1927 return Vec::new();
1928 };
1929 if ledger.source_map.files().len() <= 1 {
1930 return Vec::new();
1931 }
1932
1933 let open_canonical: std::collections::HashSet<PathBuf> = {
1936 let vfs = self.vfs.read();
1937 vfs.paths().filter_map(|p| p.canonicalize().ok()).collect()
1938 };
1939
1940 let mut overlay_all: Vec<(u16, &[Spanned<Directive>])> =
1947 Vec::with_capacity(1 + other_buffer_overlays.len());
1948 if let Some(fid) = current_file_id
1949 && current_parse.errors.is_empty()
1950 {
1951 overlay_all.push((fid, current_parse.directives.as_slice()));
1952 }
1953 overlay_all.extend_from_slice(other_buffer_overlays);
1954
1955 let mut out = Vec::new();
1956 for f in ledger.source_map.files() {
1957 let canonical = f.path.canonicalize().ok();
1958 if canonical.as_deref() == current_canonical {
1960 continue;
1961 }
1962 if let Some(c) = &canonical
1963 && open_canonical.contains(c)
1964 {
1965 continue;
1966 }
1967 let Ok(uri) = format!("file://{}", f.path.display()).parse::<Uri>() else {
1975 tracing::warn!(
1976 "skipping cross-file diagnostics for {}: path is not a valid file:// URI",
1977 f.path.display()
1978 );
1979 continue;
1980 };
1981 let parsed = parse(&f.source);
1982 let diags = all_diagnostics(
1983 &parsed,
1984 &f.source,
1985 ledger_state,
1986 Some(f.id as u16),
1987 Some(f.path.as_path()),
1988 &overlay_all,
1989 self.position_encoding,
1990 );
1991 out.push((uri, diags));
1992 }
1993 out
1994 }
1995
1996 #[allow(clippy::mutable_key_type)] fn publish_cross_file_diagnostics(
2008 &mut self,
2009 cross_file: Vec<(Uri, Vec<lsp_types::Diagnostic>)>,
2010 ) {
2011 let mut erroring: std::collections::HashSet<Uri> = std::collections::HashSet::new();
2013 for (uri, diags) in cross_file {
2014 if diags.is_empty() {
2015 continue;
2016 }
2017 erroring.insert(uri.clone());
2018 self.cross_file_diag_uris.insert(uri.clone());
2019 self.diagnostics.insert(uri.clone(), diags.clone());
2020 self.send_diagnostics(&uri, diags);
2021 }
2022
2023 let open_uris: std::collections::HashSet<Uri> = self
2026 .vfs
2027 .read()
2028 .paths()
2029 .filter_map(|p| format!("file://{}", p.display()).parse::<Uri>().ok())
2030 .collect();
2031 let stale: Vec<Uri> = self
2032 .cross_file_diag_uris
2033 .iter()
2034 .filter(|u| !erroring.contains(*u) && !open_uris.contains(*u))
2035 .cloned()
2036 .collect();
2037 for uri in stale {
2038 self.cross_file_diag_uris.remove(&uri);
2039 self.diagnostics.remove(&uri);
2040 self.send_diagnostics(&uri, vec![]);
2041 }
2042 }
2043
2044 fn send_diagnostics(&self, uri: &Uri, diagnostics: Vec<lsp_types::Diagnostic>) {
2046 let params = PublishDiagnosticsParams {
2047 uri: uri.clone(),
2048 diagnostics,
2049 version: None,
2050 };
2051
2052 let notif = lsp_server::Notification::new(PublishDiagnostics::METHOD.to_string(), params);
2053
2054 self.send(lsp_server::Message::Notification(notif));
2055 }
2056
2057 fn send(&self, msg: lsp_server::Message) {
2059 if let Err(e) = self.sender.send(msg) {
2060 tracing::error!("Failed to send message: {}", e);
2061 }
2062 }
2063}
2064
2065#[must_use]
2088pub fn run_main_loop(
2089 receiver: Receiver<lsp_server::Message>,
2090 sender: Sender<lsp_server::Message>,
2091 journal_file: Option<PathBuf>,
2092 position_encoding: crate::handlers::utils::PositionEncoding,
2093) -> i32 {
2094 run_main_loop_with_exit_action(
2099 receiver,
2100 sender,
2101 journal_file,
2102 position_encoding,
2103 |_code| {},
2104 )
2105}
2106
2107#[must_use]
2140pub fn run_main_loop_with_exit_action<F>(
2141 receiver: Receiver<lsp_server::Message>,
2142 sender: Sender<lsp_server::Message>,
2143 journal_file: Option<PathBuf>,
2144 position_encoding: crate::handlers::utils::PositionEncoding,
2145 exit_action: F,
2146) -> i32
2147where
2148 F: FnOnce(i32) + Send + 'static,
2149{
2150 let mut state = MainLoopState::new(sender, journal_file).with_exit_action(exit_action);
2151 state.position_encoding = position_encoding;
2152 let task_receiver = state.task_receiver.clone();
2153
2154 tracing::info!("Main loop started");
2155
2156 let exit_code = loop {
2157 crossbeam_channel::select! {
2158 recv(receiver) -> msg => {
2159 let msg = match msg {
2160 Ok(msg) => msg,
2161 Err(_) => break 0, };
2163 let event = match msg {
2164 lsp_server::Message::Request(req) => Event::Message(Message::Request(req)),
2165 lsp_server::Message::Notification(notif) => {
2166 Event::Message(Message::Notification(notif))
2167 }
2168 lsp_server::Message::Response(resp) => Event::Message(Message::Response(resp)),
2169 };
2170 state.handle_event(event);
2171 }
2172 recv(task_receiver) -> task_result => {
2173 if let Ok(result) = task_result {
2174 state.handle_event(Event::Task(result));
2175 }
2176 }
2177 }
2178 if let Some(code) = state.pending_exit_code {
2185 break code;
2186 }
2187 };
2188
2189 tracing::info!("Main loop ended (exit code {exit_code})");
2190 exit_code
2191}
2192
2193#[cfg(test)]
2194mod tests {
2195 use super::*;
2196
2197 #[test]
2205 fn dispatch_error_codes() {
2206 assert_eq!(
2209 DispatchError::MethodNotFound("foo/bar".into()).error_code() as i32,
2210 lsp_server::ErrorCode::MethodNotFound as i32,
2211 );
2212 assert_eq!(
2213 DispatchError::DuplicateInitialize.error_code() as i32,
2214 lsp_server::ErrorCode::InvalidRequest as i32,
2215 );
2216 assert_eq!(
2217 DispatchError::Handler("boom".into()).error_code() as i32,
2218 lsp_server::ErrorCode::InternalError as i32,
2219 );
2220 }
2221
2222 #[test]
2226 fn dispatch_error_display() {
2227 let unhandled = DispatchError::MethodNotFound("foo/bar".into()).to_string();
2228 assert!(
2229 unhandled.contains("foo/bar"),
2230 "MethodNotFound should include the method name: {unhandled}",
2231 );
2232
2233 let dup_init = DispatchError::DuplicateInitialize.to_string();
2234 assert!(
2235 dup_init.contains("exactly once"),
2236 "DuplicateInitialize message should cite the spec invariant: {dup_init}",
2237 );
2238
2239 let handler = DispatchError::Handler("custom failure".into()).to_string();
2240 assert_eq!(handler, "custom failure");
2241 }
2242}