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;
32pub use error::{DaemonError, DaemonResult};
33
34#[derive(Debug, thiserror::Error)]
35pub enum LspdRunError {
36 #[error("Failed to create tokio runtime: {0}")]
37 RuntimeCreate(std::io::Error),
38 #[error("Failed to open log file: {0}")]
39 LogFileOpen(std::io::Error),
40 #[error("{0}")]
41 Daemon(#[from] DaemonError),
42}
43pub use language_catalog::LanguageId;
44pub use language_catalog::{
45 LANGUAGE_METADATA, LanguageMetadata, LanguageServerMetadata, LspConfig, detect_project_languages,
46 extensions_for_alias, from_lsp_id, get_config_for_language, metadata_for, server_metadata_for_language,
47};
48pub use lsp_utils::symbol_kind_to_string;
49pub use socket_path::{ensure_socket_dir, lockfile_path, log_file_path, socket_path};
50
51pub use protocol::{
52 DaemonRequest, DaemonResponse, InitializeRequest, LSP_REQUEST_TIMED_OUT, LSP_TRANSPORT_CLOSED, LspErrorResponse,
53 MAX_MESSAGE_SIZE, ProtocolError,
54};
55pub use uri::{UriError, path_to_uri, uri_to_path};
56
57#[derive(clap::Args)]
58pub struct LspdArgs {
59 #[arg(long)]
61 pub socket: PathBuf,
62
63 #[arg(long, default_value = "300")]
65 pub idle_timeout: u64,
66
67 #[arg(long, env = "AETHER_LSPD_REQUEST_TIMEOUT", default_value = "120", value_parser = clap::value_parser!(u64).range(1..))]
73 pub request_timeout: u64,
74
75 #[arg(long, default_value = "info")]
77 pub log_level: String,
78
79 #[arg(long)]
81 pub log_file: Option<PathBuf>,
82}
83
84pub fn run_lspd(args: LspdArgs) -> Result<(), LspdRunError> {
85 let idle_timeout = if args.idle_timeout == 0 { None } else { Some(Duration::from_secs(args.idle_timeout)) };
86 let request_timeout = Duration::from_secs(args.request_timeout);
87
88 let runtime = tokio::runtime::Runtime::new().map_err(LspdRunError::RuntimeCreate)?;
89
90 runtime.block_on(async {
91 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&args.log_level));
92
93 if let Some(ref log_file) = args.log_file {
94 if let Some(parent) = log_file.parent() {
95 let _ = std::fs::create_dir_all(parent);
96 }
97 let file = std::fs::OpenOptions::new()
98 .create(true)
99 .append(true)
100 .open(log_file)
101 .map_err(LspdRunError::LogFileOpen)?;
102
103 tracing_subscriber::fmt()
104 .with_env_filter(filter)
105 .with_target(true)
106 .with_ansi(false)
107 .with_writer(file.with_max_level(tracing::Level::TRACE))
108 .init();
109 } else {
110 tracing_subscriber::fmt().with_env_filter(filter).with_target(true).init();
111 }
112
113 tracing::info!("Starting LSP daemon on socket: {:?}", args.socket);
114 LspDaemon::new(args.socket, idle_timeout, request_timeout).run().await.map_err(LspdRunError::Daemon)
115 })
116}