use std::future::{Future, IntoFuture};
use std::pin::Pin;
use crate::handles::session::unexpected_response;
use crate::{Pane, Result};
use rmux_proto::{CapturePaneRequest, Request, Response};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct PaneCapture {
pub stdout: Vec<u8>,
pub buffer_name: Option<String>,
}
#[derive(Debug, Clone)]
#[must_use = "pane capture builders do nothing unless awaited"]
pub struct PaneCaptureBuilder<'a> {
pane: &'a Pane,
start: Option<i64>,
end: Option<i64>,
buffer_name: Option<String>,
alternate: bool,
escape_ansi: bool,
escape_sequences: bool,
join_wrapped: bool,
use_mode_screen: bool,
preserve_trailing_spaces: bool,
do_not_trim_spaces: bool,
pending_input: bool,
quiet: bool,
start_is_absolute: bool,
end_is_absolute: bool,
}
impl<'a> PaneCaptureBuilder<'a> {
pub(crate) const fn new(pane: &'a Pane) -> Self {
Self {
pane,
start: None,
end: None,
buffer_name: None,
alternate: false,
escape_ansi: false,
escape_sequences: false,
join_wrapped: false,
use_mode_screen: false,
preserve_trailing_spaces: false,
do_not_trim_spaces: false,
pending_input: false,
quiet: false,
start_is_absolute: false,
end_is_absolute: false,
}
}
pub const fn start(mut self, line: i64) -> Self {
self.start = Some(line);
self.start_is_absolute = false;
self
}
pub const fn start_absolute(mut self, line: i64) -> Self {
self.start = Some(line);
self.start_is_absolute = true;
self
}
pub const fn end(mut self, line: i64) -> Self {
self.end = Some(line);
self.end_is_absolute = false;
self
}
pub const fn end_absolute(mut self, line: i64) -> Self {
self.end = Some(line);
self.end_is_absolute = true;
self
}
pub fn buffer(mut self, name: impl Into<String>) -> Self {
self.buffer_name = Some(name.into());
self
}
pub const fn alternate(mut self, enabled: bool) -> Self {
self.alternate = enabled;
self
}
pub const fn escape_ansi(mut self, enabled: bool) -> Self {
self.escape_ansi = enabled;
self
}
pub const fn escape_sequences(mut self, enabled: bool) -> Self {
self.escape_sequences = enabled;
self
}
pub const fn join_wrapped(mut self, enabled: bool) -> Self {
self.join_wrapped = enabled;
self
}
pub const fn use_mode_screen(mut self, enabled: bool) -> Self {
self.use_mode_screen = enabled;
self
}
pub const fn preserve_trailing_spaces(mut self, enabled: bool) -> Self {
self.preserve_trailing_spaces = enabled;
self
}
pub const fn do_not_trim_spaces(mut self, enabled: bool) -> Self {
self.do_not_trim_spaces = enabled;
self
}
pub const fn pending_input(mut self, enabled: bool) -> Self {
self.pending_input = enabled;
self
}
pub const fn quiet(mut self, enabled: bool) -> Self {
self.quiet = enabled;
self
}
async fn run(self) -> Result<PaneCapture> {
let target = self.pane.current_target().await?.to_proto();
let print = self.buffer_name.is_none();
match self
.pane
.transport()
.request(Request::CapturePane(CapturePaneRequest {
target,
start: self.start,
end: self.end,
print,
buffer_name: self.buffer_name,
alternate: self.alternate,
escape_ansi: self.escape_ansi,
escape_sequences: self.escape_sequences,
join_wrapped: self.join_wrapped,
use_mode_screen: self.use_mode_screen,
preserve_trailing_spaces: self.preserve_trailing_spaces,
do_not_trim_spaces: self.do_not_trim_spaces,
pending_input: self.pending_input,
quiet: self.quiet,
start_is_absolute: self.start_is_absolute,
end_is_absolute: self.end_is_absolute,
}))
.await?
{
Response::CapturePane(response) => Ok(PaneCapture {
stdout: response
.output
.map(|output| output.stdout)
.unwrap_or_default(),
buffer_name: response.buffer_name,
}),
response => Err(unexpected_response("capture-pane", response)),
}
}
}
impl<'a> IntoFuture for PaneCaptureBuilder<'a> {
type Output = Result<PaneCapture>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + 'a>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(self.run())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{PaneRef, RmuxEndpoint};
use rmux_proto::{encode_frame, CapturePaneResponse, CommandOutput, FrameDecoder, PaneTarget};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
fn alpha() -> rmux_proto::SessionName {
rmux_proto::SessionName::new("alpha").expect("valid session")
}
fn pane(client: crate::transport::TransportClient) -> Pane {
Pane::new(
PaneRef::new(alpha(), 1, 3),
RmuxEndpoint::Default,
None,
client,
)
}
async fn read_request(stream: &mut tokio::io::DuplexStream) -> Request {
let mut decoder = FrameDecoder::new();
let mut buffer = [0_u8; 256];
loop {
if let Some(request) = decoder
.next_frame::<Request>()
.expect("request frame decodes")
{
return request;
}
let read = stream.read(&mut buffer).await.expect("read request");
assert_ne!(read, 0, "client closed before request");
decoder.push_bytes(&buffer[..read]);
}
}
async fn write_response(stream: &mut tokio::io::DuplexStream, response: Response) {
let frame = encode_frame(&response).expect("response encodes");
stream.write_all(&frame).await.expect("write response");
stream.flush().await.expect("flush response");
}
#[tokio::test]
async fn pane_capture_builder_sends_capture_pane_options() {
let (client_stream, mut server_stream) = tokio::io::duplex(4096);
let pane = pane(crate::transport::TransportClient::spawn(client_stream));
let capture = tokio::spawn({
let pane = pane.clone();
async move {
pane.capture_pane()
.start(-20)
.end(0)
.alternate(true)
.escape_ansi(true)
.join_wrapped(true)
.await
}
});
match read_request(&mut server_stream).await {
Request::CapturePane(request) => {
assert_eq!(request.target, PaneTarget::with_window(alpha(), 1, 3));
assert_eq!(request.start, Some(-20));
assert_eq!(request.end, Some(0));
assert!(request.print);
assert!(request.alternate);
assert!(request.escape_ansi);
assert!(request.join_wrapped);
assert!(!request.escape_sequences);
}
request => panic!("expected capture-pane, got {request:?}"),
}
write_response(
&mut server_stream,
Response::CapturePane(CapturePaneResponse::from_output(
CommandOutput::from_stdout(b"hello\n".to_vec()),
)),
)
.await;
let capture = capture
.await
.expect("capture task")
.expect("capture succeeds");
assert_eq!(capture.stdout, b"hello\n");
assert_eq!(capture.buffer_name, None);
}
#[tokio::test]
async fn pane_capture_builder_can_target_a_buffer() {
let (client_stream, mut server_stream) = tokio::io::duplex(4096);
let pane = pane(crate::transport::TransportClient::spawn(client_stream));
let capture = tokio::spawn({
let pane = pane.clone();
async move { pane.capture_pane().buffer("clip").await }
});
match read_request(&mut server_stream).await {
Request::CapturePane(request) => {
assert!(!request.print);
assert_eq!(request.buffer_name.as_deref(), Some("clip"));
}
request => panic!("expected capture-pane, got {request:?}"),
}
write_response(
&mut server_stream,
Response::CapturePane(CapturePaneResponse::from_buffer("clip".to_owned())),
)
.await;
let capture = capture
.await
.expect("capture task")
.expect("capture succeeds");
assert!(capture.stdout.is_empty());
assert_eq!(capture.buffer_name.as_deref(), Some("clip"));
}
}