use crate::handlers::call_hierarchy::{
handle_incoming_calls, handle_outgoing_calls, handle_prepare_call_hierarchy,
};
use crate::handlers::code_actions::{handle_code_action_resolve, handle_code_actions};
use crate::handlers::code_lens::{handle_code_lens, handle_code_lens_resolve};
use crate::handlers::completion::handle_completion;
use crate::handlers::completion_resolve::handle_completion_resolve;
use crate::handlers::declaration::handle_goto_declaration;
use crate::handlers::definition::handle_goto_definition;
use crate::handlers::diagnostics::all_diagnostics;
use crate::handlers::document_color::{handle_color_presentation, handle_document_color};
use crate::handlers::document_highlight::handle_document_highlight;
use crate::handlers::document_links::{handle_document_link_resolve, handle_document_links};
use crate::handlers::execute_command::handle_execute_command;
use crate::handlers::folding::handle_folding_ranges;
use crate::handlers::formatting::handle_formatting;
use crate::handlers::hover::handle_hover;
use crate::handlers::inlay_hints::{handle_inlay_hint_resolve, handle_inlay_hints};
use crate::handlers::linked_editing::handle_linked_editing_range;
use crate::handlers::on_type_formatting::handle_on_type_formatting;
use crate::handlers::range_formatting::handle_range_formatting;
use crate::handlers::references::handle_references;
use crate::handlers::rename::{handle_prepare_rename, handle_rename};
use crate::handlers::selection_range::handle_selection_range;
use crate::handlers::semantic_tokens::{
handle_semantic_tokens, handle_semantic_tokens_delta, handle_semantic_tokens_range,
};
use crate::handlers::signature_help::handle_signature_help;
use crate::handlers::symbols::handle_document_symbols;
use crate::handlers::type_hierarchy::{
handle_prepare_type_hierarchy, handle_subtypes, handle_supertypes,
};
use crate::handlers::workspace_symbols::handle_workspace_symbols;
use crate::ledger_state::{SharedLedgerState, new_shared_ledger_state};
use crate::snapshot::bump_revision;
use crate::uri_to_path;
use crate::vfs::Vfs;
use crossbeam_channel::{Receiver, Sender};
use lsp_types::notification::{
DidChangeTextDocument, DidChangeWatchedFiles, DidCloseTextDocument, DidOpenTextDocument,
Notification, PublishDiagnostics,
};
use lsp_types::request::{
CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, CodeLensResolve,
ColorPresentationRequest, Completion, DocumentColor, DocumentHighlightRequest,
DocumentLinkRequest, DocumentLinkResolve, DocumentSymbolRequest, ExecuteCommand,
FoldingRangeRequest, Formatting, GotoDeclaration, GotoDefinition, HoverRequest, Initialize,
InlayHintRequest, InlayHintResolveRequest, LinkedEditingRange, OnTypeFormatting,
PrepareRenameRequest, RangeFormatting, References, Rename, Request, ResolveCompletionItem,
SelectionRangeRequest, SemanticTokensFullDeltaRequest, SemanticTokensFullRequest,
SemanticTokensRangeRequest, Shutdown, SignatureHelpRequest, TypeHierarchyPrepare,
TypeHierarchySubtypes, TypeHierarchySupertypes, WorkspaceSymbolRequest,
};
use lsp_types::{
CallHierarchyIncomingCallsParams, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
CodeAction, CodeActionParams, CodeLens, CodeLensParams, ColorPresentationParams,
CompletionItem, CompletionParams, DiagnosticOptions, DiagnosticServerCapabilities,
DocumentColorParams, DocumentFormattingParams, DocumentHighlightParams, DocumentLink,
DocumentLinkParams, DocumentOnTypeFormattingParams, DocumentRangeFormattingParams,
DocumentSymbolParams, ExecuteCommandParams, FoldingRangeParams, GotoDefinitionParams,
HoverParams, InitializeParams, InitializeResult, InlayHint, InlayHintParams,
LinkedEditingRangeParams, PublishDiagnosticsParams, ReferenceParams, RenameParams,
SelectionRangeParams, SemanticTokensDeltaParams, SemanticTokensParams,
SemanticTokensRangeParams, ServerCapabilities, ServerInfo, SignatureHelpParams,
TextDocumentPositionParams, TextDocumentSyncCapability, TextDocumentSyncKind,
TypeHierarchyPrepareParams, TypeHierarchySubtypesParams, TypeHierarchySupertypesParams, Uri,
WorkspaceSymbolParams,
};
use parking_lot::RwLock;
use rustledger_parser::{ParseResult, parse};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
#[derive(Debug)]
pub enum Event {
Message(Message),
#[allow(dead_code)] Task(TaskResult),
}
#[derive(Debug)]
pub enum Message {
Request(lsp_server::Request),
Notification(lsp_server::Notification),
Response(lsp_server::Response),
}
#[derive(Debug)]
#[allow(dead_code)] pub struct TaskResult {
pub request_id: lsp_server::RequestId,
pub result: Result<serde_json::Value, String>,
}
pub struct MainLoopState {
pub vfs: Arc<RwLock<Vfs>>,
pub sender: Sender<lsp_server::Message>,
pub diagnostics: HashMap<Uri, Vec<lsp_types::Diagnostic>>,
pub shutdown_requested: bool,
pub ledger_state: SharedLedgerState,
pub journal_file: Option<PathBuf>,
}
fn empty_parse_result() -> Arc<ParseResult> {
Arc::new(parse(""))
}
impl MainLoopState {
pub fn new(sender: Sender<lsp_server::Message>, journal_file: Option<PathBuf>) -> Self {
let ledger_state = new_shared_ledger_state();
if let Some(ref path) = journal_file {
let mut state = ledger_state.write();
if let Err(e) = state.load(path) {
tracing::error!("Failed to load journal file: {e}");
}
}
Self {
vfs: Arc::new(RwLock::new(Vfs::new())),
sender,
diagnostics: HashMap::new(),
shutdown_requested: false,
ledger_state,
journal_file,
}
}
pub fn reload_journal(&mut self) {
if let Some(ref path) = self.journal_file {
let mut state = self.ledger_state.write();
if let Err(e) = state.load(path) {
tracing::error!("Failed to reload journal file: {e}");
}
}
}
fn get_document_data(&self, uri: &Uri) -> (String, Arc<ParseResult>) {
if let Some(path) = uri_to_path(uri)
&& let Some((text, parse_result)) = self.vfs.write().get_document_data(&path)
{
return (text, parse_result);
}
(String::new(), empty_parse_result())
}
pub fn handle_event(&mut self, event: Event) {
match event {
Event::Message(msg) => self.handle_message(msg),
Event::Task(_result) => {
}
}
}
fn handle_message(&mut self, msg: Message) {
match msg {
Message::Request(req) => self.handle_request(req),
Message::Notification(notif) => self.handle_notification(notif),
Message::Response(_resp) => {
}
}
}
fn handle_request(&mut self, req: lsp_server::Request) {
let id = req.id.clone();
let result = match req.method.as_str() {
Initialize::METHOD => self.handle_initialize(req),
Shutdown::METHOD => {
self.shutdown_requested = true;
Ok(serde_json::Value::Null)
}
Completion::METHOD => self.handle_completion_request(req),
GotoDefinition::METHOD => self.handle_goto_definition_request(req),
References::METHOD => self.handle_references_request(req),
HoverRequest::METHOD => self.handle_hover_request(req),
DocumentSymbolRequest::METHOD => self.handle_document_symbols_request(req),
SemanticTokensFullRequest::METHOD => self.handle_semantic_tokens_request(req),
SemanticTokensFullDeltaRequest::METHOD => {
self.handle_semantic_tokens_delta_request(req)
}
SemanticTokensRangeRequest::METHOD => self.handle_semantic_tokens_range_request(req),
CodeActionRequest::METHOD => self.handle_code_action_request(req),
CodeActionResolveRequest::METHOD => self.handle_code_action_resolve_request(req),
WorkspaceSymbolRequest::METHOD => self.handle_workspace_symbol_request(req),
PrepareRenameRequest::METHOD => self.handle_prepare_rename_request(req),
Rename::METHOD => self.handle_rename_request(req),
Formatting::METHOD => self.handle_formatting_request(req),
RangeFormatting::METHOD => self.handle_range_formatting_request(req),
DocumentLinkRequest::METHOD => self.handle_document_link_request(req),
DocumentLinkResolve::METHOD => self.handle_document_link_resolve_request(req),
InlayHintRequest::METHOD => self.handle_inlay_hint_request(req),
InlayHintResolveRequest::METHOD => self.handle_inlay_hint_resolve_request(req),
SelectionRangeRequest::METHOD => self.handle_selection_range_request(req),
FoldingRangeRequest::METHOD => self.handle_folding_range_request(req),
TypeHierarchyPrepare::METHOD => self.handle_prepare_type_hierarchy_request(req),
TypeHierarchySupertypes::METHOD => self.handle_type_hierarchy_supertypes_request(req),
TypeHierarchySubtypes::METHOD => self.handle_type_hierarchy_subtypes_request(req),
DocumentHighlightRequest::METHOD => self.handle_document_highlight_request(req),
LinkedEditingRange::METHOD => self.handle_linked_editing_range_request(req),
OnTypeFormatting::METHOD => self.handle_on_type_formatting_request(req),
CodeLensRequest::METHOD => self.handle_code_lens_request(req),
CodeLensResolve::METHOD => self.handle_code_lens_resolve_request(req),
DocumentColor::METHOD => self.handle_document_color_request(req),
ColorPresentationRequest::METHOD => self.handle_color_presentation_request(req),
GotoDeclaration::METHOD => self.handle_goto_declaration_request(req),
CallHierarchyPrepare::METHOD => self.handle_prepare_call_hierarchy_request(req),
CallHierarchyIncomingCalls::METHOD => self.handle_incoming_calls_request(req),
CallHierarchyOutgoingCalls::METHOD => self.handle_outgoing_calls_request(req),
SignatureHelpRequest::METHOD => self.handle_signature_help_request(req),
ExecuteCommand::METHOD => self.handle_execute_command_request(req),
ResolveCompletionItem::METHOD => self.handle_completion_resolve_request(req),
_ => {
tracing::warn!("Unhandled request: {}", req.method);
Err(format!("Unhandled request: {}", req.method))
}
};
let response = match result {
Ok(value) => lsp_server::Response::new_ok(id, value),
Err(msg) => {
let error_code = if msg.starts_with("Unhandled request") {
lsp_server::ErrorCode::MethodNotFound
} else {
lsp_server::ErrorCode::InternalError
};
lsp_server::Response::new_err(id, error_code as i32, msg)
}
};
self.send(lsp_server::Message::Response(response));
}
fn handle_initialize(&mut self, req: lsp_server::Request) -> Result<serde_json::Value, String> {
let _params: InitializeParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let capabilities = ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)),
diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
..Default::default()
})),
..Default::default()
};
let result = InitializeResult {
capabilities,
server_info: Some(ServerInfo {
name: "rledger-lsp".to_string(),
version: Some(crate::VERSION.to_string()),
}),
};
serde_json::to_value(result).map_err(|e| e.to_string())
}
fn handle_completion_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: CompletionParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document_position.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let ledger_guard = self.ledger_state.read();
let ledger_state = if ledger_guard.ledger().is_some() {
Some(&*ledger_guard)
} else {
None
};
let response = handle_completion(¶ms, &text, &parse_result, ledger_state);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_goto_definition_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: GotoDefinitionParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document_position_params.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_goto_definition(¶ms, &text, &parse_result, uri);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_references_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: ReferenceParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document_position.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_references(¶ms, &text, &parse_result, uri);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_hover_request(&self, req: lsp_server::Request) -> Result<serde_json::Value, String> {
let params: HoverParams = serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document_position_params.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_hover(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_document_symbols_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: DocumentSymbolParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_document_symbols(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_semantic_tokens_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: SemanticTokensParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_semantic_tokens(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_semantic_tokens_delta_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: SemanticTokensDeltaParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_semantic_tokens_delta(¶ms, &text, &parse_result, None);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_semantic_tokens_range_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: SemanticTokensRangeParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_semantic_tokens_range(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_code_action_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: CodeActionParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_code_actions(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_code_action_resolve_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let action: CodeAction = serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri: Uri = if let Some(data) = &action.data {
data.get("uri")
.and_then(|v| v.as_str())
.and_then(|s| s.parse().ok())
.unwrap_or_else(|| "file:///unknown".parse().unwrap())
} else {
"file:///unknown".parse().unwrap()
};
let (text, parse_result) = self.get_document_data(&uri);
let resolved = handle_code_action_resolve(action, &text, &parse_result, &uri);
serde_json::to_value(resolved).map_err(|e| e.to_string())
}
fn handle_workspace_symbol_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: WorkspaceSymbolParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let mut vfs = self.vfs.write();
let documents: Vec<_> = vfs
.iter_with_parse()
.map(|(path, content, parse_result)| {
let uri_str = format!("file://{}", path.display());
let uri: Uri = uri_str
.parse()
.unwrap_or_else(|_| "file:///".parse().unwrap());
(uri, content, parse_result)
})
.collect();
let response = handle_workspace_symbols(¶ms, &documents);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_prepare_rename_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: TextDocumentPositionParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_prepare_rename(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_rename_request(&self, req: lsp_server::Request) -> Result<serde_json::Value, String> {
let params: RenameParams = serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document_position.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_rename(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_formatting_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: DocumentFormattingParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_formatting(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_folding_range_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: FoldingRangeParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_folding_ranges(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_range_formatting_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: DocumentRangeFormattingParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_range_formatting(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_document_link_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: DocumentLinkParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_document_links(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_document_link_resolve_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let link: DocumentLink = serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let resolved = handle_document_link_resolve(link);
serde_json::to_value(resolved).map_err(|e| e.to_string())
}
fn handle_inlay_hint_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: InlayHintParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_inlay_hints(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_inlay_hint_resolve_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let hint: InlayHint = serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri: Uri = if let Some(data) = &hint.data {
data.get("uri")
.and_then(|v| v.as_str())
.and_then(|s| s.parse().ok())
.unwrap_or_else(|| "file:///unknown".parse().unwrap())
} else {
"file:///unknown".parse().unwrap()
};
let (_text, parse_result) = self.get_document_data(&uri);
let resolved = handle_inlay_hint_resolve(hint, &parse_result);
serde_json::to_value(resolved).map_err(|e| e.to_string())
}
fn handle_selection_range_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: SelectionRangeParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_selection_range(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_prepare_type_hierarchy_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: TypeHierarchyPrepareParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document_position_params.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_prepare_type_hierarchy(¶ms, &text, &parse_result, uri);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_type_hierarchy_supertypes_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: TypeHierarchySupertypesParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.item.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_supertypes(¶ms, &text, &parse_result, uri);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_type_hierarchy_subtypes_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: TypeHierarchySubtypesParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.item.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_subtypes(¶ms, &text, &parse_result, uri);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_document_highlight_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: DocumentHighlightParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document_position_params.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_document_highlight(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_linked_editing_range_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: LinkedEditingRangeParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document_position_params.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_linked_editing_range(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_on_type_formatting_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: DocumentOnTypeFormattingParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document_position.text_document.uri;
let text = if let Some(path) = uri_to_path(uri) {
self.vfs.read().get_content(&path).unwrap_or_default()
} else {
String::new()
};
let response = handle_on_type_formatting(¶ms, &text);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_code_lens_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: CodeLensParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_code_lens(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_code_lens_resolve_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let lens: CodeLens = serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri: Uri = if let Some(data) = &lens.data {
data.get("uri")
.and_then(|v| v.as_str())
.and_then(|s| s.parse().ok())
.unwrap_or_else(|| "file:///unknown".parse().unwrap())
} else {
"file:///unknown".parse().unwrap()
};
let (_text, parse_result) = self.get_document_data(&uri);
let ledger_directives = {
let ledger_guard = self.ledger_state.read();
ledger_guard.directives().map(|d| d.to_vec())
};
let resolved = handle_code_lens_resolve(lens, &parse_result, ledger_directives.as_deref());
serde_json::to_value(resolved).map_err(|e| e.to_string())
}
fn handle_document_color_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: DocumentColorParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_document_color(¶ms, &text, &parse_result);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_color_presentation_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: ColorPresentationParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let response = handle_color_presentation(¶ms);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_goto_declaration_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: GotoDefinitionParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document_position_params.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_goto_declaration(¶ms, &text, &parse_result, uri);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_prepare_call_hierarchy_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: CallHierarchyPrepareParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document_position_params.text_document.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_prepare_call_hierarchy(¶ms, &text, &parse_result, uri);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_incoming_calls_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: CallHierarchyIncomingCallsParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.item.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_incoming_calls(¶ms, &text, &parse_result, uri);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_outgoing_calls_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: CallHierarchyOutgoingCallsParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.item.uri;
let (text, parse_result) = self.get_document_data(uri);
let response = handle_outgoing_calls(¶ms, &text, &parse_result, uri);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_signature_help_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: SignatureHelpParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri = ¶ms.text_document_position_params.text_document.uri;
let text = if let Some(path) = uri_to_path(uri) {
self.vfs.read().get_content(&path).unwrap_or_default()
} else {
String::new()
};
let response = handle_signature_help(¶ms, &text);
serde_json::to_value(response).map_err(|e| e.to_string())
}
fn handle_execute_command_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let params: ExecuteCommandParams =
serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri_from_args: Option<Uri> = params
.arguments
.first()
.and_then(|arg| arg.get("uri"))
.and_then(|v| v.as_str())
.and_then(|s| s.parse().ok());
if let Some(uri) = uri_from_args {
let (text, parse_result) = self.get_document_data(&uri);
let response = handle_execute_command(¶ms, &text, &parse_result, &uri);
return Ok(response.unwrap_or(serde_json::Value::Null));
}
let first_path = self.vfs.read().paths().next().cloned();
let path = match first_path {
Some(p) => p,
None => {
return Ok(serde_json::json!({
"error": "No document open"
}));
}
};
#[cfg(not(windows))]
let uri: Uri = format!("file://{}", path.display())
.parse()
.map_err(|e| format!("{:?}", e))?;
#[cfg(windows)]
let uri: Uri = format!("file:///{}", path.display())
.parse()
.map_err(|e| format!("{:?}", e))?;
let (text, parse_result) = self.get_document_data(&uri);
let response = handle_execute_command(¶ms, &text, &parse_result, &uri);
Ok(response.unwrap_or(serde_json::Value::Null))
}
fn handle_completion_resolve_request(
&self,
req: lsp_server::Request,
) -> Result<serde_json::Value, String> {
let item: CompletionItem = serde_json::from_value(req.params).map_err(|e| e.to_string())?;
let uri: Uri = if let Some(data) = &item.data {
data.get("uri")
.and_then(|v| v.as_str())
.and_then(|s| s.parse().ok())
.unwrap_or_else(|| "file:///unknown".parse().unwrap())
} else {
"file:///unknown".parse().unwrap()
};
let (_text, parse_result) = self.get_document_data(&uri);
let resolved = handle_completion_resolve(item, &parse_result);
serde_json::to_value(resolved).map_err(|e| e.to_string())
}
fn handle_notification(&mut self, notif: lsp_server::Notification) {
match notif.method.as_str() {
DidOpenTextDocument::METHOD => {
if let Ok(params) =
serde_json::from_value::<lsp_types::DidOpenTextDocumentParams>(notif.params)
{
self.on_did_open(params);
}
}
DidChangeTextDocument::METHOD => {
if let Ok(params) =
serde_json::from_value::<lsp_types::DidChangeTextDocumentParams>(notif.params)
{
self.on_did_change(params);
}
}
DidCloseTextDocument::METHOD => {
if let Ok(params) =
serde_json::from_value::<lsp_types::DidCloseTextDocumentParams>(notif.params)
{
self.on_did_close(params);
}
}
DidChangeWatchedFiles::METHOD => {
if let Ok(params) =
serde_json::from_value::<lsp_types::DidChangeWatchedFilesParams>(notif.params)
{
self.on_did_change_watched_files(params);
}
}
"initialized" => {
tracing::info!("Client initialized");
self.register_file_watchers();
}
"exit" => {
tracing::info!("Exit notification received");
std::process::exit(if self.shutdown_requested { 0 } else { 1 });
}
_ => {
tracing::debug!("Unhandled notification: {}", notif.method);
}
}
}
fn on_did_open(&mut self, params: lsp_types::DidOpenTextDocumentParams) {
let uri = params.text_document.uri;
let text = params.text_document.text;
let version = params.text_document.version;
tracing::info!("Document opened: {}", uri.as_str());
if let Some(path) = uri_to_path(&uri) {
self.vfs.write().open(path, text.clone(), version);
}
bump_revision();
self.publish_diagnostics(&uri, &text);
}
fn on_did_change(&mut self, params: lsp_types::DidChangeTextDocumentParams) {
let uri = params.text_document.uri;
let version = params.text_document.version;
if let Some(change) = params.content_changes.into_iter().last() {
let text = change.text;
tracing::debug!("Document changed: {}", uri.as_str());
if let Some(path) = uri_to_path(&uri) {
self.vfs.write().update(&path, text.clone(), version);
}
bump_revision();
self.publish_diagnostics(&uri, &text);
}
}
fn on_did_close(&mut self, params: lsp_types::DidCloseTextDocumentParams) {
let uri = params.text_document.uri;
tracing::info!("Document closed: {}", uri.as_str());
if let Some(path) = uri_to_path(&uri) {
self.vfs.write().close(&path);
}
self.diagnostics.remove(&uri);
self.send_diagnostics(&uri, vec![]);
}
fn on_did_change_watched_files(&mut self, params: lsp_types::DidChangeWatchedFilesParams) {
tracing::info!("Watched files changed: {} files", params.changes.len());
let mut should_reload_journal = false;
let mut should_revalidate = false;
for change in params.changes {
tracing::debug!("File {:?}: {:?}", change.uri.as_str(), change.typ);
if let Some(path) = uri_to_path(&change.uri) {
let ledger_guard = self.ledger_state.read();
if ledger_guard.contains_file(&path) {
should_reload_journal = true;
}
}
if change.uri.as_str().ends_with(".beancount") || change.uri.as_str().ends_with(".bean")
{
should_revalidate = true;
}
}
if should_reload_journal {
tracing::info!("Reloading journal due to external file changes");
self.reload_journal();
}
if should_revalidate {
self.revalidate_open_documents();
}
}
fn revalidate_open_documents(&mut self) {
let paths: Vec<_> = self.vfs.read().paths().cloned().collect();
let documents: Vec<_> = paths
.into_iter()
.filter_map(|path| {
let content = self.vfs.read().get_content(&path)?;
let uri_str = format!("file://{}", path.display());
let uri = uri_str.parse::<Uri>().ok()?;
Some((uri, content))
})
.collect();
for (uri, content) in documents {
tracing::debug!("Revalidating: {}", uri.as_str());
self.publish_diagnostics(&uri, &content);
}
}
fn register_file_watchers(&self) {
let watchers = vec![
lsp_types::FileSystemWatcher {
glob_pattern: lsp_types::GlobPattern::String("**/*.beancount".to_string()),
kind: Some(lsp_types::WatchKind::all()),
},
lsp_types::FileSystemWatcher {
glob_pattern: lsp_types::GlobPattern::String("**/*.bean".to_string()),
kind: Some(lsp_types::WatchKind::all()),
},
];
let registration = lsp_types::Registration {
id: "file-watcher".to_string(),
method: "workspace/didChangeWatchedFiles".to_string(),
register_options: Some(
serde_json::to_value(lsp_types::DidChangeWatchedFilesRegistrationOptions {
watchers,
})
.unwrap_or_default(),
),
};
let params = lsp_types::RegistrationParams {
registrations: vec![registration],
};
let request = lsp_server::Request::new(
lsp_server::RequestId::from("register-file-watchers".to_string()),
"client/registerCapability".to_string(),
params,
);
self.send(lsp_server::Message::Request(request));
tracing::info!("Registered file watchers for *.beancount and *.bean files");
}
fn publish_diagnostics(&mut self, uri: &Uri, text: &str) {
let result = parse(text);
let ledger_guard = self.ledger_state.read();
let (ledger_state, current_file_id) = if ledger_guard.ledger().is_some() {
let file_id = uri_to_path(uri).and_then(|uri_path| {
let canonical_uri_path = uri_path.canonicalize().ok()?;
ledger_guard.ledger().and_then(|ledger| {
ledger.source_map.files().iter().find_map(|f| {
f.path
.canonicalize()
.ok()
.filter(|canonical_f| *canonical_f == canonical_uri_path)
.map(|_| f.id as u16)
})
})
});
(Some(&*ledger_guard), file_id)
} else {
(None, None)
};
let diagnostics = all_diagnostics(&result, text, ledger_state, current_file_id);
drop(ledger_guard);
tracing::debug!(
"Publishing {} diagnostics for {} (file_id: {:?})",
diagnostics.len(),
uri.as_str(),
current_file_id
);
self.diagnostics.insert(uri.clone(), diagnostics.clone());
self.send_diagnostics(uri, diagnostics);
}
fn send_diagnostics(&self, uri: &Uri, diagnostics: Vec<lsp_types::Diagnostic>) {
let params = PublishDiagnosticsParams {
uri: uri.clone(),
diagnostics,
version: None,
};
let notif = lsp_server::Notification::new(PublishDiagnostics::METHOD.to_string(), params);
self.send(lsp_server::Message::Notification(notif));
}
fn send(&self, msg: lsp_server::Message) {
if let Err(e) = self.sender.send(msg) {
tracing::error!("Failed to send message: {}", e);
}
}
}
pub fn run_main_loop(
receiver: Receiver<lsp_server::Message>,
sender: Sender<lsp_server::Message>,
journal_file: Option<PathBuf>,
) {
let mut state = MainLoopState::new(sender, journal_file);
tracing::info!("Main loop started");
for msg in receiver {
let event = match msg {
lsp_server::Message::Request(req) => Event::Message(Message::Request(req)),
lsp_server::Message::Notification(notif) => {
Event::Message(Message::Notification(notif))
}
lsp_server::Message::Response(resp) => Event::Message(Message::Response(resp)),
};
state.handle_event(event);
}
tracing::info!("Main loop ended");
}