aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2023-10-16 13:42:19 +0200
committerGitHub <noreply@github.com>2023-10-16 13:42:19 +0200
commitd2b6fb5c5b0423143d71218196a56ea3e395cd47 (patch)
treeb913e91850c9eb98f554aec2dfc94cbe9b11bca6
parentfix(plugins): make new auxiliary functions public (#2765) (diff)
downloadsl0ppij-d2b6fb5c5b0423143d71218196a56ea3e395cd47.tar.gz
sl0ppij-d2b6fb5c5b0423143d71218196a56ea3e395cd47.tar.xz
sl0ppij-d2b6fb5c5b0423143d71218196a56ea3e395cd47.zip
feat(plugins): plugin run_command api (#2862)
* prototype * add tests * style(fmt): rustfmt * update comments * deprecation warning for execcmd
-rw-r--r--default-plugins/fixture-plugin-for-tests/src/main.rs17
-rw-r--r--default-plugins/session-manager/src/main.rs1
-rw-r--r--zellij-server/src/background_jobs.rs61
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs234
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__run_command_plugin_command.snap20
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__run_command_with_env_vars_and_cwd_plugin_command.snap22
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs38
-rw-r--r--zellij-tile/src/shim.rs40
-rw-r--r--zellij-utils/assets/prost/api.event.rs30
-rw-r--r--zellij-utils/assets/prost/api.plugin_command.rs35
-rw-r--r--zellij-utils/src/data.rs13
-rw-r--r--zellij-utils/src/errors.rs1
-rw-r--r--zellij-utils/src/plugin_api/event.proto14
-rw-r--r--zellij-utils/src/plugin_api/event.rs34
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.proto19
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.rs50
16 files changed, 620 insertions, 9 deletions
diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs
index 811a4507..cdbdc9f2 100644
--- a/default-plugins/fixture-plugin-for-tests/src/main.rs
+++ b/default-plugins/fixture-plugin-for-tests/src/main.rs
@@ -240,6 +240,23 @@ impl ZellijPlugin for State {
Key::Ctrl('1') => {
request_permission(&[PermissionType::ReadApplicationState]);
},
+ Key::Ctrl('2') => {
+ let mut context = BTreeMap::new();
+ context.insert("user_key_1".to_owned(), "user_value_1".to_owned());
+ run_command(&["ls", "-l"], context);
+ },
+ Key::Ctrl('3') => {
+ let mut context = BTreeMap::new();
+ context.insert("user_key_2".to_owned(), "user_value_2".to_owned());
+ let mut env_vars = BTreeMap::new();
+ env_vars.insert("VAR1".to_owned(), "some_value".to_owned());
+ run_command_with_env_variables_and_cwd(
+ &["ls", "-l"],
+ env_vars,
+ std::path::PathBuf::from("/some/custom/folder"),
+ context,
+ );
+ },
_ => {},
},
Event::CustomMessage(message, payload) => {
diff --git a/default-plugins/session-manager/src/main.rs b/default-plugins/session-manager/src/main.rs
index b6598631..bd0b1f2b 100644
--- a/default-plugins/session-manager/src/main.rs
+++ b/default-plugins/session-manager/src/main.rs
@@ -28,6 +28,7 @@ impl ZellijPlugin for State {
EventType::ModeUpdate,
EventType::SessionUpdate,
EventType::Key,
+ EventType::RunCommandResult,
]);
}
diff --git a/zellij-server/src/background_jobs.rs b/zellij-server/src/background_jobs.rs
index 375ba4ba..57e13364 100644
--- a/zellij-server/src/background_jobs.rs
+++ b/zellij-server/src/background_jobs.rs
@@ -3,13 +3,14 @@ use zellij_utils::consts::{
session_info_cache_file_name, session_info_folder_for_session, session_layout_cache_file_name,
ZELLIJ_SOCK_DIR,
};
-use zellij_utils::data::SessionInfo;
+use zellij_utils::data::{Event, SessionInfo};
use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType};
use std::collections::{BTreeMap, HashMap};
use std::fs;
use std::io::Write;
use std::os::unix::fs::FileTypeExt;
+use std::path::PathBuf;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
@@ -17,8 +18,10 @@ use std::sync::{
use std::time::{Duration, Instant};
use crate::panes::PaneId;
+use crate::plugins::{PluginId, PluginInstruction};
use crate::screen::ScreenInstruction;
use crate::thread_bus::Bus;
+use crate::ClientId;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum BackgroundJob {
@@ -28,6 +31,15 @@ pub enum BackgroundJob {
ReadAllSessionInfosOnMachine, // u32 - plugin_id
ReportSessionInfo(String, SessionInfo), // String - session name
ReportLayoutInfo((String, BTreeMap<String, String>)), // HashMap<file_name, pane_contents>
+ RunCommand(
+ PluginId,
+ ClientId,
+ String,
+ Vec<String>,
+ BTreeMap<String, String>,
+ PathBuf,
+ BTreeMap<String, String>,
+ ), // command, args, env_variables, cwd, context
Exit,
}
@@ -44,6 +56,7 @@ impl From<&BackgroundJob> for BackgroundJobContext {
},
BackgroundJob::ReportSessionInfo(..) => BackgroundJobContext::ReportSessionInfo,
BackgroundJob::ReportLayoutInfo(..) => BackgroundJobContext::ReportLayoutInfo,
+ BackgroundJob::RunCommand(..) => BackgroundJobContext::RunCommand,
BackgroundJob::Exit => BackgroundJobContext::Exit,
}
}
@@ -226,6 +239,52 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
}
});
},
+ BackgroundJob::RunCommand(
+ plugin_id,
+ client_id,
+ command,
+ args,
+ env_variables,
+ cwd,
+ context,
+ ) => {
+ // when async_std::process stabilizes, we should change this to be async
+ std::thread::spawn({
+ let senders = bus.senders.clone();
+ move || {
+ let output = std::process::Command::new(&command)
+ .args(&args)
+ .envs(env_variables)
+ .current_dir(cwd)
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .output();
+ match output {
+ Ok(output) => {
+ let stdout = output.stdout.to_vec();
+ let stderr = output.stderr.to_vec();
+ let exit_code = output.status.code();
+ let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
+ Some(plugin_id),
+ Some(client_id),
+ Event::RunCommandResult(exit_code, stdout, stderr, context),
+ )]));
+ },
+ Err(e) => {
+ log::error!("Failed to run command: {}", e);
+ let stdout = vec![];
+ let stderr = format!("{}", e).as_bytes().to_vec();
+ let exit_code = Some(2);
+ let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
+ Some(plugin_id),
+ Some(client_id),
+ Event::RunCommandResult(exit_code, stdout, stderr, context),
+ )]));
+ },
+ }
+ }
+ });
+ },
BackgroundJob::Exit => {
for loading_plugin in loading_plugins.values() {
loading_plugin.store(false, Ordering::SeqCst);
diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs
index 652623cc..e866987d 100644
--- a/zellij-server/src/plugins/unit/plugin_tests.rs
+++ b/zellij-server/src/plugins/unit/plugin_tests.rs
@@ -473,6 +473,90 @@ fn create_plugin_thread_with_pty_receiver(
(to_plugin, pty_receiver, screen_receiver, Box::new(teardown))
}
+fn create_plugin_thread_with_background_jobs_receiver(
+ zellij_cwd: Option<PathBuf>,
+) -> (
+ SenderWithContext<PluginInstruction>,
+ Receiver<(BackgroundJob, ErrorContext)>,
+ Receiver<(ScreenInstruction, ErrorContext)>,
+ Box<dyn FnOnce()>,
+) {
+ let zellij_cwd = zellij_cwd.unwrap_or_else(|| PathBuf::from("."));
+ let (to_server, _server_receiver): ChannelWithContext<ServerInstruction> =
+ channels::bounded(50);
+ let to_server = SenderWithContext::new(to_server);
+
+ let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channels::unbounded();
+ let to_screen = SenderWithContext::new(to_screen);
+
+ let (to_plugin, plugin_receiver): ChannelWithContext<PluginInstruction> = channels::unbounded();
+ let to_plugin = SenderWithContext::new(to_plugin);
+ let (to_pty, _pty_receiver): ChannelWithContext<PtyInstruction> = channels::unbounded();
+ let to_pty = SenderWithContext::new(to_pty);
+
+ let (to_pty_writer, _pty_writer_receiver): ChannelWithContext<PtyWriteInstruction> =
+ channels::unbounded();
+ let to_pty_writer = SenderWithContext::new(to_pty_writer);
+
+ let (to_background_jobs, background_jobs_receiver): ChannelWithContext<BackgroundJob> =
+ channels::unbounded();
+ let to_background_jobs = SenderWithContext::new(to_background_jobs);
+
+ let plugin_bus = Bus::new(
+ vec![plugin_receiver],
+ Some(&to_screen),
+ Some(&to_pty),
+ Some(&to_plugin),
+ Some(&to_server),
+ Some(&to_pty_writer),
+ Some(&to_background_jobs),
+ None,
+ )
+ .should_silently_fail();
+ let store = Store::new(wasmer::Singlepass::default());
+ let data_dir = PathBuf::from(tempdir().unwrap().path());
+ let default_shell = PathBuf::from(".");
+ let plugin_capabilities = PluginCapabilities::default();
+ let client_attributes = ClientAttributes::default();
+ let default_shell_action = None; // TODO: change me
+ let plugin_thread = std::thread::Builder::new()
+ .name("plugin_thread".to_string())
+ .spawn(move || {
+ set_var("ZELLIJ_SESSION_NAME", "zellij-test");
+ plugin_thread_main(
+ plugin_bus,
+ store,
+ data_dir,
+ PluginsConfig::default(),
+ Box::new(Layout::default()),
+ default_shell,
+ zellij_cwd,
+ plugin_capabilities,
+ client_attributes,
+ default_shell_action,
+ )
+ .expect("TEST")
+ })
+ .unwrap();
+ let teardown = {
+ let to_plugin = to_plugin.clone();
+ move || {
+ let _ = to_pty.send(PtyInstruction::Exit);
+ let _ = to_pty_writer.send(PtyWriteInstruction::Exit);
+ let _ = to_screen.send(ScreenInstruction::Exit);
+ let _ = to_server.send(ServerInstruction::KillSession);
+ let _ = to_plugin.send(PluginInstruction::Exit);
+ let _ = plugin_thread.join();
+ }
+ };
+ (
+ to_plugin,
+ background_jobs_receiver,
+ screen_receiver,
+ Box::new(teardown),
+ )
+}
+
lazy_static! {
static ref PLUGIN_FIXTURE: String = format!(
// to populate this file, make sure to run the build-e2e CI job
@@ -5184,3 +5268,153 @@ pub fn denied_permission_request_result() {
assert_snapshot!(format!("{:#?}", permissions));
}
+
+#[test]
+#[ignore]
+pub fn run_command_plugin_command() {
+ let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
+ // destructor removes the directory
+ let plugin_host_folder = PathBuf::from(temp_folder.path());
+ let cache_path = plugin_host_folder.join("permissions_test.kdl");
+ let (plugin_thread_sender, background_jobs_receiver, screen_receiver, teardown) =
+ create_plugin_thread_with_background_jobs_receiver(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ configuration: Default::default(),
+ };
+ let tab_index = 1;
+ let client_id = 1;
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let received_background_jobs_instructions = Arc::new(Mutex::new(vec![]));
+ let background_jobs_thread = log_actions_in_thread!(
+ received_background_jobs_instructions,
+ BackgroundJob::RunCommand,
+ background_jobs_receiver,
+ 1
+ );
+ let received_screen_instructions = Arc::new(Mutex::new(vec![]));
+ let _screen_thread = grant_permissions_and_log_actions_in_thread_naked_variant!(
+ received_screen_instructions,
+ ScreenInstruction::Exit,
+ screen_receiver,
+ 1,
+ &PermissionType::ChangeApplicationState,
+ cache_path,
+ plugin_thread_sender,
+ client_id
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ false,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ None,
+ client_id,
+ size,
+ ));
+ std::thread::sleep(std::time::Duration::from_millis(500));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(Key::Ctrl('2')), // this triggers the enent in the fixture plugin
+ )]));
+ background_jobs_thread.join().unwrap(); // this might take a while if the cache is cold
+ teardown();
+ let new_tab_event = received_background_jobs_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let BackgroundJob::RunCommand(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", new_tab_event));
+}
+
+#[test]
+#[ignore]
+pub fn run_command_with_env_vars_and_cwd_plugin_command() {
+ let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
+ // destructor removes the directory
+ let plugin_host_folder = PathBuf::from(temp_folder.path());
+ let cache_path = plugin_host_folder.join("permissions_test.kdl");
+ let (plugin_thread_sender, background_jobs_receiver, screen_receiver, teardown) =
+ create_plugin_thread_with_background_jobs_receiver(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ configuration: Default::default(),
+ };
+ let tab_index = 1;
+ let client_id = 1;
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let received_background_jobs_instructions = Arc::new(Mutex::new(vec![]));
+ let background_jobs_thread = log_actions_in_thread!(
+ received_background_jobs_instructions,
+ BackgroundJob::RunCommand,
+ background_jobs_receiver,
+ 1
+ );
+ let received_screen_instructions = Arc::new(Mutex::new(vec![]));
+ let _screen_thread = grant_permissions_and_log_actions_in_thread_naked_variant!(
+ received_screen_instructions,
+ ScreenInstruction::Exit,
+ screen_receiver,
+ 1,
+ &PermissionType::ChangeApplicationState,
+ cache_path,
+ plugin_thread_sender,
+ client_id
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ false,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ None,
+ client_id,
+ size,
+ ));
+ std::thread::sleep(std::time::Duration::from_millis(500));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(Key::Ctrl('3')), // this triggers the enent in the fixture plugin
+ )]));
+ background_jobs_thread.join().unwrap(); // this might take a while if the cache is cold
+ teardown();
+ let new_tab_event = received_background_jobs_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let BackgroundJob::RunCommand(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", new_tab_event));
+}
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__run_command_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__run_command_plugin_command.snap
new file mode 100644
index 00000000..a98f8568
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__run_command_plugin_command.snap
@@ -0,0 +1,20 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 5339
+expression: "format!(\"{:#?}\", new_tab_event)"
+---
+Some(
+ RunCommand(
+ 0,
+ 1,
+ "ls",
+ [
+ "-l",
+ ],
+ {},
+ ".",
+ {
+ "user_key_1": "user_value_1",
+ },
+ ),
+)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__run_command_with_env_vars_and_cwd_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__run_command_with_env_vars_and_cwd_plugin_command.snap
new file mode 100644
index 00000000..5c5d0376
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__run_command_with_env_vars_and_cwd_plugin_command.snap
@@ -0,0 +1,22 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 5414
+expression: "format!(\"{:#?}\", new_tab_event)"
+---
+Some(
+ RunCommand(
+ 0,
+ 1,
+ "ls",
+ [
+ "-l",
+ ],
+ {
+ "VAR1": "some_value",
+ },
+ "/some/custom/folder",
+ {
+ "user_key_2": "user_value_2",
+ },
+ ),
+)
diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs
index 1608df8f..496c7d64 100644
--- a/zellij-server/src/plugins/zellij_exports.rs
+++ b/zellij-server/src/plugins/zellij_exports.rs
@@ -1,4 +1,5 @@
use super::PluginInstruction;
+use crate::background_jobs::BackgroundJob;
use crate::plugins::plugin_map::{PluginEnv, Subscriptions};
use crate::plugins::wasm_bridge::handle_plugin_crash;
use crate::route::route_action;
@@ -126,6 +127,9 @@ fn host_run_plugin_command(env: FunctionEnvMut<ForeignFunctionEnv>) {
PluginCommand::SwitchTabTo(tab_index) => switch_tab_to(env, tab_index),
PluginCommand::SetTimeout(seconds) => set_timeout(env, seconds),
PluginCommand::ExecCmd(command_line) => exec_cmd(env, command_line),
+ PluginCommand::RunCommand(command_line, env_variables, cwd, context) => {
+ run_command(env, command_line, env_variables, cwd, context)
+ },
PluginCommand::PostMessageTo(plugin_message) => {
post_message_to(env, plugin_message)?
},
@@ -572,6 +576,7 @@ fn set_timeout(env: &ForeignFunctionEnv, secs: f64) {
}
fn exec_cmd(env: &ForeignFunctionEnv, mut command_line: Vec<String>) {
+ log::warn!("The ExecCmd plugin command is deprecated and will be removed in a future version. Please use RunCmd instead (it has all the things and can even show you STDOUT/STDERR and an exit code!)");
let err_context = || {
format!(
"failed to execute command on host for plugin '{}'",
@@ -595,6 +600,38 @@ fn exec_cmd(env: &ForeignFunctionEnv, mut command_line: Vec<String>) {
.non_fatal();
}
+fn run_command(
+ env: &ForeignFunctionEnv,
+ mut command_line: Vec<String>,
+ env_variables: BTreeMap<String, String>,
+ cwd: PathBuf,
+ context: BTreeMap<String, String>,
+) {
+ let err_context = || {
+ format!(
+ "failed to execute command on host for plugin '{}'",
+ env.plugin_env.name()
+ )
+ };
+ if command_line.is_empty() {
+ log::error!("Command cannot be empty");
+ } else {
+ let command = command_line.remove(0);
+ let _ = env
+ .plugin_env
+ .senders
+ .send_to_background_jobs(BackgroundJob::RunCommand(
+ env.plugin_env.plugin_id,
+ env.plugin_env.client_id,
+ command,
+ command_line,
+ env_variables,
+ cwd,
+ context,
+ ));
+ }
+}
+
fn post_message_to(env: &ForeignFunctionEnv, plugin_message: PluginMessage) -> Result<()> {
let worker_name = plugin_message
.worker_name
@@ -1159,6 +1196,7 @@ fn check_command_permission(
PluginCommand::OpenCommandPane(..)
| PluginCommand::OpenCommandPaneFloating(..)
| PluginCommand::OpenCommandPaneInPlace(..)
+ | PluginCommand::RunCommand(..)
| PluginCommand::ExecCmd(..) => PermissionType::RunCommands,
PluginCommand::Write(..) | PluginCommand::WriteChars(..) => PermissionType::WriteToStdin,
PluginCommand::SwitchTabTo(..)
diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs
index 09bb7f59..97065a2a 100644
--- a/zellij-tile/src/shim.rs
+++ b/zellij-tile/src/shim.rs
@@ -1,6 +1,9 @@
use serde::{de::DeserializeOwned, Serialize};
-use std::collections::HashSet;
-use std::{io, path::Path};
+use std::collections::{BTreeMap, HashSet};
+use std::{
+ io,
+ path::{Path, PathBuf},
+};
use zellij_utils::data::*;
use zellij_utils::errors::prelude::*;
pub use zellij_utils::plugin_api;
@@ -171,6 +174,39 @@ pub fn exec_cmd(cmd: &[&str]) {
unsafe { host_run_plugin_command() };
}
+/// Run this command in the background on the host machine, optionally being notified of its output
+/// if subscribed to the `RunCommandResult` Event
+pub fn run_command(cmd: &[&str], context: BTreeMap<String, String>) {
+ let plugin_command = PluginCommand::RunCommand(
+ cmd.iter().cloned().map(|s| s.to_owned()).collect(),
+ BTreeMap::new(),
+ PathBuf::from("."),
+ context,
+ );
+ let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
+ object_to_stdout(&protobuf_plugin_command.encode_to_vec());
+ unsafe { host_run_plugin_command() };
+}
+
+/// Run this command in the background on the host machine, providing environment variables and a
+/// cwd. Optionally being notified of its output if subscribed to the `RunCommandResult` Event
+pub fn run_command_with_env_variables_and_cwd(
+ cmd: &[&str],
+ env_variables: BTreeMap<String, String>,
+ cwd: PathBuf,
+ context: BTreeMap<String, String>,
+) {
+ let plugin_command = PluginCommand::RunCommand(
+ cmd.iter().cloned().map(|s| s.to_owned()).collect(),
+ env_variables,
+ cwd,
+ context,
+ );
+ let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
+ object_to_stdout(&protobuf_plugin_command.encode_to_vec());
+ unsafe { host_run_plugin_command() };
+}
+
/// Hide the plugin pane (suppress it) from the UI
pub fn hide_self() {
let plugin_command = PluginCommand::HideSelf;
diff --git a/zellij-utils/assets/prost/api.event.rs b/zellij-utils/assets/prost/api.event.rs
index 39bc9770..9b0097a5 100644
--- a/zellij-utils/assets/prost/api.event.rs
+++ b/zellij-utils/assets/prost/api.event.rs
@@ -9,7 +9,10 @@ pub struct EventNameList {
pub struct Event {
#[prost(enumeration = "EventType", tag = "1")]
pub name: i32,
- #[prost(oneof = "event::Payload", tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13")]
+ #[prost(
+ oneof = "event::Payload",
+ tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14"
+ )]
pub payload: ::core::option::Option<event::Payload>,
}
/// Nested message and enum types in `Event`.
@@ -41,6 +44,8 @@ pub mod event {
PermissionRequestResultPayload(super::PermissionRequestResultPayload),
#[prost(message, tag = "13")]
SessionUpdatePayload(super::SessionUpdatePayload),
+ #[prost(message, tag = "14")]
+ RunCommandResultPayload(super::RunCommandResultPayload),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
@@ -51,6 +56,26 @@ pub struct SessionUpdatePayload {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct RunCommandResultPayload {
+ #[prost(int32, optional, tag = "1")]
+ pub exit_code: ::core::option::Option<i32>,
+ #[prost(bytes = "vec", tag = "2")]
+ pub stdout: ::prost::alloc::vec::Vec<u8>,
+ #[prost(bytes = "vec", tag = "3")]
+ pub stderr: ::prost::alloc::vec::Vec<u8>,
+ #[prost(message, repeated, tag = "4")]
+ pub context: ::prost::alloc::vec::Vec<ContextItem>,
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct ContextItem {
+ #[prost(string, tag = "1")]
+ pub name: ::prost::alloc::string::String,
+ #[prost(string, tag = "2")]
+ pub value: ::prost::alloc::string::String,
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PermissionRequestResultPayload {
#[prost(bool, tag = "1")]
pub granted: bool,
@@ -261,6 +286,7 @@ pub enum EventType {
FileSystemDelete = 14,
PermissionRequestResult = 15,
SessionUpdate = 16,
+ RunCommandResult = 17,
}
impl EventType {
/// String value of the enum field names used in the ProtoBuf definition.
@@ -286,6 +312,7 @@ impl EventType {
EventType::FileSystemDelete => "FileSystemDelete",
EventType::PermissionRequestResult => "PermissionRequestResult",
EventType::SessionUpdate => "SessionUpdate",
+ EventType::RunCommandResult => "RunCommandResult",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@@ -308,6 +335,7 @@ impl EventType {
"FileSystemDelete" => Some(Self::FileSystemDelete),
"PermissionRequestResult" => Some(Self::PermissionRequestResult),
"SessionUpdate" => Some(Self::SessionUpdate),
+ "RunCommandResult" => Some(Self::RunCommandResult),
_ => None,
}
}
diff --git a/zellij-utils/assets/prost/api.plugin_command.rs b/zellij-utils/assets/prost/api.plugin_command.rs
index b86f9549..d8cd13c1 100644
--- a/zellij-utils/assets/prost/api.plugin_command.rs
+++ b/zellij-utils/assets/prost/api.plugin_command.rs
@@ -5,7 +5,7 @@ pub struct PluginCommand {
pub name: i32,
#[prost(
oneof = "plugin_command::Payload",
- tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42"
+ tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43"
)]
pub payload: ::core::option::Option<plugin_command::Payload>,
}
@@ -96,6 +96,8 @@ pub mod plugin_command {
OpenTerminalInPlacePayload(super::OpenFilePayload),
#[prost(message, tag = "42")]
OpenCommandPaneInPlacePayload(super::OpenCommandPanePayload),
+ #[prost(message, tag = "43")]
+ RunCommandPayload(super::RunCommandPayload),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
@@ -164,6 +166,34 @@ pub struct ExecCmdPayload {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct RunCommandPayload {
+ #[prost(string, repeated, tag = "1")]
+ pub command_line: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
+ #[prost(message, repeated, tag = "2")]
+ pub env_variables: ::prost::alloc::vec::Vec<EnvVariable>,
+ #[prost(string, tag = "3")]
+ pub cwd: ::prost::alloc::string::String,
+ #[prost(message, repeated, tag = "4")]
+ pub context: ::prost::alloc::vec::Vec<ContextItem>,
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct EnvVariable {
+ #[prost(string, tag = "1")]
+ pub name: ::prost::alloc::string::String,
+ #[prost(string, tag = "2")]
+ pub value: ::prost::alloc::string::String,
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct ContextItem {
+ #[prost(string, tag = "1")]
+ pub name: ::prost::alloc::string::String,
+ #[prost(string, tag = "2")]
+ pub value: ::prost::alloc::string::String,
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PluginMessagePayload {
#[prost(message, optional, tag = "1")]
pub message: ::core::option::Option<super::message::Message>,
@@ -263,6 +293,7 @@ pub enum CommandName {
OpenTerminalInPlace = 68,
OpenCommandInPlace = 69,
OpenFileInPlace = 70,
+ RunCommand = 71,
}
impl CommandName {
/// String value of the enum field names used in the ProtoBuf definition.
@@ -342,6 +373,7 @@ impl CommandName {
CommandName::OpenTerminalInPlace => "OpenTerminalInPlace",
CommandName::OpenCommandInPlace => "OpenCommandInPlace",
CommandName::OpenFileInPlace => "OpenFileInPlace",
+ CommandName::RunCommand => "RunCommand",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@@ -418,6 +450,7 @@ impl CommandName {
"OpenTerminalInPlace" => Some(Self::OpenTerminalInPlace),
"OpenCommandInPlace" => Some(Self::OpenCommandInPlace),
"OpenFileInPlace" => Some(Self::OpenFileInPlace),
+ "RunCommand" => Some(Self::RunCommand),
_ => None,
}
}
diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs
index 3702dace..8ff43836 100644
--- a/zellij-utils/src/data.rs
+++ b/zellij-utils/src/data.rs
@@ -2,7 +2,7 @@ use crate::input::actions::Action;
use crate::input::config::ConversionError;
use clap::ArgEnum;
use serde::{Deserialize, Serialize};
-use std::collections::{HashMap, HashSet};
+use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::path::{Path, PathBuf};
use std::str::FromStr;
@@ -496,6 +496,8 @@ pub enum Event {
/// A Result of plugin permission request
PermissionRequestResult(PermissionStatus),
SessionUpdate(Vec<SessionInfo>),
+ RunCommandResult(Option<i32>, Vec<u8>, Vec<u8>, BTreeMap<String, String>), // exit_code, STDOUT, STDERR,
+ // context
}
#[derive(
@@ -1064,4 +1066,13 @@ pub enum PluginCommand {
OpenTerminalInPlace(FileToOpen), // only used for the path as cwd
OpenFileInPlace(FileToOpen),
OpenCommandPaneInPlace(CommandToRun),
+ RunCommand(
+ Vec<String>,
+ BTreeMap<String, String>,
+ PathBuf,
+ BTreeMap<String, String>,
+ ), // command,
+ // env_Variables,
+ // cwd,
+ // context
}
diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs
index 03fa6c81..cee98391 100644
--- a/zellij-utils/src/errors.rs
+++ b/zellij-utils/src/errors.rs
@@ -445,6 +445,7 @@ pub enum BackgroundJobContext {
ReadAllSessionInfosOnMachine,
ReportSessionInfo,
ReportLayoutInfo,
+ RunCommand,
Exit,
}
diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto
index 4d5b805d..1463d8f0 100644
--- a/zellij-utils/src/plugin_api/event.proto
+++ b/zellij-utils/src/plugin_api/event.proto
@@ -40,6 +40,7 @@ enum EventType {
FileSystemDelete = 14;
PermissionRequestResult = 15;
SessionUpdate = 16;
+ RunCommandResult = 17;
}
message EventNameList {
@@ -61,6 +62,7 @@ message Event {
FileListPayload file_list_payload = 11;
PermissionRequestResultPayload permission_request_result_payload = 12;
SessionUpdatePayload session_update_payload = 13;
+ RunCommandResultPayload run_command_result_payload = 14;
}
}
@@ -68,6 +70,18 @@ message SessionUpdatePayload {
repeated SessionManifest session_manifests = 1;
}
+message RunCommandResultPayload {
+ optional int32 exit_code = 1;
+ bytes stdout = 2;
+ bytes stderr = 3;
+ repeated ContextItem context = 4;
+}
+
+message ContextItem {
+ string name = 1;
+ string value = 2;
+}
+
message PermissionRequestResultPayload {
bool granted = 1;
}
diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs
index 9b0e9886..6547f383 100644
--- a/zellij-utils/src/plugin_api/event.rs
+++ b/zellij-utils/src/plugin_api/event.rs
@@ -183,6 +183,21 @@ impl TryFrom<ProtobufEvent> for Event {
},
_ => Err("Malformed payload for the SessionUpdate Event"),
},
+ Some(ProtobufEventType::RunCommandResult) => match protobuf_event.payload {
+ Some(ProtobufEventPayload::RunCommandResultPayload(run_command_result_payload)) => {
+ Ok(Event::RunCommandResult(
+ run_command_result_payload.exit_code,
+ run_command_result_payload.stdout,
+ run_command_result_payload.stderr,
+ run_command_result_payload
+ .context
+ .into_iter()
+ .map(|c_i| (c_i.name, c_i.value))
+ .collect(),
+ ))
+ },
+ _ => Err("Malformed payload for the RunCommandResult Event"),
+ },
None => Err("Unknown Protobuf Event"),
}
}
@@ -338,6 +353,23 @@ impl TryFrom<Event> for ProtobufEvent {
payload: Some(event::Payload::SessionUpdatePayload(session_update_payload)),
})
},
+ Event::RunCommandResult(exit_code, stdout, stderr, context) => {
+ let run_command_result_payload = RunCommandResultPayload {
+ exit_code,
+ stdout,
+ stderr,
+ context: context
+ .into_iter()
+ .map(|(name, value)| ContextItem { name, value })
+ .collect(),
+ };
+ Ok(ProtobufEvent {
+ name: ProtobufEventType::RunCommandResult as i32,
+ payload: Some(event::Payload::RunCommandResultPayload(
+ run_command_result_payload,
+ )),
+ })
+ },
}
}
}
@@ -783,6 +815,7 @@ impl TryFrom<ProtobufEventType> for EventType {
ProtobufEventType::FileSystemDelete => EventType::FileSystemDelete,
ProtobufEventType::PermissionRequestResult => EventType::PermissionRequestResult,
ProtobufEventType::SessionUpdate => EventType::SessionUpdate,
+ ProtobufEventType::RunCommandResult => EventType::RunCommandResult,
})
}
}
@@ -808,6 +841,7 @@ impl TryFrom<EventType> for ProtobufEventType {
EventType::FileSystemDelete => ProtobufEventType::FileSystemDelete,
EventType::PermissionRequestResult => ProtobufEventType::PermissionRequestResult,
EventType::SessionUpdate => ProtobufEventType::SessionUpdate,
+ EventType::RunCommandResult => ProtobufEventType::RunCommandResult,
})
}
}
diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto
index 17b15381..e0b26184 100644
--- a/zellij-utils/src/plugin_api/plugin_command.proto
+++ b/zellij-utils/src/plugin_api/plugin_command.proto
@@ -82,6 +82,7 @@ enum CommandName {
OpenTerminalInPlace = 68;
OpenCommandInPlace = 69;
OpenFileInPlace = 70;
+ RunCommand = 71;
}
message PluginCommand {
@@ -128,6 +129,7 @@ message PluginCommand {
OpenFilePayload open_file_in_place_payload = 40;
OpenFilePayload open_terminal_in_place_payload = 41;
OpenCommandPanePayload open_command_pane_in_place_payload = 42;
+ RunCommandPayload run_command_payload = 43;
}
}
@@ -170,6 +172,23 @@ message ExecCmdPayload {
repeated string command_line = 1;
}
+message RunCommandPayload {
+ repeated string command_line = 1;
+ repeated EnvVariable env_variables = 2;
+ string cwd = 3;
+ repeated ContextItem context = 4;
+}
+
+message EnvVariable {
+ string name = 1;
+ string value = 2;
+}
+
+message ContextItem {
+ string name = 1;
+ string value = 2;
+}
+
message PluginMessagePayload {
api.message.Message message = 1;
}
diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs
index 3d34f10f..181953d4 100644
--- a/zellij-utils/src/plugin_api/plugin_command.rs
+++ b/zellij-utils/src/plugin_api/plugin_command.rs
@@ -3,9 +3,10 @@ pub use super::generated_api::api::{
event::EventNameList as ProtobufEventNameList,
input_mode::InputMode as ProtobufInputMode,
plugin_command::{
- plugin_command::Payload, CommandName, ExecCmdPayload, IdAndNewName, MovePayload,
- OpenCommandPanePayload, OpenFilePayload, PluginCommand as ProtobufPluginCommand,
- PluginMessagePayload, RequestPluginPermissionPayload, ResizePayload, SetTimeoutPayload,
+ plugin_command::Payload, CommandName, ContextItem, EnvVariable, ExecCmdPayload,
+ IdAndNewName, MovePayload, OpenCommandPanePayload, OpenFilePayload,
+ PluginCommand as ProtobufPluginCommand, PluginMessagePayload,
+ RequestPluginPermissionPayload, ResizePayload, RunCommandPayload, SetTimeoutPayload,
SubscribePayload, SwitchSessionPayload, SwitchTabToPayload, UnsubscribePayload,
},
plugin_permission::PermissionType as ProtobufPermissionType,
@@ -14,7 +15,9 @@ pub use super::generated_api::api::{
use crate::data::{ConnectToSession, PermissionType, PluginCommand};
+use std::collections::BTreeMap;
use std::convert::TryFrom;
+use std::path::PathBuf;
impl TryFrom<ProtobufPluginCommand> for PluginCommand {
type Error = &'static str;
@@ -553,6 +556,27 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
},
_ => Err("Mismatched payload for OpenCommandPaneInPlace"),
},
+ Some(CommandName::RunCommand) => match protobuf_plugin_command.payload {
+ Some(Payload::RunCommandPayload(run_command_payload)) => {
+ let env_variables: BTreeMap<String, String> = run_command_payload
+ .env_variables
+ .into_iter()
+ .map(|e| (e.name, e.value))
+ .collect();
+ let context: BTreeMap<String, String> = run_command_payload
+ .context
+ .into_iter()
+ .map(|e| (e.name, e.value))
+ .collect();
+ Ok(PluginCommand::RunCommand(
+ run_command_payload.command_line,
+ env_variables,
+ PathBuf::from(run_command_payload.cwd),
+ context,
+ ))
+ },
+ _ => Err("Mismatched payload for RunCommand"),
+ },
None => Err("Unrecognized plugin command"),
}
}
@@ -928,6 +952,26 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
},
)),
}),
+ PluginCommand::RunCommand(command_line, env_variables, cwd, context) => {
+ let env_variables: Vec<_> = env_variables
+ .into_iter()
+ .map(|(name, value)| EnvVariable { name, value })
+ .collect();
+ let context: Vec<_> = context
+ .into_iter()
+ .map(|(name, value)| ContextItem { name, value })
+ .collect();
+ let cwd = cwd.display().to_string();
+ Ok(ProtobufPluginCommand {
+ name: CommandName::RunCommand as i32,
+ payload: Some(Payload::RunCommandPayload(RunCommandPayload {
+ command_line,
+ env_variables,
+ cwd,
+ context,
+ })),
+ })
+ },
}
}
}