zellij/zellij-server/src/tab/layout_applier.rs
Aram Drevekenin 2248080de9
style(fmt): various cleanups (#3698)
* fix(configuration): rounding error in ui

* style(fmt): remove warnings

* style(fmt): rustfmt
2024-10-24 18:23:14 +02:00

920 lines
39 KiB
Rust

use zellij_utils::errors::prelude::*;
use crate::resize_pty;
use crate::tab::{get_next_terminal_position, HoldForCommand, Pane};
use crate::{
os_input_output::ServerOsApi,
panes::sixel::SixelImageStore,
panes::{FloatingPanes, TiledPanes},
panes::{LinkHandler, PaneId, PluginPane, TerminalPane},
plugins::PluginInstruction,
pty::PtyInstruction,
thread_bus::ThreadSenders,
ClientId,
};
use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::rc::Rc;
use zellij_utils::{
data::{Palette, Style},
input::layout::{FloatingPaneLayout, Run, RunPluginOrAlias, TiledPaneLayout},
pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport},
};
pub struct LayoutApplier<'a> {
viewport: Rc<RefCell<Viewport>>, // includes all non-UI panes
senders: ThreadSenders,
sixel_image_store: Rc<RefCell<SixelImageStore>>,
link_handler: Rc<RefCell<LinkHandler>>,
terminal_emulator_colors: Rc<RefCell<Palette>>,
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
connected_clients: Rc<RefCell<HashSet<ClientId>>>,
style: Style,
display_area: Rc<RefCell<Size>>, // includes all panes (including eg. the status bar and tab bar in the default layout)
tiled_panes: &'a mut TiledPanes,
floating_panes: &'a mut FloatingPanes,
draw_pane_frames: bool,
focus_pane_id: &'a mut Option<PaneId>,
os_api: Box<dyn ServerOsApi>,
debug: bool,
arrow_fonts: bool,
styled_underlines: bool,
explicitly_disable_kitty_keyboard_protocol: bool,
}
impl<'a> LayoutApplier<'a> {
pub fn new(
viewport: &Rc<RefCell<Viewport>>,
senders: &ThreadSenders,
sixel_image_store: &Rc<RefCell<SixelImageStore>>,
link_handler: &Rc<RefCell<LinkHandler>>,
terminal_emulator_colors: &Rc<RefCell<Palette>>,
terminal_emulator_color_codes: &Rc<RefCell<HashMap<usize, String>>>,
character_cell_size: &Rc<RefCell<Option<SizeInPixels>>>,
connected_clients: &Rc<RefCell<HashSet<ClientId>>>,
style: &Style,
display_area: &Rc<RefCell<Size>>, // includes all panes (including eg. the status bar and tab bar in the default layout)
tiled_panes: &'a mut TiledPanes,
floating_panes: &'a mut FloatingPanes,
draw_pane_frames: bool,
focus_pane_id: &'a mut Option<PaneId>,
os_api: &Box<dyn ServerOsApi>,
debug: bool,
arrow_fonts: bool,
styled_underlines: bool,
explicitly_disable_kitty_keyboard_protocol: bool,
) -> Self {
let viewport = viewport.clone();
let senders = senders.clone();
let sixel_image_store = sixel_image_store.clone();
let link_handler = link_handler.clone();
let terminal_emulator_colors = terminal_emulator_colors.clone();
let terminal_emulator_color_codes = terminal_emulator_color_codes.clone();
let character_cell_size = character_cell_size.clone();
let connected_clients = connected_clients.clone();
let style = style.clone();
let display_area = display_area.clone();
let os_api = os_api.clone();
LayoutApplier {
viewport,
senders,
sixel_image_store,
link_handler,
terminal_emulator_colors,
terminal_emulator_color_codes,
character_cell_size,
connected_clients,
style,
display_area,
tiled_panes,
floating_panes,
draw_pane_frames,
focus_pane_id,
os_api,
debug,
arrow_fonts,
styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
}
}
pub fn apply_layout(
&mut self,
layout: TiledPaneLayout,
floating_panes_layout: Vec<FloatingPaneLayout>,
new_terminal_ids: Vec<(u32, HoldForCommand)>,
new_floating_terminal_ids: Vec<(u32, HoldForCommand)>,
mut new_plugin_ids: HashMap<RunPluginOrAlias, Vec<u32>>,
client_id: ClientId,
) -> Result<bool> {
// true => should_show_floating_panes
let hide_floating_panes = layout.hide_floating_panes;
self.apply_tiled_panes_layout(layout, new_terminal_ids, &mut new_plugin_ids, client_id)?;
let layout_has_floating_panes = self.apply_floating_panes_layout(
floating_panes_layout,
new_floating_terminal_ids,
&mut new_plugin_ids,
)?;
let should_show_floating_panes = layout_has_floating_panes && !hide_floating_panes;
return Ok(should_show_floating_panes);
}
pub fn apply_tiled_panes_layout_to_existing_panes(
&mut self,
layout: &TiledPaneLayout,
refocus_pane: bool,
client_id: Option<ClientId>,
) -> Result<()> {
let free_space = self.total_space_for_tiled_panes();
let tiled_panes_count = self.tiled_panes.visible_panes_count();
let positions_in_layout =
match layout.position_panes_in_space(&free_space, Some(tiled_panes_count), false) {
Ok(positions_in_layout) => positions_in_layout,
// in the error branch, we try to recover by positioning the panes in the space but
// ignoring the percentage sizes (passing true as the third argument), this is a hack
// around some issues with the constraint system that should be addressed in a systemic
// manner
Err(_e) => layout
.position_panes_in_space(&free_space, Some(tiled_panes_count), true)
.map_err(|e| anyhow!(e))?,
};
let currently_focused_pane_id =
client_id.and_then(|client_id| self.tiled_panes.focused_pane_id(client_id));
let mut existing_tab_state =
ExistingTabState::new(self.tiled_panes.drain(), currently_focused_pane_id);
let mut pane_focuser = PaneFocuser::new(refocus_pane);
let mut positions_left = vec![];
for (layout, position_and_size) in positions_in_layout {
// first try to find panes with contents matching the layout exactly
match existing_tab_state.find_and_extract_exact_match_pane(
&layout.run,
&position_and_size,
true,
) {
Some(mut pane) => {
self.apply_layout_properties_to_pane(
&mut pane,
&layout,
Some(position_and_size),
);
pane_focuser.set_pane_id_in_focused_location(layout.focus, &pane);
pane_focuser.set_expanded_stacked_pane(layout.is_expanded_in_stack, &pane);
resize_pty!(pane, self.os_api, self.senders, self.character_cell_size)?;
self.tiled_panes
.add_pane_with_existing_geom(pane.pid(), pane);
},
None => {
positions_left.push((layout, position_and_size));
},
}
}
for (layout, position_and_size) in positions_left {
// now let's try to find panes on a best-effort basis
if let Some(mut pane) = existing_tab_state.find_and_extract_pane(
&layout.run,
&position_and_size,
layout.focus.unwrap_or(false),
true,
) {
self.apply_layout_properties_to_pane(&mut pane, &layout, Some(position_and_size));
pane_focuser.set_pane_id_in_focused_location(layout.focus, &pane);
pane_focuser.set_expanded_stacked_pane(layout.is_expanded_in_stack, &pane);
resize_pty!(pane, self.os_api, self.senders, self.character_cell_size)?;
self.tiled_panes
.add_pane_with_existing_geom(pane.pid(), pane);
}
}
let remaining_pane_ids: Vec<PaneId> = existing_tab_state.pane_ids();
for pane_id in remaining_pane_ids {
if let Some(mut pane) = existing_tab_state.remove_pane(&pane_id) {
self.apply_layout_properties_to_pane(&mut pane, &layout, None);
self.tiled_panes.insert_pane(pane.pid(), pane);
}
}
pane_focuser.focus_tiled_pane(&mut self.tiled_panes);
LayoutApplier::offset_viewport(
self.viewport.clone(),
self.tiled_panes,
self.draw_pane_frames,
);
Ok(())
}
fn apply_tiled_panes_layout(
&mut self,
layout: TiledPaneLayout,
new_terminal_ids: Vec<(u32, HoldForCommand)>,
new_plugin_ids: &mut HashMap<RunPluginOrAlias, Vec<u32>>,
client_id: ClientId,
) -> Result<()> {
let err_context = || format!("failed to apply tiled panes layout");
let free_space = self.total_space_for_tiled_panes();
let mut positions_in_layout = match layout.position_panes_in_space(&free_space, None, false)
{
Ok(positions_in_layout) => positions_in_layout,
// in the error branch, we try to recover by positioning the panes in the space but
// ignoring the percentage sizes (passing true as the third argument), this is a hack
// around some issues with the constraint system that should be addressed in a systemic
// manner
Err(_e) => layout
.position_panes_in_space(&free_space, None, true)
.map_err(|e| anyhow!(e))?,
};
let mut run_instructions_to_ignore = layout.run_instructions_to_ignore.clone();
let mut new_terminal_ids = new_terminal_ids.iter();
let mut focus_pane_id: Option<PaneId> = None;
let mut set_focus_pane_id = |layout: &TiledPaneLayout, pane_id: PaneId| {
if layout.focus.unwrap_or(false) && focus_pane_id.is_none() {
focus_pane_id = Some(pane_id);
}
};
// first, try to find rooms for the panes that are already running (represented by
// run_instructions_to_ignore), we try to either find an explicit position (the new
// layout has a pane with the exact run instruction) or an otherwise free position
// (the new layout has a pane with None as its run instruction)
for run_instruction in run_instructions_to_ignore.drain(..) {
if let Some(position) = positions_in_layout
.iter()
.position(|(layout, _position_and_size)| &layout.run == &run_instruction)
{
let (layout, position_and_size) = positions_in_layout.remove(position);
self.tiled_panes.set_geom_for_pane_with_run(
layout.run,
position_and_size,
layout.borderless,
);
} else if let Some(position) = positions_in_layout
.iter()
.position(|(layout, _position_and_size)| layout.run.is_none())
{
let (layout, position_and_size) = positions_in_layout.remove(position);
self.tiled_panes.set_geom_for_pane_with_run(
run_instruction,
position_and_size,
layout.borderless,
);
} else if let Some(position) =
positions_in_layout
.iter()
.position(|(layout, _position_and_size)| {
Run::is_terminal(&layout.run) && Run::is_terminal(&run_instruction)
})
{
let (layout, position_and_size) = positions_in_layout.remove(position);
self.tiled_panes.set_geom_for_pane_with_run(
run_instruction,
position_and_size,
layout.borderless,
);
} else {
log::error!(
"Failed to find room for run instruction: {:?}",
run_instruction
);
}
}
// then, we open new panes for each run instruction in the layout with the details
// we got from the plugin thread and pty thread
let positions_and_size = positions_in_layout.iter();
for (layout, position_and_size) in positions_and_size {
if let Some(Run::Plugin(run)) = layout.run.clone() {
let pane_title = run.location_string();
let pid = new_plugin_ids
.get_mut(&run)
.and_then(|ids| ids.pop())
.with_context(err_context)?;
let mut new_plugin = PluginPane::new(
pid,
*position_and_size,
self.senders
.to_plugin
.as_ref()
.with_context(err_context)?
.clone(),
pane_title,
layout.name.clone().unwrap_or_default(),
self.sixel_image_store.clone(),
self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(),
self.link_handler.clone(),
self.character_cell_size.clone(),
self.connected_clients.borrow().iter().copied().collect(),
self.style,
layout.run.clone(),
self.debug,
self.arrow_fonts,
self.styled_underlines,
);
if let Some(pane_initial_contents) = &layout.pane_initial_contents {
new_plugin.handle_pty_bytes(pane_initial_contents.as_bytes().into());
new_plugin.handle_pty_bytes("\n\r".as_bytes().into());
}
new_plugin.set_borderless(layout.borderless);
if let Some(exclude_from_sync) = layout.exclude_from_sync {
new_plugin.set_exclude_from_sync(exclude_from_sync);
}
self.tiled_panes
.add_pane_with_existing_geom(PaneId::Plugin(pid), Box::new(new_plugin));
set_focus_pane_id(layout, PaneId::Plugin(pid));
} else {
// there are still panes left to fill, use the pids we received in this method
if let Some((pid, hold_for_command)) = new_terminal_ids.next() {
let next_terminal_position =
get_next_terminal_position(&self.tiled_panes, &self.floating_panes);
let initial_title = match &layout.run {
Some(Run::Command(run_command)) => Some(run_command.to_string()),
_ => None,
};
let mut new_pane = TerminalPane::new(
*pid,
*position_and_size,
self.style,
next_terminal_position,
layout.name.clone().unwrap_or_default(),
self.link_handler.clone(),
self.character_cell_size.clone(),
self.sixel_image_store.clone(),
self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(),
initial_title,
layout.run.clone(),
self.debug,
self.arrow_fonts,
self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
);
if let Some(pane_initial_contents) = &layout.pane_initial_contents {
new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into());
new_pane.handle_pty_bytes("\n\r".as_bytes().into());
}
new_pane.set_borderless(layout.borderless);
if let Some(exclude_from_sync) = layout.exclude_from_sync {
new_pane.set_exclude_from_sync(exclude_from_sync);
}
if let Some(held_command) = hold_for_command {
new_pane.hold(None, true, held_command.clone());
}
self.tiled_panes
.add_pane_with_existing_geom(PaneId::Terminal(*pid), Box::new(new_pane));
set_focus_pane_id(layout, PaneId::Terminal(*pid));
}
}
}
for (unused_pid, _) in new_terminal_ids {
self.senders
.send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid)))
.with_context(err_context)?;
}
self.adjust_viewport().with_context(err_context)?;
self.set_focused_tiled_pane(focus_pane_id, client_id);
Ok(())
}
fn apply_floating_panes_layout(
&mut self,
floating_panes_layout: Vec<FloatingPaneLayout>,
new_floating_terminal_ids: Vec<(u32, HoldForCommand)>,
new_plugin_ids: &mut HashMap<RunPluginOrAlias, Vec<u32>>,
) -> Result<bool> {
// true => has floating panes
let err_context = || format!("Failed to apply_floating_panes_layout");
let mut layout_has_floating_panes = false;
let floating_panes_layout = floating_panes_layout.iter();
let mut focused_floating_pane = None;
let mut new_floating_terminal_ids = new_floating_terminal_ids.iter();
for floating_pane_layout in floating_panes_layout {
layout_has_floating_panes = true;
let position_and_size = self
.floating_panes
.position_floating_pane_layout(&floating_pane_layout);
if floating_pane_layout.already_running {
self.floating_panes.set_geom_for_pane_with_run(
floating_pane_layout.run.clone(),
position_and_size,
);
} else if let Some(Run::Plugin(run)) = floating_pane_layout.run.clone() {
let pane_title = run.location_string();
let pid = new_plugin_ids
.get_mut(&run)
.and_then(|ids| ids.pop())
.with_context(err_context)?;
let mut new_pane = PluginPane::new(
pid,
position_and_size,
self.senders
.to_plugin
.as_ref()
.with_context(err_context)?
.clone(),
pane_title,
floating_pane_layout.name.clone().unwrap_or_default(),
self.sixel_image_store.clone(),
self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(),
self.link_handler.clone(),
self.character_cell_size.clone(),
self.connected_clients.borrow().iter().copied().collect(),
self.style,
floating_pane_layout.run.clone(),
self.debug,
self.arrow_fonts,
self.styled_underlines,
);
if let Some(pane_initial_contents) = &floating_pane_layout.pane_initial_contents {
new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into());
new_pane.handle_pty_bytes("\n\r".as_bytes().into());
}
new_pane.set_borderless(false);
new_pane.set_content_offset(Offset::frame(1));
resize_pty!(
new_pane,
self.os_api,
self.senders,
self.character_cell_size
)?;
self.floating_panes
.add_pane(PaneId::Plugin(pid), Box::new(new_pane));
if floating_pane_layout.focus.unwrap_or(false) {
focused_floating_pane = Some(PaneId::Plugin(pid));
}
} else if let Some((pid, hold_for_command)) = new_floating_terminal_ids.next() {
let next_terminal_position =
get_next_terminal_position(&self.tiled_panes, &self.floating_panes);
let initial_title = match &floating_pane_layout.run {
Some(Run::Command(run_command)) => Some(run_command.to_string()),
_ => None,
};
let mut new_pane = TerminalPane::new(
*pid,
position_and_size,
self.style,
next_terminal_position,
floating_pane_layout.name.clone().unwrap_or_default(),
self.link_handler.clone(),
self.character_cell_size.clone(),
self.sixel_image_store.clone(),
self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(),
initial_title,
floating_pane_layout.run.clone(),
self.debug,
self.arrow_fonts,
self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
);
if let Some(pane_initial_contents) = &floating_pane_layout.pane_initial_contents {
new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into());
new_pane.handle_pty_bytes("\n\r".as_bytes().into());
}
new_pane.set_borderless(false);
new_pane.set_content_offset(Offset::frame(1));
if let Some(held_command) = hold_for_command {
new_pane.hold(None, true, held_command.clone());
}
resize_pty!(
new_pane,
self.os_api,
self.senders,
self.character_cell_size
)?;
self.floating_panes
.add_pane(PaneId::Terminal(*pid), Box::new(new_pane));
if floating_pane_layout.focus.unwrap_or(false) {
focused_floating_pane = Some(PaneId::Terminal(*pid));
}
}
}
if let Some(focused_floating_pane) = focused_floating_pane {
self.floating_panes
.focus_pane_for_all_clients(focused_floating_pane);
}
if layout_has_floating_panes {
Ok(true)
} else {
Ok(false)
}
}
pub fn apply_floating_panes_layout_to_existing_panes(
&mut self,
floating_panes_layout: &Vec<FloatingPaneLayout>,
refocus_pane: bool,
client_id: Option<ClientId>,
) -> Result<bool> {
// true => has floating panes
let mut layout_has_floating_panes = false;
let layout_has_focused_pane = floating_panes_layout
.iter()
.find(|f| f.focus.map(|f| f).unwrap_or(false))
.is_some();
let floating_panes_layout = floating_panes_layout.iter();
let currently_focused_pane_id = self
.floating_panes
.active_pane_id_or_focused_pane_id(client_id);
let mut existing_tab_state =
ExistingTabState::new(self.floating_panes.drain(), currently_focused_pane_id);
let mut pane_focuser = PaneFocuser::new(refocus_pane);
for floating_pane_layout in floating_panes_layout {
let position_and_size = self
.floating_panes
.position_floating_pane_layout(&floating_pane_layout);
let is_focused = floating_pane_layout.focus.unwrap_or(false);
if let Some(mut pane) = existing_tab_state.find_and_extract_pane(
&floating_pane_layout.run,
&position_and_size,
is_focused,
false,
) {
layout_has_floating_panes = true;
self.apply_floating_pane_layout_properties_to_pane(
&mut pane,
Some(&floating_pane_layout),
position_and_size,
);
let pane_is_focused = floating_pane_layout
.focus
.or(Some(!layout_has_focused_pane));
pane_focuser.set_pane_id_in_focused_location(pane_is_focused, &pane);
resize_pty!(pane, self.os_api, self.senders, self.character_cell_size)?;
self.floating_panes.add_pane(pane.pid(), pane);
}
}
let remaining_pane_ids: Vec<PaneId> = existing_tab_state.pane_ids();
for pane_id in remaining_pane_ids {
match self.floating_panes.find_room_for_new_pane() {
Some(position_and_size) => {
if let Some(mut pane) = existing_tab_state.remove_pane(&pane_id) {
layout_has_floating_panes = true;
self.apply_floating_pane_layout_properties_to_pane(
&mut pane,
None,
position_and_size,
);
pane_focuser
.set_pane_id_in_focused_location(Some(!layout_has_focused_pane), &pane);
resize_pty!(pane, self.os_api, self.senders, self.character_cell_size)?;
self.floating_panes.add_pane(pane.pid(), pane);
}
},
None => {
log::error!("could not find room for pane!")
},
}
}
if layout_has_floating_panes {
pane_focuser.focus_floating_pane(&mut self.floating_panes, &mut self.os_api);
Ok(true)
} else {
Ok(false)
}
}
fn resize_whole_tab(&mut self, new_screen_size: Size) -> Result<()> {
let err_context = || {
format!(
"failed to resize whole tab to new screen size {:?}",
new_screen_size
)
};
self.floating_panes.resize(new_screen_size);
// we need to do this explicitly because floating_panes.resize does not do this
self.floating_panes
.resize_pty_all_panes(&mut self.os_api)
.with_context(err_context)?;
self.tiled_panes.resize(new_screen_size);
Ok(())
}
pub fn offset_viewport(
viewport: Rc<RefCell<Viewport>>,
tiled_panes: &mut TiledPanes,
draw_pane_frames: bool,
) {
let boundary_geoms = tiled_panes.non_selectable_pane_geoms_inside_viewport();
{
// curly braces here is so that we free viewport immediately when we're done
let mut viewport = viewport.borrow_mut();
for position_and_size in boundary_geoms {
if position_and_size.x == viewport.x
&& position_and_size.x + position_and_size.cols == viewport.x + viewport.cols
{
if position_and_size.y == viewport.y {
viewport.y += position_and_size.rows;
viewport.rows -= position_and_size.rows;
} else if position_and_size.y + position_and_size.rows
== viewport.y + viewport.rows
{
viewport.rows -= position_and_size.rows;
}
}
if position_and_size.y == viewport.y
&& position_and_size.y + position_and_size.rows == viewport.y + viewport.rows
{
if position_and_size.x == viewport.x {
viewport.x += position_and_size.cols;
viewport.cols -= position_and_size.cols;
} else if position_and_size.x + position_and_size.cols
== viewport.x + viewport.cols
{
viewport.cols -= position_and_size.cols;
}
}
}
}
tiled_panes.set_pane_frames(draw_pane_frames);
}
fn adjust_viewport(&mut self) -> Result<()> {
// here we offset the viewport after applying a tiled panes layout
// from borderless panes that are on the edges of the
// screen, this is so that when we don't have pane boundaries (eg. when they were
// disabled by the user) boundaries won't be drawn around these panes
// geometrically, we can only do this with panes that are on the edges of the
// screen - so it's mostly a best-effort thing
let err_context = "failed to adjust viewport";
let display_area = {
let display_area = self.display_area.borrow();
*display_area
};
self.resize_whole_tab(display_area).context(err_context)?;
LayoutApplier::offset_viewport(
self.viewport.clone(),
self.tiled_panes,
self.draw_pane_frames,
);
Ok(())
}
fn set_focused_tiled_pane(&mut self, focus_pane_id: Option<PaneId>, client_id: ClientId) {
if let Some(pane_id) = focus_pane_id {
*self.focus_pane_id = Some(pane_id);
self.tiled_panes.focus_pane(pane_id, client_id);
} else {
let next_selectable_pane_id = self.tiled_panes.first_selectable_pane_id();
match next_selectable_pane_id {
Some(active_pane_id) => {
self.tiled_panes.focus_pane(active_pane_id, client_id);
},
None => {
self.tiled_panes.clear_active_panes();
},
}
}
}
fn apply_layout_properties_to_pane(
&self,
pane: &mut Box<dyn Pane>,
layout: &TiledPaneLayout,
position_and_size: Option<PaneGeom>,
) {
if let Some(position_and_size) = position_and_size {
pane.set_geom(position_and_size);
}
pane.set_borderless(layout.borderless);
if let Some(pane_title) = layout.name.as_ref() {
pane.set_title(pane_title.into());
}
}
fn apply_floating_pane_layout_properties_to_pane(
&self,
pane: &mut Box<dyn Pane>,
floating_pane_layout: Option<&FloatingPaneLayout>,
position_and_size: PaneGeom,
) {
pane.set_geom(position_and_size);
pane.set_borderless(false);
if let Some(pane_title) = floating_pane_layout.and_then(|f| f.name.clone()) {
pane.set_title(pane_title);
}
pane.set_content_offset(Offset::frame(1));
}
fn total_space_for_tiled_panes(&self) -> PaneGeom {
// for tiled panes we need to take the display area rather than the viewport because the
// viewport can potentially also be changed
let (display_area_cols, display_area_rows) = {
let display_area = self.display_area.borrow();
(display_area.cols, display_area.rows)
};
let mut free_space = PaneGeom::default();
free_space.cols.set_inner(display_area_cols);
free_space.rows.set_inner(display_area_rows);
free_space
}
}
struct ExistingTabState {
existing_panes: BTreeMap<PaneId, Box<dyn Pane>>,
currently_focused_pane_id: Option<PaneId>,
}
impl ExistingTabState {
pub fn new(
existing_panes: BTreeMap<PaneId, Box<dyn Pane>>,
currently_focused_pane_id: Option<PaneId>,
) -> Self {
ExistingTabState {
existing_panes,
currently_focused_pane_id,
}
}
pub fn find_and_extract_exact_match_pane(
&mut self,
run: &Option<Run>,
position_and_size: &PaneGeom,
default_to_closest_position: bool,
) -> Option<Box<dyn Pane>> {
let candidates = self.pane_candidates(run, position_and_size, default_to_closest_position);
if let Some(current_pane_id_with_same_contents) =
self.find_pane_id_with_same_contents_and_location(&candidates, run, position_and_size)
{
return self
.existing_panes
.remove(&current_pane_id_with_same_contents);
}
None
}
pub fn find_and_extract_pane(
&mut self,
run: &Option<Run>,
position_and_size: &PaneGeom,
is_focused: bool,
default_to_closest_position: bool,
) -> Option<Box<dyn Pane>> {
let candidates = self.pane_candidates(run, position_and_size, default_to_closest_position);
if let Some(current_pane_id_with_same_contents) =
self.find_pane_id_with_same_contents(&candidates, run)
{
return self
.existing_panes
.remove(&current_pane_id_with_same_contents);
} else if let Some(currently_focused_pane_id) =
self.find_focused_pane_id(is_focused, &candidates)
{
return self.existing_panes.remove(&currently_focused_pane_id);
} else if let Some(same_position_candidate_id) = candidates
.iter()
.find(|(_, p)| p.position_and_size() == *position_and_size)
.map(|(pid, _p)| *pid)
.copied()
{
return self.existing_panes.remove(&same_position_candidate_id);
} else if let Some(first_candidate) =
candidates.iter().next().map(|(pid, _p)| *pid).copied()
{
return self.existing_panes.remove(&first_candidate);
}
None
}
pub fn pane_ids(&self) -> Vec<PaneId> {
self.existing_panes.keys().copied().collect()
}
pub fn remove_pane(&mut self, pane_id: &PaneId) -> Option<Box<dyn Pane>> {
self.existing_panes.remove(pane_id)
}
fn pane_candidates(
&self,
run: &Option<Run>,
position_and_size: &PaneGeom,
default_to_closest_position: bool,
) -> Vec<(&PaneId, &Box<dyn Pane>)> {
let mut candidates: Vec<_> = self.existing_panes.iter().collect();
candidates.sort_by(|(a_id, a), (b_id, b)| {
let a_invoked_with = a.invoked_with();
let b_invoked_with = b.invoked_with();
if Run::is_same_category(run, a_invoked_with)
&& !Run::is_same_category(run, b_invoked_with)
{
std::cmp::Ordering::Less
} else if Run::is_same_category(run, b_invoked_with)
&& !Run::is_same_category(run, a_invoked_with)
{
std::cmp::Ordering::Greater
} else if Run::is_terminal(a_invoked_with) && !Run::is_terminal(b_invoked_with) {
// we place terminals before everything else because when we can't find
// an exact match, we need to prefer terminals are more often than not
// we'd be doing the right thing here
std::cmp::Ordering::Less
} else if Run::is_terminal(b_invoked_with) && !Run::is_terminal(a_invoked_with) {
std::cmp::Ordering::Greater
} else {
// try to find the closest pane
if default_to_closest_position {
let abs = |a, b| (a as isize - b as isize).abs();
let a_x_distance = abs(a.position_and_size().x, position_and_size.x);
let a_y_distance = abs(a.position_and_size().y, position_and_size.y);
let b_x_distance = abs(b.position_and_size().x, position_and_size.x);
let b_y_distance = abs(b.position_and_size().y, position_and_size.y);
(a_x_distance + a_y_distance).cmp(&(b_x_distance + b_y_distance))
} else {
a_id.cmp(&b_id) // just so it's a stable sort
}
}
});
candidates
}
fn find_focused_pane_id(
&self,
is_focused: bool,
candidates: &Vec<(&PaneId, &Box<dyn Pane>)>,
) -> Option<PaneId> {
if is_focused {
candidates
.iter()
.find(|(pid, _p)| Some(**pid) == self.currently_focused_pane_id)
.map(|(pid, _p)| *pid)
.copied()
} else {
None
}
}
fn find_pane_id_with_same_contents(
&self,
candidates: &Vec<(&PaneId, &Box<dyn Pane>)>,
run: &Option<Run>,
) -> Option<PaneId> {
candidates
.iter()
.find(|(_pid, p)| p.invoked_with() == run)
.map(|(pid, _p)| *pid)
.copied()
}
fn find_pane_id_with_same_contents_and_location(
&self,
candidates: &Vec<(&PaneId, &Box<dyn Pane>)>,
run: &Option<Run>,
position: &PaneGeom,
) -> Option<PaneId> {
candidates
.iter()
.find(|(_pid, p)| p.invoked_with() == run && p.position_and_size() == *position)
.map(|(pid, _p)| *pid)
.copied()
}
}
#[derive(Default, Debug)]
struct PaneFocuser {
refocus_pane: bool,
pane_id_in_focused_location: Option<PaneId>,
expanded_stacked_pane_ids: Vec<PaneId>,
}
impl PaneFocuser {
pub fn new(refocus_pane: bool) -> Self {
PaneFocuser {
refocus_pane,
..Default::default()
}
}
pub fn set_pane_id_in_focused_location(
&mut self,
is_focused: Option<bool>,
pane: &Box<dyn Pane>,
) {
if is_focused.unwrap_or(false) && pane.selectable() {
self.pane_id_in_focused_location = Some(pane.pid());
}
}
pub fn set_expanded_stacked_pane(&mut self, is_expanded_in_stack: bool, pane: &Box<dyn Pane>) {
if is_expanded_in_stack && pane.selectable() {
self.expanded_stacked_pane_ids.push(pane.pid());
}
}
pub fn focus_tiled_pane(&self, tiled_panes: &mut TiledPanes) {
let mut panes_in_stack = vec![];
for pane_id in &self.expanded_stacked_pane_ids {
panes_in_stack.append(&mut tiled_panes.expand_pane_in_stack(*pane_id));
}
match self.pane_id_in_focused_location {
Some(pane_id_in_focused_location) => {
if self.refocus_pane {
tiled_panes.reapply_pane_focus();
if !panes_in_stack.contains(&pane_id_in_focused_location) {
// we do not change stacked panes locations because this has already been done above
tiled_panes.switch_active_pane_with(pane_id_in_focused_location);
}
} else {
tiled_panes.reapply_pane_focus();
}
},
None => {
tiled_panes.reapply_pane_focus();
},
}
for pane_id in &self.expanded_stacked_pane_ids {
tiled_panes.expand_pane_in_stack(*pane_id);
}
}
pub fn focus_floating_pane(
&self,
floating_panes: &mut FloatingPanes,
os_api: &mut Box<dyn ServerOsApi>,
) {
floating_panes.reapply_pane_focus();
if let Some(pane_id_in_focused_location) = self.pane_id_in_focused_location {
if self.refocus_pane {
floating_panes.switch_active_pane_with(os_api, pane_id_in_focused_location);
}
}
}
}