You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
distant/distant-core/src/client/lsp.rs

1085 lines
34 KiB
Rust

use crate::{
client::{
DistantChannel, RemoteCommand, RemoteProcess, RemoteStatus, RemoteStderr, RemoteStdin,
RemoteStdout,
},
data::{Environment, PtySize},
};
use futures::stream::{Stream, StreamExt};
use std::{
io::{self, Cursor, Read},
ops::{Deref, DerefMut},
path::PathBuf,
};
use tokio::{
sync::mpsc::{self, error::TryRecvError},
task::JoinHandle,
};
mod msg;
pub use msg::*;
/// A [`RemoteLspProcess`] builder providing support to configure
/// before spawning the process on a remote machine
pub struct RemoteLspCommand {
persist: bool,
pty: Option<PtySize>,
environment: Environment,
current_dir: Option<PathBuf>,
}
impl Default for RemoteLspCommand {
fn default() -> Self {
Self::new()
}
}
impl RemoteLspCommand {
/// Creates a new set of options for a remote LSP process
pub fn new() -> Self {
Self {
persist: false,
pty: None,
environment: Environment::new(),
current_dir: None,
}
}
/// Sets whether or not the process will be persistent,
/// meaning that it will not be terminated when the
/// connection to the remote machine is terminated
pub fn persist(&mut self, persist: bool) -> &mut Self {
self.persist = persist;
self
}
/// Configures the process to leverage a PTY with the specified size
pub fn pty(&mut self, pty: Option<PtySize>) -> &mut Self {
self.pty = pty;
self
}
/// Replaces the existing environment variables with the given collection
pub fn environment(&mut self, environment: Environment) -> &mut Self {
self.environment = environment;
self
}
/// Configures the process with an alternative current directory
pub fn current_dir(&mut self, current_dir: Option<PathBuf>) -> &mut Self {
self.current_dir = current_dir;
self
}
/// Spawns the specified process on the remote machine using the given session, treating
/// the process like an LSP server
pub async fn spawn(
&mut self,
channel: DistantChannel,
cmd: impl Into<String>,
) -> io::Result<RemoteLspProcess> {
let mut command = RemoteCommand::new();
command.environment(self.environment.clone());
command.current_dir(self.current_dir.clone());
command.persist(self.persist);
command.pty(self.pty);
let mut inner = command.spawn(channel, cmd).await?;
let stdin = inner.stdin.take().map(RemoteLspStdin::new);
let stdout = inner.stdout.take().map(RemoteLspStdout::new);
let stderr = inner.stderr.take().map(RemoteLspStderr::new);
Ok(RemoteLspProcess {
inner,
stdin,
stdout,
stderr,
})
}
}
/// Represents an LSP server process on a remote machine
#[derive(Debug)]
pub struct RemoteLspProcess {
inner: RemoteProcess,
pub stdin: Option<RemoteLspStdin>,
pub stdout: Option<RemoteLspStdout>,
pub stderr: Option<RemoteLspStderr>,
}
impl RemoteLspProcess {
/// Waits for the process to terminate, returning the success status and an optional exit code
pub async fn wait(self) -> io::Result<RemoteStatus> {
self.inner.wait().await
}
}
impl Deref for RemoteLspProcess {
type Target = RemoteProcess;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for RemoteLspProcess {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
/// A handle to a remote LSP process' standard input (stdin)
#[derive(Debug)]
pub struct RemoteLspStdin {
inner: RemoteStdin,
buf: Option<Vec<u8>>,
}
impl RemoteLspStdin {
pub fn new(inner: RemoteStdin) -> Self {
Self { inner, buf: None }
}
/// Tries to write data to the stdin of a specific remote process
pub fn try_write(&mut self, data: &[u8]) -> io::Result<()> {
let queue = self.update_and_read_messages(data)?;
// Process and then send out each LSP message in our queue
for mut data in queue {
// Convert distant:// to file://
data.mut_content().convert_distant_scheme_to_local();
data.refresh_content_length();
self.inner.try_write_str(data.to_string())?;
}
Ok(())
}
pub fn try_write_str(&mut self, data: &str) -> io::Result<()> {
self.try_write(data.as_bytes())
}
/// Writes data to the stdin of a specific remote process
pub async fn write(&mut self, data: &[u8]) -> io::Result<()> {
let queue = self.update_and_read_messages(data)?;
// Process and then send out each LSP message in our queue
for mut data in queue {
// Convert distant:// to file://
data.mut_content().convert_distant_scheme_to_local();
data.refresh_content_length();
self.inner.write_str(data.to_string()).await?;
}
Ok(())
}
pub async fn write_str(&mut self, data: &str) -> io::Result<()> {
self.write(data.as_bytes()).await
}
fn update_and_read_messages(&mut self, data: &[u8]) -> io::Result<Vec<LspMsg>> {
// Create or insert into our buffer
match &mut self.buf {
Some(buf) => buf.extend(data),
None => self.buf = Some(data.to_vec()),
}
// Read LSP messages from our internal buffer
let buf = self.buf.take().unwrap();
match read_lsp_messages(&buf) {
// If we succeed, update buf with our remainder and return messages
Ok((remainder, queue)) => {
self.buf = remainder;
Ok(queue)
}
// Otherwise, if failed, reset buf back to what it was
Err(x) => {
self.buf = Some(buf);
Err(x)
}
}
}
}
/// A handle to a remote LSP process' standard output (stdout)
#[derive(Debug)]
pub struct RemoteLspStdout {
read_task: JoinHandle<()>,
rx: mpsc::Receiver<io::Result<Vec<u8>>>,
}
impl RemoteLspStdout {
pub fn new(inner: RemoteStdout) -> Self {
let (read_task, rx) = spawn_read_task(Box::pin(futures::stream::unfold(
inner,
|mut inner| async move {
match inner.read().await {
Ok(res) => Some((res, inner)),
Err(_) => None,
}
},
)));
Self { read_task, rx }
}
/// Tries to read a complete LSP message over stdout, returning `None` if no complete message
/// is available
pub fn try_read(&mut self) -> io::Result<Option<Vec<u8>>> {
match self.rx.try_recv() {
Ok(Ok(data)) => Ok(Some(data)),
Ok(Err(x)) => Err(x),
Err(TryRecvError::Empty) => Ok(None),
Err(TryRecvError::Disconnected) => Err(io::Error::from(io::ErrorKind::BrokenPipe)),
}
}
/// Same as `try_read`, but returns a string
pub fn try_read_string(&mut self) -> io::Result<Option<String>> {
self.try_read().and_then(|x| match x {
Some(data) => String::from_utf8(data)
.map(Some)
.map_err(|x| io::Error::new(io::ErrorKind::InvalidData, x)),
None => Ok(None),
})
}
/// Reads a complete LSP message over stdout
pub async fn read(&mut self) -> io::Result<Vec<u8>> {
self.rx
.recv()
.await
.ok_or_else(|| io::Error::from(io::ErrorKind::BrokenPipe))?
}
/// Same as `read`, but returns a string
pub async fn read_string(&mut self) -> io::Result<String> {
self.read().await.and_then(|data| {
String::from_utf8(data).map_err(|x| io::Error::new(io::ErrorKind::InvalidData, x))
})
}
}
impl Drop for RemoteLspStdout {
fn drop(&mut self) {
self.read_task.abort();
self.rx.close();
}
}
/// A handle to a remote LSP process' stderr
#[derive(Debug)]
pub struct RemoteLspStderr {
read_task: JoinHandle<()>,
rx: mpsc::Receiver<io::Result<Vec<u8>>>,
}
impl RemoteLspStderr {
pub fn new(inner: RemoteStderr) -> Self {
let (read_task, rx) = spawn_read_task(Box::pin(futures::stream::unfold(
inner,
|mut inner| async move {
match inner.read().await {
Ok(res) => Some((res, inner)),
Err(_) => None,
}
},
)));
Self { read_task, rx }
}
/// Tries to read a complete LSP message over stderr, returning `None` if no complete message
/// is available
pub fn try_read(&mut self) -> io::Result<Option<Vec<u8>>> {
match self.rx.try_recv() {
Ok(Ok(data)) => Ok(Some(data)),
Ok(Err(x)) => Err(x),
Err(TryRecvError::Empty) => Ok(None),
Err(TryRecvError::Disconnected) => Err(io::Error::from(io::ErrorKind::BrokenPipe)),
}
}
/// Same as `try_read`, but returns a string
pub fn try_read_string(&mut self) -> io::Result<Option<String>> {
self.try_read().and_then(|x| match x {
Some(data) => String::from_utf8(data)
.map(Some)
.map_err(|x| io::Error::new(io::ErrorKind::InvalidData, x)),
None => Ok(None),
})
}
/// Reads a complete LSP message over stderr
pub async fn read(&mut self) -> io::Result<Vec<u8>> {
self.rx
.recv()
.await
.ok_or_else(|| io::Error::from(io::ErrorKind::BrokenPipe))?
}
/// Same as `read`, but returns a string
pub async fn read_string(&mut self) -> io::Result<String> {
self.read().await.and_then(|data| {
String::from_utf8(data).map_err(|x| io::Error::new(io::ErrorKind::InvalidData, x))
})
}
}
impl Drop for RemoteLspStderr {
fn drop(&mut self) {
self.read_task.abort();
self.rx.close();
}
}
fn spawn_read_task<S>(mut stream: S) -> (JoinHandle<()>, mpsc::Receiver<io::Result<Vec<u8>>>)
where
S: Stream<Item = Vec<u8>> + Send + Unpin + 'static,
{
let (tx, rx) = mpsc::channel::<io::Result<Vec<u8>>>(1);
let read_task = tokio::spawn(async move {
let mut task_buf: Option<Vec<u8>> = None;
while let Some(data) = stream.next().await {
// Create or insert into our buffer
match &mut task_buf {
Some(buf) => buf.extend(data),
None => task_buf = Some(data),
}
// Read LSP messages from our internal buffer
let buf = task_buf.take().unwrap();
let (remainder, queue) = match read_lsp_messages(&buf) {
Ok(x) => x,
Err(x) => {
let _ = tx.send(Err(x)).await;
break;
}
};
task_buf = remainder;
// Process and then add each LSP message as output
if !queue.is_empty() {
let mut out = Vec::new();
for mut data in queue {
// Convert file:// to distant://
data.mut_content().convert_local_scheme_to_distant();
data.refresh_content_length();
out.extend(data.to_bytes());
}
if tx.send(Ok(out)).await.is_err() {
break;
}
}
}
});
(read_task, rx)
}
fn read_lsp_messages(input: &[u8]) -> io::Result<(Option<Vec<u8>>, Vec<LspMsg>)> {
let mut queue = Vec::new();
// Continue to read complete messages from the input until we either fail to parse or we reach
// end of input, resetting cursor position back to last successful parse as otherwise the
// cursor may have moved partially from lsp successfully reading the start of a message
let mut cursor = Cursor::new(input);
let mut pos = 0;
while let Ok(data) = LspMsg::from_buf_reader(&mut cursor) {
queue.push(data);
pos = cursor.position();
}
cursor.set_position(pos);
// Keep remainder of bytes not processed as LSP message in buffer
let remainder = if (cursor.position() as usize) < cursor.get_ref().len() {
let mut buf = Vec::new();
cursor.read_to_end(&mut buf)?;
Some(buf)
} else {
None
};
Ok((remainder, queue))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data::{DistantRequestData, DistantResponseData};
use distant_net::{
common::{FramedTransport, InmemoryTransport, Request, Response},
Client, ReconnectStrategy,
};
use std::{future::Future, time::Duration};
use test_log::test;
/// Timeout used with timeout function
const TIMEOUT: Duration = Duration::from_millis(50);
// Configures an lsp process with a means to send & receive data from outside
async fn spawn_lsp_process() -> (FramedTransport<InmemoryTransport>, RemoteLspProcess) {
let (mut t1, t2) = FramedTransport::pair(100);
let client = Client::spawn_inmemory(t2, ReconnectStrategy::Fail);
let spawn_task = tokio::spawn({
let channel = client.clone_channel();
async move {
RemoteLspCommand::new()
.spawn(channel, String::from("cmd arg"))
.await
}
});
// Wait until we get the request from the session
let req: Request<DistantRequestData> = t1.read_frame_as().await.unwrap().unwrap();
// Send back a response through the session
t1.write_frame_for(&Response::new(
req.id,
DistantResponseData::ProcSpawned { id: rand::random() },
))
.await
.unwrap();
// Wait for the process to be ready
let proc = spawn_task.await.unwrap().unwrap();
(t1, proc)
}
fn make_lsp_msg<T>(value: T) -> Vec<u8>
where
T: serde::Serialize,
{
let content = serde_json::to_string_pretty(&value).unwrap();
format!("Content-Length: {}\r\n\r\n{}", content.len(), content).into_bytes()
}
async fn timeout<F, R>(duration: Duration, f: F) -> io::Result<R>
where
F: Future<Output = R>,
{
tokio::select! {
res = f => {
Ok(res)
}
_ = tokio::time::sleep(duration) => {
Err(io::Error::from(io::ErrorKind::TimedOut))
}
}
}
#[test(tokio::test)]
async fn stdin_write_should_only_send_out_complete_lsp_messages() {
let (mut transport, mut proc) = spawn_lsp_process().await;
proc.stdin
.as_mut()
.unwrap()
.write(&make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
})))
.await
.unwrap();
// Validate that the outgoing req is a complete LSP message
let req: Request<DistantRequestData> = transport.read_frame_as().await.unwrap().unwrap();
match req.payload {
DistantRequestData::ProcStdin { data, .. } => {
assert_eq!(
data,
make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}))
);
}
x => panic!("Unexpected request: {:?}", x),
}
}
#[test(tokio::test)]
async fn stdin_write_should_support_buffering_output_until_a_complete_lsp_message_is_composed()
{
let (mut transport, mut proc) = spawn_lsp_process().await;
let msg = make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}));
let (msg_a, msg_b) = msg.split_at(msg.len() / 2);
// Write part of the message that isn't finished
proc.stdin.as_mut().unwrap().write(msg_a).await.unwrap();
// Verify that nothing has been sent out yet
// NOTE: Yield to ensure that data would be waiting at the transport if it was sent
tokio::task::yield_now().await;
let result = timeout(
TIMEOUT,
transport.read_frame_as::<Request<DistantRequestData>>(),
)
.await;
assert!(result.is_err(), "Unexpectedly got data: {:?}", result);
// Write remainder of message
proc.stdin.as_mut().unwrap().write(msg_b).await.unwrap();
// Validate that the outgoing req is a complete LSP message
let req: Request<DistantRequestData> = transport.read_frame_as().await.unwrap().unwrap();
match req.payload {
DistantRequestData::ProcStdin { data, .. } => {
assert_eq!(
data,
make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}))
);
}
x => panic!("Unexpected request: {:?}", x),
}
}
#[test(tokio::test)]
async fn stdin_write_should_only_consume_a_complete_lsp_message_even_if_more_is_written() {
let (mut transport, mut proc) = spawn_lsp_process().await;
let msg = make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}));
let extra = "Content-Length: 123";
// Write a full message plus some extra
proc.stdin
.as_mut()
.unwrap()
.write_str(&format!("{}{}", String::from_utf8(msg).unwrap(), extra))
.await
.unwrap();
// Validate that the outgoing req is a complete LSP message
let req: Request<DistantRequestData> = transport.read_frame_as().await.unwrap().unwrap();
match req.payload {
DistantRequestData::ProcStdin { data, .. } => {
assert_eq!(
data,
make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}))
);
}
x => panic!("Unexpected request: {:?}", x),
}
// Also validate that the internal buffer still contains the extra
assert_eq!(
String::from_utf8(proc.stdin.unwrap().buf.unwrap()).unwrap(),
extra,
"Extra was not retained"
);
}
#[test(tokio::test)]
async fn stdin_write_should_support_sending_out_multiple_lsp_messages_if_all_received_at_once()
{
let (mut transport, mut proc) = spawn_lsp_process().await;
let msg_1 = make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}));
let msg_2 = make_lsp_msg(serde_json::json!({
"field1": "c",
"field2": "d",
}));
// Write two full messages at once
proc.stdin
.as_mut()
.unwrap()
.write_str(&format!(
"{}{}",
String::from_utf8(msg_1).unwrap(),
String::from_utf8(msg_2).unwrap()
))
.await
.unwrap();
// Validate that the first outgoing req is a complete LSP message matching first
let req: Request<DistantRequestData> = transport.read_frame_as().await.unwrap().unwrap();
match req.payload {
DistantRequestData::ProcStdin { data, .. } => {
assert_eq!(
data,
make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}))
);
}
x => panic!("Unexpected request: {:?}", x),
}
// Validate that the second outgoing req is a complete LSP message matching second
let req: Request<DistantRequestData> = transport.read_frame_as().await.unwrap().unwrap();
match req.payload {
DistantRequestData::ProcStdin { data, .. } => {
assert_eq!(
data,
make_lsp_msg(serde_json::json!({
"field1": "c",
"field2": "d",
}))
);
}
x => panic!("Unexpected request: {:?}", x),
}
}
#[test(tokio::test)]
async fn stdin_write_should_convert_content_with_distant_scheme_to_file_scheme() {
let (mut transport, mut proc) = spawn_lsp_process().await;
proc.stdin
.as_mut()
.unwrap()
.write(&make_lsp_msg(serde_json::json!({
"field1": "distant://some/path",
"field2": "file://other/path",
})))
.await
.unwrap();
// Validate that the outgoing req is a complete LSP message
let req: Request<DistantRequestData> = transport.read_frame_as().await.unwrap().unwrap();
match req.payload {
DistantRequestData::ProcStdin { data, .. } => {
// Verify the contents AND headers are as expected; in this case,
// this will also ensure that the Content-Length is adjusted
// when the distant scheme was changed to file
assert_eq!(
data,
make_lsp_msg(serde_json::json!({
"field1": "file://some/path",
"field2": "file://other/path",
}))
);
}
x => panic!("Unexpected request: {:?}", x),
}
}
#[test(tokio::test)]
async fn stdout_read_should_yield_lsp_messages_as_strings() {
let (mut transport, mut proc) = spawn_lsp_process().await;
// Send complete LSP message as stdout to process
transport
.write_frame_for(&Response::new(
proc.origin_id().to_string(),
DistantResponseData::ProcStdout {
id: proc.id(),
data: make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
})),
},
))
.await
.unwrap();
// Receive complete message as stdout from process
let out = proc.stdout.as_mut().unwrap().read().await.unwrap();
assert_eq!(
out,
make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}))
);
}
#[test(tokio::test)]
async fn stdout_read_should_only_yield_complete_lsp_messages() {
let (mut transport, mut proc) = spawn_lsp_process().await;
let msg = make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}));
let (msg_a, msg_b) = msg.split_at(msg.len() / 2);
// Send half of LSP message over stdout
transport
.write_frame_for(&Response::new(
proc.origin_id().to_string(),
DistantResponseData::ProcStdout {
id: proc.id(),
data: msg_a.to_vec(),
},
))
.await
.unwrap();
// Verify that remote process has not received a complete message yet
// NOTE: Yield to ensure that data would be waiting at the transport if it was sent
tokio::task::yield_now().await;
let result = timeout(TIMEOUT, proc.stdout.as_mut().unwrap().read()).await;
assert!(result.is_err(), "Unexpectedly got data: {:?}", result);
// Send other half of LSP message over stdout
transport
.write_frame_for(&Response::new(
proc.origin_id().to_string(),
DistantResponseData::ProcStdout {
id: proc.id(),
data: msg_b.to_vec(),
},
))
.await
.unwrap();
// Receive complete message as stdout from process
let out = proc.stdout.as_mut().unwrap().read().await.unwrap();
assert_eq!(
out,
make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}))
);
}
#[test(tokio::test)]
async fn stdout_read_should_only_consume_a_complete_lsp_message_even_if_more_output_is_available(
) {
let (mut transport, mut proc) = spawn_lsp_process().await;
let msg = make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}));
let extra = "some extra content";
// Send complete LSP message as stdout to process
transport
.write_frame_for(&Response::new(
proc.origin_id().to_string(),
DistantResponseData::ProcStdout {
id: proc.id(),
data: format!("{}{}", String::from_utf8(msg).unwrap(), extra).into_bytes(),
},
))
.await
.unwrap();
// Receive complete message as stdout from process
let out = proc.stdout.as_mut().unwrap().read().await.unwrap();
assert_eq!(
out,
make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}))
);
// Verify nothing else was sent
let result = timeout(TIMEOUT, proc.stdout.as_mut().unwrap().read()).await;
assert!(
result.is_err(),
"Unexpected extra content received on stdout"
);
}
#[test(tokio::test)]
async fn stdout_read_should_support_yielding_multiple_lsp_messages_if_all_received_at_once() {
let (mut transport, mut proc) = spawn_lsp_process().await;
let msg_1 = make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}));
let msg_2 = make_lsp_msg(serde_json::json!({
"field1": "c",
"field2": "d",
}));
// Send complete LSP message as stdout to process
transport
.write_frame_for(&Response::new(
proc.origin_id().to_string(),
DistantResponseData::ProcStdout {
id: proc.id(),
data: format!(
"{}{}",
String::from_utf8(msg_1).unwrap(),
String::from_utf8(msg_2).unwrap()
)
.into_bytes(),
},
))
.await
.unwrap();
// Should send both messages back together as a single string
let out = proc.stdout.as_mut().unwrap().read().await.unwrap();
assert_eq!(
out,
format!(
"{}{}",
String::from_utf8(make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
})))
.unwrap(),
String::from_utf8(make_lsp_msg(serde_json::json!({
"field1": "c",
"field2": "d",
})))
.unwrap()
)
.into_bytes()
);
}
#[test(tokio::test)]
async fn stdout_read_should_convert_content_with_file_scheme_to_distant_scheme() {
let (mut transport, mut proc) = spawn_lsp_process().await;
// Send complete LSP message as stdout to process
transport
.write_frame_for(&Response::new(
proc.origin_id().to_string(),
DistantResponseData::ProcStdout {
id: proc.id(),
data: make_lsp_msg(serde_json::json!({
"field1": "distant://some/path",
"field2": "file://other/path",
})),
},
))
.await
.unwrap();
// Receive complete message as stdout from process
let out = proc.stdout.as_mut().unwrap().read().await.unwrap();
assert_eq!(
out,
make_lsp_msg(serde_json::json!({
"field1": "distant://some/path",
"field2": "distant://other/path",
}))
);
}
#[test(tokio::test)]
async fn stderr_read_should_yield_lsp_messages_as_strings() {
let (mut transport, mut proc) = spawn_lsp_process().await;
// Send complete LSP message as stderr to process
transport
.write_frame_for(&Response::new(
proc.origin_id().to_string(),
DistantResponseData::ProcStderr {
id: proc.id(),
data: make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
})),
},
))
.await
.unwrap();
// Receive complete message as stderr from process
let err = proc.stderr.as_mut().unwrap().read().await.unwrap();
assert_eq!(
err,
make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}))
);
}
#[test(tokio::test)]
async fn stderr_read_should_only_yield_complete_lsp_messages() {
let (mut transport, mut proc) = spawn_lsp_process().await;
let msg = make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}));
let (msg_a, msg_b) = msg.split_at(msg.len() / 2);
// Send half of LSP message over stderr
transport
.write_frame_for(&Response::new(
proc.origin_id().to_string(),
DistantResponseData::ProcStderr {
id: proc.id(),
data: msg_a.to_vec(),
},
))
.await
.unwrap();
// Verify that remote process has not received a complete message yet
// NOTE: Yield to ensure that data would be waiting at the transport if it was sent
tokio::task::yield_now().await;
let result = timeout(TIMEOUT, proc.stderr.as_mut().unwrap().read()).await;
assert!(result.is_err(), "Unexpectedly got data: {:?}", result);
// Send other half of LSP message over stderr
transport
.write_frame_for(&Response::new(
proc.origin_id().to_string(),
DistantResponseData::ProcStderr {
id: proc.id(),
data: msg_b.to_vec(),
},
))
.await
.unwrap();
// Receive complete message as stderr from process
let err = proc.stderr.as_mut().unwrap().read().await.unwrap();
assert_eq!(
err,
make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}))
);
}
#[test(tokio::test)]
async fn stderr_read_should_only_consume_a_complete_lsp_message_even_if_more_errput_is_available(
) {
let (mut transport, mut proc) = spawn_lsp_process().await;
let msg = make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}));
let extra = "some extra content";
// Send complete LSP message as stderr to process
transport
.write_frame_for(&Response::new(
proc.origin_id().to_string(),
DistantResponseData::ProcStderr {
id: proc.id(),
data: format!("{}{}", String::from_utf8(msg).unwrap(), extra).into_bytes(),
},
))
.await
.unwrap();
// Receive complete message as stderr from process
let err = proc.stderr.as_mut().unwrap().read().await.unwrap();
assert_eq!(
err,
make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}))
);
// Verify nothing else was sent
let result = timeout(TIMEOUT, proc.stderr.as_mut().unwrap().read()).await;
assert!(
result.is_err(),
"Unexpected extra content received on stderr"
);
}
#[test(tokio::test)]
async fn stderr_read_should_support_yielding_multiple_lsp_messages_if_all_received_at_once() {
let (mut transport, mut proc) = spawn_lsp_process().await;
let msg_1 = make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
}));
let msg_2 = make_lsp_msg(serde_json::json!({
"field1": "c",
"field2": "d",
}));
// Send complete LSP message as stderr to process
transport
.write_frame_for(&Response::new(
proc.origin_id().to_string(),
DistantResponseData::ProcStderr {
id: proc.id(),
data: format!(
"{}{}",
String::from_utf8(msg_1).unwrap(),
String::from_utf8(msg_2).unwrap()
)
.into_bytes(),
},
))
.await
.unwrap();
// Should send both messages back together as a single string
let err = proc.stderr.as_mut().unwrap().read().await.unwrap();
assert_eq!(
err,
format!(
"{}{}",
String::from_utf8(make_lsp_msg(serde_json::json!({
"field1": "a",
"field2": "b",
})))
.unwrap(),
String::from_utf8(make_lsp_msg(serde_json::json!({
"field1": "c",
"field2": "d",
})))
.unwrap()
)
.into_bytes()
);
}
#[test(tokio::test)]
async fn stderr_read_should_convert_content_with_file_scheme_to_distant_scheme() {
let (mut transport, mut proc) = spawn_lsp_process().await;
// Send complete LSP message as stderr to process
transport
.write_frame_for(&Response::new(
proc.origin_id().to_string(),
DistantResponseData::ProcStderr {
id: proc.id(),
data: make_lsp_msg(serde_json::json!({
"field1": "distant://some/path",
"field2": "file://other/path",
})),
},
))
.await
.unwrap();
// Receive complete message as stderr from process
let err = proc.stderr.as_mut().unwrap().read().await.unwrap();
assert_eq!(
err,
make_lsp_msg(serde_json::json!({
"field1": "distant://some/path",
"field2": "distant://other/path",
}))
);
}
}