use std::fmt;
use std::path::PathBuf;
use std::time::Duration;
use crate::events::streams::{PaneLineStream, PaneOutputStart, PaneOutputStream};
use crate::handles::split::SplitDirection;
use crate::transport::TransportClient;
use crate::{
ArmedWait, CollectedPaneOutput, InfoSnapshot, PaneExitState, PaneId, PaneRef, PaneRenderStream,
PaneSnapshot, PaneTextMatch, ProcessSpec, Result, RmuxEndpoint, RmuxError, TerminalSizeSpec,
VisibleTextExpectation,
};
#[path = "pane/capture_pane.rs"]
mod capture_pane;
#[path = "pane/info.rs"]
mod info;
#[path = "pane/input.rs"]
mod input;
#[path = "pane/lifecycle.rs"]
mod lifecycle;
#[path = "pane/snapshot.rs"]
mod snapshot;
#[path = "pane/spawn.rs"]
mod spawn;
#[path = "pane/split.rs"]
mod split;
#[path = "pane/split_builder.rs"]
mod split_builder;
#[path = "pane/target.rs"]
mod target;
#[path = "pane/title.rs"]
mod title;
pub use capture_pane::{PaneCapture, PaneCaptureBuilder};
use info::{current_pane_entry, current_pane_ref_for_id, pane_info_snapshot};
use input::{resize_to_size, send_key, send_text};
use lifecycle::{close_pane, respawn_pane};
use snapshot::pane_snapshot;
pub use spawn::PaneSpawnBuilder;
use split::split_pane;
pub use split_builder::PaneSplitBuilder;
pub(crate) use target::is_already_closed_pane_error;
use title::{get_title, set_title};
pub(crate) async fn resolve_pane_ref_for_id(
transport: &TransportClient,
session_name: &rmux_proto::SessionName,
pane_id: PaneId,
) -> Result<Option<PaneRef>> {
current_pane_ref_for_id(transport, session_name, pane_id).await
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PaneCloseOutcome {
Closed {
target: PaneRef,
window_destroyed: bool,
},
AlreadyClosed {
target: PaneRef,
},
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct PaneRespawnOptions {
pub kill: bool,
pub start_directory: Option<PathBuf>,
pub process: ProcessSpec,
pub keep_alive_on_exit: Option<bool>,
}
#[derive(Clone)]
pub struct Pane {
target: PaneRef,
stable_id: Option<PaneId>,
endpoint: RmuxEndpoint,
default_timeout: Option<Duration>,
transport: TransportClient,
}
impl Pane {
pub(crate) fn new(
target: PaneRef,
endpoint: RmuxEndpoint,
default_timeout: Option<Duration>,
transport: TransportClient,
) -> Self {
Self {
target,
stable_id: None,
endpoint,
default_timeout,
transport,
}
}
pub(crate) fn new_by_id(
target: PaneRef,
pane_id: PaneId,
endpoint: RmuxEndpoint,
default_timeout: Option<Duration>,
transport: TransportClient,
) -> Self {
Self {
target,
stable_id: Some(pane_id),
endpoint,
default_timeout,
transport,
}
}
#[must_use]
pub const fn target(&self) -> &PaneRef {
&self.target
}
#[must_use]
pub const fn endpoint(&self) -> &RmuxEndpoint {
&self.endpoint
}
#[must_use]
pub const fn configured_default_timeout(&self) -> Option<Duration> {
self.default_timeout
}
pub(crate) const fn transport(&self) -> &TransportClient {
&self.transport
}
pub(crate) fn proto_target_ref(&self) -> rmux_proto::PaneTargetRef {
match self.stable_id {
Some(pane_id) => {
rmux_proto::PaneTargetRef::by_id(self.target.session_name.clone(), pane_id)
}
None => rmux_proto::PaneTargetRef::slot(self.target.to_proto()),
}
}
pub(crate) const fn is_stable_id(&self) -> bool {
self.stable_id.is_some()
}
pub async fn wait_for(&self, bytes: impl AsRef<[u8]>) -> Result<()> {
crate::wait::wait_for_bytes(self, bytes.as_ref().to_vec()).await
}
pub async fn wait_for_next(&self, bytes: impl AsRef<[u8]>) -> Result<ArmedWait> {
crate::wait::wait_for_next_bytes(self, bytes.as_ref().to_vec()).await
}
pub async fn wait_for_text(&self, text: impl AsRef<str>) -> Result<()> {
crate::wait::wait_for_text(self, text.as_ref().to_owned()).await
}
pub async fn wait_for_text_next(&self, text: impl AsRef<str>) -> Result<ArmedWait> {
crate::wait::wait_for_text_next(self, text.as_ref().to_owned()).await
}
pub fn expect_visible_text(&self) -> VisibleTextExpectation<'_> {
VisibleTextExpectation::new(self)
}
pub async fn wait_exit(&self) -> Result<Option<PaneExitState>> {
crate::wait::wait_exit(self).await
}
pub async fn wait_for_exit(&self) -> Result<Option<PaneExitState>> {
self.wait_exit().await
}
pub async fn output_stream(&self) -> Result<PaneOutputStream> {
self.output_stream_starting_at(PaneOutputStart::Now).await
}
pub async fn output_stream_starting_at(
&self,
start: PaneOutputStart,
) -> Result<PaneOutputStream> {
PaneOutputStream::open(self.transport.clone(), self.proto_target_ref(), start).await
}
pub async fn collect_output_until_exit(&self, max_bytes: usize) -> Result<CollectedPaneOutput> {
crate::extract::collect_output_until_exit(self, max_bytes).await
}
pub async fn collect_output_until_exit_starting_at(
&self,
start: PaneOutputStart,
max_bytes: usize,
) -> Result<CollectedPaneOutput> {
crate::extract::collect_output_until_exit_starting_at(self, start, max_bytes).await
}
pub async fn line_stream(&self) -> Result<PaneLineStream> {
self.line_stream_starting_at(PaneOutputStart::Now).await
}
pub async fn line_stream_starting_at(&self, start: PaneOutputStart) -> Result<PaneLineStream> {
let inner = self.output_stream_starting_at(start).await?;
Ok(PaneLineStream::wrap(inner))
}
pub async fn render_stream(&self) -> Result<PaneRenderStream> {
PaneRenderStream::open(self.clone()).await
}
pub async fn id(&self) -> Result<Option<PaneId>> {
if let Some(pane_id) = self.stable_id {
let current =
current_pane_ref_for_id(&self.transport, &self.target.session_name, pane_id)
.await?;
return Ok(current.map(|_| pane_id));
}
Ok(current_pane_entry(&self.transport, &self.target)
.await?
.map(|entry| entry.pane_id))
}
pub async fn exists(&self) -> Result<bool> {
Ok(self.id().await?.is_some())
}
pub async fn info(&self) -> Result<InfoSnapshot> {
match self.stable_id {
Some(pane_id) => {
let Some(target) =
current_pane_ref_for_id(&self.transport, &self.target.session_name, pane_id)
.await?
else {
return Ok(InfoSnapshot::default());
};
pane_info_snapshot(&self.transport, &target).await
}
None => pane_info_snapshot(&self.transport, &self.target).await,
}
}
pub async fn snapshot(&self) -> Result<PaneSnapshot> {
pane_snapshot(self).await
}
pub fn capture_pane(&self) -> PaneCaptureBuilder<'_> {
PaneCaptureBuilder::new(self)
}
pub async fn find_text(&self, text: impl AsRef<str>) -> Result<Option<PaneTextMatch>> {
crate::extract::find_text(self, text.as_ref().to_owned()).await
}
pub async fn find_text_all(&self, text: impl AsRef<str>) -> Result<Vec<PaneTextMatch>> {
crate::extract::find_text_all(self, text.as_ref().to_owned()).await
}
pub async fn send_text(&self, text: impl AsRef<str>) -> Result<()> {
send_text(self, text.as_ref()).await
}
pub async fn send_key(&self, key: impl Into<String>) -> Result<()> {
send_key(self, key.into()).await
}
pub async fn resize(&self, size: TerminalSizeSpec) -> Result<()> {
resize_to_size(self, size).await
}
pub async fn set_title(&self, title: impl Into<String>) -> Result<()> {
set_title(self, title.into()).await
}
pub async fn title(&self) -> Result<Option<String>> {
get_title(self).await
}
pub async fn close(self) -> Result<PaneCloseOutcome> {
close_pane(self).await
}
pub fn detach(self) {}
pub async fn split(&self, direction: SplitDirection) -> Result<Self> {
let target = self.current_target().await?;
let new_target = split_pane(&self.transport, &target, direction).await?;
Ok(Self::new(
new_target,
self.endpoint.clone(),
self.default_timeout,
self.transport.clone(),
))
}
pub fn split_with(&self, direction: SplitDirection) -> PaneSplitBuilder<'_> {
PaneSplitBuilder::new(self, direction)
}
pub async fn respawn(&self, options: PaneRespawnOptions) -> Result<PaneRef> {
respawn_pane(self, options).await
}
pub fn spawn<I, S>(&self, command: I) -> PaneSpawnBuilder<'_>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
PaneSpawnBuilder::argv(self, command.into_iter().map(Into::into).collect())
}
pub fn shell(&self, command: impl Into<String>) -> PaneSpawnBuilder<'_> {
PaneSpawnBuilder::shell(self, command.into())
}
pub(crate) async fn current_target(&self) -> Result<PaneRef> {
let Some(pane_id) = self.stable_id else {
return Ok(self.target.clone());
};
current_pane_ref_for_id(&self.transport, &self.target.session_name, pane_id)
.await?
.ok_or_else(|| RmuxError::pane_not_found(self.target.session_name.clone(), pane_id))
}
}
impl fmt::Debug for Pane {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Pane")
.field("target", &self.target)
.finish_non_exhaustive()
}
}
#[cfg(test)]
#[path = "pane/tests.rs"]
mod tests;