From c6c87eaf7cefe17d4df3987e2f2e516ee8df3519 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Fri, 6 Nov 2020 08:29:32 -0800 Subject: [PATCH] tmux: plumbing for querying panes when control mode is started up refs: https://github.com/wez/wezterm/issues/336 --- mux/src/tmux.rs | 168 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 165 insertions(+), 3 deletions(-) diff --git a/mux/src/tmux.rs b/mux/src/tmux.rs index a0c3dfebf73..100c0f5c089 100644 --- a/mux/src/tmux.rs +++ b/mux/src/tmux.rs @@ -2,16 +2,122 @@ use crate::domain::{alloc_domain_id, Domain, DomainId, DomainState}; use crate::pane::{Pane, PaneId}; use crate::tab::{SplitDirection, Tab, TabId}; use crate::window::WindowId; +use crate::Mux; +use anyhow::anyhow; use async_trait::async_trait; use portable_pty::{CommandBuilder, PtySize}; use std::cell::RefCell; +use std::collections::VecDeque; use std::rc::Rc; use std::sync::Arc; +use tmux_cc::*; + +#[derive(PartialEq, Eq, Debug, Copy, Clone)] +enum State { + WaitForInitialGuard, + Idle, + WaitingForResponse, +} + +trait TmuxCommand { + fn get_command(&self) -> String; + fn process_result(&self, domain_id: DomainId, result: &Guarded) -> anyhow::Result<()>; +} + +struct ListAllPanes; +impl TmuxCommand for ListAllPanes { + fn get_command(&self) -> String { + "list-panes -aF '#{session_id} #{window_id} #{pane_id} \ + #{pane_index} #{cursor_x} #{cursor_y} #{pane_width} #{pane_height} \ + #{pane_left} #{pane_top}'\n" + .to_owned() + } + + fn process_result(&self, domain_id: DomainId, result: &Guarded) -> anyhow::Result<()> { + #[derive(Debug)] + struct Item { + session_id: TmuxSessionId, + window_id: TmuxWindowId, + pane_id: TmuxPaneId, + pane_index: u64, + cursor_x: u64, + cursor_y: u64, + pane_width: u64, + pane_height: u64, + pane_left: u64, + pane_top: u64, + } + + let mut items = vec![]; + + for line in result.output.split('\n') { + if line.is_empty() { + continue; + } + let mut fields = line.split(' '); + let session_id = fields.next().ok_or_else(|| anyhow!("missing session_id"))?; + let window_id = fields.next().ok_or_else(|| anyhow!("missing window_id"))?; + let pane_id = fields.next().ok_or_else(|| anyhow!("missing pane_id"))?; + let pane_index = fields + .next() + .ok_or_else(|| anyhow!("missing pane_index"))? + .parse()?; + let cursor_x = fields + .next() + .ok_or_else(|| anyhow!("missing cursor_x"))? + .parse()?; + let cursor_y = fields + .next() + .ok_or_else(|| anyhow!("missing cursor_y"))? + .parse()?; + let pane_width = fields + .next() + .ok_or_else(|| anyhow!("missing pane_width"))? + .parse()?; + let pane_height = fields + .next() + .ok_or_else(|| anyhow!("missing pane_height"))? + .parse()?; + let pane_left = fields + .next() + .ok_or_else(|| anyhow!("missing pane_left"))? + .parse()?; + let pane_top = fields + .next() + .ok_or_else(|| anyhow!("missing pane_top"))? + .parse()?; + + // These ids all have various sigils such as `$`, `%`, `@`, + // so skip those prior to parsing them + let session_id = session_id[1..].parse()?; + let window_id = window_id[1..].parse()?; + let pane_id = pane_id[1..].parse()?; + + items.push(Item { + session_id, + window_id, + pane_id, + pane_index, + cursor_x, + cursor_y, + pane_width, + pane_height, + pane_left, + pane_top, + }); + } + + log::error!("panes in domain_id {}: {:?}", domain_id, items); + Ok(()) + } +} pub(crate) struct TmuxDomainState { pane_id: PaneId, pub domain_id: DomainId, - parser: RefCell, + parser: RefCell, + state: RefCell, + cmd_queue: RefCell>>, } pub struct TmuxDomain { @@ -22,7 +128,55 @@ impl TmuxDomainState { pub fn advance(&self, b: u8) { let mut parser = self.parser.borrow_mut(); if let Some(event) = parser.advance_byte(b) { - log::error!("tmux: {:?}", event); + let state = *self.state.borrow(); + log::error!("tmux: {:?} in state {:?}", event, state); + if let Event::Guarded(response) = event { + match state { + State::WaitForInitialGuard => { + *self.state.borrow_mut() = State::Idle; + } + State::WaitingForResponse => { + let cmd = self.cmd_queue.borrow_mut().pop_front().unwrap(); + let domain_id = self.domain_id; + *self.state.borrow_mut() = State::Idle; + promise::spawn::spawn(async move { + if let Err(err) = cmd.process_result(domain_id, &response) { + log::error!("error processing result: {}", err); + } + }) + .detach(); + } + State::Idle => {} + } + } + } + if *self.state.borrow() == State::Idle && !self.cmd_queue.borrow().is_empty() { + let domain_id = self.domain_id; + promise::spawn::spawn(async move { + let mux = Mux::get().expect("to be called on main thread"); + if let Some(domain) = mux.get_domain(domain_id) { + if let Some(tmux_domain) = domain.downcast_ref::() { + tmux_domain.send_next_command(); + } + } + }) + .detach(); + } + } + + fn send_next_command(&self) { + if *self.state.borrow() != State::Idle { + return; + } + if let Some(first) = self.cmd_queue.borrow().front() { + let cmd = first.get_command(); + log::error!("sending cmd {:?}", cmd); + let mux = Mux::get().expect("to be called on main thread"); + if let Some(pane) = mux.get_pane(self.pane_id) { + let mut writer = pane.writer(); + let _ = write!(writer, "{}", cmd); + } + *self.state.borrow_mut() = State::WaitingForResponse; } } } @@ -30,14 +184,22 @@ impl TmuxDomainState { impl TmuxDomain { pub fn new(pane_id: PaneId) -> Self { let domain_id = alloc_domain_id(); - let parser = RefCell::new(tmux_cc::Parser::new()); + let parser = RefCell::new(Parser::new()); + let mut cmd_queue = VecDeque::>::new(); + cmd_queue.push_back(Box::new(ListAllPanes)); let inner = Arc::new(TmuxDomainState { domain_id, pane_id, parser, + state: RefCell::new(State::WaitForInitialGuard), + cmd_queue: RefCell::new(cmd_queue), }); Self { inner } } + + fn send_next_command(&self) { + self.inner.send_next_command(); + } } #[async_trait(?Send)]