Skip to main content

aether_lspd/
lib.rs

1#![doc = include_str!("../README.md")]
2
3#[cfg(feature = "testing")]
4pub mod testing;
5
6mod client_connection;
7mod diagnostics_store;
8mod document_lifecycle;
9mod file_watcher;
10pub mod language_catalog;
11mod pid_lockfile;
12mod process_transport;
13mod refresh_queue;
14mod workspace_registry;
15mod workspace_session;
16
17mod client;
18pub mod daemon;
19pub mod error;
20pub mod lsp_utils;
21pub mod protocol;
22pub mod socket_path;
23pub mod uri;
24
25use std::path::PathBuf;
26use std::time::Duration;
27use tracing_subscriber::EnvFilter;
28use tracing_subscriber::fmt::writer::MakeWriterExt;
29
30pub use client::{ClientError, ClientResult, LspClient};
31pub use daemon::{LspDaemon, run_daemon};
32pub use error::{DaemonError, DaemonResult};
33pub use language_catalog::LanguageId;
34pub use language_catalog::{
35    LANGUAGE_METADATA, LanguageMetadata, LspConfig, extensions_for_alias, from_lsp_id, get_config_for_language,
36    metadata_for,
37};
38pub use lsp_utils::symbol_kind_to_string;
39pub use socket_path::{ensure_socket_dir, lockfile_path, log_file_path, socket_path};
40
41pub use protocol::{
42    DaemonRequest, DaemonResponse, InitializeRequest, LspErrorResponse, MAX_MESSAGE_SIZE, ProtocolError,
43};
44pub use uri::{path_to_uri, uri_to_path};
45
46#[derive(clap::Args)]
47pub struct LspdArgs {
48    /// Socket path to listen on
49    #[arg(long)]
50    pub socket: PathBuf,
51
52    /// Idle timeout in seconds (0 = no timeout)
53    #[arg(long, default_value = "300")]
54    pub idle_timeout: u64,
55
56    /// Log level (trace, debug, info, warn, error)
57    #[arg(long, default_value = "info")]
58    pub log_level: String,
59
60    /// Log file path. Required for daemon mode since stderr is redirected to /dev/null.
61    #[arg(long)]
62    pub log_file: Option<PathBuf>,
63}
64
65pub fn run_lspd(args: LspdArgs) -> Result<(), String> {
66    let idle_timeout = if args.idle_timeout == 0 { None } else { Some(Duration::from_secs(args.idle_timeout)) };
67
68    let runtime = tokio::runtime::Runtime::new().map_err(|e| format!("Failed to create tokio runtime: {e}"))?;
69
70    runtime.block_on(async {
71        let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&args.log_level));
72
73        if let Some(ref log_file) = args.log_file {
74            if let Some(parent) = log_file.parent() {
75                let _ = std::fs::create_dir_all(parent);
76            }
77            let file = std::fs::OpenOptions::new()
78                .create(true)
79                .append(true)
80                .open(log_file)
81                .map_err(|e| format!("Failed to open log file: {e}"))?;
82
83            tracing_subscriber::fmt()
84                .with_env_filter(filter)
85                .with_target(true)
86                .with_ansi(false)
87                .with_writer(file.with_max_level(tracing::Level::TRACE))
88                .init();
89        } else {
90            tracing_subscriber::fmt().with_env_filter(filter).with_target(true).init();
91        }
92
93        tracing::info!("Starting LSP daemon on socket: {:?}", args.socket);
94        run_daemon(args.socket, idle_timeout).await.map_err(|e| format!("Daemon error: {e}"))
95    })
96}