feat(plugins): make plugins configurable (#2646)

* work

* make every plugin entry point configurable

* make integration tests pass

* make e2e tests pass

* add test for plugin configuration

* add test snapshot

* add plugin config parsing test

* cleanups

* style(fmt): rustfmt

* style(comment): remove commented code
This commit is contained in:
Aram Drevekenin 2023-07-25 10:04:12 +02:00
parent b1285f63e1
commit 37ce6b3d06
31 changed files with 605 additions and 100 deletions

View File

@ -2,6 +2,7 @@ mod line;
mod tab;
use std::cmp::{max, min};
use std::collections::BTreeMap;
use std::convert::TryInto;
use tab::get_tab_to_focus;
@ -30,7 +31,7 @@ static ARROW_SEPARATOR: &str = "";
register_plugin!(State);
impl ZellijPlugin for State {
fn load(&mut self) {
fn load(&mut self, configuration: BTreeMap<String, String>) {
set_selectable(false);
subscribe(&[
EventType::TabUpdate,

View File

@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use zellij_tile::prelude::*;
// This is a fixture plugin used only for tests in Zellij
@ -9,6 +10,7 @@ use zellij_tile::prelude::*;
struct State {
received_events: Vec<Event>,
received_payload: Option<String>,
configuration: BTreeMap<String, String>,
}
#[derive(Default, Serialize, Deserialize)]
@ -35,7 +37,8 @@ register_plugin!(State);
register_worker!(TestWorker, test_worker, TEST_WORKER);
impl ZellijPlugin for State {
fn load(&mut self) {
fn load(&mut self, configuration: BTreeMap<String, String>) {
self.configuration = configuration;
subscribe(&[
EventType::InputReceived,
EventType::Key,
@ -210,6 +213,9 @@ impl ZellijPlugin for State {
Key::Ctrl('x') => {
rename_tab(1, "new tab name");
},
Key::Ctrl('z') => {
go_to_tab_name(&format!("{:?}", self.configuration));
},
_ => {},
},
Event::CustomMessage(message, payload) => {

View File

@ -8,6 +8,7 @@ use ansi_term::{
Style,
};
use std::collections::BTreeMap;
use std::fmt::{Display, Error, Formatter};
use zellij_tile::prelude::actions::Action;
use zellij_tile::prelude::*;
@ -196,7 +197,7 @@ fn color_elements(palette: Palette, different_color_alternates: bool) -> Colored
}
impl ZellijPlugin for State {
fn load(&mut self) {
fn load(&mut self, configuration: BTreeMap<String, String>) {
// TODO: Should be able to choose whether to use the cache through config.
self.tip_name = get_cached_tip_name();
set_selectable(false);
@ -207,7 +208,10 @@ impl ZellijPlugin for State {
EventType::InputReceived,
EventType::SystemClipboardFailure,
]);
self.supermode = false; // TODO: from config
self.supermode = configuration
.get("supermode")
.and_then(|s| s.trim().parse().ok())
.unwrap_or(false);
self.standby_mode = InputMode::Pane;
if self.supermode {
switch_to_input_mode(&InputMode::Locked); // supermode should start locked (TODO: only

View File

@ -6,6 +6,7 @@ use search::{FileContentsWorker, FileNameWorker, MessageToSearch, ResultsOfSearc
use serde::{Deserialize, Serialize};
use serde_json;
use state::{refresh_directory, FsEntry, State};
use std::collections::BTreeMap;
use std::{cmp::min, time::Instant};
use zellij_tile::prelude::*;
@ -18,10 +19,8 @@ register_worker!(
);
impl ZellijPlugin for State {
fn load(&mut self) {
// Example
fn load(&mut self, configuration: BTreeMap<String, String>) {
request_permission(&[PermissionType::KeyboardInput]);
refresh_directory(self);
self.search_state.loading = true;
subscribe(&[

View File

@ -2,6 +2,7 @@ mod line;
mod tab;
use std::cmp::{max, min};
use std::collections::BTreeMap;
use std::convert::TryInto;
use tab::get_tab_to_focus;
@ -30,7 +31,7 @@ static ARROW_SEPARATOR: &str = "";
register_plugin!(State);
impl ZellijPlugin for State {
fn load(&mut self) {
fn load(&mut self, configuration: BTreeMap<String, String>) {
set_selectable(false);
subscribe(&[
EventType::TabUpdate,

View File

@ -39,6 +39,7 @@ fn main() {
name,
close_on_exit,
start_suspended,
configuration: None,
};
commands::send_action_to_session(command_cli_action, opts.session, config);
std::process::exit(0);

View File

@ -1,6 +1,6 @@
use crate::plugins::plugin_map::{PluginEnv, PluginMap, RunningPlugin, Subscriptions};
use crate::plugins::plugin_worker::{plugin_worker, RunningWorker};
use crate::plugins::zellij_exports::{wasi_read_string, zellij_exports};
use crate::plugins::zellij_exports::{wasi_read_string, wasi_write_object, zellij_exports};
use crate::plugins::PluginId;
use highway::{HighwayHash, PortableHash};
use log::info;
@ -745,7 +745,14 @@ impl<'a> PluginLoader<'a> {
}
}
start_function.call(&[]).with_context(err_context)?;
wasi_write_object(
&plugin_env.wasi_env,
&self.plugin.userspace_configuration.inner(),
)
.with_context(err_context)?;
load_function.call(&[]).with_context(err_context)?;
display_loading_stage!(
indicate_starting_plugin_success,
self.loading_indication,

View File

@ -2,12 +2,13 @@ use super::plugin_thread_main;
use crate::screen::ScreenInstruction;
use crate::{channels::SenderWithContext, thread_bus::Bus, ServerInstruction};
use insta::assert_snapshot;
use std::collections::BTreeMap;
use std::path::PathBuf;
use tempfile::tempdir;
use wasmer::Store;
use zellij_utils::data::{Event, Key, PluginCapabilities};
use zellij_utils::errors::ErrorContext;
use zellij_utils::input::layout::{Layout, RunPlugin, RunPluginLocation};
use zellij_utils::input::layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation};
use zellij_utils::input::plugins::PluginsConfig;
use zellij_utils::ipc::ClientAttributes;
use zellij_utils::lazy_static::lazy_static;
@ -349,6 +350,7 @@ pub fn load_new_plugin_from_hd() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -407,6 +409,7 @@ pub fn plugin_workers() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -468,6 +471,7 @@ pub fn plugin_workers_persist_state() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -541,6 +545,7 @@ pub fn can_subscribe_to_hd_events() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -605,6 +610,7 @@ pub fn switch_to_mode_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -666,6 +672,7 @@ pub fn new_tabs_with_layout_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -741,6 +748,7 @@ pub fn new_tab_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -802,6 +810,7 @@ pub fn go_to_next_tab_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -862,6 +871,7 @@ pub fn go_to_previous_tab_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -922,6 +932,7 @@ pub fn resize_focused_pane_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -982,6 +993,7 @@ pub fn resize_focused_pane_with_direction_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1042,6 +1054,7 @@ pub fn focus_next_pane_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1102,6 +1115,7 @@ pub fn focus_previous_pane_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1162,6 +1176,7 @@ pub fn move_focus_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1222,6 +1237,7 @@ pub fn move_focus_or_tab_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1282,6 +1298,7 @@ pub fn edit_scrollback_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1342,6 +1359,7 @@ pub fn write_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1402,6 +1420,7 @@ pub fn write_chars_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1462,6 +1481,7 @@ pub fn toggle_tab_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1522,6 +1542,7 @@ pub fn move_pane_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1582,6 +1603,7 @@ pub fn move_pane_with_direction_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1642,6 +1664,7 @@ pub fn clear_screen_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1702,6 +1725,7 @@ pub fn scroll_up_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1762,6 +1786,7 @@ pub fn scroll_down_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1822,6 +1847,7 @@ pub fn scroll_to_top_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1882,6 +1908,7 @@ pub fn scroll_to_bottom_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -1942,6 +1969,7 @@ pub fn page_scroll_up_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2002,6 +2030,7 @@ pub fn page_scroll_down_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2062,6 +2091,7 @@ pub fn toggle_focus_fullscreen_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2122,6 +2152,7 @@ pub fn toggle_pane_frames_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2182,6 +2213,7 @@ pub fn toggle_pane_embed_or_eject_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2242,6 +2274,7 @@ pub fn undo_rename_pane_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2302,6 +2335,7 @@ pub fn close_focus_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2362,6 +2396,7 @@ pub fn toggle_active_tab_sync_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2422,6 +2457,7 @@ pub fn close_focused_tab_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2482,6 +2518,7 @@ pub fn undo_rename_tab_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2542,6 +2579,7 @@ pub fn previous_swap_layout_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2602,6 +2640,7 @@ pub fn next_swap_layout_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2662,6 +2701,7 @@ pub fn go_to_tab_name_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2722,6 +2762,7 @@ pub fn focus_or_create_tab_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2782,6 +2823,7 @@ pub fn go_to_tab() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2842,6 +2884,7 @@ pub fn start_or_reload_plugin() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2902,6 +2945,7 @@ pub fn quit_zellij_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -2962,6 +3006,7 @@ pub fn detach_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3022,6 +3067,7 @@ pub fn open_file_floating_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3082,6 +3128,7 @@ pub fn open_file_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3142,6 +3189,7 @@ pub fn open_file_with_line_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3202,6 +3250,7 @@ pub fn open_file_with_line_floating_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3262,6 +3311,7 @@ pub fn open_terminal_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3322,6 +3372,7 @@ pub fn open_terminal_floating_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3382,6 +3433,7 @@ pub fn open_command_pane_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3442,6 +3494,7 @@ pub fn open_command_pane_floating_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3502,6 +3555,7 @@ pub fn switch_to_tab_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3562,6 +3616,7 @@ pub fn hide_self_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3622,6 +3677,7 @@ pub fn show_self_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3682,6 +3738,7 @@ pub fn close_terminal_pane_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3742,6 +3799,7 @@ pub fn close_plugin_pane_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3802,6 +3860,7 @@ pub fn focus_terminal_pane_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3862,6 +3921,7 @@ pub fn focus_plugin_pane_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3922,6 +3982,7 @@ pub fn rename_terminal_pane_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -3982,6 +4043,7 @@ pub fn rename_plugin_pane_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -4042,6 +4104,7 @@ pub fn rename_tab_plugin_command() {
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
@ -4088,3 +4151,76 @@ pub fn rename_tab_plugin_command() {
.clone();
assert_snapshot!(format!("{:#?}", new_tab_event));
}
#[test]
#[ignore]
pub fn send_configuration_to_plugins() {
let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
// destructor removes the directory
let plugin_host_folder = PathBuf::from(temp_folder.path());
let (plugin_thread_sender, screen_receiver, mut teardown) =
create_plugin_thread(Some(plugin_host_folder));
let plugin_should_float = Some(false);
let plugin_title = Some("test_plugin".to_owned());
let mut configuration = BTreeMap::new();
configuration.insert(
"fake_config_key_1".to_owned(),
"fake_config_value_1".to_owned(),
);
configuration.insert(
"fake_config_key_2".to_owned(),
"fake_config_value_2".to_owned(),
);
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: PluginUserConfiguration::new(configuration),
};
let tab_index = 1;
let client_id = 1;
let size = Size {
cols: 121,
rows: 20,
};
let received_screen_instructions = Arc::new(Mutex::new(vec![]));
let screen_thread = log_actions_in_thread!(
received_screen_instructions,
ScreenInstruction::GoToTabName,
screen_receiver,
1
);
let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
let _ = plugin_thread_sender.send(PluginInstruction::Load(
plugin_should_float,
plugin_title,
run_plugin,
tab_index,
client_id,
size,
));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
Event::Key(Key::Ctrl('z')), // this triggers the enent in the fixture plugin
)]));
std::thread::sleep(std::time::Duration::from_millis(100));
screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown();
// here we make sure we received a rename_tab event with the title being the stringified
// (Debug) configuration we sent to the fixture plugin to make sure it got there properly
let go_to_tab_event = received_screen_instructions
.lock()
.unwrap()
.iter()
.find_map(|i| {
if let ScreenInstruction::GoToTabName(..) = i {
Some(i.clone())
} else {
None
}
})
.clone();
assert_snapshot!(format!("{:#?}", go_to_tab_event));
}

View File

@ -0,0 +1,19 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 4220
expression: "format!(\"{:#?}\", go_to_tab_event)"
---
Some(
GoToTabName(
"{\"fake_config_key_1\": \"fake_config_value_1\", \"fake_config_key_2\": \"fake_config_value_2\"}\n\r",
(
[],
[],
),
None,
false,
Some(
1,
),
),
)

View File

@ -1,6 +1,6 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 2889
assertion_line: 2931
expression: "format!(\"{:#?}\", new_tab_event)"
---
Some(
@ -10,6 +10,9 @@ Some(
location: File(
"/path/to/my/plugin.wasm",
),
configuration: PluginUserConfiguration(
{},
),
},
None,
),

View File

@ -5,7 +5,7 @@ use crate::route::route_action;
use log::{debug, warn};
use serde::{de::DeserializeOwned, Serialize};
use std::{
collections::HashSet,
collections::{BTreeMap, HashSet},
path::PathBuf,
process,
sync::{Arc, Mutex},
@ -27,7 +27,7 @@ use zellij_utils::{
input::{
actions::Action,
command::{RunCommand, RunCommandAction, TerminalAction},
layout::{Layout, RunPlugin, RunPluginLocation},
layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation},
plugins::PluginType,
},
serde,
@ -1006,6 +1006,7 @@ fn host_start_or_reload_plugin(env: &ForeignFunctionEnv) {
let run_plugin = RunPlugin {
location: run_plugin_location,
_allow_exec_host_cmd: false,
configuration: PluginUserConfiguration::new(BTreeMap::new()), // TODO: allow passing configuration
};
let action = Action::StartOrReloadPlugin(run_plugin);
apply_action!(action, error_msg, env);

View File

@ -14,8 +14,8 @@ use zellij_utils::pane_size::{Size, SizeInPixels};
use zellij_utils::{
input::command::TerminalAction,
input::layout::{
FloatingPaneLayout, Run, RunPlugin, RunPluginLocation, SwapFloatingLayout, SwapTiledLayout,
TiledPaneLayout,
FloatingPaneLayout, PluginUserConfiguration, Run, RunPlugin, RunPluginLocation,
SwapFloatingLayout, SwapTiledLayout, TiledPaneLayout,
},
position::Position,
};
@ -258,11 +258,11 @@ pub enum ScreenInstruction {
PreviousSwapLayout(ClientId),
NextSwapLayout(ClientId),
QueryTabNames(ClientId),
NewTiledPluginPane(RunPluginLocation, Option<String>, ClientId), // Option<String> is
NewTiledPluginPane(RunPlugin, Option<String>, ClientId), // Option<String> is
// optional pane title
NewFloatingPluginPane(RunPlugin, Option<String>, ClientId), // Option<String> is an
// optional pane title
NewFloatingPluginPane(RunPluginLocation, Option<String>, ClientId), // Option<String> is an
StartOrReloadPluginPane(RunPlugin, Option<String>),
// optional pane title
AddPlugin(
Option<bool>, // should_float
RunPlugin,
@ -2639,14 +2639,10 @@ pub(crate) fn screen_thread_main(
.senders
.send_to_server(ServerInstruction::Log(tab_names, client_id))?;
},
ScreenInstruction::NewTiledPluginPane(run_plugin_location, pane_title, client_id) => {
ScreenInstruction::NewTiledPluginPane(run_plugin, pane_title, client_id) => {
let tab_index = screen.active_tab_indices.values().next().unwrap_or(&1);
let size = Size::default();
let should_float = Some(false);
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: run_plugin_location,
};
screen.bus.senders.send_to_plugin(PluginInstruction::Load(
should_float,
pane_title,
@ -2656,28 +2652,26 @@ pub(crate) fn screen_thread_main(
size,
))?;
},
ScreenInstruction::NewFloatingPluginPane(
run_plugin_location,
pane_title,
client_id,
) => {
let tab_index = screen.active_tab_indices.values().next().unwrap(); // TODO: no
// unwrap and
// better
let size = Size::default(); // TODO: ???
let should_float = Some(true);
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: run_plugin_location,
};
screen.bus.senders.send_to_plugin(PluginInstruction::Load(
should_float,
pane_title,
run_plugin,
*tab_index,
client_id,
size,
))?;
ScreenInstruction::NewFloatingPluginPane(run_plugin, pane_title, client_id) => {
match screen.active_tab_indices.values().next() {
Some(tab_index) => {
let size = Size::default();
let should_float = Some(true);
screen.bus.senders.send_to_plugin(PluginInstruction::Load(
should_float,
pane_title,
run_plugin,
*tab_index,
client_id,
size,
))?;
},
None => {
log::error!(
"Could not find an active tab - is there at least 1 connected user?"
);
},
}
},
ScreenInstruction::StartOrReloadPluginPane(run_plugin, pane_title) => {
let tab_index = screen.active_tab_indices.values().next().unwrap_or(&1);

View File

@ -1854,6 +1854,7 @@ pub fn send_cli_new_pane_action_with_default_parameters() {
name: None,
close_on_exit: false,
start_suspended: false,
configuration: None,
};
send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@ -1890,6 +1891,7 @@ pub fn send_cli_new_pane_action_with_split_direction() {
name: None,
close_on_exit: false,
start_suspended: false,
configuration: None,
};
send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@ -1926,6 +1928,7 @@ pub fn send_cli_new_pane_action_with_command_and_cwd() {
name: None,
close_on_exit: false,
start_suspended: false,
configuration: None,
};
send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@ -2550,6 +2553,7 @@ pub fn send_cli_launch_or_focus_plugin_action() {
let cli_action = CliAction::LaunchOrFocusPlugin {
floating: true,
url: url::Url::parse("file:/path/to/fake/plugin").unwrap(),
configuration: Default::default(),
};
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@ -2583,6 +2587,7 @@ pub fn send_cli_launch_or_focus_plugin_action_when_plugin_is_already_loaded() {
run: Some(Run::Plugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from("/path/to/fake/plugin")),
configuration: Default::default(),
})),
..Default::default()
};
@ -2605,6 +2610,7 @@ pub fn send_cli_launch_or_focus_plugin_action_when_plugin_is_already_loaded() {
let cli_action = CliAction::LaunchOrFocusPlugin {
floating: true,
url: url::Url::parse("file:/path/to/fake/plugin").unwrap(),
configuration: Default::default(),
};
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be

View File

@ -1,6 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2758
assertion_line: 2572
expression: "format!(\"{:#?}\", plugin_load_instruction)"
---
Some(
@ -14,6 +14,9 @@ Some(
location: File(
"/path/to/fake/plugin",
),
configuration: PluginUserConfiguration(
{},
),
},
0,
1,

View File

@ -19,6 +19,7 @@ pub mod prelude;
pub mod shim;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use zellij_utils::data::Event;
/// This trait should be implemented - once per plugin - on a struct (normally representing the
@ -27,7 +28,7 @@ use zellij_utils::data::Event;
#[allow(unused_variables)]
pub trait ZellijPlugin: Default {
/// Will be called when the plugin is loaded, this is a good place to [`subscribe`](shim::subscribe) to events that are interesting for this plugin.
fn load(&mut self) {}
fn load(&mut self, configuration: BTreeMap<String, String>) {}
/// Will be called with an [`Event`](prelude::Event) if the plugin is subscribed to said event.
/// If the plugin returns `true` from this function, Zellij will know it should be rendered and call its `render` function.
fn update(&mut self, event: Event) -> bool {
@ -103,7 +104,11 @@ macro_rules! register_plugin {
#[no_mangle]
fn load() {
STATE.with(|state| {
state.borrow_mut().load();
let configuration = $crate::shim::object_from_stdin()
.context($crate::PLUGIN_MISMATCH)
.to_stdout()
.unwrap();
state.borrow_mut().load(configuration);
});
}

View File

@ -2,7 +2,7 @@ use crate::data::{Direction, InputMode, Resize};
use crate::setup::Setup;
use crate::{
consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV},
input::options::CliOptions,
input::{layout::PluginUserConfiguration, options::CliOptions},
};
use clap::{Parser, Subcommand};
use serde::{Deserialize, Serialize};
@ -292,6 +292,8 @@ pub enum CliAction {
requires("command")
)]
start_suspended: bool,
#[clap(short, long, value_parser)]
configuration: Option<PluginUserConfiguration>,
},
/// Open the specified file in a new zellij pane with your default EDITOR
Edit {
@ -376,10 +378,14 @@ pub enum CliAction {
QueryTabNames,
StartOrReloadPlugin {
url: String,
#[clap(short, long, value_parser)]
configuration: Option<PluginUserConfiguration>,
},
LaunchOrFocusPlugin {
#[clap(short, long, value_parser)]
floating: bool,
url: Url,
#[clap(short, long, value_parser)]
configuration: Option<PluginUserConfiguration>,
},
}

View File

@ -230,8 +230,8 @@ pub enum Action {
/// Query all tab names
QueryTabNames,
/// Open a new tiled (embedded, non-floating) plugin pane
NewTiledPluginPane(RunPluginLocation, Option<String>), // String is an optional name
NewFloatingPluginPane(RunPluginLocation, Option<String>), // String is an optional name
NewTiledPluginPane(RunPlugin, Option<String>), // String is an optional name
NewFloatingPluginPane(RunPlugin, Option<String>), // String is an optional name
StartOrReloadPlugin(RunPlugin),
CloseTerminalPane(u32),
ClosePluginPane(u32),
@ -292,21 +292,24 @@ impl Action {
name,
close_on_exit,
start_suspended,
configuration,
} => {
let current_dir = get_current_dir();
let cwd = cwd
.map(|cwd| current_dir.join(cwd))
.or_else(|| Some(current_dir));
let user_configuration = configuration.unwrap_or_default();
if let Some(plugin) = plugin {
let location = RunPluginLocation::parse(&plugin, cwd)
.map_err(|e| format!("Failed to parse plugin loction {plugin}: {}", e))?;
let plugin = RunPlugin {
_allow_exec_host_cmd: false,
location,
configuration: user_configuration,
};
if floating {
let plugin = RunPluginLocation::parse(&plugin, cwd).map_err(|e| {
format!("Failed to parse plugin loction {plugin}: {}", e)
})?;
Ok(vec![Action::NewFloatingPluginPane(plugin, name)])
} else {
let plugin = RunPluginLocation::parse(&plugin, cwd).map_err(|e| {
format!("Failed to parse plugin location {plugin}: {}", e)
})?;
// it is intentional that a new tiled plugin pane cannot include a
// direction
// this is because the cli client opening a tiled plugin pane is a
@ -480,23 +483,29 @@ impl Action {
CliAction::PreviousSwapLayout => Ok(vec![Action::PreviousSwapLayout]),
CliAction::NextSwapLayout => Ok(vec![Action::NextSwapLayout]),
CliAction::QueryTabNames => Ok(vec![Action::QueryTabNames]),
CliAction::StartOrReloadPlugin { url } => {
CliAction::StartOrReloadPlugin { url, configuration } => {
let current_dir = get_current_dir();
let run_plugin_location = RunPluginLocation::parse(&url, Some(current_dir))
.map_err(|e| format!("Failed to parse plugin location: {}", e))?;
let run_plugin = RunPlugin {
location: run_plugin_location,
_allow_exec_host_cmd: false,
configuration: configuration.unwrap_or_default(),
};
Ok(vec![Action::StartOrReloadPlugin(run_plugin)])
},
CliAction::LaunchOrFocusPlugin { url, floating } => {
CliAction::LaunchOrFocusPlugin {
url,
floating,
configuration,
} => {
let current_dir = get_current_dir();
let run_plugin_location = RunPluginLocation::parse(url.as_str(), Some(current_dir))
.map_err(|e| format!("Failed to parse plugin location: {}", e))?;
let run_plugin = RunPlugin {
location: run_plugin_location,
_allow_exec_host_cmd: false,
configuration: configuration.unwrap_or_default(),
};
Ok(vec![Action::LaunchOrFocusPlugin(run_plugin, floating)])
},

View File

@ -600,6 +600,7 @@ mod config_test {
run: PluginType::Pane(None),
location: RunPluginLocation::Zellij(PluginTag::new("tab-bar")),
_allow_exec_host_cmd: false,
userspace_configuration: Default::default(),
},
);
expected_plugin_configuration.insert(
@ -609,6 +610,7 @@ mod config_test {
run: PluginType::Pane(None),
location: RunPluginLocation::Zellij(PluginTag::new("status-bar")),
_allow_exec_host_cmd: false,
userspace_configuration: Default::default(),
},
);
expected_plugin_configuration.insert(
@ -618,6 +620,7 @@ mod config_test {
run: PluginType::Pane(None),
location: RunPluginLocation::Zellij(PluginTag::new("strider")),
_allow_exec_host_cmd: true,
userspace_configuration: Default::default(),
},
);
expected_plugin_configuration.insert(
@ -627,6 +630,7 @@ mod config_test {
run: PluginType::Pane(None),
location: RunPluginLocation::Zellij(PluginTag::new("compact-bar")),
_allow_exec_host_cmd: false,
userspace_configuration: Default::default(),
},
);
assert_eq!(

View File

@ -212,6 +212,35 @@ pub struct RunPlugin {
#[serde(default)]
pub _allow_exec_host_cmd: bool,
pub location: RunPluginLocation,
pub configuration: PluginUserConfiguration,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct PluginUserConfiguration(BTreeMap<String, String>);
impl PluginUserConfiguration {
pub fn new(configuration: BTreeMap<String, String>) -> Self {
PluginUserConfiguration(configuration)
}
pub fn inner(&self) -> &BTreeMap<String, String> {
&self.0
}
}
impl FromStr for PluginUserConfiguration {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut ret = BTreeMap::new();
let configs = s.split(',');
for config in configs {
let mut config = config.split('=');
let key = config.next().ok_or("invalid configuration key")?.to_owned();
let value = config.map(|c| c.to_owned()).collect::<Vec<_>>().join("=");
ret.insert(key, value);
}
Ok(PluginUserConfiguration(ret))
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]

View File

@ -8,7 +8,7 @@ use thiserror::Error;
use serde::{Deserialize, Serialize};
use url::Url;
use super::layout::{RunPlugin, RunPluginLocation};
use super::layout::{PluginUserConfiguration, RunPlugin, RunPluginLocation};
#[cfg(not(target_family = "wasm"))]
use crate::consts::ASSET_MAP;
pub use crate::data::PluginTag;
@ -48,9 +48,11 @@ impl PluginsConfig {
run: PluginType::Pane(None),
_allow_exec_host_cmd: run._allow_exec_host_cmd,
location: run.location.clone(),
userspace_configuration: run.configuration.clone(),
}),
RunPluginLocation::Zellij(tag) => self.0.get(tag).cloned().map(|plugin| PluginConfig {
_allow_exec_host_cmd: run._allow_exec_host_cmd,
userspace_configuration: run.configuration.clone(),
..plugin
}),
}
@ -86,6 +88,8 @@ pub struct PluginConfig {
pub _allow_exec_host_cmd: bool,
/// Original location of the
pub location: RunPluginLocation,
/// Custom configuration for this plugin
pub userspace_configuration: PluginUserConfiguration,
}
impl PluginConfig {

View File

@ -504,9 +504,18 @@ fn layout_with_plugin_panes() {
pane {
plugin location="file:/path/to/my/plugin.wasm"
}
pane {
plugin location="zellij:status-bar" {
config_key_1 "config_value_1"
"2" true
}
}
}
"#;
let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None, None).unwrap();
let mut expected_plugin_configuration = BTreeMap::new();
expected_plugin_configuration.insert("config_key_1".to_owned(), "config_value_1".to_owned());
expected_plugin_configuration.insert("2".to_owned(), "true".to_owned());
let expected_layout = Layout {
template: Some((
TiledPaneLayout {
@ -515,6 +524,7 @@ fn layout_with_plugin_panes() {
run: Some(Run::Plugin(RunPlugin {
location: RunPluginLocation::Zellij(PluginTag::new("tab-bar")),
_allow_exec_host_cmd: false,
configuration: Default::default(),
})),
..Default::default()
},
@ -524,6 +534,15 @@ fn layout_with_plugin_panes() {
"/path/to/my/plugin.wasm",
)),
_allow_exec_host_cmd: false,
configuration: Default::default(),
})),
..Default::default()
},
TiledPaneLayout {
run: Some(Run::Plugin(RunPlugin {
location: RunPluginLocation::Zellij(PluginTag::new("status-bar")),
_allow_exec_host_cmd: false,
configuration: PluginUserConfiguration(expected_plugin_configuration),
})),
..Default::default()
},
@ -2016,6 +2035,7 @@ fn run_plugin_location_parsing() {
run: Some(Run::Plugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::Zellij(PluginTag::new("tab-bar")),
configuration: Default::default(),
})),
..Default::default()
},
@ -2025,6 +2045,7 @@ fn run_plugin_location_parsing() {
location: RunPluginLocation::File(PathBuf::from(
"/path/to/my/plugin.wasm",
)),
configuration: Default::default(),
})),
..Default::default()
},
@ -2032,6 +2053,7 @@ fn run_plugin_location_parsing() {
run: Some(Run::Plugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from("plugin.wasm")),
configuration: Default::default(),
})),
..Default::default()
},
@ -2041,6 +2063,7 @@ fn run_plugin_location_parsing() {
location: RunPluginLocation::File(PathBuf::from(
"relative/with space/plugin.wasm",
)),
configuration: Default::default(),
})),
..Default::default()
},
@ -2050,6 +2073,7 @@ fn run_plugin_location_parsing() {
location: RunPluginLocation::File(PathBuf::from(
"/absolute/with space/plugin.wasm",
)),
configuration: Default::default(),
})),
..Default::default()
},
@ -2059,6 +2083,7 @@ fn run_plugin_location_parsing() {
location: RunPluginLocation::File(PathBuf::from(
"c:/absolute/windows/plugin.wasm",
)),
configuration: Default::default(),
})),
..Default::default()
},

View File

@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1850
assertion_line: 1863
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@ -66,6 +66,9 @@ Layout {
"tab-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -150,6 +153,9 @@ Layout {
"status-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -194,6 +200,9 @@ Layout {
"tab-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -331,6 +340,9 @@ Layout {
"status-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -375,6 +387,9 @@ Layout {
"tab-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -578,6 +593,9 @@ Layout {
"status-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),

View File

@ -2,9 +2,9 @@ use crate::input::{
command::RunCommand,
config::ConfigError,
layout::{
FloatingPaneLayout, Layout, LayoutConstraint, PercentOrFixed, Run, RunPlugin,
RunPluginLocation, SplitDirection, SplitSize, SwapFloatingLayout, SwapTiledLayout,
TiledPaneLayout,
FloatingPaneLayout, Layout, LayoutConstraint, PercentOrFixed, PluginUserConfiguration, Run,
RunPlugin, RunPluginLocation, SplitDirection, SplitSize, SwapFloatingLayout,
SwapTiledLayout, TiledPaneLayout,
},
};
@ -14,7 +14,8 @@ use std::collections::{BTreeMap, HashMap, HashSet};
use std::str::FromStr;
use crate::{
kdl_child_with_name, kdl_children_nodes, kdl_get_bool_property_or_child_value,
kdl_child_with_name, kdl_children_nodes, kdl_first_entry_as_bool, kdl_first_entry_as_i64,
kdl_first_entry_as_string, kdl_get_bool_property_or_child_value,
kdl_get_bool_property_or_child_value_with_error, kdl_get_child,
kdl_get_int_property_or_child_value, kdl_get_property_or_child,
kdl_get_string_property_or_child_value, kdl_get_string_property_or_child_value_with_error,
@ -121,6 +122,11 @@ impl<'a> KdlLayoutParser<'a> {
|| property_name == "min_panes"
|| property_name == "exact_panes"
}
pub fn is_a_reserved_plugin_property(property_name: &str) -> bool {
property_name == "location"
|| property_name == "_allow_exec_host_cmd"
|| property_name == "path"
}
fn assert_legal_node_name(&self, name: &str, kdl_node: &KdlNode) -> Result<(), ConfigError> {
if name.contains(char::is_whitespace) {
Err(ConfigError::new_layout_kdl_error(
@ -305,11 +311,62 @@ impl<'a> KdlLayoutParser<'a> {
url_node.span().len(),
)
})?;
let configuration = KdlLayoutParser::parse_plugin_user_configuration(&plugin_block)?;
Ok(Some(Run::Plugin(RunPlugin {
_allow_exec_host_cmd,
location,
configuration,
})))
}
pub fn parse_plugin_user_configuration(
plugin_block: &KdlNode,
) -> Result<PluginUserConfiguration, ConfigError> {
let mut configuration = BTreeMap::new();
for user_configuration_entry in plugin_block.entries() {
let name = user_configuration_entry.name();
let value = user_configuration_entry.value();
if let Some(name) = name {
let name = name.to_string();
if KdlLayoutParser::is_a_reserved_plugin_property(&name) {
continue;
}
configuration.insert(name, value.to_string());
}
// we ignore "bare" (eg. `plugin i_am_a_bare_true_argument { arg_one 1; }`) entries
// to prevent diverging behaviour with the keybindings config
}
if let Some(user_config) = kdl_children_nodes!(plugin_block) {
for user_configuration_entry in user_config {
let config_entry_name = kdl_name!(user_configuration_entry);
if KdlLayoutParser::is_a_reserved_plugin_property(&config_entry_name) {
continue;
}
let config_entry_str_value = kdl_first_entry_as_string!(user_configuration_entry)
.map(|s| format!("{}", s.to_string()));
let config_entry_int_value = kdl_first_entry_as_i64!(user_configuration_entry)
.map(|s| format!("{}", s.to_string()));
let config_entry_bool_value = kdl_first_entry_as_bool!(user_configuration_entry)
.map(|s| format!("{}", s.to_string()));
let config_entry_children = user_configuration_entry
.children()
.map(|s| format!("{}", s.to_string().trim()));
let config_entry_value = config_entry_str_value
.or(config_entry_int_value)
.or(config_entry_bool_value)
.or(config_entry_children)
.ok_or(ConfigError::new_kdl_error(
format!(
"Failed to parse plugin block configuration: {:?}",
user_configuration_entry
),
plugin_block.span().offset(),
plugin_block.span().len(),
))?;
configuration.insert(config_entry_name.into(), config_entry_value);
}
}
Ok(PluginUserConfiguration::new(configuration))
}
fn parse_args(&self, pane_node: &KdlNode) -> Result<Option<Vec<String>>, ConfigError> {
match kdl_get_child!(pane_node, "args") {
Some(kdl_args) => {

View File

@ -3,14 +3,14 @@ use crate::data::{Direction, InputMode, Key, Palette, PaletteColor, PermissionTy
use crate::envs::EnvironmentVariables;
use crate::input::config::{Config, ConfigError, KdlError};
use crate::input::keybinds::Keybinds;
use crate::input::layout::{Layout, RunPlugin, RunPluginLocation};
use crate::input::layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation};
use crate::input::options::{Clipboard, OnForceClose, Options};
use crate::input::permission::GrantedPermission;
use crate::input::plugins::{PluginConfig, PluginTag, PluginType, PluginsConfig};
use crate::input::theme::{FrameConfig, Theme, Themes, UiConfig};
use crate::setup::{find_default_config_dir, get_layout_dir};
use kdl_layout_parser::KdlLayoutParser;
use std::collections::{HashMap, HashSet};
use std::collections::{HashMap, HashSet, BTreeMap};
use strum::IntoEnumIterator;
use miette::NamedSource;
@ -137,6 +137,17 @@ macro_rules! kdl_first_entry_as_i64 {
};
}
#[macro_export]
macro_rules! kdl_first_entry_as_bool {
( $node:expr ) => {
$node
.entries()
.iter()
.next()
.and_then(|i| i.value().as_bool())
};
}
#[macro_export]
macro_rules! entry_count {
( $node:expr ) => {{
@ -895,9 +906,11 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
.unwrap_or(false);
let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let location = RunPluginLocation::parse(&plugin_path, Some(current_dir))?;
let configuration = KdlLayoutParser::parse_plugin_user_configuration(&kdl_action)?;
let run_plugin = RunPlugin {
location,
_allow_exec_host_cmd: false,
configuration,
};
Ok(Action::LaunchOrFocusPlugin(run_plugin, should_float))
},
@ -1266,15 +1279,12 @@ macro_rules! kdl_get_string_property_or_child_value_with_error {
#[macro_export]
macro_rules! kdl_get_property_or_child {
( $kdl_node:expr, $name:expr ) => {
$kdl_node
.get($name)
// .and_then(|e| e.value().as_string())
.or_else(|| {
$kdl_node
.children()
.and_then(|c| c.get($name))
.and_then(|c| c.get(0))
})
$kdl_node.get($name).or_else(|| {
$kdl_node
.children()
.and_then(|c| c.get($name))
.and_then(|c| c.get(0))
})
};
}
@ -1403,30 +1413,6 @@ impl Options {
}
}
impl RunPlugin {
pub fn from_kdl(kdl_node: &KdlNode, cwd: Option<PathBuf>) -> Result<Self, ConfigError> {
let _allow_exec_host_cmd =
kdl_get_child_entry_bool_value!(kdl_node, "_allow_exec_host_cmd").unwrap_or(false);
let string_url = kdl_get_child_entry_string_value!(kdl_node, "location").ok_or(
ConfigError::new_kdl_error(
"Plugins must have a location".into(),
kdl_node.span().offset(),
kdl_node.span().len(),
),
)?;
let location = RunPluginLocation::parse(string_url, cwd).map_err(|e| {
ConfigError::new_layout_kdl_error(
e.to_string(),
kdl_node.span().offset(),
kdl_node.span().len(),
)
})?;
Ok(RunPlugin {
_allow_exec_host_cmd,
location,
})
}
}
impl Layout {
pub fn from_kdl(
raw_layout: &str,
@ -1714,6 +1700,9 @@ impl PluginsConfig {
run: PluginType::Pane(None),
location: RunPluginLocation::Zellij(plugin_tag.clone()),
_allow_exec_host_cmd: allow_exec_host_cmd,
userspace_configuration: PluginUserConfiguration::new(BTreeMap::new()), // TODO: consider removing the whole
// "plugins" section in the config
// because it's not used???
};
plugins.insert(plugin_tag, plugin_config);
}
@ -1848,3 +1837,51 @@ impl GrantedPermission {
kdl_doucment.to_string()
}
}
pub fn parse_plugin_user_configuration(
plugin_block: &KdlNode,
) -> Result<BTreeMap<String, String>, ConfigError> {
let mut configuration = BTreeMap::new();
for user_configuration_entry in plugin_block.entries() {
let name = user_configuration_entry.name();
let value = user_configuration_entry.value();
if let Some(name) = name {
let name = name.to_string();
if KdlLayoutParser::is_a_reserved_plugin_property(&name) {
continue;
}
configuration.insert(name, value.to_string());
}
}
if let Some(user_config) = kdl_children_nodes!(plugin_block) {
for user_configuration_entry in user_config {
let config_entry_name = kdl_name!(user_configuration_entry);
if KdlLayoutParser::is_a_reserved_plugin_property(&config_entry_name) {
continue;
}
let config_entry_str_value = kdl_first_entry_as_string!(user_configuration_entry)
.map(|s| format!("{}", s.to_string()));
let config_entry_int_value = kdl_first_entry_as_i64!(user_configuration_entry)
.map(|s| format!("{}", s.to_string()));
let config_entry_bool_value = kdl_first_entry_as_bool!(user_configuration_entry)
.map(|s| format!("{}", s.to_string()));
let config_entry_children = user_configuration_entry
.children()
.map(|s| format!("{}", s.to_string().trim()));
let config_entry_value = config_entry_str_value
.or(config_entry_int_value)
.or(config_entry_bool_value)
.or(config_entry_children)
.ok_or(ConfigError::new_kdl_error(
format!(
"Failed to parse plugin block configuration: {:?}",
user_configuration_entry
),
plugin_block.span().offset(),
plugin_block.span().len(),
))?;
configuration.insert(config_entry_name.into(), config_entry_value);
}
}
Ok(configuration)
}

View File

@ -1,5 +1,6 @@
---
source: zellij-utils/src/setup.rs
assertion_line: 640
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@ -29,6 +30,9 @@ Layout {
"tab-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -70,6 +74,9 @@ Layout {
"status-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -121,6 +128,9 @@ Layout {
"tab-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -205,6 +215,9 @@ Layout {
"status-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -249,6 +262,9 @@ Layout {
"tab-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -386,6 +402,9 @@ Layout {
"status-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -430,6 +449,9 @@ Layout {
"tab-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -633,6 +655,9 @@ Layout {
"status-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -684,6 +709,9 @@ Layout {
"tab-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -752,6 +780,9 @@ Layout {
"status-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -796,6 +827,9 @@ Layout {
"tab-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -933,6 +967,9 @@ Layout {
"status-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -977,6 +1014,9 @@ Layout {
"tab-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -1180,6 +1220,9 @@ Layout {
"status-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -1231,6 +1274,9 @@ Layout {
"tab-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),
@ -1315,6 +1361,9 @@ Layout {
"status-bar",
),
),
configuration: PluginUserConfiguration(
{},
),
},
),
),

View File

@ -1,5 +1,6 @@
---
source: zellij-utils/src/setup.rs
assertion_line: 639
expression: "format!(\"{:#?}\", config)"
---
Config {
@ -3557,6 +3558,9 @@ Config {
"compact-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"status-bar",
@ -3571,6 +3575,9 @@ Config {
"status-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"strider",
@ -3585,6 +3592,9 @@ Config {
"strider",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"tab-bar",
@ -3599,6 +3609,9 @@ Config {
"tab-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
},
ui: UiConfig {

View File

@ -1,5 +1,6 @@
---
source: zellij-utils/src/setup.rs
assertion_line: 697
expression: "format!(\"{:#?}\", config)"
---
Config {
@ -3557,6 +3558,9 @@ Config {
"compact-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"status-bar",
@ -3571,6 +3575,9 @@ Config {
"status-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"strider",
@ -3585,6 +3592,9 @@ Config {
"strider",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"tab-bar",
@ -3599,6 +3609,9 @@ Config {
"tab-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
},
ui: UiConfig {

View File

@ -1,5 +1,6 @@
---
source: zellij-utils/src/setup.rs
assertion_line: 753
expression: "format!(\"{:#?}\", config)"
---
Config {
@ -97,6 +98,9 @@ Config {
"compact-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"status-bar",
@ -111,6 +115,9 @@ Config {
"status-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"strider",
@ -125,6 +132,9 @@ Config {
"strider",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"tab-bar",
@ -139,6 +149,9 @@ Config {
"tab-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
},
ui: UiConfig {

View File

@ -1,5 +1,6 @@
---
source: zellij-utils/src/setup.rs
assertion_line: 725
expression: "format!(\"{:#?}\", config)"
---
Config {
@ -3557,6 +3558,9 @@ Config {
"compact-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"some-other-plugin",
@ -3571,6 +3575,9 @@ Config {
"some-other-plugin",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"status-bar",
@ -3585,6 +3592,9 @@ Config {
"status-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"strider",
@ -3599,6 +3609,9 @@ Config {
"strider",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"tab-bar",
@ -3613,6 +3626,9 @@ Config {
"tab-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
},
ui: UiConfig {

View File

@ -1,5 +1,6 @@
---
source: zellij-utils/src/setup.rs
assertion_line: 739
expression: "format!(\"{:#?}\", config)"
---
Config {
@ -3861,6 +3862,9 @@ Config {
"compact-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"status-bar",
@ -3875,6 +3879,9 @@ Config {
"status-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"strider",
@ -3889,6 +3896,9 @@ Config {
"strider",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"tab-bar",
@ -3903,6 +3913,9 @@ Config {
"tab-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
},
ui: UiConfig {

View File

@ -1,5 +1,6 @@
---
source: zellij-utils/src/setup.rs
assertion_line: 711
expression: "format!(\"{:#?}\", config)"
---
Config {
@ -3557,6 +3558,9 @@ Config {
"compact-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"status-bar",
@ -3571,6 +3575,9 @@ Config {
"status-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"strider",
@ -3585,6 +3592,9 @@ Config {
"strider",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
PluginTag(
"tab-bar",
@ -3599,6 +3609,9 @@ Config {
"tab-bar",
),
),
userspace_configuration: PluginUserConfiguration(
{},
),
},
},
ui: UiConfig {