diff --git a/docs/config/keys.markdown b/docs/config/keys.markdown index f64fe701b..ca55d6345 100644 --- a/docs/config/keys.markdown +++ b/docs/config/keys.markdown @@ -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"}}}, } diff --git a/src/frontend/gui/mod.rs b/src/frontend/gui/mod.rs index 29c7b052d..fd4cc849a 100644 --- a/src/frontend/gui/mod.rs +++ b/src/frontend/gui/mod.rs @@ -74,7 +74,7 @@ impl FrontEnd for GuiFrontEnd { fn spawn_new_window( &self, fontconfig: &Rc, - tab: &Rc, + tab: &Rc, window_id: MuxWindowId, ) -> anyhow::Result<()> { termwindow::TermWindow::new_window(&configuration(), fontconfig, tab, window_id) diff --git a/src/frontend/gui/overlay/copy.rs b/src/frontend/gui/overlay/copy.rs index 2a2725fc6..b2215abad 100644 --- a/src/frontend/gui/overlay/copy.rs +++ b/src/frontend/gui/overlay/copy.rs @@ -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, + delegate: Rc, render: RefCell, } struct CopyRenderable { cursor: StableCursorPosition, - delegate: Rc, + delegate: Rc, start: Option, viewport: Option, /// We use this to cancel ourselves later @@ -38,20 +38,20 @@ struct Dimensions { } impl CopyOverlay { - pub fn with_tab(term_window: &TermWindow, tab: &Rc) -> Rc { - let mut cursor = tab.renderer().get_cursor_position(); + pub fn with_pane(term_window: &TermWindow, pane: &Rc) -> Rc { + 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::() { - 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) { 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::() { - 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 { diff --git a/src/frontend/gui/overlay/mod.rs b/src/frontend/gui/overlay/mod.rs index 0cbf1cfaf..ef87a5307 100644 --- a/src/frontend/gui/overlay/mod.rs +++ b/src/frontend/gui/overlay/mod.rs @@ -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( term_window: &TermWindow, - tab: &Rc, + tab: &Rc, func: F, ) -> ( - Rc, + Rc, Pin>>>>, ) where @@ -27,7 +27,9 @@ where F: Send + 'static + FnOnce(TabId, TermWizTerminal) -> anyhow::Result, { 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(); diff --git a/src/frontend/gui/overlay/search.rs b/src/frontend/gui/overlay/search.rs index a82b48446..d39202079 100644 --- a/src/frontend/gui/overlay/search.rs +++ b/src/frontend/gui/overlay/search.rs @@ -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, - delegate: Rc, + delegate: Rc, } #[derive(Debug)] @@ -30,7 +30,7 @@ struct MatchResult { } struct SearchRenderable { - delegate: Rc, + delegate: Rc, /// 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, pattern: Pattern) -> Rc { - let viewport = term_window.get_viewport(tab.tab_id()); - let dims = tab.renderer().get_dimensions(); + pub fn with_pane( + term_window: &TermWindow, + pane: &Rc, + pattern: Pattern, + ) -> Rc { + 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 { @@ -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) { 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::() { - 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 = self.delegate.clone(); + let pane: Rc = 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::() .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::() { 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::() { - 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::() { - 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, diff --git a/src/frontend/gui/tabbar.rs b/src/frontend/gui/tabbar.rs index 6108694f7..e7eef315a 100644 --- a/src/frontend/gui/tabbar.rs +++ b/src/frontend/gui/tabbar.rs @@ -58,18 +58,22 @@ impl TabBarState { let per_tab_overhead = 2; let system_overhead = 3; - let tab_titles: Vec<_> = window + let tab_titles: Vec = 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(); diff --git a/src/frontend/gui/termwindow.rs b/src/frontend/gui/termwindow.rs index 241115b63..f0b96b40b 100644 --- a/src/frontend/gui/termwindow.rs +++ b/src/frontend/gui/termwindow.rs @@ -19,7 +19,7 @@ use crate::keyassignment::{ }; use crate::mux::domain::{DomainId, DomainState}; use crate::mux::renderable::{RenderableDimensions, StableCursorPosition}; -use crate::mux::tab::{Tab, TabId}; +use crate::mux::tab::{Pane, PaneId, Tab, TabId}; use crate::mux::window::WindowId as MuxWindowId; use crate::mux::Mux; use ::wezterm_term::input::MouseButton as TMB; @@ -40,8 +40,7 @@ use std::any::Any; use std::cell::{RefCell, RefMut}; use std::collections::HashMap; use std::convert::TryInto; -use std::ops::Range; -use std::ops::{Add, Sub}; +use std::ops::{Add, Range, Sub}; use std::rc::Rc; use std::sync::Arc; use std::sync::Mutex; @@ -142,7 +141,7 @@ impl PrevCursorPos { } #[derive(Default, Clone)] -pub struct TabState { +pub struct PaneState { /// If is_some(), the top row of the visible screen. /// Otherwise, the viewport is at the bottom of the /// scrollback. @@ -151,7 +150,15 @@ pub struct TabState { /// If is_some(), rather than display the actual tab /// contents, we're overlaying a little internal application /// tab. We'll also route input to it. - pub overlay: Option>, + pub overlay: Option>, +} + +#[derive(Default, Clone)] +pub struct TabState { + /// If is_some(), rather than display the actual tab + /// contents, we're overlaying a little internal application + /// tab. We'll also route input to it. + pub overlay: Option>, } #[derive(PartialEq, Eq, Hash)] @@ -250,6 +257,7 @@ pub struct TermWindow { last_scroll_info: RenderableDimensions, tab_state: RefCell>, + pane_state: RefCell>, /// Gross workaround for managing async keyboard fetching /// just for middle mouse button paste function @@ -321,14 +329,14 @@ impl WindowCallbacks for TermWindow { // force cursor to be repainted self.window.as_ref().unwrap().invalidate(); - if let Some(tab) = self.get_active_tab_or_overlay() { - tab.focus_changed(focused); + if let Some(pane) = self.get_active_pane_or_overlay() { + pane.focus_changed(focused); } } fn mouse_event(&mut self, event: &MouseEvent, context: &dyn WindowOps) { - let tab = match self.get_active_tab_or_overlay() { - Some(tab) => tab, + let pane = match self.get_active_pane_or_overlay() { + Some(pane) => pane, None => return, }; @@ -382,28 +390,28 @@ impl WindowCallbacks for TermWindow { self.current_mouse_button = Some(press.clone()); } - WMEK::VertWheel(amount) if !tab.is_mouse_grabbed() => { + WMEK::VertWheel(amount) if !pane.is_mouse_grabbed() => { // adjust viewport - let dims = tab.renderer().get_dimensions(); + let dims = pane.renderer().get_dimensions(); let position = self - .get_viewport(tab.tab_id()) + .get_viewport(pane.pane_id()) .unwrap_or(dims.physical_top) .saturating_sub(amount.into()); - self.set_viewport(tab.tab_id(), Some(position), dims); + self.set_viewport(pane.pane_id(), Some(position), dims); context.invalidate(); return; } WMEK::Move => { - let current_viewport = self.get_viewport(tab.tab_id()); + let current_viewport = self.get_viewport(pane.pane_id()); if let Some(from_top) = self.scroll_drag_start.as_ref() { // Dragging the scroll bar - let tab = match self.get_active_tab_or_overlay() { - Some(tab) => tab, + let pane = match self.get_active_pane_or_overlay() { + Some(pane) => pane, None => return, }; - let render = tab.renderer(); + let render = pane.renderer(); let dims = render.get_dimensions(); let effective_thumb_top = @@ -419,7 +427,7 @@ impl WindowCallbacks for TermWindow { &self.dimensions, ); drop(render); - self.set_viewport(tab.tab_id(), Some(row), dims); + self.set_viewport(pane.pane_id(), Some(row), dims); context.invalidate(); return; } @@ -430,9 +438,9 @@ impl WindowCallbacks for TermWindow { if in_tab_bar { self.mouse_event_tab_bar(x, event, context); } else if in_scroll_bar { - self.mouse_event_scroll_bar(tab, event, context); + self.mouse_event_scroll_bar(pane, event, context); } else { - self.mouse_event_terminal(tab, x, term_y, event, context); + self.mouse_event_terminal(pane, x, term_y, event, context); } } @@ -457,8 +465,8 @@ impl WindowCallbacks for TermWindow { // log::error!("key_event {:?}", key); - let tab = match self.get_active_tab_or_overlay() { - Some(tab) => tab, + let pane = match self.get_active_pane_or_overlay() { + Some(pane) => pane, None => return false, }; let modifiers = window_mods_to_termwiz_mods(window_key.modifiers); @@ -469,7 +477,7 @@ impl WindowCallbacks for TermWindow { if let Some(key) = &window_key.raw_key { if let Key::Code(key) = self.win_key_code_to_termwiz_key_code(&key) { if let Some(assignment) = self.input_map.lookup_key(key, raw_modifiers) { - self.perform_key_assignment(&tab, &assignment).ok(); + self.perform_key_assignment(&pane, &assignment).ok(); context.invalidate(); return true; } @@ -495,9 +503,9 @@ impl WindowCallbacks for TermWindow { && window_key.raw_modifiers.contains(Modifiers::ALT) && !config.send_composed_key_when_alt_is_pressed); - if bypass_compose && tab.key_down(key, raw_modifiers).is_ok() { - if !key.is_modifier() && self.tab_state(tab.tab_id()).overlay.is_none() { - self.maybe_scroll_to_bottom_for_input(&tab); + if bypass_compose && pane.key_down(key, raw_modifiers).is_ok() { + if !key.is_modifier() && self.pane_state(pane.pane_id()).overlay.is_none() { + self.maybe_scroll_to_bottom_for_input(&pane); } context.invalidate(); return true; @@ -509,12 +517,12 @@ impl WindowCallbacks for TermWindow { match key { Key::Code(key) => { if let Some(assignment) = self.input_map.lookup_key(key, modifiers) { - self.perform_key_assignment(&tab, &assignment).ok(); + self.perform_key_assignment(&pane, &assignment).ok(); context.invalidate(); true - } else if tab.key_down(key, modifiers).is_ok() { - if !key.is_modifier() && self.tab_state(tab.tab_id()).overlay.is_none() { - self.maybe_scroll_to_bottom_for_input(&tab); + } else if pane.key_down(key, modifiers).is_ok() { + if !key.is_modifier() && self.pane_state(pane.pane_id()).overlay.is_none() { + self.maybe_scroll_to_bottom_for_input(&pane); } context.invalidate(); true @@ -523,8 +531,8 @@ impl WindowCallbacks for TermWindow { } } Key::Composed(s) => { - tab.writer().write_all(s.as_bytes()).ok(); - self.maybe_scroll_to_bottom_for_input(&tab); + pane.writer().write_all(s.as_bytes()).ok(); + self.maybe_scroll_to_bottom_for_input(&pane); context.invalidate(); true } @@ -533,8 +541,8 @@ impl WindowCallbacks for TermWindow { } fn paint(&mut self, ctx: &mut dyn PaintContext) { - let tab = match self.get_active_tab_or_overlay() { - Some(tab) => tab, + let pane = match self.get_active_pane_or_overlay() { + Some(pane) => pane, None => { ctx.clear(Color::rgb(0, 0, 0)); return; @@ -542,11 +550,11 @@ impl WindowCallbacks for TermWindow { }; self.check_for_config_reload(); - self.update_text_cursor(&tab); + self.update_text_cursor(&pane); self.update_title(); let start = std::time::Instant::now(); - if let Err(err) = self.paint_tab(&tab, ctx) { + if let Err(err) = self.paint_tab(&pane, ctx) { if let Some(&OutOfTextureSpace { size }) = err.downcast_ref::() { log::error!("out of texture space, allocating {}", size); if let Err(err) = self.recreate_texture_atlas(Some(size)) { @@ -606,6 +614,7 @@ impl WindowCallbacks for TermWindow { last_scroll_info: self.last_scroll_info.clone(), clipboard_contents: Arc::clone(&clipboard_contents), tab_state: RefCell::new(self.tab_state.borrow().clone()), + pane_state: RefCell::new(self.pane_state.borrow().clone()), current_mouse_button: self.current_mouse_button.clone(), last_mouse_click: self.last_mouse_click.clone(), current_highlight: self.current_highlight.clone(), @@ -673,17 +682,17 @@ impl WindowCallbacks for TermWindow { } fn paint_opengl(&mut self, frame: &mut glium::Frame) { - let tab = match self.get_active_tab_or_overlay() { - Some(tab) => tab, + let pane = match self.get_active_pane_or_overlay() { + Some(pane) => pane, None => { frame.clear_color_srgb(0., 0., 0., 1.); return; } }; self.check_for_config_reload(); - self.update_text_cursor(&tab); + self.update_text_cursor(&pane); let start = std::time::Instant::now(); - if let Err(err) = self.paint_tab_opengl(&tab, frame) { + if let Err(err) = self.paint_tab_opengl(&pane, frame) { if let Some(&OutOfTextureSpace { size }) = err.downcast_ref::() { log::error!("out of texture space, allocating {}", size); if let Err(err) = self.recreate_texture_atlas(Some(size)) { @@ -722,10 +731,13 @@ impl TermWindow { pub fn new_window( config: &ConfigHandle, fontconfig: &Rc, - tab: &Rc, + tab: &Rc, mux_window_id: MuxWindowId, ) -> anyhow::Result<()> { - let dims = tab.renderer().get_dimensions(); + let pane = tab + .get_active_pane() + .ok_or_else(|| anyhow!("tab has no panes"))?; + let dims = pane.renderer().get_dimensions(); let physical_rows = dims.viewport_rows; let physical_cols = dims.cols; @@ -796,6 +808,7 @@ impl TermWindow { last_scroll_info: RenderableDimensions::default(), clipboard_contents: Arc::clone(&clipboard_contents), tab_state: RefCell::new(HashMap::new()), + pane_state: RefCell::new(HashMap::new()), current_mouse_button: None, last_mouse_click: None, current_highlight: None, @@ -833,7 +846,9 @@ impl TermWindow { mux_window.set_clipboard(&clipboard); for tab in mux_window.iter() { - tab.set_clipboard(&clipboard); + for pane in tab.get_active_pane() { + pane.set_clipboard(&clipboard); + } } } @@ -867,7 +882,7 @@ impl TermWindow { fn periodic_window_maintenance(&mut self, _window: &dyn WindowOps) -> anyhow::Result<()> { let mux = Mux::get().unwrap(); - if let Some(tab) = self.get_active_tab_or_overlay() { + if let Some(pane) = self.get_active_pane_or_overlay() { let mut needs_invalidate = false; // If the config was reloaded, ask the window to apply @@ -876,7 +891,7 @@ impl TermWindow { let config = configuration(); - let render = tab.renderer(); + let render = pane.renderer(); // If blinking is permitted, and the cursor shape is set // to a blinking variant, and it's been longer than the @@ -902,13 +917,15 @@ impl TermWindow { // If the model is dirty, arrange to re-paint let dims = render.get_dimensions(); - let viewport = self.get_viewport(tab.tab_id()).unwrap_or(dims.physical_top); + let viewport = self + .get_viewport(pane.pane_id()) + .unwrap_or(dims.physical_top); let visible_range = viewport..viewport + dims.viewport_rows as StableRowIndex; let dirty = render.get_dirty_lines(visible_range); if !dirty.is_empty() { - if tab.downcast_ref::().is_none() - && tab.downcast_ref::().is_none() + if pane.downcast_ref::().is_none() + && pane.downcast_ref::().is_none() { // If any of the changed lines intersect with the // selection, then we need to clear the selection, but not @@ -918,7 +935,7 @@ impl TermWindow { // and we want to allow it to retain the selection it made! let clear_selection = if let Some(selection_range) = - self.selection(tab.tab_id()).range.as_ref() + self.selection(pane.pane_id()).range.as_ref() { let selection_rows = selection_range.rows(); selection_rows.into_iter().any(|row| dirty.contains(row)) @@ -927,8 +944,8 @@ impl TermWindow { }; if clear_selection { - self.selection(tab.tab_id()).range.take(); - self.selection(tab.tab_id()).start.take(); + self.selection(pane.pane_id()).range.take(); + self.selection(pane.pane_id()).start.take(); } } @@ -1105,7 +1122,7 @@ impl TermWindow { return; } - let tab = match self.get_active_tab_or_overlay() { + let tab = match self.get_active_pane_or_overlay() { Some(tab) => tab, None => return, }; @@ -1155,19 +1172,14 @@ impl TermWindow { } let tab_no = window.get_active_idx(); - - let title = match window.get_active() { - Some(tab) => self - .tab_state(tab.tab_id()) - .overlay - .as_ref() - .unwrap_or(tab) - .get_title(), - None => return, - }; - drop(window); + let title = if let Some(pane) = self.get_active_pane_or_overlay() { + pane.get_title() + } else { + return; + }; + if let Some(window) = self.window.as_ref() { let show_tab_bar; if num_tabs == 1 { @@ -1188,8 +1200,8 @@ impl TermWindow { } } - fn update_text_cursor(&mut self, tab: &Rc) { - let term = tab.renderer(); + fn update_text_cursor(&mut self, pane: &Rc) { + let term = pane.renderer(); let cursor = term.get_cursor_position(); if let Some(win) = self.window.as_ref() { let config = configuration(); @@ -1208,7 +1220,7 @@ impl TermWindow { } fn activate_tab(&mut self, tab_idx: isize) -> anyhow::Result<()> { - if let Some(tab) = self.get_active_tab_or_overlay() { + if let Some(tab) = self.get_active_pane_or_overlay() { tab.focus_changed(false); } @@ -1230,7 +1242,7 @@ impl TermWindow { drop(window); - if let Some(tab) = self.get_active_tab_or_overlay() { + if let Some(tab) = self.get_active_pane_or_overlay() { tab.focus_changed(true); } @@ -1297,7 +1309,14 @@ impl TermWindow { // the list of tabs up front and live with a static list. let tabs: Vec<(String, TabId)> = window .iter() - .map(|tab| (tab.get_title(), tab.tab_id())) + .map(|tab| { + ( + tab.get_active_pane() + .expect("tab to have a pane") + .get_title(), + tab.tab_id(), + ) + }) .collect(); let mux_window_id = self.mux_window_id; @@ -1351,13 +1370,16 @@ impl TermWindow { }) .collect(); - let domain_id_of_current_tab = tab.domain_id(); + let domain_id_of_current_pane = tab + .get_active_pane() + .expect("tab has no panes!") + .domain_id(); let size = self.terminal_size; let (overlay, future) = start_overlay(self, &tab, move |tab_id, term| { launcher( tab_id, - domain_id_of_current_tab, + domain_id_of_current_pane, term, mux_window_id, domains, @@ -1370,18 +1392,18 @@ impl TermWindow { } fn scroll_by_page(&mut self, amount: isize) -> anyhow::Result<()> { - let tab = match self.get_active_tab_or_overlay() { - Some(tab) => tab, + let pane = match self.get_active_pane_or_overlay() { + Some(pane) => pane, None => return Ok(()), }; - let render = tab.renderer(); + let render = pane.renderer(); let dims = render.get_dimensions(); let position = self - .get_viewport(tab.tab_id()) + .get_viewport(pane.pane_id()) .unwrap_or(dims.physical_top) .saturating_add(amount * dims.viewport_rows as isize); drop(render); - self.set_viewport(tab.tab_id(), Some(position), dims); + self.set_viewport(pane.pane_id(), Some(position), dims); if let Some(win) = self.window.as_ref() { win.invalidate(); } @@ -1447,28 +1469,33 @@ impl TermWindow { SpawnTabDomain::DefaultDomain => { let cwd = mux .get_active_tab_for_window(mux_window_id) - .and_then(|tab| tab.get_current_working_dir()); + .and_then(|tab| tab.get_active_pane()) + .and_then(|pane| pane.get_current_working_dir()); (mux.default_domain().clone(), cwd) } - SpawnTabDomain::CurrentTabDomain => { + SpawnTabDomain::CurrentPaneDomain => { if new_window { - // CurrentTabDomain is the default value for the spawn domain. + // CurrentPaneDomain is the default value for the spawn domain. // It doesn't make sense to use it when spawning a new window, // so we treat it as DefaultDomain instead. let cwd = mux .get_active_tab_for_window(mux_window_id) - .and_then(|tab| tab.get_current_working_dir()); + .and_then(|tab| tab.get_active_pane()) + .and_then(|pane| pane.get_current_working_dir()); (mux.default_domain().clone(), cwd) } else { let tab = match mux.get_active_tab_for_window(mux_window_id) { Some(tab) => tab, None => bail!("window has no tabs?"), }; + let pane = tab + .get_active_pane() + .ok_or_else(|| anyhow!("current tab has no pane!?"))?; ( - mux.get_domain(tab.domain_id()).ok_or_else(|| { + mux.get_domain(pane.domain_id()).ok_or_else(|| { anyhow!("current tab has unresolvable domain id!?") })?, - tab.get_current_working_dir(), + pane.get_current_working_dir(), ) } } @@ -1529,6 +1556,9 @@ impl TermWindow { let tab = domain.spawn(size, cmd_builder, cwd, mux_window_id).await?; let tab_id = tab.tab_id(); + let pane = tab + .get_active_pane() + .ok_or_else(|| anyhow!("newly spawned tab to have a pane"))?; if new_window { let front_end = front_end().expect("to be called on gui thread"); @@ -1536,7 +1566,7 @@ impl TermWindow { front_end.spawn_new_window(&fonts, &tab, mux_window_id)?; } else { let clipboard: Arc = Arc::new(clipboard); - tab.set_clipboard(&clipboard); + pane.set_clipboard(&clipboard); let mut window = mux .get_window_mut(mux_window_id) .ok_or_else(|| anyhow!("no such window!?"))?; @@ -1561,16 +1591,16 @@ impl TermWindow { ); } - fn selection_text(&self, tab: &Rc) -> String { + fn selection_text(&self, pane: &Rc) -> String { let mut s = String::new(); if let Some(sel) = self - .selection(tab.tab_id()) + .selection(pane.pane_id()) .range .as_ref() .map(|r| r.normalize()) { let mut last_was_wrapped = false; - let mut renderer = tab.renderer(); + let mut renderer = pane.renderer(); let (first_row, lines) = renderer.get_lines(sel.rows()); for (idx, line) in lines.iter().enumerate() { let cols = sel.cols_for_row(first_row + idx as StableRowIndex); @@ -1589,8 +1619,8 @@ impl TermWindow { s } - fn paste_from_clipboard(&mut self, tab: &Rc, clipboard: Clipboard) { - let tab_id = tab.tab_id(); + fn paste_from_clipboard(&mut self, pane: &Rc, clipboard: Clipboard) { + let pane_id = pane.pane_id(); let window = self.window.as_ref().unwrap().clone(); let future = window.get_clipboard(clipboard); promise::spawn::spawn(async move { @@ -1598,13 +1628,13 @@ impl TermWindow { window.apply(move |term_window, _window| { let clip = clip.clone(); if let Some(term_window) = term_window.downcast_mut::() { - if let Some(tab) = - term_window.tab_state(tab_id).overlay.clone().or_else(|| { + if let Some(pane) = + term_window.pane_state(pane_id).overlay.clone().or_else(|| { let mux = Mux::get().unwrap(); - mux.get_tab(tab_id) + mux.get_pane(pane_id) }) { - tab.trickle_paste(clip).ok(); + pane.trickle_paste(clip).ok(); } } Ok(()) @@ -1615,7 +1645,7 @@ impl TermWindow { fn perform_key_assignment( &mut self, - tab: &Rc, + pane: &Rc, assignment: &KeyAssignment, ) -> anyhow::Result<()> { use KeyAssignment::*; @@ -1639,13 +1669,13 @@ impl TermWindow { self.window .as_ref() .unwrap() - .set_clipboard(self.selection_text(tab)); + .set_clipboard(self.selection_text(pane)); } Paste => { - self.paste_from_clipboard(tab, Clipboard::default()); + self.paste_from_clipboard(pane, Clipboard::default()); } PastePrimarySelection => { - self.paste_from_clipboard(tab, Clipboard::PrimarySelection); + self.paste_from_clipboard(pane, Clipboard::PrimarySelection); } ActivateTabRelative(n) => { self.activate_tab_relative(*n)?; @@ -1656,7 +1686,7 @@ impl TermWindow { ActivateTab(n) => { self.activate_tab(*n)?; } - SendString(s) => tab.writer().write_all(s.as_bytes())?, + SendString(s) => pane.writer().write_all(s.as_bytes())?, Hide => { if let Some(w) = self.window.as_ref() { w.hide(); @@ -1683,8 +1713,10 @@ impl TermWindow { let con = Connection::get().expect("call on gui thread"); con.terminate_message_loop(); } - SelectTextAtMouseCursor(mode) => self.select_text_at_mouse_cursor(*mode, tab), - ExtendSelectionToMouseCursor(mode) => self.extend_selection_at_mouse_cursor(*mode, tab), + SelectTextAtMouseCursor(mode) => self.select_text_at_mouse_cursor(*mode, pane), + ExtendSelectionToMouseCursor(mode) => { + self.extend_selection_at_mouse_cursor(*mode, pane) + } OpenLinkAtMouseCursor => { // They clicked on a link, so let's open it! // Ensure that we spawn the `open` call outside of the context @@ -1700,17 +1732,18 @@ impl TermWindow { } } CompleteSelectionOrOpenLinkAtMouseCursor => { - let text = self.selection_text(&tab); + let text = self.selection_text(pane); if !text.is_empty() { let window = self.window.as_ref().unwrap(); window.set_clipboard(text); window.invalidate(); } else { - return self.perform_key_assignment(tab, &KeyAssignment::OpenLinkAtMouseCursor); + return self + .perform_key_assignment(pane, &KeyAssignment::OpenLinkAtMouseCursor); } } CompleteSelection => { - let text = self.selection_text(&tab); + let text = self.selection_text(pane); if !text.is_empty() { let window = self.window.as_ref().unwrap(); window.set_clipboard(text); @@ -1718,20 +1751,20 @@ impl TermWindow { } } ClearScrollback => { - tab.erase_scrollback(); + pane.erase_scrollback(); let window = self.window.as_ref().unwrap(); window.invalidate(); } Search(pattern) => { - if let Some(tab) = self.get_active_tab_no_overlay() { - let search = SearchOverlay::with_tab(self, &tab, pattern.clone()); - self.assign_overlay(tab.tab_id(), search); + if let Some(pane) = self.get_active_pane_no_overlay() { + let search = SearchOverlay::with_pane(self, &pane, pattern.clone()); + self.assign_overlay_for_pane(pane.pane_id(), search); } } ActivateCopyMode => { - if let Some(tab) = self.get_active_tab_no_overlay() { - let copy = CopyOverlay::with_tab(self, &tab); - self.assign_overlay(tab.tab_id(), copy); + if let Some(pane) = self.get_active_pane_no_overlay() { + let copy = CopyOverlay::with_pane(self, &pane); + self.assign_overlay_for_pane(pane.pane_id(), copy); } } }; @@ -1854,7 +1887,9 @@ impl TermWindow { let mux = Mux::get().unwrap(); if let Some(window) = mux.get_window(self.mux_window_id) { for tab in window.iter() { - tab.resize(self.terminal_size).ok(); + if let Some(pane) = tab.get_active_pane() { + pane.resize(size).ok(); + } } }; self.update_title(); @@ -1924,14 +1959,14 @@ impl TermWindow { self.activate_tab_relative(0) } - fn paint_tab(&mut self, tab: &Rc, ctx: &mut dyn PaintContext) -> anyhow::Result<()> { - let palette = tab.palette(); + fn paint_tab(&mut self, pane: &Rc, ctx: &mut dyn PaintContext) -> anyhow::Result<()> { + let palette = pane.palette(); let first_line_offset = if self.show_tab_bar { 1 } else { 0 }; - let mut term = tab.renderer(); + let mut term = pane.renderer(); let cursor = term.get_cursor_position(); self.prev_cursor.update(&cursor); - let current_viewport = self.get_viewport(tab.tab_id()); + let current_viewport = self.get_viewport(pane.pane_id()); let dims = term.get_dimensions(); @@ -1960,7 +1995,7 @@ impl TermWindow { let stable_row = stable_top + line_idx as StableRowIndex; let selrange = self - .selection(tab.tab_id()) + .selection(pane.pane_id()) .range .map(|sel| sel.cols_for_row(stable_row)) .unwrap_or(0..0); @@ -2040,7 +2075,7 @@ impl TermWindow { ); if self.show_scroll_bar { - let current_viewport = self.get_viewport(tab.tab_id()); + let current_viewport = self.get_viewport(pane.pane_id()); let info = ScrollHit::thumb( &*term, current_viewport, @@ -2076,10 +2111,10 @@ impl TermWindow { fn paint_tab_opengl( &mut self, - tab: &Rc, + pane: &Rc, frame: &mut glium::Frame, ) -> anyhow::Result<()> { - let palette = tab.palette(); + let palette = pane.palette(); let background_color = palette.resolve_bg(wezterm_term::color::ColorAttribute::Default); let (r, g, b, a) = background_color.to_tuple_rgba(); @@ -2087,11 +2122,11 @@ impl TermWindow { let first_line_offset = if self.show_tab_bar { 1 } else { 0 }; - let mut term = tab.renderer(); + let mut term = pane.renderer(); let cursor = term.get_cursor_position(); self.prev_cursor.update(&cursor); - let current_viewport = self.get_viewport(tab.tab_id()); + let current_viewport = self.get_viewport(pane.pane_id()); let (stable_top, lines); let dims = term.get_dimensions(); let config = configuration(); @@ -2176,7 +2211,7 @@ impl TermWindow { quad.set_cursor_color(rgbcolor_to_window_color(background_color)); } - let selrange = self.selection(tab.tab_id()).range.clone(); + let selrange = self.selection(pane.pane_id()).range.clone(); for (line_idx, line) in lines.iter().enumerate() { let stable_row = stable_top + line_idx as StableRowIndex; @@ -2938,23 +2973,29 @@ impl TermWindow { ) } + pub fn pane_state(&self, pane_id: PaneId) -> RefMut { + RefMut::map(self.pane_state.borrow_mut(), |state| { + state.entry(pane_id).or_insert_with(PaneState::default) + }) + } + pub fn tab_state(&self, tab_id: TabId) -> RefMut { RefMut::map(self.tab_state.borrow_mut(), |state| { state.entry(tab_id).or_insert_with(TabState::default) }) } - pub fn selection(&self, tab_id: TabId) -> RefMut { - RefMut::map(self.tab_state(tab_id), |state| &mut state.selection) + pub fn selection(&self, pane_id: PaneId) -> RefMut { + RefMut::map(self.pane_state(pane_id), |state| &mut state.selection) } - pub fn get_viewport(&self, tab_id: TabId) -> Option { - self.tab_state(tab_id).viewport + pub fn get_viewport(&self, pane_id: PaneId) -> Option { + self.pane_state(pane_id).viewport } pub fn set_viewport( &mut self, - tab_id: TabId, + pane_id: PaneId, position: Option, dims: RenderableDimensions, ) { @@ -2970,7 +3011,7 @@ impl TermWindow { None => None, }; - let mut state = self.tab_state(tab_id); + let mut state = self.pane_state(pane_id); if pos != state.viewport { state.viewport = pos; @@ -2994,7 +3035,7 @@ impl TermWindow { self.activate_tab(tab_idx as isize).ok(); } TabBarItem::NewTabButton => { - self.spawn_tab(&SpawnTabDomain::CurrentTabDomain); + self.spawn_tab(&SpawnTabDomain::CurrentPaneDomain); } TabBarItem::None => {} }, @@ -3021,14 +3062,14 @@ impl TermWindow { fn mouse_event_scroll_bar( &mut self, - tab: Rc, + pane: Rc, event: &MouseEvent, context: &dyn WindowOps, ) { if let WMEK::Press(MousePress::Left) = event.kind { - let render = tab.renderer(); + let render = pane.renderer(); let dims = render.get_dimensions(); - let current_viewport = self.get_viewport(tab.tab_id()); + let current_viewport = self.get_viewport(pane.pane_id()); let hit_result = ScrollHit::test( event.coords.y, @@ -3043,7 +3084,7 @@ impl TermWindow { ScrollHit::Above => { // Page up self.set_viewport( - tab.tab_id(), + pane.pane_id(), Some( current_viewport .unwrap_or(dims.physical_top) @@ -3056,7 +3097,7 @@ impl TermWindow { ScrollHit::Below => { // Page down self.set_viewport( - tab.tab_id(), + pane.pane_id(), Some( current_viewport .unwrap_or(dims.physical_top) @@ -3075,48 +3116,54 @@ impl TermWindow { context.set_cursor(Some(MouseCursor::Arrow)); } - fn extend_selection_at_mouse_cursor(&mut self, mode: Option, tab: &Rc) { + fn extend_selection_at_mouse_cursor( + &mut self, + mode: Option, + pane: &Rc, + ) { let mode = mode.unwrap_or(SelectionMode::Cell); let (x, y) = self.last_mouse_terminal_coords; match mode { SelectionMode::Cell => { let end = SelectionCoordinate { x, y }; - let selection_range = self.selection(tab.tab_id()).range.take(); + let selection_range = self.selection(pane.pane_id()).range.take(); let sel = match selection_range { None => { - SelectionRange::start(self.selection(tab.tab_id()).start.unwrap_or(end)) + SelectionRange::start(self.selection(pane.pane_id()).start.unwrap_or(end)) .extend(end) } Some(sel) => sel.extend(end), }; - self.selection(tab.tab_id()).range = Some(sel); + self.selection(pane.pane_id()).range = Some(sel); } SelectionMode::Word => { - let end_word = - SelectionRange::word_around(SelectionCoordinate { x, y }, &mut *tab.renderer()); + let end_word = SelectionRange::word_around( + SelectionCoordinate { x, y }, + &mut *pane.renderer(), + ); let start_coord = self - .selection(tab.tab_id()) + .selection(pane.pane_id()) .start .clone() .unwrap_or(end_word.start); - let start_word = SelectionRange::word_around(start_coord, &mut *tab.renderer()); + let start_word = SelectionRange::word_around(start_coord, &mut *pane.renderer()); let selection_range = start_word.extend_with(end_word); - self.selection(tab.tab_id()).range = Some(selection_range); + self.selection(pane.pane_id()).range = Some(selection_range); } SelectionMode::Line => { let end_line = SelectionRange::line_around(SelectionCoordinate { x, y }); let start_coord = self - .selection(tab.tab_id()) + .selection(pane.pane_id()) .start .clone() .unwrap_or(end_line.start); let start_line = SelectionRange::line_around(start_coord); let selection_range = start_line.extend_with(end_line); - self.selection(tab.tab_id()).range = Some(selection_range); + self.selection(pane.pane_id()).range = Some(selection_range); } } @@ -3128,8 +3175,10 @@ impl TermWindow { // is smaller because it feels more natural for mouse selection to have // a smaller gpa. const VERTICAL_GAP: isize = 2; - let dims = tab.renderer().get_dimensions(); - let top = self.get_viewport(tab.tab_id()).unwrap_or(dims.physical_top); + let dims = pane.renderer().get_dimensions(); + let top = self + .get_viewport(pane.pane_id()) + .unwrap_or(dims.physical_top); let vertical_gap = if dims.physical_top <= VERTICAL_GAP { 1 } else { @@ -3138,36 +3187,38 @@ impl TermWindow { let top_gap = y - top; if top_gap < vertical_gap { // Increase the gap so we can "look ahead" - self.set_viewport(tab.tab_id(), Some(y.saturating_sub(vertical_gap)), dims); + self.set_viewport(pane.pane_id(), Some(y.saturating_sub(vertical_gap)), dims); } else { let bottom_gap = (dims.viewport_rows as isize).saturating_sub(top_gap); if bottom_gap < vertical_gap { - self.set_viewport(tab.tab_id(), Some(top + vertical_gap - bottom_gap), dims); + self.set_viewport(pane.pane_id(), Some(top + vertical_gap - bottom_gap), dims); } } self.window.as_ref().unwrap().invalidate(); } - fn select_text_at_mouse_cursor(&mut self, mode: SelectionMode, tab: &Rc) { + fn select_text_at_mouse_cursor(&mut self, mode: SelectionMode, pane: &Rc) { let (x, y) = self.last_mouse_terminal_coords; match mode { SelectionMode::Line => { let start = SelectionCoordinate { x, y }; let selection_range = SelectionRange::line_around(start); - self.selection(tab.tab_id()).start = Some(start); - self.selection(tab.tab_id()).range = Some(selection_range); + self.selection(pane.pane_id()).start = Some(start); + self.selection(pane.pane_id()).range = Some(selection_range); } SelectionMode::Word => { - let selection_range = - SelectionRange::word_around(SelectionCoordinate { x, y }, &mut *tab.renderer()); + let selection_range = SelectionRange::word_around( + SelectionCoordinate { x, y }, + &mut *pane.renderer(), + ); - self.selection(tab.tab_id()).start = Some(selection_range.start); - self.selection(tab.tab_id()).range = Some(selection_range); + self.selection(pane.pane_id()).start = Some(selection_range.start); + self.selection(pane.pane_id()).range = Some(selection_range); } SelectionMode::Cell => { - self.selection(tab.tab_id()) + self.selection(pane.pane_id()) .begin(SelectionCoordinate { x, y }); } } @@ -3177,19 +3228,21 @@ impl TermWindow { fn mouse_event_terminal( &mut self, - tab: Rc, + pane: Rc, x: usize, y: i64, event: &MouseEvent, context: &dyn WindowOps, ) { - let dims = tab.renderer().get_dimensions(); - let stable_row = - self.get_viewport(tab.tab_id()).unwrap_or(dims.physical_top) + y as StableRowIndex; + let dims = pane.renderer().get_dimensions(); + let stable_row = self + .get_viewport(pane.pane_id()) + .unwrap_or(dims.physical_top) + + y as StableRowIndex; self.last_mouse_terminal_coords = (x, stable_row); - let (top, mut lines) = tab.renderer().get_lines(stable_row..stable_row + 1); + let (top, mut lines) = pane.renderer().get_lines(stable_row..stable_row + 1); let new_highlight = if top == stable_row { if let Some(line) = lines.get_mut(0) { if let Some(cell) = line.cells().get(x) { @@ -3269,7 +3322,7 @@ impl TermWindow { let ignore_grab_modifier = Modifiers::SHIFT; - if !tab.is_mouse_grabbed() || event.modifiers.contains(ignore_grab_modifier) { + if !pane.is_mouse_grabbed() || event.modifiers.contains(ignore_grab_modifier) { let event_trigger_type = match event_trigger_type { Some(ett) => ett, None => return, @@ -3279,7 +3332,7 @@ impl TermWindow { // Since we use shift to force assessing the mouse bindings, pretend // that shift is not one of the mods when the mouse is grabbed. - if tab.is_mouse_grabbed() { + if pane.is_mouse_grabbed() { modifiers -= window_mods_to_termwiz_mods(ignore_grab_modifier); } @@ -3287,7 +3340,7 @@ impl TermWindow { .input_map .lookup_mouse(event_trigger_type.clone(), modifiers) { - self.perform_key_assignment(&tab, &action).ok(); + self.perform_key_assignment(&pane, &action).ok(); } return; @@ -3326,7 +3379,7 @@ impl TermWindow { modifiers: window_mods_to_termwiz_mods(event.modifiers), }; - tab.mouse_event(mouse_event).ok(); + pane.mouse_event(mouse_event).ok(); match event.kind { WMEK::Move => {} @@ -3336,20 +3389,29 @@ impl TermWindow { } } - fn maybe_scroll_to_bottom_for_input(&mut self, tab: &Rc) { + fn maybe_scroll_to_bottom_for_input(&mut self, pane: &Rc) { if configuration().scroll_to_bottom_on_input { - self.scroll_to_bottom(tab); + self.scroll_to_bottom(pane); } } - fn scroll_to_bottom(&mut self, tab: &Rc) { - self.tab_state(tab.tab_id()).viewport = None; + fn scroll_to_bottom(&mut self, pane: &Rc) { + self.pane_state(pane.pane_id()).viewport = None; } - /// Returns a Tab that we can interact with; this will typically be - /// the active tab for the window, but if the window has an overlay, - /// then that will be returned instead. - fn get_active_tab_or_overlay(&self) -> Option> { + fn get_active_pane_no_overlay(&self) -> Option> { + let mux = Mux::get().unwrap(); + mux.get_active_tab_for_window(self.mux_window_id) + .and_then(|tab| tab.get_active_pane()) + } + + /// Returns a Pane that we can interact with; this will typically be + /// the active tab for the window, but if the window has a tab-wide + /// overlay (such as the launcher / tab navigator), + /// then that will be returned instead. Otherwise, if the pane has + /// an active overlay (such as search or copy mode) then that will + /// be returned. + fn get_active_pane_or_overlay(&self) -> Option> { let mux = Mux::get().unwrap(); let tab = match mux.get_active_tab_for_window(self.mux_window_id) { Some(tab) => tab, @@ -3357,12 +3419,17 @@ impl TermWindow { }; let tab_id = tab.tab_id(); - self.tab_state(tab_id).overlay.clone().or_else(|| Some(tab)) - } - fn get_active_tab_no_overlay(&self) -> Option> { - let mux = Mux::get().unwrap(); - mux.get_active_tab_for_window(self.mux_window_id) + if let Some(tab_overlay) = self.tab_state(tab_id).overlay.clone() { + Some(tab_overlay) + } else { + let pane = tab.get_active_pane()?; + let pane_id = pane.pane_id(); + self.pane_state(pane_id) + .overlay + .clone() + .or_else(|| Some(pane)) + } } /// Removes any overlay for the specified tab @@ -3382,7 +3449,12 @@ impl TermWindow { }); } - pub fn assign_overlay(&mut self, tab_id: TabId, overlay: Rc) { + pub fn assign_overlay_for_pane(&mut self, pane_id: PaneId, overlay: Rc) { + self.pane_state(pane_id).overlay.replace(overlay); + self.update_title(); + } + + pub fn assign_overlay(&mut self, tab_id: TabId, overlay: Rc) { self.tab_state(tab_id).overlay.replace(overlay); self.update_title(); } diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index 4285fc464..ef723c1c0 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -102,7 +102,7 @@ pub trait FrontEnd: Downcast { fn spawn_new_window( &self, fontconfig: &Rc, - tab: &Rc, + tab: &Rc, window_id: WindowId, ) -> anyhow::Result<()>; } diff --git a/src/frontend/muxserver/mod.rs b/src/frontend/muxserver/mod.rs index 2492eff7b..dfc243259 100644 --- a/src/frontend/muxserver/mod.rs +++ b/src/frontend/muxserver/mod.rs @@ -66,7 +66,7 @@ impl FrontEnd for MuxServerFrontEnd { fn spawn_new_window( &self, _fontconfig: &Rc, - _tab: &Rc, + _tab: &Rc, _window_id: WindowId, ) -> anyhow::Result<()> { Ok(()) diff --git a/src/keyassignment.rs b/src/keyassignment.rs index 3f0dc7945..c2f3946ea 100644 --- a/src/keyassignment.rs +++ b/src/keyassignment.rs @@ -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)], diff --git a/src/localtab.rs b/src/localtab.rs index b928f85c5..3df0efb15 100644 --- a/src/localtab.rs +++ b/src/localtab.rs @@ -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, process: RefCell>, pty: RefCell>, @@ -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 { @@ -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, pty: Box, 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(); diff --git a/src/main.rs b/src/main.rs index 5c811b54e..0acf0bedb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 diff --git a/src/mux/domain.rs b/src/mux/domain.rs index fbc814f43..da8b88329 100644 --- a/src/mux/domain.rs +++ b/src/mux/domain.rs @@ -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, command_dir: Option, window: WindowId, - ) -> Result, Error>; + ) -> Result, 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, command_dir: Option, window: WindowId, - ) -> Result, Error> { + ) -> Result, 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 = Rc::new(LocalTab::new(terminal, child, pair.master, self.id)); + let pane: Rc = 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)?; diff --git a/src/mux/mod.rs b/src/mux/mod.rs index 0bde3d9cd..c30ad065a 100644 --- a/src/mux/mod.rs +++ b/src/mux/mod.rs @@ -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; pub struct Mux { - tabs: RefCell>>, + tabs: RefCell>>, + panes: RefCell>>, windows: RefCell>, default_domain: RefCell>>, domains: RefCell>>, @@ -38,7 +40,7 @@ pub struct Mux { subscribers: RefCell>>, } -fn read_from_tab_pty(tab_id: TabId, mut reader: Box) { +fn read_from_pane_pty(pane_id: PaneId, mut reader: Box) { 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) { 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) { 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) { } 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> { + pub fn get_pane(&self, pane_id: PaneId) -> Option> { + self.panes.borrow().get(&pane_id).map(Rc::clone) + } + + pub fn get_tab(&self, tab_id: TabId) -> Option> { self.tabs.borrow().get(&tab_id).map(Rc::clone) } - pub fn add_tab(&self, tab: &Rc) -> 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) -> 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) -> 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 = 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> { + pub fn get_active_tab_for_window(&self, window_id: WindowId) -> Option> { 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, window_id: WindowId) -> anyhow::Result<()> { + pub fn add_tab_to_window(&self, tab: &Rc, 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> { - self.tabs + pub fn iter_panes(&self) -> Vec> { + 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(); diff --git a/src/mux/tab.rs b/src/mux/tab.rs index c07c0d7d8..6cec16349 100644 --- a/src/mux/tab.rs +++ b/src/mux/tab.rs @@ -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>) { 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>>, +} + +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> { + self.pane.borrow().as_ref().map(Rc::clone) + } + + pub fn assign_pane(&self, pane: &Rc) { + 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; 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); diff --git a/src/mux/window.rs b/src/mux/window.rs index 77625a2c6..cff283374 100644 --- a/src/mux/window.rs +++ b/src/mux/window.rs @@ -8,7 +8,7 @@ pub type WindowId = usize; pub struct Window { id: WindowId, - tabs: Vec>, + tabs: Vec>, active: usize, clipboard: Option>, invalidated: bool, @@ -33,26 +33,28 @@ impl Window { self.id } - fn check_that_tab_isnt_already_in_window(&self, tab: &Rc) { + fn check_that_tab_isnt_already_in_window(&self, tab: &Rc) { 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) { + fn assign_clipboard_to_tab(&self, tab: &Rc) { 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) { + pub fn insert(&mut self, index: usize, tab: &Rc) { 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) { + pub fn push(&mut self, tab: &Rc) { 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> { + pub fn get_by_idx(&self, idx: usize) -> Option<&Rc> { self.tabs.get(idx) } @@ -80,7 +82,7 @@ impl Window { None } - pub fn remove_by_idx(&mut self, idx: usize) -> Rc { + pub fn remove_by_idx(&mut self, idx: usize) -> Rc { self.invalidated = true; self.tabs.remove(idx) } @@ -104,7 +106,7 @@ impl Window { res } - pub fn get_active(&self) -> Option<&Rc> { + pub fn get_active(&self) -> Option<&Rc> { self.get_by_idx(self.active) } @@ -119,7 +121,7 @@ impl Window { self.active = idx; } - pub fn iter(&self) -> impl Iterator> { + pub fn iter(&self) -> impl Iterator> { 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(); diff --git a/src/server/client.rs b/src/server/client.rs index b7e0b86b0..f698aa108 100644 --- a/src/server/client.rs +++ b/src/server/client.rs @@ -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::().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::().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 ); } diff --git a/src/server/codec.rs b/src/server/codec.rs index 353816c23..507018220 100644 --- a/src/server/codec.rs +++ b/src/server/codec.rs @@ -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 { + pub fn pane_id(&self) -> Option { 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 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, @@ -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, } #[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 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, } #[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>, } @@ -696,18 +699,18 @@ impl Into> 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, } diff --git a/src/server/domain.rs b/src/server/domain.rs index 38ae1c0cb..6900560dd 100644 --- a/src/server/domain.rs +++ b/src/server/domain.rs @@ -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>, remote_to_local_tab: Mutex>, + remote_to_local_pane: Mutex>, } 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 { + let mut pane_map = self.remote_to_local_pane.lock().unwrap(); - pub fn remote_to_local_tab_id(&self, remote_tab_id: TabId) -> Option { - 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::() { - 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::() { + 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 { + 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 { + pub fn remote_to_local_pane_id(&self, remote_pane_id: TabId) -> Option { 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> { @@ -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 = 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 = 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, command_dir: Option, window: WindowId, - ) -> anyhow::Result> { + ) -> anyhow::Result> { 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 = Rc::new(ClientTab::new(&inner, remote_tab_id, size, "wezterm")); + let pane: Rc = 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)?; diff --git a/src/server/listener/clientsession.rs b/src/server/listener/clientsession.rs index 3f5ff2c21..e12a1203b 100644 --- a/src/server/listener/clientsession.rs +++ b/src/server/listener/clientsession.rs @@ -38,7 +38,7 @@ impl ClientSession { 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 ClientSession { 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 = [ diff --git a/src/server/listener/sessionhandler.rs b/src/server/listener/sessionhandler.rs index 6b020560a..032e97374 100644 --- a/src/server/listener/sessionhandler.rs +++ b/src/server/listener/sessionhandler.rs @@ -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, @@ -26,39 +26,39 @@ struct PerTab { mouse_grabbed: bool, } -impl PerTab { +impl PerPane { fn compute_changes( &mut self, - tab: &Rc, + pane: &Rc, force_with_input_serial: Option, - ) -> Option { + ) -> Option { 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, +fn maybe_push_pane_changes( + pane: &Rc, sender: PollableSender, - per_tab: Arc>, + per_pane: Arc>, ) -> 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, - per_tab: HashMap>>, + per_pane: HashMap>>, } impl SessionHandler { pub fn new(to_write_tx: PollableSender) -> Self { Self { to_write_tx, - per_tab: HashMap::new(), + per_pane: HashMap::new(), } } - fn per_tab(&mut self, tab_id: TabId) -> Arc> { + fn per_pane(&mut self, pane_id: PaneId) -> Arc> { 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 { + async fn do_search(pane_id: TabId, pattern: Pattern) -> anyhow::Result { 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, - 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) -> 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 = Arc::new(RemoteClipboard { - tab_id: tab.tab_id(), + pane_id: pane.pane_id(), sender, }); - tab.set_clipboard(&clip); + pane.set_clipboard(&clip); Ok::(Pdu::SpawnResponse(SpawnResponse { + pane_id: pane.pane_id(), tab_id: tab.tab_id(), window_id, })) diff --git a/src/server/tab/clienttab.rs b/src/server/tab/clienttab.rs index 2bfeb00d2..b8a922da3 100644 --- a/src/server/tab/clienttab.rs +++ b/src/server/tab/clienttab.rs @@ -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, - local_tab_id: TabId, - remote_tab_id: TabId, + local_pane_id: TabId, + remote_pane_id: TabId, pub renderable: RefCell, - writer: RefCell, + writer: RefCell, reader: Pipe, mouse: Rc>, clipboard: RefCell>>, mouse_grabbed: RefCell, } -impl ClientTab { +impl ClientPane { pub fn new( client: &Arc, - 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 { 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> { - 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, - 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 { - 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)))?; diff --git a/src/server/tab/mod.rs b/src/server/tab/mod.rs index 3b2e74ed6..aba4139ee 100644 --- a/src/server/tab/mod.rs +++ b/src/server/tab/mod.rs @@ -1,4 +1,4 @@ -pub use clienttab::ClientTab; +pub use clienttab::ClientPane; mod clienttab; mod mousestate; diff --git a/src/server/tab/mousestate.rs b/src/server/tab/mousestate.rs index 0cc4ef529..1caa59e1a 100644 --- a/src/server/tab/mousestate.rs +++ b/src/server/tab/mousestate.rs @@ -11,13 +11,13 @@ pub struct MouseState { pending: AtomicBool, queue: VecDeque, 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 diff --git a/src/server/tab/renderable.rs b/src/server/tab/renderable.rs index a45d74d3b..7f2a787cc 100644 --- a/src/server/tab/renderable.rs +++ b/src/server/tab/renderable.rs @@ -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, - 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, - 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, to_fetch: RangeSet, 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::() { + 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::() { 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::() { + .get_pane(local_pane_id) + .ok_or_else(|| anyhow!("no such tab {}", local_pane_id))?; + if let Some(client_tab) = tab.downcast_ref::() { let renderable = client_tab.renderable.borrow_mut(); let mut inner = renderable.inner.borrow_mut(); diff --git a/src/ssh.rs b/src/ssh.rs index 87187011a..eaeb3a7c0 100644 --- a/src/ssh.rs +++ b/src/ssh.rs @@ -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, _command_dir: Option, window: WindowId, - ) -> Result, Error> { + ) -> Result, 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 = Rc::new(LocalTab::new(terminal, child, pair.master, self.id)); + let pane: Rc = 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)?; diff --git a/src/termwiztermtab.rs b/src/termwiztermtab.rs index e20a91d95..a12f75180 100644 --- a/src/termwiztermtab.rs +++ b/src/termwiztermtab.rs @@ -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, _command_dir: Option, _window: WindowId, - ) -> anyhow::Result> { - bail!("cannot spawn tabs in a TermWizTerminalTab"); + ) -> anyhow::Result> { + 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, input_tx: Sender, @@ -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, 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 { @@ -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 { @@ -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) { +pub fn allocate(width: usize, height: usize) -> (TermWizTerminal, Rc) { 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) { }; 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 = Rc::new(tab); - let mux = Mux::get().unwrap(); - mux.add_tab(&tab).expect("to be able to add tab to mux"); + let pane: Rc = 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 = Rc::new(tab); + let pane = TermWizTerminalPane::new(domain.domain_id(), width, height, input_tx, render_rx); + let pane: Rc = 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)?;