aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2024-09-30 18:04:40 +0200
committerGitHub <noreply@github.com>2024-09-30 18:04:40 +0200
commit9f1e38f9fa6efd166f5205eaa8603d6b05a68b2b (patch)
tree3e41bdf65e4bb83fc08f3ad23f913630a97a3d97
parentdocs(changelog): background plugins (diff)
downloadsl0ppij-9f1e38f9fa6efd166f5205eaa8603d6b05a68b2b.tar.gz
sl0ppij-9f1e38f9fa6efd166f5205eaa8603d6b05a68b2b.tar.xz
sl0ppij-9f1e38f9fa6efd166f5205eaa8603d6b05a68b2b.zip
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
-rw-r--r--Cargo.lock9
-rw-r--r--Cargo.toml1
-rw-r--r--default-plugins/configuration/src/main.rs42
-rw-r--r--default-plugins/fixture-plugin-for-tests/src/main.rs14
-rw-r--r--default-plugins/plugin-manager/.cargo/config.toml2
-rw-r--r--default-plugins/plugin-manager/.gitignore1
-rw-r--r--default-plugins/plugin-manager/Cargo.toml10
l---------default-plugins/plugin-manager/LICENSE.md1
-rw-r--r--default-plugins/plugin-manager/src/main.rs1073
-rw-r--r--default-plugins/status-bar/src/one_line_ui.rs20
-rw-r--r--xtask/src/main.rs1
-rw-r--r--zellij-server/src/background_jobs.rs19
-rw-r--r--zellij-server/src/plugins/mod.rs23
-rw-r--r--zellij-server/src/plugins/plugin_loader.rs2
-rw-r--r--zellij-server/src/plugins/plugin_map.rs20
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs348
-rw-r--r--zellij-server/src/plugins/wasm_bridge.rs106
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs83
-rw-r--r--zellij-server/src/pty.rs2
-rw-r--r--zellij-server/src/screen.rs1
-rw-r--r--zellij-tile/src/shim.rs28
-rw-r--r--zellij-utils/assets/config/default.kdl8
-rwxr-xr-xzellij-utils/assets/plugins/compact-bar.wasmbin906653 -> 948044 bytes
-rwxr-xr-xzellij-utils/assets/plugins/configuration.wasmbin840947 -> 945062 bytes
-rwxr-xr-xzellij-utils/assets/plugins/fixture-plugin-for-tests.wasmbin893835 -> 953115 bytes
-rwxr-xr-xzellij-utils/assets/plugins/plugin-manager.wasmbin0 -> 942086 bytes
-rwxr-xr-xzellij-utils/assets/plugins/session-manager.wasmbin976065 -> 1019610 bytes
-rwxr-xr-xzellij-utils/assets/plugins/status-bar.wasmbin1060689 -> 1238622 bytes
-rwxr-xr-xzellij-utils/assets/plugins/strider.wasmbin983300 -> 1032356 bytes
-rwxr-xr-xzellij-utils/assets/plugins/tab-bar.wasmbin890283 -> 1019195 bytes
-rw-r--r--zellij-utils/assets/prost/api.event.rs12
-rw-r--r--zellij-utils/assets/prost/api.plugin_command.rs30
-rw-r--r--zellij-utils/src/consts.rs1
-rw-r--r--zellij-utils/src/data.rs35
-rw-r--r--zellij-utils/src/errors.rs3
-rw-r--r--zellij-utils/src/input/plugins.rs1
-rw-r--r--zellij-utils/src/kdl/mod.rs2
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap10
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap10
-rw-r--r--zellij-utils/src/plugin_api/event.proto7
-rw-r--r--zellij-utils/src/plugin_api/event.rs52
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.proto15
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.rs64
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap40
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap40
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap12
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap40
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap40
48 files changed, 2095 insertions, 133 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 6588cffb..779a9ce0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3001,6 +3001,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]]
+name = "plugin-manager"
+version = "0.1.0"
+dependencies = [
+ "fuzzy-matcher",
+ "uuid",
+ "zellij-tile",
+]
+
+[[package]]
name = "polling"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 63a663fe..a90122ec 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -38,6 +38,7 @@ members = [
"default-plugins/fixture-plugin-for-tests",
"default-plugins/session-manager",
"default-plugins/configuration",
+ "default-plugins/plugin-manager",
"zellij-client",
"zellij-server",
"zellij-utils",
diff --git a/default-plugins/configuration/src/main.rs b/default-plugins/configuration/src/main.rs
index bcdc4d63..dee18b5f 100644
--- a/default-plugins/configuration/src/main.rs
+++ b/default-plugins/configuration/src/main.rs
@@ -1295,6 +1295,13 @@ keybinds clear-defaults=true {{
}};
SwitchToMode "Locked"
}}
+ bind "p" {{
+ LaunchOrFocusPlugin "plugin-manager" {{
+ floating true
+ move_to_focused_tab true
+ }};
+ SwitchToMode "Locked"
+ }}
}}
shared_except "locked" "renametab" "renamepane" {{
bind "{primary_modifier} g" {{ SwitchToMode "Locked"; }}
@@ -1475,6 +1482,13 @@ keybinds clear-defaults=true {{
}};
SwitchToMode "Normal"
}}
+ bind "p" {{
+ LaunchOrFocusPlugin "plugin-manager" {{
+ floating true
+ move_to_focused_tab true
+ }};
+ SwitchToMode "Normal"
+ }}
}}
tmux {{
bind "[" {{ SwitchToMode "Scroll"; }}
@@ -1658,6 +1672,13 @@ keybinds clear-defaults=true {{
}};
SwitchToMode "Normal"
}}
+ bind "p" {{
+ LaunchOrFocusPlugin "plugin-manager" {{
+ floating true
+ move_to_focused_tab true
+ }};
+ SwitchToMode "Normal"
+ }}
}}
tmux {{
bind "[" {{ SwitchToMode "Scroll"; }}
@@ -1830,6 +1851,13 @@ keybinds clear-defaults=true {{
}};
SwitchToMode "Normal"
}}
+ bind "p" {{
+ LaunchOrFocusPlugin "plugin-manager" {{
+ floating true
+ move_to_focused_tab true
+ }};
+ SwitchToMode "Normal"
+ }}
}}
tmux {{
bind "[" {{ SwitchToMode "Scroll"; }}
@@ -2004,6 +2032,13 @@ keybinds clear-defaults=true {{
}};
SwitchToMode "Normal"
}}
+ bind "p" {{
+ LaunchOrFocusPlugin "plugin-manager" {{
+ floating true
+ move_to_focused_tab true
+ }};
+ SwitchToMode "Normal"
+ }}
}}
tmux {{
bind "[" {{ SwitchToMode "Scroll"; }}
@@ -2162,6 +2197,13 @@ keybinds clear-defaults=true {{
}};
SwitchToMode "Normal"
}}
+ bind "p" {{
+ LaunchOrFocusPlugin "plugin-manager" {{
+ floating true
+ move_to_focused_tab true
+ }};
+ SwitchToMode "Normal"
+ }}
}}
tmux {{
bind "[" {{ SwitchToMode "Scroll"; }}
diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs
index 92935346..35f8d574 100644
--- a/default-plugins/fixture-plugin-for-tests/src/main.rs
+++ b/default-plugins/fixture-plugin-for-tests/src/main.rs
@@ -431,6 +431,20 @@ impl ZellijPlugin for State {
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) => {
diff --git a/default-plugins/plugin-manager/.cargo/config.toml b/default-plugins/plugin-manager/.cargo/config.toml
new file mode 100644
index 00000000..6b77899c
--- /dev/null
+++ b/default-plugins/plugin-manager/.cargo/config.toml
@@ -0,0 +1,2 @@
+[build]
+target = "wasm32-wasi"
diff --git a/default-plugins/plugin-manager/.gitignore b/default-plugins/plugin-manager/.gitignore
new file mode 100644
index 00000000..ea8c4bf7
--- /dev/null
+++ b/default-plugins/plugin-manager/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/default-plugins/plugin-manager/Cargo.toml b/default-plugins/plugin-manager/Cargo.toml
new file mode 100644
index 00000000..71ef3626
--- /dev/null
+++ b/default-plugins/plugin-manager/Cargo.toml
@@ -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" }
diff --git a/default-plugins/plugin-manager/LICENSE.md b/default-plugins/plugin-manager/LICENSE.md
new file mode 120000
index 00000000..f0608a63
--- /dev/null
+++ b/default-plugins/plugin-manager/LICENSE.md
@@ -0,0 +1 @@
+../../LICENSE.md \ No newline at end of file
diff --git a/default-plugins/plugin-manager/src/main.rs b/default-plugins/plugin-manager/src/main.rs
new file mode 100644
index 00000000..bc451d8a
--- /dev/null
+++ b/default-plugins/plugin-manager/src/main.rs
@@ -0,0 +1,1073 @@
+use fuzzy_matcher::skim::SkimMatcherV2;
+use fuzzy_matcher::FuzzyMatcher;
+use uuid::Uuid;
+use zellij_tile::prelude::*;
+
+use std::collections::{BTreeMap, HashMap};
+
+pub struct SearchResult {
+ plugin_id: u32,
+ plugin_info: PluginInfo,
+ indices: Vec<usize>,
+ score: i64,
+}
+
+impl SearchResult {
+ pub fn new(plugin_id: u32, plugin_info: &PluginInfo, indices: Vec<usize>, score: i64) -> Self {
+ SearchResult {
+ plugin_id,
+ plugin_info: plugin_info.clone(),
+ indices,
+ score,
+ }
+ }
+}
+
+pub struct NewPluginScreen {
+ new_plugin_url: String,
+ new_plugin_config: Vec<(String, String)>, // key/val for easy in-place manipulation
+ new_config_key: String,
+ new_config_val: String,
+ entering_plugin_url: bool,
+ entering_config_key: bool,
+ entering_config_val: bool,
+ selected_config_index: Option<usize>,
+ request_ids: Vec<String>,
+ load_in_background: bool,
+}
+
+impl Default for NewPluginScreen {
+ fn default() -> Self {
+ NewPluginScreen {
+ new_plugin_url: String::new(),
+ new_plugin_config: vec![],
+ new_config_key: String::new(),
+ new_config_val: String::new(),
+ entering_plugin_url: true,
+ entering_config_key: false,
+ entering_config_val: false,
+ selected_config_index: None,
+ request_ids: vec![],
+ load_in_background: true,
+ }
+ }
+}
+
+impl NewPluginScreen {
+ pub fn render(&self, rows: usize, cols: usize) {
+ self.render_title(cols);
+ self.render_url_field(cols);
+ self.render_configuration_title();
+ let config_list_len = self.render_config_list(cols, rows.saturating_sub(10)); // 10 - the rest
+ self.render_background_toggle(6 + config_list_len + 1);
+ if !self.editing_configuration() {
+ self.render_help(rows);
+ }
+ }
+ fn render_title(&self, cols: usize) {
+ let title_text = format!("LOAD NEW PLUGIN");
+ let title_text_len = title_text.chars().count();
+ let title = Text::new(title_text);
+ print_text_with_coordinates(
+ title,
+ (cols / 2).saturating_sub(title_text_len / 2),
+ 0,
+ None,
+ None,
+ );
+ }
+ fn render_url_field(&self, cols: usize) {
+ let url_field = if self.entering_plugin_url {
+ let truncated_url =
+ truncate_string_start(&self.new_plugin_url, cols.saturating_sub(19)); // 17 the length of the prompt + 2 for padding and cursor
+ let text = format!("Enter Plugin URL: {}_", truncated_url);
+ Text::new(text).color_range(2, ..=16).color_range(3, 18..)
+ } else {
+ let truncated_url =
+ truncate_string_start(&self.new_plugin_url, cols.saturating_sub(18)); // 17 the length of the prompt + 1 for padding
+ let text = format!("Enter Plugin URL: {}", truncated_url);
+ Text::new(text).color_range(2, ..=16).color_range(0, 18..)
+ };
+ print_text_with_coordinates(url_field, 0, 2, None, None);
+ let url_helper =
+ NestedListItem::new(format!("<Ctrl d> - Load from Disk")).color_range(3, ..=8);
+ print_nested_list_with_coordinates(vec![url_helper], 0, 3, None, None);
+ }
+ fn render_configuration_title(&self) {
+ let configuration_title =
+ if !self.editing_configuration() && self.new_plugin_config.is_empty() {
+ Text::new(format!("Plugin Configuration: <TAB> - Edit"))
+ .color_range(2, ..=20)
+ .color_range(3, 22..=26)
+ } else if !self.editing_configuration() {
+ Text::new(format!(
+ "Plugin Configuration: <TAB> - Edit, <↓↑> - Navigate, <Del> - Delete"
+ ))
+ .color_range(2, ..=20)
+ .color_range(3, 22..=26)
+ .color_range(3, 36..=39)
+ .color_range(3, 53..=57)
+ } else {
+ Text::new(format!(
+ "Plugin Configuration: [Editing: <TAB> - Next, <ENTER> - Accept]"
+ ))
+ .color_range(2, ..=20)
+ .color_range(3, 32..=36)
+ .color_range(3, 46..=52)
+ };
+ print_text_with_coordinates(configuration_title, 0, 5, None, None);
+ }
+ fn editing_configuration(&self) -> bool {
+ self.entering_config_key || self.entering_config_val
+ }
+ fn render_config_list(&self, cols: usize, rows: usize) -> usize {
+ let mut items = vec![];
+ let mut more_config_items = 0;
+ for (i, (config_key, config_val)) in self.new_plugin_config.iter().enumerate() {
+ let is_selected = Some(i) == self.selected_config_index;
+ if i >= rows {
+ more_config_items += 1;
+ } else if is_selected && self.editing_config_line() {
+ items.push(self.render_editing_config_line(config_key, config_val, cols));
+ } else {
+ items.push(self.render_config_line(config_key, config_val, is_selected, cols));
+ }
+ }
+ if self.editing_new_config_line() {
+ items.push(self.render_editing_config_line(
+ &self.new_config_key,
+ &self.new_config_val,
+ cols,
+ ));
+ } else if items.is_empty() {
+ items.push(NestedListItem::new("<NO CONFIGURATION>").color_range(0, ..));
+ }
+ let config_list_len = items.len();
+ print_nested_list_with_coordinates(items, 0, 6, Some(cols), None);
+ if more_config_items > 0 {
+ let more_text = format!("[+{}]", more_config_items);
+ print_text_with_coordinates(
+ Text::new(more_text).color_range(1, ..),
+ 0,
+ 6 + config_list_len,
+ None,
+ None,
+ );
+ }
+ config_list_len
+ }
+ fn editing_config_line(&self) -> bool {
+ self.entering_config_key || self.entering_config_val
+ }
+ fn editing_new_config_line(&self) -> bool {
+ (self.entering_config_key || self.entering_config_val)
+ && self.selected_config_index.is_none()
+ }
+ fn render_editing_config_line(
+ &self,
+ config_key: &str,
+ config_val: &str,
+ config_line_max_len: usize,
+ ) -> NestedListItem {
+ let config_line_max_len = config_line_max_len.saturating_sub(6); // 3 - line padding, 1 -
+ // cursor, 2 ": "
+ let config_key_max_len = config_line_max_len / 2;
+ let config_val_max_len = config_line_max_len.saturating_sub(config_key_max_len);
+ let config_key = if config_key.chars().count() > config_key_max_len {
+ truncate_string_start(&config_key, config_key_max_len)
+ } else {
+ config_key.to_owned()
+ };
+ let config_val = if config_val.chars().count() > config_val_max_len {
+ truncate_string_start(&config_val, config_val_max_len)
+ } else {
+ config_val.to_owned()
+ };
+ if self.entering_config_key {
+ let val = if config_val.is_empty() {
+ "<EMPTY>".to_owned()
+ } else {
+ config_val
+ };
+ NestedListItem::new(format!("{}_: {}", config_key, val))
+ .color_range(3, ..=config_key.chars().count())
+ .color_range(1, config_key.chars().count() + 3..)
+ } else {
+ let key = if config_key.is_empty() {
+ "<EMPTY>".to_owned()
+ } else {
+ config_key
+ };
+ NestedListItem::new(format!("{}: {}_", key, config_val))
+ .color_range(0, ..key.chars().count())
+ .color_range(3, key.chars().count() + 2..)
+ }
+ }
+ fn render_config_line(
+ &self,
+ config_key: &str,
+ config_val: &str,
+ is_selected: bool,
+ config_line_max_len: usize,
+ ) -> NestedListItem {
+ let config_line_max_len = config_line_max_len.saturating_sub(5); // 3 - line padding,
+ // 2 - ": "
+ let config_key = if config_key.is_empty() {
+ "<EMPTY>"
+ } else {
+ config_key
+ };
+ let config_val = if config_val.is_empty() {
+ "<EMPTY>"
+ } else {
+ config_val
+ };
+ let config_key_max_len = config_line_max_len / 2;
+ let config_val_max_len = config_line_max_len.saturating_sub(config_key_max_len);
+ let config_key = if config_key.chars().count() > config_key_max_len {
+ truncate_string_start(&config_key, config_key_max_len)
+ } else {
+ config_key.to_owned()
+ };
+
+ let config_val = if config_val.chars().count() > config_val_max_len {
+ truncate_string_start(&config_val, config_val_max_len)
+ } else {
+ config_val.to_owned()
+ };
+ let mut item = NestedListItem::new(format!("{}: {}", config_key, config_val))
+ .color_range(0, ..config_key.chars().count())
+ .color_range(1, config_key.chars().count() + 2..);
+ if is_selected {
+ item = item.selected()
+ }
+ item
+ }
+ fn render_background_toggle(&self, y_coordinates: usize) {
+ let key_shortcuts_text = format!("Ctrl l");
+ print_text_with_coordinates(
+ Text::new(&key_shortcuts_text).color_range(3, ..),
+ 0,
+ y_coordinates,
+ None,
+ None,
+ );
+ let load_in_background_text = format!("Load in Background");
+ let load_in_foreground_text = format!("Load in Foreground");
+ let (load_in_background_ribbon, load_in_foreground_ribbon) = if self.load_in_background {
+ (
+ Text::new(&load_in_background_text).selected(),
+ Text::new(&load_in_foreground_text),
+ )
+ } else {
+ (
+ Text::new(&load_in_background_text),
+ Text::new(&load_in_foreground_text).selected(),
+ )
+ };
+ print_ribbon_with_coordinates(
+ load_in_background_ribbon,
+ key_shortcuts_text.chars().count() + 1,
+ y_coordinates,
+ None,
+ None,
+ );
+ print_ribbon_with_coordinates(
+ load_in_foreground_ribbon,
+ key_shortcuts_text.chars().count() + 1 + load_in_background_text.chars().count() + 4,
+ y_coordinates,
+ None,
+ None,
+ );
+ }
+ fn render_help(&self, rows: usize) {
+ let enter_line = Text::new(format!(
+ "Help: <ENTER> - Accept and Load Plugin, <ESC> - Cancel"
+ ))
+ .color_range(3, 6..=12)
+ .color_range(3, 40..=44);
+ print_text_with_coordinates(enter_line, 0, rows, None, None);
+ }
+ fn get_field_being_edited_mut(&mut self) -> Option<&mut String> {
+ if self.entering_plugin_url {
+ Some(&mut self.new_plugin_url)
+ } else {
+ match self.selected_config_index {
+ Some(selected_config_index) => {
+ if self.entering_config_key {
+ self.new_plugin_config
+ .get_mut(selected_config_index)
+ .map(|(key, _val)| key)
+ } else if self.entering_config_val {
+ self.new_plugin_config
+ .get_mut(selected_config_index)
+ .map(|(_key, val)| val)
+ } else {
+ None
+ }
+ },
+ None => {
+ if self.entering_config_key {
+ Some(&mut self.new_config_key)
+ } else if self.entering_config_val {
+ Some(&mut self.new_config_val)
+ } else {
+ None
+ }
+ },
+ }
+ }
+ }
+ fn add_edit_buffer_to_config(&mut self) {
+ if !self.new_config_key.is_empty() || !self.new_config_val.is_empty() {
+ self.new_plugin_config.push((
+ self.new_config_key.drain(..).collect(),
+ self.new_config_val.drain(..).collect(),
+ ));
+ }
+ }
+ pub fn handle_key(&mut self, key: KeyWithModifier) -> (bool, bool) {
+ let (mut should_render, mut should_close) = (false, false);
+
+ match key.bare_key {
+ BareKey::Char(character) if key.has_no_modifiers() && character != ' ' => {
+ if let Some(field) = self.get_field_being_edited_mut() {
+ field.push(character);
+ }
+ should_render = true;
+ },
+ BareKey::Backspace if key.has_no_modifiers() => {
+ if let Some(field) = self.get_field_being_edited_mut() {
+ field.pop();
+ }
+ should_render = true;
+ },
+ BareKey::Enter if key.has_no_modifiers() => {
+ if self.editing_configuration() {
+ self.add_edit_buffer_to_config();
+ self.entering_config_key = false;
+ self.entering_config_val = false;
+ self.entering_plugin_url = true;
+ should_render = true;
+ } else {
+ let plugin_url: String = self.new_plugin_url.drain(..).collect();
+ self.add_edit_buffer_to_config();
+ let config = self.new_plugin_config.drain(..).into_iter().collect();
+ let load_in_background = self.load_in_background;
+ let skip_plugin_cache = true;
+ load_new_plugin(plugin_url, config, load_in_background, skip_plugin_cache);
+ should_render = true;
+ should_close = true;
+ }
+ },
+ BareKey::Tab if key.has_no_modifiers() => {
+ if self.entering_plugin_url {
+ self.entering_plugin_url = false;
+ self.entering_config_key = true;
+ } else if self.entering_config_key {
+ self.entering_config_key = false;
+ self.entering_config_val = true;
+ } else if self.entering_config_val {
+ self.entering_config_val = false;
+ if self.selected_config_index.is_none() {
+ // new config, add it to the map
+ self.add_edit_buffer_to_config();
+ self.entering_config_key = true;
+ } else {
+ self.entering_plugin_url = true;
+ }
+ self.selected_config_index = None;
+ } else if self.selected_config_index.is_some() {
+ self.entering_config_key = true;
+ } else {
+ self.entering_plugin_url = true;
+ }
+ should_render = true;
+ },
+ BareKey::Esc if key.has_no_modifiers() => {
+ if self.entering_config_key
+ || self.entering_config_val
+ || self.selected_config_index.is_some()
+ {
+ self.entering_plugin_url = true;
+ self.entering_config_key = false;
+ self.entering_config_val = false;
+ self.selected_config_index = None;
+ self.add_edit_buffer_to_config();
+ should_render = true;
+ } else {
+ should_close = true;
+ }
+ },
+ BareKey::Down if key.has_no_modifiers() => {
+ if !self.editing_configuration() {
+ let max_len = self.new_plugin_config.len().saturating_sub(1);
+ let has_config_values = !self.new_plugin_config.is_empty();
+ if self.selected_config_index.is_none() && has_config_values {
+ self.selected_config_index = Some(0);
+ } else if self.selected_config_index == Some(max_len) {
+ self.selected_config_index = None;
+ } else {
+ self.selected_config_index = self.selected_config_index.map(|s| s + 1);
+ }
+ }
+ should_render = true;
+ },
+ BareKey::Up if key.has_no_modifiers() => {
+ if !self.editing_configuration() {
+ let max_len = self.new_plugin_config.len().saturating_sub(1);
+ let has_config_values = !self.new_plugin_config.is_empty();
+ if self.selected_config_index.is_none() && has_config_values {
+ self.selected_config_index = Some(max_len);
+ } else if self.selected_config_index == Some(0) {
+ self.selected_config_index = None;
+ } else {
+ self.selected_config_index =
+ self.selected_config_index.map(|s| s.saturating_sub(1));
+ }
+ }
+ should_render = true;
+ },
+ BareKey::Delete if key.has_no_modifiers() => {
+ if let Some(selected_config_index) = self.selected_config_index.take() {
+ self.new_plugin_config.remove(selected_config_index);
+ should_render = true;
+ }
+ },
+ BareKey::Char('d') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
+ let mut args = BTreeMap::new();
+ let request_id = Uuid::new_v4();
+ self.request_ids.push(request_id.to_string());
+ let mut config = BTreeMap::new();
+ config.insert("request_id".to_owned(), request_id.to_string());
+ args.insert("request_id".to_owned(), request_id.to_string());
+ pipe_message_to_plugin(
+ MessageToPlugin::new("filepicker")
+ .with_plugin_url("filepicker")
+ .with_plugin_config(config)
+ .new_plugin_instance_should_have_pane_title(
+ "Select a .wasm file to load as a plugin...",
+ )
+ .with_args(args),
+ );
+ },
+ BareKey::Char('l') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
+ self.load_in_background = !self.load_in_background;
+ should_render = true;
+ },
+ _ => {},
+ }
+
+ (should_render, should_close)
+ }
+}
+
+#[derive(Default)]
+struct State {
+ userspace_configuration: BTreeMap<String, String>,
+ plugins: BTreeMap<u32, PluginInfo>,
+ search_results: Vec<SearchResult>,
+ selected_index: Option<usize>,
+ expanded_indices: Vec<usize>,
+ tab_position_to_tab_name: HashMap<usize, String>,
+ plugin_id_to_tab_position: HashMap<u32, usize>,
+ search_term: String,
+ new_plugin_screen: Option<NewPluginScreen>,
+}
+
+register_plugin!(State);
+
+impl ZellijPlugin for State {
+ fn load(&mut self, configuration: BTreeMap<String, String>) {
+ self.userspace_configuration = configuration;
+ subscribe(&[
+ EventType::ModeUpdate,
+ EventType::PaneUpdate,
+ EventType::TabUpdate,
+ EventType::Key,
+ EventType::SessionUpdate,
+ EventType::PermissionRequestResult,
+ ]);
+ }
+ fn pipe(&mut self, pipe_message: PipeMessage) -> bool {
+ if pipe_message.name == "filepicker_result" {
+ match (pipe_message.payload, pipe_message.args.get("request_id")) {
+ (Some(payload), Some(request_id)) => {
+ match self
+ .new_plugin_screen
+ .as_mut()
+ .and_then(|n| n.request_ids.iter().position(|p| p == request_id))
+ {
+ Some(request_id_position) => {
+ self.new_plugin_screen
+ .as_mut()
+ .map(|n| n.request_ids.remove(request_id_position));
+ let chosen_plugin_location = std::path::PathBuf::from(payload);
+ self.new_plugin_screen.as_mut().map(|n| {
+ n.new_plugin_url =
+ format!("file:{}", chosen_plugin_location.display())
+ });
+ },
+ None => {
+ eprintln!("request id not found");
+ },
+ }
+ },
+ _ => {},
+ }
+ true
+ } else {
+ false
+ }
+ }
+ fn update(&mut self, event: Event) -> bool {
+ let mut should_render = false;
+ match event {
+ Event::PermissionRequestResult(_) => {
+ let own_plugin_id = get_plugin_ids().plugin_id;
+ rename_plugin_pane(own_plugin_id, "Plugin Manager");
+ should_render = true;
+ },
+ Event::SessionUpdate(live_sessions, _dead_sessions) => {
+ for session in live_sessions {
+ if session.is_current_session {
+ if session.plugins != self.plugins {
+ self.plugins = session.plugins;
+ self.reset_selection();
+ self.update_search_term();
+ }
+ for tab in session.tabs {
+ self.tab_position_to_tab_name.insert(tab.position, tab.name);
+ }
+ }
+ }
+ should_render = true;
+ },
+ Event::PaneUpdate(pane_manifest) => {
+ for (tab_position, panes) in pane_manifest.panes {
+ for pane_info in panes {
+ if pane_info.is_plugin {
+ self.plugin_id_to_tab_position
+ .insert(pane_info.id, tab_position);
+ }
+ }
+ }
+ },
+ Event::Key(key) => match self.new_plugin_screen.as_mut() {
+ Some(new_plugin_screen) => {
+ let (should_render_new_plugin_screen, should_close_new_plugin_screen) =
+ new_plugin_screen.handle_key(key);
+ if should_close_new_plugin_screen {
+ self.new_plugin_screen = None;
+ should_render = true;
+ } else {
+ should_render = should_render_new_plugin_screen;
+ }
+ },
+ None => should_render = self.handle_main_screen_key(key),
+ },
+ _ => (),
+ };
+ should_render
+ }
+
+ fn render(&mut self, rows: usize, cols: usize) {
+ match &self.new_plugin_screen {
+ Some(new_plugin_screen) => {
+ new_plugin_screen.render(rows, cols);
+ },
+ None => {
+ self.render_search(cols);
+ let list_y = 2;
+ let max_list_items = rows.saturating_sub(4); // 2 top padding, 2 bottom padding
+ let (selected_index_in_list, plugin_list) = if self.is_searching() {
+ self.render_search_results(cols)
+ } else {
+ self.render_plugin_list(cols)
+ };
+ let (more_above, more_below, truncated_list) = self.truncate_list_to_screen(
+ selected_index_in_list,
+ plugin_list,
+ max_list_items,
+ );
+ self.render_more_indication(
+ more_above,
+ more_below,
+ cols,
+ list_y,
+ truncated_list.len(),
+ );
+ print_nested_list_with_coordinates(truncated_list, 0, list_y, Some(cols), None);
+ self.render_help(rows, cols);
+ },
+ }
+ }
+}
+
+impl State {
+ fn render_search_results(&self, cols: usize) -> (Option<usize>, Vec<NestedListItem>) {
+ let mut selected_index_in_list = None;
+ let mut plugin_list = vec![];
+ for (i, search_result) in self.search_results.iter().enumerate() {
+ let is_selected = Some(i) == self.selected_index;
+ if is_selected {
+ selected_index_in_list = Some(plugin_list.len());
+ }
+ let is_expanded = self.expanded_indices.contains(&i);
+ plugin_list.append(&mut self.render_search_result(
+ search_result,
+ is_selected,
+ is_expanded,
+ cols,
+ None,
+ ));
+ }
+ (selected_index_in_list, plugin_list)
+ }
+ fn render_plugin_list(&self, cols: usize) -> (Option<usize>, Vec<NestedListItem>) {
+ let mut selected_index_in_list = None;
+ let mut plugin_list = vec![];
+ for (i, (plugin_id, plugin_info)) in self.plugins.iter().enumerate() {
+ let is_selected = Some(i) == self.selected_index;
+ let is_expanded = self.expanded_indices.contains(&i);
+ if is_selected {
+ selected_index_in_list = Some(plugin_list.len());
+ }
+ plugin_list.append(&mut self.render_plugin(
+ *plugin_id,
+ plugin_info,
+ is_selected,
+ is_expanded,
+ cols,
+ ));
+ }
+ (selected_index_in_list, plugin_list)
+ }
+ fn render_more_indication(
+ &self,
+ more_above: usize,
+ more_below: usize,
+ cols: usize,
+ list_y: usize,
+ list_len: usize,
+ ) {
+ if more_above > 0 {
+ let text = format!("↑ [+{}]", more_above);
+ let text_len = text.chars().count();
+ print_text_with_coordinates(
+ Text::new(text).color_range(1, ..),
+ cols.saturating_sub(text_len),
+ list_y.saturating_sub(1),
+ None,
+ None,
+ );
+ }
+ if more_below > 0 {
+ let text = format!("↓ [+{}]", more_below);
+ let text_len = text.chars().count();
+ print_text_with_coordinates(
+ Text::new(text).color_range(1, ..),
+ cols.saturating_sub(text_len),
+ list_y + list_len,
+ None,
+ None,
+ );
+ }
+ }
+ pub fn render_search(&self, cols: usize) {
+ let text = format!(" SEARCH: {}_", self.search_term);
+ if text.chars().count() <= cols {
+ let text = Text::new(text).color_range(3, 9..);
+ print_text_with_coordinates(text, 0, 0, None, None);
+ } else {
+ let truncated_search_term =
+ truncate_string_start(&self.search_term, cols.saturating_sub(10)); // 9 the length of the SEARCH prompt + 1 for the cursor
+ let text = format!(" SEARCH: {}_", truncated_search_term);
+ let text = Text::new(text).color_range(3, 9..);
+ print_text_with_coordinates(text, 0, 0, None, None);
+ }
+ }
+ pub fn render_plugin(
+ &self,
+ plugin_id: u32,
+ plugin_info: &PluginInfo,
+ is_selected: bool,
+ is_expanded: bool,
+ cols: usize,
+ ) -> Vec<NestedListItem> {
+ let mut items = vec![];
+ let plugin_location_len = plugin_info.location.chars().count();
+ let max_location_len = cols.saturating_sub(3); // 3 for the bulletin
+ let location_string = if plugin_location_len > max_location_len {
+ truncate_string_start(&plugin_info.location, max_location_len)
+ } else {
+ plugin_info.location.clone()
+ };
+ let mut item = self.render_plugin_line(location_string, None);
+ if is_selected {
+ item = item.selected();
+ }
+ items.push(item);
+ if is_expanded {
+ let tab_line = self.render_tab_line(plugin_id, cols);
+ items.push(tab_line);
+ if !plugin_info.configuration.is_empty() {
+ let config_line = NestedListItem::new(format!("Configuration:"))
+ .color_range(2, ..=13)
+ .indent(1);
+ items.push(config_line);
+ for (config_key, config_val) in &plugin_info.configuration {
+ items.push(self.render_config_line(config_key, config_val, cols))
+ }
+ }
+ }
+ items
+ }
+ fn render_config_line(
+ &self,
+ config_key: &str,
+ config_val: &str,
+ cols: usize,
+ ) -> NestedListItem {
+ let config_line_padding = 9; // 7, left padding + 2 for the ": " between key/val
+ let config_line_max_len = cols.saturating_sub(config_line_padding);
+ let config_key_max_len = config_line_max_len / 2;
+ let config_val_max_len = config_line_max_len.saturating_sub(config_key_max_len);
+ let config_key = if config_key.chars().count() > config_key_max_len {
+ truncate_string_start(&config_key, config_key_max_len)
+ } else {
+ config_key.to_owned()
+ };
+
+ let config_val = if config_val.chars().count() > config_val_max_len {
+ truncate_string_start(&config_val, config_val_max_len)
+ } else {
+ config_val.to_owned()
+ };
+ NestedListItem::new(format!("{}: {}", config_key, config_val))
+ .indent(2)
+ .color_range(0, ..config_key.chars().count())
+ .color_range(1, config_key.chars().count() + 2..)
+ }
+ pub fn render_search_result(
+ &self,
+ search_result: &SearchResult,
+ is_selected: bool,
+ is_expanded: bool,
+ cols: usize,
+ plus_indication: Option<usize>,
+ ) -> Vec<NestedListItem> {
+ let mut items = vec![];
+ let plugin_info = &search_result.plugin_info;
+ let plugin_id = search_result.plugin_id;
+ let indices = &search_result.indices;
+ let plus_indication_len = plus_indication
+ .map(|p| p.to_string().chars().count() + 4)
+ .unwrap_or(0); // 4 for the plus indication decorators and space
+ let max_location_len = cols.saturating_sub(plus_indication_len + 3); // 3 for the bulletin
+ let (location_string, indices) = if plugin_info.location.chars().count() <= max_location_len
+ {
+ (plugin_info.location.clone(), indices.clone())
+ } else {
+ truncate_search_result(&plugin_info.location, max_location_len, indices)
+ };
+ let mut item = match plus_indication {
+ Some(plus_indication) => self.render_plugin_line_with_plus_indication(
+ location_string,
+ plus_indication,
+ Some(indices),
+ ),
+ None => self.render_plugin_line(location_string, Some(indices)),
+ };
+ if is_selected {
+ item = item.selected();
+ }
+ items.push(item);
+ if is_expanded {
+ let tab_line = self.render_tab_line(plugin_id, cols);
+ items.push(tab_line);
+ if !plugin_info.configuration.is_empty() {
+ let config_line = NestedListItem::new(format!("Configuration:"))
+ .color_range(2, ..=13)
+ .indent(1);
+ items.push(config_line);
+ for (config_key, config_value) in &plugin_info.configuration {
+ items.push(self.render_config_line(config_key, config_value, cols))
+ }
+ }
+ }
+ items
+ }
+ fn render_plugin_line_with_plus_indication(
+ &self,
+ location_string: String,
+ plus_indication: usize,
+ indices: Option<Vec<usize>>,
+ ) -> NestedListItem {
+ let mut item = NestedListItem::new(&format!("{} [+{}]", location_string, plus_indication))
+ .color_range(0, ..)
+ .color_range(1, location_string.chars().count() + 1..);
+ if let Some(indices) = indices {
+ item = item.color_indices(3, indices);
+ }
+ item
+ }
+ fn render_plugin_line(
+ &self,
+ location_string: String,
+ indices: Option<Vec<usize>>,
+ ) -> NestedListItem {
+ let mut item = NestedListItem::new(location_string).color_range(0, ..);
+ if let Some(indices) = indices {
+ item = item.color_indices(3, indices);
+ }
+ item
+ }
+ fn render_tab_line(&self, plugin_id: u32, max_width: usize) -> NestedListItem {
+ let tab_of_plugin_id = self
+ .get_tab_of_plugin_id(plugin_id)
+ .unwrap_or_else(|| "N/A".to_owned());
+ let tab_line_padding_count = 10; // 5 the length of the "Tab: " + 5 for the left padding
+
+ let tab_of_plugin_id =
+ if tab_of_plugin_id.chars().count() + tab_line_padding_count > max_width {
+ truncate_string_start(
+ &tab_of_plugin_id,
+ max_width.saturating_sub(tab_line_padding_count),
+ )
+ } else {
+ tab_of_plugin_id
+ };
+
+ let tab_line = NestedListItem::new(format!("Tab: {}", tab_of_plugin_id))
+ .color_range(2, ..=3)
+ .indent(1);
+ tab_line
+ }
+ pub fn render_help(&self, y: usize, cols: usize) {
+ let full_text = "Help: <←↓↑→> - Navigate/Expand, <ENTER> - focus, <TAB> - Reload, <Del> - Close, <INSERT> - New";
+ let middle_text =
+ "Help: <←↓↑→/ENTER> - Navigate, <TAB> - Reload, <Del> - Close, <INSERT> - New";
+ let short_text = "<←↓↑→/ENTER/TAB/Del> - Navigate/Expand/Reload/Close, <INSERT> - New";
+ if cols >= full_text.chars().count() {
+ let text = Text::new(full_text)
+ .color_range(3, 5..=11)
+ .color_range(3, 32..=38)
+ .color_range(3, 49..=53)
+ .color_range(3, 65..=69)
+ .color_range(3, 80..=87);
+ print_text_with_coordinates(text, 0, y, Some(cols), None);
+ } else if cols >= middle_text.chars().count() {
+ let text = Text::new(middle_text)
+ .color_range(3, 6..=17)
+ .color_range(3, 31..=35)
+ .color_range(3, 47..=51)
+ .color_range(3, 62..=69);
+ print_text_with_coordinates(text, 0, y, Some(cols), None);
+ } else {
+ let text = Text::new(short_text)
+ .color_range(3, ..=21)
+ .color_range(3, 53..=60);
+ print_text_with_coordinates(text, 0, y, Some(cols), None);
+ }
+ }
+ pub fn selected_plugin_id(&self) -> Option<u32> {
+ if self.is_searching() {
+ self.selected_index
+ .and_then(|i| self.search_results.iter().nth(i))
+ .map(|search_result| search_result.plugin_id)
+ } else {
+ self.selected_index
+ .and_then(|i| self.plugins.iter().nth(i))
+ .map(|(id, _)| *id)
+ }
+ }
+ pub fn focus_selected(&self) {
+ if let Some(selected_plugin_id) = self.selected_plugin_id() {
+ focus_pane_with_id(PaneId::Plugin(selected_plugin_id), true);
+ }
+ }
+ pub fn reload_selected(&self) {
+ if let Some(selected_plugin_id) = self.selected_plugin_id() {
+ reload_plugin_with_id(selected_plugin_id);
+ }
+ }
+ pub fn close_selected(&self) {
+ if let Some(selected_plugin_id) = self.selected_plugin_id() {
+ close_plugin_pane(selected_plugin_id);
+ }
+ }
+ pub fn reset_selection(&mut self) {
+ self.selected_index = None;
+ self.expanded_indices.clear();
+ }
+ pub fn expand_selected(&mut self) {
+ if let Some(selected_index) = &self.selected_index {
+ self.expanded_indices.push(*selected_index);
+ }
+ }
+ pub fn collapse_selected(&mut self) {
+ if let Some(selected_index) = &self.selected_index {
+ self.expanded_indices.retain(|i| i != selected_index);
+ }
+ }
+ pub fn get_tab_of_plugin_id(&self, plugin_id: u32) -> Option<String> {
+ self.plugin_id_to_tab_position
+ .get(&plugin_id)
+ .and_then(|plugin_id| self.tab_position_to_tab_name.get(plugin_id))
+ .cloned()
+ }
+ pub fn update_search_term(&mut self) {
+ if self.search_term.is_empty() {
+ self.search_results.clear();
+ } else {
+ let mut matches = vec![];
+ let matcher = SkimMatcherV2::default().use_cache(true);
+ for (plugin_id, plugin_info) in &self.plugins {
+ if let Some((score, indices)) =
+ matcher.fuzzy_indices(&plugin_info.location, &self.search_term)
+ {
+ matches.push(SearchResult::new(*plugin_id, plugin_info, indices, score));
+ }
+ }
+ matches.sort_by(|a, b| b.score.cmp(&a.score));
+ self.search_results = matches;
+ }
+ }
+ pub fn handle_main_screen_key(&mut self, key: KeyWithModifier) -> bool {
+ let mut should_render = false;
+ match key.bare_key {
+ BareKey::Char(character) if key.has_no_modifiers() && character != ' ' => {
+ self.search_term.push(character);
+ self.update_search_term();
+ self.reset_selection();
+ should_render = true;
+ },
+ BareKey::Backspace if key.has_no_modifiers() => {
+ self.search_term.pop();
+ self.update_search_term();
+ self.reset_selection();
+ should_render = true;
+ },
+ BareKey::Down if key.has_no_modifiers() => {
+ let max_len = if self.is_searching() {
+ self.search_results.len().saturating_sub(1)
+ } else {
+ self.plugins.keys().len().saturating_sub(1)
+ };
+ if self.selected_index.is_none() {
+ self.selected_index = Some(0);
+ } else if self.selected_index == Some(max_len) {
+ self.selected_index = None;
+ } else {
+ self.selected_index = self.selected_index.map(|s| s + 1);
+ }
+ should_render = true;
+ },
+ BareKey::Up if key.has_no_modifiers() => {
+ let max_len = if self.is_searching() {
+ self.plugins.keys().len().saturating_sub(1)
+ } else {
+ self.search_results.len().saturating_sub(1)
+ };
+ if self.selected_index.is_none() {
+ self.selected_index = Some(max_len);
+ } else if self.selected_index == Some(0) {
+ self.selected_index = None;
+ } else {
+ self.selected_index = self.selected_index.map(|s| s.saturating_sub(1));
+ }
+ should_render = true;
+ },
+ BareKey::Enter if key.has_no_modifiers() => {
+ self.focus_selected();
+ },
+ BareKey::Right if key.has_no_modifiers() => {
+ self.expand_selected();
+ should_render = true;
+ },
+ BareKey::Left if key.has_no_modifiers() => {
+ self.collapse_selected();
+ should_render = true;
+ },
+ BareKey::Tab if key.has_no_modifiers() => {
+ self.reload_selected();
+ },
+ BareKey::Insert if key.has_no_modifiers() => {
+ self.new_plugin_screen = Some(Default::default());
+ should_render = true;
+ },
+ BareKey::Delete if key.has_no_modifiers() => {
+ self.close_selected();
+ },
+ BareKey::Esc if key.has_no_modifiers() => {
+ self.search_term.clear();
+ self.update_search_term();
+ should_render = true;
+ },
+ _ => {},
+ }
+ should_render
+ }
+ pub fn is_searching(&self) -> bool {
+ self.search_term.len() > 0
+ }
+ fn truncate_list_to_screen(
+ &self,
+ selected_index_in_list: Option<usize>,
+ mut plugin_list: Vec<NestedListItem>,
+ max_list_items: usize,
+ ) -> (usize, usize, Vec<NestedListItem>) {
+ let mut more_above = 0;
+ let mut more_below = 0;
+ if plugin_list.len() > max_list_items {
+ let anchor_line = selected_index_in_list.unwrap_or(0);
+ let list_start = anchor_line.saturating_sub(max_list_items / 2);
+ let list_end = (list_start + max_list_items).saturating_sub(1);
+ let mut to_render = vec![];
+ for (i, item) in plugin_list.drain(..).enumerate() {
+ if i >= list_start && i < list_end {
+ to_render.push(item);
+ } else if i >= list_end {
+ more_below += 1;
+ } else if i < list_start {
+ more_above += 1;
+ }
+ }
+ plugin_list = to_render;
+ }
+ (more_above, more_below, plugin_list)
+ }
+}
+
+fn truncate_string_start(string_to_truncate: &str, max_len: usize) -> String {
+ let mut truncated_string = string_to_truncate.to_owned();
+ let count_to_remove = truncated_string.chars().count().saturating_sub(max_len) + 5;
+ if truncated_string.chars().count() > max_len {
+ truncated_string.replace_range(0..count_to_remove, "[...]");
+ }
+ truncated_string
+}
+
+fn truncate_search_result(
+ plugin_location: &str,
+ max_location_len: usize,
+ indices: &Vec<usize>,
+) -> (String, Vec<usize>) {
+ let truncated_location = truncate_string_start(&plugin_location, max_location_len);
+ let truncated_count = plugin_location
+ .chars()
+ .count()
+ .saturating_sub(max_location_len);
+ let adjusted_indices = indices
+ .iter()
+ .filter_map(|i| {
+ if i.saturating_sub(truncated_count) >= 5 {
+ Some(i.saturating_sub(truncated_count))
+ } else {
+ None
+ }
+ })
+ .collect();
+ (truncated_location, adjusted_indices)
+}
diff --git a/default-plugins/status-bar/src/one_line_ui.rs b/default-plugins/status-bar/src/one_line_ui.rs
index 8d1227af..0a2c3fac 100644
--- a/default-plugins/status-bar/src/one_line_ui.rs
+++ b/default-plugins/status-bar/src/one_line_ui.rs
@@ -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("Session Manager"), s("Manager"), session_manager_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),
]} else if mi.mode == IM::Tmux { vec![
(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> {
let mut matching = keymap.iter().find_map(|(key, acvec)| {
let has_match = acvec
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 28bd7b3c..88effcea 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -37,6 +37,7 @@ lazy_static::lazy_static! {
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/configuration", build: true},
+ WorkspaceMember{crate_name: "default-plugins/plugin-manager", build: true},
WorkspaceMember{crate_name: "zellij-utils", build: false},
WorkspaceMember{crate_name: "zellij-tile-utils", build: false},
WorkspaceMember{crate_name: "zellij-tile", build: false},
diff --git a/zellij-server/src/background_jobs.rs b/zellij-server/src/background_jobs.rs
index d1bd0585..1f984536 100644
--- a/zellij-server/src/background_jobs.rs
+++ b/zellij-server/src/background_jobs.rs
@@ -5,6 +5,7 @@ use zellij_utils::consts::{
};
use zellij_utils::data::{Event, HttpVerb, SessionInfo};
use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType};
+use zellij_utils::input::layout::RunPlugin;
use zellij_utils::surf::{
http::{Method, Url},
RequestBuilder,
@@ -34,6 +35,7 @@ pub enum BackgroundJob {
StopPluginLoadingAnimation(u32), // u32 - plugin_id
ReadAllSessionInfosOnMachine, // u32 - plugin_id
ReportSessionInfo(String, SessionInfo), // String - session name
+ ReportPluginList(BTreeMap<PluginId, RunPlugin>), // String - session name
ReportLayoutInfo((String, BTreeMap<String, String>)), // BTreeMap<file_name, pane_contents>
RunCommand(
PluginId,
@@ -71,6 +73,7 @@ impl From<&BackgroundJob> for BackgroundJobContext {
BackgroundJob::ReportLayoutInfo(..) => BackgroundJobContext::ReportLayoutInfo,
BackgroundJob::RunCommand(..) => BackgroundJobContext::RunCommand,
BackgroundJob::WebRequest(..) => BackgroundJobContext::WebRequest,
+ BackgroundJob::ReportPluginList(..) => BackgroundJobContext::ReportPluginList,
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 current_session_name = Arc::new(Mutex::new(String::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 last_serialization_time = Arc::new(Mutex::new(Instant::now()));
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_info.lock().unwrap() = session_info;
},
+ BackgroundJob::ReportPluginList(plugin_list) => {
+ *current_session_plugin_list.lock().unwrap() = plugin_list;
+ },
BackgroundJob::ReportLayoutInfo(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_name = current_session_name.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();
async move {
loop {
@@ -183,8 +192,16 @@ pub(crate) fn background_jobs_main(
current_session_layout,
);
}
- let session_infos_on_machine =
+ let mut session_infos_on_machine =
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 =
find_resurrectable_sessions(&session_infos_on_machine);
let _ = senders.send_to_screen(ScreenInstruction::UpdateSessionInfos(
diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs
index af2723a0..7f412d52 100644
--- a/zellij-server/src/plugins/mod.rs
+++ b/zellij-server/src/plugins/mod.rs
@@ -14,6 +14,7 @@ use std::{
};
use wasmtime::Engine;
+use crate::background_jobs::BackgroundJob;
use crate::panes::PaneId;
use crate::screen::ScreenInstruction;
use crate::session_layout_metadata::SessionLayoutMetadata;
@@ -49,13 +50,14 @@ pub enum PluginInstruction {
bool, // should be opened in place
Option<String>, // pane title
RunPluginOrAlias,
- usize, // tab index
+ Option<usize>, // tab index
Option<PaneId>, // pane id to replace if this is to be opened "in-place"
ClientId,
Size,
Option<PathBuf>, // cwd
bool, // skip cache
),
+ LoadBackgroundPlugin(RunPluginOrAlias, ClientId),
Update(Vec<(Option<PluginId>, Option<ClientId>, Event)>), // Focused plugin / broadcast, client_id, event data
Unload(PluginId), // plugin_id
Reload(
@@ -65,6 +67,7 @@ pub enum PluginInstruction {
usize, // tab index
Size,
),
+ ReloadPluginWithId(u32),
Resize(PluginId, usize, usize), // plugin_id, columns, rows
AddClient(ClientId),
RemoveClient(ClientId),
@@ -162,9 +165,11 @@ impl From<&PluginInstruction> for PluginContext {
fn from(plugin_instruction: &PluginInstruction) -> Self {
match *plugin_instruction {
PluginInstruction::Load(..) => PluginContext::Load,
+ PluginInstruction::LoadBackgroundPlugin(..) => PluginContext::LoadBackgroundPlugin,
PluginInstruction::Update(..) => PluginContext::Update,
PluginInstruction::Unload(..) => PluginContext::Unload,
PluginInstruction::Reload(..) => PluginContext::Reload,
+ PluginInstruction::ReloadPluginWithId(..) => PluginContext::ReloadPluginWithId,
PluginInstruction::Resize(..) => PluginContext::Resize,
PluginInstruction::Exit => PluginContext::Exit,
PluginInstruction::AddClient(_) => PluginContext::AddClient,
@@ -279,7 +284,7 @@ pub(crate) fn plugin_thread_main(
let start_suppressed = false;
match wasm_bridge.load_plugin(
&run_plugin,
- Some(tab_index),
+ tab_index,
size,
cwd.clone(),
skip_cache,
@@ -292,7 +297,7 @@ pub(crate) fn plugin_thread_main(
should_be_open_in_place,
run_plugin_or_alias,
pane_title,
- Some(tab_index),
+ tab_index,
plugin_id,
pane_id_to_replace,
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) => {
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) => {
wasm_bridge.resize_plugin(pid, new_columns, new_rows, shutdown_send.clone())?;
},
diff --git a/zellij-server/src/plugins/plugin_loader.rs b/zellij-server/src/plugins/plugin_loader.rs
index 41bdb7f6..663d2687 100644
--- a/zellij-server/src/plugins/plugin_loader.rs
+++ b/zellij-server/src/plugins/plugin_loader.rs
@@ -422,6 +422,8 @@ impl<'a> PluginLoader<'a> {
rows: running_plugin.rows,
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();
loading_indication.set_name(running_plugin.store.data().name());
PluginLoader::new(
diff --git a/zellij-server/src/plugins/plugin_map.rs b/zellij-server/src/plugins/plugin_map.rs
index c33a6735..3cda4fc1 100644
--- a/zellij-server/src/plugins/plugin_map.rs
+++ b/zellij-server/src/plugins/plugin_map.rs
@@ -3,7 +3,7 @@ use crate::plugins::PluginId;
use bytes::Bytes;
use std::io::Write;
use std::{
- collections::{HashMap, HashSet, VecDeque},
+ collections::{BTreeMap, HashMap, HashSet, VecDeque},
path::PathBuf,
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>;
diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs
index 690ef516..630ae70b 100644
--- a/zellij-server/src/plugins/unit/plugin_tests.rs
+++ b/zellij-server/src/plugins/unit/plugin_tests.rs
@@ -708,7 +708,7 @@ pub fn load_new_plugin_from_hd() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -788,7 +788,7 @@ pub fn load_new_plugin_with_plugin_alias() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -864,7 +864,7 @@ pub fn plugin_workers() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -944,7 +944,7 @@ pub fn plugin_workers_persist_state() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -1034,7 +1034,7 @@ pub fn can_subscribe_to_hd_events() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -1112,7 +1112,7 @@ pub fn switch_to_mode_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -1184,7 +1184,7 @@ pub fn switch_to_mode_plugin_command_permission_denied() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -1256,7 +1256,7 @@ pub fn new_tabs_with_layout_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -1342,7 +1342,7 @@ pub fn new_tab_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -1414,7 +1414,7 @@ pub fn go_to_next_tab_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -1485,7 +1485,7 @@ pub fn go_to_previous_tab_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -1556,7 +1556,7 @@ pub fn resize_focused_pane_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -1627,7 +1627,7 @@ pub fn resize_focused_pane_with_direction_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -1698,7 +1698,7 @@ pub fn focus_next_pane_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -1769,7 +1769,7 @@ pub fn focus_previous_pane_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -1840,7 +1840,7 @@ pub fn move_focus_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -1911,7 +1911,7 @@ pub fn move_focus_or_tab_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -1982,7 +1982,7 @@ pub fn edit_scrollback_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -2053,7 +2053,7 @@ pub fn write_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -2124,7 +2124,7 @@ pub fn write_chars_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -2195,7 +2195,7 @@ pub fn toggle_tab_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -2266,7 +2266,7 @@ pub fn move_pane_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -2337,7 +2337,7 @@ pub fn move_pane_with_direction_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -2409,7 +2409,7 @@ pub fn clear_screen_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -2481,7 +2481,7 @@ pub fn scroll_up_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -2552,7 +2552,7 @@ pub fn scroll_down_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -2623,7 +2623,7 @@ pub fn scroll_to_top_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -2694,7 +2694,7 @@ pub fn scroll_to_bottom_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -2765,7 +2765,7 @@ pub fn page_scroll_up_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -2836,7 +2836,7 @@ pub fn page_scroll_down_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -2907,7 +2907,7 @@ pub fn toggle_focus_fullscreen_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -2978,7 +2978,7 @@ pub fn toggle_pane_frames_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -3049,7 +3049,7 @@ pub fn toggle_pane_embed_or_eject_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -3120,7 +3120,7 @@ pub fn undo_rename_pane_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -3191,7 +3191,7 @@ pub fn close_focus_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -3262,7 +3262,7 @@ pub fn toggle_active_tab_sync_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -3333,7 +3333,7 @@ pub fn close_focused_tab_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -3404,7 +3404,7 @@ pub fn undo_rename_tab_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -3475,7 +3475,7 @@ pub fn previous_swap_layout_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -3546,7 +3546,7 @@ pub fn next_swap_layout_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -3617,7 +3617,7 @@ pub fn go_to_tab_name_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -3688,7 +3688,7 @@ pub fn focus_or_create_tab_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -3759,7 +3759,7 @@ pub fn go_to_tab() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -3830,7 +3830,7 @@ pub fn start_or_reload_plugin() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -3908,7 +3908,7 @@ pub fn quit_zellij_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -3986,7 +3986,7 @@ pub fn detach_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -4064,7 +4064,7 @@ pub fn open_file_floating_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -4146,7 +4146,7 @@ pub fn open_file_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -4229,7 +4229,7 @@ pub fn open_file_with_line_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -4311,7 +4311,7 @@ pub fn open_file_with_line_floating_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -4393,7 +4393,7 @@ pub fn open_terminal_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -4471,7 +4471,7 @@ pub fn open_terminal_floating_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -4549,7 +4549,7 @@ pub fn open_command_pane_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -4627,7 +4627,7 @@ pub fn open_command_pane_floating_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -4698,7 +4698,7 @@ pub fn switch_to_tab_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -4769,7 +4769,7 @@ pub fn hide_self_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -4839,7 +4839,7 @@ pub fn show_self_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -4910,7 +4910,7 @@ pub fn close_terminal_pane_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -4981,7 +4981,7 @@ pub fn close_plugin_pane_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -5052,7 +5052,7 @@ pub fn focus_terminal_pane_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -5123,7 +5123,7 @@ pub fn focus_plugin_pane_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -5194,7 +5194,7 @@ pub fn rename_terminal_pane_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -5265,7 +5265,7 @@ pub fn rename_plugin_pane_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -5336,7 +5336,7 @@ pub fn rename_tab_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -5416,7 +5416,7 @@ pub fn send_configuration_to_plugins() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -5484,7 +5484,7 @@ pub fn request_plugin_permissions() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -5576,7 +5576,7 @@ pub fn granted_permission_request_result() {
false,
plugin_title,
run_plugin.clone(),
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -5667,7 +5667,7 @@ pub fn denied_permission_request_result() {
false,
plugin_title,
run_plugin.clone(),
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -5738,7 +5738,7 @@ pub fn run_command_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -5816,7 +5816,7 @@ pub fn run_command_with_env_vars_and_cwd_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -5894,7 +5894,7 @@ pub fn web_request_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -5965,7 +5965,7 @@ pub fn unblock_input_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -6047,7 +6047,7 @@ pub fn block_input_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -6137,7 +6137,7 @@ pub fn pipe_output_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -6220,7 +6220,7 @@ pub fn pipe_message_to_plugin_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -6314,7 +6314,7 @@ pub fn switch_session_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -6395,7 +6395,7 @@ pub fn switch_session_with_layout_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -6476,7 +6476,7 @@ pub fn switch_session_with_layout_and_cwd_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -6557,7 +6557,7 @@ pub fn disconnect_other_clients_plugins_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -6638,7 +6638,7 @@ pub fn reconfigure_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -6723,7 +6723,7 @@ pub fn run_plugin_in_specific_cwd() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -6797,7 +6797,7 @@ pub fn hide_pane_with_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -6868,7 +6868,7 @@ pub fn show_pane_with_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -6946,7 +6946,7 @@ pub fn open_command_pane_background_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -7021,7 +7021,7 @@ pub fn rerun_command_pane_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -7092,7 +7092,7 @@ pub fn resize_pane_with_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -7163,7 +7163,7 @@ pub fn edit_scrollback_for_pane_with_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -7234,7 +7234,7 @@ pub fn write_to_pane_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -7305,7 +7305,7 @@ pub fn write_chars_to_pane_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -7376,7 +7376,7 @@ pub fn move_pane_with_pane_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -7447,7 +7447,7 @@ pub fn move_pane_with_pane_id_in_direction_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -7518,7 +7518,7 @@ pub fn clear_screen_for_pane_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -7589,7 +7589,7 @@ pub fn scroll_up_in_pane_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -7660,7 +7660,7 @@ pub fn scroll_down_in_pane_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -7731,7 +7731,7 @@ pub fn scroll_to_top_in_pane_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -7802,7 +7802,7 @@ pub fn scroll_to_bottom_in_pane_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -7873,7 +7873,7 @@ pub fn page_scroll_up_in_pane_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -7944,7 +7944,7 @@ pub fn page_scroll_down_in_pane_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -8015,7 +8015,7 @@ pub fn toggle_pane_id_fullscreen_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -8086,7 +8086,7 @@ pub fn toggle_pane_embed_or_eject_for_pane_id_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -8157,7 +8157,7 @@ pub fn close_tab_with_index_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -8228,7 +8228,7 @@ pub fn break_panes_to_new_tab_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -8299,7 +8299,7 @@ pub fn break_panes_to_tab_with_index_plugin_command() {
false,
plugin_title,
run_plugin,
- tab_index,
+ Some(tab_index),
None,
client_id,
size,
@@ -8328,3 +8328,145 @@ pub fn break_panes_to_tab_with_index_plugin_command() {
.clone();
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);
+}
diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs
index 9e0583cf..799642e2 100644
--- a/zellij-server/src/plugins/wasm_bridge.rs
+++ b/zellij-server/src/plugins/wasm_bridge.rs
@@ -10,7 +10,7 @@ use crate::plugins::zellij_exports::{wasi_read_string, wasi_write_object};
use highway::{HighwayHash, PortableHash};
use log::info;
use std::{
- collections::{HashMap, HashSet},
+ collections::{BTreeMap, HashMap, HashSet},
path::PathBuf,
str::FromStr,
sync::{Arc, Mutex},
@@ -258,7 +258,7 @@ impl WasmBridge {
plugin_cache,
senders.clone(),
engine,
- plugin_map,
+ plugin_map.clone(),
size,
connected_clients.clone(),
&mut loading_indication,
@@ -273,7 +273,10 @@ impl WasmBridge {
default_mode,
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(
&senders,
plugin_id,
@@ -327,6 +330,84 @@ impl WasmBridge {
.send_to_server(ServerInstruction::UnblockCliPipeInput(pipe_name))
.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(())
}
pub fn reload_plugin(&mut self, run_plugin: &RunPlugin) -> Result<()> {
@@ -386,7 +467,8 @@ impl WasmBridge {
&keybinds,
) {
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 {
if plugin_id == &first_plugin_id {
// no need to reload the plugin we just reloaded
@@ -412,7 +494,14 @@ impl WasmBridge {
&base_modes,
&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(
&senders,
*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_screen(ScreenInstruction::RequestStateUpdateForPlugins);
+ let _ = senders.send_to_background_jobs(BackgroundJob::ReportPluginList(plugin_list));
}
fn handle_plugin_loading_failure(
diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs
index d1e938a2..82038c31 100644
--- a/zellij-server/src/plugins/zellij_exports.rs
+++ b/zellij-server/src/plugins/zellij_exports.rs
@@ -5,7 +5,7 @@ use crate::plugins::wasm_bridge::handle_plugin_crash;
use crate::pty::{ClientTabIndexOrPaneId, PtyInstruction};
use crate::route::route_action;
use crate::ServerInstruction;
-use log::{debug, warn};
+use log::warn;
use serde::Serialize;
use std::{
collections::{BTreeMap, HashSet},
@@ -339,6 +339,13 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) {
tab_index,
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) => {
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 action = Action::CloseTerminalPane(terminal_pane_id);
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) {
let error_msg = || format!("failed to change tab focus in plugin {}", env.name());
let action = Action::ClosePluginPane(plugin_pane_id);
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) {
@@ -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.
//
// 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::BreakPanesToNewTab(..)
| PluginCommand::BreakPanesToTabWithIndex(..)
+ | PluginCommand::ReloadPlugin(..)
+ | PluginCommand::LoadNewPlugin { .. }
| PluginCommand::KillSessions(..) => PermissionType::ChangeApplicationState,
PluginCommand::UnblockCliPipeInput(..)
| PluginCommand::BlockCliPipeInput(..)
diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs
index 92270943..47542dc4 100644
--- a/zellij-server/src/pty.rs
+++ b/zellij-server/src/pty.rs
@@ -1551,7 +1551,7 @@ impl Pty {
should_open_in_place,
pane_title,
run,
- tab_index,
+ Some(tab_index),
pane_id_to_replace,
client_id,
size,
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index e7b0cb7b..a4482807 100644
--- a/zellij-server/src/screen.rs
+++ b/zellij-server/src/screen.rs
@@ -1615,6 +1615,7 @@ impl Screen {
connected_clients: self.active_tab_indices.keys().len(),
is_current_session: true,
available_layouts,
+ plugins: Default::default(), // these are filled in by the wasm thread
};
self.bus
.senders
diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs
index 268e10f2..8ede34c4 100644
--- a/zellij-tile/src/shim.rs
+++ b/zellij-tile/src/shim.rs
@@ -1062,6 +1062,34 @@ pub fn break_panes_to_tab_with_index(
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
#[allow(unused)]
diff --git a/zellij-utils/assets/config/default.kdl b/zellij-utils/assets/config/default.kdl
index 2765c78e..e7dfcf5d 100644
--- a/zellij-utils/assets/config/default.kdl
+++ b/zellij-utils/assets/config/default.kdl
@@ -127,6 +127,13 @@ keybinds {
};
SwitchToMode "Normal"
}
+ bind "p" {
+ LaunchOrFocusPlugin "plugin-manager" {
+ floating true
+ move_to_focused_tab true
+ };
+ SwitchToMode "Normal"
+ }
}
tmux {
bind "[" { SwitchToMode "Scroll"; }
@@ -208,6 +215,7 @@ plugins {
cwd "/"
}
configuration location="zellij:configuration"
+ plugin-manager location="zellij:plugin-manager"
}
// Plugins to load in the background when a new session starts
diff --git a/zellij-utils/assets/plugins/compact-bar.wasm b/zellij-utils/assets/plugins/compact-bar.wasm
index e585f4ab..10c11343 100755
--- a/zellij-utils/assets/plugins/compact-bar.wasm
+++ b/zellij-utils/assets/plugins/compact-bar.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/configuration.wasm b/zellij-utils/assets/plugins/configuration.wasm
index 581dbfb1..c4682e9d 100755
--- a/zellij-utils/assets/plugins/configuration.wasm
+++ b/zellij-utils/assets/plugins/configuration.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm
index bb260003..8de431e7 100755
--- a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm
+++ b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/plugin-manager.wasm b/zellij-utils/assets/plugins/plugin-manager.wasm
new file mode 100755
index 00000000..92bd8be7
--- /dev/null
+++ b/zellij-utils/assets/plugins/plugin-manager.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/session-manager.wasm b/zellij-utils/assets/plugins/session-manager.wasm
index 7d2fad73..4c5891ae 100755
--- a/zellij-utils/assets/plugins/session-manager.wasm
+++ b/zellij-utils/assets/plugins/session-manager.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/status-bar.wasm b/zellij-utils/assets/plugins/status-bar.wasm
index 4e882917..1e7ccc9a 100755
--- a/zellij-utils/assets/plugins/status-bar.wasm
+++ b/zellij-utils/assets/plugins/status-bar.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/strider.wasm b/zellij-utils/assets/plugins/strider.wasm
index a339363c..747d4330 100755
--- a/zellij-utils/assets/plugins/strider.wasm
+++ b/zellij-utils/assets/plugins/strider.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/tab-bar.wasm b/zellij-utils/assets/plugins/tab-bar.wasm
index afb73e8a..ae3cc734 100755
--- a/zellij-utils/assets/plugins/tab-bar.wasm
+++ b/zellij-utils/assets/plugins/tab-bar.wasm
Binary files differ
diff --git a/zellij-utils/assets/prost/api.event.rs b/zellij-utils/assets/prost/api.event.rs
index 2e9fafa7..79910d9b 100644
--- a/zellij-utils/assets/prost/api.event.rs
+++ b/zellij-utils/assets/prost/api.event.rs
@@ -270,6 +270,18 @@ pub struct SessionManifest {
pub is_current_session: bool,
#[prost(message, repeated, tag = "6")]
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)]
#[derive(Clone, PartialEq, ::prost::Message)]
diff --git a/zellij-utils/assets/prost/api.plugin_command.rs b/zellij-utils/assets/prost/api.plugin_command.rs
index 110e1669..19c146ac 100644
--- a/zellij-utils/assets/prost/api.plugin_command.rs
+++ b/zellij-utils/assets/prost/api.plugin_command.rs
@@ -5,7 +5,7 @@ pub struct PluginCommand {
pub name: i32,
#[prost(
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>,
}
@@ -168,10 +168,32 @@ pub mod plugin_command {
BreakPanesToNewTabPayload(super::BreakPanesToNewTabPayload),
#[prost(message, tag = "85")]
BreakPanesToTabWithIndexPayload(super::BreakPanesToTabWithIndexPayload),
+ #[prost(message, tag = "86")]
+ ReloadPluginPayload(super::ReloadPluginPayload),
+ #[prost(message, tag = "87")]
+ LoadNewPluginPayload(super::LoadNewPluginPayload),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[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 {
#[prost(message, repeated, tag = "1")]
pub pane_ids: ::prost::alloc::vec::Vec<PaneId>,
@@ -660,6 +682,8 @@ pub enum CommandName {
CloseTabWithIndex = 107,
BreakPanesToNewTab = 108,
BreakPanesToTabWithIndex = 109,
+ ReloadPlugin = 110,
+ LoadNewPlugin = 111,
}
impl CommandName {
/// String value of the enum field names used in the ProtoBuf definition.
@@ -780,6 +804,8 @@ impl CommandName {
CommandName::CloseTabWithIndex => "CloseTabWithIndex",
CommandName::BreakPanesToNewTab => "BreakPanesToNewTab",
CommandName::BreakPanesToTabWithIndex => "BreakPanesToTabWithIndex",
+ CommandName::ReloadPlugin => "ReloadPlugin",
+ CommandName::LoadNewPlugin => "LoadNewPlugin",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@@ -897,6 +923,8 @@ impl CommandName {
"CloseTabWithIndex" => Some(Self::CloseTabWithIndex),
"BreakPanesToNewTab" => Some(Self::BreakPanesToNewTab),
"BreakPanesToTabWithIndex" => Some(Self::BreakPanesToTabWithIndex),
+ "ReloadPlugin" => Some(Self::ReloadPlugin),
+ "LoadNewPlugin" => Some(Self::LoadNewPlugin),
_ => None,
}
}
diff --git a/zellij-utils/src/consts.rs b/zellij-utils/src/consts.rs
index 1c11815c..66e0a8a8 100644
--- a/zellij-utils/src/consts.rs
+++ b/zellij-utils/src/consts.rs
@@ -111,6 +111,7 @@ mod not_wasm {
add_plugin!(assets, "strider.wasm");
add_plugin!(assets, "session-manager.wasm");
add_plugin!(assets, "configuration.wasm");
+ add_plugin!(assets, "plugin-manager.wasm");
assets
};
}
diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs
index eb717945..8c2d2081 100644
--- a/zellij-utils/src/data.rs
+++ b/zellij-utils/src/data.rs
@@ -1,7 +1,7 @@
use crate::input::actions::Action;
use crate::input::config::ConversionError;
use crate::input::keybinds::Keybinds;
-use crate::input::layout::SplitSize;
+use crate::input::layout::{RunPlugin, SplitSize};
use clap::ArgEnum;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
@@ -1199,6 +1199,22 @@ pub struct SessionInfo {
pub connected_clients: usize,
pub is_current_session: bool,
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)]
@@ -1253,6 +1269,14 @@ impl SessionInfo {
pub fn update_connected_clients(&mut self, new_connected_clients: usize) {
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.
@@ -1841,5 +1865,12 @@ pub enum PluginCommand {
// Option<String> - optional name for
// the new tab
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,
+ },
}
diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs
index 64f21936..5f2cea0b 100644
--- a/zellij-utils/src/errors.rs
+++ b/zellij-utils/src/errors.rs
@@ -402,10 +402,12 @@ pub enum PtyContext {
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum PluginContext {
Load,
+ LoadBackgroundPlugin,
Update,
Render,
Unload,
Reload,
+ ReloadPluginWithId,
Resize,
Exit,
AddClient,
@@ -503,6 +505,7 @@ pub enum BackgroundJobContext {
ReportLayoutInfo,
RunCommand,
WebRequest,
+ ReportPluginList,
Exit,
}
diff --git a/zellij-utils/src/input/plugins.rs b/zellij-utils/src/input/plugins.rs
index a646b386..e1e161d3 100644
--- a/zellij-utils/src/input/plugins.rs
+++ b/zellij-utils/src/input/plugins.rs
@@ -63,6 +63,7 @@ impl PluginConfig {
|| tag == "strider"
|| tag == "session-manager"
|| tag == "configuration"
+ || tag == "plugin-manager"
{
Some(PluginConfig {
path: PathBuf::from(&tag),
diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs
index e57bb828..a46d69f5 100644
--- a/zellij-utils/src/kdl/mod.rs
+++ b/zellij-utils/src/kdl/mod.rs
@@ -4127,6 +4127,7 @@ impl SessionInfo {
connected_clients,
is_current_session,
available_layouts,
+ plugins: Default::default(), // we do not serialize plugin information
})
}
pub fn to_string(&self) -> String {
@@ -4662,6 +4663,7 @@ fn serialize_and_deserialize_session_info_with_data() {
LayoutInfo::BuiltIn("layout2".to_owned()),
LayoutInfo::File("layout3".to_owned()),
],
+ plugins: Default::default(),
};
let serialized = session_info.to_string();
let deserealized = SessionInfo::from_string(&serialized, "not this session").unwrap();
diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap
index a385211a..6a709ef9 100644
--- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap
+++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/kdl/mod.rs
-assertion_line: 5525
+assertion_line: 5535
expression: fake_config_stringified
---
keybinds clear-defaults=true {
@@ -107,6 +107,13 @@ keybinds clear-defaults=true {
SwitchToMode "normal"
}
bind "Ctrl o" { SwitchToMode "normal"; }
+ bind "p" {
+ LaunchOrFocusPlugin "plugin-manager" {
+ floating true
+ move_to_focused_tab true
+ }
+ SwitchToMode "normal"
+ }
bind "w" {
LaunchOrFocusPlugin "session-manager" {
floating true
@@ -229,6 +236,7 @@ plugins {
filepicker location="zellij:strider" {
cwd "/"
}
+ plugin-manager location="zellij:plugin-manager"
session-manager location="zellij:session-manager"
status-bar location="zellij:status-bar"
strider location="zellij:strider"
diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap
index 3f438da1..b4bbbefe 100644
--- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap
+++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/kdl/mod.rs
-assertion_line: 5537
+assertion_line: 5547
expression: fake_config_stringified
---
keybinds clear-defaults=true {
@@ -107,6 +107,13 @@ keybinds clear-defaults=true {
SwitchToMode "normal"
}
bind "Ctrl o" { SwitchToMode "normal"; }
+ bind "p" {
+ LaunchOrFocusPlugin "plugin-manager" {
+ floating true
+ move_to_focused_tab true
+ }
+ SwitchToMode "normal"
+ }
bind "w" {
LaunchOrFocusPlugin "session-manager" {
floating true
@@ -232,6 +239,7 @@ plugins {
filepicker location="zellij:strider" {
cwd "/"
}
+ plugin-manager location="zellij:plugin-manager"
session-manager location="zellij:session-manager"
status-bar location="zellij:status-bar"
strider location="zellij:strider"
diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto
index a985806f..e3f3cacb 100644
--- a/zellij-utils/src/plugin_api/event.proto
+++ b/zellij-utils/src/plugin_api/event.proto
@@ -223,6 +223,13 @@ message SessionManifest {
uint32 connected_clients = 4;
bool is_current_session = 5;
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 {
diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs
index faeb53d2..d1e2f50d 100644
--- a/zellij-utils/src/plugin_api/event.rs
+++ b/zellij-utils/src/plugin_api/event.rs
@@ -8,7 +8,7 @@ pub use super::generated_api::api::{
LayoutInfo as ProtobufLayoutInfo, ModeUpdatePayload as ProtobufModeUpdatePayload,
PaneId as ProtobufPaneId, PaneInfo as ProtobufPaneInfo,
PaneManifest as ProtobufPaneManifest, PaneType as ProtobufPaneType,
- ResurrectableSession as ProtobufResurrectableSession,
+ PluginInfo as ProtobufPluginInfo, ResurrectableSession as ProtobufResurrectableSession,
SessionManifest as ProtobufSessionManifest, TabInfo as ProtobufTabInfo, *,
},
input_mode::InputMode as ProtobufInputMode,
@@ -19,13 +19,13 @@ pub use super::generated_api::api::{
use crate::data::{
CopyDestination, Event, EventType, FileMetadata, InputMode, KeyWithModifier, LayoutInfo,
ModeInfo, Mouse, PaneId, PaneInfo, PaneManifest, PermissionStatus, PluginCapabilities,
- SessionInfo, Style, TabInfo,
+ PluginInfo, SessionInfo, Style, TabInfo,
};
use crate::errors::prelude::*;
use crate::input::actions::Action;
-use std::collections::{HashMap, HashSet};
+use std::collections::{BTreeMap, HashMap, HashSet};
use std::convert::TryFrom;
use std::path::PathBuf;
use std::time::Duration;
@@ -667,10 +667,29 @@ impl TryFrom<SessionInfo> for ProtobufSessionManifest {
.into_iter()
.filter_map(|l| ProtobufLayoutInfo::try_from(l).ok())
.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 {
type Error = &'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 {
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 {
name: protobuf_session_manifest.name,
tabs: protobuf_session_manifest
@@ -704,6 +737,7 @@ impl TryFrom<ProtobufSessionManifest> for SessionInfo {
.into_iter()
.filter_map(|l| LayoutInfo::try_from(l).ok())
.collect(),
+ plugins,
})
}
}
@@ -1702,6 +1736,16 @@ fn serialize_session_update_event_with_non_default_values() {
},
];
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 {
name: "session 1".to_owned(),
tabs: tab_infos,
@@ -1713,6 +1757,7 @@ fn serialize_session_update_event_with_non_default_values() {
LayoutInfo::BuiltIn("layout2".to_owned()),
LayoutInfo::File("layout3".to_owned()),
],
+ plugins,
};
let session_info_2 = SessionInfo {
name: "session 2".to_owned(),
@@ -1727,6 +1772,7 @@ fn serialize_session_update_event_with_non_default_values() {
LayoutInfo::BuiltIn("layout2".to_owned()),
LayoutInfo::File("layout3".to_owned()),
],
+ plugins: Default::default(),
};
let session_infos = vec![session_info_1, session_info_2];
let resurrectable_sessions = vec![];
diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto
index 758d4e77..adb91e53 100644
--- a/zellij-utils/src/plugin_api/plugin_command.proto
+++ b/zellij-utils/src/plugin_api/plugin_command.proto
@@ -121,6 +121,8 @@ enum CommandName {
CloseTabWithIndex = 107;
BreakPanesToNewTab = 108;
BreakPanesToTabWithIndex = 109;
+ ReloadPlugin = 110;
+ LoadNewPlugin = 111;
}
message PluginCommand {
@@ -201,9 +203,22 @@ message PluginCommand {
CloseTabWithIndexPayload close_tab_with_index_payload = 83;
BreakPanesToNewTabPayload break_panes_to_new_tab_payload = 84;
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 {
repeated PaneId pane_ids = 1;
uint32 tab_index = 2;
diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs
index 15c56d03..d455500a 100644
--- a/zellij-utils/src/plugin_api/plugin_command.rs
+++ b/zellij-utils/src/plugin_api/plugin_command.rs
@@ -9,18 +9,19 @@ pub use super::generated_api::api::{
FixedOrPercent as ProtobufFixedOrPercent,
FixedOrPercentValue as ProtobufFixedOrPercentValue,
FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, HidePaneWithIdPayload,
- HttpVerb as ProtobufHttpVerb, IdAndNewName, KillSessionsPayload, MessageToPluginPayload,
- MovePaneWithPaneIdInDirectionPayload, MovePaneWithPaneIdPayload, MovePayload,
- NewPluginArgs as ProtobufNewPluginArgs, NewTabsWithLayoutInfoPayload,
+ HttpVerb as ProtobufHttpVerb, IdAndNewName, KillSessionsPayload, LoadNewPluginPayload,
+ MessageToPluginPayload, MovePaneWithPaneIdInDirectionPayload, MovePaneWithPaneIdPayload,
+ MovePayload, NewPluginArgs as ProtobufNewPluginArgs, NewTabsWithLayoutInfoPayload,
OpenCommandPanePayload, OpenFilePayload, PageScrollDownInPaneIdPayload,
PageScrollUpInPaneIdPayload, PaneId as ProtobufPaneId, PaneType as ProtobufPaneType,
PluginCommand as ProtobufPluginCommand, PluginMessagePayload, ReconfigurePayload,
- RequestPluginPermissionPayload, RerunCommandPanePayload, ResizePaneIdWithDirectionPayload,
- ResizePayload, RunCommandPayload, ScrollDownInPaneIdPayload, ScrollToBottomInPaneIdPayload,
- ScrollToTopInPaneIdPayload, ScrollUpInPaneIdPayload, SetTimeoutPayload,
- ShowPaneWithIdPayload, SubscribePayload, SwitchSessionPayload, SwitchTabToPayload,
- TogglePaneEmbedOrEjectForPaneIdPayload, TogglePaneIdFullscreenPayload, UnsubscribePayload,
- WebRequestPayload, WriteCharsToPaneIdPayload, WriteToPaneIdPayload,
+ ReloadPluginPayload, RequestPluginPermissionPayload, RerunCommandPanePayload,
+ ResizePaneIdWithDirectionPayload, ResizePayload, RunCommandPayload,
+ ScrollDownInPaneIdPayload, ScrollToBottomInPaneIdPayload, ScrollToTopInPaneIdPayload,
+ ScrollUpInPaneIdPayload, SetTimeoutPayload, ShowPaneWithIdPayload, SubscribePayload,
+ SwitchSessionPayload, SwitchTabToPayload, TogglePaneEmbedOrEjectForPaneIdPayload,
+ TogglePaneIdFullscreenPayload, UnsubscribePayload, WebRequestPayload,
+ WriteCharsToPaneIdPayload, WriteToPaneIdPayload,
},
plugin_permission::PermissionType as ProtobufPermissionType,
resize::ResizeAction as ProtobufResizeAction,
@@ -1203,6 +1204,28 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
)),
_ => 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"),
}
}
@@ -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,
+ })),
+ }),
}
}
}
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
index 07892df6..541a7196 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
@@ -4140,6 +4140,34 @@ Config {
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 {
+ bare_key: Char(
+ 'p',
+ ),
key_modifiers: {
Ctrl,
},
@@ -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 {
_allow_exec_host_cmd: false,
location: Zellij(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
index 2d4200de..af80f720 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
@@ -4140,6 +4140,34 @@ Config {
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 {
+ bare_key: Char(
+ 'p',
+ ),
key_modifiers: {
Ctrl,
},
@@ -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 {
_allow_exec_host_cmd: false,
location: Zellij(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
index b7b66e3a..72b3df8e 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
@@ -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 {
_allow_exec_host_cmd: false,
location: Zellij(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
index 87ccfb70..5c59e9dd 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
@@ -4140,6 +4140,34 @@ Config {
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 {
+ bare_key: Char(
+ 'p',
+ ),
key_modifiers: {
Ctrl,
},
@@ -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 {
_allow_exec_host_cmd: false,
location: Zellij(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
index 17f0e6d6..83edeb6c 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
@@ -4140,6 +4140,34 @@ Config {
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 {
+ bare_key: Char(
+ 'p',
+ ),
key_modifiers: {
Ctrl,
},
@@ -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 {
_allow_exec_host_cmd: false,
location: Zellij(