1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-11 14:25:57 +03:00

tmux: attach control mode parser to terminal

This causes `tmux -CC attach` to enter control mode and patch
into the terminal, printing out parsed event messages.

refs: https://github.com/wez/wezterm/issues/336
This commit is contained in:
Wez Furlong 2020-11-02 08:33:01 -08:00
parent 0b64683a60
commit aaf63285d2
8 changed files with 250 additions and 10 deletions

1
Cargo.lock generated
View File

@ -2150,6 +2150,7 @@ dependencies = [
"termwiz",
"textwrap 0.12.1",
"thiserror",
"tmux-cc",
"unicode-segmentation",
"url",
"wezterm-term",

View File

@ -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

View File

@ -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"

View File

@ -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;

View File

@ -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::{
@ -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),

85
mux/src/tmux.rs Normal file
View File

@ -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
}
}

View File

@ -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> {
@ -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();
@ -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())?;
@ -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
@ -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();
@ -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
);

View File

@ -1,21 +1,31 @@
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 |
@ -23,8 +33,13 @@ line = _{ (
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 }