1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-21 03:39:16 +03:00

wezterm: introduce concept of Pane to the Mux model

This adds an extra level of indirection to the Mux model;
previously we allowed for Windows to contain an ordered
collection of Tabs, where each Tab represented some kind
of pty.

This change effectively renames the existing Tab trait to Pane
and introduces a new Tab container, so the new model is that
a Window contains an ordered collection of Tabs, and each Tab
can have a single optional Pane.

refs: https://github.com/wez/wezterm/issues/157
This commit is contained in:
Wez Furlong 2020-06-26 16:49:07 -07:00
parent 3484478ba2
commit a6316315bb
27 changed files with 835 additions and 597 deletions

View File

@ -95,8 +95,8 @@ The default key bindings are:
| `CTRL` | `=` | `IncreaseFontSize` |
| `SUPER` | `0` | `ResetFontSize` |
| `CTRL` | `0` | `ResetFontSize` |
| `SUPER` | `t` | `SpawnTab="CurrentTabDomain"` |
| `CTRL+SHIFT` | `t` | `SpawnTab="CurrentTabDomain"` |
| `SUPER` | `t` | `SpawnTab="CurrentPaneDomain"` |
| `CTRL+SHIFT` | `t` | `SpawnTab="CurrentPaneDomain"` |
| `SUPER+SHIFT` | `T` | `SpawnTab="DefaultDomain"` |
| `SUPER` | `w` | `CloseCurrentTab` |
| `SUPER` | `1` | `ActivateTab=0` |
@ -284,7 +284,7 @@ return {
-- Create a new tab in the default domain
{key="t", mods="SHIFT|ALT", action=wezterm.action{SpawnTab="DefaultDomain"}},
-- Create a new tab in the same domain as the current tab
{key="t", mods="SHIFT|ALT", action=wezterm.action{SpawnTab="CurrentTabDomain"}},
{key="t", mods="SHIFT|ALT", action=wezterm.action{SpawnTab="CurrentPaneDomain"}},
-- Create a tab in a named domain
{key="t", mods="SHIFT|ALT", action=wezterm.action{SpawnTab={DomainName="unix"}}},
}

View File

@ -74,7 +74,7 @@ impl FrontEnd for GuiFrontEnd {
fn spawn_new_window(
&self,
fontconfig: &Rc<FontConfiguration>,
tab: &Rc<dyn Tab>,
tab: &Rc<Tab>,
window_id: MuxWindowId,
) -> anyhow::Result<()> {
termwindow::TermWindow::new_window(&configuration(), fontconfig, tab, window_id)

View File

@ -2,7 +2,7 @@ use crate::frontend::gui::selection::{SelectionCoordinate, SelectionRange};
use crate::frontend::gui::termwindow::TermWindow;
use crate::mux::domain::DomainId;
use crate::mux::renderable::*;
use crate::mux::tab::{Tab, TabId};
use crate::mux::tab::{Pane, PaneId};
use portable_pty::PtySize;
use rangeset::RangeSet;
use std::cell::{RefCell, RefMut};
@ -18,13 +18,13 @@ use wezterm_term::{
use window::WindowOps;
pub struct CopyOverlay {
delegate: Rc<dyn Tab>,
delegate: Rc<dyn Pane>,
render: RefCell<CopyRenderable>,
}
struct CopyRenderable {
cursor: StableCursorPosition,
delegate: Rc<dyn Tab>,
delegate: Rc<dyn Pane>,
start: Option<SelectionCoordinate>,
viewport: Option<StableRowIndex>,
/// We use this to cancel ourselves later
@ -38,20 +38,20 @@ struct Dimensions {
}
impl CopyOverlay {
pub fn with_tab(term_window: &TermWindow, tab: &Rc<dyn Tab>) -> Rc<dyn Tab> {
let mut cursor = tab.renderer().get_cursor_position();
pub fn with_pane(term_window: &TermWindow, pane: &Rc<dyn Pane>) -> Rc<dyn Pane> {
let mut cursor = pane.renderer().get_cursor_position();
cursor.shape = termwiz::surface::CursorShape::SteadyBlock;
let window = term_window.window.clone().unwrap();
let render = CopyRenderable {
cursor,
window,
delegate: Rc::clone(tab),
delegate: Rc::clone(pane),
start: None,
viewport: term_window.get_viewport(tab.tab_id()),
viewport: term_window.get_viewport(pane.pane_id()),
};
Rc::new(CopyOverlay {
delegate: Rc::clone(tab),
delegate: Rc::clone(pane),
render: RefCell::new(render),
})
}
@ -99,10 +99,10 @@ impl CopyRenderable {
}
fn adjust_selection(&self, start: SelectionCoordinate, range: SelectionRange) {
let tab_id = self.delegate.tab_id();
let pane_id = self.delegate.pane_id();
self.window.apply(move |term_window, window| {
if let Some(term_window) = term_window.downcast_mut::<TermWindow>() {
let mut selection = term_window.selection(tab_id);
let mut selection = term_window.selection(pane_id);
selection.start = Some(start);
selection.range = Some(range);
window.invalidate();
@ -152,10 +152,10 @@ impl CopyRenderable {
fn set_viewport(&self, row: Option<StableRowIndex>) {
let dims = self.delegate.renderer().get_dimensions();
let tab_id = self.delegate.tab_id();
let pane_id = self.delegate.pane_id();
self.window.apply(move |term_window, _window| {
if let Some(term_window) = term_window.downcast_mut::<TermWindow>() {
term_window.set_viewport(tab_id, row, dims);
term_window.set_viewport(pane_id, row, dims);
}
Ok(())
});
@ -163,7 +163,7 @@ impl CopyRenderable {
fn close(&self) {
self.set_viewport(None);
TermWindow::schedule_cancel_overlay(self.window.clone(), self.delegate.tab_id());
TermWindow::schedule_cancel_overlay(self.window.clone(), self.delegate.pane_id());
}
fn page_up(&mut self) {
@ -375,9 +375,9 @@ impl CopyRenderable {
}
}
impl Tab for CopyOverlay {
fn tab_id(&self) -> TabId {
self.delegate.tab_id()
impl Pane for CopyOverlay {
fn pane_id(&self) -> PaneId {
self.delegate.pane_id()
}
fn renderer(&self) -> RefMut<dyn Renderable> {

View File

@ -1,5 +1,5 @@
use crate::frontend::gui::termwindow::TermWindow;
use crate::mux::tab::{Tab, TabId};
use crate::mux::tab::{Pane, Tab, TabId};
use crate::termwiztermtab::{allocate, TermWizTerminal};
use std::pin::Pin;
use std::rc::Rc;
@ -16,10 +16,10 @@ pub use tabnavigator::tab_navigator;
pub fn start_overlay<T, F>(
term_window: &TermWindow,
tab: &Rc<dyn Tab>,
tab: &Rc<Tab>,
func: F,
) -> (
Rc<dyn Tab>,
Rc<dyn Pane>,
Pin<Box<dyn std::future::Future<Output = Option<anyhow::Result<T>>>>>,
)
where
@ -27,7 +27,9 @@ where
F: Send + 'static + FnOnce(TabId, TermWizTerminal) -> anyhow::Result<T>,
{
let tab_id = tab.tab_id();
let dims = tab.renderer().get_dimensions();
let pane = tab.get_active_pane().expect("tab to have pane");
// TODO: our overlay should overlap the overall contents of the pane
let dims = pane.renderer().get_dimensions();
let (tw_term, tw_tab) = allocate(dims.cols, dims.viewport_rows);
let window = term_window.window.clone().unwrap();

View File

@ -2,8 +2,8 @@ use crate::frontend::gui::selection::{SelectionCoordinate, SelectionRange};
use crate::frontend::gui::termwindow::TermWindow;
use crate::mux::domain::DomainId;
use crate::mux::renderable::*;
use crate::mux::tab::{Pane, PaneId};
use crate::mux::tab::{Pattern, SearchResult};
use crate::mux::tab::{Tab, TabId};
use portable_pty::PtySize;
use rangeset::RangeSet;
use std::cell::{RefCell, RefMut};
@ -20,7 +20,7 @@ use window::WindowOps;
pub struct SearchOverlay {
renderer: RefCell<SearchRenderable>,
delegate: Rc<dyn Tab>,
delegate: Rc<dyn Pane>,
}
#[derive(Debug)]
@ -30,7 +30,7 @@ struct MatchResult {
}
struct SearchRenderable {
delegate: Rc<dyn Tab>,
delegate: Rc<dyn Pane>,
/// The text that the user entered
pattern: Pattern,
/// The most recently queried set of matches
@ -50,13 +50,17 @@ struct SearchRenderable {
}
impl SearchOverlay {
pub fn with_tab(term_window: &TermWindow, tab: &Rc<dyn Tab>, pattern: Pattern) -> Rc<dyn Tab> {
let viewport = term_window.get_viewport(tab.tab_id());
let dims = tab.renderer().get_dimensions();
pub fn with_pane(
term_window: &TermWindow,
pane: &Rc<dyn Pane>,
pattern: Pattern,
) -> Rc<dyn Pane> {
let viewport = term_window.get_viewport(pane.pane_id());
let dims = pane.renderer().get_dimensions();
let window = term_window.window.clone().unwrap();
let mut renderer = SearchRenderable {
delegate: Rc::clone(tab),
delegate: Rc::clone(pane),
pattern,
results: vec![],
by_line: HashMap::new(),
@ -75,7 +79,7 @@ impl SearchOverlay {
Rc::new(SearchOverlay {
renderer: RefCell::new(renderer),
delegate: Rc::clone(tab),
delegate: Rc::clone(pane),
})
}
@ -93,9 +97,9 @@ impl SearchOverlay {
}
}
impl Tab for SearchOverlay {
fn tab_id(&self) -> TabId {
self.delegate.tab_id()
impl Pane for SearchOverlay {
fn pane_id(&self) -> PaneId {
self.delegate.pane_id()
}
fn renderer(&self) -> RefMut<dyn Renderable> {
@ -263,15 +267,15 @@ impl SearchRenderable {
}
fn close(&self) {
TermWindow::schedule_cancel_overlay(self.window.clone(), self.delegate.tab_id());
TermWindow::schedule_cancel_overlay(self.window.clone(), self.delegate.pane_id());
}
fn set_viewport(&self, row: Option<StableRowIndex>) {
let dims = self.delegate.renderer().get_dimensions();
let tab_id = self.delegate.tab_id();
let pane_id = self.delegate.pane_id();
self.window.apply(move |term_window, _window| {
if let Some(term_window) = term_window.downcast_mut::<TermWindow>() {
term_window.set_viewport(tab_id, row, dims);
term_window.set_viewport(pane_id, row, dims);
}
Ok(())
});
@ -337,20 +341,20 @@ impl SearchRenderable {
self.dirty_results.add(bar_pos);
if !self.pattern.is_empty() {
let tab: Rc<dyn Tab> = self.delegate.clone();
let pane: Rc<dyn Pane> = self.delegate.clone();
let window = self.window.clone();
let pattern = self.pattern.clone();
promise::spawn::spawn(async move {
let mut results = tab.search(pattern).await?;
let mut results = pane.search(pattern).await?;
results.sort();
let tab_id = tab.tab_id();
let pane_id = pane.pane_id();
let mut results = Some(results);
window.apply(move |term_window, _window| {
let term_window = term_window
.downcast_mut::<TermWindow>()
.expect("to be TermWindow");
let state = term_window.tab_state(tab_id);
let state = term_window.pane_state(pane_id);
if let Some(overlay) = state.overlay.as_ref() {
if let Some(search_overlay) = overlay.downcast_ref::<SearchOverlay>() {
let mut r = search_overlay.renderer.borrow_mut();
@ -377,10 +381,10 @@ impl SearchRenderable {
}
fn clear_selection(&mut self) {
let tab_id = self.delegate.tab_id();
let pane_id = self.delegate.pane_id();
self.window.apply(move |term_window, _window| {
if let Some(term_window) = term_window.downcast_mut::<TermWindow>() {
let mut selection = term_window.selection(tab_id);
let mut selection = term_window.selection(pane_id);
selection.start.take();
selection.range.take();
}
@ -392,10 +396,10 @@ impl SearchRenderable {
self.result_pos.replace(n);
let result = self.results[n].clone();
let tab_id = self.delegate.tab_id();
let pane_id = self.delegate.pane_id();
self.window.apply(move |term_window, _window| {
if let Some(term_window) = term_window.downcast_mut::<TermWindow>() {
let mut selection = term_window.selection(tab_id);
let mut selection = term_window.selection(pane_id);
let start = SelectionCoordinate {
x: result.start_x,
y: result.start_y,

View File

@ -58,18 +58,22 @@ impl TabBarState {
let per_tab_overhead = 2;
let system_overhead = 3;
let tab_titles: Vec<_> = window
let tab_titles: Vec<String> = window
.iter()
.map(|w| {
let mut title = w.get_title();
// We have a preferred soft minimum on tab width to make it
// easier to click on tab titles, but we'll still go below
// this if there are too many tabs to fit the window at
// this width.
while title.len() < 5 {
title.push(' ');
.map(|tab| {
if let Some(pane) = tab.get_active_pane() {
let mut title = pane.get_title();
// We have a preferred soft minimum on tab width to make it
// easier to click on tab titles, but we'll still go below
// this if there are too many tabs to fit the window at
// this width.
while title.len() < 5 {
title.push(' ');
}
title
} else {
"no pane".to_string()
}
title
})
.collect();
let titles_len: usize = tab_titles.iter().map(|s| unicode_column_width(s)).sum();

File diff suppressed because it is too large Load Diff

View File

@ -102,7 +102,7 @@ pub trait FrontEnd: Downcast {
fn spawn_new_window(
&self,
fontconfig: &Rc<FontConfiguration>,
tab: &Rc<dyn Tab>,
tab: &Rc<Tab>,
window_id: WindowId,
) -> anyhow::Result<()>;
}

View File

@ -66,7 +66,7 @@ impl FrontEnd for MuxServerFrontEnd {
fn spawn_new_window(
&self,
_fontconfig: &Rc<FontConfiguration>,
_tab: &Rc<dyn Tab>,
_tab: &Rc<Tab>,
_window_id: WindowId,
) -> anyhow::Result<()> {
Ok(())

View File

@ -29,7 +29,7 @@ pub enum SpawnTabDomain {
/// Use the default domain
DefaultDomain,
/// Use the domain from the current tab in the associated window
CurrentTabDomain,
CurrentPaneDomain,
/// Use a specific domain by id
Domain(DomainId),
/// Use a specific domain by name
@ -38,7 +38,7 @@ pub enum SpawnTabDomain {
impl Default for SpawnTabDomain {
fn default() -> Self {
Self::CurrentTabDomain
Self::CurrentPaneDomain
}
}
@ -187,17 +187,17 @@ impl InputMap {
[
KeyModifiers::SUPER,
KeyCode::Char('t'),
SpawnTab(SpawnTabDomain::CurrentTabDomain)
SpawnTab(SpawnTabDomain::CurrentPaneDomain)
],
[
ctrl_shift,
KeyCode::Char('T'),
SpawnTab(SpawnTabDomain::CurrentTabDomain)
SpawnTab(SpawnTabDomain::CurrentPaneDomain)
],
[
KeyModifiers::SUPER | KeyModifiers::SHIFT,
KeyCode::Char('T'),
SpawnTab(SpawnTabDomain::CurrentTabDomain)
SpawnTab(SpawnTabDomain::CurrentPaneDomain)
],
[KeyModifiers::SUPER, KeyCode::Char('1'), ActivateTab(0)],
[KeyModifiers::SUPER, KeyCode::Char('2'), ActivateTab(1)],

View File

@ -1,6 +1,6 @@
use crate::mux::domain::DomainId;
use crate::mux::renderable::Renderable;
use crate::mux::tab::{alloc_tab_id, Tab, TabId};
use crate::mux::tab::{alloc_pane_id, Pane, PaneId};
use crate::mux::tab::{Pattern, SearchResult};
use anyhow::Error;
use async_trait::async_trait;
@ -11,8 +11,8 @@ use url::Url;
use wezterm_term::color::ColorPalette;
use wezterm_term::{Clipboard, KeyCode, KeyModifiers, MouseEvent, StableRowIndex, Terminal};
pub struct LocalTab {
tab_id: TabId,
pub struct LocalPane {
pane_id: PaneId,
terminal: RefCell<Terminal>,
process: RefCell<Box<dyn Child>>,
pty: RefCell<Box<dyn MasterPty>>,
@ -20,10 +20,9 @@ pub struct LocalTab {
}
#[async_trait(?Send)]
impl Tab for LocalTab {
#[inline]
fn tab_id(&self) -> TabId {
self.tab_id
impl Pane for LocalPane {
fn pane_id(&self) -> PaneId {
self.pane_id
}
fn renderer(&self) -> RefMut<dyn Renderable> {
@ -34,7 +33,7 @@ impl Tab for LocalTab {
if let Ok(None) = self.process.borrow_mut().try_wait() {
false
} else {
log::error!("is_dead: {:?}", self.tab_id);
log::error!("is_dead: {:?}", self.pane_id);
true
}
}
@ -213,16 +212,16 @@ impl Tab for LocalTab {
}
}
impl LocalTab {
impl LocalPane {
pub fn new(
terminal: Terminal,
process: Box<dyn Child>,
pty: Box<dyn MasterPty>,
domain_id: DomainId,
) -> Self {
let tab_id = alloc_tab_id();
let pane_id = alloc_pane_id();
Self {
tab_id,
pane_id,
terminal: RefCell::new(terminal),
process: RefCell::new(process),
pty: RefCell::new(pty),
@ -231,7 +230,7 @@ impl LocalTab {
}
}
impl Drop for LocalTab {
impl Drop for LocalPane {
fn drop(&mut self) {
// Avoid lingering zombies
self.process.borrow_mut().kill().ok();

View File

@ -921,6 +921,10 @@ fn run() -> anyhow::Result<()> {
name: "TABID".to_string(),
alignment: Alignment::Right,
},
Column {
name: "PANEID".to_string(),
alignment: Alignment::Right,
},
Column {
name: "SIZE".to_string(),
alignment: Alignment::Left,
@ -940,6 +944,7 @@ fn run() -> anyhow::Result<()> {
data.push(vec![
entry.window_id.to_string(),
entry.tab_id.to_string(),
entry.pane_id.to_string(),
format!("{}x{}", entry.size.cols, entry.size.rows),
entry.title.clone(),
entry

View File

@ -6,7 +6,8 @@
//! of an ssh session somewhere.
use crate::config::configuration;
use crate::localtab::LocalTab;
use crate::localtab::LocalPane;
use crate::mux::tab::Pane;
use crate::mux::tab::Tab;
use crate::mux::window::WindowId;
use crate::mux::Mux;
@ -39,7 +40,7 @@ pub trait Domain: Downcast {
command: Option<CommandBuilder>,
command_dir: Option<String>,
window: WindowId,
) -> Result<Rc<dyn Tab>, Error>;
) -> Result<Rc<Tab>, Error>;
/// Returns false if the `spawn` method will never succeed.
/// There are some internal placeholder domains that are
@ -102,7 +103,7 @@ impl Domain for LocalDomain {
command: Option<CommandBuilder>,
command_dir: Option<String>,
window: WindowId,
) -> Result<Rc<dyn Tab>, Error> {
) -> Result<Rc<Tab>, Error> {
let config = configuration();
let mut cmd = match command {
Some(mut cmd) => {
@ -138,7 +139,10 @@ impl Domain for LocalDomain {
);
let mux = Mux::get().unwrap();
let tab: Rc<dyn Tab> = Rc::new(LocalTab::new(terminal, child, pair.master, self.id));
let pane: Rc<dyn Pane> = Rc::new(LocalPane::new(terminal, child, pair.master, self.id));
let tab = Rc::new(Tab::new());
tab.assign_pane(&pane);
mux.add_tab(&tab)?;
mux.add_tab_to_window(&tab, window)?;

View File

@ -1,3 +1,4 @@
use crate::mux::tab::{Pane, PaneId};
use crate::mux::tab::{Tab, TabId};
use crate::mux::window::{Window, WindowId};
use crate::ratelim::RateLimiter;
@ -22,7 +23,7 @@ pub mod window;
#[derive(Clone, Debug)]
pub enum MuxNotification {
TabOutput(TabId),
PaneOutput(PaneId),
}
static SUB_ID: AtomicUsize = AtomicUsize::new(0);
@ -30,7 +31,8 @@ static SUB_ID: AtomicUsize = AtomicUsize::new(0);
pub type MuxSubscriber = PollableReceiver<MuxNotification>;
pub struct Mux {
tabs: RefCell<HashMap<TabId, Rc<dyn Tab>>>,
tabs: RefCell<HashMap<TabId, Rc<Tab>>>,
panes: RefCell<HashMap<PaneId, Rc<dyn Pane>>>,
windows: RefCell<HashMap<WindowId, Window>>,
default_domain: RefCell<Option<Arc<dyn Domain>>>,
domains: RefCell<HashMap<DomainId, Arc<dyn Domain>>>,
@ -38,7 +40,7 @@ pub struct Mux {
subscribers: RefCell<HashMap<usize, PollableSender<MuxNotification>>>,
}
fn read_from_tab_pty(tab_id: TabId, mut reader: Box<dyn std::io::Read>) {
fn read_from_pane_pty(pane_id: PaneId, mut reader: Box<dyn std::io::Read>) {
const BUFSIZE: usize = 32 * 1024;
let mut buf = [0; BUFSIZE];
@ -47,11 +49,11 @@ fn read_from_tab_pty(tab_id: TabId, mut reader: Box<dyn std::io::Read>) {
loop {
match reader.read(&mut buf) {
Ok(size) if size == 0 => {
error!("read_pty EOF: tab_id {}", tab_id);
error!("read_pty EOF: pane_id {}", pane_id);
break;
}
Err(err) => {
error!("read_pty failed: tab {} {:?}", tab_id, err);
error!("read_pty failed: pane {} {:?}", pane_id, err);
break;
}
Ok(size) => {
@ -66,9 +68,9 @@ fn read_from_tab_pty(tab_id: TabId, mut reader: Box<dyn std::io::Read>) {
pos += len;
promise::spawn::spawn_into_main_thread_with_low_priority(async move {
let mux = Mux::get().unwrap();
if let Some(tab) = mux.get_tab(tab_id) {
tab.advance_bytes(&data);
mux.notify(MuxNotification::TabOutput(tab_id));
if let Some(pane) = mux.get_pane(pane_id) {
pane.advance_bytes(&data);
mux.notify(MuxNotification::PaneOutput(pane_id));
}
});
}
@ -83,7 +85,7 @@ fn read_from_tab_pty(tab_id: TabId, mut reader: Box<dyn std::io::Read>) {
}
promise::spawn::spawn_into_main_thread(async move {
let mux = Mux::get().unwrap();
mux.remove_tab(tab_id);
mux.remove_pane(pane_id);
});
}
@ -106,6 +108,7 @@ impl Mux {
Self {
tabs: RefCell::new(HashMap::new()),
panes: RefCell::new(HashMap::new()),
windows: RefCell::new(HashMap::new()),
default_domain: RefCell::new(default_domain),
domains_by_name: RefCell::new(domains_by_name),
@ -178,20 +181,38 @@ impl Mux {
res
}
pub fn get_tab(&self, tab_id: TabId) -> Option<Rc<dyn Tab>> {
pub fn get_pane(&self, pane_id: PaneId) -> Option<Rc<dyn Pane>> {
self.panes.borrow().get(&pane_id).map(Rc::clone)
}
pub fn get_tab(&self, tab_id: TabId) -> Option<Rc<Tab>> {
self.tabs.borrow().get(&tab_id).map(Rc::clone)
}
pub fn add_tab(&self, tab: &Rc<dyn Tab>) -> Result<(), Error> {
self.tabs.borrow_mut().insert(tab.tab_id(), Rc::clone(tab));
let reader = tab.reader()?;
let tab_id = tab.tab_id();
thread::spawn(move || read_from_tab_pty(tab_id, reader));
pub fn add_pane(&self, pane: &Rc<dyn Pane>) -> Result<(), Error> {
self.panes
.borrow_mut()
.insert(pane.pane_id(), Rc::clone(pane));
let reader = pane.reader()?;
let pane_id = pane.pane_id();
thread::spawn(move || read_from_pane_pty(pane_id, reader));
Ok(())
}
pub fn add_tab(&self, tab: &Rc<Tab>) -> Result<(), Error> {
self.tabs.borrow_mut().insert(tab.tab_id(), Rc::clone(tab));
let pane = tab
.get_active_pane()
.ok_or_else(|| anyhow!("tab MUST have an active pane"))?;
self.add_pane(&pane)
}
pub fn remove_pane(&self, pane_id: TabId) {
debug!("removing pane {}", pane_id);
self.panes.borrow_mut().remove(&pane_id);
self.prune_dead_windows();
}
pub fn remove_tab(&self, tab_id: TabId) {
debug!("removing tab {}", tab_id);
self.tabs.borrow_mut().remove(&tab_id);
@ -220,6 +241,17 @@ impl Mux {
self.tabs.borrow_mut().remove(&tab_id);
}
let dead_pane_ids: Vec<TabId> = self
.panes
.borrow()
.iter()
.filter_map(|(&id, pane)| if pane.is_dead() { Some(id) } else { None })
.collect();
for pane_id in dead_pane_ids {
self.panes.borrow_mut().remove(&pane_id);
}
for window_id in dead_windows {
error!("removing window {}", window_id);
windows.remove(&window_id);
@ -253,7 +285,7 @@ impl Mux {
}))
}
pub fn get_active_tab_for_window(&self, window_id: WindowId) -> Option<Rc<dyn Tab>> {
pub fn get_active_tab_for_window(&self, window_id: WindowId) -> Option<Rc<Tab>> {
let window = self.get_window(window_id)?;
window.get_active().map(Rc::clone)
}
@ -265,7 +297,7 @@ impl Mux {
window_id
}
pub fn add_tab_to_window(&self, tab: &Rc<dyn Tab>, window_id: WindowId) -> anyhow::Result<()> {
pub fn add_tab_to_window(&self, tab: &Rc<Tab>, window_id: WindowId) -> anyhow::Result<()> {
let mut window = self
.get_window_mut(window_id)
.ok_or_else(|| anyhow!("add_tab_to_window: no such window_id {}", window_id))?;
@ -277,8 +309,8 @@ impl Mux {
self.tabs.borrow().is_empty()
}
pub fn iter_tabs(&self) -> Vec<Rc<dyn Tab>> {
self.tabs
pub fn iter_panes(&self) -> Vec<Rc<dyn Pane>> {
self.panes
.borrow()
.iter()
.map(|(_, v)| Rc::clone(v))
@ -294,9 +326,9 @@ impl Mux {
}
pub fn domain_was_detached(&self, domain: DomainId) {
self.tabs
self.panes
.borrow_mut()
.retain(|_tab_id, tab| tab.domain_id() != domain);
.retain(|_pane_id, pane| pane.domain_id() != domain);
// Ideally we'd do this here, but that seems to cause problems
// at the moment:
// self.prune_dead_windows();

View File

@ -5,7 +5,8 @@ use async_trait::async_trait;
use downcast_rs::{impl_downcast, Downcast};
use portable_pty::PtySize;
use serde::{Deserialize, Serialize};
use std::cell::RefMut;
use std::cell::{RefCell, RefMut};
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use url::Url;
use wezterm_term::color::ColorPalette;
@ -14,14 +15,17 @@ use wezterm_term::{Clipboard, KeyCode, KeyModifiers, MouseEvent, StableRowIndex}
static TAB_ID: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::AtomicUsize::new(0);
pub type TabId = usize;
pub fn alloc_tab_id() -> TabId {
TAB_ID.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed)
static PANE_ID: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::AtomicUsize::new(0);
pub type PaneId = usize;
pub fn alloc_pane_id() -> PaneId {
PANE_ID.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed)
}
const PASTE_CHUNK_SIZE: usize = 1024;
struct Paste {
tab_id: TabId,
pane_id: PaneId,
text: String,
offset: usize,
}
@ -31,12 +35,12 @@ fn schedule_next_paste(paste: &Arc<Mutex<Paste>>) {
promise::spawn::spawn(async move {
let mut locked = paste.lock().unwrap();
let mux = Mux::get().unwrap();
let tab = mux.get_tab(locked.tab_id).unwrap();
let pane = mux.get_pane(locked.pane_id).unwrap();
let remain = locked.text.len() - locked.offset;
let chunk = remain.min(PASTE_CHUNK_SIZE);
let text_slice = &locked.text[locked.offset..locked.offset + chunk];
tab.send_paste(text_slice).unwrap();
pane.send_paste(text_slice).unwrap();
if chunk < remain {
// There is more to send
@ -84,9 +88,46 @@ pub struct SearchResult {
pub end_x: usize,
}
/// A Tab is a container of Panes
/// At this time only a single pane is supported
pub struct Tab {
id: TabId,
pane: RefCell<Option<Rc<dyn Pane>>>,
}
impl Tab {
pub fn new() -> Self {
Self {
id: TAB_ID.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed),
pane: RefCell::new(None),
}
}
pub fn tab_id(&self) -> TabId {
self.id
}
pub fn is_dead(&self) -> bool {
if let Some(pane) = self.get_active_pane() {
pane.is_dead()
} else {
true
}
}
pub fn get_active_pane(&self) -> Option<Rc<dyn Pane>> {
self.pane.borrow().as_ref().map(Rc::clone)
}
pub fn assign_pane(&self, pane: &Rc<dyn Pane>) {
self.pane.borrow_mut().replace(Rc::clone(pane));
}
}
/// A Pane represents a view on a terminal
#[async_trait(?Send)]
pub trait Tab: Downcast {
fn tab_id(&self) -> TabId;
pub trait Pane: Downcast {
fn pane_id(&self) -> PaneId;
fn renderer(&self) -> RefMut<dyn Renderable>;
fn get_title(&self) -> String;
fn send_paste(&self, text: &str) -> anyhow::Result<()>;
@ -131,7 +172,7 @@ pub trait Tab: Downcast {
self.send_paste(&text[0..PASTE_CHUNK_SIZE])?;
let paste = Arc::new(Mutex::new(Paste {
tab_id: self.tab_id(),
pane_id: self.pane_id(),
text,
offset: PASTE_CHUNK_SIZE,
}));
@ -140,4 +181,4 @@ pub trait Tab: Downcast {
Ok(())
}
}
impl_downcast!(Tab);
impl_downcast!(Pane);

View File

@ -8,7 +8,7 @@ pub type WindowId = usize;
pub struct Window {
id: WindowId,
tabs: Vec<Rc<dyn Tab>>,
tabs: Vec<Rc<Tab>>,
active: usize,
clipboard: Option<Arc<dyn Clipboard>>,
invalidated: bool,
@ -33,26 +33,28 @@ impl Window {
self.id
}
fn check_that_tab_isnt_already_in_window(&self, tab: &Rc<dyn Tab>) {
fn check_that_tab_isnt_already_in_window(&self, tab: &Rc<Tab>) {
for t in &self.tabs {
assert_ne!(t.tab_id(), tab.tab_id(), "tab already added to this window");
}
}
fn assign_clipboard_to_tab(&self, tab: &Rc<dyn Tab>) {
fn assign_clipboard_to_tab(&self, tab: &Rc<Tab>) {
if let Some(clip) = self.clipboard.as_ref() {
tab.set_clipboard(clip);
if let Some(pane) = tab.get_active_pane() {
pane.set_clipboard(clip);
}
}
}
pub fn insert(&mut self, index: usize, tab: &Rc<dyn Tab>) {
pub fn insert(&mut self, index: usize, tab: &Rc<Tab>) {
self.check_that_tab_isnt_already_in_window(tab);
self.assign_clipboard_to_tab(tab);
self.tabs.insert(index, Rc::clone(tab));
self.invalidated = true;
}
pub fn push(&mut self, tab: &Rc<dyn Tab>) {
pub fn push(&mut self, tab: &Rc<Tab>) {
self.check_that_tab_isnt_already_in_window(tab);
self.assign_clipboard_to_tab(tab);
self.tabs.push(Rc::clone(tab));
@ -67,7 +69,7 @@ impl Window {
self.tabs.len()
}
pub fn get_by_idx(&self, idx: usize) -> Option<&Rc<dyn Tab>> {
pub fn get_by_idx(&self, idx: usize) -> Option<&Rc<Tab>> {
self.tabs.get(idx)
}
@ -80,7 +82,7 @@ impl Window {
None
}
pub fn remove_by_idx(&mut self, idx: usize) -> Rc<dyn Tab> {
pub fn remove_by_idx(&mut self, idx: usize) -> Rc<Tab> {
self.invalidated = true;
self.tabs.remove(idx)
}
@ -104,7 +106,7 @@ impl Window {
res
}
pub fn get_active(&self) -> Option<&Rc<dyn Tab>> {
pub fn get_active(&self) -> Option<&Rc<Tab>> {
self.get_by_idx(self.active)
}
@ -119,7 +121,7 @@ impl Window {
self.active = idx;
}
pub fn iter(&self) -> impl Iterator<Item = &Rc<dyn Tab>> {
pub fn iter(&self) -> impl Iterator<Item = &Rc<Tab>> {
self.tabs.iter()
}
@ -128,10 +130,14 @@ impl Window {
.tabs
.iter()
.filter_map(|tab| {
if tab.is_dead() {
Some(tab.tab_id())
if let Some(pane) = tab.get_active_pane() {
if pane.is_dead() {
return Some(tab.tab_id());
} else {
None
}
} else {
None
Some(tab.tab_id())
}
})
.collect();

View File

@ -6,7 +6,7 @@ use crate::mux::Mux;
use crate::server::codec::*;
use crate::server::domain::{ClientDomain, ClientDomainConfig};
use crate::server::pollable::*;
use crate::server::tab::ClientTab;
use crate::server::tab::ClientPane;
use crate::server::UnixStream;
use crate::ssh::ssh_connect_with_ui;
use anyhow::{anyhow, bail, Context, Error};
@ -89,7 +89,7 @@ macro_rules! rpc {
}
fn process_unilateral(local_domain_id: DomainId, decoded: DecodedPdu) -> anyhow::Result<()> {
if let Some(tab_id) = decoded.pdu.tab_id() {
if let Some(pane_id) = decoded.pdu.pane_id() {
let pdu = decoded.pdu;
promise::spawn::spawn_into_main_thread(async move {
let mux = Mux::get().unwrap();
@ -102,27 +102,30 @@ fn process_unilateral(local_domain_id: DomainId, decoded: DecodedPdu) -> anyhow:
anyhow!("domain {} is not a ClientDomain instance", local_domain_id)
})?;
let local_tab_id = client_domain
.remote_to_local_tab_id(tab_id)
.ok_or_else(|| anyhow!("remote tab id {} does not have a local tab id", tab_id))?;
let tab = mux
.get_tab(local_tab_id)
.ok_or_else(|| anyhow!("no such tab {}", local_tab_id))?;
let client_tab = tab.downcast_ref::<ClientTab>().ok_or_else(|| {
let local_pane_id =
client_domain
.remote_to_local_pane_id(pane_id)
.ok_or_else(|| {
anyhow!("remote pane id {} does not have a local pane id", pane_id)
})?;
let pane = mux
.get_pane(local_pane_id)
.ok_or_else(|| anyhow!("no such pane {}", local_pane_id))?;
let client_pane = pane.downcast_ref::<ClientPane>().ok_or_else(|| {
log::error!(
"received unilateral PDU for tab {} which is \
not an instance of ClientTab: {:?}",
local_tab_id,
"received unilateral PDU for pane {} which is \
not an instance of ClientPane: {:?}",
local_pane_id,
pdu
);
anyhow!(
"received unilateral PDU for tab {} which is \
not an instance of ClientTab: {:?}",
local_tab_id,
"received unilateral PDU for pane {} which is \
not an instance of ClientPane: {:?}",
local_pane_id,
pdu
)
})?;
client_tab.process_unilateral(pdu)
client_pane.process_unilateral(pdu)
});
} else {
bail!("don't know how to handle {:?}", decoded);
@ -871,22 +874,22 @@ impl Client {
rpc!(ping, Ping = (), Pong);
rpc!(list_tabs, ListTabs = (), ListTabsResponse);
rpc!(spawn, Spawn, SpawnResponse);
rpc!(write_to_tab, WriteToTab, UnitResponse);
rpc!(write_to_pane, WriteToPane, UnitResponse);
rpc!(send_paste, SendPaste, UnitResponse);
rpc!(key_down, SendKeyDown, UnitResponse);
rpc!(mouse_event, SendMouseEvent, UnitResponse);
rpc!(resize, Resize, UnitResponse);
rpc!(
get_tab_render_changes,
GetTabRenderChanges,
TabLivenessResponse
GetPaneRenderChanges,
LivenessResponse
);
rpc!(get_lines, GetLines, GetLinesResponse);
rpc!(get_codec_version, GetCodecVersion, GetCodecVersionResponse);
rpc!(get_tls_creds, GetTlsCreds = (), GetTlsCredsResponse);
rpc!(
search_scrollback,
SearchTabScrollbackRequest,
SearchTabScrollbackResponse
SearchScrollbackRequest,
SearchScrollbackResponse
);
}

View File

@ -13,7 +13,7 @@
use crate::mux::domain::DomainId;
use crate::mux::renderable::{RenderableDimensions, StableCursorPosition};
use crate::mux::tab::TabId;
use crate::mux::tab::{PaneId, TabId};
use crate::mux::window::WindowId;
use anyhow::{bail, Context as _, Error};
use leb128;
@ -246,7 +246,7 @@ macro_rules! pdu {
/// The overall version of the codec.
/// This must be bumped when backwards incompatible changes
/// are made to the types and protocol.
pub const CODEC_VERSION: usize = 4;
pub const CODEC_VERSION: usize = 5;
// Defines the Pdu enum.
// Each struct has an explicit identifying number.
@ -260,7 +260,7 @@ pdu! {
ListTabsResponse: 4,
Spawn: 7,
SpawnResponse: 8,
WriteToTab: 9,
WriteToPane: 9,
UnitResponse: 10,
SendKeyDown: 11,
SendMouseEvent: 12,
@ -269,15 +269,15 @@ pdu! {
SetClipboard: 20,
GetLines: 22,
GetLinesResponse: 23,
GetTabRenderChanges: 24,
GetTabRenderChangesResponse: 25,
GetPaneRenderChanges: 24,
GetPaneRenderChangesResponse: 25,
GetCodecVersion: 26,
GetCodecVersionResponse: 27,
GetTlsCreds: 28,
GetTlsCredsResponse: 29,
TabLivenessResponse: 30,
SearchTabScrollbackRequest: 31,
SearchTabScrollbackResponse: 32,
LivenessResponse: 30,
SearchScrollbackRequest: 31,
SearchScrollbackResponse: 32,
}
impl Pdu {
@ -347,12 +347,12 @@ impl Pdu {
}
}
pub fn tab_id(&self) -> Option<TabId> {
pub fn pane_id(&self) -> Option<PaneId> {
match self {
Pdu::GetTabRenderChangesResponse(GetTabRenderChangesResponse { tab_id, .. }) => {
Some(*tab_id)
Pdu::GetPaneRenderChangesResponse(GetPaneRenderChangesResponse { pane_id, .. }) => {
Some(*pane_id)
}
Pdu::SetClipboard(SetClipboard { tab_id, .. }) => Some(*tab_id),
Pdu::SetClipboard(SetClipboard { pane_id, .. }) => Some(*pane_id),
_ => None,
}
}
@ -431,8 +431,10 @@ impl Into<String> for SerdeUrl {
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct WindowAndTabEntry {
// FIXME: rename to WindowTabPaneEntry ?
pub window_id: WindowId,
pub tab_id: TabId,
pub pane_id: TabId,
pub title: String,
pub size: PtySize,
pub working_dir: Option<SerdeUrl>,
@ -456,24 +458,25 @@ pub struct Spawn {
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SpawnResponse {
pub tab_id: TabId,
pub pane_id: PaneId,
pub window_id: WindowId,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct WriteToTab {
pub tab_id: TabId,
pub struct WriteToPane {
pub pane_id: PaneId,
pub data: Vec<u8>,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SendPaste {
pub tab_id: TabId,
pub pane_id: PaneId,
pub data: String,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SendKeyDown {
pub tab_id: TabId,
pub pane_id: TabId,
pub event: termwiz::input::KeyEvent,
pub input_serial: InputSerial,
}
@ -514,36 +517,36 @@ impl Into<InputSerial> for std::time::SystemTime {
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SendMouseEvent {
pub tab_id: TabId,
pub pane_id: PaneId,
pub event: wezterm_term::input::MouseEvent,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SetClipboard {
pub tab_id: TabId,
pub pane_id: PaneId,
pub clipboard: Option<String>,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct Resize {
pub tab_id: TabId,
pub pane_id: PaneId,
pub size: PtySize,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetTabRenderChanges {
pub tab_id: TabId,
pub struct GetPaneRenderChanges {
pub pane_id: PaneId,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct TabLivenessResponse {
pub tab_id: TabId,
pub tab_alive: bool,
pub struct LivenessResponse {
pub pane_id: PaneId,
pub is_alive: bool,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetTabRenderChangesResponse {
pub tab_id: TabId,
pub struct GetPaneRenderChangesResponse {
pub pane_id: PaneId,
pub mouse_grabbed: bool,
pub cursor_position: StableCursorPosition,
pub dimensions: RenderableDimensions,
@ -559,7 +562,7 @@ pub struct GetTabRenderChangesResponse {
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetLines {
pub tab_id: TabId,
pub pane_id: PaneId,
pub lines: Vec<Range<StableRowIndex>>,
}
@ -696,18 +699,18 @@ impl Into<Vec<(StableRowIndex, Line)>> for SerializedLines {
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetLinesResponse {
pub tab_id: TabId,
pub pane_id: PaneId,
pub lines: SerializedLines,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SearchTabScrollbackRequest {
pub tab_id: TabId,
pub struct SearchScrollbackRequest {
pub pane_id: PaneId,
pub pattern: crate::mux::tab::Pattern,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SearchTabScrollbackResponse {
pub struct SearchScrollbackResponse {
pub results: Vec<crate::mux::tab::SearchResult>,
}

View File

@ -3,12 +3,12 @@ use crate::connui::ConnectionUI;
use crate::font::FontConfiguration;
use crate::frontend::front_end;
use crate::mux::domain::{alloc_domain_id, Domain, DomainId, DomainState};
use crate::mux::tab::{Tab, TabId};
use crate::mux::tab::{Pane, PaneId, Tab, TabId};
use crate::mux::window::WindowId;
use crate::mux::Mux;
use crate::server::client::Client;
use crate::server::codec::{ListTabsResponse, Spawn};
use crate::server::tab::ClientTab;
use crate::server::tab::ClientPane;
use anyhow::{anyhow, bail};
use async_trait::async_trait;
use portable_pty::{CommandBuilder, PtySize};
@ -24,6 +24,7 @@ pub struct ClientInner {
pub remote_domain_id: DomainId,
remote_to_local_window: Mutex<HashMap<WindowId, WindowId>>,
remote_to_local_tab: Mutex<HashMap<TabId, TabId>>,
remote_to_local_pane: Mutex<HashMap<PaneId, PaneId>>,
}
impl ClientInner {
@ -56,31 +57,55 @@ impl ClientInner {
None
}
pub fn remove_old_tab_mapping(&self, remote_tab_id: TabId) {
let mut tab_map = self.remote_to_local_tab.lock().unwrap();
tab_map.remove(&remote_tab_id);
}
pub fn remote_to_local_pane_id(&self, remote_pane_id: PaneId) -> Option<TabId> {
let mut pane_map = self.remote_to_local_pane.lock().unwrap();
pub fn remote_to_local_tab_id(&self, remote_tab_id: TabId) -> Option<TabId> {
let mut tab_map = self.remote_to_local_tab.lock().unwrap();
if let Some(id) = tab_map.get(&remote_tab_id) {
if let Some(id) = pane_map.get(&remote_pane_id) {
return Some(*id);
}
let mux = Mux::get().unwrap();
for tab in mux.iter_tabs() {
if let Some(tab) = tab.downcast_ref::<ClientTab>() {
if tab.remote_tab_id() == remote_tab_id {
let local_tab_id = tab.tab_id();
tab_map.insert(remote_tab_id, local_tab_id);
return Some(local_tab_id);
for pane in mux.iter_panes() {
if let Some(pane) = pane.downcast_ref::<ClientPane>() {
if pane.remote_pane_id() == remote_pane_id {
let local_pane_id = pane.pane_id();
pane_map.insert(remote_pane_id, local_pane_id);
return Some(local_pane_id);
}
}
}
None
}
pub fn remove_old_pane_mapping(&self, remote_pane_id: PaneId) {
let mut pane_map = self.remote_to_local_pane.lock().unwrap();
pane_map.remove(&remote_pane_id);
}
pub fn remove_old_tab_mapping(&self, remote_tab_id: TabId) {
let mut tab_map = self.remote_to_local_tab.lock().unwrap();
tab_map.remove(&remote_tab_id);
}
fn record_remote_to_local_tab_mapping(&self, remote_tab_id: TabId, local_tab_id: TabId) {
let mut map = self.remote_to_local_tab.lock().unwrap();
map.insert(remote_tab_id, local_tab_id);
log::info!(
"record_remote_to_local_tab_mapping: {} -> {}",
remote_tab_id,
local_tab_id
);
}
pub fn remote_to_local_tab_id(&self, remote_tab_id: TabId) -> Option<TabId> {
let map = self.remote_to_local_tab.lock().unwrap();
for (remote, local) in map.iter() {
if *remote == remote_tab_id {
return Some(*local);
}
}
None
}
}
#[derive(Clone, Debug)]
@ -131,6 +156,7 @@ impl ClientInner {
remote_domain_id,
remote_to_local_window: Mutex::new(HashMap::new()),
remote_to_local_tab: Mutex::new(HashMap::new()),
remote_to_local_pane: Mutex::new(HashMap::new()),
}
}
}
@ -165,9 +191,9 @@ impl ClientDomain {
mux.domain_was_detached(self.local_domain_id);
}
pub fn remote_to_local_tab_id(&self, remote_tab_id: TabId) -> Option<TabId> {
pub fn remote_to_local_pane_id(&self, remote_pane_id: TabId) -> Option<TabId> {
let inner = self.inner()?;
inner.remote_to_local_tab_id(remote_tab_id)
inner.remote_to_local_pane_id(remote_pane_id)
}
pub fn get_client_inner_for_domain(domain_id: DomainId) -> anyhow::Result<Arc<ClientInner>> {
@ -215,28 +241,47 @@ impl ClientDomain {
// removed it from the mux. Let's add it back, but
// with a new id.
inner.remove_old_tab_mapping(entry.tab_id);
tab = Rc::new(ClientTab::new(
tab = Rc::new(Tab::new());
inner.record_remote_to_local_tab_mapping(entry.tab_id, tab.tab_id());
}
};
} else {
tab = Rc::new(Tab::new());
inner.record_remote_to_local_tab_mapping(entry.tab_id, tab.tab_id());
}
if let Some(pane_id) = inner.remote_to_local_pane_id(entry.pane_id) {
match mux.get_pane(pane_id) {
Some(_pane) => {}
None => {
// We likely decided that we hit EOF on the tab and
// removed it from the mux. Let's add it back, but
// with a new id.
inner.remove_old_pane_mapping(entry.pane_id);
let pane: Rc<dyn Pane> = Rc::new(ClientPane::new(
&inner,
entry.tab_id,
entry.pane_id,
entry.size,
&entry.title,
));
mux.add_tab(&tab)?;
tab.assign_pane(&pane);
mux.add_pane(&pane)?;
}
};
} else {
log::info!(
"attaching to remote tab {} in remote window {} {}",
entry.tab_id,
"attaching to remote pane {} in remote window {} {}",
entry.pane_id,
entry.window_id,
entry.title
);
tab = Rc::new(ClientTab::new(
let pane: Rc<dyn Pane> = Rc::new(ClientPane::new(
&inner,
entry.tab_id,
entry.pane_id,
entry.size,
&entry.title,
));
tab.assign_pane(&pane);
mux.add_tab(&tab)?;
}
@ -306,7 +351,7 @@ impl Domain for ClientDomain {
command: Option<CommandBuilder>,
command_dir: Option<String>,
window: WindowId,
) -> anyhow::Result<Rc<dyn Tab>> {
) -> anyhow::Result<Rc<Tab>> {
let inner = self
.inner()
.ok_or_else(|| anyhow!("domain is not attached"))?;
@ -326,7 +371,10 @@ impl Domain for ClientDomain {
result.tab_id
};
let tab: Rc<dyn Tab> = Rc::new(ClientTab::new(&inner, remote_tab_id, size, "wezterm"));
let pane: Rc<dyn Pane> = Rc::new(ClientPane::new(&inner, remote_tab_id, size, "wezterm"));
let tab = Rc::new(Tab::new());
tab.assign_pane(&pane);
let mux = Mux::get().unwrap();
mux.add_tab(&tab)?;
mux.add_tab_to_window(&tab, window)?;

View File

@ -38,7 +38,7 @@ impl<S: ReadAndWrite> ClientSession<S> {
fn process(&mut self) -> Result<(), Error> {
let mut read_buffer = Vec::with_capacity(1024);
let mut tabs_to_output = HashSet::new();
let mut panes_to_output = HashSet::new();
loop {
loop {
@ -56,15 +56,15 @@ impl<S: ReadAndWrite> ClientSession<S> {
match self.mux_rx.try_recv() {
Ok(notif) => match notif {
// Coalesce multiple TabOutputs for the same tab
MuxNotification::TabOutput(tab_id) => tabs_to_output.insert(tab_id),
MuxNotification::PaneOutput(pane_id) => panes_to_output.insert(pane_id),
},
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => bail!("mux_rx is Disconnected"),
};
}
for tab_id in tabs_to_output.drain() {
self.handler.schedule_tab_push(tab_id);
for pane_id in panes_to_output.drain() {
self.handler.schedule_pane_push(pane_id);
}
let mut poll_array = [

View File

@ -1,5 +1,5 @@
use crate::mux::renderable::{RenderableDimensions, StableCursorPosition};
use crate::mux::tab::{Tab, TabId};
use crate::mux::tab::{Pane, PaneId, TabId};
use crate::mux::Mux;
use crate::server::codec::*;
use crate::server::listener::PKI;
@ -17,7 +17,7 @@ use wezterm_term::terminal::Clipboard;
use wezterm_term::StableRowIndex;
#[derive(Default, Debug)]
struct PerTab {
struct PerPane {
cursor_position: StableCursorPosition,
title: String,
working_dir: Option<Url>,
@ -26,39 +26,39 @@ struct PerTab {
mouse_grabbed: bool,
}
impl PerTab {
impl PerPane {
fn compute_changes(
&mut self,
tab: &Rc<dyn Tab>,
pane: &Rc<dyn Pane>,
force_with_input_serial: Option<InputSerial>,
) -> Option<GetTabRenderChangesResponse> {
) -> Option<GetPaneRenderChangesResponse> {
let mut changed = false;
let mouse_grabbed = tab.is_mouse_grabbed();
let mouse_grabbed = pane.is_mouse_grabbed();
if mouse_grabbed != self.mouse_grabbed {
changed = true;
}
let dims = tab.renderer().get_dimensions();
let dims = pane.renderer().get_dimensions();
if dims != self.dimensions {
changed = true;
}
let cursor_position = tab.renderer().get_cursor_position();
let cursor_position = pane.renderer().get_cursor_position();
if cursor_position != self.cursor_position {
changed = true;
}
let title = tab.get_title();
let title = pane.get_title();
if title != self.title {
changed = true;
}
let working_dir = tab.get_current_working_dir();
let working_dir = pane.get_current_working_dir();
if working_dir != self.working_dir {
changed = true;
}
let mut all_dirty_lines = tab
let mut all_dirty_lines = pane
.renderer()
.get_dirty_lines(0..dims.physical_top + dims.viewport_rows as StableRowIndex);
let dirty_delta = all_dirty_lines.difference(&self.dirty_lines);
@ -74,7 +74,7 @@ impl PerTab {
let viewport_range =
dims.physical_top..dims.physical_top + dims.viewport_rows as StableRowIndex;
let (first_line, lines) = tab.renderer().get_lines(viewport_range);
let (first_line, lines) = pane.renderer().get_lines(viewport_range);
let mut bonus_lines = lines
.into_iter()
.enumerate()
@ -87,7 +87,7 @@ impl PerTab {
// Always send the cursor's row, as that tends to the busiest and we don't
// have a sequencing concept for our idea of the remote state.
let (cursor_line, lines) = tab
let (cursor_line, lines) = pane
.renderer()
.get_lines(cursor_position.y..cursor_position.y + 1);
bonus_lines.push((cursor_line, lines[0].clone()));
@ -101,8 +101,8 @@ impl PerTab {
let dirty_lines = dirty_delta.iter().cloned().collect();
let bonus_lines = bonus_lines.into();
Some(GetTabRenderChangesResponse {
tab_id: tab.tab_id(),
Some(GetPaneRenderChangesResponse {
pane_id: pane.pane_id(),
mouse_grabbed,
dirty_lines,
dimensions: dims,
@ -119,15 +119,15 @@ impl PerTab {
}
}
fn maybe_push_tab_changes(
tab: &Rc<dyn Tab>,
fn maybe_push_pane_changes(
pane: &Rc<dyn Pane>,
sender: PollableSender<DecodedPdu>,
per_tab: Arc<Mutex<PerTab>>,
per_pane: Arc<Mutex<PerPane>>,
) -> anyhow::Result<()> {
let mut per_tab = per_tab.lock().unwrap();
if let Some(resp) = per_tab.compute_changes(tab, None) {
let mut per_pane = per_pane.lock().unwrap();
if let Some(resp) = per_pane.compute_changes(pane, None) {
sender.send(DecodedPdu {
pdu: Pdu::GetTabRenderChangesResponse(resp),
pdu: Pdu::GetPaneRenderChangesResponse(resp),
serial: 0,
})?;
}
@ -136,33 +136,33 @@ fn maybe_push_tab_changes(
pub struct SessionHandler {
to_write_tx: PollableSender<DecodedPdu>,
per_tab: HashMap<TabId, Arc<Mutex<PerTab>>>,
per_pane: HashMap<TabId, Arc<Mutex<PerPane>>>,
}
impl SessionHandler {
pub fn new(to_write_tx: PollableSender<DecodedPdu>) -> Self {
Self {
to_write_tx,
per_tab: HashMap::new(),
per_pane: HashMap::new(),
}
}
fn per_tab(&mut self, tab_id: TabId) -> Arc<Mutex<PerTab>> {
fn per_pane(&mut self, pane_id: PaneId) -> Arc<Mutex<PerPane>> {
Arc::clone(
self.per_tab
.entry(tab_id)
.or_insert_with(|| Arc::new(Mutex::new(PerTab::default()))),
self.per_pane
.entry(pane_id)
.or_insert_with(|| Arc::new(Mutex::new(PerPane::default()))),
)
}
pub fn schedule_tab_push(&mut self, tab_id: TabId) {
pub fn schedule_pane_push(&mut self, pane_id: PaneId) {
let sender = self.to_write_tx.clone();
let per_tab = self.per_tab(tab_id);
let per_pane = self.per_pane(pane_id);
spawn_into_main_thread(async move {
let mux = Mux::get().unwrap();
let tab = mux
.get_tab(tab_id)
.ok_or_else(|| anyhow!("no such tab {}", tab_id))?;
maybe_push_tab_changes(&tab, sender, per_tab)?;
let pane = mux
.get_pane(pane_id)
.ok_or_else(|| anyhow!("no such pane {}", pane_id))?;
maybe_push_pane_changes(&pane, sender, per_pane)?;
Ok::<(), anyhow::Error>(())
});
}
@ -202,20 +202,23 @@ impl SessionHandler {
for window_id in mux.iter_windows().into_iter() {
let window = mux.get_window(window_id).unwrap();
for tab in window.iter() {
let dims = tab.renderer().get_dimensions();
let working_dir = tab.get_current_working_dir();
tabs.push(WindowAndTabEntry {
window_id,
tab_id: tab.tab_id(),
title: tab.get_title(),
size: PtySize {
cols: dims.cols as u16,
rows: dims.viewport_rows as u16,
pixel_height: 0,
pixel_width: 0,
},
working_dir: working_dir.map(Into::into),
});
if let Some(pane) = tab.get_active_pane() {
let dims = pane.renderer().get_dimensions();
let working_dir = pane.get_current_working_dir();
tabs.push(WindowAndTabEntry {
window_id,
tab_id: tab.tab_id(),
pane_id: pane.pane_id(),
title: pane.get_title(),
size: PtySize {
cols: dims.cols as u16,
rows: dims.viewport_rows as u16,
pixel_height: 0,
pixel_width: 0,
},
working_dir: working_dir.map(Into::into),
});
}
}
}
log::error!("ListTabs {:#?}", tabs);
@ -226,36 +229,36 @@ impl SessionHandler {
});
}
Pdu::WriteToTab(WriteToTab { tab_id, data }) => {
Pdu::WriteToPane(WriteToPane { pane_id, data }) => {
let sender = self.to_write_tx.clone();
let per_tab = self.per_tab(tab_id);
let per_pane = self.per_pane(pane_id);
spawn_into_main_thread(async move {
catch(
move || {
let mux = Mux::get().unwrap();
let tab = mux
.get_tab(tab_id)
.ok_or_else(|| anyhow!("no such tab {}", tab_id))?;
tab.writer().write_all(&data)?;
maybe_push_tab_changes(&tab, sender, per_tab)?;
let pane = mux
.get_pane(pane_id)
.ok_or_else(|| anyhow!("no such pane {}", pane_id))?;
pane.writer().write_all(&data)?;
maybe_push_pane_changes(&pane, sender, per_pane)?;
Ok(Pdu::UnitResponse(UnitResponse {}))
},
send_response,
);
});
}
Pdu::SendPaste(SendPaste { tab_id, data }) => {
Pdu::SendPaste(SendPaste { pane_id, data }) => {
let sender = self.to_write_tx.clone();
let per_tab = self.per_tab(tab_id);
let per_pane = self.per_pane(pane_id);
spawn_into_main_thread(async move {
catch(
move || {
let mux = Mux::get().unwrap();
let tab = mux
.get_tab(tab_id)
.ok_or_else(|| anyhow!("no such tab {}", tab_id))?;
tab.send_paste(&data)?;
maybe_push_tab_changes(&tab, sender, per_tab)?;
let pane = mux
.get_pane(pane_id)
.ok_or_else(|| anyhow!("no such pane {}", pane_id))?;
pane.send_paste(&data)?;
maybe_push_pane_changes(&pane, sender, per_pane)?;
Ok(Pdu::UnitResponse(UnitResponse {}))
},
send_response,
@ -263,37 +266,37 @@ impl SessionHandler {
});
}
Pdu::SearchTabScrollbackRequest(SearchTabScrollbackRequest { tab_id, pattern }) => {
Pdu::SearchScrollbackRequest(SearchScrollbackRequest { pane_id, pattern }) => {
use crate::mux::tab::Pattern;
async fn do_search(tab_id: TabId, pattern: Pattern) -> anyhow::Result<Pdu> {
async fn do_search(pane_id: TabId, pattern: Pattern) -> anyhow::Result<Pdu> {
let mux = Mux::get().unwrap();
let tab = mux
.get_tab(tab_id)
.ok_or_else(|| anyhow!("no such tab {}", tab_id))?;
let pane = mux
.get_pane(pane_id)
.ok_or_else(|| anyhow!("no such pane {}", pane_id))?;
tab.search(pattern).await.map(|results| {
Pdu::SearchTabScrollbackResponse(SearchTabScrollbackResponse { results })
pane.search(pattern).await.map(|results| {
Pdu::SearchScrollbackResponse(SearchScrollbackResponse { results })
})
}
spawn_into_main_thread(async move {
promise::spawn::spawn(async move {
let result = do_search(tab_id, pattern).await;
let result = do_search(pane_id, pattern).await;
send_response(result);
});
});
}
Pdu::Resize(Resize { tab_id, size }) => {
Pdu::Resize(Resize { pane_id, size }) => {
spawn_into_main_thread(async move {
catch(
move || {
let mux = Mux::get().unwrap();
let tab = mux
.get_tab(tab_id)
.ok_or_else(|| anyhow!("no such tab {}", tab_id))?;
tab.resize(size)?;
let pane = mux
.get_pane(pane_id)
.ok_or_else(|| anyhow!("no such pane {}", pane_id))?;
pane.resize(size)?;
Ok(Pdu::UnitResponse(UnitResponse {}))
},
send_response,
@ -302,28 +305,29 @@ impl SessionHandler {
}
Pdu::SendKeyDown(SendKeyDown {
tab_id,
pane_id,
event,
input_serial,
}) => {
let sender = self.to_write_tx.clone();
let per_tab = self.per_tab(tab_id);
let per_pane = self.per_pane(pane_id);
spawn_into_main_thread(async move {
catch(
move || {
let mux = Mux::get().unwrap();
let tab = mux
.get_tab(tab_id)
.ok_or_else(|| anyhow!("no such tab {}", tab_id))?;
tab.key_down(event.key, event.modifiers)?;
let pane = mux
.get_pane(pane_id)
.ok_or_else(|| anyhow!("no such pane {}", pane_id))?;
pane.key_down(event.key, event.modifiers)?;
// For a key press, we want to always send back the
// cursor position so that the predictive echo doesn't
// leave the cursor in the wrong place
let mut per_tab = per_tab.lock().unwrap();
if let Some(resp) = per_tab.compute_changes(&tab, Some(input_serial)) {
let mut per_pane = per_pane.lock().unwrap();
if let Some(resp) = per_pane.compute_changes(&pane, Some(input_serial))
{
sender.send(DecodedPdu {
pdu: Pdu::GetTabRenderChangesResponse(resp),
pdu: Pdu::GetPaneRenderChangesResponse(resp),
serial: 0,
})?;
}
@ -333,18 +337,18 @@ impl SessionHandler {
)
});
}
Pdu::SendMouseEvent(SendMouseEvent { tab_id, event }) => {
Pdu::SendMouseEvent(SendMouseEvent { pane_id, event }) => {
let sender = self.to_write_tx.clone();
let per_tab = self.per_tab(tab_id);
let per_pane = self.per_pane(pane_id);
spawn_into_main_thread(async move {
catch(
move || {
let mux = Mux::get().unwrap();
let tab = mux
.get_tab(tab_id)
.ok_or_else(|| anyhow!("no such tab {}", tab_id))?;
tab.mouse_event(event)?;
maybe_push_tab_changes(&tab, sender, per_tab)?;
let pane = mux
.get_pane(pane_id)
.ok_or_else(|| anyhow!("no such pane {}", pane_id))?;
pane.mouse_event(event)?;
maybe_push_pane_changes(&pane, sender, per_pane)?;
Ok(Pdu::UnitResponse(UnitResponse {}))
},
send_response,
@ -359,23 +363,23 @@ impl SessionHandler {
});
}
Pdu::GetTabRenderChanges(GetTabRenderChanges { tab_id, .. }) => {
Pdu::GetPaneRenderChanges(GetPaneRenderChanges { pane_id, .. }) => {
let sender = self.to_write_tx.clone();
let per_tab = self.per_tab(tab_id);
let per_pane = self.per_pane(pane_id);
spawn_into_main_thread(async move {
catch(
move || {
let mux = Mux::get().unwrap();
let tab_alive = match mux.get_tab(tab_id) {
Some(tab) => {
maybe_push_tab_changes(&tab, sender, per_tab)?;
let is_alive = match mux.get_pane(pane_id) {
Some(pane) => {
maybe_push_pane_changes(&pane, sender, per_pane)?;
true
}
None => false,
};
Ok(Pdu::TabLivenessResponse(TabLivenessResponse {
tab_id,
tab_alive,
Ok(Pdu::LivenessResponse(LivenessResponse {
pane_id,
is_alive,
}))
},
send_response,
@ -383,30 +387,30 @@ impl SessionHandler {
});
}
Pdu::GetLines(GetLines { tab_id, lines }) => {
let per_tab = self.per_tab(tab_id);
Pdu::GetLines(GetLines { pane_id, lines }) => {
let per_pane = self.per_pane(pane_id);
spawn_into_main_thread(async move {
catch(
move || {
let mux = Mux::get().unwrap();
let tab = mux
.get_tab(tab_id)
.ok_or_else(|| anyhow!("no such tab {}", tab_id))?;
let mut renderer = tab.renderer();
let pane = mux
.get_pane(pane_id)
.ok_or_else(|| anyhow!("no such pane {}", pane_id))?;
let mut renderer = pane.renderer();
let mut lines_and_indices = vec![];
let mut per_tab = per_tab.lock().unwrap();
let mut per_pane = per_pane.lock().unwrap();
for range in lines {
let (first_row, lines) = renderer.get_lines(range);
for (idx, line) in lines.into_iter().enumerate() {
let stable_row = first_row + idx as StableRowIndex;
per_tab.mark_clean(stable_row);
per_pane.mark_clean(stable_row);
lines_and_indices.push((stable_row, line));
}
}
Ok(Pdu::GetLinesResponse(GetLinesResponse {
tab_id,
pane_id,
lines: lines_and_indices.into(),
}))
},
@ -441,10 +445,10 @@ impl SessionHandler {
| Pdu::ListTabsResponse { .. }
| Pdu::SetClipboard { .. }
| Pdu::SpawnResponse { .. }
| Pdu::GetTabRenderChangesResponse { .. }
| Pdu::GetPaneRenderChangesResponse { .. }
| Pdu::UnitResponse { .. }
| Pdu::TabLivenessResponse { .. }
| Pdu::SearchTabScrollbackResponse { .. }
| Pdu::LivenessResponse { .. }
| Pdu::SearchScrollbackResponse { .. }
| Pdu::GetLinesResponse { .. }
| Pdu::GetCodecVersionResponse { .. }
| Pdu::GetTlsCredsResponse { .. }
@ -468,7 +472,7 @@ where
struct RemoteClipboard {
sender: PollableSender<DecodedPdu>,
tab_id: TabId,
pane_id: TabId,
}
impl Clipboard for RemoteClipboard {
@ -480,7 +484,7 @@ impl Clipboard for RemoteClipboard {
self.sender.send(DecodedPdu {
serial: 0,
pdu: Pdu::SetClipboard(SetClipboard {
tab_id: self.tab_id,
pane_id: self.pane_id,
clipboard,
}),
})?;
@ -506,13 +510,18 @@ async fn domain_spawn(spawn: Spawn, sender: PollableSender<DecodedPdu>) -> anyho
.spawn(spawn.size, spawn.command, spawn.command_dir, window_id)
.await?;
let pane = tab
.get_active_pane()
.ok_or_else(|| anyhow!("missing active pane on tab!?"))?;
let clip: Arc<dyn Clipboard> = Arc::new(RemoteClipboard {
tab_id: tab.tab_id(),
pane_id: pane.pane_id(),
sender,
});
tab.set_clipboard(&clip);
pane.set_clipboard(&clip);
Ok::<Pdu, anyhow::Error>(Pdu::SpawnResponse(SpawnResponse {
pane_id: pane.pane_id(),
tab_id: tab.tab_id(),
window_id,
}))

View File

@ -1,7 +1,7 @@
use crate::config::configuration;
use crate::mux::domain::DomainId;
use crate::mux::renderable::{Renderable, RenderableDimensions};
use crate::mux::tab::{alloc_tab_id, Pattern, SearchResult, Tab, TabId};
use crate::mux::tab::{alloc_pane_id, Pane, Pattern, SearchResult, TabId};
use crate::ratelim::RateLimiter;
use crate::server::codec::*;
use crate::server::domain::ClientInner;
@ -21,33 +21,33 @@ use url::Url;
use wezterm_term::color::ColorPalette;
use wezterm_term::{Clipboard, KeyCode, KeyModifiers, MouseEvent};
pub struct ClientTab {
pub struct ClientPane {
client: Arc<ClientInner>,
local_tab_id: TabId,
remote_tab_id: TabId,
local_pane_id: TabId,
remote_pane_id: TabId,
pub renderable: RefCell<RenderableState>,
writer: RefCell<TabWriter>,
writer: RefCell<PaneWriter>,
reader: Pipe,
mouse: Rc<RefCell<MouseState>>,
clipboard: RefCell<Option<Arc<dyn Clipboard>>>,
mouse_grabbed: RefCell<bool>,
}
impl ClientTab {
impl ClientPane {
pub fn new(
client: &Arc<ClientInner>,
remote_tab_id: TabId,
remote_pane_id: TabId,
size: PtySize,
title: &str,
) -> Self {
let local_tab_id = alloc_tab_id();
let writer = TabWriter {
let local_pane_id = alloc_pane_id();
let writer = PaneWriter {
client: Arc::clone(client),
remote_tab_id,
remote_pane_id,
};
let mouse = Rc::new(RefCell::new(MouseState::new(
remote_tab_id,
remote_pane_id,
client.client.clone(),
)));
@ -57,8 +57,8 @@ impl ClientTab {
let render = RenderableState {
inner: RefCell::new(RenderableInner::new(
client,
remote_tab_id,
local_tab_id,
remote_pane_id,
local_pane_id,
RenderableDimensions {
cols: size.cols as _,
viewport_rows: size.rows as _,
@ -76,8 +76,8 @@ impl ClientTab {
Self {
client: Arc::clone(client),
mouse,
remote_tab_id,
local_tab_id,
remote_pane_id,
local_pane_id,
renderable: RefCell::new(render),
writer: RefCell::new(writer),
reader,
@ -88,7 +88,7 @@ impl ClientTab {
pub fn process_unilateral(&self, pdu: Pdu) -> anyhow::Result<()> {
match pdu {
Pdu::GetTabRenderChangesResponse(delta) => {
Pdu::GetPaneRenderChangesResponse(delta) => {
*self.mouse_grabbed.borrow_mut() = delta.mouse_grabbed;
self.renderable
.borrow()
@ -102,7 +102,7 @@ impl ClientTab {
clip.set_contents(clipboard)?;
}
None => {
log::error!("ClientTab: Ignoring SetClipboard request {:?}", clipboard);
log::error!("ClientPane: Ignoring SetClipboard request {:?}", clipboard);
}
}
}
@ -111,15 +111,15 @@ impl ClientTab {
Ok(())
}
pub fn remote_tab_id(&self) -> TabId {
self.remote_tab_id
pub fn remote_pane_id(&self) -> TabId {
self.remote_pane_id
}
}
#[async_trait(?Send)]
impl Tab for ClientTab {
fn tab_id(&self) -> TabId {
self.local_tab_id
impl Pane for ClientPane {
fn pane_id(&self) -> TabId {
self.local_pane_id
}
fn renderer(&self) -> RefMut<dyn Renderable> {
self.renderable.borrow_mut()
@ -137,7 +137,7 @@ impl Tab for ClientTab {
fn send_paste(&self, text: &str) -> anyhow::Result<()> {
let client = Arc::clone(&self.client);
let remote_tab_id = self.remote_tab_id;
let remote_pane_id = self.remote_pane_id;
self.renderable
.borrow()
.inner
@ -149,7 +149,7 @@ impl Tab for ClientTab {
client
.client
.send_paste(SendPaste {
tab_id: remote_tab_id,
pane_id: remote_pane_id,
data,
})
.await
@ -163,7 +163,7 @@ impl Tab for ClientTab {
}
fn reader(&self) -> anyhow::Result<Box<dyn std::io::Read + Send>> {
info!("made reader for ClientTab");
info!("made reader for ClientPane");
Ok(Box::new(self.reader.read.try_clone()?))
}
@ -186,12 +186,12 @@ impl Tab for ClientTab {
inner.make_all_stale();
let client = Arc::clone(&self.client);
let remote_tab_id = self.remote_tab_id;
let remote_pane_id = self.remote_pane_id;
promise::spawn::spawn(async move {
client
.client
.resize(Resize {
tab_id: remote_tab_id,
pane_id: remote_pane_id,
size,
})
.await
@ -205,13 +205,13 @@ impl Tab for ClientTab {
match self
.client
.client
.search_scrollback(SearchTabScrollbackRequest {
tab_id: self.remote_tab_id,
.search_scrollback(SearchScrollbackRequest {
pane_id: self.remote_pane_id,
pattern,
})
.await
{
Ok(SearchTabScrollbackResponse { results }) => Ok(results),
Ok(SearchScrollbackResponse { results }) => Ok(results),
Err(e) => Err(e),
}
}
@ -226,12 +226,12 @@ impl Tab for ClientTab {
inner.predict_from_key_event(key, mods);
}
let client = Arc::clone(&self.client);
let remote_tab_id = self.remote_tab_id;
let remote_pane_id = self.remote_pane_id;
promise::spawn::spawn(async move {
client
.client
.key_down(SendKeyDown {
tab_id: remote_tab_id,
pane_id: remote_pane_id,
event: KeyEvent {
key,
modifiers: mods,
@ -261,7 +261,7 @@ impl Tab for ClientTab {
}
fn advance_bytes(&self, _buf: &[u8]) {
panic!("ClientTab::advance_bytes not impl");
panic!("ClientPane::advance_bytes not impl");
}
fn is_dead(&self) -> bool {
@ -309,15 +309,15 @@ impl Tab for ClientTab {
}
}
struct TabWriter {
struct PaneWriter {
client: Arc<ClientInner>,
remote_tab_id: TabId,
remote_pane_id: TabId,
}
impl std::io::Write for TabWriter {
impl std::io::Write for PaneWriter {
fn write(&mut self, data: &[u8]) -> Result<usize, std::io::Error> {
promise::spawn::block_on(self.client.client.write_to_tab(WriteToTab {
tab_id: self.remote_tab_id,
promise::spawn::block_on(self.client.client.write_to_pane(WriteToPane {
pane_id: self.remote_pane_id,
data: data.to_vec(),
}))
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e)))?;

View File

@ -1,4 +1,4 @@
pub use clienttab::ClientTab;
pub use clienttab::ClientPane;
mod clienttab;
mod mousestate;

View File

@ -11,13 +11,13 @@ pub struct MouseState {
pending: AtomicBool,
queue: VecDeque<MouseEvent>,
client: Client,
remote_tab_id: TabId,
remote_pane_id: TabId,
}
impl MouseState {
pub fn new(remote_tab_id: TabId, client: Client) -> Self {
pub fn new(remote_pane_id: TabId, client: Client) -> Self {
Self {
remote_tab_id,
remote_pane_id,
client,
pending: AtomicBool::new(false),
queue: VecDeque::new(),
@ -71,12 +71,12 @@ impl MouseState {
let state = Rc::clone(&state);
mouse.pending.store(true, Ordering::SeqCst);
let remote_tab_id = mouse.remote_tab_id;
let remote_pane_id = mouse.remote_pane_id;
promise::spawn::spawn(async move {
client
.mouse_event(SendMouseEvent {
tab_id: remote_tab_id,
pane_id: remote_pane_id,
event,
})
.await

View File

@ -5,7 +5,7 @@ use crate::mux::Mux;
use crate::ratelim::RateLimiter;
use crate::server::codec::*;
use crate::server::domain::ClientInner;
use crate::server::tab::clienttab::ClientTab;
use crate::server::tab::clienttab::ClientPane;
use anyhow::anyhow;
use lru::LruCache;
use promise::BrokenPromise;
@ -55,8 +55,8 @@ impl LineEntry {
pub struct RenderableInner {
client: Arc<ClientInner>,
remote_tab_id: TabId,
local_tab_id: TabId,
remote_pane_id: TabId,
local_pane_id: TabId,
last_poll: Instant,
pub dead: bool,
poll_in_progress: AtomicBool,
@ -86,8 +86,8 @@ pub struct RenderableState {
impl RenderableInner {
pub fn new(
client: &Arc<ClientInner>,
remote_tab_id: TabId,
local_tab_id: TabId,
remote_pane_id: TabId,
local_pane_id: TabId,
dimensions: RenderableDimensions,
title: &str,
fetch_limiter: RateLimiter,
@ -96,8 +96,8 @@ impl RenderableInner {
Self {
client: Arc::clone(client),
remote_tab_id,
local_tab_id,
remote_pane_id,
local_pane_id,
last_poll: now,
dead: false,
poll_in_progress: AtomicBool::new(false),
@ -299,7 +299,7 @@ impl RenderableInner {
self.poll_interval = BASE_POLL_INTERVAL;
}
pub fn apply_changes_to_surface(&mut self, delta: GetTabRenderChangesResponse) {
pub fn apply_changes_to_surface(&mut self, delta: GetPaneRenderChangesResponse) {
let now = Instant::now();
self.poll_interval = BASE_POLL_INTERVAL;
self.last_recv_time = now;
@ -350,7 +350,7 @@ impl RenderableInner {
if !dirty.is_empty() {
Mux::get()
.unwrap()
.notify(crate::mux::MuxNotification::TabOutput(self.local_tab_id));
.notify(crate::mux::MuxNotification::PaneOutput(self.local_pane_id));
}
let mut to_fetch = RangeSet::new();
@ -478,40 +478,40 @@ impl RenderableInner {
return;
}
let local_tab_id = self.local_tab_id;
let local_pane_id = self.local_pane_id;
log::trace!(
"will fetch lines {:?} for remote tab id {} at {:?}",
to_fetch,
self.remote_tab_id,
self.remote_pane_id,
now,
);
let client = Arc::clone(&self.client);
let remote_tab_id = self.remote_tab_id;
let remote_pane_id = self.remote_pane_id;
promise::spawn::spawn(async move {
let result = client
.client
.get_lines(GetLines {
tab_id: remote_tab_id,
pane_id: remote_pane_id,
lines: to_fetch.clone().into(),
})
.await;
Self::apply_lines(local_tab_id, result, to_fetch, now)
Self::apply_lines(local_pane_id, result, to_fetch, now)
});
}
fn apply_lines(
local_tab_id: TabId,
local_pane_id: TabId,
result: anyhow::Result<GetLinesResponse>,
to_fetch: RangeSet<StableRowIndex>,
now: Instant,
) -> anyhow::Result<()> {
let mux = Mux::get().unwrap();
let tab = mux
.get_tab(local_tab_id)
.ok_or_else(|| anyhow!("no such tab {}", local_tab_id))?;
if let Some(client_tab) = tab.downcast_ref::<ClientTab>() {
let pane = mux
.get_pane(local_pane_id)
.ok_or_else(|| anyhow!("no such tab {}", local_pane_id))?;
if let Some(client_tab) = pane.downcast_ref::<ClientPane>() {
let renderable = client_tab.renderable.borrow_mut();
let mut inner = renderable.inner.borrow_mut();
@ -566,18 +566,18 @@ impl RenderableInner {
self.last_poll = Instant::now();
self.poll_in_progress.store(true, Ordering::SeqCst);
let remote_tab_id = self.remote_tab_id;
let local_tab_id = self.local_tab_id;
let remote_pane_id = self.remote_pane_id;
let local_pane_id = self.local_pane_id;
let client = Arc::clone(&self.client);
promise::spawn::spawn(async move {
let alive = match client
.client
.get_tab_render_changes(GetTabRenderChanges {
tab_id: remote_tab_id,
.get_tab_render_changes(GetPaneRenderChanges {
pane_id: remote_pane_id,
})
.await
{
Ok(resp) => resp.tab_alive,
Ok(resp) => resp.is_alive,
// if we got a timeout on a reconnectable, don't
// consider the tab to be dead; that helps to
// avoid having a tab get shuffled around
@ -586,9 +586,9 @@ impl RenderableInner {
let mux = Mux::get().unwrap();
let tab = mux
.get_tab(local_tab_id)
.ok_or_else(|| anyhow!("no such tab {}", local_tab_id))?;
if let Some(client_tab) = tab.downcast_ref::<ClientTab>() {
.get_pane(local_pane_id)
.ok_or_else(|| anyhow!("no such tab {}", local_pane_id))?;
if let Some(client_tab) = tab.downcast_ref::<ClientPane>() {
let renderable = client_tab.renderable.borrow_mut();
let mut inner = renderable.inner.borrow_mut();

View File

@ -1,7 +1,7 @@
use crate::connui::ConnectionUI;
use crate::localtab::LocalTab;
use crate::localtab::LocalPane;
use crate::mux::domain::{alloc_domain_id, Domain, DomainId, DomainState};
use crate::mux::tab::Tab;
use crate::mux::tab::{Pane, Tab};
use crate::mux::window::WindowId;
use crate::mux::Mux;
use anyhow::{anyhow, bail, Context, Error};
@ -241,7 +241,7 @@ impl Domain for RemoteSshDomain {
command: Option<CommandBuilder>,
_command_dir: Option<String>,
window: WindowId,
) -> Result<Rc<dyn Tab>, Error> {
) -> Result<Rc<Tab>, Error> {
let cmd = match command {
Some(c) => c,
None => CommandBuilder::new_default_prog(),
@ -264,7 +264,9 @@ impl Domain for RemoteSshDomain {
);
let mux = Mux::get().unwrap();
let tab: Rc<dyn Tab> = Rc::new(LocalTab::new(terminal, child, pair.master, self.id));
let pane: Rc<dyn Pane> = Rc::new(LocalPane::new(terminal, child, pair.master, self.id));
let tab = Rc::new(Tab::new());
tab.assign_pane(&pane);
mux.add_tab(&tab)?;
mux.add_tab_to_window(&tab, window)?;

View File

@ -7,7 +7,7 @@ use crate::font::FontConfiguration;
use crate::frontend::front_end;
use crate::mux::domain::{alloc_domain_id, Domain, DomainId, DomainState};
use crate::mux::renderable::Renderable;
use crate::mux::tab::{alloc_tab_id, Tab, TabId};
use crate::mux::tab::{alloc_pane_id, Pane, PaneId, Tab};
use crate::mux::window::WindowId;
use crate::mux::Mux;
use anyhow::{bail, Error};
@ -51,8 +51,8 @@ impl Domain for TermWizTerminalDomain {
_command: Option<CommandBuilder>,
_command_dir: Option<String>,
_window: WindowId,
) -> anyhow::Result<Rc<dyn Tab>> {
bail!("cannot spawn tabs in a TermWizTerminalTab");
) -> anyhow::Result<Rc<Tab>> {
bail!("cannot spawn tabs in a TermWizTerminalPane");
}
fn spawnable(&self) -> bool {
@ -79,8 +79,8 @@ impl Domain for TermWizTerminalDomain {
}
}
pub struct TermWizTerminalTab {
tab_id: TabId,
pub struct TermWizTerminalPane {
pane_id: PaneId,
domain_id: DomainId,
terminal: RefCell<wezterm_term::Terminal>,
input_tx: Sender<InputEvent>,
@ -89,7 +89,7 @@ pub struct TermWizTerminalTab {
render_rx: FileDescriptor,
}
impl TermWizTerminalTab {
impl TermWizTerminalPane {
fn new(
domain_id: DomainId,
width: usize,
@ -97,7 +97,7 @@ impl TermWizTerminalTab {
input_tx: Sender<InputEvent>,
render_rx: FileDescriptor,
) -> Self {
let tab_id = alloc_tab_id();
let pane_id = alloc_pane_id();
let terminal = RefCell::new(wezterm_term::Terminal::new(
height,
@ -111,7 +111,7 @@ impl TermWizTerminalTab {
));
Self {
tab_id,
pane_id,
domain_id,
terminal,
writer: RefCell::new(Vec::new()),
@ -122,9 +122,9 @@ impl TermWizTerminalTab {
}
}
impl Tab for TermWizTerminalTab {
fn tab_id(&self) -> TabId {
self.tab_id
impl Pane for TermWizTerminalPane {
fn pane_id(&self) -> PaneId {
self.pane_id
}
fn renderer(&self) -> RefMut<dyn Renderable> {
@ -304,11 +304,11 @@ impl termwiz::terminal::Terminal for TermWizTerminal {
}
fn enter_alternate_screen(&mut self) -> anyhow::Result<()> {
bail!("TermWizTerminalTab has no alt screen");
bail!("TermWizTerminalPane has no alt screen");
}
fn exit_alternate_screen(&mut self) -> anyhow::Result<()> {
bail!("TermWizTerminalTab has no alt screen");
bail!("TermWizTerminalPane has no alt screen");
}
fn get_screen_size(&mut self) -> anyhow::Result<ScreenSize> {
@ -316,7 +316,7 @@ impl termwiz::terminal::Terminal for TermWizTerminal {
}
fn set_screen_size(&mut self, _size: ScreenSize) -> anyhow::Result<()> {
bail!("TermWizTerminalTab cannot set screen size");
bail!("TermWizTerminalPane cannot set screen size");
}
fn render(&mut self, changes: &[Change]) -> anyhow::Result<()> {
@ -346,7 +346,7 @@ impl termwiz::terminal::Terminal for TermWizTerminal {
}
}
pub fn allocate(width: usize, height: usize) -> (TermWizTerminal, Rc<dyn Tab>) {
pub fn allocate(width: usize, height: usize) -> (TermWizTerminal, Rc<dyn Pane>) {
let render_pipe = Pipe::new().expect("Pipe creation not to fail");
let (input_tx, input_rx) = channel();
@ -368,14 +368,15 @@ pub fn allocate(width: usize, height: usize) -> (TermWizTerminal, Rc<dyn Tab>) {
};
let domain_id = 0;
let tab = TermWizTerminalTab::new(domain_id, width, height, input_tx, render_pipe.read);
let pane = TermWizTerminalPane::new(domain_id, width, height, input_tx, render_pipe.read);
// Add the tab to the mux so that the output is processed
let tab: Rc<dyn Tab> = Rc::new(tab);
let mux = Mux::get().unwrap();
mux.add_tab(&tab).expect("to be able to add tab to mux");
let pane: Rc<dyn Pane> = Rc::new(pane);
(tw_term, tab)
let mux = Mux::get().unwrap();
mux.add_pane(&pane).expect("to be able to add pane to mux");
(tw_term, pane)
}
fn new_wezterm_terminfo_renderer() -> TerminfoRenderer {
@ -445,8 +446,11 @@ pub async fn run<
let window_id = mux.new_empty_window();
let tab = TermWizTerminalTab::new(domain.domain_id(), width, height, input_tx, render_rx);
let tab: Rc<dyn Tab> = Rc::new(tab);
let pane = TermWizTerminalPane::new(domain.domain_id(), width, height, input_tx, render_rx);
let pane: Rc<dyn Pane> = Rc::new(pane);
let tab = Rc::new(Tab::new());
tab.assign_pane(&pane);
mux.add_tab(&tab)?;
mux.add_tab_to_window(&tab, window_id)?;