pub(crate) mod daemon_task;
pub(crate) mod idle_monitor;
pub mod server;
pub mod socket;
pub mod spawn;
use {
crate::protocol::ipc::{
Endpoint,
runtime_dir::runtime_socket_dir,
},
std::path::PathBuf,
};
pub use {
crate::connect::lsp::DaemonConnection,
server::DaemonServer,
socket::{
DaemonStatus,
check_daemon_status,
cleanup_stale_socket,
wait_for_daemon,
write_pid_file,
},
spawn::{
daemonize,
spawn_daemon,
},
};
const DEFAULT_SHUTDOWN_TIMEOUT: std::time::Duration =
std::time::Duration::from_secs(5);
#[derive(Debug, Clone)]
pub struct DaemonConfig {
pub server_name: String,
pub workspace_id: String,
pub idle_timeout: Option<std::time::Duration>,
pub shutdown_timeout: std::time::Duration,
}
impl DaemonConfig {
pub fn new(
server_name: impl Into<String>,
workspace_id: impl Into<String>,
) -> Self {
let server_name = server_name.into();
let workspace_id = workspace_id.into();
assert!(!server_name.is_empty(), "server_name must not be empty");
assert!(!workspace_id.is_empty(), "workspace_id must not be empty");
assert!(
!server_name.contains('/') && !server_name.contains('\\'),
"server_name must not contain path separators"
);
assert!(
!workspace_id.contains('/') && !workspace_id.contains('\\'),
"workspace_id must not contain path separators"
);
Self {
server_name,
workspace_id,
idle_timeout: None,
shutdown_timeout: DEFAULT_SHUTDOWN_TIMEOUT,
}
}
pub fn with_idle_timeout(mut self, timeout: std::time::Duration) -> Self {
self.idle_timeout = Some(timeout);
self
}
pub fn with_shutdown_timeout(mut self, timeout: std::time::Duration) -> Self {
self.shutdown_timeout = timeout;
self
}
pub fn runtime_dir(&self) -> PathBuf {
runtime_socket_dir(&self.server_name, None).join(&self.workspace_id)
}
#[cfg(unix)]
pub fn socket_path(&self) -> PathBuf {
self.runtime_dir().join("lsp.sock")
}
#[cfg(unix)]
pub fn pid_file_path(&self) -> PathBuf {
self.runtime_dir().join("daemon.pid")
}
#[cfg(unix)]
pub fn lock_file_path(&self) -> PathBuf {
self.runtime_dir().join("daemon.lock")
}
pub fn endpoint(&self) -> Endpoint {
#[cfg(unix)]
{
Endpoint::UnixSocket(self.socket_path())
}
#[cfg(windows)]
{
Endpoint::NamedPipe(format!(
"\\\\.\\pipe\\{}-{}",
self.server_name, self.workspace_id
))
}
}
}
pub fn compute_workspace_id(path: &std::path::Path) -> String {
use std::hash::{
Hash,
Hasher,
};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
path.hash(&mut hasher);
let hash = hasher.finish();
format!("{:016x}", hash)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_daemon_config_endpoint() {
let config = DaemonConfig::new("test-server", "workspace123");
let endpoint = config.endpoint();
#[cfg(unix)]
{
if let Endpoint::UnixSocket(path) = endpoint {
assert!(path.to_string_lossy().contains("workspace123"));
assert!(path.to_string_lossy().ends_with("lsp.sock"));
} else {
panic!("expected UnixSocket on Unix");
}
}
#[cfg(windows)]
{
if let Endpoint::NamedPipe(name) = endpoint {
assert!(name.contains("test-server"));
assert!(name.contains("workspace123"));
} else {
panic!("expected NamedPipe on Windows");
}
}
}
#[test]
fn test_daemon_config_socket_path() {
let config = DaemonConfig::new("test-server", "workspace456");
#[cfg(unix)]
{
let socket_path = config.socket_path();
assert!(socket_path.to_string_lossy().contains("workspace456"));
assert!(socket_path.to_string_lossy().ends_with("lsp.sock"));
}
}
#[test]
fn test_daemon_config_pid_file_path() {
let config = DaemonConfig::new("test-server", "workspace789");
#[cfg(unix)]
{
let pid_path = config.pid_file_path();
assert!(pid_path.to_string_lossy().contains("workspace789"));
assert!(pid_path.to_string_lossy().ends_with("daemon.pid"));
}
}
#[test]
fn test_compute_workspace_id() {
let path1 = std::path::Path::new("/home/user/project");
let path2 = std::path::Path::new("/home/user/other");
let path3 = std::path::Path::new("/home/user/project");
let id1 = compute_workspace_id(path1);
let id2 = compute_workspace_id(path2);
let id3 = compute_workspace_id(path3);
assert_eq!(id1.len(), 16);
assert_ne!(id1, id2);
assert_eq!(id1, id3);
}
#[test]
fn test_daemon_config_with_idle_timeout() {
let config = DaemonConfig::new("test", "ws")
.with_idle_timeout(std::time::Duration::from_secs(300));
assert_eq!(
config.idle_timeout,
Some(std::time::Duration::from_secs(300))
);
}
}