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:
parent
0b64683a60
commit
aaf63285d2
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2150,6 +2150,7 @@ dependencies = [
|
||||
"termwiz",
|
||||
"textwrap 0.12.1",
|
||||
"thiserror",
|
||||
"tmux-cc",
|
||||
"unicode-segmentation",
|
||||
"url",
|
||||
"wezterm-term",
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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
85
mux/src/tmux.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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
|
||||
);
|
||||
|
@ -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 }
|
||||
|
Loading…
Reference in New Issue
Block a user