use crate::error::{LspError, Result};
use crate::protocol::{LspMessage, RequestId};
use dashmap::DashMap;
use serde_json::Value;
use std::sync::Arc;
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter};
use tokio::process::{Child, ChildStdin, ChildStdout};
use tokio::sync::{mpsc, oneshot};
use tracing::{debug, error, info, warn};
pub struct LspProcess {
process: Child,
message_tx: mpsc::UnboundedSender<LspMessage>,
_handles: Vec<tokio::task::JoinHandle<()>>,
}
impl LspProcess {
pub fn new(
mut process: Child,
pending_requests: Arc<DashMap<RequestId, oneshot::Sender<Result<Value>>>>,
server_id: String,
) -> Result<Self> {
let stdin = process.stdin.take().ok_or_else(|| {
LspError::communication("Process stdin not available")
})?;
let stdout = process.stdout.take().ok_or_else(|| {
LspError::communication("Process stdout not available")
})?;
let (message_tx, message_rx) = mpsc::unbounded_channel();
let writer_handle = Self::start_writer_task(stdin, message_rx, server_id.clone());
let reader_handle = Self::start_reader_task(stdout, pending_requests, server_id);
let handles = vec![writer_handle, reader_handle];
Ok(Self {
process,
message_tx,
_handles: handles,
})
}
pub fn send_message(&self, message: LspMessage) -> Result<()> {
self.message_tx.send(message)
.map_err(|_| LspError::communication("Failed to send message to process").into())
}
pub async fn kill(&mut self) -> Result<()> {
self.process.kill().await
.map_err(|e| LspError::communication(format!("Failed to kill process: {e}")).into())
}
pub async fn wait(&mut self) -> Result<std::process::ExitStatus> {
self.process.wait().await
.map_err(|e| LspError::communication(format!("Failed to wait for process: {e}")).into())
}
pub fn is_running(&mut self) -> bool {
match self.process.try_wait() {
Ok(None) => true, Ok(Some(_)) => false, Err(_) => false, }
}
fn start_writer_task(
stdin: ChildStdin,
mut message_rx: mpsc::UnboundedReceiver<LspMessage>,
server_id: String,
) -> tokio::task::JoinHandle<()> {
tokio::spawn(async move {
let mut writer = BufWriter::new(stdin);
while let Some(message) = message_rx.recv().await {
if let Err(e) = Self::send_message_internal(&mut writer, &message).await {
error!("Failed to send message for server {}: {}", server_id, e);
break;
}
}
debug!("Writer task completed for server {}", server_id);
})
}
fn start_reader_task(
stdout: ChildStdout,
pending_requests: Arc<DashMap<RequestId, oneshot::Sender<Result<Value>>>>,
server_id: String,
) -> tokio::task::JoinHandle<()> {
tokio::spawn(async move {
let mut reader = BufReader::new(stdout);
loop {
match Self::read_message_internal(&mut reader).await {
Ok(content) => {
if let Err(e) = Self::handle_incoming_message(&content, &pending_requests).await {
error!("Failed to handle incoming message for server {}: {}", server_id, e);
}
}
Err(e) => {
error!("Failed to read message for server {}: {}", server_id, e);
break;
}
}
}
debug!("Reader task completed for server {}", server_id);
})
}
async fn send_message_internal<W>(writer: &mut BufWriter<W>, message: &LspMessage) -> Result<()>
where
W: AsyncWriteExt + Unpin,
{
let json = serde_json::to_string(message)
.map_err(|e| LspError::protocol(format!("Failed to serialize message: {e}")))?;
let content_length = json.len();
let header = format!("Content-Length: {content_length}\r\n\r\n");
writer.write_all(header.as_bytes()).await
.map_err(|e| LspError::communication(format!("Failed to write header: {e}")))?;
writer.write_all(json.as_bytes()).await
.map_err(|e| LspError::communication(format!("Failed to write content: {e}")))?;
writer.flush().await
.map_err(|e| LspError::communication(format!("Failed to flush: {e}")))?;
debug!("Sent LSP message: {} bytes", content_length);
Ok(())
}
async fn read_message_internal<R>(reader: &mut BufReader<R>) -> Result<String>
where
R: tokio::io::AsyncRead + Unpin,
{
let mut content_length = 0;
let mut line = String::new();
loop {
line.clear();
let bytes_read = reader.read_line(&mut line).await
.map_err(|e| LspError::communication(format!("Failed to read header line: {e}")))?;
if bytes_read == 0 {
return Err(LspError::communication("Unexpected end of stream").into());
}
if line.trim().is_empty() {
break; }
if let Some(length) = Self::parse_content_length(&line) {
content_length = length;
}
}
if content_length == 0 {
return Err(LspError::protocol("No Content-Length header found").into());
}
let mut content = vec![0u8; content_length];
reader.read_exact(&mut content).await
.map_err(|e| LspError::communication(format!("Failed to read message content: {e}")))?;
let content_str = String::from_utf8(content)
.map_err(|_| LspError::protocol("Invalid UTF-8 in message content"))?;
debug!("Received LSP message: {} bytes", content_length);
Ok(content_str)
}
pub fn parse_content_length(header: &str) -> Option<usize> {
if header.starts_with("Content-Length:") {
header
.split(':')
.nth(1)?
.trim()
.parse()
.ok()
} else {
None
}
}
async fn handle_incoming_message(
content: &str,
pending_requests: &DashMap<RequestId, oneshot::Sender<Result<Value>>>,
) -> Result<()> {
let message: LspMessage = serde_json::from_str(content)
.map_err(|e| LspError::protocol(format!("Failed to parse LSP message: {e}")))?;
match message {
LspMessage::Response(response) => {
if let Some((_, sender)) = pending_requests.remove(&response.id) {
let result = if let Some(error) = response.error {
Err(LspError::json_rpc(error.message).into())
} else {
Ok(response.result.unwrap_or(Value::Null))
};
let _ = sender.send(result);
} else {
debug!("Received response for unknown request: {:?}", response.id);
}
}
LspMessage::Notification(notification) => {
debug!("Received notification: {}", notification.method);
match notification.method.as_str() {
"textDocument/publishDiagnostics" => {
info!("Received diagnostics notification");
if let Some(params) = notification.params {
if let Ok(diagnostics) = serde_json::from_value::<lsp_types::PublishDiagnosticsParams>(params) {
debug!("Received {} diagnostics for {:?}",
diagnostics.diagnostics.len(),
diagnostics.uri);
}
}
}
"window/logMessage" => {
if let Some(params) = notification.params {
if let Ok(log_msg) = serde_json::from_value::<lsp_types::LogMessageParams>(params) {
match log_msg.typ {
lsp_types::MessageType::ERROR => error!("LSP Server: {}", log_msg.message),
lsp_types::MessageType::WARNING => warn!("LSP Server: {}", log_msg.message),
lsp_types::MessageType::INFO => info!("LSP Server: {}", log_msg.message),
lsp_types::MessageType::LOG => debug!("LSP Server: {}", log_msg.message),
_ => debug!("LSP Server: {}", log_msg.message),
}
}
}
}
"window/showMessage" => {
info!("Received show message notification");
if let Some(params) = notification.params {
if let Ok(msg) = serde_json::from_value::<lsp_types::ShowMessageParams>(params) {
match msg.typ {
lsp_types::MessageType::ERROR => error!("LSP Server Message: {}", msg.message),
lsp_types::MessageType::WARNING => warn!("LSP Server Message: {}", msg.message),
lsp_types::MessageType::INFO => info!("LSP Server Message: {}", msg.message),
lsp_types::MessageType::LOG => debug!("LSP Server Message: {}", msg.message),
_ => debug!("LSP Server Message (unknown type): {}", msg.message),
}
}
}
}
_ => {
debug!("Unhandled notification: {}", notification.method);
}
}
}
LspMessage::Request(request) => {
debug!("Received request from server: {}", request.method);
match request.method.as_str() {
"workspace/configuration" => {
debug!("Server requested workspace configuration");
if let Some(params) = request.params {
if let Ok(config_params) = serde_json::from_value::<lsp_types::ConfigurationParams>(params) {
debug!("Server requested {} configuration items", config_params.items.len());
let _config_items: Vec<serde_json::Value> = config_params.items
.iter()
.map(|_| serde_json::Value::Null)
.collect();
debug!("Responding with default configuration values");
}
}
}
"client/registerCapability" => {
debug!("Server requested capability registration");
if let Some(params) = request.params {
if let Ok(reg_params) = serde_json::from_value::<lsp_types::RegistrationParams>(params) {
debug!("Server requested {} capability registrations", reg_params.registrations.len());
for registration in ®_params.registrations {
debug!(
"Registration request: id={}, method={}, options={:?}",
registration.id,
registration.method,
registration.register_options
);
}
debug!("Dynamic capability registration processed");
}
}
}
"window/showMessageRequest" => {
debug!("Server requested user input");
if let Some(params) = request.params {
if let Ok(msg_params) = serde_json::from_value::<lsp_types::ShowMessageRequestParams>(params.clone()) {
info!(
"Message request from server: {} (options: {})",
msg_params.message,
msg_params.actions.as_ref().map_or(0, |a| a.len())
);
let _response = match msg_params.actions {
Some(actions) if !actions.is_empty() => {
serde_json::to_value(&actions[0]).unwrap_or(Value::Null)
}
_ => Value::Null,
};
debug!("Auto-responding to message request with first option");
}
}
}
_ => {
debug!("Unhandled server request: {}", request.method);
}
}
}
}
Ok(())
}
}
impl Drop for LspProcess {
fn drop(&mut self) {
if self.is_running() {
let _ = futures::executor::block_on(self.kill());
}
}
}