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 protocol;
21pub mod socket_path;
22pub mod uri;
23
24use std::path::PathBuf;
25use std::time::Duration;
26use tracing_subscriber::EnvFilter;
27use tracing_subscriber::fmt::writer::MakeWriterExt;
28
29pub use client::{ClientError, ClientResult, LspClient};
30pub use daemon::LspDaemon;
31pub use error::{DaemonError, DaemonResult};
32
33#[derive(Debug, thiserror::Error)]
34pub enum LspdRunError {
35    #[error("Failed to create tokio runtime: {0}")]
36    RuntimeCreate(std::io::Error),
37    #[error("Failed to open log file: {0}")]
38    LogFileOpen(std::io::Error),
39    #[error("{0}")]
40    Daemon(#[from] DaemonError),
41}
42pub use language_catalog::LanguageId;
43pub use language_catalog::{
44    LANGUAGE_METADATA, LanguageMetadata, LanguageServerMetadata, LspConfig, detect_project_languages,
45    extensions_for_alias, from_lsp_id, get_config_for_language, metadata_for, server_metadata_for_language,
46};
47pub use socket_path::{ensure_socket_dir, lockfile_path, log_file_path, socket_path};
48
49pub use protocol::{
50    DaemonRequest, DaemonResponse, InitializeRequest, LSP_REQUEST_TIMED_OUT, LSP_TRANSPORT_CLOSED, LspErrorResponse,
51    MAX_MESSAGE_SIZE, ProtocolError,
52};
53pub use uri::{UriError, path_to_uri, uri_to_path};
54
55#[derive(clap::Args)]
56pub struct LspdArgs {
57    /// Socket path to listen on
58    #[arg(long)]
59    pub socket: PathBuf,
60
61    /// Idle timeout in seconds (0 = no timeout)
62    #[arg(long, default_value = "300")]
63    pub idle_timeout: u64,
64
65    /// Per-request LSP timeout in seconds. When a language server exceeds it,
66    /// the server is killed and replaced on the next request. The env var
67    /// matters because the daemon is usually auto-spawned with fixed arguments,
68    /// so the environment is the only configuration channel available to
69    /// whoever launches the client.
70    #[arg(long, env = "AETHER_LSPD_REQUEST_TIMEOUT", default_value = "120", value_parser = clap::value_parser!(u64).range(1..))]
71    pub request_timeout: u64,
72
73    /// Log level (trace, debug, info, warn, error)
74    #[arg(long, default_value = "info")]
75    pub log_level: String,
76
77    /// Log file path. Required for daemon mode since stderr is redirected to /dev/null.
78    #[arg(long)]
79    pub log_file: Option<PathBuf>,
80}
81
82pub fn run_lspd(args: LspdArgs) -> Result<(), LspdRunError> {
83    let idle_timeout = if args.idle_timeout == 0 { None } else { Some(Duration::from_secs(args.idle_timeout)) };
84    let request_timeout = Duration::from_secs(args.request_timeout);
85
86    let runtime = tokio::runtime::Runtime::new().map_err(LspdRunError::RuntimeCreate)?;
87
88    runtime.block_on(async {
89        let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&args.log_level));
90
91        if let Some(ref log_file) = args.log_file {
92            if let Some(parent) = log_file.parent() {
93                let _ = std::fs::create_dir_all(parent);
94            }
95            let file = std::fs::OpenOptions::new()
96                .create(true)
97                .append(true)
98                .open(log_file)
99                .map_err(LspdRunError::LogFileOpen)?;
100
101            tracing_subscriber::fmt()
102                .with_env_filter(filter)
103                .with_target(true)
104                .with_ansi(false)
105                .with_writer(file.with_max_level(tracing::Level::TRACE))
106                .init();
107        } else {
108            tracing_subscriber::fmt().with_env_filter(filter).with_target(true).init();
109        }
110
111        tracing::info!("Starting LSP daemon on socket: {:?}", args.socket);
112        LspDaemon::new(args.socket, idle_timeout, request_timeout).run().await.map_err(LspdRunError::Daemon)
113    })
114}