Skip to main content

beancount_language_server/
lib.rs

1pub mod beancount_data;
2mod capabilities;
3pub mod checkers;
4mod config;
5mod dispatcher;
6pub mod document;
7//pub mod error;
8pub mod forest;
9pub mod handlers;
10pub mod progress;
11pub mod providers;
12mod query_utils;
13pub mod server;
14//pub mod session;
15mod treesitter_utils;
16mod utils;
17
18use crate::config::Config;
19use crate::server::LspServerState;
20use anyhow::Result;
21use lsp_server::Connection;
22use lsp_types::InitializeParams;
23use serde::{Serialize, de::DeserializeOwned};
24use utils::ToFilePath;
25
26pub fn run_server() -> Result<()> {
27    tracing::info!("beancount-language-server started");
28
29    //Setup IO connections
30    tracing::debug!("Setting up stdio connections");
31    let (connection, io_threads) = lsp_server::Connection::stdio();
32
33    //wait for client to connection
34    tracing::debug!("Waiting for client initialization");
35    let (request_id, initialize_params) = connection.initialize_start()?;
36    tracing::debug!("Received initialize request: id={}", request_id);
37
38    let initialize_params = match serde_json::from_value::<InitializeParams>(initialize_params) {
39        Ok(params) => {
40            tracing::debug!("Successfully parsed initialization parameters");
41            params
42        }
43        Err(e) => {
44            tracing::error!("Failed to parse initialization parameters: {}", e);
45            return Err(e.into());
46        }
47    };
48
49    if let Some(client_info) = &initialize_params.client_info {
50        tracing::info!(
51            "Connected to client: '{}' version {}",
52            client_info.name,
53            client_info.version.as_deref().unwrap_or("unknown")
54        );
55    } else {
56        tracing::warn!("Client did not provide client info");
57    }
58
59    // Parse config first so we can conditionally register capabilities
60    let config = {
61        let root_file = if let Some(workspace_folders) = &initialize_params.workspace_folders {
62            let root = workspace_folders
63                .first()
64                .and_then(|folder| folder.uri.to_file_path().ok())
65                .unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
66            tracing::info!("Using workspace folder as root: {}", root.display());
67            root
68        } else {
69            #[allow(deprecated)]
70            let root = match initialize_params
71                .root_uri
72                .and_then(|it| it.to_file_path().ok())
73            {
74                Some(it) => it,
75                None => std::env::current_dir()?,
76            };
77            tracing::info!("Using root URI as root: {}", root.display());
78            root
79        };
80
81        let mut config = Config::new(root_file);
82        if let Some(json) = initialize_params.initialization_options {
83            tracing::info!("Applying initialization options: {}", json);
84            match config.update(json) {
85                Ok(()) => tracing::debug!("Configuration updated successfully"),
86                Err(e) => {
87                    tracing::warn!("Failed to update configuration: {}", e);
88                    return Err(e);
89                }
90            }
91        } else {
92            tracing::debug!("No initialization options provided, using default config");
93        }
94        config
95    };
96
97    let server_capabilities = capabilities::server_capabilities();
98    tracing::debug!("Server capabilities configured");
99
100    let initialize_result = lsp_types::InitializeResult {
101        capabilities: server_capabilities,
102        server_info: Some(lsp_types::ServerInfo {
103            name: String::from("beancount-language-server"),
104            version: Some(String::from(env!("CARGO_PKG_VERSION"))),
105        }),
106    };
107
108    let initialize_result =
109        serde_json::to_value(initialize_result).expect("Failed to serialize InitializeResult");
110
111    connection.initialize_finish(request_id, initialize_result)?;
112    tracing::info!("Initialization completed successfully");
113
114    tracing::debug!("Starting main loop");
115    main_loop(connection, config)?;
116
117    tracing::debug!("Waiting for IO threads to complete");
118    io_threads.join()?;
119    tracing::info!("Language server stopped");
120
121    Ok(())
122}
123
124pub fn main_loop(connection: Connection, config: Config) -> Result<()> {
125    tracing::info!("initial config: {:#?}", config);
126    LspServerState::new(connection.sender, config).run(connection.receiver)
127}
128
129pub fn from_json<T: DeserializeOwned>(what: &'static str, json: serde_json::Value) -> Result<T> {
130    T::deserialize(&json)
131        .map_err(|e| anyhow::anyhow!("could not deserialize {}: {} - {}", what, e, json))
132}
133
134pub fn to_json<T: Serialize>(value: T) -> Result<serde_json::Value> {
135    serde_json::to_value(value).map_err(|e| anyhow::anyhow!("could not serialize to json {}", e))
136}