use super::mux::tests::{manners_state, socket_path, spawn_daemon};
use super::mux_transport::SEND_LOCK_BUSY;
use crate::app::tmux_handler::tmux_state::{MuxJob, MuxJobResult, TmuxTransport};
use crate::app::window_state::WindowState;
use par_term_emu_core_rust::mux::MuxClient;
use std::time::{Duration, Instant};
pub(crate) const DAEMON_DEADLINE: Duration = Duration::from_secs(60);
pub(crate) fn poll_until(mut cond: impl FnMut() -> bool) -> bool {
let deadline = Instant::now() + DAEMON_DEADLINE;
loop {
if cond() {
return true;
}
if Instant::now() >= deadline {
return false;
}
std::thread::sleep(Duration::from_millis(25));
}
}
pub(crate) fn wait_until(what: &str, cond: impl FnMut() -> bool) {
assert!(
poll_until(cond),
"{what}: not observed within {DAEMON_DEADLINE:?}"
);
}
pub(crate) trait SettledSend {
fn send_settled(&self, command: &str) -> std::io::Result<Vec<String>>;
fn query_settled(&self, command: &str) -> std::io::Result<Vec<String>>;
}
impl<T: TmuxTransport + ?Sized> SettledSend for T {
fn send_settled(&self, command: &str) -> std::io::Result<Vec<String>> {
let mut result = None;
poll_until(|| match self.send_command(command) {
Err(e) if e.to_string() == SEND_LOCK_BUSY => {
result = Some(Err(e));
false
}
other => {
result = Some(other);
true
}
});
result.expect("polled at least once")
}
fn query_settled(&self, command: &str) -> std::io::Result<Vec<String>> {
let deadline = Instant::now() + DAEMON_DEADLINE;
loop {
match self.send_command(command) {
Err(e)
if e.to_string() == SEND_LOCK_BUSY
|| e.kind() == std::io::ErrorKind::TimedOut =>
{
if Instant::now() >= deadline {
return Err(e);
}
std::thread::sleep(Duration::from_millis(25));
}
other => return other,
}
}
}
}
pub(crate) fn attach_until_installed(
ws: &mut WindowState,
mut arm: impl FnMut() -> super::mux::MuxAttachPending,
socket: &std::path::Path,
) {
wait_until("attach installs", || {
if ws.tmux_state.transport.is_none() && ws.tmux_state.mux_attach_pending.is_none() {
ws.tmux_state.mux_attach_socket = Some(socket.to_path_buf());
ws.tmux_state.mux_attach_pending = Some(arm());
}
ws.poll_mux_attach();
ws.tmux_state.transport.is_some()
});
}
pub(crate) fn quiesce(ws: &mut WindowState) {
wait_until("off-loop mux jobs land", || {
!ws.tmux_state.mux_jobs_in_flight() || {
ws.check_mux_notifications();
!ws.tmux_state.mux_jobs_in_flight()
}
});
drain_send_worker(ws.tmux_state.transport.as_deref().expect("transport"));
}
pub(crate) fn drain_send_worker(transport: &dyn TmuxTransport) {
assert!(
transport.submit_job(MuxJob::SeedPanes(Vec::new())),
"the barrier job is queued"
);
let mut results = Vec::new();
wait_until("the send worker drains its queue", || {
results.extend(transport.take_job_results());
!results.is_empty()
});
assert!(
matches!(results.as_slice(), [MuxJobResult::PaneSeeds(seeds)] if seeds.is_empty()),
"only the barrier was in flight ({} results)",
results.len()
);
}
pub(crate) fn attached_window_with_tabs(
tag: &str,
tabs: usize,
) -> (WindowState, std::path::PathBuf) {
let path = socket_path(tag);
spawn_daemon(&path);
let mut ws = manners_state();
attach_until_installed(
&mut ws,
|| {
let core = MuxClient::connect(&path).expect("connect");
let (tx, rx) = std::sync::mpsc::channel();
tx.send(Ok(core)).unwrap();
drop(tx);
super::mux::MuxAttachPending {
name: tag.to_string(),
rx,
}
},
&path,
);
for _ in 1..tabs {
ws.tmux_state
.transport
.as_ref()
.expect("transport")
.send_settled("new-window")
.expect("new-window");
}
wait_until("tabs mapped", || {
if let Some(t) = ws.tmux_state.transport.as_ref() {
for pane in 0..tabs as u64 {
let _ = t.send_command_no_wait(&format!("refresh-client -t %{pane} -C 80x24"));
}
}
ws.check_tmux_notifications();
ws.tab_manager.tab_count() >= tabs && ws.tmux_state.tmux_pane_owners.len() >= tabs
});
quiesce(&mut ws);
(ws, path)
}