diff options
author | Aram Drevekenin <aram@poor.dev> | 2023-03-27 19:19:34 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-27 19:19:34 +0200 |
commit | 341f9eb8c8771a59b2e4d238ba49ba88c3720d6d (patch) | |
tree | 51205536dd0789efb770dbe0095af7210a60eed3 /zellij-server/src/tab | |
parent | 7b609b053f3aaf466258e12be53d57614c8884c7 (diff) |
feat(plugins): async plugin loading (#2327)
* work
* refactor(plugins): break down start plugin async function
* work
* loading messages
* nice ui
* floating panes and error handling
* cleanups and conflicting plugin/direction
* find exact pane when relayouting
* fix plugin pane titles
* kill loading tasks on exit
* refactor: move stuff around
* style(fmt): rustfmt
* various fixes and refactors
Diffstat (limited to 'zellij-server/src/tab')
-rw-r--r-- | zellij-server/src/tab/layout_applier.rs | 61 | ||||
-rw-r--r-- | zellij-server/src/tab/mod.rs | 146 |
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(¤t_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); |