use std::{
io,
pin::Pin,
task::{Context, Poll},
};
use futures::{
channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
io::{AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt},
StreamExt,
};
pub const WORKER_READY: &str = "__disposition_lsp_worker_ready__";
pub fn byte_pipe() -> (PipeWriter, PipeReader) {
let (tx, rx) = unbounded();
(
PipeWriter { tx },
PipeReader {
rx,
leftover: Vec::new(),
pos: 0,
},
)
}
pub fn frame(json: &str) -> Vec<u8> {
format!("Content-Length: {}\r\n\r\n{json}", json.len()).into_bytes()
}
pub async fn read_message<R>(reader: &mut R) -> Option<String>
where
R: AsyncBufRead + Unpin,
{
let mut content_length = None;
loop {
let mut line = Vec::new();
match reader.read_until(b'\n', &mut line).await {
Ok(0) => return None, Ok(_) => {}
Err(_) => return None,
}
let header = String::from_utf8_lossy(&line);
let header = header.trim_end_matches(['\r', '\n']);
if header.is_empty() {
break; }
if let Some(value) = header.strip_prefix("Content-Length:") {
content_length = value.trim().parse::<usize>().ok();
}
}
let mut body = vec![0u8; content_length?];
reader.read_exact(&mut body).await.ok()?;
Some(String::from_utf8_lossy(&body).into_owned())
}
#[derive(Clone)]
pub struct PipeWriter {
tx: UnboundedSender<Vec<u8>>,
}
impl PipeWriter {
pub fn send_bytes(&self, bytes: Vec<u8>) -> bool {
self.tx.unbounded_send(bytes).is_ok()
}
}
impl futures::io::AsyncWrite for PipeWriter {
fn poll_write(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
if self.send_bytes(buf.to_vec()) {
Poll::Ready(Ok(buf.len()))
} else {
Poll::Ready(Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"LSP pipe reader dropped",
)))
}
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.tx.close_channel();
Poll::Ready(Ok(()))
}
}
pub struct PipeReader {
rx: UnboundedReceiver<Vec<u8>>,
leftover: Vec<u8>,
pos: usize,
}
impl AsyncRead for PipeReader {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
if self.pos >= self.leftover.len() {
match self.rx.poll_next_unpin(cx) {
Poll::Ready(Some(chunk)) => {
self.leftover = chunk;
self.pos = 0;
}
Poll::Ready(None) => return Poll::Ready(Ok(0)),
Poll::Pending => return Poll::Pending,
}
}
let available = &self.leftover[self.pos..];
let count = available.len().min(buf.len());
buf[..count].copy_from_slice(&available[..count]);
self.pos += count;
Poll::Ready(Ok(count))
}
}