summaryrefslogtreecommitdiffstats
path: root/zellij-server/src/tab
diff options
context:
space:
mode:
Diffstat (limited to 'zellij-server/src/tab')
-rw-r--r--zellij-server/src/tab/layout_applier.rs61
-rw-r--r--zellij-server/src/tab/mod.rs146
2 files changed, 203 insertions, 4 deletions
diff --git a/zellij-server/src/tab/layout_applier.rs b/zellij-server/src/tab/layout_applier.rs
index 7964b6989..04c22fe9a 100644
--- a/zellij-server/src/tab/layout_applier.rs
+++ b/zellij-server/src/tab/layout_applier.rs
@@ -14,7 +14,7 @@ use crate::{
ClientId,
};
use std::cell::RefCell;
-use std::collections::{BTreeMap, HashMap};
+use std::collections::{BTreeMap, HashMap, HashSet};
use std::rc::Rc;
use zellij_utils::{
data::{Palette, Style},
@@ -30,6 +30,7 @@ pub struct LayoutApplier<'a> {
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,
@@ -48,6 +49,7 @@ impl<'a> LayoutApplier<'a> {
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,
@@ -63,6 +65,7 @@ impl<'a> LayoutApplier<'a> {
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();
@@ -74,6 +77,7 @@ impl<'a> LayoutApplier<'a> {
terminal_emulator_colors,
terminal_emulator_color_codes,
character_cell_size,
+ connected_clients,
style,
display_area,
tiled_panes,
@@ -119,7 +123,32 @@ impl<'a> LayoutApplier<'a> {
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);
+ 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,
@@ -198,6 +227,7 @@ impl<'a> LayoutApplier<'a> {
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(),
);
@@ -300,6 +330,7 @@ impl<'a> LayoutApplier<'a> {
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(),
);
@@ -574,6 +605,22 @@ impl ExistingTabState {
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>,
@@ -679,6 +726,18 @@ impl ExistingTabState {
.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)]
diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs
index a2eb79203..1c1a77ac8 100644
--- a/zellij-server/src/tab/mod.rs
+++ b/zellij-server/src/tab/mod.rs
@@ -18,7 +18,7 @@ use zellij_utils::{position::Position, serde};
use crate::background_jobs::BackgroundJob;
use crate::pty_writer::PtyWriteInstruction;
use crate::screen::CopyOptions;
-use crate::ui::pane_boundaries_frame::FrameParams;
+use crate::ui::{loading_indication::LoadingIndication, pane_boundaries_frame::FrameParams};
use layout_applier::LayoutApplier;
use swap_layouts::SwapLayouts;
@@ -28,7 +28,7 @@ use crate::{
output::{CharacterChunk, Output, SixelImageChunk},
panes::sixel::SixelImageStore,
panes::{FloatingPanes, TiledPanes},
- panes::{LinkHandler, PaneId, TerminalPane},
+ panes::{LinkHandler, PaneId, PluginPane, TerminalPane},
plugins::PluginInstruction,
pty::{ClientOrTabIndex, PtyInstruction, VteBytes},
thread_bus::ThreadSenders,
@@ -438,6 +438,8 @@ pub trait Pane {
fn frame_color_override(&self) -> Option<PaletteColor>;
fn invoked_with(&self) -> &Option<Run>;
fn set_title(&mut self, title: String);
+ fn update_loading_indication(&mut self, _loading_indication: LoadingIndication) {} // only relevant for plugins
+ fn progress_animation_offset(&mut self) {} // only relevant for plugins
}
#[derive(Clone, Debug)]
@@ -599,6 +601,7 @@ impl Tab {
&self.terminal_emulator_colors,
&self.terminal_emulator_color_codes,
&self.character_cell_size,
+ &self.connected_clients,
&self.style,
&self.display_area,
&mut self.tiled_panes,
@@ -657,6 +660,7 @@ impl Tab {
&self.terminal_emulator_colors,
&self.terminal_emulator_color_codes,
&self.character_cell_size,
+ &self.connected_clients,
&self.style,
&self.display_area,
&mut self.tiled_panes,
@@ -709,6 +713,7 @@ impl Tab {
&self.terminal_emulator_colors,
&self.terminal_emulator_color_codes,
&self.character_cell_size,
+ &self.connected_clients,
&self.style,
&self.display_area,
&mut self.tiled_panes,
@@ -1104,6 +1109,114 @@ impl Tab {
}
Ok(())
}
+ pub fn new_plugin_pane(
+ &mut self,
+ pid: PaneId,
+ initial_pane_title: String,
+ should_float: Option<bool>,
+ run_plugin: Run,
+ client_id: Option<ClientId>,
+ ) -> Result<()> {
+ let err_context = || format!("failed to create new pane with id {pid:?}");
+
+ match should_float {
+ Some(true) => self.show_floating_panes(),
+ Some(false) => self.hide_floating_panes(),
+ None => {},
+ };
+ if self.floating_panes.panes_are_visible() {
+ if let Some(new_pane_geom) = self.floating_panes.find_room_for_new_pane() {
+ if let PaneId::Plugin(plugin_pid) = pid {
+ let mut new_pane = PluginPane::new(
+ plugin_pid,
+ new_pane_geom,
+ self.senders
+ .to_plugin
+ .as_ref()
+ .with_context(err_context)?
+ .clone(),
+ initial_pane_title,
+ String::new(),
+ 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,
+ Some(run_plugin),
+ );
+ new_pane.set_active_at(Instant::now());
+ new_pane.set_content_offset(Offset::frame(1)); // floating panes always have a frame
+ resize_pty!(
+ new_pane,
+ self.os_api,
+ self.senders,
+ self.character_cell_size
+ )
+ .with_context(err_context)?;
+ self.floating_panes.add_pane(pid, Box::new(new_pane));
+ self.floating_panes.focus_pane_for_all_clients(pid);
+ }
+ if self.auto_layout && !self.swap_layouts.is_floating_damaged() {
+ // only do this if we're already in this layout, otherwise it might be
+ // confusing and not what the user intends
+ self.swap_layouts.set_is_floating_damaged(); // we do this so that we won't skip to the
+ // next layout
+ self.next_swap_layout(client_id, true)?;
+ }
+ }
+ } else {
+ if self.tiled_panes.fullscreen_is_active() {
+ self.tiled_panes.unset_fullscreen();
+ }
+ let should_auto_layout = self.auto_layout && !self.swap_layouts.is_tiled_damaged();
+ if self.tiled_panes.has_room_for_new_pane() {
+ if let PaneId::Plugin(plugin_pid) = pid {
+ let mut new_pane = PluginPane::new(
+ plugin_pid,
+ PaneGeom::default(), // the initial size will be set later
+ self.senders
+ .to_plugin
+ .as_ref()
+ .with_context(err_context)?
+ .clone(),
+ initial_pane_title,
+ String::new(),
+ 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,
+ Some(run_plugin),
+ );
+ new_pane.set_active_at(Instant::now());
+ if should_auto_layout {
+ // no need to relayout here, we'll do it when reapplying the swap layout
+ // below
+ self.tiled_panes
+ .insert_pane_without_relayout(pid, Box::new(new_pane));
+ } else {
+ self.tiled_panes.insert_pane(pid, Box::new(new_pane));
+ }
+ self.should_clear_display_before_rendering = true;
+ if let Some(client_id) = client_id {
+ self.tiled_panes.focus_pane(pid, client_id);
+ }
+ }
+ }
+ if should_auto_layout {
+ // only do this if we're already in this layout, otherwise it might be
+ // confusing and not what the user intends
+ self.swap_layouts.set_is_tiled_damaged(); // we do this so that we won't skip to the
+ // next layout
+ self.next_swap_layout(client_id, true)?;
+ }
+ }
+ Ok(())
+ }
pub fn suppress_active_pane(&mut self, pid: PaneId, client_id: ClientId) -> Result<()> {
// this method creates a new pane from pid and replaces it with the active pane
// the active pane is then suppressed (hidden and not rendered) until the current
@@ -3220,7 +3333,34 @@ impl Tab {
pane.clear_pane_frame_color_override();
}
}
-
+ pub fn update_plugin_loading_stage(&mut self, pid: u32, loading_indication: LoadingIndication) {
+ if let Some(plugin_pane) = self
+ .tiled_panes
+ .get_pane_mut(PaneId::Plugin(pid))
+ .or_else(|| self.floating_panes.get_pane_mut(PaneId::Plugin(pid)))
+ .or_else(|| {
+ self.suppressed_panes
+ .values_mut()
+ .find(|s_p| s_p.pid() == PaneId::Plugin(pid))
+ })
+ {
+ plugin_pane.update_loading_indication(loading_indication);
+ }
+ }
+ pub fn progress_plugin_loading_offset(&mut self, pid: u32) {
+ if let Some(plugin_pane) = self
+ .tiled_panes
+ .get_pane_mut(PaneId::Plugin(pid))
+ .or_else(|| self.floating_panes.get_pane_mut(PaneId::Plugin(pid)))
+ .or_else(|| {
+ self.suppressed_panes
+ .values_mut()
+ .find(|s_p| s_p.pid() == PaneId::Plugin(pid))
+ })
+ {
+ plugin_pane.progress_animation_offset();
+ }
+ }
fn show_floating_panes(&mut self) {
// this function is to be preferred to directly invoking floating_panes.toggle_show_panes(true)
self.floating_panes.toggle_show_panes(true);