1#![doc = include_str!("../README.md")]
2
3#[cfg(feature = "testing")]
4pub mod testing;
5
6mod client_connection;
7mod daemonize;
8mod diagnostics_store;
9mod document_lifecycle;
10mod file_watcher;
11pub mod language_catalog;
12mod pid_lockfile;
13mod process_transport;
14mod refresh_queue;
15mod workspace_registry;
16mod workspace_session;
17
18mod client;
19pub mod daemon;
20pub mod error;
21pub mod lsp_utils;
22pub mod protocol;
23pub mod socket_path;
24pub mod uri;
25
26use std::path::PathBuf;
27use std::time::Duration;
28use tracing_subscriber::EnvFilter;
29use tracing_subscriber::fmt::writer::MakeWriterExt;
30
31pub use client::{ClientError, ClientResult, LspClient};
32pub use daemon::{LspDaemon, run_daemon};
33pub use error::{DaemonError, DaemonResult};
34pub use language_catalog::LanguageId;
35pub use language_catalog::{
36 LANGUAGE_METADATA, LanguageMetadata, LspConfig, extensions_for_alias, from_lsp_id, get_config_for_language,
37 metadata_for,
38};
39pub use lsp_utils::symbol_kind_to_string;
40pub use socket_path::{ensure_socket_dir, lockfile_path, log_file_path, socket_path};
41
42pub use protocol::{
43 DaemonRequest, DaemonResponse, InitializeRequest, LspErrorResponse, MAX_MESSAGE_SIZE, ProtocolError,
44};
45pub use uri::{path_to_uri, uri_to_path};
46
47#[derive(clap::Args)]
48pub struct LspdArgs {
49 #[arg(long)]
51 pub socket: PathBuf,
52
53 #[arg(long, default_value = "300")]
55 pub idle_timeout: u64,
56
57 #[arg(long, default_value = "info")]
59 pub log_level: String,
60
61 #[arg(long)]
63 pub log_file: Option<PathBuf>,
64}
65
66pub fn run_lspd(args: LspdArgs) -> Result<(), String> {
67 let idle_timeout = if args.idle_timeout == 0 {
68 None
69 } else {
70 Some(Duration::from_secs(args.idle_timeout))
71 };
72
73 daemonize::daemonize()?;
74
75 let runtime =
76 tokio::runtime::Runtime::new().map_err(|e| format!("Failed to create tokio runtime: {e}"))?;
77
78 runtime.block_on(async {
79 let filter =
80 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(|e| format!("Failed to open log file: {e}"))?;
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()
100 .with_env_filter(filter)
101 .with_target(true)
102 .init();
103 }
104
105 tracing::info!("Starting LSP daemon on socket: {:?}", args.socket);
106 run_daemon(args.socket, idle_timeout)
107 .await
108 .map_err(|e| format!("Daemon error: {e}"))
109 })
110}