Skip to content

Commit

Permalink
tmux: attach control mode parser to terminal
Browse files Browse the repository at this point in the history
This causes `tmux -CC attach` to enter control mode and patch
into the terminal, printing out parsed event messages.

refs: #336
  • Loading branch information
wez committed Nov 20, 2020
1 parent 0b64683 commit aaf6328
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 10 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["wezterm-mux-server", "wezterm", "wezterm-gui", "strip-ansi-escapes", "tmux-cc"]
members = ["wezterm-mux-server", "wezterm", "wezterm-gui", "strip-ansi-escapes"]

[profile.release]
opt-level = 3
7 changes: 4 additions & 3 deletions mux/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ ratelim= { path = "../ratelim" }
regex = "1"
serde = {version="1.0", features = ["rc", "derive"]}
ssh2 = "0.8"
terminfo = "0.7"
termwiz = { path = "../termwiz" }
textwrap = "0.12"
thiserror = "1.0"
tmux-cc = { path = "../tmux-cc" }
unicode-segmentation = "1.6"
url = "2"
wezterm-term = { path = "../term", features=["use_serde"] }
terminfo = "0.7"
unicode-segmentation = "1.6"
textwrap = "0.12"
1 change: 1 addition & 0 deletions mux/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub mod renderable;
pub mod ssh;
pub mod tab;
pub mod termwiztermtab;
pub mod tmux;
pub mod window;

use crate::activity::Activity;
Expand Down
65 changes: 64 additions & 1 deletion mux/src/localpane.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use crate::domain::DomainId;
use crate::pane::{Pane, PaneId, Pattern, SearchResult};
use crate::renderable::Renderable;
use crate::tmux::{TmuxDomain, TmuxDomainState};
use crate::{Domain, Mux};
use anyhow::Error;
use async_trait::async_trait;
use portable_pty::{Child, MasterPty, PtySize};
use std::cell::{RefCell, RefMut};
use std::sync::Arc;
use termwiz::escape::DeviceControlMode;
use url::Url;
use wezterm_term::color::ColorPalette;
use wezterm_term::{
Expand Down Expand Up @@ -227,14 +230,74 @@ impl Pane for LocalPane {
}
}

struct LocalPaneDCSHandler {
pane_id: PaneId,
tmux_domain: Option<Arc<TmuxDomainState>>,
}

impl wezterm_term::DeviceControlHandler for LocalPaneDCSHandler {
fn handle_device_control(&mut self, control: termwiz::escape::DeviceControlMode) {
match control {
DeviceControlMode::Enter(mode) => {
if !mode.ignored_extra_intermediates
&& mode.params.len() == 1
&& mode.params[0] == 1000
&& mode.intermediates.is_empty()
{
log::error!("tmux -CC mode requested");

// Create a new domain to host these tmux tabs
let domain = TmuxDomain::new(self.pane_id);
let tmux_domain = Arc::clone(&domain.inner);

let domain: Arc<dyn Domain> = Arc::new(domain);
let mux = Mux::get().expect("to be called on main thread");
mux.add_domain(&domain);

self.tmux_domain.replace(tmux_domain);

// TODO: do we need to proactively list available tabs here?
// if so we should arrange to call domain.attach() and make
// it do the right thing.
} else {
log::error!("unknown DeviceControlMode::Enter {:?}", mode,);
}
}
DeviceControlMode::Exit => {
if let Some(tmux) = self.tmux_domain.take() {
log::error!("FIXME: detach domain here!");
}
}
DeviceControlMode::Data(c) => {
if let Some(tmux) = self.tmux_domain.as_ref() {
tmux.advance(c);
} else {
log::error!(
"unhandled DeviceControlMode::Data {:x} {}",
c,
(c as char).escape_debug()
);
}
}
_ => {
log::error!("unhandled: {:?}", control);
}
}
}
}

impl LocalPane {
pub fn new(
pane_id: PaneId,
terminal: Terminal,
mut terminal: Terminal,
process: Box<dyn Child>,
pty: Box<dyn MasterPty>,
domain_id: DomainId,
) -> Self {
terminal.set_device_control_handler(Box::new(LocalPaneDCSHandler {
pane_id,
tmux_domain: None,
}));
Self {
pane_id,
terminal: RefCell::new(terminal),
Expand Down
85 changes: 85 additions & 0 deletions mux/src/tmux.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
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 async_trait::async_trait;
use portable_pty::{CommandBuilder, PtySize};
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;

pub(crate) struct TmuxDomainState {
pane_id: PaneId,
domain_id: DomainId,
parser: RefCell<tmux_cc::Parser>,
}

pub struct TmuxDomain {
pub(crate) inner: Arc<TmuxDomainState>,
}

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);
}
}
}

impl TmuxDomain {
pub fn new(pane_id: PaneId) -> Self {
let domain_id = alloc_domain_id();
let parser = RefCell::new(tmux_cc::Parser::new());
let inner = Arc::new(TmuxDomainState {
domain_id,
pane_id,
parser,
});
Self { inner }
}
}

#[async_trait(?Send)]
impl Domain for TmuxDomain {
async fn spawn(
&self,
size: PtySize,
command: Option<CommandBuilder>,
command_dir: Option<String>,
window: WindowId,
) -> anyhow::Result<Rc<Tab>> {
anyhow::bail!("Spawn not yet implemented for TmuxDomain");
}

async fn split_pane(
&self,
command: Option<CommandBuilder>,
command_dir: Option<String>,
tab: TabId,
pane_id: PaneId,
direction: SplitDirection,
) -> anyhow::Result<Rc<dyn Pane>> {
anyhow::bail!("split_pane not yet implemented for TmuxDomain");
}

fn domain_id(&self) -> DomainId {
self.inner.domain_id
}

fn domain_name(&self) -> &str {
"tmux"
}

async fn attach(&self) -> anyhow::Result<()> {
Ok(())
}

fn detach(&self) -> anyhow::Result<()> {
anyhow::bail!("detach not implemented for TmuxDomain");
}

fn state(&self) -> DomainState {
DomainState::Attached
}
}
80 changes: 77 additions & 3 deletions tmux-cc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,43 @@ pub enum Event {
pane: TmuxPaneId,
text: String,
},
Exit,
Exit {
reason: Option<String>,
},
SessionsChanged,
SessionChanged {
session: TmuxSessionId,
name: String,
},
SessionRenamed {
name: String,
},
SessionWindowChanged {
session: TmuxSessionId,
window: TmuxWindowId,
},
ClientSessionChanged {
client_name: String,
session: TmuxSessionId,
session_name: String,
},
PaneModeChanged {
pane: TmuxPaneId,
},
WindowAdd {
window: TmuxWindowId,
},
WindowClose {
window: TmuxWindowId,
},
WindowPaneChanged {
window: TmuxWindowId,
pane: TmuxPaneId,
},
WindowRenamed {
window: TmuxWindowId,
name: String,
},
}

fn parse_pane_id(pair: Pair<Rule>) -> anyhow::Result<TmuxPaneId> {
Expand Down Expand Up @@ -146,7 +171,11 @@ fn parse_line(line: &str) -> anyhow::Result<Event> {
flags,
})
}
Rule::exit => Ok(Event::Exit),
Rule::exit => {
let mut pairs = pair.into_inner();
let reason = pairs.next().map(|pair| pair.as_str().to_owned());
Ok(Event::Exit { reason })
}
Rule::sessions_changed => Ok(Event::SessionsChanged),
Rule::pane_mode_changed => {
let mut pairs = pair.into_inner();
Expand All @@ -158,6 +187,23 @@ fn parse_line(line: &str) -> anyhow::Result<Event> {
let window = parse_window_id(pairs.next().unwrap())?;
Ok(Event::WindowAdd { window })
}
Rule::window_close => {
let mut pairs = pair.into_inner();
let window = parse_window_id(pairs.next().unwrap())?;
Ok(Event::WindowClose { window })
}
Rule::window_pane_changed => {
let mut pairs = pair.into_inner();
let window = parse_window_id(pairs.next().unwrap())?;
let pane = parse_pane_id(pairs.next().unwrap())?;
Ok(Event::WindowPaneChanged { window, pane })
}
Rule::window_renamed => {
let mut pairs = pair.into_inner();
let window = parse_window_id(pairs.next().unwrap())?;
let name = unvis(pairs.next().unwrap().as_str())?;
Ok(Event::WindowRenamed { window, name })
}
Rule::output => {
let mut pairs = pair.into_inner();
let pane = parse_pane_id(pairs.next().unwrap())?;
Expand All @@ -170,7 +216,31 @@ fn parse_line(line: &str) -> anyhow::Result<Event> {
let name = unvis(pairs.next().unwrap().as_str())?;
Ok(Event::SessionChanged { session, name })
}
Rule::client_session_changed => {
let mut pairs = pair.into_inner();
let client_name = unvis(pairs.next().unwrap().as_str())?;
let session = parse_session_id(pairs.next().unwrap())?;
let session_name = unvis(pairs.next().unwrap().as_str())?;
Ok(Event::ClientSessionChanged {
client_name,
session,
session_name,
})
}
Rule::session_renamed => {
let mut pairs = pair.into_inner();
let name = unvis(pairs.next().unwrap().as_str())?;
Ok(Event::SessionRenamed { name })
}
Rule::session_window_changed => {
let mut pairs = pair.into_inner();
let session = parse_session_id(pairs.next().unwrap())?;
let window = parse_window_id(pairs.next().unwrap())?;
Ok(Event::SessionWindowChanged { session, window })
}
Rule::pane_id
| Rule::word
| Rule::client_name
| Rule::window_id
| Rule::session_id
| Rule::any_text
Expand Down Expand Up @@ -531,6 +601,7 @@ here
%output %1 \\033]7;file://cube-localdomain/home/wez\\033\\134
%output %1 \\033[K\\033[?2004h
%exit
%exit I said so
";

let mut p = Parser::new();
Expand Down Expand Up @@ -569,7 +640,10 @@ here
pane: 1,
text: "\x1b[K\x1b[?2004h".to_owned(),
},
Event::Exit,
Event::Exit { reason: None },
Event::Exit {
reason: Some("I said so".to_owned())
},
],
events
);
Expand Down
19 changes: 17 additions & 2 deletions tmux-cc/src/tmux.pest
Original file line number Diff line number Diff line change
@@ -1,30 +1,45 @@
number = { ASCII_DIGIT+ }
any_text = { ANY* }
word = { ASCII_ALPHANUMERIC+ }

pane_id = { "%" ~ number }
window_id = { "@" ~ number }
session_id = { "$" ~ number }
client_name = { word }

begin = { "%begin " ~ number ~ " " ~ number ~ " " ~ number }
end = { "%end " ~ number ~ " " ~ number ~ " " ~ number }
error = { "%error " ~ number ~ " " ~ number ~ " " ~ number }

client_session_changed = { "%client-session-changed " ~ client_name ~ " " ~ session_id ~ " " ~any_text }
output = { "%output " ~ pane_id ~ " " ~ any_text }
exit = { "%exit" }
exit = { "%exit" ~ (" " ~ any_text)? }
sessions_changed = { "%sessions-changed" }
pane_mode_changed = { "%pane-mode-changed " ~ pane_id }
window_add = { "%window-add " ~ window_id }
window_close = { "%window-close " ~ window_id }
window_pane_changed = { "%window-pane-changed " ~ window_id ~ " " ~ pane_id }
window_renamed = { "%window-renamed " ~ window_id ~ " " ~ any_text }
session_changed = { "%session-changed " ~ session_id ~ " " ~ any_text }
session_renamed = { "%session-renamed " ~ any_text }
session_window_changed = { "%session-window-changed " ~ session_id ~ " " ~ window_id }

line = _{ (
client_session_changed |
begin |
end |
error |
exit |
output |
pane_mode_changed |
session_changed |
session_renamed |
session_window_changed |
sessions_changed |
window_add
window_add |
window_close |
window_pane_changed |
window_renamed
) }

line_entire = _{ SOI ~ line ~ EOI }

0 comments on commit aaf6328

Please sign in to comment.