feat(plugin): add manifest to allow for plugin configuration (#660)
* feat(plugins-manifest): Add a plugins manifest to allow for more configuration of plugins * refactor(plugins-manifest): Better storage of plugin metadata in wasm_vm * fix(plugins-manifest): Inherit permissions from run configuration * refactor(plugins-manifest): Rename things for more clarity - The Plugins/Plugin structs had "Config" appended to them to clarify that they're metadata about plugins, and not the plugins themselves. - The PluginType::OncePerPane variant was renamed to be just PluginType::Pane, and the documentation clarified to explain what it is. - The "service" nomenclature was completely removed in favor of "headless". * refactor(plugins-manifest): Move security warning into start plugin * refactor(plugins-manifest): Remove hack in favor of standard method * refactor(plugins-manifest): Change display of plugin location The only time that a plugin location is displayed in Zellij is the border of the pane. Having `zellij:strider` display instead of just `strider` was a little annoying, so we're stripping out the scheme information from a locations display. * refactor(plugins-manifest): Add a little more documentation * fix(plugins-manifest): Formatting Co-authored-by: Jesse Tuchsen <not@disclosing>
This commit is contained in:
parent
c39f021810
commit
c9372212f6
25 changed files with 833 additions and 186 deletions
79
Cargo.lock
generated
79
Cargo.lock
generated
|
|
@ -748,6 +748,16 @@ version = "1.0.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
|
|
@ -970,6 +980,17 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.7.0"
|
||||
|
|
@ -1206,6 +1227,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.4"
|
||||
|
|
@ -1432,6 +1459,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.7"
|
||||
|
|
@ -2107,6 +2140,21 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.26"
|
||||
|
|
@ -2179,6 +2227,21 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.8.0"
|
||||
|
|
@ -2206,6 +2269,19 @@ dependencies = [
|
|||
"traitobject",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.1.1"
|
||||
|
|
@ -2680,6 +2756,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"typetag",
|
||||
"unicode-width",
|
||||
"url",
|
||||
"wasmer",
|
||||
"wasmer-wasi",
|
||||
"zellij-utils",
|
||||
|
|
@ -2720,6 +2797,7 @@ dependencies = [
|
|||
"nix",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"signal-hook 0.3.9",
|
||||
"strip-ansi-escapes",
|
||||
|
|
@ -2728,6 +2806,7 @@ dependencies = [
|
|||
"tempfile",
|
||||
"termion",
|
||||
"unicode-width",
|
||||
"url",
|
||||
"vte 0.10.1",
|
||||
"zellij-tile",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ pub fn start_client(
|
|||
Box::new(opts),
|
||||
Box::new(config_options.clone()),
|
||||
layout.unwrap(),
|
||||
Some(config.plugins.clone()),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ byteorder = "1.4.3"
|
|||
daemonize = "0.4.1"
|
||||
serde_json = "1.0"
|
||||
unicode-width = "0.1.8"
|
||||
url = "2.2.2"
|
||||
wasmer = "1.0.0"
|
||||
wasmer-wasi = "1.0.0"
|
||||
cassowary = "0.3.0"
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ use zellij_utils::{
|
|||
get_mode_info,
|
||||
layout::LayoutFromYaml,
|
||||
options::Options,
|
||||
plugins::PluginsConfig,
|
||||
},
|
||||
ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
|
||||
setup::get_default_data_dir,
|
||||
|
|
@ -46,7 +47,13 @@ use zellij_utils::{
|
|||
/// Instructions related to server-side application
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum ServerInstruction {
|
||||
NewClient(ClientAttributes, Box<CliArgs>, Box<Options>, LayoutFromYaml),
|
||||
NewClient(
|
||||
ClientAttributes,
|
||||
Box<CliArgs>,
|
||||
Box<Options>,
|
||||
LayoutFromYaml,
|
||||
Option<PluginsConfig>,
|
||||
),
|
||||
Render(Option<String>),
|
||||
UnblockInputThread,
|
||||
ClientExit,
|
||||
|
|
@ -58,8 +65,8 @@ pub(crate) enum ServerInstruction {
|
|||
impl From<ClientToServerMsg> for ServerInstruction {
|
||||
fn from(instruction: ClientToServerMsg) -> Self {
|
||||
match instruction {
|
||||
ClientToServerMsg::NewClient(attrs, opts, options, layout) => {
|
||||
ServerInstruction::NewClient(attrs, opts, options, layout)
|
||||
ClientToServerMsg::NewClient(attrs, opts, options, layout, plugins) => {
|
||||
ServerInstruction::NewClient(attrs, opts, options, layout, plugins)
|
||||
}
|
||||
ClientToServerMsg::AttachClient(attrs, force, options) => {
|
||||
ServerInstruction::AttachClient(attrs, force, options)
|
||||
|
|
@ -193,15 +200,24 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
|||
let (instruction, mut err_ctx) = server_receiver.recv().unwrap();
|
||||
err_ctx.add_call(ContextType::IPCServer((&instruction).into()));
|
||||
match instruction {
|
||||
ServerInstruction::NewClient(client_attributes, opts, config_options, layout) => {
|
||||
ServerInstruction::NewClient(
|
||||
client_attributes,
|
||||
opts,
|
||||
config_options,
|
||||
layout,
|
||||
plugins,
|
||||
) => {
|
||||
let session = init_session(
|
||||
os_input.clone(),
|
||||
opts,
|
||||
config_options.clone(),
|
||||
to_server.clone(),
|
||||
client_attributes,
|
||||
session_state.clone(),
|
||||
layout.clone(),
|
||||
SessionOptions {
|
||||
opts,
|
||||
layout: layout.clone(),
|
||||
plugins,
|
||||
config_options: config_options.clone(),
|
||||
},
|
||||
);
|
||||
*session_data.write().unwrap() = Some(session);
|
||||
*session_state.write().unwrap() = SessionState::Attached;
|
||||
|
|
@ -302,15 +318,26 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
|||
drop(std::fs::remove_file(&socket_path));
|
||||
}
|
||||
|
||||
pub struct SessionOptions {
|
||||
pub opts: Box<CliArgs>,
|
||||
pub config_options: Box<Options>,
|
||||
pub layout: LayoutFromYaml,
|
||||
pub plugins: Option<PluginsConfig>,
|
||||
}
|
||||
|
||||
fn init_session(
|
||||
os_input: Box<dyn ServerOsApi>,
|
||||
opts: Box<CliArgs>,
|
||||
config_options: Box<Options>,
|
||||
to_server: SenderWithContext<ServerInstruction>,
|
||||
client_attributes: ClientAttributes,
|
||||
session_state: Arc<RwLock<SessionState>>,
|
||||
layout: LayoutFromYaml,
|
||||
options: SessionOptions,
|
||||
) -> SessionMetaData {
|
||||
let SessionOptions {
|
||||
opts,
|
||||
config_options,
|
||||
layout,
|
||||
plugins,
|
||||
} = options;
|
||||
let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channels::unbounded();
|
||||
let to_screen = SenderWithContext::new(to_screen);
|
||||
|
||||
|
|
@ -394,7 +421,7 @@ fn init_session(
|
|||
);
|
||||
let store = Store::default();
|
||||
|
||||
move || wasm_thread_main(plugin_bus, store, data_dir)
|
||||
move || wasm_thread_main(plugin_bus, store, data_dir, plugins.unwrap_or_default())
|
||||
})
|
||||
.unwrap();
|
||||
SessionMetaData {
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ pub(crate) struct Pty {
|
|||
task_handles: HashMap<RawFd, JoinHandle<()>>,
|
||||
}
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub(crate) fn pty_thread_main(mut pty: Pty, layout: LayoutFromYaml) {
|
||||
loop {
|
||||
let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel");
|
||||
|
|
@ -104,7 +106,10 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: LayoutFromYaml) {
|
|||
});
|
||||
|
||||
let merged_layout = layout.template.clone().insert_tab_layout(tab_layout);
|
||||
pty.spawn_terminals_for_layout(merged_layout.into(), terminal_action.clone());
|
||||
let layout: Layout =
|
||||
Layout::try_from(merged_layout).unwrap_or_else(|err| panic!("{}", err));
|
||||
|
||||
pty.spawn_terminals_for_layout(layout, terminal_action.clone());
|
||||
|
||||
if let Some(tab_name) = tab_name {
|
||||
// clear current name at first
|
||||
|
|
|
|||
|
|
@ -322,23 +322,18 @@ impl Tab {
|
|||
|
||||
for (layout, position_and_size) in positions_and_size {
|
||||
// A plugin pane
|
||||
if let Some(Run::Plugin(Some(plugin))) = &layout.run {
|
||||
if let Some(Run::Plugin(run)) = layout.run.clone() {
|
||||
let (pid_tx, pid_rx) = channel();
|
||||
let pane_title = run.location.to_string();
|
||||
self.senders
|
||||
.send_to_plugin(PluginInstruction::Load(
|
||||
pid_tx,
|
||||
plugin.path.clone(),
|
||||
tab_index,
|
||||
plugin._allow_exec_host_cmd,
|
||||
))
|
||||
.send_to_plugin(PluginInstruction::Load(pid_tx, run, tab_index))
|
||||
.unwrap();
|
||||
let pid = pid_rx.recv().unwrap();
|
||||
let title = String::from(plugin.path.as_path().as_os_str().to_string_lossy());
|
||||
let mut new_plugin = PluginPane::new(
|
||||
pid,
|
||||
*position_and_size,
|
||||
self.senders.to_plugin.as_ref().unwrap().clone(),
|
||||
title,
|
||||
pane_title,
|
||||
);
|
||||
new_plugin.set_borderless(layout.borderless);
|
||||
self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin));
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use crate::{
|
|||
thread_bus::Bus,
|
||||
SessionState,
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use zellij_utils::input::command::TerminalAction;
|
||||
|
|
@ -101,7 +102,7 @@ fn create_new_screen(size: Size) -> Screen {
|
|||
}
|
||||
|
||||
fn new_tab(screen: &mut Screen, pid: i32) {
|
||||
screen.apply_layout(LayoutTemplate::default().into(), vec![pid]);
|
||||
screen.apply_layout(LayoutTemplate::default().try_into().unwrap(), vec![pid]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
thread_bus::ThreadSenders,
|
||||
SessionState,
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use zellij_utils::input::layout::LayoutTemplate;
|
||||
|
|
@ -101,7 +102,11 @@ fn create_new_tab(size: Size) -> Tab {
|
|||
session_state,
|
||||
true, // draw pane frames
|
||||
);
|
||||
tab.apply_layout(LayoutTemplate::default().into(), vec![1], index);
|
||||
tab.apply_layout(
|
||||
LayoutTemplate::default().try_into().unwrap(),
|
||||
vec![1],
|
||||
index,
|
||||
);
|
||||
tab
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use log::{info, warn};
|
||||
use log::{debug, info, warn};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{mpsc::Sender, Arc, Mutex};
|
||||
|
|
@ -9,6 +9,7 @@ use std::thread;
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use url::Url;
|
||||
use wasmer::{
|
||||
imports, ChainableNamedResolver, Function, ImportObject, Instance, Module, Store, Value,
|
||||
WasmerEnv,
|
||||
|
|
@ -24,11 +25,16 @@ use crate::{
|
|||
thread_bus::{Bus, ThreadSenders},
|
||||
};
|
||||
use zellij_utils::errors::{ContextType, PluginContext};
|
||||
use zellij_utils::{input::command::TerminalAction, serde, zellij_tile};
|
||||
use zellij_utils::{
|
||||
input::command::TerminalAction,
|
||||
input::layout::RunPlugin,
|
||||
input::plugins::{PluginConfig, PluginType, PluginsConfig},
|
||||
serde, zellij_tile,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum PluginInstruction {
|
||||
Load(Sender<u32>, PathBuf, usize, bool), // tx_pid, path_of_plugin , tab_index, allow_exec_host_cmd
|
||||
Load(Sender<u32>, RunPlugin, usize), // tx_pid, plugin metadata, tab_index
|
||||
Update(Option<u32>, Event), // Focused plugin / broadcast, event data
|
||||
Render(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols
|
||||
Unload(u32),
|
||||
|
|
@ -50,83 +56,62 @@ impl From<&PluginInstruction> for PluginContext {
|
|||
#[derive(WasmerEnv, Clone)]
|
||||
pub(crate) struct PluginEnv {
|
||||
pub plugin_id: u32,
|
||||
pub tab_index: usize,
|
||||
pub plugin: PluginConfig,
|
||||
pub senders: ThreadSenders,
|
||||
pub wasi_env: WasiEnv,
|
||||
pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
|
||||
// FIXME: Once permission system is ready, this could be removed
|
||||
pub _allow_exec_host_cmd: bool,
|
||||
plugin_own_data_dir: PathBuf,
|
||||
}
|
||||
|
||||
// Thread main --------------------------------------------------------------------------------------------------------
|
||||
pub(crate) fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_dir: PathBuf) {
|
||||
pub(crate) fn wasm_thread_main(
|
||||
bus: Bus<PluginInstruction>,
|
||||
store: Store,
|
||||
data_dir: PathBuf,
|
||||
plugins: PluginsConfig,
|
||||
) {
|
||||
info!("Wasm main thread starts");
|
||||
|
||||
let mut plugin_id = 0;
|
||||
let mut plugin_map = HashMap::new();
|
||||
let plugin_dir = data_dir.join("plugins/");
|
||||
let plugin_global_data_dir = plugin_dir.join("data");
|
||||
fs::create_dir_all(plugin_global_data_dir.as_path()).unwrap();
|
||||
|
||||
for plugin in plugins.iter() {
|
||||
if let PluginType::Headless = plugin.run {
|
||||
let (instance, plugin_env) = start_plugin(
|
||||
plugin_id,
|
||||
plugin,
|
||||
0,
|
||||
&bus,
|
||||
&store,
|
||||
&data_dir,
|
||||
&plugin_global_data_dir,
|
||||
);
|
||||
plugin_map.insert(plugin_id, (instance, plugin_env));
|
||||
plugin_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
|
||||
err_ctx.add_call(ContextType::Plugin((&event).into()));
|
||||
match event {
|
||||
PluginInstruction::Load(pid_tx, path, tab_index, _allow_exec_host_cmd) => {
|
||||
let wasm_bytes = fs::read(&path)
|
||||
.or_else(|_| fs::read(&path.with_extension("wasm")))
|
||||
.or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm")))
|
||||
.unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display()));
|
||||
PluginInstruction::Load(pid_tx, run, tab_index) => {
|
||||
let plugin = plugins
|
||||
.get(&run)
|
||||
.unwrap_or_else(|| panic!("Plugin {:?} could not be resolved", run));
|
||||
|
||||
// FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
|
||||
let module = Module::new(&store, &wasm_bytes).unwrap();
|
||||
|
||||
let output = Pipe::new();
|
||||
let input = Pipe::new();
|
||||
let stderr = LoggingPipe::new(
|
||||
path.as_path().file_name().unwrap().to_str().unwrap(),
|
||||
plugin_id,
|
||||
);
|
||||
|
||||
let plugin_name = path.as_path().file_stem().unwrap();
|
||||
let plugin_own_data_dir = plugin_global_data_dir.join(plugin_name);
|
||||
fs::create_dir_all(&plugin_own_data_dir).unwrap();
|
||||
|
||||
let mut wasi_env = WasiState::new("Zellij")
|
||||
.env("CLICOLOR_FORCE", "1")
|
||||
.map_dir("/host", ".")
|
||||
.unwrap()
|
||||
.map_dir("/data", plugin_own_data_dir.as_path())
|
||||
.unwrap()
|
||||
.stdin(Box::new(input))
|
||||
.stdout(Box::new(output))
|
||||
.stderr(Box::new(stderr))
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let wasi = wasi_env.import_object(&module).unwrap();
|
||||
|
||||
if _allow_exec_host_cmd {
|
||||
info!("Plugin({:?}) is able to run any host command, this may lead to some security issues!", path);
|
||||
}
|
||||
|
||||
let plugin_env = PluginEnv {
|
||||
let (instance, plugin_env) = start_plugin(
|
||||
plugin_id,
|
||||
&plugin,
|
||||
tab_index,
|
||||
senders: bus.senders.clone(),
|
||||
wasi_env,
|
||||
subscriptions: Arc::new(Mutex::new(HashSet::new())),
|
||||
_allow_exec_host_cmd,
|
||||
plugin_own_data_dir,
|
||||
};
|
||||
|
||||
let zellij = zellij_exports(&store, &plugin_env);
|
||||
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
|
||||
|
||||
let start = instance.exports.get_function("_start").unwrap();
|
||||
|
||||
// This eventually calls the `.load()` method
|
||||
start.call(&[]).unwrap();
|
||||
&bus,
|
||||
&store,
|
||||
&data_dir,
|
||||
&plugin_global_data_dir,
|
||||
);
|
||||
|
||||
plugin_map.insert(plugin_id, (instance, plugin_env));
|
||||
pid_tx.send(plugin_id).unwrap();
|
||||
|
|
@ -150,7 +135,6 @@ pub(crate) fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_d
|
|||
buf_tx.send(String::new()).unwrap();
|
||||
} else {
|
||||
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
|
||||
|
||||
let render = instance.exports.get_function("render").unwrap();
|
||||
|
||||
render
|
||||
|
|
@ -172,6 +156,71 @@ pub(crate) fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_d
|
|||
fs::remove_dir_all(plugin_global_data_dir.as_path()).unwrap();
|
||||
}
|
||||
|
||||
fn start_plugin(
|
||||
plugin_id: u32,
|
||||
plugin: &PluginConfig,
|
||||
tab_index: usize,
|
||||
bus: &Bus<PluginInstruction>,
|
||||
store: &Store,
|
||||
data_dir: &Path,
|
||||
plugin_global_data_dir: &Path,
|
||||
) -> (Instance, PluginEnv) {
|
||||
if plugin._allow_exec_host_cmd {
|
||||
info!(
|
||||
"Plugin({:?}) is able to run any host command, this may lead to some security issues!",
|
||||
plugin.path
|
||||
);
|
||||
}
|
||||
|
||||
let wasm_bytes = plugin
|
||||
.resolve_wasm_bytes(&data_dir.join("plugins/"))
|
||||
.unwrap_or_else(|| panic!("Cannot resolve wasm bytes for plugin {:?}", plugin));
|
||||
|
||||
// FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
|
||||
let module = Module::new(store, &wasm_bytes).unwrap();
|
||||
|
||||
let output = Pipe::new();
|
||||
let input = Pipe::new();
|
||||
let stderr = LoggingPipe::new(&plugin.location.to_string(), plugin_id);
|
||||
let plugin_own_data_dir = plugin_global_data_dir.join(Url::from(&plugin.location).to_string());
|
||||
fs::create_dir_all(&plugin_own_data_dir).unwrap();
|
||||
|
||||
let mut wasi_env = WasiState::new("Zellij")
|
||||
.env("CLICOLOR_FORCE", "1")
|
||||
.map_dir("/host", ".")
|
||||
.unwrap()
|
||||
.map_dir("/data", plugin_own_data_dir.as_path())
|
||||
.unwrap()
|
||||
.stdin(Box::new(input))
|
||||
.stdout(Box::new(output))
|
||||
.stderr(Box::new(stderr))
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let wasi = wasi_env.import_object(&module).unwrap();
|
||||
let mut plugin = plugin.clone();
|
||||
plugin.set_tab_index(tab_index);
|
||||
|
||||
let plugin_env = PluginEnv {
|
||||
plugin_id,
|
||||
plugin,
|
||||
senders: bus.senders.clone(),
|
||||
wasi_env,
|
||||
subscriptions: Arc::new(Mutex::new(HashSet::new())),
|
||||
plugin_own_data_dir,
|
||||
};
|
||||
|
||||
let zellij = zellij_exports(store, &plugin_env);
|
||||
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
|
||||
|
||||
let start = instance.exports.get_function("_start").unwrap();
|
||||
|
||||
// This eventually calls the `.load()` method
|
||||
start.call(&[]).unwrap();
|
||||
|
||||
(instance, plugin_env)
|
||||
}
|
||||
|
||||
// Plugin API ---------------------------------------------------------------------------------------------------------
|
||||
|
||||
pub(crate) fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObject {
|
||||
|
|
@ -210,16 +259,26 @@ fn host_unsubscribe(plugin_env: &PluginEnv) {
|
|||
}
|
||||
|
||||
fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
|
||||
match plugin_env.plugin.run {
|
||||
PluginType::Pane(Some(tab_index)) => {
|
||||
let selectable = selectable != 0;
|
||||
plugin_env
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::SetSelectable(
|
||||
PaneId::Plugin(plugin_env.plugin_id),
|
||||
selectable,
|
||||
plugin_env.tab_index,
|
||||
tab_index,
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
_ => {
|
||||
debug!(
|
||||
"{} - Calling method 'host_set_selectable' does nothing for headless plugins",
|
||||
plugin_env.plugin.location
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn host_get_plugin_ids(plugin_env: &PluginEnv) {
|
||||
let ids = PluginIds {
|
||||
|
|
@ -273,7 +332,7 @@ fn host_exec_cmd(plugin_env: &PluginEnv) {
|
|||
let command = cmdline.remove(0);
|
||||
|
||||
// Bail out if we're forbidden to run command
|
||||
if !plugin_env._allow_exec_host_cmd {
|
||||
if !plugin_env.plugin._allow_exec_host_cmd {
|
||||
warn!("This plugin isn't allow to run command in host side, skip running this command: '{cmd} {args}'.",
|
||||
cmd = command, args = cmdline.join(" "));
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use strum_macros::{EnumDiscriminants, EnumIter, EnumString, ToString};
|
||||
|
||||
|
|
@ -169,6 +170,28 @@ pub struct PluginIds {
|
|||
pub zellij_pid: u32,
|
||||
}
|
||||
|
||||
/// Tag used to identify the plugin in layout and config yaml files
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||
pub struct PluginTag(String);
|
||||
|
||||
impl PluginTag {
|
||||
pub fn new(url: impl Into<String>) -> Self {
|
||||
PluginTag(url.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PluginTag> for String {
|
||||
fn from(tag: PluginTag) -> Self {
|
||||
tag.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PluginTag {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||
pub struct PluginCapabilities {
|
||||
pub arrow_fonts: bool,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ macro_rules! register_plugin {
|
|||
#[no_mangle]
|
||||
pub fn update() {
|
||||
STATE.with(|state| {
|
||||
state.borrow_mut().update($crate::shim::object_from_stdin());
|
||||
state
|
||||
.borrow_mut()
|
||||
.update($crate::shim::object_from_stdin().unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ pub fn set_selectable(selectable: bool) {
|
|||
// Query Functions
|
||||
pub fn get_plugin_ids() -> PluginIds {
|
||||
unsafe { host_get_plugin_ids() };
|
||||
object_from_stdin()
|
||||
object_from_stdin().unwrap()
|
||||
}
|
||||
|
||||
// Host Functions
|
||||
|
|
@ -45,10 +45,10 @@ pub fn exec_cmd(cmd: &[&str]) {
|
|||
// Internal Functions
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn object_from_stdin<T: DeserializeOwned>() -> T {
|
||||
pub fn object_from_stdin<T: DeserializeOwned>() -> Result<T, serde_json::Error> {
|
||||
let mut json = String::new();
|
||||
io::stdin().read_line(&mut json).unwrap();
|
||||
serde_json::from_str(&json).unwrap()
|
||||
serde_json::from_str(&json)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
|
|
|
|||
|
|
@ -21,11 +21,13 @@ nix = "0.19.1"
|
|||
once_cell = "1.7.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_yaml = "0.8"
|
||||
serde_json = "1.0"
|
||||
signal-hook = "0.3"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
structopt = "0.3"
|
||||
strum = "0.20.0"
|
||||
termion = "1.5.0"
|
||||
url = { version = "2.2.2", features = ["serde"] }
|
||||
vte = "0.10.1"
|
||||
zellij-tile = { path = "../zellij-tile/", version = "0.18.0" }
|
||||
log = "0.4.14"
|
||||
|
|
|
|||
|
|
@ -248,6 +248,13 @@ keybinds:
|
|||
key: [Ctrl: 'q',]
|
||||
- action: [Detach,]
|
||||
key: [Char: 'd',]
|
||||
plugins:
|
||||
- path: tab-bar
|
||||
tag: tab-bar
|
||||
- path: status-bar
|
||||
tag: status-bar
|
||||
- path: strider
|
||||
tag: strider
|
||||
|
||||
# Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP
|
||||
# eg. when terminal window with an active zellij session is closed
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ template:
|
|||
Fixed: 1
|
||||
run:
|
||||
plugin:
|
||||
path: tab-bar
|
||||
location: "zellij:tab-bar"
|
||||
- direction: Vertical
|
||||
body: true
|
||||
- direction: Vertical
|
||||
|
|
@ -17,6 +17,6 @@ template:
|
|||
Fixed: 2
|
||||
run:
|
||||
plugin:
|
||||
path: status-bar
|
||||
location: "zellij:status-bar"
|
||||
tabs:
|
||||
- direction: Vertical
|
||||
|
|
|
|||
|
|
@ -8,6 +8,6 @@ template:
|
|||
Fixed: 1
|
||||
run:
|
||||
plugin:
|
||||
path: tab-bar
|
||||
location: "zellij:tab-bar"
|
||||
- direction: Vertical
|
||||
body: true
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ template:
|
|||
Fixed: 1
|
||||
run:
|
||||
plugin:
|
||||
path: tab-bar
|
||||
location: "zellij:tab-bar"
|
||||
- direction: Vertical
|
||||
body: true
|
||||
- direction: Vertical
|
||||
|
|
@ -17,7 +17,7 @@ template:
|
|||
Fixed: 2
|
||||
run:
|
||||
plugin:
|
||||
path: status-bar
|
||||
location: "zellij:status-bar"
|
||||
tabs:
|
||||
- direction: Vertical
|
||||
parts:
|
||||
|
|
@ -26,5 +26,5 @@ tabs:
|
|||
Percent: 20
|
||||
run:
|
||||
plugin:
|
||||
path: strider
|
||||
location: "zellij:strider"
|
||||
- direction: Horizontal
|
||||
|
|
|
|||
|
|
@ -5,15 +5,16 @@ use std::fs::File;
|
|||
use std::io::{self, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use super::keybinds::{Keybinds, KeybindsFromYaml};
|
||||
use super::options::Options;
|
||||
use super::plugins::{PluginsConfig, PluginsConfigError, PluginsConfigFromYaml};
|
||||
use super::theme::ThemesFromYaml;
|
||||
use crate::cli::{CliArgs, Command};
|
||||
use crate::setup;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
const DEFAULT_CONFIG_FILE_NAME: &str = "config.yaml";
|
||||
|
||||
type ConfigResult = Result<Config, ConfigError>;
|
||||
|
|
@ -25,6 +26,8 @@ pub struct ConfigFromYaml {
|
|||
pub options: Option<Options>,
|
||||
pub keybinds: Option<KeybindsFromYaml>,
|
||||
pub themes: Option<ThemesFromYaml>,
|
||||
#[serde(default)]
|
||||
pub plugins: PluginsConfigFromYaml,
|
||||
}
|
||||
|
||||
/// Main configuration.
|
||||
|
|
@ -33,6 +36,7 @@ pub struct Config {
|
|||
pub keybinds: Keybinds,
|
||||
pub options: Options,
|
||||
pub themes: Option<ThemesFromYaml>,
|
||||
pub plugins: PluginsConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -47,6 +51,8 @@ pub enum ConfigError {
|
|||
FromUtf8(std::string::FromUtf8Error),
|
||||
// Naming a part in a tab is unsupported
|
||||
LayoutNameInTab(LayoutNameInTabError),
|
||||
// Plugins have a semantic error, usually trying to parse two of the same tag
|
||||
PluginsError(PluginsConfigError),
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
|
|
@ -54,11 +60,13 @@ impl Default for Config {
|
|||
let keybinds = Keybinds::default();
|
||||
let options = Options::default();
|
||||
let themes = None;
|
||||
let plugins = PluginsConfig::default();
|
||||
|
||||
Config {
|
||||
keybinds,
|
||||
options,
|
||||
themes,
|
||||
plugins,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -106,9 +114,11 @@ impl Config {
|
|||
let keybinds = Keybinds::get_default_keybinds_with_config(config.keybinds);
|
||||
let options = Options::from_yaml(config.options);
|
||||
let themes = config.themes;
|
||||
let plugins = PluginsConfig::get_plugins_with_default(config.plugins.try_into()?);
|
||||
Ok(Config {
|
||||
keybinds,
|
||||
options,
|
||||
plugins,
|
||||
themes,
|
||||
})
|
||||
}
|
||||
|
|
@ -129,10 +139,11 @@ impl Config {
|
|||
}
|
||||
|
||||
/// Gets default configuration from assets
|
||||
// TODO Deserialize the Configuration from bytes &[u8],
|
||||
// TODO Deserialize the Config from bytes &[u8],
|
||||
// once serde-yaml supports zero-copy
|
||||
pub fn from_default_assets() -> ConfigResult {
|
||||
Self::from_yaml(String::from_utf8(setup::DEFAULT_CONFIG.to_vec())?.as_str())
|
||||
let cfg = String::from_utf8(setup::DEFAULT_CONFIG.to_vec())?;
|
||||
Self::from_yaml(cfg.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -179,6 +190,7 @@ impl Display for ConfigError {
|
|||
ConfigError::LayoutNameInTab(ref err) => {
|
||||
write!(formatter, "There was an error in the layout file, {}", err)
|
||||
}
|
||||
ConfigError::PluginsError(ref err) => write!(formatter, "PluginsError: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -191,6 +203,7 @@ impl std::error::Error for ConfigError {
|
|||
ConfigError::Serde(ref err) => Some(err),
|
||||
ConfigError::FromUtf8(ref err) => Some(err),
|
||||
ConfigError::LayoutNameInTab(ref err) => Some(err),
|
||||
ConfigError::PluginsError(ref err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -219,6 +232,12 @@ impl From<LayoutNameInTabError> for ConfigError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<PluginsConfigError> for ConfigError {
|
||||
fn from(err: PluginsConfigError) -> ConfigError {
|
||||
ConfigError::PluginsError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// The unit test location.
|
||||
#[cfg(test)]
|
||||
mod config_test {
|
||||
|
|
|
|||
|
|
@ -18,14 +18,18 @@ use crate::{
|
|||
};
|
||||
use crate::{serde, serde_yaml};
|
||||
|
||||
use super::plugins::{PluginTag, PluginsConfigError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::vec::Vec;
|
||||
use std::{
|
||||
cmp::max,
|
||||
fmt, fs,
|
||||
ops::Not,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::{fs::File, io::prelude::*};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
|
||||
#[serde(crate = "self::serde")]
|
||||
|
|
@ -56,17 +60,68 @@ pub enum SplitSize {
|
|||
#[serde(crate = "self::serde")]
|
||||
pub enum Run {
|
||||
#[serde(rename = "plugin")]
|
||||
Plugin(Option<RunPlugin>),
|
||||
Plugin(RunPlugin),
|
||||
#[serde(rename = "command")]
|
||||
Command(RunCommand),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
pub struct RunPlugin {
|
||||
pub path: PathBuf,
|
||||
pub enum RunFromYaml {
|
||||
#[serde(rename = "plugin")]
|
||||
Plugin(RunPluginFromYaml),
|
||||
#[serde(rename = "command")]
|
||||
Command(RunCommand),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
pub struct RunPluginFromYaml {
|
||||
#[serde(default)]
|
||||
pub _allow_exec_host_cmd: bool,
|
||||
pub location: Url,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
pub struct RunPlugin {
|
||||
#[serde(default)]
|
||||
pub _allow_exec_host_cmd: bool,
|
||||
pub location: RunPluginLocation,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
pub enum RunPluginLocation {
|
||||
File(PathBuf),
|
||||
Zellij(PluginTag),
|
||||
}
|
||||
|
||||
impl From<&RunPluginLocation> for Url {
|
||||
fn from(location: &RunPluginLocation) -> Self {
|
||||
let url = match location {
|
||||
RunPluginLocation::File(path) => format!(
|
||||
"file:{}",
|
||||
path.clone().into_os_string().into_string().unwrap()
|
||||
),
|
||||
RunPluginLocation::Zellij(tag) => format!("zellij:{}", tag),
|
||||
};
|
||||
Self::parse(&url).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RunPluginLocation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
Self::File(path) => write!(
|
||||
f,
|
||||
"{}",
|
||||
path.clone().into_os_string().into_string().unwrap()
|
||||
),
|
||||
|
||||
Self::Zellij(tag) => write!(f, "{}", tag),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The layout struct ultimately used to build the layouts.
|
||||
|
|
@ -193,7 +248,7 @@ pub struct LayoutTemplate {
|
|||
#[serde(default)]
|
||||
pub body: bool,
|
||||
pub split_size: Option<SplitSize>,
|
||||
pub run: Option<Run>,
|
||||
pub run: Option<RunFromYaml>,
|
||||
}
|
||||
|
||||
impl LayoutTemplate {
|
||||
|
|
@ -235,9 +290,9 @@ pub struct TabLayout {
|
|||
#[serde(default)]
|
||||
pub parts: Vec<TabLayout>,
|
||||
pub split_size: Option<SplitSize>,
|
||||
pub run: Option<Run>,
|
||||
#[serde(default)]
|
||||
pub name: String,
|
||||
pub run: Option<RunFromYaml>,
|
||||
}
|
||||
|
||||
impl TabLayout {
|
||||
|
|
@ -291,25 +346,23 @@ impl Layout {
|
|||
split_space(space, self)
|
||||
}
|
||||
|
||||
pub fn merge_tab_layout(&mut self, tab: TabLayout) {
|
||||
self.parts.push(tab.into());
|
||||
}
|
||||
|
||||
pub fn merge_layout_parts(&mut self, mut parts: Vec<Layout>) {
|
||||
self.parts.append(&mut parts);
|
||||
}
|
||||
|
||||
fn from_vec_tab_layout(tab_layout: Vec<TabLayout>) -> Vec<Self> {
|
||||
fn from_vec_tab_layout(tab_layout: Vec<TabLayout>) -> Result<Vec<Self>, ConfigError> {
|
||||
tab_layout
|
||||
.iter()
|
||||
.map(|tab_layout| Layout::from(tab_layout.to_owned()))
|
||||
.map(|tab_layout| Layout::try_from(tab_layout.to_owned()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn from_vec_template_layout(layout_template: Vec<LayoutTemplate>) -> Vec<Self> {
|
||||
fn from_vec_template_layout(
|
||||
layout_template: Vec<LayoutTemplate>,
|
||||
) -> Result<Vec<Self>, ConfigError> {
|
||||
layout_template
|
||||
.iter()
|
||||
.map(|layout_template| Layout::from(layout_template.to_owned()))
|
||||
.map(|layout_template| Layout::try_from(layout_template.to_owned()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
@ -408,15 +461,55 @@ fn split_space(space_to_split: &PaneGeom, layout: &Layout) -> Vec<(Layout, PaneG
|
|||
pane_positions
|
||||
}
|
||||
|
||||
impl From<TabLayout> for Layout {
|
||||
fn from(tab: TabLayout) -> Self {
|
||||
Layout {
|
||||
impl TryFrom<Url> for RunPluginLocation {
|
||||
type Error = PluginsConfigError;
|
||||
|
||||
fn try_from(url: Url) -> Result<Self, Self::Error> {
|
||||
match url.scheme() {
|
||||
"zellij" => Ok(Self::Zellij(PluginTag::new(url.path()))),
|
||||
"file" => {
|
||||
let path = PathBuf::from(url.path());
|
||||
let canonicalize = |p: &Path| {
|
||||
fs::canonicalize(p)
|
||||
.map_err(|_| PluginsConfigError::InvalidPluginLocation(p.to_owned()))
|
||||
};
|
||||
canonicalize(&path)
|
||||
.or_else(|_| match path.strip_prefix("/") {
|
||||
Ok(path) => canonicalize(path),
|
||||
Err(_) => Err(PluginsConfigError::InvalidPluginLocation(path.to_owned())),
|
||||
})
|
||||
.map(Self::File)
|
||||
}
|
||||
_ => Err(PluginsConfigError::InvalidUrl(url)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RunFromYaml> for Run {
|
||||
type Error = PluginsConfigError;
|
||||
|
||||
fn try_from(run: RunFromYaml) -> Result<Self, Self::Error> {
|
||||
match run {
|
||||
RunFromYaml::Command(command) => Ok(Run::Command(command)),
|
||||
RunFromYaml::Plugin(plugin) => Ok(Run::Plugin(RunPlugin {
|
||||
_allow_exec_host_cmd: plugin._allow_exec_host_cmd,
|
||||
location: plugin.location.try_into()?,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TabLayout> for Layout {
|
||||
type Error = ConfigError;
|
||||
|
||||
fn try_from(tab: TabLayout) -> Result<Self, Self::Error> {
|
||||
Ok(Layout {
|
||||
direction: tab.direction,
|
||||
borderless: tab.borderless,
|
||||
parts: Self::from_vec_tab_layout(tab.parts),
|
||||
parts: Self::from_vec_tab_layout(tab.parts)?,
|
||||
split_size: tab.split_size,
|
||||
run: tab.run,
|
||||
}
|
||||
run: tab.run.map(Run::try_from).transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -433,15 +526,22 @@ impl From<TabLayout> for LayoutTemplate {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<LayoutTemplate> for Layout {
|
||||
fn from(template: LayoutTemplate) -> Self {
|
||||
Layout {
|
||||
impl TryFrom<LayoutTemplate> for Layout {
|
||||
type Error = ConfigError;
|
||||
|
||||
fn try_from(template: LayoutTemplate) -> Result<Self, Self::Error> {
|
||||
Ok(Layout {
|
||||
direction: template.direction,
|
||||
borderless: template.borderless,
|
||||
parts: Self::from_vec_template_layout(template.parts),
|
||||
parts: Self::from_vec_template_layout(template.parts)?,
|
||||
split_size: template.split_size,
|
||||
run: template.run,
|
||||
}
|
||||
run: template
|
||||
.run
|
||||
.map(Run::try_from)
|
||||
// FIXME: This is just Result::transpose but that method is unstable, when it
|
||||
// stabalizes we should swap this out.
|
||||
.map_or(Ok(None), |r| r.map(Some))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ pub mod keybinds;
|
|||
pub mod layout;
|
||||
pub mod mouse;
|
||||
pub mod options;
|
||||
pub mod plugins;
|
||||
pub mod theme;
|
||||
|
||||
use termion::input::TermRead;
|
||||
|
|
|
|||
315
zellij-utils/src/input/plugins.rs
Normal file
315
zellij-utils/src/input/plugins.rs
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
//! Plugins configuration metadata
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Display};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use super::config::ConfigFromYaml;
|
||||
use super::layout::{RunPlugin, RunPluginLocation};
|
||||
use crate::setup;
|
||||
pub use zellij_tile::data::PluginTag;
|
||||
|
||||
lazy_static! {
|
||||
static ref DEFAULT_CONFIG_PLUGINS: PluginsConfig = {
|
||||
let cfg = String::from_utf8(setup::DEFAULT_CONFIG.to_vec()).unwrap();
|
||||
let cfg_yaml: ConfigFromYaml = serde_yaml::from_str(cfg.as_str()).unwrap();
|
||||
PluginsConfig::try_from(cfg_yaml.plugins).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
|
||||
pub struct PluginsConfigFromYaml(Vec<PluginConfigFromYaml>);
|
||||
|
||||
/// Used in the config struct for plugin metadata
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct PluginsConfig(HashMap<PluginTag, PluginConfig>);
|
||||
|
||||
impl PluginsConfig {
|
||||
pub fn new() -> Self {
|
||||
Self(HashMap::new())
|
||||
}
|
||||
|
||||
/// Entrypoint from the config module
|
||||
pub fn get_plugins_with_default(user_plugins: Self) -> Self {
|
||||
let mut base_plugins = DEFAULT_CONFIG_PLUGINS.clone();
|
||||
base_plugins.0.extend(user_plugins.0);
|
||||
base_plugins
|
||||
}
|
||||
|
||||
/// Get plugin config from run configuration specified in layout files.
|
||||
pub fn get(&self, run: impl Borrow<RunPlugin>) -> Option<PluginConfig> {
|
||||
let run = run.borrow();
|
||||
match &run.location {
|
||||
RunPluginLocation::File(path) => Some(PluginConfig {
|
||||
path: path.clone(),
|
||||
run: PluginType::Pane(None),
|
||||
_allow_exec_host_cmd: run._allow_exec_host_cmd,
|
||||
location: run.location.clone(),
|
||||
}),
|
||||
RunPluginLocation::Zellij(tag) => self.0.get(tag).cloned().map(|plugin| PluginConfig {
|
||||
_allow_exec_host_cmd: run._allow_exec_host_cmd,
|
||||
..plugin
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &PluginConfig> {
|
||||
self.0.values()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PluginsConfig {
|
||||
fn default() -> Self {
|
||||
Self::get_plugins_with_default(PluginsConfig::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PluginsConfigFromYaml> for PluginsConfig {
|
||||
type Error = PluginsConfigError;
|
||||
|
||||
fn try_from(yaml: PluginsConfigFromYaml) -> Result<Self, PluginsConfigError> {
|
||||
let mut plugins = HashMap::new();
|
||||
for plugin in yaml.0 {
|
||||
if plugins.contains_key(&plugin.tag) {
|
||||
return Err(PluginsConfigError::DuplicatePlugins(plugin.tag));
|
||||
}
|
||||
plugins.insert(plugin.tag.clone(), plugin.into());
|
||||
}
|
||||
|
||||
Ok(PluginsConfig(plugins))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PluginConfigFromYaml> for PluginConfig {
|
||||
fn from(plugin: PluginConfigFromYaml) -> Self {
|
||||
PluginConfig {
|
||||
path: plugin.path,
|
||||
run: match plugin.run {
|
||||
PluginTypeFromYaml::Pane => PluginType::Pane(None),
|
||||
PluginTypeFromYaml::Headless => PluginType::Headless,
|
||||
},
|
||||
_allow_exec_host_cmd: plugin._allow_exec_host_cmd,
|
||||
location: RunPluginLocation::Zellij(plugin.tag),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin metadata
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct PluginConfig {
|
||||
/// Path of the plugin, see resolve_wasm_bytes for resolution semantics
|
||||
pub path: PathBuf,
|
||||
/// Plugin type
|
||||
pub run: PluginType,
|
||||
/// Allow command execution from plugin
|
||||
pub _allow_exec_host_cmd: bool,
|
||||
/// Original location of the
|
||||
pub location: RunPluginLocation,
|
||||
}
|
||||
|
||||
impl PluginConfig {
|
||||
/// Resolve wasm plugin bytes for the plugin path and given plugin directory. Attempts to first
|
||||
/// resolve the plugin path as an absolute path, then adds a ".wasm" extension to the path and
|
||||
/// resolves that, finally we use the plugin directoy joined with the path with an appended
|
||||
/// ".wasm" extension. So if our path is "tab-bar" and the given plugin dir is
|
||||
/// "/home/bob/.zellij/plugins" the lookup chain will be this:
|
||||
///
|
||||
/// ```bash
|
||||
/// /tab-bar
|
||||
/// /tab-bar.wasm
|
||||
/// /home/bob/.zellij/plugins/tab-bar.wasm
|
||||
/// ```
|
||||
///
|
||||
pub fn resolve_wasm_bytes(&self, plugin_dir: &Path) -> Option<Vec<u8>> {
|
||||
fs::read(&self.path)
|
||||
.or_else(|_| fs::read(&self.path.with_extension("wasm")))
|
||||
.or_else(|_| fs::read(plugin_dir.join(&self.path).with_extension("wasm")))
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Sets the tab index inside of the plugin type of the run field.
|
||||
pub fn set_tab_index(&mut self, tab_index: usize) {
|
||||
match self.run {
|
||||
PluginType::Pane(..) => {
|
||||
self.run = PluginType::Pane(Some(tab_index));
|
||||
}
|
||||
PluginType::Headless => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of the plugin. Defaults to Pane.
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum PluginType {
|
||||
// TODO: A plugin with output thats cloned across every pane in a tab, or across the entire
|
||||
// application might be useful
|
||||
// Tab
|
||||
// Static
|
||||
/// Starts immediately when Zellij is started and runs without a visible pane
|
||||
Headless,
|
||||
/// Runs once per pane declared inside a layout file
|
||||
Pane(Option<usize>), // tab_index
|
||||
}
|
||||
|
||||
impl Default for PluginType {
|
||||
fn default() -> Self {
|
||||
Self::Pane(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
|
||||
pub struct PluginConfigFromYaml {
|
||||
pub path: PathBuf,
|
||||
pub tag: PluginTag,
|
||||
#[serde(default)]
|
||||
pub run: PluginTypeFromYaml,
|
||||
#[serde(default)]
|
||||
pub config: serde_yaml::Value,
|
||||
#[serde(default)]
|
||||
pub _allow_exec_host_cmd: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum PluginTypeFromYaml {
|
||||
Headless,
|
||||
Pane,
|
||||
}
|
||||
|
||||
impl Default for PluginTypeFromYaml {
|
||||
fn default() -> Self {
|
||||
Self::Pane
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum PluginsConfigError {
|
||||
DuplicatePlugins(PluginTag),
|
||||
InvalidUrl(Url),
|
||||
InvalidPluginLocation(PathBuf),
|
||||
}
|
||||
|
||||
impl std::error::Error for PluginsConfigError {}
|
||||
impl Display for PluginsConfigError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
PluginsConfigError::DuplicatePlugins(tag) => write!(
|
||||
formatter,
|
||||
"Duplication in plugin tag names is not allowed: '{}'",
|
||||
String::from(tag.clone())
|
||||
),
|
||||
PluginsConfigError::InvalidUrl(url) => write!(
|
||||
formatter,
|
||||
"Only 'file:' and 'zellij:' url schemes are supported for plugin lookup. '{}' does not match either.",
|
||||
url
|
||||
),
|
||||
PluginsConfigError::InvalidPluginLocation(path) => write!(
|
||||
formatter,
|
||||
"Could not find plugin at the path: '{:?}'", path
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::input::config::ConfigError;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[test]
|
||||
fn run_plugin_permissions_are_inherited() -> Result<(), ConfigError> {
|
||||
let yaml_plugins: PluginsConfigFromYaml = serde_yaml::from_str(
|
||||
"
|
||||
- path: boo.wasm
|
||||
tag: boo
|
||||
_allow_exec_host_cmd: false
|
||||
",
|
||||
)?;
|
||||
let plugins = PluginsConfig::try_from(yaml_plugins)?;
|
||||
|
||||
assert_eq!(
|
||||
plugins.get(RunPlugin {
|
||||
_allow_exec_host_cmd: true,
|
||||
location: RunPluginLocation::Zellij(PluginTag::new("boo"))
|
||||
}),
|
||||
Some(PluginConfig {
|
||||
_allow_exec_host_cmd: true,
|
||||
path: PathBuf::from("boo.wasm"),
|
||||
location: RunPluginLocation::Zellij(PluginTag::new("boo")),
|
||||
run: PluginType::Pane(None),
|
||||
})
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_from_yaml_fails_when_duplicate_tag_names_are_present() -> Result<(), ConfigError> {
|
||||
let ConfigFromYaml { plugins, .. } = serde_yaml::from_str(
|
||||
"
|
||||
plugins:
|
||||
- path: /foo/bar/baz.wasm
|
||||
tag: boo
|
||||
- path: /foo/bar/boo.wasm
|
||||
tag: boo
|
||||
",
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
PluginsConfig::try_from(plugins),
|
||||
Err(PluginsConfigError::DuplicatePlugins(PluginTag::new("boo")))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_plugins() -> Result<(), ConfigError> {
|
||||
let ConfigFromYaml { plugins, .. } = serde_yaml::from_str(
|
||||
"
|
||||
plugins:
|
||||
- path: boo.wasm
|
||||
tag: boo
|
||||
",
|
||||
)?;
|
||||
let plugins = PluginsConfig::get_plugins_with_default(plugins.try_into()?);
|
||||
|
||||
assert_eq!(plugins.iter().collect::<Vec<_>>().len(), 4);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_plugins_allow_overriding() -> Result<(), ConfigError> {
|
||||
let ConfigFromYaml { plugins, .. } = serde_yaml::from_str(
|
||||
"
|
||||
plugins:
|
||||
- path: boo.wasm
|
||||
tag: tab-bar
|
||||
",
|
||||
)?;
|
||||
let plugins = PluginsConfig::get_plugins_with_default(plugins.try_into()?);
|
||||
|
||||
assert_eq!(
|
||||
plugins.get(RunPlugin {
|
||||
_allow_exec_host_cmd: false,
|
||||
location: RunPluginLocation::Zellij(PluginTag::new("tab-bar"))
|
||||
}),
|
||||
Some(PluginConfig {
|
||||
_allow_exec_host_cmd: false,
|
||||
path: PathBuf::from("boo.wasm"),
|
||||
location: RunPluginLocation::Zellij(PluginTag::new("tab-bar")),
|
||||
run: PluginType::Pane(None),
|
||||
})
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ template:
|
|||
Fixed: 1
|
||||
run:
|
||||
plugin:
|
||||
path: tab-bar
|
||||
location: "zellij:tab-bar"
|
||||
- direction: Horizontal
|
||||
body: true
|
||||
- direction: Vertical
|
||||
|
|
@ -15,8 +15,7 @@ template:
|
|||
Fixed: 2
|
||||
run:
|
||||
plugin:
|
||||
path: status-bar
|
||||
|
||||
location: "zellij:status-bar"
|
||||
tabs:
|
||||
- direction: Vertical
|
||||
parts:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use super::super::layout::*;
|
||||
use std::convert::TryInto;
|
||||
|
||||
fn layout_test_dir(layout: String) -> PathBuf {
|
||||
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
|
|
@ -45,10 +46,10 @@ fn default_layout_merged_correctly() {
|
|||
borderless: true,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Fixed(1)),
|
||||
run: Some(Run::Plugin(Some(RunPlugin {
|
||||
path: "tab-bar".into(),
|
||||
..Default::default()
|
||||
}))),
|
||||
run: Some(Run::Plugin(RunPlugin {
|
||||
location: RunPluginLocation::Zellij(PluginTag::new("tab-bar")),
|
||||
_allow_exec_host_cmd: false,
|
||||
})),
|
||||
},
|
||||
Layout {
|
||||
direction: Direction::Vertical,
|
||||
|
|
@ -62,16 +63,16 @@ fn default_layout_merged_correctly() {
|
|||
borderless: true,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Fixed(2)),
|
||||
run: Some(Run::Plugin(Some(RunPlugin {
|
||||
path: "status-bar".into(),
|
||||
..Default::default()
|
||||
}))),
|
||||
run: Some(Run::Plugin(RunPlugin {
|
||||
location: RunPluginLocation::Zellij(PluginTag::new("status-bar")),
|
||||
_allow_exec_host_cmd: false,
|
||||
})),
|
||||
},
|
||||
],
|
||||
split_size: None,
|
||||
run: None,
|
||||
};
|
||||
assert_eq!(merged_layout, tab_layout.into());
|
||||
assert_eq!(merged_layout, tab_layout.try_into().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -89,10 +90,10 @@ fn default_layout_new_tab_correct() {
|
|||
borderless: true,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Fixed(1)),
|
||||
run: Some(Run::Plugin(Some(RunPlugin {
|
||||
path: "tab-bar".into(),
|
||||
..Default::default()
|
||||
}))),
|
||||
run: Some(Run::Plugin(RunPlugin {
|
||||
location: RunPluginLocation::Zellij(PluginTag::new("tab-bar")),
|
||||
_allow_exec_host_cmd: false,
|
||||
})),
|
||||
},
|
||||
Layout {
|
||||
direction: Direction::Horizontal,
|
||||
|
|
@ -106,16 +107,16 @@ fn default_layout_new_tab_correct() {
|
|||
borderless: true,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Fixed(2)),
|
||||
run: Some(Run::Plugin(Some(RunPlugin {
|
||||
path: "status-bar".into(),
|
||||
..Default::default()
|
||||
}))),
|
||||
run: Some(Run::Plugin(RunPlugin {
|
||||
location: RunPluginLocation::Zellij(PluginTag::new("status-bar")),
|
||||
_allow_exec_host_cmd: false,
|
||||
})),
|
||||
},
|
||||
],
|
||||
split_size: None,
|
||||
run: None,
|
||||
};
|
||||
assert_eq!(merged_layout, tab_layout.into());
|
||||
assert_eq!(merged_layout, tab_layout.try_into().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -207,7 +208,7 @@ fn three_panes_with_tab_merged_correctly() {
|
|||
split_size: None,
|
||||
run: None,
|
||||
};
|
||||
assert_eq!(merged_layout, tab_layout.into());
|
||||
assert_eq!(merged_layout, tab_layout.try_into().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -229,7 +230,7 @@ fn three_panes_with_tab_new_tab_is_correct() {
|
|||
split_size: None,
|
||||
run: None,
|
||||
};
|
||||
assert_eq!(merged_layout, tab_layout.into());
|
||||
assert_eq!(merged_layout, tab_layout.try_into().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -265,10 +266,10 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
|
|||
borderless: false,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Fixed(1)),
|
||||
run: Some(Run::Plugin(Some(RunPlugin {
|
||||
path: "tab-bar".into(),
|
||||
..Default::default()
|
||||
}))),
|
||||
run: Some(Run::Plugin(RunPlugin {
|
||||
location: RunPluginLocation::Zellij(PluginTag::new("tab-bar")),
|
||||
_allow_exec_host_cmd: false,
|
||||
})),
|
||||
},
|
||||
Layout {
|
||||
direction: Direction::Vertical,
|
||||
|
|
@ -312,16 +313,16 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
|
|||
borderless: false,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Fixed(2)),
|
||||
run: Some(Run::Plugin(Some(RunPlugin {
|
||||
path: "status-bar".into(),
|
||||
..Default::default()
|
||||
}))),
|
||||
run: Some(Run::Plugin(RunPlugin {
|
||||
location: RunPluginLocation::Zellij(PluginTag::new("status-bar")),
|
||||
_allow_exec_host_cmd: false,
|
||||
})),
|
||||
},
|
||||
],
|
||||
split_size: None,
|
||||
run: None,
|
||||
};
|
||||
assert_eq!(merged_layout, tab_layout.into());
|
||||
assert_eq!(merged_layout, tab_layout.try_into().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -339,10 +340,10 @@ fn three_panes_with_tab_and_default_plugins_new_tab_is_correct() {
|
|||
borderless: false,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Fixed(1)),
|
||||
run: Some(Run::Plugin(Some(RunPlugin {
|
||||
path: "tab-bar".into(),
|
||||
..Default::default()
|
||||
}))),
|
||||
run: Some(Run::Plugin(RunPlugin {
|
||||
location: RunPluginLocation::Zellij(PluginTag::new("tab-bar")),
|
||||
_allow_exec_host_cmd: false,
|
||||
})),
|
||||
},
|
||||
Layout {
|
||||
direction: Direction::Horizontal,
|
||||
|
|
@ -356,16 +357,16 @@ fn three_panes_with_tab_and_default_plugins_new_tab_is_correct() {
|
|||
borderless: false,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Fixed(2)),
|
||||
run: Some(Run::Plugin(Some(RunPlugin {
|
||||
path: "status-bar".into(),
|
||||
..Default::default()
|
||||
}))),
|
||||
run: Some(Run::Plugin(RunPlugin {
|
||||
location: RunPluginLocation::Zellij(PluginTag::new("status-bar")),
|
||||
_allow_exec_host_cmd: false,
|
||||
})),
|
||||
},
|
||||
],
|
||||
split_size: None,
|
||||
run: None,
|
||||
};
|
||||
assert_eq!(merged_layout, tab_layout.into());
|
||||
assert_eq!(merged_layout, tab_layout.try_into().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -473,7 +474,7 @@ fn deeply_nested_tab_merged_correctly() {
|
|||
split_size: None,
|
||||
run: None,
|
||||
};
|
||||
assert_eq!(merged_layout, tab_layout.into());
|
||||
assert_eq!(merged_layout, tab_layout.try_into().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -523,7 +524,7 @@ fn three_tabs_tab_one_merged_correctly() {
|
|||
run: None,
|
||||
};
|
||||
|
||||
assert_eq!(merged_layout, tab_layout.into());
|
||||
assert_eq!(merged_layout, tab_layout.try_into().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -573,7 +574,7 @@ fn three_tabs_tab_two_merged_correctly() {
|
|||
run: None,
|
||||
};
|
||||
|
||||
assert_eq!(merged_layout, tab_layout.into());
|
||||
assert_eq!(merged_layout, tab_layout.try_into().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -622,7 +623,7 @@ fn three_tabs_tab_three_merged_correctly() {
|
|||
split_size: None,
|
||||
run: None,
|
||||
};
|
||||
assert_eq!(merged_layout, tab_layout.into());
|
||||
assert_eq!(merged_layout, tab_layout.try_into().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -660,7 +661,7 @@ fn no_tabs_merged_correctly() {
|
|||
run: None,
|
||||
};
|
||||
|
||||
assert_eq!(merged_layout, tab_layout.into());
|
||||
assert_eq!(merged_layout, tab_layout.try_into().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -716,5 +717,5 @@ fn no_layout_template_merged_correctly() {
|
|||
borderless: false,
|
||||
};
|
||||
|
||||
assert_eq!(merged_layout, tab_layout.into());
|
||||
assert_eq!(merged_layout, tab_layout.try_into().unwrap());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use crate::{
|
||||
cli::CliArgs,
|
||||
errors::{get_current_ctx, ErrorContext},
|
||||
input::{actions::Action, layout::LayoutFromYaml, options::Options},
|
||||
input::{actions::Action, layout::LayoutFromYaml, options::Options, plugins::PluginsConfig},
|
||||
pane_size::Size,
|
||||
};
|
||||
use interprocess::local_socket::LocalSocketStream;
|
||||
|
|
@ -58,7 +58,13 @@ pub enum ClientToServerMsg {
|
|||
// Disconnect from the session we're connected to
|
||||
DisconnectFromSession,*/
|
||||
TerminalResize(Size),
|
||||
NewClient(ClientAttributes, Box<CliArgs>, Box<Options>, LayoutFromYaml),
|
||||
NewClient(
|
||||
ClientAttributes,
|
||||
Box<CliArgs>,
|
||||
Box<Options>,
|
||||
LayoutFromYaml,
|
||||
Option<PluginsConfig>,
|
||||
),
|
||||
AttachClient(ClientAttributes, bool, Options),
|
||||
Action(Action),
|
||||
ClientExited,
|
||||
|
|
|
|||
|
|
@ -188,7 +188,6 @@ impl Setup {
|
|||
return Err(e);
|
||||
}
|
||||
};
|
||||
//.map(|layout| layout.template);
|
||||
|
||||
if let Some(Command::Setup(ref setup)) = &opts.command {
|
||||
setup.from_cli(opts, &config_options).map_or_else(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue