use super::PluginInstruction;
use crate::plugins::plugin_map::{PluginEnv, Subscriptions};
use crate::plugins::wasm_bridge::handle_plugin_crash;
use crate::route::route_action;
use crate::ServerInstruction;
use log::{debug, warn};
use serde::Serialize;
use std::{
collections::{BTreeMap, HashSet},
path::PathBuf,
process,
str::FromStr,
sync::{Arc, Mutex},
thread,
time::{Duration, Instant},
};
use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
use wasmer_wasi::WasiEnv;
use zellij_utils::data::{
CommandType, ConnectToSession, PermissionStatus, PermissionType, PluginPermission,
};
use zellij_utils::input::permission::PermissionCache;
use url::Url;
use crate::{panes::PaneId, screen::ScreenInstruction};
use zellij_utils::{
consts::VERSION,
data::{
CommandToRun, Direction, Event, EventType, FileToOpen, InputMode, PluginCommand, PluginIds,
PluginMessage, Resize, ResizeStrategy,
},
errors::prelude::*,
input::{
actions::Action,
command::{RunCommand, RunCommandAction, TerminalAction},
layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation},
plugins::PluginType,
},
plugin_api::{
plugin_command::ProtobufPluginCommand,
plugin_ids::{ProtobufPluginIds, ProtobufZellijVersion},
},
prost::Message,
serde,
};
macro_rules! apply_action {
($action:ident, $error_message:ident, $env: ident) => {
if let Err(e) = route_action(
$action,
$env.plugin_env.client_id,
$env.plugin_env.senders.clone(),
$env.plugin_env.capabilities.clone(),
$env.plugin_env.client_attributes.clone(),
$env.plugin_env.default_shell.clone(),
$env.plugin_env.default_layout.clone(),
) {
log::error!("{}: {:?}", $error_message(), e);
}
};
}
pub fn zellij_exports(
store: &Store,
plugin_env: &PluginEnv,
subscriptions: &Arc<Mutex<Subscriptions>>,
) -> ImportObject {
imports! {
"zellij" => {
"host_run_plugin_command" => {
Function::new_native_with_env(store, ForeignFunctionEnv::new(plugin_env, subscriptions), host_run_plugin_command)
}
}
}
}
#[derive(WasmerEnv, Clone)]
pub struct ForeignFunctionEnv {
pub plugin_env: PluginEnv,
pub subscriptions: Arc<Mutex<Subscriptions>>,
}
impl ForeignFunctionEnv {
pub fn new(plugin_env: &PluginEnv, subscriptions: &Arc<Mutex<Subscriptions>>) -> Self {
ForeignFunctionEnv {
plugin_env: plugin_env.clone(),
subscriptions: subscriptions.clone(),
}
}
}
fn host_run_plugin_command(env: &ForeignFunctionEnv) {
let err_context = || format!("failed to run plugin command {}", env.plugin_env.name());
wasi_read_bytes(&env.plugin_env.wasi_env)
.and_then(|bytes| {
let command: ProtobufPluginCommand = ProtobufPluginCommand::decode(bytes.as_slice())?;
let command: PluginCommand = command
.try_into()
.map_err(|e| anyhow!("failed to convert serialized command: {}", e))?;
match check_command_permission(&env.plugin_env, &command) {
(PermissionStatus::Granted, _) => match command {
PluginCommand::Subscribe(event_list) => subscribe(env, event_list)?,
PluginCommand::Unsubscribe(event_list) => unsubscribe(env, event_list)?,
PluginCommand::SetSelectable(selectable) => set_selectable(env, selectable),
PluginCommand::GetPluginIds => get_plugin_ids(env),
PluginCommand::GetZellijVersion => get_zellij_version(env),
PluginCommand::OpenFile(file_to_open) => open_file(env, file_to_open),
PluginCommand::OpenFileFloating(file_to_open) => {
open_file_floating(env, file_to_open)
},
PluginCommand::OpenTerminal(cwd) => open_terminal(env, cwd.path.try_into()?),
PluginCommand::OpenTerminalFloating(cwd) => {
open_terminal_floating(env, cwd.path.try_into()?)
},
PluginCommand::OpenCommandPane(command_to_run) => {
open_command_pane(env, command_to_run)
},
PluginCommand::OpenCommandPaneFloating(command_to_run) => {
open_command_pane_floating(env, command_to_run)
},
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::PostMessageTo(plugin_message) => {
post_message_to(env, plugin_message)?
},
PluginCommand::PostMessageToPlugin(plugin_message) => {
post_message_to_plugin(env, plugin_message)?
},
PluginCommand::HideSelf => hide_self(env)?,
PluginCommand::ShowSelf(should_float_if_hidden) => {
show_self(env, should_float_if_hidden)
},
PluginCommand::SwitchToMode(input_mode) => {
switch_to_mode(env, input_mode.try_into()?)
},
PluginCommand::NewTabsWithLayout(raw_layout) => {
new_tabs_with_layout(env, &raw_layout)?
},
PluginCommand::NewTab => new_tab(env),
PluginCommand::GoToNextTab => go_to_next_tab(env),
PluginCommand::GoToPreviousTab => go_to_previous_tab(env),
PluginCommand::Resize(resize_payload) => resize(env, resize_payload),
PluginCommand::ResizeWithDirection(resize_strategy) => {
resize_with_direction(env, resize_strategy)
},
PluginCommand::FocusNextPane => focus_next_pane(env),
PluginCommand::FocusPreviousPane => focus_previous_pane(env),
PluginCommand::MoveFocus(direction) => move_focus(env, direction),
PluginCommand::MoveFocusOrTab(direction) => move_focus_or_tab(env, direction),
PluginCommand::Detach => detach(env),
PluginCommand::EditScrollback => edit_scrollback(env),
PluginCommand::Write(bytes) => write(env, bytes),
PluginCommand::WriteChars(chars) => write_chars(env, chars),
PluginCommand::ToggleTab => toggle_tab(env),
PluginCommand::MovePane => move_pane(env),
PluginCommand::MovePaneWithDirection(direction) => {
move_pane_with_direction(env, direction)
},
PluginCommand::ClearScreen => clear_screen(env),
PluginCommand::ScrollUp => scroll_up(env),
PluginCommand::ScrollDown => scroll_down(env),
PluginCommand::ScrollToTop => scroll_to_top(env),
PluginCommand::ScrollToBottom => scroll_to_bottom(env),
PluginCommand::PageScrollUp => page_scroll_up(env),
PluginCommand::PageScrollDown => page_scroll_down(env),
PluginCommand::ToggleFocusFullscreen => toggle_focus_fullscreen(env),
PluginCommand::TogglePaneFrames => toggle_pane_frames(env),
PluginCommand::TogglePaneEmbedOrEject => toggle_pane_embed_or_eject(env),
PluginCommand::UndoRenamePane => undo_rename_pane(env),
PluginCommand::CloseFocus => close_focus(env),
PluginCommand::ToggleActiveTabSync => toggle_active_tab_sync(env),
PluginCommand::CloseFocusedTab => close_focused_tab(env),
PluginCommand::UndoRenameTab => undo_rename_tab(env),
PluginCommand::QuitZellij => quit_zellij(env),
PluginCommand::PreviousSwapLayout => previous_swap_layout(env),
PluginCommand::NextSwapLayout => next_swap_layout(env),
PluginCommand::GoToTabName(tab_name) => go_to_tab_name(env, tab_name),
PluginCommand::FocusOrCreateTab(tab_name) => focus_or_create_tab(env, tab_name),
PluginCommand::GoToTab(tab_index) => go_to_tab(env, tab_index),
PluginCommand::StartOrReloadPlugin(plugin_url) => {
start_or_reload_plugin(env, &plugin_url)?
},
PluginCommand::CloseTerminalPane(terminal_pane_id) => {
close_terminal_pane(env, terminal_pane_id)
},
PluginCommand::ClosePluginPane(plugin_pane_id) => {
close_plugin_pane(env, plugin_pane_id)
},
PluginCommand::FocusTerminalPane(terminal_pane_id, should_float_if_hidden) => {
focus_terminal_pane(env, terminal_pane_id, should_float_if_hidden)
},
PluginCommand::FocusPluginPane(plugin_pane_id, should_float_if_hidden) => {
focus_plugin_pane(env, plugin_pane_id, should_float_if_hidden)
},
PluginCommand::RenameTerminalPane(terminal_pane_id, new_name) => {
rename_terminal_pane(env, terminal_pane_id, &new_name)
},
PluginCommand::RenamePluginPane(plugin_pane_id, new_name) => {
rename_plugin_pane(env, plugin_pane_id, &new_name)
},
PluginCommand::RenameTab(tab_index, new_name) => {
rename_tab(env, tab_index, &new_name)
},
PluginCommand::ReportPanic(crash_payload) => report_panic(env, &crash_payload),
PluginCommand::RequestPluginPermissions(permissions) => {
request_permission(env, permissions)?
},
PluginCommand::SwitchSession(connect_to_session) => switch_session(
env,
connect_to_session.name,
connect_to_session.tab_position,
connect_to_session.pane_id,
)?,
},
(PermissionStatus::Denied, permission) => {
log::error!(
"Plugin '{}' permission '{}' denied - Command '{:?}' denied",
env.plugin_env.name(),
permission
.map(|p| p.to_string())
.unwrap_or("UNKNOWN".to_owned()),
CommandType::from_str(&command.to_string()).with_context(err_context)?
);
},
};
Ok(())
})
.with_context(|| format!("failed to run plugin command {}", env.plugin_env.name()))
.non_fatal();
}
fn subscribe(env: &ForeignFunctionEnv, event_list: HashSet<EventType>) -> Result<()> {
env.subscriptions
.lock()
.to_anyhow()?
.extend(event_list.clone());
env.plugin_env
.senders
.send_to_plugin(PluginInstruction::PluginSubscribedToEvents(
env.plugin_env.plugin_id,
env.plugin_env.client_id,
event_list,
))
}
fn unsubscribe(env: &ForeignFunctionEnv, event_list: HashSet<EventType>) -> Result<()> {
env.subscriptions
.lock()
.to_anyhow()?
.retain(|k| !event_list.contains(k));
Ok(())
}
fn set_selectable(env: &ForeignFunctionEnv, selectable: bool) {
match env.plugin_env.plugin.run {
PluginType::Pane(Some(tab_index)) => {
env.plugin_env
.senders
.send_to_screen(ScreenInstruction::SetSelectable(
PaneId::Plugin(env.plugin_env.plugin_id),
selectable,
tab_index,
))
.with_context(|| {
format!(
"failed to set plugin {} selectable from plugin {}",
selectable,
env.plugin_env.name()
)
})
.non_fatal();
},
_ => {
debug!(
"{} - Calling method 'set_selectable' does nothing for headless plugins",
env.plugin_env.plugin.location
)
},
}
}
fn request_permission(env: &ForeignFunctionEnv, permissions: Vec<PermissionType>) -> Result<()> {
if PermissionCache::from_path_or_default(None)
.check_permissions(env.plugin_env.plugin.location.to_string(), &permissions)
{
return env
.plugin_env
.senders
.send_to_plugin(PluginInstruction::PermissionRequestResult(
env.plugin_env.plugin_id,
Some(env.plugin_env.client_id),
permissions.to_vec(),
PermissionStatus::Granted,
None,
));
}
env.plugin_env
.senders
.send_to_screen(ScreenInstruction::RequestPluginPermissions(
env.plugin_env.plugin_id,
PluginPermission::new(env.plugin_env.plugin.location.to_string(), permissions),
))
}
fn get_plugin_ids(env: &ForeignFunctionEnv) {
let ids = PluginIds {
plugin_id: env.plugin_env.plugin_id,
zellij_pid: process::id(),
};
ProtobufPluginIds::try_from(ids)
.map_err(|e| anyhow!("Failed to serialized plugin ids: {}", e))
.and_then(|serialized| {
wasi_write_object(&env.plugin_env.wasi_env, &serialized.encode_to_vec())?;
Ok(())
})
.with_context(|| {
format!(
"failed to query plugin IDs from host for plugin {}",
env.plugin_env.name()
)
})
.non_fatal();
}
fn get_zellij_version(env: &ForeignFunctionEnv) {
let protobuf_zellij_version = ProtobufZellijVersion {
version: VERSION.to_owned(),
};
wasi_write_object(
&env.plugin_env.wasi_env,
&protobuf_zellij_version.encode_to_vec(),
)
.with_context(|| {
format!(
"failed to request zellij version from host for plugin {}",
env.plugin_env.name()
)
})
.non_fatal();
}
fn open_file(env: &ForeignFunctionEnv, file_to_open: FileToOpen) {
let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name());
let floating = false;
let action = Action::EditFile(
file_to_open.path,
file_to_open.line_number,
file_to_open.cwd,
None,
floating,
);
apply_action!(action, error_msg, env);
}
fn open_file_floating(env: &ForeignFunctionEnv, file_to_open: FileToOpen) {
let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name());
let floating = true;
let action = Action::EditFile(
file_to_open.path,
file_to_open.line_number,
file_to_open.cwd,
None,
floating,
);
apply_action!(action, error_msg, env);
}
fn open_terminal(env: &ForeignFunctionEnv, cwd: PathBuf) {
let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name());
let mut default_shell = env
.plugin_env
.default_shell
.clone()
.unwrap_or_else(|| TerminalAction::RunCommand(RunCommand::default()));
default_shell.change_cwd(cwd);
let run_command_action: Option<RunCommandAction> = match default_shell {
TerminalAction::RunCommand(run_command) => Some(run_command.into()),
_ => None,
};
let action = Action::NewTiledPane(None, run_command_action, None);
apply_action!(action, error_msg, env);
}
fn open_terminal_floating(env: &ForeignFunctionEnv, cwd: PathBuf) {
let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name());
let mut default_shell = env
.plugin_env
.default_shell
.clone()
.unwrap_or_else(|| TerminalAction::RunCommand(RunCommand::default()));
default_shell.change_cwd(cwd);
let run_command_action: Option<RunCommandAction> = match default_shell {
TerminalAction::RunCommand(run_command) => Some(run_command.into()),
_ => None,
};
let action = Action::NewFloatingPane(run_command_action, None);
apply_action!(action, error_msg, env);
}
fn open_command_pane(env: &ForeignFunctionEnv, command_to_run: CommandToRun) {
let error_msg = || format!("failed to open command in plugin {}", env.plugin_env.name());
let command = command_to_run.path;
let cwd = command_to_run.cwd;
let args = command_to_run.args;
let direction = None;
let hold_on_close = true;
let hold_on_start = false;
let name = None;
let run_command_action = RunCommandAction {
command,
args,
cwd,
direction,
hold_on_close,
hold_on_start,
};
let action = Action::NewTiledPane(direction, Some(run_command_action), name);
apply_action!(action, error_msg, env);
}
fn open_command_pane_floating(env: &ForeignFunctionEnv, command_to_run: CommandToRun) {
let error_msg = || format!("failed to open command in plugin {}", env.plugin_env.name());
let command = command_to_run.path;
let cwd = command_to_run.cwd;
let args = command_to_run.args;
let direction = None;
let hold_on_close = true;
let hold_on_start = false;
let name = None;
let run_command_action = RunCommandAction {
command,
args,
cwd,
direction,
hold_on_close,
hold_on_start,
};
let action = Action::NewFloatingPane(Some(run_command_action), name);
apply_action!(action, error_msg, env);
}
fn switch_tab_to(env: &ForeignFunctionEnv, tab_idx: u32) {
env.plugin_env
.senders
.send_to_screen(ScreenInstruction::GoToTab(
tab_idx,
Some(env.plugin_env.client_id),
))
.with_context(|| {
format!(
"failed to switch to tab {tab_idx} from plugin {}",
env.plugin_env.name()
)
})
.non_fatal();
}
fn set_timeout(env: &ForeignFunctionEnv, secs: f64) {
let send_plugin_instructions = env.plugin_env.senders.to_plugin.clone();
let update_target = Some(env.plugin_env.plugin_id);
let client_id = env.plugin_env.client_id;
let plugin_name = env.plugin_env.name();
thread::spawn(move || {
let start_time = Instant::now();
thread::sleep(Duration::from_secs_f64(secs));
let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64();
send_plugin_instructions
.ok_or(anyhow!("found no sender to send plugin instruction to"))
.and_then(|sender| {
sender
.send(PluginInstruction::Update(vec![(
update_target,
Some(client_id),
Event::Timer(elapsed_time),
)]))
.to_anyhow()
})
.with_context(|| {
format!(
"failed to set host timeout of {secs} s for plugin {}",
plugin_name
)
})
.non_fatal();
});
}
fn exec_cmd(env: &ForeignFunctionEnv, mut command_line: Vec<String>) {
let err_context = || {
format!(
"failed to execute command on host for plugin '{}'",
env.plugin_env.name()
)
};
let command = command_line.remove(0);
if !env.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 = command_line.join(" "));
return;
}
process::Command::new(command)
.args(command_line)
.spawn()
.with_context(err_context)
.non_fatal();
}
fn post_message_to(env: &ForeignFunctionEnv, plugin_message: PluginMessage) -> Result<()> {
let worker_name = plugin_message
.worker_name
.ok_or(anyhow!("Worker name not specified in message to worker"))?;
env.plugin_env
.senders
.send_to_plugin(PluginInstruction::PostMessagesToPluginWorker(
env.plugin_env.plugin_id,
env.plugin_env.client_id,
worker_name,
vec![(plugin_message.name, plugin_message.payload)],
))
}
fn post_message_to_plugin(env: &ForeignFunctionEnv, plugin_message: PluginMessage) -> Result<()> {
if let Some(worker_name) = plugin_message.worker_name {
return Err(anyhow!(
"Worker name (\"{}\") should not be specified in message to plugin",
worker_name
));
}
env.plugin_env
.senders
.send_to_plugin(PluginInstruction::PostMessageToPlugin(
env.plugin_env.plugin_id,
env.plugin_env.client_id,
plugin_message.name,
plugin_message.payload,
))
}
fn hide_self(env: &ForeignFunctionEnv) -> Result<()> {
env.plugin_env
.senders
.send_to_screen(ScreenInstruction::SuppressPane(
PaneId::Plugin(env.plugin_env.plugin_id),
env.plugin_env.client_id,
))
.with_context(|| format!("failed to hide self"))
}
fn show_self(env: &ForeignFunctionEnv, should_float_if_hidden: bool) {
let action = Action::FocusPluginPaneWithId(env.plugin_env.plugin_id, should_float_if_hidden);
let error_msg = || format!("Failed to show self for plugin");
apply_action!(action, error_msg, env);
}
fn switch_to_mode(env: &ForeignFunctionEnv, input_mode: InputMode) {
let action = Action::SwitchToMode(input_mode);
let error_msg = || {
format!(
"failed to switch to mode in plugin {}",
env.plugin_env.name()
)
};
apply_action!(action, error_msg, env);
}
fn new_tabs_with_layout(env: &ForeignFunctionEnv, raw_layout: &str) -> Result<()> {
let layout = Layout::from_str(
&raw_layout,
format!("Layout from plugin: {}", env.plugin_env.name()),
None,
None,
)
.map_err(|e| anyhow!("Failed to parse layout: {:?}", e))?;
let mut tabs_to_open = vec![];
let tabs = layout.tabs();
if tabs.is_empty() {
let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone());
let swap_floating_layouts = Some(layout.swap_floating_layouts.clone());
let action = Action::NewTab(
layout.template.as_ref().map(|t| t.0.clone()),
layout.template.map(|t| t.1).unwrap_or_default(),
swap_tiled_layouts,
swap_floating_layouts,
None,
);
tabs_to_open.push(action);
} else {
for (tab_name, tiled_pane_layout, floating_pane_layout) in layout.tabs() {
let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone());
let swap_floating_layouts = Some(layout.swap_floating_layouts.clone());
let action = Action::NewTab(
Some(tiled_pane_layout),
floating_pane_layout,
swap_tiled_layouts,
swap_floating_layouts,
tab_name,
);
tabs_to_open.push(action);
}
}
for action in tabs_to_open {
let error_msg = || format!("Failed to create layout tab");
apply_action!(action, error_msg, env);
}
Ok(())
}
fn new_tab(env: &ForeignFunctionEnv) {
let action = Action::NewTab(None, vec![], None, None, None);
let error_msg = || format!("Failed to open new tab");
apply_action!(action, error_msg, env);
}
fn go_to_next_tab(env: &ForeignFunctionEnv) {
let action = Action::GoToNextTab;
let error_msg = || format!("Failed to go to next tab");
apply_action!(action, error_msg, env);
}
fn go_to_previous_tab(env: &ForeignFunctionEnv) {
let action = Action::GoToPreviousTab;
let error_msg = || format!("Failed to go to previous tab");
apply_action!(action, error_msg, env);
}
fn resize(env: &ForeignFunctionEnv, resize: Resize) {
let error_msg = || format!("failed to resize in plugin {}", env.plugin_env.name());
let action = Action::Resize(resize, None);
apply_action!(action, error_msg, env);
}
fn resize_with_direction(env: &ForeignFunctionEnv, resize: ResizeStrategy) {
let error_msg = || format!("failed to resize in plugin {}", env.plugin_env.name());
let action = Action::Resize(resize.resize, resize.direction);
apply_action!(action, error_msg, env);
}
fn focus_next_pane(env: &ForeignFunctionEnv) {
let action = Action::FocusNextPane;
let error_msg = || format!("Failed to focus next pane");
apply_action!(action, error_msg, env);
}
fn focus_previous_pane(env: &ForeignFunctionEnv) {
let action = Action::FocusPreviousPane;
let error_msg = || format!("Failed to focus previous pane");
apply_action!(action, error_msg, env);
}
fn move_focus(env: &ForeignFunctionEnv, direction: Direction) {
let error_msg = || format!("failed to move focus in plugin {}", env.plugin_env.name());
let action = Action::MoveFocus(direction);
apply_action!(action, error_msg, env);
}
fn move_focus_or_tab(env: &ForeignFunctionEnv, direction: Direction) {
let error_msg = || format!("failed to move focus in plugin {}", env.plugin_env.name());
let action = Action::MoveFocusOrTab(direction);
apply_action!(action, error_msg, env);
}
fn detach(env: &ForeignFunctionEnv) {
let action = Action::Detach;
let error_msg = || format!("Failed to detach");
apply_action!(action, error_msg, env);
}
fn switch_session(
env: &ForeignFunctionEnv,
session_name: Option<String>,
tab_position: Option<usize>,
pane_id: Option<(u32, bool)>,
) -> Result<()> {
let err_context = || format!("Failed to switch session");
let client_id = env.plugin_env.client_id;
let tab_position = tab_position.map(|p| p + 1); let connect_to_session = ConnectToSession {
name: session_name,
tab_position,
pane_id,
};
env.plugin_env
.senders
.send_to_server(ServerInstruction::SwitchSession(
connect_to_session,
client_id,
))
.with_context(err_context)?;
Ok(())
}
fn edit_scrollback(env: &ForeignFunctionEnv) {
let action = Action::EditScrollback;
let error_msg = || format!("Failed to edit scrollback");
apply_action!(action, error_msg, env);
}
fn write(env: &ForeignFunctionEnv, bytes: Vec<u8>) {
let error_msg = || format!("failed to write in plugin {}", env.plugin_env.name());
let action = Action::Write(bytes);
apply_action!(action, error_msg, env);
}
fn write_chars(env: &ForeignFunctionEnv, chars_to_write: String) {
let error_msg = || format!("failed to write in plugin {}", env.plugin_env.name());
let action = Action::WriteChars(chars_to_write);
apply_action!(action, error_msg, env);
}
fn toggle_tab(env: &ForeignFunctionEnv) {
let error_msg = || format!("Failed to toggle tab");
let action = Action::ToggleTab;
apply_action!(action, error_msg, env);
}
fn move_pane(env: &ForeignFunctionEnv) {
let error_msg = || format!("failed to move pane in plugin {}", env.plugin_env.name());
let action = Action::MovePane(None);
apply_action!(action, error_msg, env);
}
fn move_pane_with_direction(env: &ForeignFunctionEnv, direction: Direction) {
let error_msg = || format!("failed to move pane in plugin {}", env.plugin_env.name());
let action = Action::MovePane(Some(direction));
apply_action!(action, error_msg, env);
}
fn clear_screen(env: &ForeignFunctionEnv) {
let error_msg = || format!("failed to clear screen in plugin {}", env.plugin_env.name());
let action = Action::ClearScreen;
apply_action!(action, error_msg, env);
}
fn scroll_up(env: &ForeignFunctionEnv) {
let error_msg = || format!("failed to scroll up in plugin {}", env.plugin_env.name());
let action = Action::ScrollUp;
apply_action!(action, error_msg, env);
}
fn scroll_down(env: &ForeignFunctionEnv) {
let error_msg = || format!("failed to scroll down in plugin {}", env.plugin_env.name());
let action = Action::ScrollDown;
apply_action!(action, error_msg, env);
}
fn scroll_to_top(env: &ForeignFunctionEnv) {
let error_msg = || format!("failed to scroll in plugin {}", env.plugin_env.name());
let action = Action::ScrollToTop;
apply_action!(action, error_msg, env);
}
fn scroll_to_bottom(env: &ForeignFunctionEnv) {
let error_msg = || format!("failed to scroll in plugin {}", env.plugin_env.name());
let action = Action::ScrollToBottom;
apply_action!(action, error_msg, env);
}
fn page_scroll_up(env: &ForeignFunctionEnv) {
let error_msg = || format!("failed to scroll in plugin {}", env.plugin_env.name());
let action = Action::PageScrollUp;
apply_action!(action, error_msg, env);
}
fn page_scroll_down(env: &ForeignFunctionEnv) {
let error_msg = || format!("failed to scroll in plugin {}", env.plugin_env.name());
let action = Action::PageScrollDown;
apply_action!(action, error_msg, env);
}
fn toggle_focus_fullscreen(env: &ForeignFunctionEnv) {
let error_msg = || {
format!(
"failed to toggle full screen in plugin {}",
env.plugin_env.name()
)
};
let action = Action::ToggleFocusFullscreen;
apply_action!(action, error_msg, env);
}
fn toggle_pane_frames(env: &ForeignFunctionEnv) {
let error_msg = || {
format!(
"failed to toggle full screen in plugin {}",
env.plugin_env.name()
)
};
let action = Action::TogglePaneFrames;
apply_action!(action, error_msg, env);
}
fn toggle_pane_embed_or_eject(env: &ForeignFunctionEnv) {
let error_msg = || {
format!(
"failed to toggle pane embed or eject in plugin {}",
env.plugin_env.name()
)
};
let action = Action::TogglePaneEmbedOrFloating;
apply_action!(action, error_msg, env);
}
fn undo_rename_pane(env: &ForeignFunctionEnv) {
let error_msg = || {
format!(
"failed to undo rename pane in plugin {}",
env.plugin_env.name()
)
};
let action = Action::UndoRenamePane;
apply_action!(action, error_msg, env);
}
fn close_focus(env: &ForeignFunctionEnv) {
let error_msg = || {
format!(
"failed to close focused pane in plugin {}",
env.plugin_env.name()
)
};
let action = Action::CloseFocus;
apply_action!(action, error_msg, env);
}
fn toggle_active_tab_sync(env: &ForeignFunctionEnv) {
let error_msg = || {
format!(
"failed to toggle active tab sync in plugin {}",
env.plugin_env.name()
)
};
let action = Action::ToggleActiveSyncTab;
apply_action!(action, error_msg, env);
}
fn close_focused_tab(env: &ForeignFunctionEnv) {
let error_msg = || {
format!(
"failed to close active tab in plugin {}",
env.plugin_env.name()
)
};
let action = Action::CloseTab;
apply_action!(action, error_msg, env);
}
fn undo_rename_tab(env: &ForeignFunctionEnv) {
let error_msg = || {
format!(
"failed to undo rename tab in plugin {}",
env.plugin_env.name()
)
};
let action = Action::UndoRenameTab;
apply_action!(action, error_msg, env);
}
fn quit_zellij(env: &ForeignFunctionEnv) {
let error_msg = || format!("failed to quit zellij in plugin {}", env.plugin_env.name());
let action = Action::Quit;
apply_action!(action, error_msg, env);
}
fn previous_swap_layout(env: &ForeignFunctionEnv) {
let error_msg = || {
format!(
"failed to switch swap layout in plugin {}",
env.plugin_env.name()
)
};
let action = Action::PreviousSwapLayout;
apply_action!(action, error_msg, env);
}
fn next_swap_layout(env: &ForeignFunctionEnv) {
let error_msg = || {
format!(
"failed to switch swap layout in plugin {}",
env.plugin_env.name()
)
};
let action = Action::NextSwapLayout;
apply_action!(action, error_msg, env);
}
fn go_to_tab_name(env: &ForeignFunctionEnv, tab_name: String) {
let error_msg = || format!("failed to change tab in plugin {}", env.plugin_env.name());
let create = false;
let action = Action::GoToTabName(tab_name, create);
apply_action!(action, error_msg, env);
}
fn focus_or_create_tab(env: &ForeignFunctionEnv, tab_name: String) {
let error_msg = || {
format!(
"failed to change or create tab in plugin {}",
env.plugin_env.name()
)
};
let create = true;
let action = Action::GoToTabName(tab_name, create);
apply_action!(action, error_msg, env);
}
fn go_to_tab(env: &ForeignFunctionEnv, tab_index: u32) {
let error_msg = || {
format!(
"failed to change tab focus in plugin {}",
env.plugin_env.name()
)
};
let action = Action::GoToTab(tab_index + 1);
apply_action!(action, error_msg, env);
}
fn start_or_reload_plugin(env: &ForeignFunctionEnv, url: &str) -> Result<()> {
let error_msg = || {
format!(
"failed to start or reload plugin in plugin {}",
env.plugin_env.name()
)
};
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let url = Url::parse(&url).map_err(|e| anyhow!("Failed to parse url: {}", e))?;
let run_plugin_location = RunPluginLocation::parse(url.as_str(), Some(cwd))
.map_err(|e| anyhow!("Failed to parse plugin location: {}", e))?;
let run_plugin = RunPlugin {
location: run_plugin_location,
_allow_exec_host_cmd: false,
configuration: PluginUserConfiguration::new(BTreeMap::new()), };
let action = Action::StartOrReloadPlugin(run_plugin);
apply_action!(action, error_msg, env);
Ok(())
}
fn close_terminal_pane(env: &ForeignFunctionEnv, terminal_pane_id: u32) {
let error_msg = || {
format!(
"failed to change tab focus in plugin {}",
env.plugin_env.name()
)
};
let action = Action::CloseTerminalPane(terminal_pane_id);
apply_action!(action, error_msg, env);
}
fn close_plugin_pane(env: &ForeignFunctionEnv, plugin_pane_id: u32) {
let error_msg = || {
format!(
"failed to change tab focus in plugin {}",
env.plugin_env.name()
)
};
let action = Action::ClosePluginPane(plugin_pane_id);
apply_action!(action, error_msg, env);
}
fn focus_terminal_pane(
env: &ForeignFunctionEnv,
terminal_pane_id: u32,
should_float_if_hidden: bool,
) {
let action = Action::FocusTerminalPaneWithId(terminal_pane_id, should_float_if_hidden);
let error_msg = || format!("Failed to focus terminal pane");
apply_action!(action, error_msg, env);
}
fn focus_plugin_pane(env: &ForeignFunctionEnv, plugin_pane_id: u32, should_float_if_hidden: bool) {
let action = Action::FocusPluginPaneWithId(plugin_pane_id, should_float_if_hidden);
let error_msg = || format!("Failed to focus plugin pane");
apply_action!(action, error_msg, env);
}
fn rename_terminal_pane(env: &ForeignFunctionEnv, terminal_pane_id: u32, new_name: &str) {
let error_msg = || format!("Failed to rename terminal pane");
let rename_pane_action =
Action::RenameTerminalPane(terminal_pane_id, new_name.as_bytes().to_vec());
apply_action!(rename_pane_action, error_msg, env);
}
fn rename_plugin_pane(env: &ForeignFunctionEnv, plugin_pane_id: u32, new_name: &str) {
let error_msg = || format!("Failed to rename plugin pane");
let rename_pane_action = Action::RenamePluginPane(plugin_pane_id, new_name.as_bytes().to_vec());
apply_action!(rename_pane_action, error_msg, env);
}
fn rename_tab(env: &ForeignFunctionEnv, tab_index: u32, new_name: &str) {
let error_msg = || format!("Failed to rename tab");
let rename_tab_action = Action::RenameTab(tab_index, new_name.as_bytes().to_vec());
apply_action!(rename_tab_action, error_msg, env);
}
fn report_panic(env: &ForeignFunctionEnv, msg: &str) {
log::error!("PANIC IN PLUGIN!\n\r{}", msg);
handle_plugin_crash(
env.plugin_env.plugin_id,
msg.to_owned(),
env.plugin_env.senders.clone(),
);
}
pub fn wasi_read_string(wasi_env: &WasiEnv) -> Result<String> {
let err_context = || format!("failed to read string from WASI env '{wasi_env:?}'");
let mut buf = vec![];
wasi_env
.state()
.fs
.stdout_mut()
.map_err(anyError::new)
.and_then(|stdout| {
stdout
.as_mut()
.ok_or(anyhow!("failed to get mutable reference to stdout"))
})
.and_then(|wasi_file| wasi_file.read_to_end(&mut buf).map_err(anyError::new))
.with_context(err_context)?;
let buf = String::from_utf8_lossy(&buf);
Ok(buf.replace("\n", "\n\r"))
}
pub fn wasi_write_string(wasi_env: &WasiEnv, buf: &str) -> Result<()> {
wasi_env
.state()
.fs
.stdin_mut()
.map_err(anyError::new)
.and_then(|stdin| {
stdin
.as_mut()
.ok_or(anyhow!("failed to get mutable reference to stdin"))
})
.and_then(|stdin| writeln!(stdin, "{}\r", buf).map_err(anyError::new))
.with_context(|| format!("failed to write string to WASI env '{wasi_env:?}'"))
}
pub fn wasi_write_object(wasi_env: &WasiEnv, object: &(impl Serialize + ?Sized)) -> Result<()> {
serde_json::to_string(&object)
.map_err(anyError::new)
.and_then(|string| wasi_write_string(wasi_env, &string))
.with_context(|| format!("failed to serialize object for WASI env '{wasi_env:?}'"))
}
pub fn wasi_read_bytes(wasi_env: &WasiEnv) -> Result<Vec<u8>> {
wasi_read_string(wasi_env)
.and_then(|string| serde_json::from_str(&string).map_err(anyError::new))
.with_context(|| format!("failed to deserialize object from WASI env '{wasi_env:?}'"))
}
fn check_command_permission(
plugin_env: &PluginEnv,
command: &PluginCommand,
) -> (PermissionStatus, Option<PermissionType>) {
if plugin_env.plugin.is_builtin() {
return (PermissionStatus::Granted, None);
}
let permission = match command {
PluginCommand::OpenFile(..) | PluginCommand::OpenFileFloating(..) => {
PermissionType::OpenFiles
},
PluginCommand::OpenTerminal(..)
| PluginCommand::StartOrReloadPlugin(..)
| PluginCommand::OpenTerminalFloating(..) => PermissionType::OpenTerminalsOrPlugins,
PluginCommand::OpenCommandPane(..)
| PluginCommand::OpenCommandPaneFloating(..)
| PluginCommand::ExecCmd(..) => PermissionType::RunCommands,
PluginCommand::Write(..) | PluginCommand::WriteChars(..) => PermissionType::WriteToStdin,
PluginCommand::SwitchTabTo(..)
| PluginCommand::SwitchToMode(..)
| PluginCommand::NewTabsWithLayout(..)
| PluginCommand::NewTab
| PluginCommand::GoToNextTab
| PluginCommand::GoToPreviousTab
| PluginCommand::Resize(..)
| PluginCommand::ResizeWithDirection(..)
| PluginCommand::FocusNextPane
| PluginCommand::MoveFocus(..)
| PluginCommand::MoveFocusOrTab(..)
| PluginCommand::Detach
| PluginCommand::EditScrollback
| PluginCommand::ToggleTab
| PluginCommand::MovePane
| PluginCommand::MovePaneWithDirection(..)
| PluginCommand::ClearScreen
| PluginCommand::ScrollUp
| PluginCommand::ScrollDown
| PluginCommand::ScrollToTop
| PluginCommand::ScrollToBottom
| PluginCommand::PageScrollUp
| PluginCommand::PageScrollDown
| PluginCommand::ToggleFocusFullscreen
| PluginCommand::TogglePaneFrames
| PluginCommand::TogglePaneEmbedOrEject
| PluginCommand::UndoRenamePane
| PluginCommand::CloseFocus
| PluginCommand::ToggleActiveTabSync
| PluginCommand::CloseFocusedTab
| PluginCommand::UndoRenameTab
| PluginCommand::QuitZellij
| PluginCommand::PreviousSwapLayout
| PluginCommand::NextSwapLayout
| PluginCommand::GoToTabName(..)
| PluginCommand::FocusOrCreateTab(..)
| PluginCommand::GoToTab(..)
| PluginCommand::CloseTerminalPane(..)
| PluginCommand::ClosePluginPane(..)
| PluginCommand::FocusTerminalPane(..)
| PluginCommand::FocusPluginPane(..)
| PluginCommand::RenameTerminalPane(..)
| PluginCommand::RenamePluginPane(..)
| PluginCommand::SwitchSession(..)
| PluginCommand::RenameTab(..) => PermissionType::ChangeApplicationState,
_ => return (PermissionStatus::Granted, None),
};
if let Some(permissions) = plugin_env.permissions.lock().unwrap().as_ref() {
if permissions.contains(&permission) {
return (PermissionStatus::Granted, None);
}
}
(PermissionStatus::Denied, Some(permission))
}