feat(ui): built-in plugin manager (#3633)

* add plugin list to session info

* feat(plugins): new_plugin and reload_plugin API commands

* feat(plugins): built-in plugin manager

* style(fmt): rustfmt

* update plugins
This commit is contained in:
Aram Drevekenin 2024-09-30 18:04:40 +02:00 committed by GitHub
parent 3569daf7c9
commit 9f1e38f9fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 2095 additions and 133 deletions

9
Cargo.lock generated
View file

@ -3000,6 +3000,15 @@ version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]]
name = "plugin-manager"
version = "0.1.0"
dependencies = [
"fuzzy-matcher",
"uuid",
"zellij-tile",
]
[[package]] [[package]]
name = "polling" name = "polling"
version = "2.2.0" version = "2.2.0"

View file

@ -38,6 +38,7 @@ members = [
"default-plugins/fixture-plugin-for-tests", "default-plugins/fixture-plugin-for-tests",
"default-plugins/session-manager", "default-plugins/session-manager",
"default-plugins/configuration", "default-plugins/configuration",
"default-plugins/plugin-manager",
"zellij-client", "zellij-client",
"zellij-server", "zellij-server",
"zellij-utils", "zellij-utils",

View file

@ -1295,6 +1295,13 @@ keybinds clear-defaults=true {{
}}; }};
SwitchToMode "Locked" SwitchToMode "Locked"
}} }}
bind "p" {{
LaunchOrFocusPlugin "plugin-manager" {{
floating true
move_to_focused_tab true
}};
SwitchToMode "Locked"
}}
}} }}
shared_except "locked" "renametab" "renamepane" {{ shared_except "locked" "renametab" "renamepane" {{
bind "{primary_modifier} g" {{ SwitchToMode "Locked"; }} bind "{primary_modifier} g" {{ SwitchToMode "Locked"; }}
@ -1475,6 +1482,13 @@ keybinds clear-defaults=true {{
}}; }};
SwitchToMode "Normal" SwitchToMode "Normal"
}} }}
bind "p" {{
LaunchOrFocusPlugin "plugin-manager" {{
floating true
move_to_focused_tab true
}};
SwitchToMode "Normal"
}}
}} }}
tmux {{ tmux {{
bind "[" {{ SwitchToMode "Scroll"; }} bind "[" {{ SwitchToMode "Scroll"; }}
@ -1658,6 +1672,13 @@ keybinds clear-defaults=true {{
}}; }};
SwitchToMode "Normal" SwitchToMode "Normal"
}} }}
bind "p" {{
LaunchOrFocusPlugin "plugin-manager" {{
floating true
move_to_focused_tab true
}};
SwitchToMode "Normal"
}}
}} }}
tmux {{ tmux {{
bind "[" {{ SwitchToMode "Scroll"; }} bind "[" {{ SwitchToMode "Scroll"; }}
@ -1830,6 +1851,13 @@ keybinds clear-defaults=true {{
}}; }};
SwitchToMode "Normal" SwitchToMode "Normal"
}} }}
bind "p" {{
LaunchOrFocusPlugin "plugin-manager" {{
floating true
move_to_focused_tab true
}};
SwitchToMode "Normal"
}}
}} }}
tmux {{ tmux {{
bind "[" {{ SwitchToMode "Scroll"; }} bind "[" {{ SwitchToMode "Scroll"; }}
@ -2004,6 +2032,13 @@ keybinds clear-defaults=true {{
}}; }};
SwitchToMode "Normal" SwitchToMode "Normal"
}} }}
bind "p" {{
LaunchOrFocusPlugin "plugin-manager" {{
floating true
move_to_focused_tab true
}};
SwitchToMode "Normal"
}}
}} }}
tmux {{ tmux {{
bind "[" {{ SwitchToMode "Scroll"; }} bind "[" {{ SwitchToMode "Scroll"; }}
@ -2162,6 +2197,13 @@ keybinds clear-defaults=true {{
}}; }};
SwitchToMode "Normal" SwitchToMode "Normal"
}} }}
bind "p" {{
LaunchOrFocusPlugin "plugin-manager" {{
floating true
move_to_focused_tab true
}};
SwitchToMode "Normal"
}}
}} }}
tmux {{ tmux {{
bind "[" {{ SwitchToMode "Scroll"; }} bind "[" {{ SwitchToMode "Scroll"; }}

View file

@ -431,6 +431,20 @@ impl ZellijPlugin for State {
should_change_focus_to_target_tab, should_change_focus_to_target_tab,
); );
}, },
BareKey::Char('w') if key.has_modifiers(&[KeyModifier::Alt]) => {
reload_plugin_with_id(0);
},
BareKey::Char('x') if key.has_modifiers(&[KeyModifier::Alt]) => {
let config = BTreeMap::new();
let load_in_background = true;
let skip_plugin_cache = true;
load_new_plugin(
"zellij:OWN_URL",
config,
load_in_background,
skip_plugin_cache,
)
},
_ => {}, _ => {},
}, },
Event::CustomMessage(message, payload) => { Event::CustomMessage(message, payload) => {

View file

@ -0,0 +1,2 @@
[build]
target = "wasm32-wasi"

View file

@ -0,0 +1 @@
/target

View file

@ -0,0 +1,10 @@
[package]
name = "plugin-manager"
version = "0.1.0"
authors = ["Aram Drevekenin <aram@poor.dev>"]
edition = "2018"
[dependencies]
uuid = { version = "1.7.0", features = ["v4"] }
fuzzy-matcher = "0.3.7"
zellij-tile = { path = "../../zellij-tile" }

View file

@ -0,0 +1 @@
../../LICENSE.md

File diff suppressed because it is too large Load diff

View file

@ -1266,6 +1266,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<KeyWithModifier
(s("Detach"), s("Detach"), action_key(&km, &[Action::Detach])), (s("Detach"), s("Detach"), action_key(&km, &[Action::Detach])),
(s("Session Manager"), s("Manager"), session_manager_key(&km)), (s("Session Manager"), s("Manager"), session_manager_key(&km)),
(s("Configure"), s("Config"), configuration_key(&km)), (s("Configure"), s("Config"), configuration_key(&km)),
(s("Plugin Manager"), s("Plugins"), plugin_manager_key(&km)),
(s("Select pane"), s("Select"), to_basemode_key), (s("Select pane"), s("Select"), to_basemode_key),
]} else if mi.mode == IM::Tmux { vec![ ]} else if mi.mode == IM::Tmux { vec![
(s("Move focus"), s("Move"), action_key_group(&km, &[ (s("Move focus"), s("Move"), action_key_group(&km, &[
@ -1356,6 +1357,25 @@ fn session_manager_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWith
} }
} }
fn plugin_manager_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWithModifier> {
let mut matching = keymap.iter().find_map(|(key, acvec)| {
let has_match = acvec
.iter()
.find(|a| a.launches_plugin("plugin-manager"))
.is_some();
if has_match {
Some(key.clone())
} else {
None
}
});
if let Some(matching) = matching.take() {
vec![matching]
} else {
vec![]
}
}
fn configuration_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWithModifier> { fn configuration_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWithModifier> {
let mut matching = keymap.iter().find_map(|(key, acvec)| { let mut matching = keymap.iter().find_map(|(key, acvec)| {
let has_match = acvec let has_match = acvec

View file

@ -37,6 +37,7 @@ lazy_static::lazy_static! {
WorkspaceMember{crate_name: "default-plugins/fixture-plugin-for-tests", build: true}, WorkspaceMember{crate_name: "default-plugins/fixture-plugin-for-tests", build: true},
WorkspaceMember{crate_name: "default-plugins/session-manager", build: true}, WorkspaceMember{crate_name: "default-plugins/session-manager", build: true},
WorkspaceMember{crate_name: "default-plugins/configuration", build: true}, WorkspaceMember{crate_name: "default-plugins/configuration", build: true},
WorkspaceMember{crate_name: "default-plugins/plugin-manager", build: true},
WorkspaceMember{crate_name: "zellij-utils", build: false}, WorkspaceMember{crate_name: "zellij-utils", build: false},
WorkspaceMember{crate_name: "zellij-tile-utils", build: false}, WorkspaceMember{crate_name: "zellij-tile-utils", build: false},
WorkspaceMember{crate_name: "zellij-tile", build: false}, WorkspaceMember{crate_name: "zellij-tile", build: false},

View file

@ -5,6 +5,7 @@ use zellij_utils::consts::{
}; };
use zellij_utils::data::{Event, HttpVerb, SessionInfo}; use zellij_utils::data::{Event, HttpVerb, SessionInfo};
use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType}; use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType};
use zellij_utils::input::layout::RunPlugin;
use zellij_utils::surf::{ use zellij_utils::surf::{
http::{Method, Url}, http::{Method, Url},
RequestBuilder, RequestBuilder,
@ -34,6 +35,7 @@ pub enum BackgroundJob {
StopPluginLoadingAnimation(u32), // u32 - plugin_id StopPluginLoadingAnimation(u32), // u32 - plugin_id
ReadAllSessionInfosOnMachine, // u32 - plugin_id ReadAllSessionInfosOnMachine, // u32 - plugin_id
ReportSessionInfo(String, SessionInfo), // String - session name ReportSessionInfo(String, SessionInfo), // String - session name
ReportPluginList(BTreeMap<PluginId, RunPlugin>), // String - session name
ReportLayoutInfo((String, BTreeMap<String, String>)), // BTreeMap<file_name, pane_contents> ReportLayoutInfo((String, BTreeMap<String, String>)), // BTreeMap<file_name, pane_contents>
RunCommand( RunCommand(
PluginId, PluginId,
@ -71,6 +73,7 @@ impl From<&BackgroundJob> for BackgroundJobContext {
BackgroundJob::ReportLayoutInfo(..) => BackgroundJobContext::ReportLayoutInfo, BackgroundJob::ReportLayoutInfo(..) => BackgroundJobContext::ReportLayoutInfo,
BackgroundJob::RunCommand(..) => BackgroundJobContext::RunCommand, BackgroundJob::RunCommand(..) => BackgroundJobContext::RunCommand,
BackgroundJob::WebRequest(..) => BackgroundJobContext::WebRequest, BackgroundJob::WebRequest(..) => BackgroundJobContext::WebRequest,
BackgroundJob::ReportPluginList(..) => BackgroundJobContext::ReportPluginList,
BackgroundJob::Exit => BackgroundJobContext::Exit, BackgroundJob::Exit => BackgroundJobContext::Exit,
} }
} }
@ -91,6 +94,8 @@ pub(crate) fn background_jobs_main(
let mut loading_plugins: HashMap<u32, Arc<AtomicBool>> = HashMap::new(); // u32 - plugin_id let mut loading_plugins: HashMap<u32, Arc<AtomicBool>> = HashMap::new(); // u32 - plugin_id
let current_session_name = Arc::new(Mutex::new(String::default())); let current_session_name = Arc::new(Mutex::new(String::default()));
let current_session_info = Arc::new(Mutex::new(SessionInfo::default())); let current_session_info = Arc::new(Mutex::new(SessionInfo::default()));
let current_session_plugin_list: Arc<Mutex<BTreeMap<PluginId, RunPlugin>>> =
Arc::new(Mutex::new(BTreeMap::new()));
let current_session_layout = Arc::new(Mutex::new((String::new(), BTreeMap::new()))); let current_session_layout = Arc::new(Mutex::new((String::new(), BTreeMap::new())));
let last_serialization_time = Arc::new(Mutex::new(Instant::now())); let last_serialization_time = Arc::new(Mutex::new(Instant::now()));
let serialization_interval = serialization_interval.map(|s| s * 1000); // convert to let serialization_interval = serialization_interval.map(|s| s * 1000); // convert to
@ -152,6 +157,9 @@ pub(crate) fn background_jobs_main(
*current_session_name.lock().unwrap() = session_name; *current_session_name.lock().unwrap() = session_name;
*current_session_info.lock().unwrap() = session_info; *current_session_info.lock().unwrap() = session_info;
}, },
BackgroundJob::ReportPluginList(plugin_list) => {
*current_session_plugin_list.lock().unwrap() = plugin_list;
},
BackgroundJob::ReportLayoutInfo(session_layout) => { BackgroundJob::ReportLayoutInfo(session_layout) => {
*current_session_layout.lock().unwrap() = session_layout; *current_session_layout.lock().unwrap() = session_layout;
}, },
@ -168,6 +176,7 @@ pub(crate) fn background_jobs_main(
let current_session_info = current_session_info.clone(); let current_session_info = current_session_info.clone();
let current_session_name = current_session_name.clone(); let current_session_name = current_session_name.clone();
let current_session_layout = current_session_layout.clone(); let current_session_layout = current_session_layout.clone();
let current_session_plugin_list = current_session_plugin_list.clone();
let last_serialization_time = last_serialization_time.clone(); let last_serialization_time = last_serialization_time.clone();
async move { async move {
loop { loop {
@ -183,8 +192,16 @@ pub(crate) fn background_jobs_main(
current_session_layout, current_session_layout,
); );
} }
let session_infos_on_machine = let mut session_infos_on_machine =
read_other_live_session_states(&current_session_name); read_other_live_session_states(&current_session_name);
for (session_name, session_info) in session_infos_on_machine.iter_mut()
{
if session_name == &current_session_name {
let current_session_plugin_list =
current_session_plugin_list.lock().unwrap().clone();
session_info.populate_plugin_list(current_session_plugin_list);
}
}
let resurrectable_sessions = let resurrectable_sessions =
find_resurrectable_sessions(&session_infos_on_machine); find_resurrectable_sessions(&session_infos_on_machine);
let _ = senders.send_to_screen(ScreenInstruction::UpdateSessionInfos( let _ = senders.send_to_screen(ScreenInstruction::UpdateSessionInfos(

View file

@ -14,6 +14,7 @@ use std::{
}; };
use wasmtime::Engine; use wasmtime::Engine;
use crate::background_jobs::BackgroundJob;
use crate::panes::PaneId; use crate::panes::PaneId;
use crate::screen::ScreenInstruction; use crate::screen::ScreenInstruction;
use crate::session_layout_metadata::SessionLayoutMetadata; use crate::session_layout_metadata::SessionLayoutMetadata;
@ -49,13 +50,14 @@ pub enum PluginInstruction {
bool, // should be opened in place bool, // should be opened in place
Option<String>, // pane title Option<String>, // pane title
RunPluginOrAlias, RunPluginOrAlias,
usize, // tab index Option<usize>, // tab index
Option<PaneId>, // pane id to replace if this is to be opened "in-place" Option<PaneId>, // pane id to replace if this is to be opened "in-place"
ClientId, ClientId,
Size, Size,
Option<PathBuf>, // cwd Option<PathBuf>, // cwd
bool, // skip cache bool, // skip cache
), ),
LoadBackgroundPlugin(RunPluginOrAlias, ClientId),
Update(Vec<(Option<PluginId>, Option<ClientId>, Event)>), // Focused plugin / broadcast, client_id, event data Update(Vec<(Option<PluginId>, Option<ClientId>, Event)>), // Focused plugin / broadcast, client_id, event data
Unload(PluginId), // plugin_id Unload(PluginId), // plugin_id
Reload( Reload(
@ -65,6 +67,7 @@ pub enum PluginInstruction {
usize, // tab index usize, // tab index
Size, Size,
), ),
ReloadPluginWithId(u32),
Resize(PluginId, usize, usize), // plugin_id, columns, rows Resize(PluginId, usize, usize), // plugin_id, columns, rows
AddClient(ClientId), AddClient(ClientId),
RemoveClient(ClientId), RemoveClient(ClientId),
@ -162,9 +165,11 @@ impl From<&PluginInstruction> for PluginContext {
fn from(plugin_instruction: &PluginInstruction) -> Self { fn from(plugin_instruction: &PluginInstruction) -> Self {
match *plugin_instruction { match *plugin_instruction {
PluginInstruction::Load(..) => PluginContext::Load, PluginInstruction::Load(..) => PluginContext::Load,
PluginInstruction::LoadBackgroundPlugin(..) => PluginContext::LoadBackgroundPlugin,
PluginInstruction::Update(..) => PluginContext::Update, PluginInstruction::Update(..) => PluginContext::Update,
PluginInstruction::Unload(..) => PluginContext::Unload, PluginInstruction::Unload(..) => PluginContext::Unload,
PluginInstruction::Reload(..) => PluginContext::Reload, PluginInstruction::Reload(..) => PluginContext::Reload,
PluginInstruction::ReloadPluginWithId(..) => PluginContext::ReloadPluginWithId,
PluginInstruction::Resize(..) => PluginContext::Resize, PluginInstruction::Resize(..) => PluginContext::Resize,
PluginInstruction::Exit => PluginContext::Exit, PluginInstruction::Exit => PluginContext::Exit,
PluginInstruction::AddClient(_) => PluginContext::AddClient, PluginInstruction::AddClient(_) => PluginContext::AddClient,
@ -279,7 +284,7 @@ pub(crate) fn plugin_thread_main(
let start_suppressed = false; let start_suppressed = false;
match wasm_bridge.load_plugin( match wasm_bridge.load_plugin(
&run_plugin, &run_plugin,
Some(tab_index), tab_index,
size, size,
cwd.clone(), cwd.clone(),
skip_cache, skip_cache,
@ -292,7 +297,7 @@ pub(crate) fn plugin_thread_main(
should_be_open_in_place, should_be_open_in_place,
run_plugin_or_alias, run_plugin_or_alias,
pane_title, pane_title,
Some(tab_index), tab_index,
plugin_id, plugin_id,
pane_id_to_replace, pane_id_to_replace,
cwd, cwd,
@ -305,6 +310,15 @@ pub(crate) fn plugin_thread_main(
}, },
} }
}, },
PluginInstruction::LoadBackgroundPlugin(run_plugin_or_alias, client_id) => {
load_background_plugin(
run_plugin_or_alias,
&mut wasm_bridge,
&bus,
&plugin_aliases,
client_id,
);
},
PluginInstruction::Update(updates) => { PluginInstruction::Update(updates) => {
wasm_bridge.update_plugins(updates, shutdown_send.clone())?; wasm_bridge.update_plugins(updates, shutdown_send.clone())?;
}, },
@ -379,6 +393,9 @@ pub(crate) fn plugin_thread_main(
}, },
} }
}, },
PluginInstruction::ReloadPluginWithId(plugin_id) => {
wasm_bridge.reload_plugin_with_id(plugin_id).non_fatal();
},
PluginInstruction::Resize(pid, new_columns, new_rows) => { PluginInstruction::Resize(pid, new_columns, new_rows) => {
wasm_bridge.resize_plugin(pid, new_columns, new_rows, shutdown_send.clone())?; wasm_bridge.resize_plugin(pid, new_columns, new_rows, shutdown_send.clone())?;
}, },

View file

@ -422,6 +422,8 @@ impl<'a> PluginLoader<'a> {
rows: running_plugin.rows, rows: running_plugin.rows,
cols: running_plugin.columns, cols: running_plugin.columns,
}; };
let keybinds = running_plugin.store.data().keybinds.clone();
let default_mode = running_plugin.store.data().default_mode;
let plugin_config = running_plugin.store.data().plugin.clone(); let plugin_config = running_plugin.store.data().plugin.clone();
loading_indication.set_name(running_plugin.store.data().name()); loading_indication.set_name(running_plugin.store.data().name());
PluginLoader::new( PluginLoader::new(

View file

@ -3,7 +3,7 @@ use crate::plugins::PluginId;
use bytes::Bytes; use bytes::Bytes;
use std::io::Write; use std::io::Write;
use std::{ use std::{
collections::{HashMap, HashSet, VecDeque}, collections::{BTreeMap, HashMap, HashSet, VecDeque},
path::PathBuf, path::PathBuf,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
@ -263,6 +263,24 @@ impl PluginMap {
} }
}) })
} }
pub fn list_plugins(&self) -> BTreeMap<PluginId, RunPlugin> {
let all_plugin_ids: HashSet<PluginId> = self
.all_plugin_ids()
.into_iter()
.map(|(plugin_id, _client_id)| plugin_id)
.collect();
let mut plugin_ids_to_cmds: BTreeMap<u32, RunPlugin> = BTreeMap::new();
for plugin_id in all_plugin_ids {
let plugin_cmd = self.run_plugin_of_plugin_id(plugin_id);
match plugin_cmd {
Some(plugin_cmd) => {
plugin_ids_to_cmds.insert(plugin_id, plugin_cmd.clone());
},
None => log::error!("Plugin with id: {plugin_id} not found"),
}
}
plugin_ids_to_cmds
}
} }
pub type Subscriptions = HashSet<EventType>; pub type Subscriptions = HashSet<EventType>;

View file

@ -708,7 +708,7 @@ pub fn load_new_plugin_from_hd() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -788,7 +788,7 @@ pub fn load_new_plugin_with_plugin_alias() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -864,7 +864,7 @@ pub fn plugin_workers() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -944,7 +944,7 @@ pub fn plugin_workers_persist_state() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -1034,7 +1034,7 @@ pub fn can_subscribe_to_hd_events() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -1112,7 +1112,7 @@ pub fn switch_to_mode_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -1184,7 +1184,7 @@ pub fn switch_to_mode_plugin_command_permission_denied() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -1256,7 +1256,7 @@ pub fn new_tabs_with_layout_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -1342,7 +1342,7 @@ pub fn new_tab_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -1414,7 +1414,7 @@ pub fn go_to_next_tab_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -1485,7 +1485,7 @@ pub fn go_to_previous_tab_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -1556,7 +1556,7 @@ pub fn resize_focused_pane_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -1627,7 +1627,7 @@ pub fn resize_focused_pane_with_direction_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -1698,7 +1698,7 @@ pub fn focus_next_pane_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -1769,7 +1769,7 @@ pub fn focus_previous_pane_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -1840,7 +1840,7 @@ pub fn move_focus_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -1911,7 +1911,7 @@ pub fn move_focus_or_tab_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -1982,7 +1982,7 @@ pub fn edit_scrollback_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -2053,7 +2053,7 @@ pub fn write_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -2124,7 +2124,7 @@ pub fn write_chars_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -2195,7 +2195,7 @@ pub fn toggle_tab_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -2266,7 +2266,7 @@ pub fn move_pane_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -2337,7 +2337,7 @@ pub fn move_pane_with_direction_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -2409,7 +2409,7 @@ pub fn clear_screen_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -2481,7 +2481,7 @@ pub fn scroll_up_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -2552,7 +2552,7 @@ pub fn scroll_down_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -2623,7 +2623,7 @@ pub fn scroll_to_top_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -2694,7 +2694,7 @@ pub fn scroll_to_bottom_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -2765,7 +2765,7 @@ pub fn page_scroll_up_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -2836,7 +2836,7 @@ pub fn page_scroll_down_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -2907,7 +2907,7 @@ pub fn toggle_focus_fullscreen_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -2978,7 +2978,7 @@ pub fn toggle_pane_frames_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -3049,7 +3049,7 @@ pub fn toggle_pane_embed_or_eject_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -3120,7 +3120,7 @@ pub fn undo_rename_pane_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -3191,7 +3191,7 @@ pub fn close_focus_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -3262,7 +3262,7 @@ pub fn toggle_active_tab_sync_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -3333,7 +3333,7 @@ pub fn close_focused_tab_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -3404,7 +3404,7 @@ pub fn undo_rename_tab_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -3475,7 +3475,7 @@ pub fn previous_swap_layout_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -3546,7 +3546,7 @@ pub fn next_swap_layout_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -3617,7 +3617,7 @@ pub fn go_to_tab_name_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -3688,7 +3688,7 @@ pub fn focus_or_create_tab_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -3759,7 +3759,7 @@ pub fn go_to_tab() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -3830,7 +3830,7 @@ pub fn start_or_reload_plugin() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -3908,7 +3908,7 @@ pub fn quit_zellij_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -3986,7 +3986,7 @@ pub fn detach_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -4064,7 +4064,7 @@ pub fn open_file_floating_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -4146,7 +4146,7 @@ pub fn open_file_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -4229,7 +4229,7 @@ pub fn open_file_with_line_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -4311,7 +4311,7 @@ pub fn open_file_with_line_floating_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -4393,7 +4393,7 @@ pub fn open_terminal_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -4471,7 +4471,7 @@ pub fn open_terminal_floating_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -4549,7 +4549,7 @@ pub fn open_command_pane_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -4627,7 +4627,7 @@ pub fn open_command_pane_floating_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -4698,7 +4698,7 @@ pub fn switch_to_tab_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -4769,7 +4769,7 @@ pub fn hide_self_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -4839,7 +4839,7 @@ pub fn show_self_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -4910,7 +4910,7 @@ pub fn close_terminal_pane_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -4981,7 +4981,7 @@ pub fn close_plugin_pane_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -5052,7 +5052,7 @@ pub fn focus_terminal_pane_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -5123,7 +5123,7 @@ pub fn focus_plugin_pane_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -5194,7 +5194,7 @@ pub fn rename_terminal_pane_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -5265,7 +5265,7 @@ pub fn rename_plugin_pane_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -5336,7 +5336,7 @@ pub fn rename_tab_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -5416,7 +5416,7 @@ pub fn send_configuration_to_plugins() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -5484,7 +5484,7 @@ pub fn request_plugin_permissions() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -5576,7 +5576,7 @@ pub fn granted_permission_request_result() {
false, false,
plugin_title, plugin_title,
run_plugin.clone(), run_plugin.clone(),
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -5667,7 +5667,7 @@ pub fn denied_permission_request_result() {
false, false,
plugin_title, plugin_title,
run_plugin.clone(), run_plugin.clone(),
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -5738,7 +5738,7 @@ pub fn run_command_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -5816,7 +5816,7 @@ pub fn run_command_with_env_vars_and_cwd_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -5894,7 +5894,7 @@ pub fn web_request_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -5965,7 +5965,7 @@ pub fn unblock_input_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -6047,7 +6047,7 @@ pub fn block_input_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -6137,7 +6137,7 @@ pub fn pipe_output_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -6220,7 +6220,7 @@ pub fn pipe_message_to_plugin_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -6314,7 +6314,7 @@ pub fn switch_session_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -6395,7 +6395,7 @@ pub fn switch_session_with_layout_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -6476,7 +6476,7 @@ pub fn switch_session_with_layout_and_cwd_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -6557,7 +6557,7 @@ pub fn disconnect_other_clients_plugins_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -6638,7 +6638,7 @@ pub fn reconfigure_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -6723,7 +6723,7 @@ pub fn run_plugin_in_specific_cwd() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -6797,7 +6797,7 @@ pub fn hide_pane_with_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -6868,7 +6868,7 @@ pub fn show_pane_with_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -6946,7 +6946,7 @@ pub fn open_command_pane_background_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -7021,7 +7021,7 @@ pub fn rerun_command_pane_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -7092,7 +7092,7 @@ pub fn resize_pane_with_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -7163,7 +7163,7 @@ pub fn edit_scrollback_for_pane_with_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -7234,7 +7234,7 @@ pub fn write_to_pane_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -7305,7 +7305,7 @@ pub fn write_chars_to_pane_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -7376,7 +7376,7 @@ pub fn move_pane_with_pane_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -7447,7 +7447,7 @@ pub fn move_pane_with_pane_id_in_direction_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -7518,7 +7518,7 @@ pub fn clear_screen_for_pane_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -7589,7 +7589,7 @@ pub fn scroll_up_in_pane_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -7660,7 +7660,7 @@ pub fn scroll_down_in_pane_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -7731,7 +7731,7 @@ pub fn scroll_to_top_in_pane_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -7802,7 +7802,7 @@ pub fn scroll_to_bottom_in_pane_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -7873,7 +7873,7 @@ pub fn page_scroll_up_in_pane_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -7944,7 +7944,7 @@ pub fn page_scroll_down_in_pane_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -8015,7 +8015,7 @@ pub fn toggle_pane_id_fullscreen_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -8086,7 +8086,7 @@ pub fn toggle_pane_embed_or_eject_for_pane_id_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -8157,7 +8157,7 @@ pub fn close_tab_with_index_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -8228,7 +8228,7 @@ pub fn break_panes_to_new_tab_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -8299,7 +8299,7 @@ pub fn break_panes_to_tab_with_index_plugin_command() {
false, false,
plugin_title, plugin_title,
run_plugin, run_plugin,
tab_index, Some(tab_index),
None, None,
client_id, client_id,
size, size,
@ -8328,3 +8328,145 @@ pub fn break_panes_to_tab_with_index_plugin_command() {
.clone(); .clone();
assert_snapshot!(format!("{:#?}", screen_instruction)); assert_snapshot!(format!("{:#?}", screen_instruction));
} }
#[test]
#[ignore]
pub fn reload_plugin_plugin_command() {
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 cache_path = plugin_host_folder.join("permissions_test.kdl");
let (plugin_thread_sender, screen_receiver, 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 = RunPluginOrAlias::RunPlugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
..Default::default()
});
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 = grant_permissions_and_log_actions_in_thread_naked_variant!(
received_screen_instructions,
ScreenInstruction::RequestStateUpdateForPlugins, // happens on successful plugin (re)load
screen_receiver,
3,
&PermissionType::ChangeApplicationState,
cache_path,
plugin_thread_sender,
client_id
);
let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
let _ = plugin_thread_sender.send(PluginInstruction::Load(
plugin_should_float,
false,
plugin_title,
run_plugin,
Some(tab_index),
None,
client_id,
size,
None,
false,
));
std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
Event::Key(KeyWithModifier::new(BareKey::Char('w')).with_alt_modifier()), // this triggers the enent in the fixture plugin
)]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown();
let request_state_update_requests = received_screen_instructions
.lock()
.unwrap()
.iter()
.filter(|i| {
if let ScreenInstruction::RequestStateUpdateForPlugins = i {
true
} else {
false
}
})
.count();
assert_eq!(request_state_update_requests, 3);
}
#[test]
#[ignore]
pub fn load_new_plugin_plugin_command() {
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 cache_path = plugin_host_folder.join("permissions_test.kdl");
let (plugin_thread_sender, screen_receiver, 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 = RunPluginOrAlias::RunPlugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
..Default::default()
});
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 = grant_permissions_and_log_actions_in_thread_naked_variant!(
received_screen_instructions,
ScreenInstruction::RequestStateUpdateForPlugins, // happens on successful plugin (re)load
screen_receiver,
3,
&PermissionType::ChangeApplicationState,
cache_path,
plugin_thread_sender,
client_id
);
let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
let _ = plugin_thread_sender.send(PluginInstruction::Load(
plugin_should_float,
false,
plugin_title,
run_plugin,
Some(tab_index),
None,
client_id,
size,
None,
false,
));
std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
Event::Key(KeyWithModifier::new(BareKey::Char('x')).with_alt_modifier()), // this triggers the enent in the fixture plugin
)]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown();
let request_state_update_requests = received_screen_instructions
.lock()
.unwrap()
.iter()
.filter(|i| {
if let ScreenInstruction::RequestStateUpdateForPlugins = i {
true
} else {
false
}
})
.count();
assert_eq!(request_state_update_requests, 3);
}

View file

@ -10,7 +10,7 @@ use crate::plugins::zellij_exports::{wasi_read_string, wasi_write_object};
use highway::{HighwayHash, PortableHash}; use highway::{HighwayHash, PortableHash};
use log::info; use log::info;
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{BTreeMap, HashMap, HashSet},
path::PathBuf, path::PathBuf,
str::FromStr, str::FromStr,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
@ -258,7 +258,7 @@ impl WasmBridge {
plugin_cache, plugin_cache,
senders.clone(), senders.clone(),
engine, engine,
plugin_map, plugin_map.clone(),
size, size,
connected_clients.clone(), connected_clients.clone(),
&mut loading_indication, &mut loading_indication,
@ -273,7 +273,10 @@ impl WasmBridge {
default_mode, default_mode,
keybinds, keybinds,
) { ) {
Ok(_) => handle_plugin_successful_loading(&senders, plugin_id), Ok(_) => {
let plugin_list = plugin_map.lock().unwrap().list_plugins();
handle_plugin_successful_loading(&senders, plugin_id, plugin_list);
},
Err(e) => handle_plugin_loading_failure( Err(e) => handle_plugin_loading_failure(
&senders, &senders,
plugin_id, plugin_id,
@ -327,6 +330,84 @@ impl WasmBridge {
.send_to_server(ServerInstruction::UnblockCliPipeInput(pipe_name)) .send_to_server(ServerInstruction::UnblockCliPipeInput(pipe_name))
.context("failed to unblock input pipe"); .context("failed to unblock input pipe");
} }
let plugin_list = plugin_map.list_plugins();
let _ = self
.senders
.send_to_background_jobs(BackgroundJob::ReportPluginList(plugin_list));
Ok(())
}
pub fn reload_plugin_with_id(&mut self, plugin_id: u32) -> Result<()> {
let Some(run_plugin) = self.run_plugin_of_plugin_id(plugin_id).map(|r| r.clone()) else {
log::error!("Failed to find plugin with id: {}", plugin_id);
return Ok(());
};
let (rows, columns) = self.size_of_plugin_id(plugin_id).unwrap_or((0, 0));
self.cached_events_for_pending_plugins
.insert(plugin_id, vec![]);
self.cached_resizes_for_pending_plugins
.insert(plugin_id, (rows, columns));
let mut loading_indication = LoadingIndication::new(run_plugin.location.to_string());
self.start_plugin_loading_indication(&[plugin_id], &loading_indication);
let load_plugin_task = task::spawn({
let plugin_dir = self.plugin_dir.clone();
let plugin_cache = self.plugin_cache.clone();
let senders = self.senders.clone();
let engine = self.engine.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();
let capabilities = self.capabilities.clone();
let client_attributes = self.client_attributes.clone();
let default_shell = self.default_shell.clone();
let default_layout = self.default_layout.clone();
let layout_dir = self.layout_dir.clone();
let base_modes = self.base_modes.clone();
let keybinds = self.keybinds.clone();
async move {
match PluginLoader::reload_plugin(
plugin_id,
plugin_dir.clone(),
plugin_cache.clone(),
senders.clone(),
engine.clone(),
plugin_map.clone(),
connected_clients.clone(),
&mut loading_indication,
path_to_default_shell.clone(),
zellij_cwd.clone(),
capabilities.clone(),
client_attributes.clone(),
default_shell.clone(),
default_layout.clone(),
layout_dir.clone(),
&base_modes,
&keybinds,
) {
Ok(_) => {
let plugin_list = plugin_map.lock().unwrap().list_plugins();
handle_plugin_successful_loading(&senders, plugin_id, plugin_list);
},
Err(e) => {
handle_plugin_loading_failure(
&senders,
plugin_id,
&mut loading_indication,
&e,
None,
);
},
}
let _ = senders.send_to_plugin(PluginInstruction::ApplyCachedEvents {
plugin_ids: vec![plugin_id],
done_receiving_permissions: false,
});
}
});
self.loading_plugins
.insert((plugin_id, run_plugin.clone()), load_plugin_task);
Ok(()) Ok(())
} }
pub fn reload_plugin(&mut self, run_plugin: &RunPlugin) -> Result<()> { pub fn reload_plugin(&mut self, run_plugin: &RunPlugin) -> Result<()> {
@ -386,7 +467,8 @@ impl WasmBridge {
&keybinds, &keybinds,
) { ) {
Ok(_) => { Ok(_) => {
handle_plugin_successful_loading(&senders, first_plugin_id); let plugin_list = plugin_map.lock().unwrap().list_plugins();
handle_plugin_successful_loading(&senders, first_plugin_id, plugin_list);
for plugin_id in &plugin_ids { for plugin_id in &plugin_ids {
if plugin_id == &first_plugin_id { if plugin_id == &first_plugin_id {
// no need to reload the plugin we just reloaded // no need to reload the plugin we just reloaded
@ -412,7 +494,14 @@ impl WasmBridge {
&base_modes, &base_modes,
&keybinds, &keybinds,
) { ) {
Ok(_) => handle_plugin_successful_loading(&senders, *plugin_id), Ok(_) => {
let plugin_list = plugin_map.lock().unwrap().list_plugins();
handle_plugin_successful_loading(
&senders,
*plugin_id,
plugin_list,
);
},
Err(e) => handle_plugin_loading_failure( Err(e) => handle_plugin_loading_failure(
&senders, &senders,
*plugin_id, *plugin_id,
@ -1303,9 +1392,14 @@ impl WasmBridge {
} }
} }
fn handle_plugin_successful_loading(senders: &ThreadSenders, plugin_id: PluginId) { fn handle_plugin_successful_loading(
senders: &ThreadSenders,
plugin_id: PluginId,
plugin_list: BTreeMap<PluginId, RunPlugin>,
) {
let _ = senders.send_to_background_jobs(BackgroundJob::StopPluginLoadingAnimation(plugin_id)); let _ = senders.send_to_background_jobs(BackgroundJob::StopPluginLoadingAnimation(plugin_id));
let _ = senders.send_to_screen(ScreenInstruction::RequestStateUpdateForPlugins); let _ = senders.send_to_screen(ScreenInstruction::RequestStateUpdateForPlugins);
let _ = senders.send_to_background_jobs(BackgroundJob::ReportPluginList(plugin_list));
} }
fn handle_plugin_loading_failure( fn handle_plugin_loading_failure(

View file

@ -5,7 +5,7 @@ use crate::plugins::wasm_bridge::handle_plugin_crash;
use crate::pty::{ClientTabIndexOrPaneId, PtyInstruction}; use crate::pty::{ClientTabIndexOrPaneId, PtyInstruction};
use crate::route::route_action; use crate::route::route_action;
use crate::ServerInstruction; use crate::ServerInstruction;
use log::{debug, warn}; use log::warn;
use serde::Serialize; use serde::Serialize;
use std::{ use std::{
collections::{BTreeMap, HashSet}, collections::{BTreeMap, HashSet},
@ -339,6 +339,13 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) {
tab_index, tab_index,
should_change_focus_to_new_tab, should_change_focus_to_new_tab,
), ),
PluginCommand::ReloadPlugin(plugin_id) => reload_plugin(env, plugin_id),
PluginCommand::LoadNewPlugin {
url,
config,
load_in_background,
skip_plugin_cache,
} => load_new_plugin(env, url, config, load_in_background, skip_plugin_cache),
}, },
(PermissionStatus::Denied, permission) => { (PermissionStatus::Denied, permission) => {
log::error!( log::error!(
@ -1347,12 +1354,20 @@ fn close_terminal_pane(env: &PluginEnv, terminal_pane_id: u32) {
let error_msg = || format!("failed to change tab focus in plugin {}", env.name()); let error_msg = || format!("failed to change tab focus in plugin {}", env.name());
let action = Action::CloseTerminalPane(terminal_pane_id); let action = Action::CloseTerminalPane(terminal_pane_id);
apply_action!(action, error_msg, env); apply_action!(action, error_msg, env);
env.senders
.send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(
terminal_pane_id,
)))
.non_fatal();
} }
fn close_plugin_pane(env: &PluginEnv, plugin_pane_id: u32) { fn close_plugin_pane(env: &PluginEnv, plugin_pane_id: u32) {
let error_msg = || format!("failed to change tab focus in plugin {}", env.name()); let error_msg = || format!("failed to change tab focus in plugin {}", env.name());
let action = Action::ClosePluginPane(plugin_pane_id); let action = Action::ClosePluginPane(plugin_pane_id);
apply_action!(action, error_msg, env); apply_action!(action, error_msg, env);
env.senders
.send_to_plugin(PluginInstruction::Unload(plugin_pane_id))
.non_fatal();
} }
fn focus_terminal_pane(env: &PluginEnv, terminal_pane_id: u32, should_float_if_hidden: bool) { fn focus_terminal_pane(env: &PluginEnv, terminal_pane_id: u32, should_float_if_hidden: bool) {
@ -1634,6 +1649,70 @@ fn break_panes_to_tab_with_index(
}); });
} }
fn reload_plugin(env: &PluginEnv, plugin_id: u32) {
let _ = env
.senders
.send_to_plugin(PluginInstruction::ReloadPluginWithId(plugin_id));
}
fn load_new_plugin(
env: &PluginEnv,
url: String,
config: BTreeMap<String, String>,
load_in_background: bool,
skip_plugin_cache: bool,
) {
let url = if &url == "zellij:OWN_URL" {
env.plugin.location.display()
} else {
url
};
if load_in_background {
match RunPluginOrAlias::from_url(&url, &Some(config), None, Some(env.plugin_cwd.clone())) {
Ok(run_plugin_or_alias) => {
let _ = env
.senders
.send_to_plugin(PluginInstruction::LoadBackgroundPlugin(
run_plugin_or_alias,
env.client_id,
));
},
Err(e) => {
log::error!("Failed to load new plugin: {:?}", e);
},
}
} else {
let should_float = Some(true);
let should_be_open_in_place = false;
let pane_title = None;
let tab_index = None;
let pane_id_to_replace = None;
let client_id = env.client_id;
let size = Default::default();
let cwd = Some(env.plugin_cwd.clone());
let skip_cache = skip_plugin_cache;
match RunPluginOrAlias::from_url(&url, &Some(config), None, Some(env.plugin_cwd.clone())) {
Ok(run_plugin_or_alias) => {
let _ = env.senders.send_to_plugin(PluginInstruction::Load(
should_float,
should_be_open_in_place,
pane_title,
run_plugin_or_alias,
tab_index,
pane_id_to_replace,
client_id,
size,
cwd,
skip_cache,
));
},
Err(e) => {
log::error!("Failed to load new plugin: {:?}", e);
},
}
}
}
// Custom panic handler for plugins. // Custom panic handler for plugins.
// //
// This is called when a panic occurs in a plugin. Since most panics will likely originate in the // This is called when a panic occurs in a plugin. Since most panics will likely originate in the
@ -1781,6 +1860,8 @@ fn check_command_permission(
| PluginCommand::CloseTabWithIndex(..) | PluginCommand::CloseTabWithIndex(..)
| PluginCommand::BreakPanesToNewTab(..) | PluginCommand::BreakPanesToNewTab(..)
| PluginCommand::BreakPanesToTabWithIndex(..) | PluginCommand::BreakPanesToTabWithIndex(..)
| PluginCommand::ReloadPlugin(..)
| PluginCommand::LoadNewPlugin { .. }
| PluginCommand::KillSessions(..) => PermissionType::ChangeApplicationState, | PluginCommand::KillSessions(..) => PermissionType::ChangeApplicationState,
PluginCommand::UnblockCliPipeInput(..) PluginCommand::UnblockCliPipeInput(..)
| PluginCommand::BlockCliPipeInput(..) | PluginCommand::BlockCliPipeInput(..)

View file

@ -1551,7 +1551,7 @@ impl Pty {
should_open_in_place, should_open_in_place,
pane_title, pane_title,
run, run,
tab_index, Some(tab_index),
pane_id_to_replace, pane_id_to_replace,
client_id, client_id,
size, size,

View file

@ -1615,6 +1615,7 @@ impl Screen {
connected_clients: self.active_tab_indices.keys().len(), connected_clients: self.active_tab_indices.keys().len(),
is_current_session: true, is_current_session: true,
available_layouts, available_layouts,
plugins: Default::default(), // these are filled in by the wasm thread
}; };
self.bus self.bus
.senders .senders

View file

@ -1062,6 +1062,34 @@ pub fn break_panes_to_tab_with_index(
unsafe { host_run_plugin_command() }; unsafe { host_run_plugin_command() };
} }
/// Reload an already-running in this session, optionally skipping the cache
pub fn reload_plugin_with_id(plugin_id: u32) {
let plugin_command = PluginCommand::ReloadPlugin(plugin_id);
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
unsafe { host_run_plugin_command() };
}
/// Reload an already-running in this session, optionally skipping the cache
pub fn load_new_plugin<S: AsRef<str>>(
url: S,
config: BTreeMap<String, String>,
load_in_background: bool,
skip_plugin_cache: bool,
) where
S: ToString,
{
let plugin_command = PluginCommand::LoadNewPlugin {
url: url.to_string(),
config,
load_in_background,
skip_plugin_cache,
};
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
unsafe { host_run_plugin_command() };
}
// Utility Functions // Utility Functions
#[allow(unused)] #[allow(unused)]

View file

@ -127,6 +127,13 @@ keybinds {
}; };
SwitchToMode "Normal" SwitchToMode "Normal"
} }
bind "p" {
LaunchOrFocusPlugin "plugin-manager" {
floating true
move_to_focused_tab true
};
SwitchToMode "Normal"
}
} }
tmux { tmux {
bind "[" { SwitchToMode "Scroll"; } bind "[" { SwitchToMode "Scroll"; }
@ -208,6 +215,7 @@ plugins {
cwd "/" cwd "/"
} }
configuration location="zellij:configuration" configuration location="zellij:configuration"
plugin-manager location="zellij:plugin-manager"
} }
// Plugins to load in the background when a new session starts // Plugins to load in the background when a new session starts

Binary file not shown.

View file

@ -270,6 +270,18 @@ pub struct SessionManifest {
pub is_current_session: bool, pub is_current_session: bool,
#[prost(message, repeated, tag = "6")] #[prost(message, repeated, tag = "6")]
pub available_layouts: ::prost::alloc::vec::Vec<LayoutInfo>, pub available_layouts: ::prost::alloc::vec::Vec<LayoutInfo>,
#[prost(message, repeated, tag = "7")]
pub plugins: ::prost::alloc::vec::Vec<PluginInfo>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PluginInfo {
#[prost(uint32, tag = "1")]
pub plugin_id: u32,
#[prost(string, tag = "2")]
pub plugin_url: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "3")]
pub plugin_config: ::prost::alloc::vec::Vec<ContextItem>,
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]

View file

@ -5,7 +5,7 @@ pub struct PluginCommand {
pub name: i32, pub name: i32,
#[prost( #[prost(
oneof = "plugin_command::Payload", oneof = "plugin_command::Payload",
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85" tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87"
)] )]
pub payload: ::core::option::Option<plugin_command::Payload>, pub payload: ::core::option::Option<plugin_command::Payload>,
} }
@ -168,10 +168,32 @@ pub mod plugin_command {
BreakPanesToNewTabPayload(super::BreakPanesToNewTabPayload), BreakPanesToNewTabPayload(super::BreakPanesToNewTabPayload),
#[prost(message, tag = "85")] #[prost(message, tag = "85")]
BreakPanesToTabWithIndexPayload(super::BreakPanesToTabWithIndexPayload), BreakPanesToTabWithIndexPayload(super::BreakPanesToTabWithIndexPayload),
#[prost(message, tag = "86")]
ReloadPluginPayload(super::ReloadPluginPayload),
#[prost(message, tag = "87")]
LoadNewPluginPayload(super::LoadNewPluginPayload),
} }
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct LoadNewPluginPayload {
#[prost(string, tag = "1")]
pub plugin_url: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "2")]
pub plugin_config: ::prost::alloc::vec::Vec<ContextItem>,
#[prost(bool, tag = "3")]
pub should_load_plugin_in_background: bool,
#[prost(bool, tag = "4")]
pub should_skip_plugin_cache: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ReloadPluginPayload {
#[prost(uint32, tag = "1")]
pub plugin_id: u32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BreakPanesToTabWithIndexPayload { pub struct BreakPanesToTabWithIndexPayload {
#[prost(message, repeated, tag = "1")] #[prost(message, repeated, tag = "1")]
pub pane_ids: ::prost::alloc::vec::Vec<PaneId>, pub pane_ids: ::prost::alloc::vec::Vec<PaneId>,
@ -660,6 +682,8 @@ pub enum CommandName {
CloseTabWithIndex = 107, CloseTabWithIndex = 107,
BreakPanesToNewTab = 108, BreakPanesToNewTab = 108,
BreakPanesToTabWithIndex = 109, BreakPanesToTabWithIndex = 109,
ReloadPlugin = 110,
LoadNewPlugin = 111,
} }
impl CommandName { impl CommandName {
/// String value of the enum field names used in the ProtoBuf definition. /// String value of the enum field names used in the ProtoBuf definition.
@ -780,6 +804,8 @@ impl CommandName {
CommandName::CloseTabWithIndex => "CloseTabWithIndex", CommandName::CloseTabWithIndex => "CloseTabWithIndex",
CommandName::BreakPanesToNewTab => "BreakPanesToNewTab", CommandName::BreakPanesToNewTab => "BreakPanesToNewTab",
CommandName::BreakPanesToTabWithIndex => "BreakPanesToTabWithIndex", CommandName::BreakPanesToTabWithIndex => "BreakPanesToTabWithIndex",
CommandName::ReloadPlugin => "ReloadPlugin",
CommandName::LoadNewPlugin => "LoadNewPlugin",
} }
} }
/// Creates an enum from field names used in the ProtoBuf definition. /// Creates an enum from field names used in the ProtoBuf definition.
@ -897,6 +923,8 @@ impl CommandName {
"CloseTabWithIndex" => Some(Self::CloseTabWithIndex), "CloseTabWithIndex" => Some(Self::CloseTabWithIndex),
"BreakPanesToNewTab" => Some(Self::BreakPanesToNewTab), "BreakPanesToNewTab" => Some(Self::BreakPanesToNewTab),
"BreakPanesToTabWithIndex" => Some(Self::BreakPanesToTabWithIndex), "BreakPanesToTabWithIndex" => Some(Self::BreakPanesToTabWithIndex),
"ReloadPlugin" => Some(Self::ReloadPlugin),
"LoadNewPlugin" => Some(Self::LoadNewPlugin),
_ => None, _ => None,
} }
} }

View file

@ -111,6 +111,7 @@ mod not_wasm {
add_plugin!(assets, "strider.wasm"); add_plugin!(assets, "strider.wasm");
add_plugin!(assets, "session-manager.wasm"); add_plugin!(assets, "session-manager.wasm");
add_plugin!(assets, "configuration.wasm"); add_plugin!(assets, "configuration.wasm");
add_plugin!(assets, "plugin-manager.wasm");
assets assets
}; };
} }

View file

@ -1,7 +1,7 @@
use crate::input::actions::Action; use crate::input::actions::Action;
use crate::input::config::ConversionError; use crate::input::config::ConversionError;
use crate::input::keybinds::Keybinds; use crate::input::keybinds::Keybinds;
use crate::input::layout::SplitSize; use crate::input::layout::{RunPlugin, SplitSize};
use clap::ArgEnum; use clap::ArgEnum;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
@ -1199,6 +1199,22 @@ pub struct SessionInfo {
pub connected_clients: usize, pub connected_clients: usize,
pub is_current_session: bool, pub is_current_session: bool,
pub available_layouts: Vec<LayoutInfo>, pub available_layouts: Vec<LayoutInfo>,
pub plugins: BTreeMap<u32, PluginInfo>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct PluginInfo {
pub location: String,
pub configuration: BTreeMap<String, String>,
}
impl From<RunPlugin> for PluginInfo {
fn from(run_plugin: RunPlugin) -> Self {
PluginInfo {
location: run_plugin.location.display(),
configuration: run_plugin.configuration.inner().clone(),
}
}
} }
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
@ -1253,6 +1269,14 @@ impl SessionInfo {
pub fn update_connected_clients(&mut self, new_connected_clients: usize) { pub fn update_connected_clients(&mut self, new_connected_clients: usize) {
self.connected_clients = new_connected_clients; self.connected_clients = new_connected_clients;
} }
pub fn populate_plugin_list(&mut self, plugins: BTreeMap<u32, RunPlugin>) {
// u32 - plugin_id
let mut plugin_list = BTreeMap::new();
for (plugin_id, run_plugin) in plugins {
plugin_list.insert(plugin_id, run_plugin.into());
}
self.plugins = plugin_list;
}
} }
/// Contains all the information for a currently opened tab. /// Contains all the information for a currently opened tab.
@ -1842,4 +1866,11 @@ pub enum PluginCommand {
// the new tab // the new tab
BreakPanesToTabWithIndex(Vec<PaneId>, usize, bool), // usize - tab_index, bool - BreakPanesToTabWithIndex(Vec<PaneId>, usize, bool), // usize - tab_index, bool -
// should_change_focus_to_new_tab // should_change_focus_to_new_tab
ReloadPlugin(u32), // u32 - plugin pane id
LoadNewPlugin {
url: String,
config: BTreeMap<String, String>,
load_in_background: bool,
skip_plugin_cache: bool,
},
} }

View file

@ -402,10 +402,12 @@ pub enum PtyContext {
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum PluginContext { pub enum PluginContext {
Load, Load,
LoadBackgroundPlugin,
Update, Update,
Render, Render,
Unload, Unload,
Reload, Reload,
ReloadPluginWithId,
Resize, Resize,
Exit, Exit,
AddClient, AddClient,
@ -503,6 +505,7 @@ pub enum BackgroundJobContext {
ReportLayoutInfo, ReportLayoutInfo,
RunCommand, RunCommand,
WebRequest, WebRequest,
ReportPluginList,
Exit, Exit,
} }

View file

@ -63,6 +63,7 @@ impl PluginConfig {
|| tag == "strider" || tag == "strider"
|| tag == "session-manager" || tag == "session-manager"
|| tag == "configuration" || tag == "configuration"
|| tag == "plugin-manager"
{ {
Some(PluginConfig { Some(PluginConfig {
path: PathBuf::from(&tag), path: PathBuf::from(&tag),

View file

@ -4127,6 +4127,7 @@ impl SessionInfo {
connected_clients, connected_clients,
is_current_session, is_current_session,
available_layouts, available_layouts,
plugins: Default::default(), // we do not serialize plugin information
}) })
} }
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
@ -4662,6 +4663,7 @@ fn serialize_and_deserialize_session_info_with_data() {
LayoutInfo::BuiltIn("layout2".to_owned()), LayoutInfo::BuiltIn("layout2".to_owned()),
LayoutInfo::File("layout3".to_owned()), LayoutInfo::File("layout3".to_owned()),
], ],
plugins: Default::default(),
}; };
let serialized = session_info.to_string(); let serialized = session_info.to_string();
let deserealized = SessionInfo::from_string(&serialized, "not this session").unwrap(); let deserealized = SessionInfo::from_string(&serialized, "not this session").unwrap();

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/kdl/mod.rs source: zellij-utils/src/kdl/mod.rs
assertion_line: 5525 assertion_line: 5535
expression: fake_config_stringified expression: fake_config_stringified
--- ---
keybinds clear-defaults=true { keybinds clear-defaults=true {
@ -107,6 +107,13 @@ keybinds clear-defaults=true {
SwitchToMode "normal" SwitchToMode "normal"
} }
bind "Ctrl o" { SwitchToMode "normal"; } bind "Ctrl o" { SwitchToMode "normal"; }
bind "p" {
LaunchOrFocusPlugin "plugin-manager" {
floating true
move_to_focused_tab true
}
SwitchToMode "normal"
}
bind "w" { bind "w" {
LaunchOrFocusPlugin "session-manager" { LaunchOrFocusPlugin "session-manager" {
floating true floating true
@ -229,6 +236,7 @@ plugins {
filepicker location="zellij:strider" { filepicker location="zellij:strider" {
cwd "/" cwd "/"
} }
plugin-manager location="zellij:plugin-manager"
session-manager location="zellij:session-manager" session-manager location="zellij:session-manager"
status-bar location="zellij:status-bar" status-bar location="zellij:status-bar"
strider location="zellij:strider" strider location="zellij:strider"

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/kdl/mod.rs source: zellij-utils/src/kdl/mod.rs
assertion_line: 5537 assertion_line: 5547
expression: fake_config_stringified expression: fake_config_stringified
--- ---
keybinds clear-defaults=true { keybinds clear-defaults=true {
@ -107,6 +107,13 @@ keybinds clear-defaults=true {
SwitchToMode "normal" SwitchToMode "normal"
} }
bind "Ctrl o" { SwitchToMode "normal"; } bind "Ctrl o" { SwitchToMode "normal"; }
bind "p" {
LaunchOrFocusPlugin "plugin-manager" {
floating true
move_to_focused_tab true
}
SwitchToMode "normal"
}
bind "w" { bind "w" {
LaunchOrFocusPlugin "session-manager" { LaunchOrFocusPlugin "session-manager" {
floating true floating true
@ -232,6 +239,7 @@ plugins {
filepicker location="zellij:strider" { filepicker location="zellij:strider" {
cwd "/" cwd "/"
} }
plugin-manager location="zellij:plugin-manager"
session-manager location="zellij:session-manager" session-manager location="zellij:session-manager"
status-bar location="zellij:status-bar" status-bar location="zellij:status-bar"
strider location="zellij:strider" strider location="zellij:strider"

View file

@ -223,6 +223,13 @@ message SessionManifest {
uint32 connected_clients = 4; uint32 connected_clients = 4;
bool is_current_session = 5; bool is_current_session = 5;
repeated LayoutInfo available_layouts = 6; repeated LayoutInfo available_layouts = 6;
repeated PluginInfo plugins = 7;
}
message PluginInfo {
uint32 plugin_id = 1;
string plugin_url = 2;
repeated ContextItem plugin_config = 3;
} }
message LayoutInfo { message LayoutInfo {

View file

@ -8,7 +8,7 @@ pub use super::generated_api::api::{
LayoutInfo as ProtobufLayoutInfo, ModeUpdatePayload as ProtobufModeUpdatePayload, LayoutInfo as ProtobufLayoutInfo, ModeUpdatePayload as ProtobufModeUpdatePayload,
PaneId as ProtobufPaneId, PaneInfo as ProtobufPaneInfo, PaneId as ProtobufPaneId, PaneInfo as ProtobufPaneInfo,
PaneManifest as ProtobufPaneManifest, PaneType as ProtobufPaneType, PaneManifest as ProtobufPaneManifest, PaneType as ProtobufPaneType,
ResurrectableSession as ProtobufResurrectableSession, PluginInfo as ProtobufPluginInfo, ResurrectableSession as ProtobufResurrectableSession,
SessionManifest as ProtobufSessionManifest, TabInfo as ProtobufTabInfo, *, SessionManifest as ProtobufSessionManifest, TabInfo as ProtobufTabInfo, *,
}, },
input_mode::InputMode as ProtobufInputMode, input_mode::InputMode as ProtobufInputMode,
@ -19,13 +19,13 @@ pub use super::generated_api::api::{
use crate::data::{ use crate::data::{
CopyDestination, Event, EventType, FileMetadata, InputMode, KeyWithModifier, LayoutInfo, CopyDestination, Event, EventType, FileMetadata, InputMode, KeyWithModifier, LayoutInfo,
ModeInfo, Mouse, PaneId, PaneInfo, PaneManifest, PermissionStatus, PluginCapabilities, ModeInfo, Mouse, PaneId, PaneInfo, PaneManifest, PermissionStatus, PluginCapabilities,
SessionInfo, Style, TabInfo, PluginInfo, SessionInfo, Style, TabInfo,
}; };
use crate::errors::prelude::*; use crate::errors::prelude::*;
use crate::input::actions::Action; use crate::input::actions::Action;
use std::collections::{HashMap, HashSet}; use std::collections::{BTreeMap, HashMap, HashSet};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::Duration; use std::time::Duration;
@ -667,10 +667,29 @@ impl TryFrom<SessionInfo> for ProtobufSessionManifest {
.into_iter() .into_iter()
.filter_map(|l| ProtobufLayoutInfo::try_from(l).ok()) .filter_map(|l| ProtobufLayoutInfo::try_from(l).ok())
.collect(), .collect(),
plugins: session_info
.plugins
.into_iter()
.map(|p| ProtobufPluginInfo::from(p))
.collect(),
}) })
} }
} }
impl From<(u32, PluginInfo)> for ProtobufPluginInfo {
fn from((plugin_id, plugin_info): (u32, PluginInfo)) -> ProtobufPluginInfo {
ProtobufPluginInfo {
plugin_id,
plugin_url: plugin_info.location,
plugin_config: plugin_info
.configuration
.into_iter()
.map(|(name, value)| ContextItem { name, value })
.collect(),
}
}
}
impl TryFrom<ProtobufSessionManifest> for SessionInfo { impl TryFrom<ProtobufSessionManifest> for SessionInfo {
type Error = &'static str; type Error = &'static str;
fn try_from(protobuf_session_manifest: ProtobufSessionManifest) -> Result<Self, &'static str> { fn try_from(protobuf_session_manifest: ProtobufSessionManifest) -> Result<Self, &'static str> {
@ -689,6 +708,20 @@ impl TryFrom<ProtobufSessionManifest> for SessionInfo {
let panes = PaneManifest { let panes = PaneManifest {
panes: pane_manifest, panes: pane_manifest,
}; };
let mut plugins = BTreeMap::new();
for plugin_info in protobuf_session_manifest.plugins.into_iter() {
let mut configuration = BTreeMap::new();
for context_item in plugin_info.plugin_config.into_iter() {
configuration.insert(context_item.name, context_item.value);
}
plugins.insert(
plugin_info.plugin_id,
PluginInfo {
location: plugin_info.plugin_url,
configuration,
},
);
}
Ok(SessionInfo { Ok(SessionInfo {
name: protobuf_session_manifest.name, name: protobuf_session_manifest.name,
tabs: protobuf_session_manifest tabs: protobuf_session_manifest
@ -704,6 +737,7 @@ impl TryFrom<ProtobufSessionManifest> for SessionInfo {
.into_iter() .into_iter()
.filter_map(|l| LayoutInfo::try_from(l).ok()) .filter_map(|l| LayoutInfo::try_from(l).ok())
.collect(), .collect(),
plugins,
}) })
} }
} }
@ -1702,6 +1736,16 @@ fn serialize_session_update_event_with_non_default_values() {
}, },
]; ];
panes.insert(0, panes_list); panes.insert(0, panes_list);
let mut plugins = BTreeMap::new();
let mut plugin_configuration = BTreeMap::new();
plugin_configuration.insert("config_key".to_owned(), "config_value".to_owned());
plugins.insert(
1,
PluginInfo {
location: "https://example.com/my-plugin.wasm".to_owned(),
configuration: plugin_configuration,
},
);
let session_info_1 = SessionInfo { let session_info_1 = SessionInfo {
name: "session 1".to_owned(), name: "session 1".to_owned(),
tabs: tab_infos, tabs: tab_infos,
@ -1713,6 +1757,7 @@ fn serialize_session_update_event_with_non_default_values() {
LayoutInfo::BuiltIn("layout2".to_owned()), LayoutInfo::BuiltIn("layout2".to_owned()),
LayoutInfo::File("layout3".to_owned()), LayoutInfo::File("layout3".to_owned()),
], ],
plugins,
}; };
let session_info_2 = SessionInfo { let session_info_2 = SessionInfo {
name: "session 2".to_owned(), name: "session 2".to_owned(),
@ -1727,6 +1772,7 @@ fn serialize_session_update_event_with_non_default_values() {
LayoutInfo::BuiltIn("layout2".to_owned()), LayoutInfo::BuiltIn("layout2".to_owned()),
LayoutInfo::File("layout3".to_owned()), LayoutInfo::File("layout3".to_owned()),
], ],
plugins: Default::default(),
}; };
let session_infos = vec![session_info_1, session_info_2]; let session_infos = vec![session_info_1, session_info_2];
let resurrectable_sessions = vec![]; let resurrectable_sessions = vec![];

View file

@ -121,6 +121,8 @@ enum CommandName {
CloseTabWithIndex = 107; CloseTabWithIndex = 107;
BreakPanesToNewTab = 108; BreakPanesToNewTab = 108;
BreakPanesToTabWithIndex = 109; BreakPanesToTabWithIndex = 109;
ReloadPlugin = 110;
LoadNewPlugin = 111;
} }
message PluginCommand { message PluginCommand {
@ -201,9 +203,22 @@ message PluginCommand {
CloseTabWithIndexPayload close_tab_with_index_payload = 83; CloseTabWithIndexPayload close_tab_with_index_payload = 83;
BreakPanesToNewTabPayload break_panes_to_new_tab_payload = 84; BreakPanesToNewTabPayload break_panes_to_new_tab_payload = 84;
BreakPanesToTabWithIndexPayload break_panes_to_tab_with_index_payload = 85; BreakPanesToTabWithIndexPayload break_panes_to_tab_with_index_payload = 85;
ReloadPluginPayload reload_plugin_payload = 86;
LoadNewPluginPayload load_new_plugin_payload = 87;
} }
} }
message LoadNewPluginPayload {
string plugin_url = 1;
repeated ContextItem plugin_config = 2;
bool should_load_plugin_in_background = 3;
bool should_skip_plugin_cache = 4;
}
message ReloadPluginPayload {
uint32 plugin_id = 1;
}
message BreakPanesToTabWithIndexPayload { message BreakPanesToTabWithIndexPayload {
repeated PaneId pane_ids = 1; repeated PaneId pane_ids = 1;
uint32 tab_index = 2; uint32 tab_index = 2;

View file

@ -9,18 +9,19 @@ pub use super::generated_api::api::{
FixedOrPercent as ProtobufFixedOrPercent, FixedOrPercent as ProtobufFixedOrPercent,
FixedOrPercentValue as ProtobufFixedOrPercentValue, FixedOrPercentValue as ProtobufFixedOrPercentValue,
FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, HidePaneWithIdPayload, FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, HidePaneWithIdPayload,
HttpVerb as ProtobufHttpVerb, IdAndNewName, KillSessionsPayload, MessageToPluginPayload, HttpVerb as ProtobufHttpVerb, IdAndNewName, KillSessionsPayload, LoadNewPluginPayload,
MovePaneWithPaneIdInDirectionPayload, MovePaneWithPaneIdPayload, MovePayload, MessageToPluginPayload, MovePaneWithPaneIdInDirectionPayload, MovePaneWithPaneIdPayload,
NewPluginArgs as ProtobufNewPluginArgs, NewTabsWithLayoutInfoPayload, MovePayload, NewPluginArgs as ProtobufNewPluginArgs, NewTabsWithLayoutInfoPayload,
OpenCommandPanePayload, OpenFilePayload, PageScrollDownInPaneIdPayload, OpenCommandPanePayload, OpenFilePayload, PageScrollDownInPaneIdPayload,
PageScrollUpInPaneIdPayload, PaneId as ProtobufPaneId, PaneType as ProtobufPaneType, PageScrollUpInPaneIdPayload, PaneId as ProtobufPaneId, PaneType as ProtobufPaneType,
PluginCommand as ProtobufPluginCommand, PluginMessagePayload, ReconfigurePayload, PluginCommand as ProtobufPluginCommand, PluginMessagePayload, ReconfigurePayload,
RequestPluginPermissionPayload, RerunCommandPanePayload, ResizePaneIdWithDirectionPayload, ReloadPluginPayload, RequestPluginPermissionPayload, RerunCommandPanePayload,
ResizePayload, RunCommandPayload, ScrollDownInPaneIdPayload, ScrollToBottomInPaneIdPayload, ResizePaneIdWithDirectionPayload, ResizePayload, RunCommandPayload,
ScrollToTopInPaneIdPayload, ScrollUpInPaneIdPayload, SetTimeoutPayload, ScrollDownInPaneIdPayload, ScrollToBottomInPaneIdPayload, ScrollToTopInPaneIdPayload,
ShowPaneWithIdPayload, SubscribePayload, SwitchSessionPayload, SwitchTabToPayload, ScrollUpInPaneIdPayload, SetTimeoutPayload, ShowPaneWithIdPayload, SubscribePayload,
TogglePaneEmbedOrEjectForPaneIdPayload, TogglePaneIdFullscreenPayload, UnsubscribePayload, SwitchSessionPayload, SwitchTabToPayload, TogglePaneEmbedOrEjectForPaneIdPayload,
WebRequestPayload, WriteCharsToPaneIdPayload, WriteToPaneIdPayload, TogglePaneIdFullscreenPayload, UnsubscribePayload, WebRequestPayload,
WriteCharsToPaneIdPayload, WriteToPaneIdPayload,
}, },
plugin_permission::PermissionType as ProtobufPermissionType, plugin_permission::PermissionType as ProtobufPermissionType,
resize::ResizeAction as ProtobufResizeAction, resize::ResizeAction as ProtobufResizeAction,
@ -1203,6 +1204,28 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
)), )),
_ => Err("Mismatched payload for BreakPanesToTabWithIndex"), _ => Err("Mismatched payload for BreakPanesToTabWithIndex"),
}, },
Some(CommandName::ReloadPlugin) => match protobuf_plugin_command.payload {
Some(Payload::ReloadPluginPayload(reload_plugin_payload)) => {
Ok(PluginCommand::ReloadPlugin(reload_plugin_payload.plugin_id))
},
_ => Err("Mismatched payload for ReloadPlugin"),
},
Some(CommandName::LoadNewPlugin) => match protobuf_plugin_command.payload {
Some(Payload::LoadNewPluginPayload(load_new_plugin_payload)) => {
Ok(PluginCommand::LoadNewPlugin {
url: load_new_plugin_payload.plugin_url,
config: load_new_plugin_payload
.plugin_config
.into_iter()
.map(|e| (e.name, e.value))
.collect(),
load_in_background: load_new_plugin_payload
.should_load_plugin_in_background,
skip_plugin_cache: load_new_plugin_payload.should_skip_plugin_cache,
})
},
_ => Err("Mismatched payload for LoadNewPlugin"),
},
None => Err("Unrecognized plugin command"), None => Err("Unrecognized plugin command"),
} }
} }
@ -1985,6 +2008,29 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
}, },
)), )),
}), }),
PluginCommand::ReloadPlugin(plugin_id) => Ok(ProtobufPluginCommand {
name: CommandName::ReloadPlugin as i32,
payload: Some(Payload::ReloadPluginPayload(ReloadPluginPayload {
plugin_id,
})),
}),
PluginCommand::LoadNewPlugin {
url,
config,
load_in_background,
skip_plugin_cache,
} => Ok(ProtobufPluginCommand {
name: CommandName::LoadNewPlugin as i32,
payload: Some(Payload::LoadNewPluginPayload(LoadNewPluginPayload {
plugin_url: url,
plugin_config: config
.into_iter()
.map(|(name, value)| ContextItem { name, value })
.collect(),
should_skip_plugin_cache: skip_plugin_cache,
should_load_plugin_in_background: load_in_background,
})),
}),
} }
} }
} }

View file

@ -4136,6 +4136,34 @@ Config {
Right, Right,
), ),
], ],
KeyWithModifier {
bare_key: Char(
'p',
),
key_modifiers: {},
}: [
LaunchOrFocusPlugin(
Alias(
PluginAlias {
name: "plugin-manager",
configuration: Some(
PluginUserConfiguration(
{},
),
),
initial_cwd: None,
run_plugin: None,
},
),
true,
true,
false,
false,
),
SwitchToMode(
Normal,
),
],
KeyWithModifier { KeyWithModifier {
bare_key: Char( bare_key: Char(
'p', 'p',
@ -5611,6 +5639,18 @@ Config {
"/", "/",
), ),
}, },
"plugin-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
PluginTag(
"plugin-manager",
),
),
configuration: PluginUserConfiguration(
{},
),
initial_cwd: None,
},
"session-manager": RunPlugin { "session-manager": RunPlugin {
_allow_exec_host_cmd: false, _allow_exec_host_cmd: false,
location: Zellij( location: Zellij(

View file

@ -4136,6 +4136,34 @@ Config {
Right, Right,
), ),
], ],
KeyWithModifier {
bare_key: Char(
'p',
),
key_modifiers: {},
}: [
LaunchOrFocusPlugin(
Alias(
PluginAlias {
name: "plugin-manager",
configuration: Some(
PluginUserConfiguration(
{},
),
),
initial_cwd: None,
run_plugin: None,
},
),
true,
true,
false,
false,
),
SwitchToMode(
Normal,
),
],
KeyWithModifier { KeyWithModifier {
bare_key: Char( bare_key: Char(
'p', 'p',
@ -5611,6 +5639,18 @@ Config {
"/", "/",
), ),
}, },
"plugin-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
PluginTag(
"plugin-manager",
),
),
configuration: PluginUserConfiguration(
{},
),
initial_cwd: None,
},
"session-manager": RunPlugin { "session-manager": RunPlugin {
_allow_exec_host_cmd: false, _allow_exec_host_cmd: false,
location: Zellij( location: Zellij(

View file

@ -160,6 +160,18 @@ Config {
"/", "/",
), ),
}, },
"plugin-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
PluginTag(
"plugin-manager",
),
),
configuration: PluginUserConfiguration(
{},
),
initial_cwd: None,
},
"session-manager": RunPlugin { "session-manager": RunPlugin {
_allow_exec_host_cmd: false, _allow_exec_host_cmd: false,
location: Zellij( location: Zellij(

View file

@ -4136,6 +4136,34 @@ Config {
Right, Right,
), ),
], ],
KeyWithModifier {
bare_key: Char(
'p',
),
key_modifiers: {},
}: [
LaunchOrFocusPlugin(
Alias(
PluginAlias {
name: "plugin-manager",
configuration: Some(
PluginUserConfiguration(
{},
),
),
initial_cwd: None,
run_plugin: None,
},
),
true,
true,
false,
false,
),
SwitchToMode(
Normal,
),
],
KeyWithModifier { KeyWithModifier {
bare_key: Char( bare_key: Char(
'p', 'p',
@ -5918,6 +5946,18 @@ Config {
"/", "/",
), ),
}, },
"plugin-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
PluginTag(
"plugin-manager",
),
),
configuration: PluginUserConfiguration(
{},
),
initial_cwd: None,
},
"session-manager": RunPlugin { "session-manager": RunPlugin {
_allow_exec_host_cmd: false, _allow_exec_host_cmd: false,
location: Zellij( location: Zellij(

View file

@ -4136,6 +4136,34 @@ Config {
Right, Right,
), ),
], ],
KeyWithModifier {
bare_key: Char(
'p',
),
key_modifiers: {},
}: [
LaunchOrFocusPlugin(
Alias(
PluginAlias {
name: "plugin-manager",
configuration: Some(
PluginUserConfiguration(
{},
),
),
initial_cwd: None,
run_plugin: None,
},
),
true,
true,
false,
false,
),
SwitchToMode(
Normal,
),
],
KeyWithModifier { KeyWithModifier {
bare_key: Char( bare_key: Char(
'p', 'p',
@ -5611,6 +5639,18 @@ Config {
"/", "/",
), ),
}, },
"plugin-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
PluginTag(
"plugin-manager",
),
),
configuration: PluginUserConfiguration(
{},
),
initial_cwd: None,
},
"session-manager": RunPlugin { "session-manager": RunPlugin {
_allow_exec_host_cmd: false, _allow_exec_host_cmd: false,
location: Zellij( location: Zellij(