aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-server/src
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2023-06-07 12:43:35 +0200
committerGitHub <noreply@github.com>2023-06-07 12:43:35 +0200
commitc11d75f9157873fc99fe0d40933de8ec5fbb4f6b (patch)
treefc55dc7f9132395585613dd9cce94c98c8c76600 /zellij-server/src
parentdocs(changelog): fix cwd of newtab action (diff)
downloadsl0ppij-c11d75f9157873fc99fe0d40933de8ec5fbb4f6b.tar.gz
sl0ppij-c11d75f9157873fc99fe0d40933de8ec5fbb4f6b.tar.xz
sl0ppij-c11d75f9157873fc99fe0d40933de8ec5fbb4f6b.zip
feat(wasm-plugin-system): major overhaul and some goodies (#2510)
* strider resiliency * worker channel prototype * finalized ui * show hide plugin * fs events to plugins * tests for events and new screen instructions * various refactoringz * report plugin errors instead of crashing zellij * fix plugin loading with workers * refactor: move watch filesystem * some fixes and refactoring * refactor(panes): combine pane insertion logic * refactor(screen): launch or focus * refactor(pty): consolidate default shell fetching * refactor: various cleanups * initial refactoring * more initial refactoring * refactor(strider): search * style(fmt): rustfmt * style(pty): cleanup * style(clippy): ok clippy * style(fmt): rustfmt
Diffstat (limited to 'zellij-server/src')
-rw-r--r--zellij-server/src/lib.rs9
-rw-r--r--zellij-server/src/panes/floating_panes/mod.rs17
-rw-r--r--zellij-server/src/panes/plugin_pane.rs2
-rw-r--r--zellij-server/src/panes/tiled_panes/mod.rs20
-rw-r--r--zellij-server/src/plugins/mod.rs13
-rw-r--r--zellij-server/src/plugins/plugin_loader.rs43
-rw-r--r--zellij-server/src/plugins/plugin_map.rs70
-rw-r--r--zellij-server/src/plugins/plugin_worker.rs89
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs82
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__can_subscribe_to_hd_events.snap12
-rw-r--r--zellij-server/src/plugins/wasm_bridge.rs150
-rw-r--r--zellij-server/src/plugins/watch_filesystem.rs63
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs123
-rw-r--r--zellij-server/src/pty.rs14
-rw-r--r--zellij-server/src/route.rs10
-rw-r--r--zellij-server/src/screen.rs77
-rw-r--r--zellij-server/src/tab/mod.rs399
-rw-r--r--zellij-server/src/tab/unit/tab_integration_tests.rs457
-rw-r--r--zellij-server/src/tab/unit/tab_tests.rs26
-rw-r--r--zellij-server/src/ui/loading_indication.rs16
-rw-r--r--zellij-server/src/unit/screen_tests.rs156
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-2.snap26
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-3.snap6
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane.snap26
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_launch_or_focus_plugin_action.snap25
25 files changed, 1297 insertions, 634 deletions
diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs
index 166a60c5..4e491142 100644
--- a/zellij-server/src/lib.rs
+++ b/zellij-server/src/lib.rs
@@ -32,7 +32,7 @@ use wasmer::Store;
use crate::{
os_input_output::ServerOsApi,
plugins::{plugin_thread_main, PluginInstruction},
- pty::{pty_thread_main, Pty, PtyInstruction},
+ pty::{get_default_shell, pty_thread_main, Pty, PtyInstruction},
screen::{screen_thread_main, ScreenInstruction},
thread_bus::{Bus, ThreadSenders},
};
@@ -705,6 +705,10 @@ fn init_session(
..Default::default()
})
});
+ let path_to_default_shell = config_options
+ .default_shell
+ .clone()
+ .unwrap_or_else(|| get_default_shell());
let pty_thread = thread::Builder::new()
.name("pty".to_string())
@@ -757,6 +761,7 @@ fn init_session(
})
.unwrap();
+ let zellij_cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
let plugin_thread = thread::Builder::new()
.name("wasm".to_string())
.spawn({
@@ -780,6 +785,8 @@ fn init_session(
data_dir,
plugins.unwrap_or_default(),
layout,
+ path_to_default_shell,
+ zellij_cwd,
)
.fatal()
}
diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs
index a85cf80f..071f6e7c 100644
--- a/zellij-server/src/panes/floating_panes/mod.rs
+++ b/zellij-server/src/panes/floating_panes/mod.rs
@@ -25,7 +25,7 @@ use zellij_utils::{
data::{ModeInfo, Style},
errors::prelude::*,
input::command::RunCommand,
- input::layout::FloatingPaneLayout,
+ input::layout::{FloatingPaneLayout, Run, RunPlugin},
pane_size::{Dimension, Offset, PaneGeom, Size, SizeInPixels, Viewport},
};
@@ -870,4 +870,19 @@ impl FloatingPanes {
self.focus_pane_for_all_clients(active_pane_id);
}
}
+ pub fn get_plugin_pane_id(&self, run_plugin: &RunPlugin) -> Option<PaneId> {
+ let run = Some(Run::Plugin(run_plugin.clone()));
+ self.panes
+ .iter()
+ .find(|(_id, s_p)| s_p.invoked_with() == &run)
+ .map(|(id, _)| *id)
+ }
+ pub fn focus_pane_if_exists(&mut self, pane_id: PaneId, client_id: ClientId) -> Result<()> {
+ if self.panes.get(&pane_id).is_some() {
+ self.focus_pane(pane_id, client_id);
+ Ok(())
+ } else {
+ Err(anyhow!("Pane not found"))
+ }
+ }
}
diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs
index 7915069c..28fdb3d1 100644
--- a/zellij-server/src/panes/plugin_pane.rs
+++ b/zellij-server/src/panes/plugin_pane.rs
@@ -537,7 +537,7 @@ impl Pane for PluginPane {
self.pane_title = title;
}
fn update_loading_indication(&mut self, loading_indication: LoadingIndication) {
- if self.loading_indication.ended {
+ if self.loading_indication.ended && !loading_indication.is_error() {
return;
}
self.loading_indication.merge(loading_indication);
diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs
index 08a633b0..3734214a 100644
--- a/zellij-server/src/panes/tiled_panes/mod.rs
+++ b/zellij-server/src/panes/tiled_panes/mod.rs
@@ -20,7 +20,10 @@ use stacked_panes::StackedPanes;
use zellij_utils::{
data::{Direction, ModeInfo, ResizeStrategy, Style},
errors::prelude::*,
- input::{command::RunCommand, layout::SplitDirection},
+ input::{
+ command::RunCommand,
+ layout::{Run, RunPlugin, SplitDirection},
+ },
pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport},
};
@@ -529,6 +532,14 @@ impl TiledPanes {
}
self.reset_boundaries();
}
+ pub fn focus_pane_if_exists(&mut self, pane_id: PaneId, client_id: ClientId) -> Result<()> {
+ if self.panes.get(&pane_id).is_some() {
+ self.focus_pane(pane_id, client_id);
+ Ok(())
+ } else {
+ Err(anyhow!("Pane not found"))
+ }
+ }
pub fn focus_pane_at_position(&mut self, position_and_size: PaneGeom, client_id: ClientId) {
if let Some(pane_id) = self
.panes
@@ -1691,6 +1702,13 @@ impl TiledPanes {
fn reset_boundaries(&mut self) {
self.client_id_to_boundaries.clear();
}
+ pub fn get_plugin_pane_id(&self, run_plugin: &RunPlugin) -> Option<PaneId> {
+ let run = Some(Run::Plugin(run_plugin.clone()));
+ self.panes
+ .iter()
+ .find(|(_id, s_p)| s_p.invoked_with() == &run)
+ .map(|(id, _)| *id)
+ }
}
#[allow(clippy::borrowed_box)]
diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs
index 9384b3f6..7b322b05 100644
--- a/zellij-server/src/plugins/mod.rs
+++ b/zellij-server/src/plugins/mod.rs
@@ -1,6 +1,8 @@
mod plugin_loader;
mod plugin_map;
+mod plugin_worker;
mod wasm_bridge;
+mod watch_filesystem;
mod zellij_exports;
use log::info;
use std::{collections::HashMap, fs, path::PathBuf};
@@ -104,13 +106,22 @@ pub(crate) fn plugin_thread_main(
data_dir: PathBuf,
plugins: PluginsConfig,
layout: Box<Layout>,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<()> {
info!("Wasm main thread starts");
let plugin_dir = data_dir.join("plugins/");
let plugin_global_data_dir = plugin_dir.join("data");
- let mut wasm_bridge = WasmBridge::new(plugins, bus.senders.clone(), store, plugin_dir);
+ let mut wasm_bridge = WasmBridge::new(
+ plugins,
+ bus.senders.clone(),
+ store,
+ plugin_dir,
+ path_to_default_shell,
+ zellij_cwd,
+ );
loop {
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
diff --git a/zellij-server/src/plugins/plugin_loader.rs b/zellij-server/src/plugins/plugin_loader.rs
index 91eb6c93..4d77bd9e 100644
--- a/zellij-server/src/plugins/plugin_loader.rs
+++ b/zellij-server/src/plugins/plugin_loader.rs
@@ -1,6 +1,5 @@
-use crate::plugins::plugin_map::{
- PluginEnv, PluginMap, RunningPlugin, RunningWorker, Subscriptions,
-};
+use crate::plugins::plugin_map::{PluginEnv, PluginMap, RunningPlugin, Subscriptions};
+use crate::plugins::plugin_worker::{plugin_worker, RunningWorker};
use crate::plugins::zellij_exports::{wasi_read_string, zellij_exports};
use crate::plugins::PluginId;
use highway::{HighwayHash, PortableHash};
@@ -164,6 +163,8 @@ pub struct PluginLoader<'a> {
plugin_own_data_dir: PathBuf,
size: Size,
wasm_blob_on_hd: Option<(Vec<u8>, PathBuf)>,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
}
impl<'a> PluginLoader<'a> {
@@ -176,6 +177,8 @@ impl<'a> PluginLoader<'a> {
plugin_map: Arc<Mutex<PluginMap>>,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<()> {
let err_context = || format!("failed to reload plugin {plugin_id} from memory");
let mut connected_clients: Vec<ClientId> =
@@ -194,6 +197,8 @@ impl<'a> PluginLoader<'a> {
first_client_id,
&store,
&plugin_dir,
+ path_to_default_shell,
+ zellij_cwd,
)?;
plugin_loader
.load_module_from_memory()
@@ -227,6 +232,8 @@ impl<'a> PluginLoader<'a> {
size: Size,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<()> {
let err_context = || format!("failed to start plugin {plugin_id} for client {client_id}");
let mut plugin_loader = PluginLoader::new(
@@ -240,6 +247,8 @@ impl<'a> PluginLoader<'a> {
&plugin_dir,
tab_index,
size,
+ path_to_default_shell,
+ zellij_cwd,
)?;
plugin_loader
.load_module_from_memory()
@@ -273,6 +282,8 @@ impl<'a> PluginLoader<'a> {
plugin_map: Arc<Mutex<PluginMap>>,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<()> {
let mut new_plugins = HashSet::new();
for plugin_id in plugin_map.lock().unwrap().plugin_ids() {
@@ -288,6 +299,8 @@ impl<'a> PluginLoader<'a> {
existing_client_id,
&store,
&plugin_dir,
+ path_to_default_shell.clone(),
+ zellij_cwd.clone(),
)?;
plugin_loader
.load_module_from_memory()
@@ -314,6 +327,8 @@ impl<'a> PluginLoader<'a> {
plugin_map: Arc<Mutex<PluginMap>>,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<()> {
let err_context = || format!("failed to reload plugin id {plugin_id}");
@@ -333,6 +348,8 @@ impl<'a> PluginLoader<'a> {
first_client_id,
&store,
&plugin_dir,
+ path_to_default_shell,
+ zellij_cwd,
)?;
plugin_loader
.compile_module()
@@ -363,6 +380,8 @@ impl<'a> PluginLoader<'a> {
plugin_dir: &'a PathBuf,
tab_index: usize,
size: Size,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<Self> {
let plugin_own_data_dir = ZELLIJ_SESSION_CACHE_DIR
.join(Url::from(&plugin.location).to_string())
@@ -383,6 +402,8 @@ impl<'a> PluginLoader<'a> {
plugin_own_data_dir,
size,
wasm_blob_on_hd: None,
+ path_to_default_shell,
+ zellij_cwd,
})
}
pub fn new_from_existing_plugin_attributes(
@@ -394,6 +415,8 @@ impl<'a> PluginLoader<'a> {
client_id: ClientId,
store: &Store,
plugin_dir: &'a PathBuf,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<Self> {
let err_context = || "Failed to find existing plugin";
let (running_plugin, _subscriptions, _workers) = {
@@ -421,6 +444,8 @@ impl<'a> PluginLoader<'a> {
plugin_dir,
tab_index,
size,
+ path_to_default_shell,
+ zellij_cwd,
)
}
pub fn new_from_different_client_id(
@@ -432,6 +457,8 @@ impl<'a> PluginLoader<'a> {
client_id: ClientId,
store: &Store,
plugin_dir: &'a PathBuf,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<Self> {
let err_context = || "Failed to find existing plugin";
let running_plugin = {
@@ -460,6 +487,8 @@ impl<'a> PluginLoader<'a> {
plugin_dir,
tab_index,
size,
+ path_to_default_shell,
+ zellij_cwd,
)
}
pub fn load_module_from_memory(&mut self) -> Result<Module> {
@@ -625,7 +654,8 @@ impl<'a> PluginLoader<'a> {
let worker =
RunningWorker::new(instance, &function_name, plugin_config, plugin_env);
- workers.insert(function_name.into(), Arc::new(Mutex::new(worker)));
+ let worker_sender = plugin_worker(worker);
+ workers.insert(function_name.into(), worker_sender);
}
}
start_function.call(&[]).with_context(err_context)?;
@@ -689,6 +719,8 @@ impl<'a> PluginLoader<'a> {
*client_id,
&self.store,
&self.plugin_dir,
+ self.path_to_default_shell.clone(),
+ self.zellij_cwd.clone(),
)?;
plugin_loader_for_client
.load_module_from_memory()
@@ -746,7 +778,7 @@ impl<'a> PluginLoader<'a> {
};
let mut wasi_env = WasiState::new("Zellij")
.env("CLICOLOR_FORCE", "1")
- .map_dir("/host", ".")
+ .map_dir("/host", self.zellij_cwd.clone())
.and_then(|wasi| wasi.map_dir("/data", &self.plugin_own_data_dir))
.and_then(|wasi| wasi.map_dir("/tmp", ZELLIJ_TMP_DIR.as_path()))
.and_then(|wasi| {
@@ -771,6 +803,7 @@ impl<'a> PluginLoader<'a> {
wasi_env,
plugin_own_data_dir: self.plugin_own_data_dir.clone(),
tab_index: self.tab_index,
+ path_to_default_shell: self.path_to_default_shell.clone(),
};
let subscriptions = Arc::new(Mutex::new(HashSet::new()));
diff --git a/zellij-server/src/plugins/plugin_map.rs b/zellij-server/src/plugins/plugin_map.rs
index 0c3df931..040c9f9f 100644
--- a/zellij-server/src/plugins/plugin_map.rs
+++ b/zellij-server/src/plugins/plugin_map.rs
@@ -1,5 +1,4 @@
-use crate::plugins::plugin_loader::{PluginLoader, VersionMismatchError};
-use crate::plugins::zellij_exports::wasi_write_object;
+use crate::plugins::plugin_worker::MessageToWorker;
use crate::plugins::PluginId;
use std::{
collections::{HashMap, HashSet},
@@ -11,10 +10,10 @@ use wasmer_wasi::WasiEnv;
use crate::{thread_bus::ThreadSenders, ClientId};
+use zellij_utils::async_channel::Sender;
use zellij_utils::errors::prelude::*;
use zellij_utils::{
- consts::VERSION, data::EventType, input::layout::RunPluginLocation,
- input::plugins::PluginConfig,
+ data::EventType, input::layout::RunPluginLocation, input::plugins::PluginConfig,
};
// the idea here is to provide atomicity when adding/removing plugins from the map (eg. when a new
@@ -29,7 +28,7 @@ pub struct PluginMap {
(
Arc<Mutex<RunningPlugin>>,
Arc<Mutex<Subscriptions>>,
- HashMap<String, Arc<Mutex<RunningWorker>>>,
+ HashMap<String, Sender<MessageToWorker>>,
),
>,
}
@@ -41,7 +40,7 @@ impl PluginMap {
) -> Vec<(
Arc<Mutex<RunningPlugin>>,
Arc<Mutex<Subscriptions>>,
- HashMap<String, Arc<Mutex<RunningWorker>>>,
+ HashMap<String, Sender<MessageToWorker>>,
)> {
let mut removed = vec![];
let ids_in_plugin_map: Vec<(PluginId, ClientId)> =
@@ -62,7 +61,7 @@ impl PluginMap {
) -> Option<(
Arc<Mutex<RunningPlugin>>,
Arc<Mutex<Subscriptions>>,
- HashMap<String, Arc<Mutex<RunningWorker>>>,
+ HashMap<String, Sender<MessageToWorker>>,
)> {
self.plugin_assets.remove(&(plugin_id, client_id))
}
@@ -132,12 +131,12 @@ impl PluginMap {
.and_then(|(_, (running_plugin, _, _))| Some(running_plugin.clone())),
}
}
- pub fn clone_worker(
+ pub fn worker_sender(
&self,
plugin_id: PluginId,
client_id: ClientId,
worker_name: &str,
- ) -> Option<Arc<Mutex<RunningWorker>>> {
+ ) -> Option<Sender<MessageToWorker>> {
self.plugin_assets
.iter()
.find(|((p_id, c_id), _)| p_id == &plugin_id && c_id == &client_id)
@@ -174,7 +173,7 @@ impl PluginMap {
client_id: ClientId,
running_plugin: Arc<Mutex<RunningPlugin>>,
subscriptions: Arc<Mutex<Subscriptions>>,
- running_workers: HashMap<String, Arc<Mutex<RunningWorker>>>,
+ running_workers: HashMap<String, Sender<MessageToWorker>>,
) {
self.plugin_assets.insert(
(plugin_id, client_id),
@@ -195,6 +194,7 @@ pub struct PluginEnv {
pub client_id: ClientId,
#[allow(dead_code)]
pub plugin_own_data_dir: PathBuf,
+ pub path_to_default_shell: PathBuf,
}
impl PluginEnv {
@@ -256,53 +256,3 @@ impl RunningPlugin {
}
}
}
-
-pub struct RunningWorker {
- pub instance: Instance,
- pub name: String,
- pub plugin_config: PluginConfig,
- pub plugin_env: PluginEnv,
-}
-
-impl RunningWorker {
- pub fn new(
- instance: Instance,
- name: &str,
- plugin_config: PluginConfig,
- plugin_env: PluginEnv,
- ) -> Self {
- RunningWorker {
- instance,
- name: name.into(),
- plugin_config,
- plugin_env,
- }
- }
- pub fn send_message(&self, message: String, payload: String) -> Result<()> {
- let err_context = || format!("Failed to send message to worker");
-
- let work_function = self
- .instance
- .exports
- .get_function(&self.name)
- .with_context(err_context)?;
- wasi_write_object(&self.plugin_env.wasi_env, &(message, payload))
- .with_context(err_context)?;
- work_function.call(&[]).or_else::<anyError, _>(|e| {
- match e.downcast::<serde_json::Error>() {
- Ok(_) => panic!(
- "{}",
- anyError::new(VersionMismatchError::new(
- VERSION,
- "Unavailable",
- &self.plugin_config.path,
- self.plugin_config.is_builtin(),
- ))
- ),
- Err(e) => Err(e).with_context(err_context),
- }
- })?;
-
- Ok(())
- }
-}
diff --git a/zellij-server/src/plugins/plugin_worker.rs b/zellij-server/src/plugins/plugin_worker.rs
new file mode 100644
index 00000000..bc7303c7
--- /dev/null
+++ b/zellij-server/src/plugins/plugin_worker.rs
@@ -0,0 +1,89 @@
+use crate::plugins::plugin_loader::VersionMismatchError;
+use crate::plugins::plugin_map::PluginEnv;
+use crate::plugins::zellij_exports::wasi_write_object;
+use wasmer::Instance;
+
+use zellij_utils::async_channel::{unbounded, Receiver, Sender};
+use zellij_utils::async_std::task;
+use zellij_utils::errors::prelude::*;
+use zellij_utils::{consts::VERSION, input::plugins::PluginConfig};
+
+pub struct RunningWorker {
+ pub instance: Instance,
+ pub name: String,
+ pub plugin_config: PluginConfig,
+ pub plugin_env: PluginEnv,
+}
+
+impl RunningWorker {
+ pub fn new(
+ instance: Instance,
+ name: &str,
+ plugin_config: PluginConfig,
+ plugin_env: PluginEnv,
+ ) -> Self {
+ RunningWorker {
+ instance,
+ name: name.into(),
+ plugin_config,
+ plugin_env,
+ }
+ }
+ pub fn send_message(&self, message: String, payload: String) -> Result<()> {
+ let err_context = || format!("Failed to send message to worker");
+
+ let work_function = self
+ .instance
+ .exports
+ .get_function(&self.name)
+ .with_context(err_context)?;
+ wasi_write_object(&self.plugin_env.wasi_env, &(message, payload))
+ .with_context(err_context)?;
+ work_function.call(&[]).or_else::<anyError, _>(|e| {
+ match e.downcast::<serde_json::Error>() {
+ Ok(_) => panic!(
+ "{}",
+ anyError::new(VersionMismatchError::new(
+ VERSION,
+ "Unavailable",
+ &self.plugin_config.path,
+ self.plugin_config.is_builtin(),
+ ))
+ ),
+ Err(e) => Err(e).with_context(err_context),
+ }
+ })?;
+
+ Ok(())
+ }
+}
+
+pub enum MessageToWorker {
+ Message(String, String), // message, payload
+ Exit,
+}
+
+pub fn plugin_worker(worker: RunningWorker) -> Sender<MessageToWorker> {
+ let (sender, receiver): (Sender<MessageToWorker>, Receiver<MessageToWorker>) = unbounded();
+ task::spawn({
+ async move {
+ loop {
+ match receiver.recv().await {
+ Ok(MessageToWorker::Message(message, payload)) => {
+ if let Err(e) = worker.send_message(message, payload) {
+ log::error!("Failed to send message to worker: {:?}", e);
+ }
+ },
+ Ok(MessageToWorker::Exit) => {
+ break;
+ },
+ Err(e) => {
+ log::error!("Failed to receive worker message on channel: {:?}", e);
+ break;
+ },
+ }
+ }
+ }
+ });
+ sender
+}
diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs
index d1e79a5f..d41a477b 100644
--- a/zellij-server/src/plugins/unit/plugin_tests.rs
+++ b/zellij-server/src/plugins/unit/plugin_tests.rs
@@ -50,11 +50,14 @@ macro_rules! log_actions_in_thread {
};
}
-fn create_plugin_thread() -> (
+fn create_plugin_thread(
+ zellij_cwd: Option<PathBuf>,
+) -> (
SenderWithContext<PluginInstruction>,
Receiver<(ScreenInstruction, ErrorContext)>,
Box<dyn FnMut()>,
) {
+ let zellij_cwd = zellij_cwd.unwrap_or_else(|| PathBuf::from("."));
let (to_server, _server_receiver): ChannelWithContext<ServerInstruction> =
channels::bounded(50);
let to_server = SenderWithContext::new(to_server);
@@ -88,6 +91,7 @@ fn create_plugin_thread() -> (
.should_silently_fail();
let store = Store::new(&wasmer::Universal::new(wasmer::Singlepass::default()).engine());
let data_dir = PathBuf::from(tempdir().unwrap().path());
+ let default_shell = PathBuf::from(".");
let _plugin_thread = std::thread::Builder::new()
.name("plugin_thread".to_string())
.spawn(move || {
@@ -98,6 +102,8 @@ fn create_plugin_thread() -> (
data_dir,
PluginsConfig::default(),
Box::new(Layout::default()),
+ default_shell,
+ zellij_cwd,
)
.expect("TEST")
})
@@ -134,7 +140,7 @@ pub fn load_new_plugin_from_hd() {
// message (this is what the fixture plugin does)
// we then listen on our mock screen receiver to make sure we got a PluginBytes instruction
// that contains said render, and assert against it
- let (plugin_thread_sender, screen_receiver, mut teardown) = create_plugin_thread();
+ let (plugin_thread_sender, screen_receiver, mut teardown) = create_plugin_thread(None);
let plugin_should_float = Some(false);
let plugin_title = Some("test_plugin".to_owned());
let run_plugin = RunPlugin {
@@ -192,7 +198,7 @@ pub fn load_new_plugin_from_hd() {
#[test]
#[ignore]
pub fn plugin_workers() {
- let (plugin_thread_sender, screen_receiver, mut teardown) = create_plugin_thread();
+ let (plugin_thread_sender, screen_receiver, mut teardown) = create_plugin_thread(None);
let plugin_should_float = Some(false);
let plugin_title = Some("test_plugin".to_owned());
let run_plugin = RunPlugin {
@@ -253,7 +259,7 @@ pub fn plugin_workers() {
#[test]
#[ignore]
pub fn plugin_workers_persist_state() {
- let (plugin_thread_sender, screen_receiver, mut teardown) = create_plugin_thread();
+ let (plugin_thread_sender, screen_receiver, mut teardown) = create_plugin_thread(None);
let plugin_should_float = Some(false);
let plugin_title = Some("test_plugin".to_owned());
let run_plugin = RunPlugin {
@@ -318,3 +324,71 @@ pub fn plugin_workers_persist_state() {
});
assert_snapshot!(format!("{:#?}", plugin_bytes_event));
}
+
+#[test]
+#[ignore]
+pub fn can_subscribe_to_hd_events() {
+ let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
+ // destructor removes the directory
+ let plugin_host_folder = PathBuf::from(temp_folder.path());
+ let (plugin_thread_sender, screen_receiver, mut teardown) =
+ create_plugin_thread(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ };
+ let tab_index = 1;
+ let client_id = 1;
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let received_screen_instructions = Arc::new(Mutex::new(vec![]));
+ let screen_thread = log_actions_in_thread!(
+ received_screen_instructions,
+ ScreenInstruction::PluginBytes,
+ screen_receiver,
+ 3
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ client_id,
+ size,
+ ));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::InputReceived,
+ )])); // will be cached and sent to the plugin once it's loaded
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ std::fs::OpenOptions::new()
+ .create(true)
+ .write(true)
+ .open(PathBuf::from(temp_folder.path()).join("test1"))
+ .unwrap();
+ screen_thread.join().unwrap(); // this might take a while if the cache is cold
+ teardown();
+ let plugin_bytes_event = received_screen_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let ScreenInstruction::PluginBytes(plugin_bytes) = i {
+ for (plugin_id, client_id, plugin_bytes) in plugin_bytes {
+ let plugin_bytes = String::from_utf8_lossy(plugin_bytes).to_string();
+ if plugin_bytes.contains("FileSystem") {
+ return Some((*plugin_id, *client_id, plugin_bytes));
+ }
+ }
+ }
+ None
+ });
+ assert_snapshot!(format!("{:#?}", plugin_bytes_event));
+}
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__can_subscribe_to_hd_events.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__can_subscribe_to_hd_events.snap
new file mode 100644
index 00000000..57e31f15
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__can_subscribe_to_hd_events.snap
@@ -0,0 +1,12 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 387
+expression: "format!(\"{:#?}\", plugin_bytes_event)"
+---
+Some(
+ (
+ 0,
+ 1,
+ "Rows: 20, Cols: 121, Received events: [InputReceived, FileSystemRead([\"/host/test1\"])]\n\r",
+ ),
+)
diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs
index 0015552c..1ede55e2 100644
--- a/zellij-server/src/plugins/wasm_bridge.rs
+++ b/zellij-server/src/plugins/wasm_bridge.rs
@@ -1,24 +1,24 @@
use super::{PluginId, PluginInstruction};
use crate::plugins::plugin_loader::{PluginLoader, VersionMismatchError};
-use crate::plugins::plugin_map::{
- AtomicEvent, PluginEnv, PluginMap, RunningPlugin, RunningWorker, Subscriptions,
-};
+use crate::plugins::plugin_map::{AtomicEvent, PluginEnv, PluginMap, RunningPlugin, Subscriptions};
+use crate::plugins::plugin_worker::MessageToWorker;
+use crate::plugins::watch_filesystem::watch_filesystem;
use crate::plugins::zellij_exports::{wasi_read_string, wasi_write_object};
use log::info;
use std::{
collections::{HashMap, HashSet},
path::PathBuf,
str::FromStr,
- sync::{Arc, Mutex, TryLockError},
+ sync::{Arc, Mutex},
};
use wasmer::{Instance, Module, Store, Value};
use zellij_utils::async_std::task::{self, JoinHandle};
+use zellij_utils::notify::{RecommendedWatcher, Watcher};
use crate::{
background_jobs::BackgroundJob, screen::ScreenInstruction, thread_bus::ThreadSenders,
ui::loading_indication::LoadingIndication, ClientId,
};
-
use zellij_utils::{
consts::VERSION,
data::{Event, EventType},
@@ -30,8 +30,6 @@ use zellij_utils::{
pane_size::Size,
};
-const RETRY_INTERVAL_MS: u64 = 100;
-
pub struct WasmBridge {
connected_clients: Arc<Mutex<Vec<ClientId>>>,
plugins: PluginsConfig,
@@ -49,6 +47,9 @@ pub struct WasmBridge {
// payload>
loading_plugins: HashMap<(PluginId, RunPlugin), JoinHandle<()>>, // plugin_id to join-handle
pending_plugin_reloads: HashSet<RunPlugin>,
+ path_to_default_shell: PathBuf,
+ watcher: Option<RecommendedWatcher>,
+ zellij_cwd: PathBuf,
}
impl WasmBridge {
@@ -57,11 +58,20 @@ impl WasmBridge {
senders: ThreadSenders,
store: Store,
plugin_dir: PathBuf,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Self {
let plugin_map = Arc::new(Mutex::new(PluginMap::default()));
let connected_clients: Arc<Mutex<Vec<ClientId>>> = Arc::new(Mutex::new(vec![]));
let plugin_cache: Arc<Mutex<HashMap<PathBuf, Module>>> =
Arc::new(Mutex::new(HashMap::new()));
+ let watcher = match watch_filesystem(senders.clone(), &zellij_cwd) {
+ Ok(watcher) => Some(watcher),
+ Err(e) => {
+ log::error!("Failed to watch filesystem: {:?}", e);
+ None
+ },
+ };
WasmBridge {
connected_clients,
plugins,
@@ -70,12 +80,15 @@ impl WasmBridge {
plugin_dir,
plugin_cache,
plugin_map,
+ path_to_default_shell,
+ watcher,
next_plugin_id: 0,
cached_events_for_pending_plugins: HashMap::new(),
cached_resizes_for_pending_plugins: HashMap::new(),
cached_worker_messages: HashMap::new(),
loading_plugins: HashMap::new(),
pending_plugin_reloads: HashSet::new(),
+ zellij_cwd,
}
}
pub fn load_plugin(
@@ -122,6 +135,8 @@ impl WasmBridge {
let store = self.store.clone();
let plugin_map = self.plugin_map.clone();
let connected_clients = self.connected_clients.clone();
+ let path_to_default_shell = self.path_to_default_shell.clone();
+ let zellij_cwd = self.zellij_cwd.clone();
async move {
let _ =
senders.send_to_background_jobs(BackgroundJob::AnimatePluginLoading(plugin_id));
@@ -139,6 +154,8 @@ impl WasmBridge {
size,
connected_clients.clone(),
&mut loading_indication,
+ path_to_default_shell,
+ zellij_cwd.clone(),
) {
Ok(_) => handle_plugin_successful_loading(&senders, plugin_id),
Err(e) => handle_plugin_loading_failure(
@@ -160,7 +177,10 @@ impl WasmBridge {
pub fn unload_plugin(&mut self, pid: PluginId) -> Result<()> {
info!("Bye from plugin {}", &pid);
let mut plugin_map = self.plugin_map.lock().unwrap();
- for (running_plugin, _, _) in plugin_map.remove_plugins(pid) {
+ for (running_plugin, _, workers) in plugin_map.remove_plugins(pid) {
+ for (_worker_name, worker_sender) in workers {
+ drop(worker_sender.send(MessageToWorker::Exit));
+ }
let running_plugin = running_plugin.lock().unwrap();
let cache_dir = running_plugin.plugin_env.plugin_own_data_dir.clone();
if let Err(e) = std::fs::remove_dir_all(cache_dir) {
@@ -195,6 +215,8 @@ impl WasmBridge {
let store = self.store.clone();
let plugin_map = self.plugin_map.clone();
let connected_clients = self.connected_clients.clone();
+ let path_to_default_shell = self.path_to_default_shell.clone();
+ let zellij_cwd = self.zellij_cwd.clone();
async move {
match PluginLoader::reload_plugin(
first_plugin_id,
@@ -205,6 +227,8 @@ impl WasmBridge {
plugin_map.clone(),
connected_clients.clone(),
&mut loading_indication,
+ path_to_default_shell.clone(),
+ zellij_cwd.clone(),
) {
Ok(_) => {
handle_plugin_successful_loading(&senders, first_plugin_id);
@@ -223,6 +247,8 @@ impl WasmBridge {
plugin_map.clone(),
connected_clients.clone(),
&mut loading_indication,
+ path_to_default_shell.clone(),
+ zellij_cwd.clone(),
) {
Ok(_) => handle_plugin_successful_loading(&senders, *plugin_id),
Err(e) => handle_plugin_loading_failure(
@@ -263,6 +289,8 @@ impl WasmBridge {
self.plugin_map.clone(),
self.connected_clients.clone(),
&mut loading_indication,
+ self.path_to_default_shell.clone(),
+ self.zellij_cwd.clone(),
) {
Ok(_) => {
let _ = self
@@ -414,7 +442,17 @@ impl WasmBridge {
));
},
Err(e) => {
- log::error!("{}", e);
+ log::error!("{:?}", e);
+
+ // https://stackoverflow.com/questions/66450942/in-rust-is-there-a-way-to-make-literal-newlines-in-r-using-windows-c
+ let stringified_error =
+ format!("{:?}", e).replace("\n", "\n\r");
+
+ handle_plugin_crash(
+ plugin_id,
+ stringified_error,
+ senders.clone(),
+ );
},
}
}
@@ -460,6 +498,7 @@ impl WasmBridge {
for plugin_id in &plugin_ids {
drop(self.unload_plugin(*plugin_id));
}
+ drop(self.watcher.as_mut().map(|w| w.unwatch(&self.zellij_cwd)));
}
fn run_plugin_of_plugin_id(&self, plugin_id: PluginId) -> Option<&RunPlugin> {
self.loading_plugins
@@ -599,36 +638,23 @@ impl WasmBridge {
self.plugin_map
.lock()
.unwrap()
- .clone_worker(plugin_id, client_id, &worker_name);
- let mut cache_messages = || {
- for (message, payload) in messages.drain(..) {
- self.cached_worker_messages
- .entry(plugin_id)
- .or_default()
- .push((client_id, worker_name.clone(), message, payload));
- }
- };
+ .worker_sender(plugin_id, client_id, &worker_name);
match worker {
Some(worker) => {
- let worker_is_busy = { worker.try_lock().is_err() };
- if worker_is_busy {
- // most messages will be caught here, we do this once before the async task to
- // bulk most messages together and prevent them from cascading
- cache_messages();
- } else {
- async_send_messages_to_worker(
- self.senders.clone(),
- messages,
- worker,
- plugin_id,
- client_id,
- worker_name,
- );
+ for (message, payload) in messages.drain(..) {
+ if let Err(e) = worker.try_send(MessageToWorker::Message(message, payload)) {
+ log::error!("Failed to send message to worker: {:?}", e);
+ }
}
},
None => {
- log::warn!("Worker {worker_name} not found, placing message in cache");
- cache_messages();
+ log::warn!("Worker {worker_name} not found, caching messages");
+ for (message, payload) in messages.drain(..) {
+ self.cached_worker_messages
+ .entry(plugin_id)
+ .or_default()
+ .push((client_id, worker_name.clone(), message, payload));
+ }
},
}
Ok(())
@@ -708,52 +734,12 @@ pub fn apply_event_to_plugin(
Ok(())
}
-fn async_send_messages_to_worker(
- senders: ThreadSenders,
- mut messages: Vec<(String, String)>,
- worker: Arc<Mutex<RunningWorker>>,
- plugin_id: PluginId,
- client_id: ClientId,
- worker_name: String,
-) {
- task::spawn({
- async move {
- match worker.try_lock() {
- Ok(worker) => {
- for (message, payload) in messages.drain(..) {
- worker.send_message(message, payload).ok();
- }
- let _ = senders
- .send_to_plugin(PluginInstruction::ApplyCachedWorkerMessages(plugin_id));
- },
- Err(TryLockError::WouldBlock) => {
- task::spawn({
- async move {
- log::warn!(
- "Worker {} busy, retrying sending message after: {}ms",
- worker_name,
- RETRY_INTERVAL_MS
- );
- task::sleep(std::time::Duration::from_millis(RETRY_INTERVAL_MS)).await;
- let _ = senders.send_to_plugin(
- PluginInstruction::PostMessagesToPluginWorker(
- plugin_id,
- client_id,
- worker_name,
- messages,
- ),
- );
- }
- });
- },
- Err(e) => {
- log::error!(
- "Failed to send message to worker \"{}\": {:?}",
- worker_name,
- e
- );
- },
- }
- }
- });
+pub fn handle_plugin_crash(plugin_id: PluginId, message: String, senders: ThreadSenders) {
+ let mut loading_indication = LoadingIndication::new("Panic!".to_owned());
+ loading_indication.indicate_loading_error(message);
+ let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
+ plugin_id,
+ loading_indication,
+ ));
+ let _ = senders.send_to_plugin(PluginInstruction::Unload(plugin_id));
}
diff --git a/zellij-server/src/plugins/watch_filesystem.rs b/zellij-server/src/plugins/watch_filesystem.rs
new file mode 100644
index 00000000..864bd45f
--- /dev/null
+++ b/zellij-server/src/plugins/watch_filesystem.rs
@@ -0,0 +1,63 @@
+use super::PluginInstruction;
+use std::path::PathBuf;
+
+use crate::thread_bus::ThreadSenders;
+use std::path::Path;
+
+use zellij_utils::{data::Event, errors::prelude::*};
+
+use zellij_utils::notify::{self, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
+pub fn watch_filesystem(senders: ThreadSenders, zellij_cwd: &Path) -> Result<RecommendedWatcher> {
+ let path_prefix_in_plugins = PathBuf::from("/host");
+ let current_dir = PathBuf::from(zellij_cwd);
+ let mut watcher = notify::recommended_watcher({
+ move |res: notify::Result<notify::Event>| match res {
+ Ok(event) => {
+ let paths: Vec<PathBuf> = event
+ .paths
+ .iter()
+ .map(|p| {
+ let stripped_prefix_path =
+ p.strip_prefix(&current_dir).unwrap_or_else(|_| p);
+ path_prefix_in_plugins.join(stripped_prefix_path)
+ })
+ .collect();
+ match event.kind {
+ EventKind::Access(_) => {
+ let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
+ None,
+ None,
+ Event::FileSystemRead(paths),
+ )]));
+ },
+ EventKind::Create(_) => {
+ let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
+ None,
+ None,
+ Event::FileSystemCreate(paths),
+ )]));
+ },
+ EventKind::Modify(_) => {
+ let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
+ None,
+ None,
+ Event::FileSystemUpdate(paths),
+ )]));
+ },
+ EventKind::Remove(_) => {
+ let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
+ None,
+ None,
+ Event::FileSystemDelete(paths),
+ )]));
+ },
+ _ => {},
+ }
+ },
+ Err(e) => log::error!("watch error: {:?}", e),
+ }
+ })?;
+
+ watcher.watch(zellij_cwd, RecursiveMode::Recursive)?;
+ Ok(watcher)
+}
diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs
index 3515cb34..d64d867b 100644
--- a/zellij-server/src/plugins/zellij_exports.rs
+++ b/zellij-server/src/plugins/zellij_exports.rs
@@ -1,5 +1,6 @@
use super::PluginInstruction;
use crate::plugins::plugin_map::{PluginEnv, Subscriptions};
+use crate::plugins::wasm_bridge::handle_plugin_crash;
use log::{debug, warn};
use serde::{de::DeserializeOwned, Serialize};
use std::{
@@ -23,7 +24,10 @@ use zellij_utils::{
consts::VERSION,
data::{Event, EventType, PluginIds},
errors::prelude::*,
- input::{command::TerminalAction, plugins::PluginType},
+ input::{
+ command::{RunCommand, TerminalAction},
+ plugins::PluginType,
+ },
serde,
};
@@ -50,13 +54,18 @@ pub fn zellij_exports(
host_get_plugin_ids,
host_get_zellij_version,
host_open_file,
+ host_open_file_floating,
host_open_file_with_line,
+ host_open_file_with_line_floating,
+ host_open_terminal,
+ host_open_terminal_floating,
host_switch_tab_to,
host_set_timeout,
host_exec_cmd,
host_report_panic,
host_post_message_to,
host_post_message_to_plugin,
+ host_hide_self,
}
}
@@ -160,9 +169,30 @@ fn host_open_file(env: &ForeignFunctionEnv) {
.senders
.send_to_pty(PtyInstruction::SpawnTerminal(
Some(TerminalAction::OpenFile(path, None, None)),
+ Some(false),
None,
+ ClientOrTabIndex::ClientId(env.plugin_env.client_id),
+ ))
+ })
+ .with_context(|| {
+ format!(
+ "failed to open file on host from plugin {}",
+ env.plugin_env.name()
+ )
+ })
+ .non_fatal();
+}
+
+fn host_open_file_floating(env: &ForeignFunctionEnv) {
+ wasi_read_object::<PathBuf>(&env.plugin_env.wasi_env)
+ .and_then(|path| {
+ env.plugin_env
+ .senders
+ .send_to_pty(PtyInstruction::SpawnTerminal(
+ Some(TerminalAction::OpenFile(path, None, None)),
+ Some(true),
None,
- ClientOrTabIndex::TabIndex(env.plugin_env.tab_index),
+ ClientOrTabIndex::ClientId(env.plugin_env.client_id),
))
})
.with_context(|| {
@@ -181,9 +211,30 @@ fn host_open_file_with_line(env: &ForeignFunctionEnv) {
.senders
.send_to_pty(PtyInstruction::SpawnTerminal(
Some(TerminalAction::OpenFile(path, Some(line), None)), // TODO: add cwd
+ Some(false),
None,
+ ClientOrTabIndex::ClientId(env.plugin_env.client_id),
+ ))
+ })
+ .with_context(|| {
+ format!(
+ "failed to open file on host from plugin {}",
+ env.plugin_env.name()
+ )
+ })
+ .non_fatal();
+}
+
+fn host_open_file_with_line_floating(env: &ForeignFunctionEnv) {
+ wasi_read_object::<(PathBuf, usize)>(&env.plugin_env.wasi_env)
+ .and_then(|(path, line)| {
+ env.plugin_env
+ .senders
+ .send_to_pty(PtyInstruction::SpawnTerminal(
+ Some(TerminalAction::OpenFile(path, Some(line), None)), // TODO: add cwd
+ Some(true),
None,
- ClientOrTabIndex::TabIndex(env.plugin_env.tab_index),
+ ClientOrTabIndex::ClientId(env.plugin_env.client_id),
))
})
.with_context(|| {
@@ -195,6 +246,54 @@ fn host_open_file_with_line(env: &ForeignFunctionEnv) {
.non_fatal();
}
+fn host_open_terminal(env: &ForeignFunctionEnv) {
+ wasi_read_object::<PathBuf>(&env.plugin_env.wasi_env)
+ .and_then(|path| {
+ env.plugin_env
+ .senders
+ .send_to_pty(PtyInstruction::SpawnTerminal(
+ Some(TerminalAction::RunCommand(
+ RunCommand::new(env.plugin_env.path_to_default_shell.clone())
+ .with_cwd(path),
+ )),
+ Some(false),
+ None,
+ ClientOrTabIndex::ClientId(env.plugin_env.client_id),
+ ))
+ })
+ .with_context(|| {
+ format!(
+ "failed to open terminal on host from plugin {}",
+ env.plugin_env.name()
+ )
+ })
+ .non_fatal();
+}
+
+fn host_open_terminal_floating(env: &ForeignFunctionEnv) {
+ wasi_read_object::<PathBuf>(&env.plugin_env.wasi_env)
+ .and_then(|path| {
+ env.plugin_env
+ .senders
+ .send_to_pty(PtyInstruction::SpawnTerminal(
+ Some(TerminalAction::RunCommand(
+ RunCommand::new(env.plugin_env.path_to_default_shell.clone())
+ .with_cwd(path),
+ )),
+ Some(true),
+ None,
+ ClientOrTabIndex::ClientId(env.plugin_env.client_id),
+ ))
+ })
+ .with_context(|| {
+ format!(
+ "failed to open terminal on host from plugin {}",
+ env.plugin_env.name()
+ )
+ })
+ .non_fatal();
+}
+
fn host_switch_tab_to(env: &ForeignFunctionEnv, tab_idx: u32) {
env.plugin_env
.senders
@@ -314,6 +413,17 @@ fn host_post_message_to_plugin(env: &ForeignFunctionEnv) {
.fatal();
}
+fn host_hide_self(env: &ForeignFunctionEnv) {
+ env.plugin_env
+ .senders
+ .send_to_screen(ScreenInstruction::SuppressPane(
+ PaneId::Plugin(env.plugin_env.plugin_id),
+ env.plugin_env.client_id,
+ ))
+ .with_context(|| format!("failed to hide self"))
+ .fatal();
+}
+
// Custom panic handler for plugins.
//
// This is called when a panic occurs in a plugin. Since most panics will likely originate in the
@@ -328,7 +438,12 @@ fn host_report_panic(env: &ForeignFunctionEnv) {
)
})
.fatal();
- panic!("{}", msg);
+ log::error!("PANIC IN PLUGIN! {}", msg);
+ handle_plugin_crash(
+ env.plugin_env.plugin_id,
+ msg,
+ env.plugin_env.senders.clone(),
+ );
}
// Helper Functions ---------------------------------------------------------------------------------------------------
diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs
index fb58aef8..84f282f8 100644
--- a/zellij-server/src/pty.rs
+++ b/zellij-server/src/pty.rs
@@ -7,7 +7,7 @@ use crate::{
ClientId, ServerInstruction,
};
use async_std::task::{self, JoinHandle};
-use std::{collections::HashMap, env, os::unix::io::RawFd, path::PathBuf};
+use std::{collections::HashMap, os::unix::io::RawFd, path::PathBuf};
use zellij_utils::nix::unistd::Pid;
use zellij_utils::{
async_std,
@@ -468,10 +468,7 @@ impl Pty {
default_shell
},
None => {
- let shell = PathBuf::from(env::var("SHELL").unwrap_or_else(|_| {
- log::warn!("Cannot read SHELL env, falling back to use /bin/sh");
- "/bin/sh".to_string()
- }));
+ let shell = get_default_shell();
TerminalAction::RunCommand(RunCommand {
args: vec![],
command: shell,
@@ -1048,3 +1045,10 @@ fn send_command_not_found_to_screen(
.with_context(err_context)?;
Ok(())
}
+
+pub fn get_default_shell() -> PathBuf {
+ PathBuf::from(std::env::var("SHELL").unwrap_or_else(|_| {
+ log::warn!("Cannot read SHELL env, falling back to use /bin/sh");
+ "/bin/sh".to_string()
+ }))
+}
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs
index f3455379..c43e99f1 100644
--- a/zellij-server/src/route.rs
+++ b/zellij-server/src/route.rs
@@ -702,6 +702,16 @@ pub(crate) fn route_action(
))
.with_context(err_context)?;
},
+ Action::LaunchOrFocusPlugin(run_plugin, should_float) => {
+ session
+ .senders
+ .send_to_screen(ScreenInstruction::LaunchOrFocusPlugin(
+ run_plugin,
+ should_float,
+ client_id,
+ ))
+ .with_context(err_context)?;
+ },
}
Ok(should_break)
}
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index 030a63a5..e88baf29 100644
--- a/zellij-server/src/screen.rs
+++ b/zellij-server/src/screen.rs
@@ -273,6 +273,8 @@ pub enum ScreenInstruction {
StartPluginLoadingIndication(u32, LoadingIndication), // u32 - plugin_id
ProgressPluginLoadingOffset(u32), // u32 - plugin id
RequestStateUpdateForPlugins,
+ LaunchOrFocusPlugin(RunPlugin, bool, ClientId), // bool is should_float
+ SuppressPane(PaneId, ClientId),
}
impl From<&ScreenInstruction> for ScreenContext {
@@ -435,6 +437,8 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::RequestStateUpdateForPlugins => {
ScreenContext::RequestStateUpdateForPlugins
},
+ ScreenInstruction::LaunchOrFocusPlugin(..) => ScreenContext::LaunchOrFocusPlugin,
+ ScreenInstruction::SuppressPane(..) => ScreenContext::SuppressPane,
}
}
}
@@ -1462,6 +1466,24 @@ impl Screen {
self.render()
}
+ pub fn focus_plugin_pane(
+ &mut self,
+ run_plugin: &RunPlugin,
+ should_float: bool,
+ client_id: ClientId,
+ ) -> Result<bool> {
+ // true => found and focused, false => not
+ let all_tabs = self.get_tabs_mut();
+ for tab in all_tabs.values_mut() {
+ if let Some(plugin_pane_id) = tab.find_plugin(&run_plugin) {
+ tab.focus_pane_with_id(plugin_pane_id, should_float, client_id)
+ .context("failed to focus plugin pane")?;
+ return Ok(true);
+ }
+ }
+ Ok(false)
+ }
+
fn unblock_input(&self) -> Result<()> {
self.bus
.senders
@@ -1557,6 +1579,7 @@ pub(crate) fn screen_thread_main(
client_id: ClientId| tab .new_pane(pid,
initial_pane_title,
should_float,
+ None,
Some(client_id)),
?);
if let Some(hold_for_command) = hold_for_command {
@@ -1575,7 +1598,13 @@ pub(crate) fn screen_thread_main(
},
ClientOrTabIndex::TabIndex(tab_index) => {
if let Some(active_tab) = screen.tabs.get_mut(&tab_index) {
- active_tab.new_pane(pid, initial_pane_title, should_float, None)?;
+ active_tab.new_pane(
+ pid,
+ initial_pane_title,
+ should_float,
+ None,
+ None,
+ )?;
if let Some(hold_for_command) = hold_for_command {
let is_first_run = true;
active_tab.hold_pane(pid, None, is_first_run, hold_for_command);
@@ -2558,11 +2587,11 @@ pub(crate) fn screen_thread_main(
pane_title.unwrap_or_else(|| run_plugin_location.location.to_string());
let run_plugin = Run::Plugin(run_plugin_location);
if let Some(active_tab) = screen.tabs.get_mut(&tab_index) {
- active_tab.new_plugin_pane(
+ active_tab.new_pane(
PaneId::Plugin(plugin_id),
- pane_title,
+ Some(pane_title),
should_float,
- run_plugin,
+ Some(run_plugin),
None,
)?;
} else {
@@ -2608,6 +2637,46 @@ pub(crate) fn screen_thread_main(
screen.update_tabs()?;
screen.render()?;
},
+ ScreenInstruction::LaunchOrFocusPlugin(run_plugin, should_float, client_id) => {
+ let client_id = if screen.active_tab_indices.contains_key(&client_id) {
+ Some(client_id)
+ } else {
+ screen.get_first_client_id()
+ };
+ let client_id_and_focused_tab = client_id.and_then(|client_id| {
+ screen
+ .active_tab_indices
+ .get(&client_id)
+ .map(|tab_index| (*tab_index, client_id))
+ });
+ match client_id_and_focused_tab {
+ Some((tab_index, client_id)) => {
+ if screen.focus_plugin_pane(&run_plugin, should_float, client_id)? {
+ screen.render()?;
+ } else {
+ screen.bus.senders.send_to_plugin(PluginInstruction::Load(
+ Some(should_float),
+ None,
+ run_plugin,
+ tab_index,
+ client_id,
+ Size::default(),
+ ))?;
+ }
+ },
+ None => log::error!("No connected clients found - cannot load or focus plugin"),
+ }
+ },
+ ScreenInstruction::SuppressPane(pane_id, client_id) => {
+ let all_tabs = screen.get_tabs_mut();
+ for tab in all_tabs.values_mut() {
+ if tab.has_pane_with_pid(&pane_id) {
+ tab.suppress_pane(pane_id, client_id);
+ drop(screen.render());
+ break;
+ }
+ }
+ },
}
}
Ok(())
diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs
index 4a37a090..e695117e 100644
--- a/zellij-server/src/tab/mod.rs
+++ b/zellij-server/src/tab/mod.rs
@@ -47,8 +47,8 @@ use zellij_utils::{
input::{
command::TerminalAction,
layout::{
- FloatingPaneLayout, Run, RunPluginLocation, SwapFloatingLayout, SwapTiledLayout,
- TiledPaneLayout,
+ FloatingPaneLayout, Run, RunPlugin, RunPluginLocation, SwapFloatingLayout,
+ SwapTiledLayout, TiledPaneLayout,
},
parse_keys,
},
@@ -901,7 +901,6 @@ impl Tab {
pub fn toggle_pane_embed_or_floating(&mut self, client_id: ClientId) -> Result<()> {
let err_context =
|| format!("failed to toggle embedded/floating pane for client {client_id}");
-
if self.tiled_panes.fullscreen_is_active() {
self.tiled_panes.unset_fullscreen();
}
@@ -914,55 +913,24 @@ impl Tab {
"failed to find floating pane (ID: {focused_floating_pane_id:?}) to embed for client {client_id}",
))
.with_context(err_context)?;
- self.tiled_panes
- .insert_pane(focused_floating_pane_id, floating_pane_to_embed);
- self.should_clear_display_before_rendering = true;
- self.tiled_panes
- .focus_pane(focused_floating_pane_id, client_id);
self.hide_floating_panes();
- if self.auto_layout && !self.swap_layouts.is_tiled_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_tiled_damaged(); // we do this so that we won't skip to the
- // next layout
- self.next_swap_layout(Some(client_id), true)?;
- }
+ self.add_tiled_pane(
+ floating_pane_to_embed,
+ focused_floating_pane_id,
+ Some(client_id),
+ )?;
}
}
} else if let Some(focused_pane_id) = self.tiled_panes.focused_pane_id(client_id) {
- if let Some(new_pane_geom) = self.floating_panes.find_room_for_new_pane() {
- if self.get_selectable_tiled_panes().count() <= 1 {
- // don't close the only pane on screen...
- return Ok(());
- }
- if let Some(mut embedded_pane_to_float) =
- self.close_pane(focused_pane_id, true, Some(client_id))
- {
- if !embedded_pane_to_float.borderless() {
- // floating panes always have a frame unless they're explicitly borderless
- embedded_pane_to_float.set_content_offset(Offset::frame(1));
- }
- embedded_pane_to_float.set_geom(new_pane_geom);
- resize_pty!(
- embedded_pane_to_float,
- self.os_api,
- self.senders,
- self.character_cell_size
- )
- .with_context(err_context)?;
- embedded_pane_to_float.set_active_at(Instant::now());
- self.floating_panes
- .add_pane(focused_pane_id, embedded_pane_to_float);
- self.floating_panes.focus_pane(focused_pane_id, client_id);
- self.show_floating_panes();
- 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(Some(client_id), true)?;
- }
- }
+ if self.get_selectable_tiled_panes().count() <= 1 {
+ // don't close the only pane on screen...
+ return Ok(());
+ }
+ if let Some(embedded_pane_to_float) =
+ self.close_pane(focused_pane_id, true, Some(client_id))
+ {
+ self.show_floating_panes();
+ self.add_floating_pane(embedded_pane_to_float, focused_pane_id, Some(client_id))?;
}
}
Ok(())
@@ -1018,10 +986,10 @@ impl Tab {
pid: PaneId,
initial_pane_title: Option<String>,
should_float: Option<bool>,
+ run_plugin: Option<Run>, // only relevant if this is a plugin pane
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(),
@@ -1029,198 +997,51 @@ impl Tab {
};
self.close_down_to_max_terminals()
.with_context(err_context)?;
- if self.floating_panes.panes_are_visible() {
- if let Some(new_pane_geom) = self.floating_panes.find_room_for_new_pane() {
+ let new_pane = match pid {
+ PaneId::Terminal(term_pid) => {
let next_terminal_position = self.get_next_terminal_position();
- if let PaneId::Terminal(term_pid) = pid {
- let mut new_pane = TerminalPane::new(
- term_pid,
- new_pane_geom,
- self.style,
- next_terminal_position,
- String::new(),
- 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_pane_title,
- None,
- );
- 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::Terminal(term_pid) = pid {
- let next_terminal_position = self.get_next_terminal_position();
- let mut new_terminal = TerminalPane::new(
- term_pid,
- PaneGeom::default(), // the initial size will be set later
- self.style,
- next_terminal_position,
- String::new(),
- 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_pane_title,
- None,
- );
- new_terminal.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_terminal));
- } else {
- self.tiled_panes.insert_pane(pid, Box::new(new_terminal));
- }
- 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 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 => {},
+ Box::new(TerminalPane::new(
+ term_pid,
+ PaneGeom::default(), // this will be filled out later
+ self.style,
+ next_terminal_position,
+ String::new(),
+ 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_pane_title,
+ None,
+ )) as Box<dyn Pane>
+ },
+ PaneId::Plugin(plugin_pid) => {
+ Box::new(PluginPane::new(
+ plugin_pid,
+ PaneGeom::default(), // this will be filled out later
+ self.senders
+ .to_plugin
+ .as_ref()
+ .with_context(err_context)?
+ .clone(),
+ initial_pane_title.unwrap_or("".to_owned()),
+ 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,
+ run_plugin,
+ )) as Box<dyn Pane>
+ },
};
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)?;
- }
- }
+ self.add_floating_pane(new_pane, pid, client_id)
} 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)?;
- }
+ self.add_tiled_pane(new_pane, pid, client_id)
}
- 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
@@ -3426,6 +3247,112 @@ impl Tab {
self.floating_panes.toggle_show_panes(false);
self.tiled_panes.focus_all_panes();
}
+
+ pub fn find_plugin(&self, run_plugin: &RunPlugin) -> Option<PaneId> {
+ self.tiled_panes
+ .get_plugin_pane_id(run_plugin)
+ .or_else(|| self.floating_panes.get_plugin_pane_id(run_plugin))
+ .or_else(|| {
+ let run = Some(Run::Plugin(run_plugin.clone()));
+ self.suppressed_panes
+ .iter()
+ .find(|(_id, s_p)| s_p.invoked_with() == &run)
+ .map(|(id, _)| *id)
+ })
+ }
+
+ pub fn focus_pane_with_id(
+ &mut self,
+ pane_id: PaneId,
+ should_float: bool,
+ client_id: ClientId,
+ ) -> Result<()> {
+ self.tiled_panes
+ .focus_pane_if_exists(pane_id, client_id)
+ .or_else(|_| {
+ let focused_floating_pane =
+ self.floating_panes.focus_pane_if_exists(pane_id, client_id);
+ if focused_floating_pane.is_ok() {
+ self.show_floating_panes()
+ };
+ focused_floating_pane
+ })
+ .or_else(|_| match self.suppressed_panes.remove(&pane_id) {
+ Some(pane) => {
+ if should_float {
+ self.show_floating_panes();
+ self.add_floating_pane(pane, pane_id, Some(client_id))
+ } else {
+ self.hide_floating_panes();
+ self.add_tiled_pane(pane, pane_id, Some(client_id))
+ }
+ },
+ None => Ok(()),
+ })
+ }
+ pub fn suppress_pane(&mut self, pane_id: PaneId, client_id: ClientId) {
+ if let Some(pane) = self.close_pane(pane_id, true, Some(client_id)) {
+ self.suppressed_panes.insert(pane_id, pane);
+ }
+ }
+ fn add_floating_pane(
+ &mut self,
+ mut pane: Box<dyn Pane>,
+ pane_id: PaneId,
+ client_id: Option<ClientId>,
+ ) -> Result<()> {
+ let err_context = || format!("failed to add floating pane");
+ if let Some(new_pane_geom) = self.floating_panes.find_room_for_new_pane() {
+ pane.set_active_at(Instant::now());
+ pane.set_geom(new_pane_geom);
+ pane.set_content_offset(Offset::frame(1)); // floating panes always have a frame
+ resize_pty!(pane, self.os_api, self.senders, self.character_cell_size)
+ .with_context(err_context)?;
+ self.floating_panes.add_pane(pane_id, pane);
+ self.floating_panes.focus_pane_for_all_clients(pane_id);
+ }
+ 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)?;
+ }
+ Ok(())
+ }
+ fn add_tiled_pane(
+ &mut self,
+ mut pane: Box<dyn Pane>,
+ pane_id: PaneId,
+ client_id: Option<ClientId>,
+ ) -> Result<()> {
+ 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() {
+ 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(pane_id, pane);
+ } else {
+ self.tiled_panes.insert_pane(pane_id, pane);
+ }
+ self.should_clear_display_before_rendering = true;
+ if let Some(client_id) = client_id {
+ self.tiled_panes.focus_pane(pane_id, 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(())
+ }
}
#[cfg(test)]
diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs
index 48d841a5..7cd32c0f 100644
--- a/zellij-server/src/tab/unit/tab_integration_tests.rs
+++ b/zellij-server/src/tab/unit/tab_integration_tests.rs
@@ -696,7 +696,7 @@ fn dump_screen() {
..Default::default()
});
let new_pane_id = PaneId::Terminal(2);
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(2, Vec::from("scratch".as_bytes()))
.unwrap();
@@ -724,7 +724,7 @@ fn clear_screen() {
..Default::default()
});
let new_pane_id = PaneId::Terminal(2);
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(2, Vec::from("scratch".as_bytes()))
.unwrap();
@@ -750,7 +750,7 @@ fn new_floating_pane() {
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -778,7 +778,7 @@ fn floating_panes_persist_across_toggles() {
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
// here we send bytes to the pane when it's not visible to make sure they're still handled and
@@ -810,7 +810,7 @@ fn toggle_floating_panes_off() {
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -839,7 +839,7 @@ fn toggle_floating_panes_on() {
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -873,15 +873,15 @@ fn five_new_floating_panes() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -917,7 +917,7 @@ fn increase_floating_pane_size() {
let new_pane_id_1 = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -947,7 +947,7 @@ fn decrease_floating_pane_size() {
let new_pane_id_1 = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -977,7 +977,7 @@ fn resize_floating_pane_left() {
let new_pane_id_1 = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1010,7 +1010,7 @@ fn resize_floating_pane_right() {
let new_pane_id_1 = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1043,7 +1043,7 @@ fn resize_floating_pane_up() {
let new_pane_id_1 = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1076,7 +1076,7 @@ fn resize_floating_pane_down() {
let new_pane_id_1 = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1113,15 +1113,15 @@ fn move_floating_pane_focus_left() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1168,15 +1168,15 @@ fn move_floating_pane_focus_right() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1224,15 +1224,15 @@ fn move_floating_pane_focus_up() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1279,15 +1279,15 @@ fn move_floating_pane_focus_down() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1335,15 +1335,15 @@ fn move_floating_pane_focus_with_mouse() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1393,15 +1393,15 @@ fn move_pane_focus_with_mouse_to_non_floating_pane() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1451,15 +1451,15 @@ fn drag_pane_with_mouse() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1509,15 +1509,15 @@ fn mark_text_inside_floating_pane() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1575,15 +1575,15 @@ fn resize_tab_with_floating_panes() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1629,15 +1629,15 @@ fn shrink_whole_tab_with_floating_panes_horizontally_and_vertically() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1679,15 +1679,15 @@ fn shrink_whole_tab_with_floating_panes_horizontally_and_vertically_and_expand_b
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1730,7 +1730,7 @@ fn embed_floating_pane() {
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1758,7 +1758,7 @@ fn float_embedded_pane() {
let mut tab = create_new_tab(size, ModeInfo::default());
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1788,7 +1788,7 @@ fn embed_floating_pane_without_pane_frames() {
let mut output = Output::default();
tab.set_pane_frames(false);
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1817,7 +1817,7 @@ fn float_embedded_pane_without_pane_frames() {
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
tab.set_pane_frames(false);
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1920,7 +1920,7 @@ fn rename_floating_pane() {
let mut tab = create_new_tab(size, ModeInfo::default());
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -2006,7 +2006,7 @@ fn move_floating_pane_with_sixel_image() {
let mut output = Output::new(sixel_image_store.clone(), character_cell_size);
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
let fixture = read_fixture("sixel-image-500px.six");
tab.handle_pty_bytes(2, fixture).unwrap();
@@ -2044,7 +2044,7 @@ fn floating_pane_above_sixel_image() {
let mut output = Output::new(sixel_image_store.clone(), character_cell_size);
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
let fixture = read_fixture("sixel-image-500px.six");
tab.handle_pty_bytes(1, fixture).unwrap();
@@ -2101,7 +2101,7 @@ fn suppress_floating_pane() {
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.suppress_active_pane(editor_pane_id, client_id).unwrap();
tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
@@ -2155,7 +2155,7 @@ fn close_suppressing_floating_pane() {
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.suppress_active_pane(editor_pane_id, client_id).unwrap();
tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
@@ -2213,7 +2213,7 @@ fn suppress_floating_pane_embed_it_and_close_it() {
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.suppress_active_pane(editor_pane_id, client_id).unwrap();
tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
@@ -2273,7 +2273,7 @@ fn resize_whole_tab_while_floting_pane_is_suppressed() {
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.suppress_active_pane(editor_pane_id, client_id).unwrap();
tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
@@ -2374,7 +2374,7 @@ fn enter_search_floating_pane() {
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
let pane_content = read_fixture("grid_copy");
@@ -2879,7 +2879,7 @@ fn move_pane_focus_sends_tty_csi_event() {
});
let mut tab = create_new_tab_with_os_api(size, ModeInfo::default(), &os_api);
let new_pane_id_1 = PaneId::Terminal(2);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
1,
@@ -2914,9 +2914,9 @@ fn move_floating_pane_focus_sends_tty_csi_event() {
let new_pane_id_2 = PaneId::Terminal(3);
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
1,
@@ -2957,9 +2957,9 @@ fn toggle_floating_panes_on_sends_tty_csi_event() {
let new_pane_id_2 = PaneId::Terminal(3);
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
tab.handle_pty_bytes(
@@ -3001,9 +3001,9 @@ fn toggle_floating_panes_off_sends_tty_csi_event() {
let new_pane_id_2 = PaneId::Terminal(3);
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
1,
@@ -3063,7 +3063,7 @@ fn can_swap_tiled_layout_at_runtime() {
);
let new_pane_id_1 = PaneId::Terminal(2);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.next_swap_layout(Some(client_id), false).unwrap();
tab.render(&mut output, None).unwrap();
@@ -3118,9 +3118,9 @@ fn can_swap_floating_layout_at_runtime() {
let new_pane_id_2 = PaneId::Terminal(3);
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
tab.next_swap_layout(Some(client_id), false).unwrap();
tab.render(&mut output, None).unwrap();
@@ -3171,7 +3171,7 @@ fn swapping_layouts_after_resize_snaps_to_current_layout() {
);
let new_pane_id_1 = PaneId::Terminal(2);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.next_swap_layout(Some(client_id), false).unwrap();
tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
@@ -3221,11 +3221,11 @@ fn swap_tiled_layout_with_stacked_children() {
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
tab.render(&mut output, None).unwrap();
let snapshot = take_snapshot(
@@ -3268,11 +3268,11 @@ fn swap_tiled_layout_with_only_stacked_children() {
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
tab.render(&mut output, None).unwrap();
let snapshot = take_snapshot(
@@ -3318,11 +3318,11 @@ fn swap_tiled_layout_with_stacked_children_and_no_pane_frames() {
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
tab.render(&mut output, None).unwrap();
let snapshot = take_snapshot(
@@ -3368,11 +3368,11 @@ fn move_focus_up_with_stacked_panes() {
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_right(client_id);
tab.move_focus_up(client_id);
@@ -3420,11 +3420,11 @@ fn move_focus_down_with_stacked_panes() {
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_right(client_id);
tab.move_focus_up(client_id);
@@ -3474,8 +3474,14 @@ fn move_focus_right_into_stacked_panes() {
);
for i in 0..12 {
let new_pane_id = i + 2;
- tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id))
- .unwrap();
+ tab.new_pane(
+ PaneId::Terminal(new_pane_id),
+ None,
+ None,
+ None,
+ Some(client_id),
+ )
+ .unwrap();
}
tab.move_focus_left(client_id);
tab.horizontal_split(PaneId::Terminal(16), None, client_id)
@@ -3535,8 +3541,14 @@ fn move_focus_left_into_stacked_panes() {
);
for i in 0..13 {
let new_pane_id = i + 2;
- tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id))
- .unwrap();
+ tab.new_pane(
+ PaneId::Terminal(new_pane_id),
+ None,
+ None,
+ None,
+ Some(client_id),
+ )
+ .unwrap();
}
tab.move_focus_right(client_id);
tab.horizontal_split(PaneId::Terminal(1), None, client_id)
@@ -3598,8 +3610,14 @@ fn move_focus_up_into_stacked_panes() {
);
for i in 0..4 {
let new_pane_id = i + 3;
- tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id))
- .unwrap();
+ tab.new_pane(
+ PaneId::Terminal(new_pane_id),
+ None,
+ None,
+ None,
+ Some(client_id),
+ )
+ .unwrap();
}
tab.move_focus_right(client_id);
tab.move_focus_up(client_id);
@@ -3662,8 +3680,14 @@ fn move_focus_down_into_stacked_panes() {
);
for i in 0..4 {
let new_pane_id = i + 3;
- tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id))
- .unwrap();
+ tab.new_pane(
+ PaneId::Terminal(new_pane_id),
+ None,
+ None,
+ None,
+ Some(client_id),
+ )
+ .unwrap();
}
tab.move_focus_left(client_id);
tab.move_focus_up(client_id);
@@ -3722,11 +3746,11 @@ fn close_main_stacked_pane() {
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
tab.close_pane(new_pane_id_2, false, None);
tab.render(&mut output, None).unwrap();
@@ -3775,15 +3799,15 @@ fn close_main_stacked_pane_in_mid_stack() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_right(client_id);
tab.move_focus_up(client_id);
@@ -3835,15 +3859,15 @@ fn close_one_liner_stacked_pane_below_main_pane() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_left(client_id);
tab.move_focus_right(client_id);
@@ -3896,15 +3920,15 @@ fn close_one_liner_stacked_pane_above_main_pane() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_right(client_id);
tab.move_focus_up(client_id);
@@ -3956,15 +3980,15 @@ fn can_increase_size_of_main_pane_in_stack_horizontally() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_right(client_id);
tab.resize(
@@ -4020,15 +4044,15 @@ fn can_increase_size_of_main_pane_in_stack_vertically() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_right(client_id);
tab.resize(
@@ -4084,15 +4108,15 @@ fn can_increase_size_of_main_pane_in_stack_non_directionally() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
let _ = tab.move_focus_up(client_id);
let _ = tab.move_focus_right(client_id);
@@ -4144,15 +4168,15 @@ fn can_increase_size_into_pane_stack_horizontally() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.resize(
client_id,
@@ -4207,15 +4231,15 @@ fn can_increase_size_into_pane_stack_vertically() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_right(client_id);
tab.move_focus_down(client_id);
@@ -4272,15 +4296,15 @@ fn can_increase_size_into_pane_stack_non_directionally() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
let _ = tab.move_focus_up(client_id);
tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
@@ -4331,15 +4355,15 @@ fn decreasing_size_of_whole_tab_treats_stacked_panes_properly() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.resize_whole_tab(Size {
cols: 100,
@@ -4391,15 +4415,15 @@ fn increasing_size_of_whole_tab_treats_stacked_panes_properly() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.resize_whole_tab(Size {
cols: 100,
@@ -4456,15 +4480,15 @@ fn cannot_decrease_stack_size_beyond_minimum_height() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_down(client_id);
for _ in 0..6 {
@@ -4521,15 +4545,15 @@ fn focus_stacked_pane_over_flexible_pane_with_the_mouse() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_left_click(&Position::new(1, 71), client_id)
.unwrap();
@@ -4580,15 +4604,15 @@ fn focus_stacked_pane_under_flexible_pane_with_the_mouse() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_left_click(&Position::new(1, 71), client_id)
.unwrap();
@@ -4641,15 +4665,15 @@ fn close_stacked_pane_with_previously_focused_other_pane() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_left_click(&Position::new(2, 71), client_id)
.unwrap();
@@ -4708,15 +4732,15 @@ fn close_pane_near_stacked_panes() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.close_pane(PaneId::Terminal(6), false, None);
tab.render(&mut output, None).unwrap();
@@ -4772,15 +4796,15 @@ fn focus_next_pane_expands_stacked_panes() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_left(client_id);
tab.focus_next_pane(client_id);
@@ -4832,15 +4856,15 @@ fn stacked_panes_can_become_fullscreen() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_up(client_id);
tab.toggle_active_pane_fullscreen(client_id);
@@ -5467,8 +5491,14 @@ fn new_pane_in_auto_layout() {
];
for i in 0..7 {
let new_pane_id = i + 2;
- tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id))
- .unwrap();
+ tab.new_pane(
+ PaneId::Terminal(new_pane_id),
+ None,
+ None,
+ None,
+ Some(client_id),
+ )
+ .unwrap();
tab.render(&mut output, None).unwrap();
let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
@@ -6379,6 +6409,7 @@ fn new_floating_pane_in_auto_layout() {
PaneId::Terminal(new_pane_id),
None,
Some(should_float),
+ None,
Some(client_id),
)
.unwrap();
diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs
index bcf02202..5374ed70 100644
--- a/zellij-server/src/tab/unit/tab_tests.rs
+++ b/zellij-server/src/tab/unit/tab_tests.rs
@@ -537,7 +537,8 @@ fn split_largest_pane() {
let mut tab = create_new_tab(size);
for i in 2..5 {
let new_pane_id = PaneId::Terminal(i);
- tab.new_pane(new_pane_id, None, None, Some(1)).unwrap();
+ tab.new_pane(new_pane_id, None, None, None, Some(1))
+ .unwrap();
}
assert_eq!(tab.tiled_panes.panes.len(), 4, "The tab has four panes");
@@ -742,7 +743,7 @@ pub fn cannot_split_panes_horizontally_when_active_pane_is_too_small() {
pub fn cannot_split_largest_pane_when_there_is_no_room() {
let size = Size { cols: 8, rows: 4 };
let mut tab = create_new_tab(size);
- tab.new_pane(PaneId::Terminal(2), None, None, Some(1))
+ tab.new_pane(PaneId::Terminal(2), None, None, None, Some(1))
.unwrap();
assert_eq!(
tab.tiled_panes.panes.len(),
@@ -786,7 +787,8 @@ pub fn toggle_focused_pane_fullscreen() {
let mut tab = create_new_tab(size);
for i in 2..5 {
let new_pane_id = PaneId::Terminal(i);
- tab.new_pane(new_pane_id, None, None, Some(1)).unwrap();
+ tab.new_pane(new_pane_id, None, None, None, Some(1))
+ .unwrap();
}
tab.toggle_active_pane_fullscreen(1);
assert_eq!(
@@ -860,16 +862,16 @@ fn switch_to_next_pane_fullscreen() {
let mut active_tab = create_new_tab(size);
active_tab
- .new_pane(PaneId::Terminal(1), None, None, Some(1))
+ .new_pane(PaneId::Terminal(1), None, None, None, Some(1))
.unwrap();
active_tab
- .new_pane(PaneId::Terminal(2), None, None, Some(1))
+ .new_pane(PaneId::Terminal(2), None, None, None, Some(1))
.unwrap();
active_tab
- .new_pane(PaneId::Terminal(3), None, None, Some(1))
+ .new_pane(PaneId::Terminal(3), None, None, None, Some(1))
.unwrap();
active_tab
- .new_pane(PaneId::Terminal(4), None, None, Some(1))
+ .new_pane(PaneId::Terminal(4), None, None, None, Some(1))
.unwrap();
active_tab.toggle_active_pane_fullscreen(1);
@@ -900,16 +902,16 @@ fn switch_to_prev_pane_fullscreen() {
//testing four consecutive switches in fullscreen mode
active_tab
- .new_pane(PaneId::Terminal(1), None, None, Some(1))
+ .new_pane(PaneId::Terminal(1), None, None, None, Some(1))
.unwrap();
active_tab
- .new_pane(PaneId::Terminal(2), None, None, Some(1))
+ .new_pane(PaneId::Terminal(2), None, None, None, Some(1))
.unwrap();
active_tab
- .new_pane(PaneId::Terminal(3), None, None, Some(1))
+ .new_pane(PaneId::Terminal(3), None, None, None, Some(1))
.unwrap();
active_tab
- .new_pane(PaneId::Terminal(4), None, None, Some(1))
+ .new_pane(PaneId::Terminal(4), None, None, None, Some(1))
.unwrap();
active_tab.toggle_active_pane_fullscreen(1);
// order is now 1 2 3 4
@@ -14391,7 +14393,7 @@ fn correctly_resize_frameless_panes_on_pane_close() {
let content_size = (pane.get_content_columns(), pane.get_content_rows());
assert_eq!(content_size, (cols, rows));
- tab.new_pane(PaneId::Terminal(2), None, None, Some(1))
+ tab.new_pane(PaneId::Terminal(2), None, None, None, Some(1))
.unwrap();
tab.close_pane(PaneId::Terminal(2), true, None);
diff --git a/zellij-server/src/ui/loading_indication.rs b/zellij-server/src/ui/loading_indication.rs
index 78af2c10..1aa514ec 100644
--- a/zellij-server/src/ui/loading_indication.rs
+++ b/zellij-server/src/ui/loading_indication.rs
@@ -45,9 +45,15 @@ impl LoadingIndication {
pub fn merge(&mut self, other: LoadingIndication) {
let current_animation_offset = self.animation_offset;
let current_terminal_emulator_colors = self.terminal_emulator_colors.take();
+ let mut current_error = self.error.take();
drop(std::mem::replace(self, other));
self.animation_offset = current_animation_offset;
self.terminal_emulator_colors = current_terminal_emulator_colors;
+ if let Some(current_error) = current_error.take() {
+ // we do this so that only the first error (usually the root cause) will be shown
+ // when plugins support scrolling, we might want to do an append here
+ self.error = Some(current_error);
+ }
}
pub fn indicate_loading_plugin_from_memory(&mut self) {
self.loading_from_memory = Some(LoadingStatus::InProgress);
@@ -104,6 +110,9 @@ impl LoadingIndication {
pub fn indicate_loading_error(&mut self, error_text: String) {
self.error = Some(error_text);
}
+ pub fn is_error(&self) -> bool {
+ self.error.is_some()
+ }
fn started_loading(&self) -> bool {
self.loading_from_memory.is_some()
|| self.loading_from_hd_cache.is_some()
@@ -257,6 +266,13 @@ impl Display for LoadingIndication {
}
if let Some(error_text) = &self.error {
stringified.push_str(&format!("\n\r{} {error_text}", red.bold().paint("ERROR:")));
+ // we add this additional line explicitly to make it easier to realize when something
+ // is wrong in very small plugins (eg. the tab-bar and status-bar)
+ stringified.push_str(&format!(
+ "\n\r{}",
+ red.bold()
+ .paint("ERROR IN PLUGIN - check logs for more info")
+ ));
}
write!(f, "{}", stringified)
}
diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs
index d4cd8c1e..8639970b 100644
--- a/zellij-server/src/unit/screen_tests.rs
+++ b/zellij-server/src/unit/screen_tests.rs
@@ -14,7 +14,9 @@ use zellij_utils::data::Resize;
use zellij_utils::errors::{prelude::*, ErrorContext};
use zellij_utils::input::actions::Action;
use zellij_utils::input::command::{RunCommand, TerminalAction};
-use zellij_utils::input::layout::{Layout, SplitDirection, TiledPaneLayout};
+use zellij_utils::input::layout::{
+ Layout, Run, RunPlugin, RunPluginLocation, SplitDirection, TiledPaneLayout,
+};
use zellij_utils::input::options::Options;
use zellij_utils::ipc::IpcReceiverWithContext;
use zellij_utils::pane_size::{Size, SizeInPixels};
@@ -291,7 +293,11 @@ impl MockScreen {
let pane_layout = initial_layout.unwrap_or_default();
let pane_count = pane_layout.extract_run_instructions().len();
let mut pane_ids = vec![];
- let plugin_ids = HashMap::new();
+ let mut plugin_ids = HashMap::new();
+ plugin_ids.insert(
+ RunPluginLocation::File(PathBuf::from("/path/to/fake/plugin")),
+ vec![1],
+ );
for i in 0..pane_count {
pane_ids.push((i as u32, None));
}
@@ -864,7 +870,7 @@ fn switch_to_tab_with_fullscreen() {
{
let active_tab = screen.get_active_tab_mut(1).unwrap();
active_tab
- .new_pane(PaneId::Terminal(2), None, None, Some(1))
+ .new_pane(PaneId::Terminal(2), None, None, None, Some(1))
.unwrap();
active_tab.toggle_active_pane_fullscreen(1);
}
@@ -979,7 +985,7 @@ fn attach_after_first_tab_closed() {
{
let active_tab = screen.get_active_tab_mut(1).unwrap();
active_tab
- .new_pane(PaneId::Terminal(2), None, None, Some(1))
+ .new_pane(PaneId::Terminal(2), None, None, None, Some(1))
.unwrap();
active_tab.toggle_active_pane_fullscreen(1);
}
@@ -2715,3 +2721,145 @@ pub fn send_cli_query_tab_names_action() {
.cloned();
assert_snapshot!(format!("{:#?}", log_tab_names_instruction));
}
+
+#[test]
+pub fn send_cli_launch_or_focus_plugin_action() {
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let client_id = 10; // fake client id should not appear in the screen's state
+ let mut mock_screen = MockScreen::new(size);
+ let plugin_receiver = mock_screen.plugin_receiver.take().unwrap();
+ let session_metadata = mock_screen.clone_session_metadata();
+ let screen_thread = mock_screen.run(None);
+ let received_plugin_instructions = Arc::new(Mutex::new(vec![]));
+ let plugin_thread = log_actions_in_thread!(
+ received_plugin_instructions,
+ PluginInstruction::Exit,
+ plugin_receiver
+ );
+ let cli_action = CliAction::LaunchOrFocusPlugin {
+ floating: true,
+ url: url::Url::parse("file:/path/to/fake/plugin").unwrap(),
+ };
+ send_cli_action_to_server(&session_metadata, cli_action, &mut mock_screen, client_id);
+ std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
+ mock_screen.teardown(vec![plugin_thread, screen_thread]);
+
+ let plugin_load_instruction = received_plugin_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find(|instruction| match instruction {
+ PluginInstruction::Load(..) => true,
+ _ => false,
+ })
+ .cloned();
+
+ assert_snapshot!(format!("{:#?}", plugin_load_instruction));
+}
+
+#[test]
+pub fn send_cli_launch_or_focus_plugin_action_when_plugin_is_already_loaded() {
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let client_id = 10; // fake client id should not appear in the screen's state
+ let mut mock_screen = MockScreen::new(size);
+ let plugin_receiver = mock_screen.plugin_receiver.take().unwrap();
+ let session_metadata = mock_screen.clone_session_metadata();
+ let mut initial_layout = TiledPaneLayout::default();
+ let existing_plugin_pane = TiledPaneLayout {
+ run: Some(Run::Plugin(RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from("/path/to/fake/plugin")),
+ })),
+ ..Default::default()
+ };
+ initial_layout.children_split_direction = SplitDirection::Vertical;
+ initial_layout.children = vec![TiledPaneLayout::default(), existing_plugin_pane];
+ let screen_thread = mock_screen.run(Some(initial_layout));
+ let received_plugin_instructions = Arc::new(Mutex::new(vec![]));
+ let plugin_thread = log_actions_in_thread!(
+ received_plugin_instructions,
+ PluginInstruction::Exit,
+ plugin_receiver
+ );
+ let received_server_instructions = Arc::new(Mutex::new(vec![]));
+ let server_receiver = mock_screen.server_receiver.take().unwrap();
+ let server_thread = log_actions_in_thread!(
+ received_server_instructions,
+ ServerInstruction::KillSession,
+ server_receiver
+ );
+ let cli_action = CliAction::LaunchOrFocusPlugin {
+ floating: true,
+ url: url::Url::parse("file:/path/to/fake/plugin").unwrap(),
+ };
+ send_cli_action_to_server(&session_metadata, cli_action, &mut mock_screen, client_id);
+ std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
+ mock_screen.teardown(vec![plugin_thread, server_thread, screen_thread]);
+
+ let plugin_load_instruction_sent = received_plugin_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find(|instruction| match instruction {
+ PluginInstruction::Load(..) => true,
+ _ => false,
+ })
+ .is_some();
+ assert!(
+ !plugin_load_instruction_sent,
+ "Plugin Load instruction should not be sent for an already loaded plugin"
+ );
+ let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
+ received_server_instructions.lock().unwrap().iter(),
+ size,
+ );
+ let snapshot_count = snapshots.len();
+ assert_eq!(
+ snapshot_count, 2,
+ "Another render was sent for focusing the already loaded plugin"
+ );
+ for (cursor_coordinates, _snapshot) in snapshots.iter().skip(1) {
+ assert!(
+ cursor_coordinates.is_none(),
+ "Cursor moved to existing plugin in final snapshot indicating focus changed"
+ );
+ }
+}
+
+#[test]
+pub fn screen_can_suppress_pane() {
+ let size = Size { cols: 80, rows: 20 };
+ let mut initial_layout = TiledPaneLayout::default();
+ initial_layout.children_split_direction = SplitDirection::Vertical;
+ initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
+ let mut mock_screen = MockScreen::new(size);
+ let screen_thread = mock_screen.run(Some(initial_layout));
+ let received_server_instructions = Arc::new(Mutex::new(vec![]));
+ let server_receiver = mock_screen.server_receiver.take().unwrap();
+ let server_thread = log_actions_in_thread!(
+ received_server_instructions,
+ ServerInstruction::KillSession,
+ server_receiver
+ );
+ let _ = mock_screen
+ .to_screen
+ .send(ScreenInstruction::SuppressPane(PaneId::Terminal(1), 1));
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ mock_screen.teardown(vec![server_thread, screen_thread]);
+
+ let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
+ received_server_instructions.lock().unwrap().iter(),
+ size,
+ );
+ let snapshot_count = snapshots.len();
+ for (_cursor_coordinates, snapshot) in snapshots {
+ assert_snapshot!(format!("{}", snapshot));
+ }
+ assert_snapshot!(format!("{}", snapshot_count));
+}
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-2.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-2.snap
new file mode 100644
index 00000000..e4fbe942
--- /dev/null
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-2.snap
@@ -0,0 +1,26 @@
+---
+source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 2852
+expression: "format!(\"{}\", snapshot)"
+---
+00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐
+01 (C): │ │
+02 (C): │ │
+03 (C): │ │
+04 (C): │ │
+05 (C): │ │
+06 (C): │ │
+07 (C): │ │
+08 (C): │ │
+09 (C): │ │
+10 (C): │ │
+11 (C): │ │
+12 (C): │ │
+13 (C): │ │
+14 (C): │ │
+15 (C): │ │
+16 (C): │ │
+17 (C): │ │
+18 (C): │ │
+19 (C): └──────────────────────────────────────────────────────────────────────────────┘
+
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-3.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-3.snap
new file mode 100644
index 00000000..ea3ddf34
--- /dev/null
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-3.snap
@@ -0,0 +1,6 @@
+---
+source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 2854
+expression: "format!(\"{}\", snapshot_count)"
+---
+2
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane.snap
new file mode 100644
index 00000000..3a14dc55
--- /dev/null
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane.snap
@@ -0,0 +1,26 @@
+---
+source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 2852
+expression: "format!(\"{}\", snapshot)"
+---
+00 (C): ┌ Pane #1 ─────────────────────────────┐┌ Pane #2 ─────────────────────────────┐
+01 (C): │ ││ │
+02 (C): │ ││ │
+03 (C): │ ││ │
+04 (C): │ ││ │
+05 (C): │ ││ │
+06 (C): │ ││ │
+07 (C): │ ││ │
+08 (C): │ ││ │
+09 (C): │ ││ │
+10 (C): │ ││ │
+11 (C): │ ││ │
+12 (C): │ ││ │
+13 (C): │ ││ │
+14 (C): │ ││ │
+15 (C): │ ││ │
+16 (C): │ ││ │
+17 (C): │ ││ │
+18 (C): │ ││ │
+19 (C): └──────────────────────────────────────┘└──────────────────────────────────────┘
+
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_launch_or_focus_plugin_action.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_launch_or_focus_plugin_action.snap
new file mode 100644
index 00000000..0751fa5f
--- /dev/null
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_launch_or_focus_plugin_action.snap
@@ -0,0 +1,25 @@
+---
+source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 2758
+expression: "format!(\"{:#?}\", plugin_load_instruction)"
+---
+Some(
+ Load(
+ Some(
+ true,
+ ),
+ None,
+ RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: File(
+ "/path/to/fake/plugin",
+ ),
+ },
+ 0,
+ 1,
+ Size {
+ rows: 0,
+ cols: 0,
+ },
+ ),
+)