zellij/zellij-server/src/panes/tiled_panes/mod.rs
har7an e45a3e5826
errors: Don't unwrap in server::os_input_output (#1895)
* server/os_io: Redefine `ServerOsApi` result types

to use `anyhow::Result` instead. This mostly makes the need of custom
`SpawnTerminalError` obsolete (tbd in subsequent commits) and unifies
error handling across the application.

* utils/errors: Implement new `ZellijError` type

to replace any previously defined, isolated custom error types
throughout the application. Currently implements all error variants
found in `SpawnTerminalError`.

In the long term, this will allow zellij to fall back to a single error
type for all application-specific errors, instead of having different
error types per module.

* server/unit/screen: Impl new `ServerOsApi`

with updated `Result`-types.

* server/tab/unit: Impl new `ServerOsApi`

with updated `Result`-types.

* server/os_io: Impl new `ServerOsApi`

with updated `Result`-types.

* utils/ipc: Return `anyhow::Error` in `send`

rather than a `&'static str`, which isn't compatible with
`anyhow::Context`.

* server/tab: Handle `Result` in `resize_pty!`

which is returned due to the changed return types in `ServerOsApi`.

* server/tab: Handle new `Result`s

originating in the change to the `ServerOsApi` trait definition.

* server/screen: Handle new `Result`s

originating in the change to the `ServerOsApi` trait definition.

* server/panes/tiled: Handle new `Result`s

originating in the change to the `ServerOsApi` trait definition.

* server/panes/floating: Handle new `Result`s

originating in the change to the `ServerOsApi` trait definition.

* server/lib: Unwrap on new `Result`s

originating in the change to the `ServerOsApi` trait definition. The
functions here don't return a `Result` yet, this is better left to a
follow-up PR.

* server: Remove `SpawnTerminalError`

and make use of the new `ZellijError` instead. Make use of `anyhow`s
downcast capabilities to restore the underlying original errors where
necessary, as was done previously. This gives us the flexibility to
attach context information to all errors while still allowing us to
handle specific errors in greater detail.

* server/pty: Fix vars broken in rebase

* server/os_io: Remove last `SpawnTerminalError`

* changelog: Add PR #1895
2022-11-02 05:29:50 +00:00

1171 lines
49 KiB
Rust

mod pane_resizer;
mod tiled_pane_grid;
use crate::tab::{Pane, MIN_TERMINAL_HEIGHT, MIN_TERMINAL_WIDTH};
use tiled_pane_grid::{split, TiledPaneGrid};
use crate::{
os_input_output::ServerOsApi, output::Output, panes::PaneId, ui::boundaries::Boundaries,
ui::pane_contents_and_ui::PaneContentsAndUi, ClientId,
};
use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::rc::Rc;
use std::time::Instant;
use zellij_utils::errors::prelude::*;
use zellij_utils::{
data::{ModeInfo, Style},
input::command::RunCommand,
input::layout::SplitDirection,
pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport},
};
macro_rules! resize_pty {
($pane:expr, $os_input:expr) => {
if let PaneId::Terminal(ref pid) = $pane.pid() {
// FIXME: This `set_terminal_size_using_terminal_id` call would be best in
// `TerminalPane::reflow_lines`
$os_input.set_terminal_size_using_terminal_id(
*pid,
$pane.get_content_columns() as u16,
$pane.get_content_rows() as u16,
)
} else {
Ok(())
}
};
}
fn pane_content_offset(position_and_size: &PaneGeom, viewport: &Viewport) -> (usize, usize) {
// (columns_offset, rows_offset)
// if the pane is not on the bottom or right edge on the screen, we need to reserve one space
// from its content to leave room for the boundary between it and the next pane (if it doesn't
// draw its own frame)
let columns_offset = if position_and_size.x + position_and_size.cols.as_usize() < viewport.cols
{
1
} else {
0
};
let rows_offset = if position_and_size.y + position_and_size.rows.as_usize() < viewport.rows {
1
} else {
0
};
(columns_offset, rows_offset)
}
pub struct TiledPanes {
pub panes: BTreeMap<PaneId, Box<dyn Pane>>,
display_area: Rc<RefCell<Size>>,
viewport: Rc<RefCell<Viewport>>,
connected_clients: Rc<RefCell<HashSet<ClientId>>>,
connected_clients_in_app: Rc<RefCell<HashSet<ClientId>>>,
mode_info: Rc<RefCell<HashMap<ClientId, ModeInfo>>>,
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
default_mode_info: ModeInfo,
style: Style,
session_is_mirrored: bool,
active_panes: HashMap<ClientId, PaneId>,
draw_pane_frames: bool,
panes_to_hide: HashSet<PaneId>,
fullscreen_is_active: bool,
os_api: Box<dyn ServerOsApi>,
}
impl TiledPanes {
#[allow(clippy::too_many_arguments)]
pub fn new(
display_area: Rc<RefCell<Size>>,
viewport: Rc<RefCell<Viewport>>,
connected_clients: Rc<RefCell<HashSet<ClientId>>>,
connected_clients_in_app: Rc<RefCell<HashSet<ClientId>>>,
mode_info: Rc<RefCell<HashMap<ClientId, ModeInfo>>>,
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
session_is_mirrored: bool,
draw_pane_frames: bool,
default_mode_info: ModeInfo,
style: Style,
os_api: Box<dyn ServerOsApi>,
) -> Self {
TiledPanes {
panes: BTreeMap::new(),
display_area,
viewport,
connected_clients,
connected_clients_in_app,
mode_info,
character_cell_size,
default_mode_info,
style,
session_is_mirrored,
active_panes: HashMap::new(),
draw_pane_frames,
panes_to_hide: HashSet::new(),
fullscreen_is_active: false,
os_api,
}
}
pub fn add_pane_with_existing_geom(&mut self, pane_id: PaneId, mut pane: Box<dyn Pane>) {
if self.draw_pane_frames {
pane.set_content_offset(Offset::frame(1));
}
self.panes.insert(pane_id, pane);
}
pub fn replace_active_pane(
&mut self,
pane: Box<dyn Pane>,
client_id: ClientId,
) -> Option<Box<dyn Pane>> {
let pane_id = pane.pid();
// remove the currently active pane
let previously_active_pane = self
.active_panes
.get(&client_id)
.copied()
.and_then(|active_pane_id| self.replace_pane(active_pane_id, pane));
// move clients from the previously active pane to the new pane we just inserted
if let Some(previously_active_pane) = previously_active_pane.as_ref() {
let previously_active_pane_id = previously_active_pane.pid();
self.move_clients_between_panes(previously_active_pane_id, pane_id);
}
previously_active_pane
}
pub fn replace_pane(
&mut self,
pane_id: PaneId,
mut with_pane: Box<dyn Pane>,
) -> Option<Box<dyn Pane>> {
let with_pane_id = with_pane.pid();
if self.draw_pane_frames {
with_pane.set_content_offset(Offset::frame(1));
}
let removed_pane = self.panes.remove(&pane_id).map(|removed_pane| {
let with_pane_id = with_pane.pid();
let removed_pane_geom = removed_pane.position_and_size();
let removed_pane_geom_override = removed_pane.geom_override();
with_pane.set_geom(removed_pane_geom);
match removed_pane_geom_override {
Some(geom_override) => with_pane.set_geom_override(geom_override),
None => with_pane.reset_size_and_position_override(),
};
self.panes.insert(with_pane_id, with_pane);
removed_pane
});
// move clients from the previously active pane to the new pane we just inserted
self.move_clients_between_panes(pane_id, with_pane_id);
removed_pane
}
pub fn insert_pane(&mut self, pane_id: PaneId, mut pane: Box<dyn Pane>) {
let cursor_height_width_ratio = self.cursor_height_width_ratio();
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let pane_id_and_split_direction =
pane_grid.find_room_for_new_pane(cursor_height_width_ratio);
if let Some((pane_id_to_split, split_direction)) = pane_id_and_split_direction {
// this unwrap is safe because floating panes should not be visible if there are no floating panes
let pane_to_split = self.panes.get_mut(&pane_id_to_split).unwrap();
let size_of_both_panes = pane_to_split.position_and_size();
if let Some((first_geom, second_geom)) = split(split_direction, &size_of_both_panes) {
pane_to_split.set_geom(first_geom);
pane.set_geom(second_geom);
self.panes.insert(pane_id, pane);
self.relayout(!split_direction);
}
}
}
pub fn has_room_for_new_pane(&mut self) -> bool {
let cursor_height_width_ratio = self.cursor_height_width_ratio();
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
pane_grid
.find_room_for_new_pane(cursor_height_width_ratio)
.is_some()
}
pub fn fixed_pane_geoms(&self) -> Vec<Viewport> {
self.panes
.values()
.filter_map(|p| {
let geom = p.position_and_size();
if geom.cols.is_fixed() || geom.rows.is_fixed() {
Some(geom.into())
} else {
None
}
})
.collect()
}
pub fn first_selectable_pane_id(&self) -> Option<PaneId> {
self.panes
.iter()
.filter(|(_id, pane)| pane.selectable())
.map(|(id, _)| id.to_owned())
.next()
}
pub fn pane_ids(&self) -> impl Iterator<Item = &PaneId> {
self.panes.keys()
}
pub fn relayout(&mut self, direction: SplitDirection) {
let mut pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let result = match direction {
SplitDirection::Horizontal => {
pane_grid.layout(direction, (*self.display_area.borrow()).cols)
},
SplitDirection::Vertical => {
pane_grid.layout(direction, (*self.display_area.borrow()).rows)
},
};
if let Err(e) = &result {
log::error!("{:?} relayout of the tab failed: {}", direction, e);
}
self.set_pane_frames(self.draw_pane_frames);
}
pub fn set_pane_frames(&mut self, draw_pane_frames: bool) {
self.draw_pane_frames = draw_pane_frames;
let viewport = *self.viewport.borrow();
for pane in self.panes.values_mut() {
if !pane.borderless() {
pane.set_frame(draw_pane_frames);
}
#[allow(clippy::if_same_then_else)]
if draw_pane_frames & !pane.borderless() {
// there's definitely a frame around this pane, offset its contents
pane.set_content_offset(Offset::frame(1));
} else if draw_pane_frames && pane.borderless() {
// there's no frame around this pane, and the tab isn't handling the boundaries
// between panes (they each draw their own frames as they please)
// this one doesn't - do not offset its content
pane.set_content_offset(Offset::default());
} else if !is_inside_viewport(&viewport, pane) {
// this pane is outside the viewport and has no border - it should not have an offset
pane.set_content_offset(Offset::default());
} else {
// no draw_pane_frames and this pane should have a separation to other panes
// according to its position in the viewport (eg. no separation if its at the
// viewport bottom) - offset its content accordingly
let position_and_size = pane.current_geom();
let (pane_columns_offset, pane_rows_offset) =
pane_content_offset(&position_and_size, &viewport);
pane.set_content_offset(Offset::shift(pane_rows_offset, pane_columns_offset));
}
resize_pty!(pane, self.os_api).unwrap();
}
}
pub fn can_split_pane_horizontally(&mut self, client_id: ClientId) -> bool {
if let Some(active_pane_id) = &self.active_panes.get(&client_id) {
if let Some(active_pane) = self.panes.get_mut(active_pane_id) {
let full_pane_size = active_pane.position_and_size();
if full_pane_size.rows.as_usize() < MIN_TERMINAL_HEIGHT * 2 {
return false;
} else {
return split(SplitDirection::Horizontal, &full_pane_size).is_some();
}
}
}
false
}
pub fn can_split_pane_vertically(&mut self, client_id: ClientId) -> bool {
if let Some(active_pane_id) = &self.active_panes.get(&client_id) {
if let Some(active_pane) = self.panes.get_mut(active_pane_id) {
let full_pane_size = active_pane.position_and_size();
if full_pane_size.cols.as_usize() < MIN_TERMINAL_WIDTH * 2 {
return false;
}
return split(SplitDirection::Vertical, &full_pane_size).is_some();
}
}
false
}
pub fn split_pane_horizontally(
&mut self,
pid: PaneId,
mut new_pane: Box<dyn Pane>,
client_id: ClientId,
) {
let active_pane_id = &self.active_panes.get(&client_id).unwrap();
let active_pane = self.panes.get_mut(active_pane_id).unwrap();
let full_pane_size = active_pane.position_and_size();
if let Some((top_winsize, bottom_winsize)) =
split(SplitDirection::Horizontal, &full_pane_size)
{
active_pane.set_geom(top_winsize);
new_pane.set_geom(bottom_winsize);
self.panes.insert(pid, new_pane);
self.relayout(SplitDirection::Vertical);
}
}
pub fn split_pane_vertically(
&mut self,
pid: PaneId,
mut new_pane: Box<dyn Pane>,
client_id: ClientId,
) {
let active_pane_id = &self.active_panes.get(&client_id).unwrap();
let active_pane = self.panes.get_mut(active_pane_id).unwrap();
let full_pane_size = active_pane.position_and_size();
if let Some((left_winsize, right_winsize)) =
split(SplitDirection::Vertical, &full_pane_size)
{
active_pane.set_geom(left_winsize);
new_pane.set_geom(right_winsize);
self.panes.insert(pid, new_pane);
self.relayout(SplitDirection::Horizontal);
}
}
pub fn focus_pane(&mut self, pane_id: PaneId, client_id: ClientId) {
self.active_panes.insert(client_id, pane_id);
if self.session_is_mirrored {
// move all clients
let connected_clients: Vec<ClientId> =
self.connected_clients.borrow().iter().copied().collect();
for client_id in connected_clients {
self.active_panes.insert(client_id, pane_id);
}
}
}
pub fn clear_active_panes(&mut self) {
self.active_panes.clear();
}
pub fn first_active_pane_id(&self) -> Option<PaneId> {
self.connected_clients
.borrow()
.iter()
.next()
.and_then(|first_client_id| self.active_panes.get(first_client_id).copied())
}
pub fn focused_pane_id(&self, client_id: ClientId) -> Option<PaneId> {
self.active_panes.get(&client_id).copied()
}
// FIXME: Really not a fan of allowing this... Someone with more energy
// than me should clean this up someday...
#[allow(clippy::borrowed_box)]
pub fn get_pane(&self, pane_id: PaneId) -> Option<&Box<dyn Pane>> {
self.panes.get(&pane_id)
}
pub fn get_pane_mut(&mut self, pane_id: PaneId) -> Option<&mut Box<dyn Pane>> {
self.panes.get_mut(&pane_id)
}
pub fn get_active_pane_id(&self, client_id: ClientId) -> Option<PaneId> {
self.active_panes.get(&client_id).copied()
}
pub fn panes_contain(&self, pane_id: &PaneId) -> bool {
self.panes.contains_key(pane_id)
}
pub fn set_force_render(&mut self) {
for pane in self.panes.values_mut() {
pane.set_should_render(true);
pane.set_should_render_boundaries(true);
pane.render_full_viewport();
}
}
pub fn has_active_panes(&self) -> bool {
!self.active_panes.is_empty()
}
pub fn has_panes(&self) -> bool {
!self.panes.is_empty()
}
pub fn render(&mut self, output: &mut Output, floating_panes_are_visible: bool) -> Result<()> {
let err_context = || "failed to render tiled panes";
let connected_clients: Vec<ClientId> =
{ self.connected_clients.borrow().iter().copied().collect() };
let multiple_users_exist_in_session = { self.connected_clients_in_app.borrow().len() > 1 };
let mut client_id_to_boundaries: HashMap<ClientId, Boundaries> = HashMap::new();
let active_panes = if self.session_is_mirrored || floating_panes_are_visible {
HashMap::new()
} else {
self.active_panes
.iter()
.filter(|(client_id, _pane_id)| connected_clients.contains(client_id))
.map(|(client_id, pane_id)| (*client_id, *pane_id))
.collect()
};
for (kind, pane) in self.panes.iter_mut() {
if !self.panes_to_hide.contains(&pane.pid()) {
let mut pane_contents_and_ui = PaneContentsAndUi::new(
pane,
output,
self.style,
&active_panes,
multiple_users_exist_in_session,
None,
);
for client_id in &connected_clients {
let client_mode = self
.mode_info
.borrow()
.get(client_id)
.unwrap_or(&self.default_mode_info)
.mode;
let err_context =
|| format!("failed to render tiled panes for client {client_id}");
if let PaneId::Plugin(..) = kind {
pane_contents_and_ui
.render_pane_contents_for_client(*client_id)
.with_context(err_context)?;
}
if self.draw_pane_frames {
pane_contents_and_ui
.render_pane_frame(*client_id, client_mode, self.session_is_mirrored)
.with_context(err_context)?;
} else {
let boundaries = client_id_to_boundaries
.entry(*client_id)
.or_insert_with(|| Boundaries::new(*self.viewport.borrow()));
pane_contents_and_ui.render_pane_boundaries(
*client_id,
client_mode,
boundaries,
self.session_is_mirrored,
);
}
pane_contents_and_ui.render_terminal_title_if_needed(*client_id, client_mode);
// this is done for panes that don't have their own cursor (eg. panes of
// another user)
pane_contents_and_ui
.render_fake_cursor_if_needed(*client_id)
.with_context(err_context)?;
}
if let PaneId::Terminal(..) = kind {
pane_contents_and_ui
.render_pane_contents_to_multiple_clients(connected_clients.iter().copied())
.with_context(err_context)?;
}
}
}
// render boundaries if needed
for (client_id, boundaries) in &mut client_id_to_boundaries {
// TODO: add some conditional rendering here so this isn't rendered for every character
output
.add_character_chunks_to_client(
*client_id,
boundaries.render().with_context(err_context)?,
None,
)
.with_context(err_context)?;
}
Ok(())
}
pub fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
self.panes.iter()
}
pub fn resize(&mut self, new_screen_size: Size) {
// this is blocked out to appease the borrow checker
{
let mut display_area = self.display_area.borrow_mut();
let mut viewport = self.viewport.borrow_mut();
let Size { rows, cols } = new_screen_size;
let mut pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*display_area,
*viewport,
);
match pane_grid.layout(SplitDirection::Horizontal, cols) {
Ok(_) => {
let column_difference = cols as isize - display_area.cols as isize;
// FIXME: Should the viewport be an Offset?
viewport.cols = (viewport.cols as isize + column_difference) as usize;
display_area.cols = cols;
},
Err(e) => {
log::error!("Failed to horizontally resize the tab: {:?}", e);
},
};
if pane_grid.layout(SplitDirection::Vertical, rows).is_ok() {
let row_difference = rows as isize - display_area.rows as isize;
viewport.rows = (viewport.rows as isize + row_difference) as usize;
display_area.rows = rows;
} else {
log::error!("Failed to vertically resize the tab!!!");
}
}
self.set_pane_frames(self.draw_pane_frames);
}
pub fn resize_active_pane_left(&mut self, client_id: ClientId) {
if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
let mut pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
pane_grid.resize_pane_left(&active_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, self.os_api).unwrap();
}
}
}
pub fn resize_active_pane_right(&mut self, client_id: ClientId) {
if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
let mut pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
pane_grid.resize_pane_right(&active_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, self.os_api).unwrap();
}
}
}
pub fn resize_active_pane_up(&mut self, client_id: ClientId) {
if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
let mut pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
pane_grid.resize_pane_up(&active_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, self.os_api).unwrap();
}
}
}
pub fn resize_active_pane_down(&mut self, client_id: ClientId) {
if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
let mut pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
pane_grid.resize_pane_down(&active_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, self.os_api).unwrap();
}
}
}
pub fn resize_active_pane_increase(&mut self, client_id: ClientId) {
if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
let mut pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
pane_grid.resize_increase(&active_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, self.os_api).unwrap();
}
}
}
pub fn resize_active_pane_decrease(&mut self, client_id: ClientId) {
if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
let mut pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
pane_grid.resize_decrease(&active_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, self.os_api).unwrap();
}
}
}
pub fn focus_next_pane(&mut self, client_id: ClientId) {
let connected_clients: Vec<ClientId> =
{ self.connected_clients.borrow().iter().copied().collect() };
let active_pane_id = self.get_active_pane_id(client_id).unwrap();
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let next_active_pane_id = pane_grid.next_selectable_pane_id(&active_pane_id);
for client_id in connected_clients {
self.active_panes.insert(client_id, next_active_pane_id);
}
self.set_pane_active_at(next_active_pane_id);
}
pub fn focus_previous_pane(&mut self, client_id: ClientId) {
let connected_clients: Vec<ClientId> =
{ self.connected_clients.borrow().iter().copied().collect() };
let active_pane_id = self.get_active_pane_id(client_id).unwrap();
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let next_active_pane_id = pane_grid.previous_selectable_pane_id(&active_pane_id);
for client_id in connected_clients {
self.active_panes.insert(client_id, next_active_pane_id);
}
self.set_pane_active_at(next_active_pane_id);
}
fn set_pane_active_at(&mut self, pane_id: PaneId) {
if let Some(pane) = self.get_pane_mut(pane_id) {
pane.set_active_at(Instant::now());
}
}
pub fn cursor_height_width_ratio(&self) -> Option<usize> {
let character_cell_size = self.character_cell_size.borrow();
character_cell_size.map(|size_in_pixels| {
(size_in_pixels.height as f64 / size_in_pixels.width as f64).round() as usize
})
}
pub fn move_focus_left(&mut self, client_id: ClientId) -> bool {
match self.get_active_pane_id(client_id) {
Some(active_pane_id) => {
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let next_index = pane_grid.next_selectable_pane_id_to_the_left(&active_pane_id);
match next_index {
Some(p) => {
// render previously active pane so that its frame does not remain actively
// colored
let previously_active_pane = self
.panes
.get_mut(self.active_panes.get(&client_id).unwrap())
.unwrap();
previously_active_pane.set_should_render(true);
// we render the full viewport to remove any ui elements that might have been
// there before (eg. another user's cursor)
previously_active_pane.render_full_viewport();
let next_active_pane = self.panes.get_mut(&p).unwrap();
next_active_pane.set_should_render(true);
// we render the full viewport to remove any ui elements that might have been
// there before (eg. another user's cursor)
next_active_pane.render_full_viewport();
self.focus_pane(p, client_id);
self.set_pane_active_at(p);
true
},
None => false,
}
},
None => false,
}
}
pub fn move_focus_down(&mut self, client_id: ClientId) -> bool {
match self.get_active_pane_id(client_id) {
Some(active_pane_id) => {
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let next_index = pane_grid.next_selectable_pane_id_below(&active_pane_id);
match next_index {
Some(p) => {
// render previously active pane so that its frame does not remain actively
// colored
let previously_active_pane = self
.panes
.get_mut(self.active_panes.get(&client_id).unwrap())
.unwrap();
previously_active_pane.set_should_render(true);
// we render the full viewport to remove any ui elements that might have been
// there before (eg. another user's cursor)
previously_active_pane.render_full_viewport();
let next_active_pane = self.panes.get_mut(&p).unwrap();
next_active_pane.set_should_render(true);
// we render the full viewport to remove any ui elements that might have been
// there before (eg. another user's cursor)
next_active_pane.render_full_viewport();
self.focus_pane(p, client_id);
self.set_pane_active_at(p);
true
},
None => false,
}
},
None => false,
}
}
pub fn move_focus_up(&mut self, client_id: ClientId) -> bool {
match self.get_active_pane_id(client_id) {
Some(active_pane_id) => {
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let next_index = pane_grid.next_selectable_pane_id_above(&active_pane_id);
match next_index {
Some(p) => {
// render previously active pane so that its frame does not remain actively
// colored
let previously_active_pane = self
.panes
.get_mut(self.active_panes.get(&client_id).unwrap())
.unwrap();
previously_active_pane.set_should_render(true);
// we render the full viewport to remove any ui elements that might have been
// there before (eg. another user's cursor)
previously_active_pane.render_full_viewport();
let next_active_pane = self.panes.get_mut(&p).unwrap();
next_active_pane.set_should_render(true);
// we render the full viewport to remove any ui elements that might have been
// there before (eg. another user's cursor)
next_active_pane.render_full_viewport();
self.focus_pane(p, client_id);
self.set_pane_active_at(p);
true
},
None => false,
}
},
None => false,
}
}
pub fn move_focus_right(&mut self, client_id: ClientId) -> bool {
match self.get_active_pane_id(client_id) {
Some(active_pane_id) => {
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let next_index = pane_grid.next_selectable_pane_id_to_the_right(&active_pane_id);
match next_index {
Some(p) => {
// render previously active pane so that its frame does not remain actively
// colored
let previously_active_pane = self
.panes
.get_mut(self.active_panes.get(&client_id).unwrap())
.unwrap();
previously_active_pane.set_should_render(true);
// we render the full viewport to remove any ui elements that might have been
// there before (eg. another user's cursor)
previously_active_pane.render_full_viewport();
let next_active_pane = self.panes.get_mut(&p).unwrap();
next_active_pane.set_should_render(true);
// we render the full viewport to remove any ui elements that might have been
// there before (eg. another user's cursor)
next_active_pane.render_full_viewport();
self.focus_pane(p, client_id);
self.set_pane_active_at(p);
true
},
None => false,
}
},
None => false,
}
}
pub fn move_active_pane(&mut self, client_id: ClientId) {
let active_pane_id = self.get_active_pane_id(client_id).unwrap();
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let new_position_id = pane_grid.next_selectable_pane_id(&active_pane_id);
let current_position = self.panes.get(&active_pane_id).unwrap();
let prev_geom = current_position.position_and_size();
let prev_geom_override = current_position.geom_override();
let new_position = self.panes.get_mut(&new_position_id).unwrap();
let next_geom = new_position.position_and_size();
let next_geom_override = new_position.geom_override();
new_position.set_geom(prev_geom);
if let Some(geom) = prev_geom_override {
new_position.set_geom_override(geom);
}
resize_pty!(new_position, self.os_api).unwrap();
new_position.set_should_render(true);
let current_position = self.panes.get_mut(&active_pane_id).unwrap();
current_position.set_geom(next_geom);
if let Some(geom) = next_geom_override {
current_position.set_geom_override(geom);
}
resize_pty!(current_position, self.os_api).unwrap();
current_position.set_should_render(true);
}
pub fn move_active_pane_down(&mut self, client_id: ClientId) {
if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let next_index = pane_grid.next_selectable_pane_id_below(&active_pane_id);
if let Some(p) = next_index {
let active_pane_id = self.active_panes.get(&client_id).unwrap();
let current_position = self.panes.get(active_pane_id).unwrap();
let prev_geom = current_position.position_and_size();
let prev_geom_override = current_position.geom_override();
let new_position = self.panes.get_mut(&p).unwrap();
let next_geom = new_position.position_and_size();
let next_geom_override = new_position.geom_override();
new_position.set_geom(prev_geom);
if let Some(geom) = prev_geom_override {
new_position.set_geom_override(geom);
}
resize_pty!(new_position, self.os_api).unwrap();
new_position.set_should_render(true);
let current_position = self.panes.get_mut(active_pane_id).unwrap();
current_position.set_geom(next_geom);
if let Some(geom) = next_geom_override {
current_position.set_geom_override(geom);
}
resize_pty!(current_position, self.os_api).unwrap();
current_position.set_should_render(true);
}
}
}
pub fn move_active_pane_left(&mut self, client_id: ClientId) {
if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let next_index = pane_grid.next_selectable_pane_id_to_the_left(&active_pane_id);
if let Some(p) = next_index {
let active_pane_id = self.active_panes.get(&client_id).unwrap();
let current_position = self.panes.get(active_pane_id).unwrap();
let prev_geom = current_position.position_and_size();
let prev_geom_override = current_position.geom_override();
let new_position = self.panes.get_mut(&p).unwrap();
let next_geom = new_position.position_and_size();
let next_geom_override = new_position.geom_override();
new_position.set_geom(prev_geom);
if let Some(geom) = prev_geom_override {
new_position.set_geom_override(geom);
}
resize_pty!(new_position, self.os_api).unwrap();
new_position.set_should_render(true);
let current_position = self.panes.get_mut(active_pane_id).unwrap();
current_position.set_geom(next_geom);
if let Some(geom) = next_geom_override {
current_position.set_geom_override(geom);
}
resize_pty!(current_position, self.os_api).unwrap();
current_position.set_should_render(true);
}
}
}
pub fn move_active_pane_right(&mut self, client_id: ClientId) {
if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let next_index = pane_grid.next_selectable_pane_id_to_the_right(&active_pane_id);
if let Some(p) = next_index {
let active_pane_id = self.active_panes.get(&client_id).unwrap();
let current_position = self.panes.get(active_pane_id).unwrap();
let prev_geom = current_position.position_and_size();
let prev_geom_override = current_position.geom_override();
let new_position = self.panes.get_mut(&p).unwrap();
let next_geom = new_position.position_and_size();
let next_geom_override = new_position.geom_override();
new_position.set_geom(prev_geom);
if let Some(geom) = prev_geom_override {
new_position.set_geom_override(geom);
}
resize_pty!(new_position, self.os_api).unwrap();
new_position.set_should_render(true);
let current_position = self.panes.get_mut(active_pane_id).unwrap();
current_position.set_geom(next_geom);
if let Some(geom) = next_geom_override {
current_position.set_geom_override(geom);
}
resize_pty!(current_position, self.os_api).unwrap();
current_position.set_should_render(true);
}
}
}
pub fn move_active_pane_up(&mut self, client_id: ClientId) {
if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let next_index = pane_grid.next_selectable_pane_id_above(&active_pane_id);
if let Some(p) = next_index {
let active_pane_id = self.active_panes.get(&client_id).unwrap();
let current_position = self.panes.get(active_pane_id).unwrap();
let prev_geom = current_position.position_and_size();
let prev_geom_override = current_position.geom_override();
let new_position = self.panes.get_mut(&p).unwrap();
let next_geom = new_position.position_and_size();
let next_geom_override = new_position.geom_override();
new_position.set_geom(prev_geom);
if let Some(geom) = prev_geom_override {
new_position.set_geom_override(geom);
}
resize_pty!(new_position, self.os_api).unwrap();
new_position.set_should_render(true);
let current_position = self.panes.get_mut(active_pane_id).unwrap();
current_position.set_geom(next_geom);
if let Some(geom) = next_geom_override {
current_position.set_geom_override(geom);
}
resize_pty!(current_position, self.os_api).unwrap();
current_position.set_should_render(true);
}
}
}
pub fn move_clients_out_of_pane(&mut self, pane_id: PaneId) {
let active_panes: Vec<(ClientId, PaneId)> = self
.active_panes
.iter()
.map(|(cid, pid)| (*cid, *pid))
.collect();
match self
.panes
.iter()
.filter(|(p_id, _)| !self.panes_to_hide.contains(p_id))
.find(|(p_id, p)| **p_id != pane_id && p.selectable())
.map(|(p_id, _p)| p_id)
{
Some(next_active_pane) => {
for (client_id, active_pane_id) in active_panes {
if active_pane_id == pane_id {
self.active_panes.insert(client_id, *next_active_pane);
}
}
},
None => self.active_panes.clear(),
}
}
pub fn extract_pane(&mut self, pane_id: PaneId) -> Option<Box<dyn Pane>> {
self.panes.remove(&pane_id)
}
pub fn remove_pane(&mut self, pane_id: PaneId) -> Option<Box<dyn Pane>> {
let mut pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
if pane_grid.fill_space_over_pane(pane_id) {
// successfully filled space over pane
let closed_pane = self.panes.remove(&pane_id);
self.move_clients_out_of_pane(pane_id);
self.set_pane_frames(self.draw_pane_frames); // recalculate pane frames and update size
closed_pane
} else {
self.panes.remove(&pane_id);
// this is a bit of a roundabout way to say: this is the last pane and so the tab
// should be destroyed
self.active_panes.clear();
None
}
}
pub fn hold_pane(
&mut self,
pane_id: PaneId,
exit_status: Option<i32>,
is_first_run: bool,
run_command: RunCommand,
) {
self.panes
.get_mut(&pane_id)
.map(|p| p.hold(exit_status, is_first_run, run_command));
}
pub fn panes_to_hide_contains(&self, pane_id: PaneId) -> bool {
self.panes_to_hide.contains(&pane_id)
}
pub fn fullscreen_is_active(&self) -> bool {
self.fullscreen_is_active
}
pub fn unset_fullscreen(&mut self) {
if self.fullscreen_is_active {
let first_client_id = {
let connected_clients = self.connected_clients.borrow();
*connected_clients.iter().next().unwrap()
};
let active_pane_id = self.get_active_pane_id(first_client_id).unwrap();
let panes_to_hide: Vec<_> = self.panes_to_hide.iter().copied().collect();
for pane_id in panes_to_hide {
let pane = self.get_pane_mut(pane_id).unwrap();
pane.set_should_render(true);
pane.set_should_render_boundaries(true);
}
let viewport_pane_ids: Vec<_> = self
.panes
.keys()
.copied()
.into_iter()
.filter(|id| {
!is_inside_viewport(&*self.viewport.borrow(), self.get_pane(*id).unwrap())
})
.collect();
for pid in viewport_pane_ids {
let viewport_pane = self.get_pane_mut(pid).unwrap();
viewport_pane.reset_size_and_position_override();
}
self.panes_to_hide.clear();
let active_terminal = self.get_pane_mut(active_pane_id).unwrap();
active_terminal.reset_size_and_position_override();
self.set_force_render();
let display_area = *self.display_area.borrow();
self.resize(display_area);
self.fullscreen_is_active = false;
}
}
pub fn toggle_active_pane_fullscreen(&mut self, client_id: ClientId) {
if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
if self.fullscreen_is_active {
self.unset_fullscreen();
} else {
let pane_ids_to_hide = self.panes.iter().filter_map(|(&id, _pane)| {
if id != active_pane_id
&& is_inside_viewport(&*self.viewport.borrow(), self.get_pane(id).unwrap())
{
Some(id)
} else {
None
}
});
self.panes_to_hide = pane_ids_to_hide.collect();
if self.panes_to_hide.is_empty() {
// nothing to do, pane is already as fullscreen as it can be, let's bail
return;
} else {
// For all of the panes outside of the viewport staying on the fullscreen
// screen, switch them to using override positions as well so that the resize
// system doesn't get confused by viewport and old panes that no longer line up
let viewport_pane_ids: Vec<_> = self
.panes
.keys()
.copied()
.into_iter()
.filter(|id| {
!is_inside_viewport(
&*self.viewport.borrow(),
self.get_pane(*id).unwrap(),
)
})
.collect();
for pid in viewport_pane_ids {
let viewport_pane = self.get_pane_mut(pid).unwrap();
viewport_pane.set_geom_override(viewport_pane.position_and_size());
}
let viewport = { *self.viewport.borrow() };
let active_terminal = self.get_pane_mut(active_pane_id).unwrap();
let full_screen_geom = PaneGeom {
x: viewport.x,
y: viewport.y,
..Default::default()
};
active_terminal.set_geom_override(full_screen_geom);
}
let connected_client_list: Vec<ClientId> =
{ self.connected_clients.borrow().iter().copied().collect() };
for client_id in connected_client_list {
self.focus_pane(active_pane_id, client_id);
}
self.set_force_render();
let display_area = *self.display_area.borrow();
self.resize(display_area);
self.fullscreen_is_active = true;
}
}
}
pub fn switch_next_pane_fullscreen(&mut self, client_id: ClientId) {
self.unset_fullscreen();
self.focus_next_pane(client_id);
self.toggle_active_pane_fullscreen(client_id);
}
pub fn switch_prev_pane_fullscreen(&mut self, client_id: ClientId) {
self.unset_fullscreen();
self.focus_previous_pane(client_id);
self.toggle_active_pane_fullscreen(client_id);
}
pub fn panes_to_hide_count(&self) -> usize {
self.panes_to_hide.len()
}
pub fn add_to_hidden_panels(&mut self, pid: PaneId) {
self.panes_to_hide.insert(pid);
}
pub fn remove_from_hidden_panels(&mut self, pid: PaneId) {
self.panes_to_hide.remove(&pid);
}
fn move_clients_between_panes(&mut self, from_pane_id: PaneId, to_pane_id: PaneId) {
let clients_in_pane: Vec<ClientId> = self
.active_panes
.iter()
.filter(|(_cid, pid)| **pid == from_pane_id)
.map(|(cid, _pid)| *cid)
.collect();
for client_id in clients_in_pane {
self.active_panes.remove(&client_id);
self.active_panes.insert(client_id, to_pane_id);
}
}
}
#[allow(clippy::borrowed_box)]
pub fn is_inside_viewport(viewport: &Viewport, pane: &Box<dyn Pane>) -> bool {
let pane_position_and_size = pane.current_geom();
pane_position_and_size.y >= viewport.y
&& pane_position_and_size.y + pane_position_and_size.rows.as_usize()
<= viewport.y + viewport.rows
}
pub fn pane_geom_is_inside_viewport(viewport: &Viewport, geom: &PaneGeom) -> bool {
geom.y >= viewport.y
&& geom.y + geom.rows.as_usize() <= viewport.y + viewport.rows
&& geom.x >= viewport.x
&& geom.x + geom.cols.as_usize() <= viewport.x + viewport.cols
}