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 #[arg(long)]
50 pub socket: PathBuf,
51
52 #[arg(long, default_value = "300")]
54 pub idle_timeout: u64,
55
56 #[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 #[arg(long, default_value = "info")]
66 pub log_level: String,
67
68 #[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}