use std::ops::ControlFlow; use std::pin::Pin; use std::process::Stdio; use std::task::{Context, Poll}; use async_lsp::{AnyEvent, AnyNotification, AnyRequest, LspService, MainLoop}; use async_process::Command; use futures::Future; use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt}; use tower_service::Service; use tracing::Level; struct Forward(Option); impl Service for Forward { type Response = S::Response; type Error = S::Error; type Future = S::Future; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.0.as_mut().unwrap().poll_ready(cx) } fn call(&mut self, req: AnyRequest) -> Self::Future { self.0.as_mut().unwrap().call(req) } } impl LspService for Forward { fn notify(&mut self, notif: AnyNotification) -> ControlFlow> { self.0.as_mut().unwrap().notify(notif) } fn emit(&mut self, event: AnyEvent) -> ControlFlow> { self.0.as_mut().unwrap().emit(event) } } struct Inspect { service: S, incoming: &'static str, outgoing: &'static str, } impl Service for Inspect where S::Future: Send + 'static, { type Response = S::Response; type Error = S::Error; type Future = Pin> + Send>>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx) } fn call(&mut self, req: AnyRequest) -> Self::Future { tracing::info!("{} request {}", self.incoming, req.method); let method = req.method.clone(); let fut = self.service.call(req); let outgoing = self.outgoing; Box::pin(async move { let resp_ret = fut.await; tracing::info!( "{} response {}: {}", outgoing, method, if resp_ret.is_ok() { "ok" } else { "err" }, ); resp_ret }) } } impl LspService for Inspect where S::Future: Send + 'static, { fn notify(&mut self, notif: AnyNotification) -> ControlFlow> { tracing::info!("{} notification {}", self.incoming, notif.method); self.service.notify(notif) } fn emit(&mut self, _event: AnyEvent) -> ControlFlow> { unreachable!() } } #[tokio::main(flavor = "current_thread")] async fn main() { tracing_subscriber::fmt() .with_max_level(Level::INFO) .with_ansi(false) .with_writer(std::io::stderr) .init(); let server_path = std::env::args() .nth(1) .expect("expect argument to the forwarded LSP server"); let mut child = Command::new(server_path) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) .spawn() .expect("failed to spawn"); // Mock client to communicate with the server. Incoming messages are forwarded to stdin/out. let (mut mock_client, server_socket) = MainLoop::new_client(|_| Inspect { service: Forward(None), incoming: "<", outgoing: ">", }); // Mock server to communicate with the client. Incoming messages are forwarded to child LSP. let (mock_server, client_socket) = MainLoop::new_server(|_| Inspect { service: server_socket, incoming: ">", outgoing: "<", }); // Link to form a bidirectional connection. mock_client.get_mut().service.0 = Some(client_socket); let child_stdin = child.stdin.take().unwrap(); let child_stdout = child.stdout.take().unwrap(); let main1 = tokio::spawn(mock_client.run_buffered(child_stdout, child_stdin)); let stdin = tokio::io::stdin().compat(); let stdout = tokio::io::stdout().compat_write(); let main2 = tokio::spawn(mock_server.run_buffered(stdin, stdout)); let ret = tokio::select! { ret = main1 => ret, ret = main2 => ret, }; ret.expect("join error").unwrap(); }