use std::sync::{Arc, Weak};
use anyhow::{Result, bail};
use crossbeam::channel::Receiver;
use lsp_server::{
Connection as LSPConnection, IoThreads, Message, Notification, Request, RequestId, Response,
};
use lsp_types::notification::{Exit, Notification as NotificationTrait};
use lsp_types::request::{Request as RequestTrait, Shutdown};
use lsp_types::{InitializeResult, ServerCapabilities};
use tracing::{error, info};
type ConnectionSender = crossbeam::channel::Sender<Message>;
type ConnectionReceiver = crossbeam::channel::Receiver<Message>;
pub struct ConnectionInitializer {
connection: LSPConnection,
threads: Option<IoThreads>,
}
pub struct Connection {
sender: Arc<ConnectionSender>,
receiver: ConnectionReceiver,
threads: Option<IoThreads>,
}
impl ConnectionInitializer {
pub fn stdio() -> Self {
let (connection, threads) = LSPConnection::stdio();
Self { connection, threads: Some(threads) }
}
#[cfg(feature = "testing")]
pub fn memory() -> (Self, LSPConnection) {
let (server, client) = LSPConnection::memory();
(Self { connection: server, threads: None }, client)
}
pub fn initialize_start(&self) -> Result<(RequestId, lsp_types::InitializeParams)> {
let (id, params) = self.connection.initialize_start()?;
Ok((id, serde_json::from_value(params)?))
}
pub fn initialize_finish(
self,
id: RequestId,
server_capabilities: ServerCapabilities,
) -> Result<Connection> {
let initialize_result =
InitializeResult { capabilities: server_capabilities, server_info: None };
self.connection.initialize_finish(id, serde_json::to_value(initialize_result).unwrap())?;
let Self { connection: LSPConnection { sender, receiver }, threads } = self;
Ok(Connection { sender: Arc::new(sender), receiver, threads })
}
}
impl Connection {
pub fn make_sender(&self) -> ClientSender {
ClientSender { weak_sender: Arc::downgrade(&self.sender) }
}
pub fn incoming(&self) -> Receiver<Message> {
self.receiver.clone()
}
pub fn handle_shutdown(&self, message: &Message) -> Result<bool> {
match message {
Message::Request(Request { id, method, .. }) if method == Shutdown::METHOD => {
self.sender.send(Response::new_ok(id.clone(), ()).into())?;
info!("shutdown request received, waiting for an exit notification...");
match self.receiver.recv_timeout(std::time::Duration::from_secs(30))? {
Message::Notification(Notification { method, .. })
if method == Exit::METHOD =>
{
info!("exit notification received, server shutting down...");
Ok(true)
}
message => bail!(
"server received unexpected message {message:?} while waiting for exit \
notification"
),
}
}
Message::Notification(Notification { method, .. }) if method == Exit::METHOD => {
error!(
"server received an exit notification before a shutdown request was sent, \
exiting..."
);
Ok(true)
}
_ => Ok(false),
}
}
pub fn close(self) -> Result<()> {
drop(
Arc::into_inner(self.sender)
.expect("the client sender shouldn't have more than one strong reference"),
);
drop(self.receiver);
if let Some(threads) = self.threads {
threads.join()?;
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct ClientSender {
weak_sender: Weak<ConnectionSender>,
}
impl ClientSender {
pub fn send(&self, msg: Message) -> Result<()> {
let Some(sender) = self.weak_sender.upgrade() else {
bail!("the connection with the client has been closed");
};
Ok(sender.send(msg)?)
}
}