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;
7mod client_connection;
8mod daemon;
9mod diagnostics_store;
10mod document_lifecycle;
11pub mod error;
12mod file_watcher;
13pub mod language_catalog;
14mod pid_lockfile;
15mod process_transport;
16mod protocol;
17mod refresh_queue;
18mod socket_path;
19pub mod uri;
20mod workspace_registry;
21mod workspace_session;
22
23use daemon::{LspDaemon, Timeouts};
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 error::{DaemonError, DaemonResult};
31pub use language_catalog::{LANGUAGES, LanguageId, LanguageMetadata, ServerSpec, detect_project_languages};
32pub use protocol::LSP_REQUEST_TIMED_OUT;
33pub use socket_path::socket_path;
34pub use uri::{UriError, path_to_uri, uri_to_path};
35
36#[derive(Debug, thiserror::Error)]
37pub enum LspdRunError {
38    #[error("Failed to create tokio runtime: {0}")]
39    RuntimeCreate(std::io::Error),
40    #[error("Failed to open log file: {0}")]
41    LogFileOpen(std::io::Error),
42    #[error("{0}")]
43    Daemon(#[from] DaemonError),
44}
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    /// Per-request LSP timeout in seconds. When a language server exceeds it,
57    /// the server is killed and replaced on the next request. The env var
58    /// matters because the daemon is usually auto-spawned with fixed arguments,
59    /// so the environment is the only configuration channel available to
60    /// whoever launches the client.
61    #[arg(long, env = "AETHER_LSPD_REQUEST_TIMEOUT", default_value = "120", value_parser = clap::value_parser!(u64).range(1..))]
62    pub request_timeout: u64,
63
64    /// Log level (trace, debug, info, warn, error)
65    #[arg(long, default_value = "info")]
66    pub log_level: String,
67
68    /// Log file path. Required for daemon mode since stderr is redirected to /dev/null.
69    #[arg(long)]
70    pub log_file: Option<PathBuf>,
71}
72
73pub fn run_lspd(args: LspdArgs) -> Result<(), LspdRunError> {
74    let idle_timeout = if args.idle_timeout == 0 { None } else { Some(Duration::from_secs(args.idle_timeout)) };
75    let timeouts = Timeouts { request: Duration::from_secs(args.request_timeout), ..Timeouts::default() };
76
77    let runtime = tokio::runtime::Runtime::new().map_err(LspdRunError::RuntimeCreate)?;
78
79    runtime.block_on(async {
80        let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&args.log_level));
81
82        if let Some(ref log_file) = args.log_file {
83            if let Some(parent) = log_file.parent() {
84                let _ = std::fs::create_dir_all(parent);
85            }
86            let file = std::fs::OpenOptions::new()
87                .create(true)
88                .append(true)
89                .open(log_file)
90                .map_err(LspdRunError::LogFileOpen)?;
91
92            tracing_subscriber::fmt()
93                .with_env_filter(filter)
94                .with_target(true)
95                .with_ansi(false)
96                .with_writer(file.with_max_level(tracing::Level::TRACE))
97                .init();
98        } else {
99            tracing_subscriber::fmt().with_env_filter(filter).with_target(true).init();
100        }
101
102        tracing::info!("Starting LSP daemon on socket: {:?}", args.socket);
103        Ok(LspDaemon::bind(args.socket, idle_timeout, timeouts)?.run().await?)
104    })
105}