zellij_server/plugins/
zellij_exports.rs

1use super::PluginInstruction;
2use crate::background_jobs::BackgroundJob;
3use crate::plugins::plugin_map::PluginEnv;
4use crate::plugins::wasm_bridge::handle_plugin_crash;
5use crate::pty::{ClientTabIndexOrPaneId, NewPanePlacement, PtyInstruction};
6use crate::route::route_action;
7use crate::ServerInstruction;
8use async_std::task;
9use interprocess::local_socket::LocalSocketStream;
10use log::warn;
11use serde::Serialize;
12use std::{
13    collections::{BTreeMap, HashSet},
14    io::{Read, Write},
15    path::PathBuf,
16    process,
17    str::FromStr,
18    thread,
19    time::{Duration, Instant},
20};
21use wasmtime::{Caller, Linker};
22use zellij_utils::data::{
23    CommandType, ConnectToSession, FloatingPaneCoordinates, HttpVerb, KeyWithModifier, LayoutInfo,
24    MessageToPlugin, OriginatingPlugin, PermissionStatus, PermissionType, PluginPermission,
25};
26use zellij_utils::input::permission::PermissionCache;
27use zellij_utils::ipc::{ClientToServerMsg, IpcSenderWithContext};
28#[cfg(feature = "web_server_capability")]
29use zellij_utils::web_authentication_tokens::{
30    create_token, list_tokens, rename_token, revoke_all_tokens, revoke_token,
31};
32#[cfg(feature = "web_server_capability")]
33use zellij_utils::web_server_commands::shutdown_all_webserver_instances;
34
35use crate::{panes::PaneId, screen::ScreenInstruction};
36
37use prost::Message;
38use zellij_utils::{
39    consts::{VERSION, ZELLIJ_SESSION_INFO_CACHE_DIR, ZELLIJ_SOCK_DIR},
40    data::{
41        CommandToRun, Direction, Event, EventType, FileToOpen, InputMode, PluginCommand, PluginIds,
42        PluginMessage, Resize, ResizeStrategy,
43    },
44    errors::prelude::*,
45    input::{
46        actions::Action,
47        command::{OpenFilePayload, RunCommand, RunCommandAction, TerminalAction},
48        layout::{Layout, RunPluginOrAlias},
49    },
50    plugin_api::{
51        plugin_command::ProtobufPluginCommand,
52        plugin_ids::{ProtobufPluginIds, ProtobufZellijVersion},
53    },
54};
55
56#[cfg(feature = "web_server_capability")]
57use zellij_utils::plugin_api::plugin_command::{
58    CreateTokenResponse, ListTokensResponse, RenameWebTokenResponse, RevokeAllWebTokensResponse,
59    RevokeTokenResponse,
60};
61
62macro_rules! apply_action {
63    ($action:ident, $error_message:ident, $env: ident) => {
64        if let Err(e) = route_action(
65            $action,
66            $env.client_id,
67            Some(PaneId::Plugin($env.plugin_id)),
68            $env.senders.clone(),
69            $env.capabilities.clone(),
70            $env.client_attributes.clone(),
71            $env.default_shell.clone(),
72            $env.default_layout.clone(),
73            None,
74            $env.keybinds.clone(),
75            $env.default_mode.clone(),
76        ) {
77            log::error!("{}: {:?}", $error_message(), e);
78        }
79    };
80}
81
82pub fn zellij_exports(linker: &mut Linker<PluginEnv>) {
83    linker
84        .func_wrap("zellij", "host_run_plugin_command", host_run_plugin_command)
85        .unwrap();
86}
87
88fn host_run_plugin_command(mut caller: Caller<'_, PluginEnv>) {
89    let mut env = caller.data_mut();
90    let plugin_command = env.name();
91    let err_context = || format!("failed to run plugin command {}", plugin_command);
92    wasi_read_bytes(env)
93        .and_then(|bytes| {
94            let command: ProtobufPluginCommand = ProtobufPluginCommand::decode(bytes.as_slice())?;
95            let command: PluginCommand = command
96                .try_into()
97                .map_err(|e| anyhow!("failed to convert serialized command: {}", e))?;
98            match check_command_permission(&env, &command) {
99                (PermissionStatus::Granted, _) => match command {
100                    PluginCommand::Subscribe(event_list) => subscribe(env, event_list)?,
101                    PluginCommand::Unsubscribe(event_list) => unsubscribe(env, event_list)?,
102                    PluginCommand::SetSelectable(selectable) => set_selectable(env, selectable),
103                    PluginCommand::GetPluginIds => get_plugin_ids(env),
104                    PluginCommand::GetZellijVersion => get_zellij_version(env),
105                    PluginCommand::OpenFile(file_to_open, context) => {
106                        open_file(env, file_to_open, context)
107                    },
108                    PluginCommand::OpenFileFloating(
109                        file_to_open,
110                        floating_pane_coordinates,
111                        context,
112                    ) => open_file_floating(env, file_to_open, floating_pane_coordinates, context),
113                    PluginCommand::OpenTerminal(cwd) => open_terminal(env, cwd.path.try_into()?),
114                    PluginCommand::OpenTerminalNearPlugin(cwd) => {
115                        open_terminal_near_plugin(env, cwd.path.try_into()?)
116                    },
117                    PluginCommand::OpenTerminalFloating(cwd, floating_pane_coordinates) => {
118                        open_terminal_floating(env, cwd.path.try_into()?, floating_pane_coordinates)
119                    },
120                    PluginCommand::OpenTerminalFloatingNearPlugin(
121                        cwd,
122                        floating_pane_coordinates,
123                    ) => open_terminal_floating_near_plugin(
124                        env,
125                        cwd.path.try_into()?,
126                        floating_pane_coordinates,
127                    ),
128                    PluginCommand::OpenCommandPane(command_to_run, context) => {
129                        open_command_pane(env, command_to_run, context)
130                    },
131                    PluginCommand::OpenCommandPaneNearPlugin(command_to_run, context) => {
132                        open_command_pane_near_plugin(env, command_to_run, context)
133                    },
134                    PluginCommand::OpenCommandPaneFloating(
135                        command_to_run,
136                        floating_pane_coordinates,
137                        context,
138                    ) => open_command_pane_floating(
139                        env,
140                        command_to_run,
141                        floating_pane_coordinates,
142                        context,
143                    ),
144                    PluginCommand::OpenCommandPaneFloatingNearPlugin(
145                        command_to_run,
146                        floating_pane_coordinates,
147                        context,
148                    ) => open_command_pane_floating_near_plugin(
149                        env,
150                        command_to_run,
151                        floating_pane_coordinates,
152                        context,
153                    ),
154                    PluginCommand::SwitchTabTo(tab_index) => switch_tab_to(env, tab_index),
155                    PluginCommand::SetTimeout(seconds) => set_timeout(env, seconds),
156                    PluginCommand::ExecCmd(command_line) => exec_cmd(env, command_line),
157                    PluginCommand::RunCommand(command_line, env_variables, cwd, context) => {
158                        run_command(env, command_line, env_variables, cwd, context)
159                    },
160                    PluginCommand::WebRequest(url, verb, headers, body, context) => {
161                        web_request(env, url, verb, headers, body, context)
162                    },
163                    PluginCommand::PostMessageTo(plugin_message) => {
164                        post_message_to(env, plugin_message)?
165                    },
166                    PluginCommand::PostMessageToPlugin(plugin_message) => {
167                        post_message_to_plugin(env, plugin_message)?
168                    },
169                    PluginCommand::HideSelf => hide_self(env)?,
170                    PluginCommand::ShowSelf(should_float_if_hidden) => {
171                        show_self(env, should_float_if_hidden)
172                    },
173                    PluginCommand::SwitchToMode(input_mode) => {
174                        switch_to_mode(env, input_mode.try_into()?)
175                    },
176                    PluginCommand::NewTabsWithLayout(raw_layout) => {
177                        new_tabs_with_layout(env, &raw_layout)?
178                    },
179                    PluginCommand::NewTabsWithLayoutInfo(layout_info) => {
180                        new_tabs_with_layout_info(env, layout_info)?
181                    },
182                    PluginCommand::NewTab { name, cwd } => new_tab(env, name, cwd),
183                    PluginCommand::GoToNextTab => go_to_next_tab(env),
184                    PluginCommand::GoToPreviousTab => go_to_previous_tab(env),
185                    PluginCommand::Resize(resize_payload) => resize(env, resize_payload),
186                    PluginCommand::ResizeWithDirection(resize_strategy) => {
187                        resize_with_direction(env, resize_strategy)
188                    },
189                    PluginCommand::FocusNextPane => focus_next_pane(env),
190                    PluginCommand::FocusPreviousPane => focus_previous_pane(env),
191                    PluginCommand::MoveFocus(direction) => move_focus(env, direction),
192                    PluginCommand::MoveFocusOrTab(direction) => move_focus_or_tab(env, direction),
193                    PluginCommand::Detach => detach(env),
194                    PluginCommand::EditScrollback => edit_scrollback(env),
195                    PluginCommand::Write(bytes) => write(env, bytes),
196                    PluginCommand::WriteChars(chars) => write_chars(env, chars),
197                    PluginCommand::ToggleTab => toggle_tab(env),
198                    PluginCommand::MovePane => move_pane(env),
199                    PluginCommand::MovePaneWithDirection(direction) => {
200                        move_pane_with_direction(env, direction)
201                    },
202                    PluginCommand::ClearScreen => clear_screen(env),
203                    PluginCommand::ScrollUp => scroll_up(env),
204                    PluginCommand::ScrollDown => scroll_down(env),
205                    PluginCommand::ScrollToTop => scroll_to_top(env),
206                    PluginCommand::ScrollToBottom => scroll_to_bottom(env),
207                    PluginCommand::PageScrollUp => page_scroll_up(env),
208                    PluginCommand::PageScrollDown => page_scroll_down(env),
209                    PluginCommand::ToggleFocusFullscreen => toggle_focus_fullscreen(env),
210                    PluginCommand::TogglePaneFrames => toggle_pane_frames(env),
211                    PluginCommand::TogglePaneEmbedOrEject => toggle_pane_embed_or_eject(env),
212                    PluginCommand::UndoRenamePane => undo_rename_pane(env),
213                    PluginCommand::CloseFocus => close_focus(env),
214                    PluginCommand::ToggleActiveTabSync => toggle_active_tab_sync(env),
215                    PluginCommand::CloseFocusedTab => close_focused_tab(env),
216                    PluginCommand::UndoRenameTab => undo_rename_tab(env),
217                    PluginCommand::QuitZellij => quit_zellij(env),
218                    PluginCommand::PreviousSwapLayout => previous_swap_layout(env),
219                    PluginCommand::NextSwapLayout => next_swap_layout(env),
220                    PluginCommand::GoToTabName(tab_name) => go_to_tab_name(env, tab_name),
221                    PluginCommand::FocusOrCreateTab(tab_name) => focus_or_create_tab(env, tab_name),
222                    PluginCommand::GoToTab(tab_index) => go_to_tab(env, tab_index),
223                    PluginCommand::StartOrReloadPlugin(plugin_url) => {
224                        start_or_reload_plugin(env, &plugin_url)?
225                    },
226                    PluginCommand::CloseTerminalPane(terminal_pane_id) => {
227                        close_terminal_pane(env, terminal_pane_id)
228                    },
229                    PluginCommand::ClosePluginPane(plugin_pane_id) => {
230                        close_plugin_pane(env, plugin_pane_id)
231                    },
232                    PluginCommand::FocusTerminalPane(terminal_pane_id, should_float_if_hidden) => {
233                        focus_terminal_pane(env, terminal_pane_id, should_float_if_hidden)
234                    },
235                    PluginCommand::FocusPluginPane(plugin_pane_id, should_float_if_hidden) => {
236                        focus_plugin_pane(env, plugin_pane_id, should_float_if_hidden)
237                    },
238                    PluginCommand::RenameTerminalPane(terminal_pane_id, new_name) => {
239                        rename_terminal_pane(env, terminal_pane_id, &new_name)
240                    },
241                    PluginCommand::RenamePluginPane(plugin_pane_id, new_name) => {
242                        rename_plugin_pane(env, plugin_pane_id, &new_name)
243                    },
244                    PluginCommand::RenameTab(tab_index, new_name) => {
245                        rename_tab(env, tab_index, &new_name)
246                    },
247                    PluginCommand::ReportPanic(crash_payload) => report_panic(env, &crash_payload),
248                    PluginCommand::RequestPluginPermissions(permissions) => {
249                        request_permission(env, permissions)?
250                    },
251                    PluginCommand::SwitchSession(connect_to_session) => switch_session(
252                        env,
253                        connect_to_session.name,
254                        connect_to_session.tab_position,
255                        connect_to_session.pane_id,
256                        connect_to_session.layout,
257                        connect_to_session.cwd,
258                    )?,
259                    PluginCommand::DeleteDeadSession(session_name) => {
260                        delete_dead_session(session_name)?
261                    },
262                    PluginCommand::DeleteAllDeadSessions => delete_all_dead_sessions()?,
263                    PluginCommand::OpenFileInPlace(file_to_open, context) => {
264                        open_file_in_place(env, file_to_open, context)
265                    },
266                    PluginCommand::OpenTerminalInPlace(cwd) => {
267                        open_terminal_in_place(env, cwd.path.try_into()?)
268                    },
269                    PluginCommand::OpenTerminalInPlaceOfPlugin(cwd, close_plugin_after_replace) => {
270                        open_terminal_in_place_of_plugin(
271                            env,
272                            cwd.path.try_into()?,
273                            close_plugin_after_replace,
274                        )
275                    },
276                    PluginCommand::OpenCommandPaneInPlace(command_to_run, context) => {
277                        open_command_pane_in_place(env, command_to_run, context)
278                    },
279                    PluginCommand::OpenCommandPaneInPlaceOfPlugin(
280                        command_to_run,
281                        close_plugin_after_replace,
282                        context,
283                    ) => open_command_pane_in_place_of_plugin(
284                        env,
285                        command_to_run,
286                        close_plugin_after_replace,
287                        context,
288                    ),
289                    PluginCommand::RenameSession(new_session_name) => {
290                        rename_session(env, new_session_name)
291                    },
292                    PluginCommand::UnblockCliPipeInput(pipe_name) => {
293                        unblock_cli_pipe_input(env, pipe_name)
294                    },
295                    PluginCommand::BlockCliPipeInput(pipe_name) => {
296                        block_cli_pipe_input(env, pipe_name)
297                    },
298                    PluginCommand::CliPipeOutput(pipe_name, output) => {
299                        cli_pipe_output(env, pipe_name, output)?
300                    },
301                    PluginCommand::MessageToPlugin(message) => message_to_plugin(env, message)?,
302                    PluginCommand::DisconnectOtherClients => disconnect_other_clients(env),
303                    PluginCommand::KillSessions(session_list) => kill_sessions(session_list),
304                    PluginCommand::ScanHostFolder(folder_to_scan) => {
305                        scan_host_folder(env, folder_to_scan)
306                    },
307                    PluginCommand::WatchFilesystem => watch_filesystem(env),
308                    PluginCommand::DumpSessionLayout => dump_session_layout(env),
309                    PluginCommand::CloseSelf => close_self(env),
310                    PluginCommand::Reconfigure(new_config, write_config_to_disk) => {
311                        reconfigure(env, new_config, write_config_to_disk)?
312                    },
313                    PluginCommand::HidePaneWithId(pane_id) => {
314                        hide_pane_with_id(env, pane_id.into())?
315                    },
316                    PluginCommand::ShowPaneWithId(pane_id, should_float_if_hidden) => {
317                        show_pane_with_id(env, pane_id.into(), should_float_if_hidden)
318                    },
319                    PluginCommand::OpenCommandPaneBackground(command_to_run, context) => {
320                        open_command_pane_background(env, command_to_run, context)
321                    },
322                    PluginCommand::RerunCommandPane(terminal_pane_id) => {
323                        rerun_command_pane(env, terminal_pane_id)
324                    },
325                    PluginCommand::ResizePaneIdWithDirection(resize, pane_id) => {
326                        resize_pane_with_id(env, resize, pane_id.into())
327                    },
328                    PluginCommand::EditScrollbackForPaneWithId(pane_id) => {
329                        edit_scrollback_for_pane_with_id(env, pane_id.into())
330                    },
331                    PluginCommand::WriteToPaneId(bytes, pane_id) => {
332                        write_to_pane_id(env, bytes, pane_id.into())
333                    },
334                    PluginCommand::WriteCharsToPaneId(chars, pane_id) => {
335                        write_chars_to_pane_id(env, chars, pane_id.into())
336                    },
337                    PluginCommand::MovePaneWithPaneId(pane_id) => {
338                        move_pane_with_pane_id(env, pane_id.into())
339                    },
340                    PluginCommand::MovePaneWithPaneIdInDirection(pane_id, direction) => {
341                        move_pane_with_pane_id_in_direction(env, pane_id.into(), direction)
342                    },
343                    PluginCommand::ClearScreenForPaneId(pane_id) => {
344                        clear_screen_for_pane_id(env, pane_id.into())
345                    },
346                    PluginCommand::ScrollUpInPaneId(pane_id) => {
347                        scroll_up_in_pane_id(env, pane_id.into())
348                    },
349                    PluginCommand::ScrollDownInPaneId(pane_id) => {
350                        scroll_down_in_pane_id(env, pane_id.into())
351                    },
352                    PluginCommand::ScrollToTopInPaneId(pane_id) => {
353                        scroll_to_top_in_pane_id(env, pane_id.into())
354                    },
355                    PluginCommand::ScrollToBottomInPaneId(pane_id) => {
356                        scroll_to_bottom_in_pane_id(env, pane_id.into())
357                    },
358                    PluginCommand::PageScrollUpInPaneId(pane_id) => {
359                        page_scroll_up_in_pane_id(env, pane_id.into())
360                    },
361                    PluginCommand::PageScrollDownInPaneId(pane_id) => {
362                        page_scroll_down_in_pane_id(env, pane_id.into())
363                    },
364                    PluginCommand::TogglePaneIdFullscreen(pane_id) => {
365                        toggle_pane_id_fullscreen(env, pane_id.into())
366                    },
367                    PluginCommand::TogglePaneEmbedOrEjectForPaneId(pane_id) => {
368                        toggle_pane_embed_or_eject_for_pane_id(env, pane_id.into())
369                    },
370                    PluginCommand::CloseTabWithIndex(tab_index) => {
371                        close_tab_with_index(env, tab_index)
372                    },
373                    PluginCommand::BreakPanesToNewTab(
374                        pane_ids,
375                        new_tab_name,
376                        should_change_focus_to_new_tab,
377                    ) => break_panes_to_new_tab(
378                        env,
379                        pane_ids.into_iter().map(|p_id| p_id.into()).collect(),
380                        new_tab_name,
381                        should_change_focus_to_new_tab,
382                    ),
383                    PluginCommand::BreakPanesToTabWithIndex(
384                        pane_ids,
385                        should_change_focus_to_new_tab,
386                        tab_index,
387                    ) => break_panes_to_tab_with_index(
388                        env,
389                        pane_ids.into_iter().map(|p_id| p_id.into()).collect(),
390                        tab_index,
391                        should_change_focus_to_new_tab,
392                    ),
393                    PluginCommand::ReloadPlugin(plugin_id) => reload_plugin(env, plugin_id),
394                    PluginCommand::LoadNewPlugin {
395                        url,
396                        config,
397                        load_in_background,
398                        skip_plugin_cache,
399                    } => load_new_plugin(env, url, config, load_in_background, skip_plugin_cache),
400                    PluginCommand::RebindKeys {
401                        keys_to_rebind,
402                        keys_to_unbind,
403                        write_config_to_disk,
404                    } => rebind_keys(env, keys_to_rebind, keys_to_unbind, write_config_to_disk)?,
405                    PluginCommand::ListClients => list_clients(env),
406                    PluginCommand::ChangeHostFolder(new_host_folder) => {
407                        change_host_folder(env, new_host_folder)
408                    },
409                    PluginCommand::SetFloatingPanePinned(pane_id, should_be_pinned) => {
410                        set_floating_pane_pinned(env, pane_id.into(), should_be_pinned)
411                    },
412                    PluginCommand::StackPanes(pane_ids) => {
413                        stack_panes(env, pane_ids.into_iter().map(|p_id| p_id.into()).collect())
414                    },
415                    PluginCommand::ChangeFloatingPanesCoordinates(pane_ids_and_coordinates) => {
416                        change_floating_panes_coordinates(
417                            env,
418                            pane_ids_and_coordinates
419                                .into_iter()
420                                .map(|(p_id, coordinates)| (p_id.into(), coordinates))
421                                .collect(),
422                        )
423                    },
424                    PluginCommand::OpenFileNearPlugin(file_to_open, context) => {
425                        open_file_near_plugin(env, file_to_open, context)
426                    },
427                    PluginCommand::OpenFileFloatingNearPlugin(
428                        file_to_open,
429                        floating_pane_coordinates,
430                        context,
431                    ) => open_file_floating_near_plugin(
432                        env,
433                        file_to_open,
434                        floating_pane_coordinates,
435                        context,
436                    ),
437                    PluginCommand::OpenFileInPlaceOfPlugin(
438                        file_to_open,
439                        close_plugin_after_replace,
440                        context,
441                    ) => open_file_in_place_of_plugin(
442                        env,
443                        file_to_open,
444                        close_plugin_after_replace,
445                        context,
446                    ),
447                    PluginCommand::GroupAndUngroupPanes(
448                        panes_to_group,
449                        panes_to_ungroup,
450                        for_all_clients,
451                    ) => group_and_ungroup_panes(
452                        env,
453                        panes_to_group.into_iter().map(|p| p.into()).collect(),
454                        panes_to_ungroup.into_iter().map(|p| p.into()).collect(),
455                        for_all_clients,
456                    ),
457                    PluginCommand::HighlightAndUnhighlightPanes(
458                        panes_to_highlight,
459                        panes_to_unhighlight,
460                    ) => highlight_and_unhighlight_panes(
461                        env,
462                        panes_to_highlight.into_iter().map(|p| p.into()).collect(),
463                        panes_to_unhighlight.into_iter().map(|p| p.into()).collect(),
464                    ),
465                    PluginCommand::CloseMultiplePanes(pane_ids) => {
466                        close_multiple_panes(env, pane_ids.into_iter().map(|p| p.into()).collect())
467                    },
468                    PluginCommand::FloatMultiplePanes(pane_ids) => {
469                        float_multiple_panes(env, pane_ids.into_iter().map(|p| p.into()).collect())
470                    },
471                    PluginCommand::EmbedMultiplePanes(pane_ids) => {
472                        embed_multiple_panes(env, pane_ids.into_iter().map(|p| p.into()).collect())
473                    },
474                    PluginCommand::StartWebServer => start_web_server(env),
475                    PluginCommand::StopWebServer => stop_web_server(env),
476                    PluginCommand::QueryWebServerStatus => query_web_server_status(env),
477                    PluginCommand::ShareCurrentSession => share_current_session(env),
478                    PluginCommand::StopSharingCurrentSession => stop_sharing_current_session(env),
479                    PluginCommand::SetSelfMouseSelectionSupport(selection_support) => {
480                        set_self_mouse_selection_support(env, selection_support);
481                    },
482                    PluginCommand::GenerateWebLoginToken(token_label) => {
483                        generate_web_login_token(env, token_label);
484                    },
485                    PluginCommand::RevokeWebLoginToken(label) => {
486                        revoke_web_login_token(env, label);
487                    },
488                    PluginCommand::ListWebLoginTokens => {
489                        list_web_login_tokens(env);
490                    },
491                    PluginCommand::RevokeAllWebLoginTokens => {
492                        revoke_all_web_login_tokens(env);
493                    },
494                    PluginCommand::RenameWebLoginToken(old_name, new_name) => {
495                        rename_web_login_token(env, old_name, new_name);
496                    },
497                    PluginCommand::InterceptKeyPresses => intercept_key_presses(&mut env),
498                    PluginCommand::ClearKeyPressesIntercepts => {
499                        clear_key_presses_intercepts(&mut env)
500                    },
501                    PluginCommand::ReplacePaneWithExistingPane(
502                        pane_id_to_replace,
503                        existing_pane_id,
504                    ) => replace_pane_with_existing_pane(
505                        &mut env,
506                        pane_id_to_replace.into(),
507                        existing_pane_id.into(),
508                    ),
509                },
510                (PermissionStatus::Denied, permission) => {
511                    log::error!(
512                        "Plugin '{}' permission '{}' denied - Command '{:?}' denied",
513                        env.name(),
514                        permission
515                            .map(|p| p.to_string())
516                            .unwrap_or("UNKNOWN".to_owned()),
517                        CommandType::from_str(&command.to_string()).with_context(err_context)?
518                    );
519                },
520            };
521            Ok(())
522        })
523        .with_context(|| format!("failed to run plugin command {}", env.name()))
524        .non_fatal();
525}
526
527fn subscribe(env: &PluginEnv, event_list: HashSet<EventType>) -> Result<()> {
528    env.subscriptions
529        .lock()
530        .to_anyhow()?
531        .extend(event_list.clone());
532    env.senders
533        .send_to_plugin(PluginInstruction::PluginSubscribedToEvents(
534            env.plugin_id,
535            env.client_id,
536            event_list,
537        ))
538}
539
540fn unblock_cli_pipe_input(env: &PluginEnv, pipe_name: String) {
541    env.input_pipes_to_unblock.lock().unwrap().insert(pipe_name);
542}
543
544fn block_cli_pipe_input(env: &PluginEnv, pipe_name: String) {
545    env.input_pipes_to_block.lock().unwrap().insert(pipe_name);
546}
547
548fn cli_pipe_output(env: &PluginEnv, pipe_name: String, output: String) -> Result<()> {
549    env.senders
550        .send_to_server(ServerInstruction::CliPipeOutput(pipe_name, output))
551        .context("failed to send pipe output")
552}
553
554fn message_to_plugin(env: &PluginEnv, mut message_to_plugin: MessageToPlugin) -> Result<()> {
555    if message_to_plugin.plugin_url.as_ref().map(|s| s.as_str()) == Some("zellij:OWN_URL") {
556        message_to_plugin.plugin_url = Some(env.plugin.location.display());
557    }
558    env.senders
559        .send_to_plugin(PluginInstruction::MessageFromPlugin {
560            source_plugin_id: env.plugin_id,
561            message: message_to_plugin,
562        })
563        .context("failed to send message to plugin")
564}
565
566fn unsubscribe(env: &PluginEnv, event_list: HashSet<EventType>) -> Result<()> {
567    env.subscriptions
568        .lock()
569        .to_anyhow()?
570        .retain(|k| !event_list.contains(k));
571    Ok(())
572}
573
574fn set_selectable(env: &PluginEnv, selectable: bool) {
575    env.senders
576        .send_to_screen(ScreenInstruction::SetSelectable(
577            PaneId::Plugin(env.plugin_id),
578            selectable,
579        ))
580        .with_context(|| {
581            format!(
582                "failed to set plugin {} selectable from plugin {}",
583                selectable,
584                env.name()
585            )
586        })
587        .non_fatal();
588}
589
590fn request_permission(env: &PluginEnv, permissions: Vec<PermissionType>) -> Result<()> {
591    if PermissionCache::from_path_or_default(None)
592        .check_permissions(env.plugin.location.to_string(), &permissions)
593    {
594        return env
595            .senders
596            .send_to_plugin(PluginInstruction::PermissionRequestResult(
597                env.plugin_id,
598                Some(env.client_id),
599                permissions.to_vec(),
600                PermissionStatus::Granted,
601                None,
602            ));
603    }
604
605    // we do this so that messages that have arrived while the user is seeing the permission screen
606    // will be cached and reapplied once the permission is granted
607    let _ = env
608        .senders
609        .send_to_plugin(PluginInstruction::CachePluginEvents {
610            plugin_id: env.plugin_id,
611        });
612
613    env.senders
614        .send_to_screen(ScreenInstruction::RequestPluginPermissions(
615            env.plugin_id,
616            PluginPermission::new(env.plugin.location.to_string(), permissions),
617        ))
618}
619
620fn get_plugin_ids(env: &PluginEnv) {
621    let ids = PluginIds {
622        plugin_id: env.plugin_id,
623        zellij_pid: process::id(),
624        initial_cwd: env.plugin_cwd.clone(),
625        client_id: env.client_id,
626    };
627    ProtobufPluginIds::try_from(ids)
628        .map_err(|e| anyhow!("Failed to serialized plugin ids: {}", e))
629        .and_then(|serialized| {
630            wasi_write_object(env, &serialized.encode_to_vec())?;
631            Ok(())
632        })
633        .with_context(|| {
634            format!(
635                "failed to query plugin IDs from host for plugin {}",
636                env.name()
637            )
638        })
639        .non_fatal();
640}
641
642fn get_zellij_version(env: &PluginEnv) {
643    let protobuf_zellij_version = ProtobufZellijVersion {
644        version: VERSION.to_owned(),
645    };
646    wasi_write_object(env, &protobuf_zellij_version.encode_to_vec())
647        .with_context(|| {
648            format!(
649                "failed to request zellij version from host for plugin {}",
650                env.name()
651            )
652        })
653        .non_fatal();
654}
655
656fn open_file(env: &PluginEnv, file_to_open: FileToOpen, context: BTreeMap<String, String>) {
657    let error_msg = || format!("failed to open file in plugin {}", env.name());
658    let floating = false;
659    let in_place = false;
660    let start_suppressed = false;
661    let path = env.plugin_cwd.join(file_to_open.path);
662    let cwd = file_to_open
663        .cwd
664        .map(|cwd| env.plugin_cwd.join(cwd))
665        .or_else(|| Some(env.plugin_cwd.clone()));
666    let action = Action::EditFile(
667        OpenFilePayload::new(path, file_to_open.line_number, cwd).with_originating_plugin(
668            OriginatingPlugin::new(env.plugin_id, env.client_id, context),
669        ),
670        None,
671        floating,
672        in_place,
673        start_suppressed,
674        None,
675    );
676    apply_action!(action, error_msg, env);
677}
678
679fn open_file_floating(
680    env: &PluginEnv,
681    file_to_open: FileToOpen,
682    floating_pane_coordinates: Option<FloatingPaneCoordinates>,
683    context: BTreeMap<String, String>,
684) {
685    let error_msg = || format!("failed to open file in plugin {}", env.name());
686    let floating = true;
687    let in_place = false;
688    let start_suppressed = false;
689    let path = env.plugin_cwd.join(file_to_open.path);
690    let cwd = file_to_open
691        .cwd
692        .map(|cwd| env.plugin_cwd.join(cwd))
693        .or_else(|| Some(env.plugin_cwd.clone()));
694    let action = Action::EditFile(
695        OpenFilePayload::new(path, file_to_open.line_number, cwd).with_originating_plugin(
696            OriginatingPlugin::new(env.plugin_id, env.client_id, context),
697        ),
698        None,
699        floating,
700        in_place,
701        start_suppressed,
702        floating_pane_coordinates,
703    );
704    apply_action!(action, error_msg, env);
705}
706
707fn open_file_in_place(
708    env: &PluginEnv,
709    file_to_open: FileToOpen,
710    context: BTreeMap<String, String>,
711) {
712    let error_msg = || format!("failed to open file in plugin {}", env.name());
713    let floating = false;
714    let in_place = true;
715    let start_suppressed = false;
716    let path = env.plugin_cwd.join(file_to_open.path);
717    let cwd = file_to_open
718        .cwd
719        .map(|cwd| env.plugin_cwd.join(cwd))
720        .or_else(|| Some(env.plugin_cwd.clone()));
721
722    let action = Action::EditFile(
723        OpenFilePayload::new(path, file_to_open.line_number, cwd).with_originating_plugin(
724            OriginatingPlugin::new(env.plugin_id, env.client_id, context),
725        ),
726        None,
727        floating,
728        in_place,
729        start_suppressed,
730        None,
731    );
732    apply_action!(action, error_msg, env);
733}
734
735fn open_file_near_plugin(
736    env: &PluginEnv,
737    file_to_open: FileToOpen,
738    context: BTreeMap<String, String>,
739) {
740    let cwd = file_to_open
741        .cwd
742        .map(|cwd| env.plugin_cwd.join(cwd))
743        .or_else(|| Some(env.plugin_cwd.clone()));
744    let path = env.plugin_cwd.join(file_to_open.path);
745    let open_file_payload =
746        OpenFilePayload::new(path, file_to_open.line_number, cwd).with_originating_plugin(
747            OriginatingPlugin::new(env.plugin_id, env.client_id, context),
748        );
749    let title = format!("Editing: {}", open_file_payload.path.display());
750    let start_suppressed = false;
751    let open_file = TerminalAction::OpenFile(open_file_payload);
752    let pty_instr = PtyInstruction::SpawnTerminal(
753        Some(open_file),
754        Some(title),
755        NewPanePlacement::default(),
756        start_suppressed,
757        ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
758    );
759    let _ = env.senders.send_to_pty(pty_instr);
760}
761
762fn open_file_floating_near_plugin(
763    env: &PluginEnv,
764    file_to_open: FileToOpen,
765    floating_pane_coordinates: Option<FloatingPaneCoordinates>,
766    context: BTreeMap<String, String>,
767) {
768    let cwd = file_to_open
769        .cwd
770        .map(|cwd| env.plugin_cwd.join(cwd))
771        .or_else(|| Some(env.plugin_cwd.clone()));
772    let path = env.plugin_cwd.join(file_to_open.path);
773    let open_file_payload =
774        OpenFilePayload::new(path, file_to_open.line_number, cwd).with_originating_plugin(
775            OriginatingPlugin::new(env.plugin_id, env.client_id, context),
776        );
777    let title = format!("Editing: {}", open_file_payload.path.display());
778    let start_suppressed = false;
779    let open_file = TerminalAction::OpenFile(open_file_payload);
780    let pty_instr = PtyInstruction::SpawnTerminal(
781        Some(open_file),
782        Some(title),
783        NewPanePlacement::Floating(floating_pane_coordinates),
784        start_suppressed,
785        ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
786    );
787    let _ = env.senders.send_to_pty(pty_instr);
788}
789
790fn open_file_in_place_of_plugin(
791    env: &PluginEnv,
792    file_to_open: FileToOpen,
793    close_plugin_after_replace: bool,
794    context: BTreeMap<String, String>,
795) {
796    let cwd = file_to_open
797        .cwd
798        .map(|cwd| env.plugin_cwd.join(cwd))
799        .or_else(|| Some(env.plugin_cwd.clone()));
800    let path = env.plugin_cwd.join(file_to_open.path);
801    let open_file_payload =
802        OpenFilePayload::new(path, file_to_open.line_number, cwd).with_originating_plugin(
803            OriginatingPlugin::new(env.plugin_id, env.client_id, context),
804        );
805    let title = format!("Editing: {}", open_file_payload.path.display());
806    let open_file = TerminalAction::OpenFile(open_file_payload);
807    let pty_instr = PtyInstruction::SpawnInPlaceTerminal(
808        Some(open_file),
809        Some(title),
810        close_plugin_after_replace,
811        ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
812    );
813    let _ = env.senders.send_to_pty(pty_instr);
814}
815
816fn open_terminal(env: &PluginEnv, cwd: PathBuf) {
817    let error_msg = || format!("failed to open file in plugin {}", env.name());
818    let cwd = env.plugin_cwd.join(cwd);
819    let mut default_shell = env.default_shell.clone().unwrap_or_else(|| {
820        TerminalAction::RunCommand(RunCommand {
821            command: env.path_to_default_shell.clone(),
822            use_terminal_title: true,
823            ..Default::default()
824        })
825    });
826    default_shell.change_cwd(cwd);
827    let run_command_action: Option<RunCommandAction> = match default_shell {
828        TerminalAction::RunCommand(run_command) => Some(run_command.into()),
829        _ => None,
830    };
831    let action = Action::NewTiledPane(None, run_command_action, None);
832    apply_action!(action, error_msg, env);
833}
834
835fn open_terminal_near_plugin(env: &PluginEnv, cwd: PathBuf) {
836    let cwd = env.plugin_cwd.join(cwd);
837    let mut default_shell = env.default_shell.clone().unwrap_or_else(|| {
838        TerminalAction::RunCommand(RunCommand {
839            command: env.path_to_default_shell.clone(),
840            use_terminal_title: true,
841            ..Default::default()
842        })
843    });
844    let name = None;
845    default_shell.change_cwd(cwd);
846    let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal(
847        Some(default_shell),
848        name,
849        NewPanePlacement::Tiled(None),
850        false,
851        ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
852    ));
853}
854
855fn open_terminal_floating(
856    env: &PluginEnv,
857    cwd: PathBuf,
858    floating_pane_coordinates: Option<FloatingPaneCoordinates>,
859) {
860    let error_msg = || format!("failed to open file in plugin {}", env.name());
861    let cwd = env.plugin_cwd.join(cwd);
862    let mut default_shell = env.default_shell.clone().unwrap_or_else(|| {
863        TerminalAction::RunCommand(RunCommand {
864            command: env.path_to_default_shell.clone(),
865            use_terminal_title: true,
866            ..Default::default()
867        })
868    });
869    default_shell.change_cwd(cwd);
870    let run_command_action: Option<RunCommandAction> = match default_shell {
871        TerminalAction::RunCommand(run_command) => Some(run_command.into()),
872        _ => None,
873    };
874    let action = Action::NewFloatingPane(run_command_action, None, floating_pane_coordinates);
875    apply_action!(action, error_msg, env);
876}
877
878fn open_terminal_floating_near_plugin(
879    env: &PluginEnv,
880    cwd: PathBuf,
881    floating_pane_coordinates: Option<FloatingPaneCoordinates>,
882) {
883    let cwd = env.plugin_cwd.join(cwd);
884    let mut default_shell = env.default_shell.clone().unwrap_or_else(|| {
885        TerminalAction::RunCommand(RunCommand {
886            command: env.path_to_default_shell.clone(),
887            use_terminal_title: true,
888            ..Default::default()
889        })
890    });
891    default_shell.change_cwd(cwd);
892    let name = None;
893    let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal(
894        Some(default_shell),
895        name,
896        NewPanePlacement::Floating(floating_pane_coordinates),
897        false,
898        ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
899    ));
900}
901
902fn open_terminal_in_place(env: &PluginEnv, cwd: PathBuf) {
903    let error_msg = || format!("failed to open file in plugin {}", env.name());
904    let cwd = env.plugin_cwd.join(cwd);
905    let mut default_shell = env.default_shell.clone().unwrap_or_else(|| {
906        TerminalAction::RunCommand(RunCommand {
907            command: env.path_to_default_shell.clone(),
908            use_terminal_title: true,
909            ..Default::default()
910        })
911    });
912    default_shell.change_cwd(cwd);
913    let run_command_action: Option<RunCommandAction> = match default_shell {
914        TerminalAction::RunCommand(run_command) => Some(run_command.into()),
915        _ => None,
916    };
917    let action = Action::NewInPlacePane(run_command_action, None);
918    apply_action!(action, error_msg, env);
919}
920
921fn open_terminal_in_place_of_plugin(
922    env: &PluginEnv,
923    cwd: PathBuf,
924    close_plugin_after_replace: bool,
925) {
926    let cwd = env.plugin_cwd.join(cwd);
927    let mut default_shell = env.default_shell.clone().unwrap_or_else(|| {
928        TerminalAction::RunCommand(RunCommand {
929            command: env.path_to_default_shell.clone(),
930            use_terminal_title: true,
931            ..Default::default()
932        })
933    });
934    default_shell.change_cwd(cwd);
935    let name = None;
936    let _ = env
937        .senders
938        .send_to_pty(PtyInstruction::SpawnInPlaceTerminal(
939            Some(default_shell),
940            name,
941            close_plugin_after_replace,
942            ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
943        ));
944}
945
946fn open_command_pane_in_place_of_plugin(
947    env: &PluginEnv,
948    command_to_run: CommandToRun,
949    close_plugin_after_replace: bool,
950    context: BTreeMap<String, String>,
951) {
952    let command = command_to_run.path;
953    let cwd = command_to_run.cwd.map(|cwd| env.plugin_cwd.join(cwd));
954    let args = command_to_run.args;
955    let direction = None;
956    let hold_on_close = true;
957    let hold_on_start = false;
958    let name = None;
959    let use_terminal_title = false; // TODO: support this
960    let run_command_action = RunCommandAction {
961        command,
962        args,
963        cwd,
964        direction,
965        hold_on_close,
966        hold_on_start,
967        originating_plugin: Some(OriginatingPlugin::new(
968            env.plugin_id,
969            env.client_id,
970            context,
971        )),
972        use_terminal_title,
973    };
974    let run_cmd = TerminalAction::RunCommand(run_command_action.into());
975    let _ = env
976        .senders
977        .send_to_pty(PtyInstruction::SpawnInPlaceTerminal(
978            Some(run_cmd),
979            name,
980            close_plugin_after_replace,
981            ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
982        ));
983}
984
985fn open_command_pane(
986    env: &PluginEnv,
987    command_to_run: CommandToRun,
988    context: BTreeMap<String, String>,
989) {
990    let error_msg = || format!("failed to open command in plugin {}", env.name());
991    let command = command_to_run.path;
992    let cwd = command_to_run.cwd.map(|cwd| env.plugin_cwd.join(cwd));
993    let args = command_to_run.args;
994    let direction = None;
995    let hold_on_close = true;
996    let hold_on_start = false;
997    let name = None;
998    let use_terminal_title = false; // TODO: support this
999    let run_command_action = RunCommandAction {
1000        command,
1001        args,
1002        cwd,
1003        direction,
1004        hold_on_close,
1005        hold_on_start,
1006        originating_plugin: Some(OriginatingPlugin::new(
1007            env.plugin_id,
1008            env.client_id,
1009            context,
1010        )),
1011        use_terminal_title,
1012    };
1013    let action = Action::NewTiledPane(direction, Some(run_command_action), name);
1014    apply_action!(action, error_msg, env);
1015}
1016
1017fn open_command_pane_near_plugin(
1018    env: &PluginEnv,
1019    command_to_run: CommandToRun,
1020    context: BTreeMap<String, String>,
1021) {
1022    let command = command_to_run.path;
1023    let cwd = command_to_run.cwd.map(|cwd| env.plugin_cwd.join(cwd));
1024    let args = command_to_run.args;
1025    let direction = None;
1026    let hold_on_close = true;
1027    let hold_on_start = false;
1028    let name = None;
1029    let use_terminal_title = false; // TODO: support this
1030    let run_command_action = RunCommandAction {
1031        command,
1032        args,
1033        cwd,
1034        direction,
1035        hold_on_close,
1036        hold_on_start,
1037        originating_plugin: Some(OriginatingPlugin::new(
1038            env.plugin_id,
1039            env.client_id,
1040            context,
1041        )),
1042        use_terminal_title,
1043    };
1044    let run_cmd = TerminalAction::RunCommand(run_command_action.into());
1045    let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal(
1046        Some(run_cmd),
1047        name,
1048        NewPanePlacement::Tiled(None),
1049        false,
1050        ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
1051    ));
1052}
1053
1054fn open_command_pane_floating(
1055    env: &PluginEnv,
1056    command_to_run: CommandToRun,
1057    floating_pane_coordinates: Option<FloatingPaneCoordinates>,
1058    context: BTreeMap<String, String>,
1059) {
1060    let error_msg = || format!("failed to open command in plugin {}", env.name());
1061    let command = command_to_run.path;
1062    let cwd = command_to_run.cwd.map(|cwd| env.plugin_cwd.join(cwd));
1063    let args = command_to_run.args;
1064    let direction = None;
1065    let hold_on_close = true;
1066    let hold_on_start = false;
1067    let name = None;
1068    let use_terminal_title = false; // TODO: support this
1069    let run_command_action = RunCommandAction {
1070        command,
1071        args,
1072        cwd,
1073        direction,
1074        hold_on_close,
1075        hold_on_start,
1076        originating_plugin: Some(OriginatingPlugin::new(
1077            env.plugin_id,
1078            env.client_id,
1079            context,
1080        )),
1081        use_terminal_title,
1082    };
1083    let action = Action::NewFloatingPane(Some(run_command_action), name, floating_pane_coordinates);
1084    apply_action!(action, error_msg, env);
1085}
1086
1087fn open_command_pane_floating_near_plugin(
1088    env: &PluginEnv,
1089    command_to_run: CommandToRun,
1090    floating_pane_coordinates: Option<FloatingPaneCoordinates>,
1091    context: BTreeMap<String, String>,
1092) {
1093    let command = command_to_run.path;
1094    let cwd = command_to_run.cwd.map(|cwd| env.plugin_cwd.join(cwd));
1095    let args = command_to_run.args;
1096    let direction = None;
1097    let hold_on_close = true;
1098    let hold_on_start = false;
1099    let name = None;
1100    let use_terminal_title = false; // TODO: support this
1101    let run_command_action = RunCommandAction {
1102        command,
1103        args,
1104        cwd,
1105        direction,
1106        hold_on_close,
1107        hold_on_start,
1108        originating_plugin: Some(OriginatingPlugin::new(
1109            env.plugin_id,
1110            env.client_id,
1111            context,
1112        )),
1113        use_terminal_title,
1114    };
1115    let run_cmd = TerminalAction::RunCommand(run_command_action.into());
1116    let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal(
1117        Some(run_cmd),
1118        name,
1119        NewPanePlacement::Floating(floating_pane_coordinates),
1120        false,
1121        ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
1122    ));
1123}
1124
1125fn open_command_pane_in_place(
1126    env: &PluginEnv,
1127    command_to_run: CommandToRun,
1128    context: BTreeMap<String, String>,
1129) {
1130    let error_msg = || format!("failed to open command in plugin {}", env.name());
1131    let command = command_to_run.path;
1132    let cwd = command_to_run.cwd.map(|cwd| env.plugin_cwd.join(cwd));
1133    let args = command_to_run.args;
1134    let direction = None;
1135    let hold_on_close = true;
1136    let hold_on_start = false;
1137    let name = None;
1138    let use_terminal_title = false; // TODO: support this
1139    let run_command_action = RunCommandAction {
1140        command,
1141        args,
1142        cwd,
1143        direction,
1144        hold_on_close,
1145        hold_on_start,
1146        originating_plugin: Some(OriginatingPlugin::new(
1147            env.plugin_id,
1148            env.client_id,
1149            context,
1150        )),
1151        use_terminal_title,
1152    };
1153    let action = Action::NewInPlacePane(Some(run_command_action), name);
1154    apply_action!(action, error_msg, env);
1155}
1156
1157fn open_command_pane_background(
1158    env: &PluginEnv,
1159    command_to_run: CommandToRun,
1160    context: BTreeMap<String, String>,
1161) {
1162    let command = command_to_run.path;
1163    let cwd = command_to_run
1164        .cwd
1165        .map(|cwd| env.plugin_cwd.join(cwd))
1166        .or_else(|| Some(env.plugin_cwd.clone()));
1167    let args = command_to_run.args;
1168    let direction = None;
1169    let hold_on_close = true;
1170    let hold_on_start = false;
1171    let start_suppressed = true;
1172    let name = None;
1173    let use_terminal_title = false; // TODO: support this
1174    let run_command_action = RunCommandAction {
1175        command,
1176        args,
1177        cwd,
1178        direction,
1179        hold_on_close,
1180        hold_on_start,
1181        originating_plugin: Some(OriginatingPlugin::new(
1182            env.plugin_id,
1183            env.client_id,
1184            context,
1185        )),
1186        use_terminal_title,
1187    };
1188    let run_cmd = TerminalAction::RunCommand(run_command_action.into());
1189    let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal(
1190        Some(run_cmd),
1191        name,
1192        NewPanePlacement::default(),
1193        start_suppressed,
1194        ClientTabIndexOrPaneId::ClientId(env.client_id),
1195    ));
1196}
1197
1198fn rerun_command_pane(env: &PluginEnv, terminal_pane_id: u32) {
1199    let _ = env
1200        .senders
1201        .send_to_screen(ScreenInstruction::RerunCommandPane(terminal_pane_id));
1202}
1203
1204fn switch_tab_to(env: &PluginEnv, tab_idx: u32) {
1205    env.senders
1206        .send_to_screen(ScreenInstruction::GoToTab(tab_idx, Some(env.client_id)))
1207        .with_context(|| {
1208            format!(
1209                "failed to switch to tab {tab_idx} from plugin {}",
1210                env.name()
1211            )
1212        })
1213        .non_fatal();
1214}
1215
1216fn set_timeout(env: &PluginEnv, secs: f64) {
1217    let send_plugin_instructions = env.senders.to_plugin.clone();
1218    let update_target = Some(env.plugin_id);
1219    let client_id = env.client_id;
1220    let plugin_name = env.name();
1221    task::spawn(async move {
1222        let start_time = Instant::now();
1223        task::sleep(Duration::from_secs_f64(secs)).await;
1224        // FIXME: The way that elapsed time is being calculated here is not exact; it doesn't take into account the
1225        // time it takes an event to actually reach the plugin after it's sent to the `wasm` thread.
1226        let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64();
1227
1228        send_plugin_instructions
1229            .ok_or(anyhow!("found no sender to send plugin instruction to"))
1230            .and_then(|sender| {
1231                sender
1232                    .send(PluginInstruction::Update(vec![(
1233                        update_target,
1234                        Some(client_id),
1235                        Event::Timer(elapsed_time),
1236                    )]))
1237                    .to_anyhow()
1238            })
1239            .with_context(|| {
1240                format!(
1241                    "failed to set host timeout of {secs} s for plugin {}",
1242                    plugin_name
1243                )
1244            })
1245            .non_fatal();
1246    });
1247}
1248
1249fn exec_cmd(env: &PluginEnv, mut command_line: Vec<String>) {
1250    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!)");
1251    let err_context = || {
1252        format!(
1253            "failed to execute command on host for plugin '{}'",
1254            env.name()
1255        )
1256    };
1257    let command = command_line.remove(0);
1258
1259    // Bail out if we're forbidden to run command
1260    if !env.plugin._allow_exec_host_cmd {
1261        warn!("This plugin isn't allow to run command in host side, skip running this command: '{cmd} {args}'.",
1262        	cmd = command, args = command_line.join(" "));
1263        return;
1264    }
1265
1266    // Here, we don't wait the command to finish
1267    process::Command::new(command)
1268        .args(command_line)
1269        .spawn()
1270        .with_context(err_context)
1271        .non_fatal();
1272}
1273
1274fn run_command(
1275    env: &PluginEnv,
1276    mut command_line: Vec<String>,
1277    env_variables: BTreeMap<String, String>,
1278    cwd: PathBuf,
1279    context: BTreeMap<String, String>,
1280) {
1281    if command_line.is_empty() {
1282        log::error!("Command cannot be empty");
1283    } else {
1284        let command = command_line.remove(0);
1285        let cwd = env.plugin_cwd.join(cwd);
1286        let _ = env
1287            .senders
1288            .send_to_background_jobs(BackgroundJob::RunCommand(
1289                env.plugin_id,
1290                env.client_id,
1291                command,
1292                command_line,
1293                env_variables,
1294                cwd,
1295                context,
1296            ));
1297    }
1298}
1299
1300fn web_request(
1301    env: &PluginEnv,
1302    url: String,
1303    verb: HttpVerb,
1304    headers: BTreeMap<String, String>,
1305    body: Vec<u8>,
1306    context: BTreeMap<String, String>,
1307) {
1308    let _ = env
1309        .senders
1310        .send_to_background_jobs(BackgroundJob::WebRequest(
1311            env.plugin_id,
1312            env.client_id,
1313            url,
1314            verb,
1315            headers,
1316            body,
1317            context,
1318        ));
1319}
1320
1321fn post_message_to(env: &PluginEnv, plugin_message: PluginMessage) -> Result<()> {
1322    let worker_name = plugin_message
1323        .worker_name
1324        .ok_or(anyhow!("Worker name not specified in message to worker"))?;
1325    env.senders
1326        .send_to_plugin(PluginInstruction::PostMessagesToPluginWorker(
1327            env.plugin_id,
1328            env.client_id,
1329            worker_name,
1330            vec![(plugin_message.name, plugin_message.payload)],
1331        ))
1332}
1333
1334fn post_message_to_plugin(env: &PluginEnv, plugin_message: PluginMessage) -> Result<()> {
1335    if let Some(worker_name) = plugin_message.worker_name {
1336        return Err(anyhow!(
1337            "Worker name (\"{}\") should not be specified in message to plugin",
1338            worker_name
1339        ));
1340    }
1341    env.senders
1342        .send_to_plugin(PluginInstruction::PostMessageToPlugin(
1343            env.plugin_id,
1344            env.client_id,
1345            plugin_message.name,
1346            plugin_message.payload,
1347        ))
1348}
1349
1350fn hide_self(env: &PluginEnv) -> Result<()> {
1351    env.senders
1352        .send_to_screen(ScreenInstruction::SuppressPane(
1353            PaneId::Plugin(env.plugin_id),
1354            env.client_id,
1355        ))
1356        .with_context(|| format!("failed to hide self"))
1357}
1358
1359fn hide_pane_with_id(env: &PluginEnv, pane_id: PaneId) -> Result<()> {
1360    env.senders
1361        .send_to_screen(ScreenInstruction::SuppressPane(pane_id, env.client_id))
1362        .with_context(|| format!("failed to hide self"))
1363}
1364
1365fn show_self(env: &PluginEnv, should_float_if_hidden: bool) {
1366    let action = Action::FocusPluginPaneWithId(env.plugin_id, should_float_if_hidden);
1367    let error_msg = || format!("Failed to show self for plugin");
1368    apply_action!(action, error_msg, env);
1369}
1370
1371fn show_pane_with_id(env: &PluginEnv, pane_id: PaneId, should_float_if_hidden: bool) {
1372    let _ = env
1373        .senders
1374        .send_to_screen(ScreenInstruction::FocusPaneWithId(
1375            pane_id,
1376            should_float_if_hidden,
1377            env.client_id,
1378        ));
1379}
1380
1381fn close_self(env: &PluginEnv) {
1382    env.senders
1383        .send_to_screen(ScreenInstruction::ClosePane(
1384            PaneId::Plugin(env.plugin_id),
1385            None,
1386        ))
1387        .with_context(|| format!("failed to close self"))
1388        .non_fatal();
1389    env.senders
1390        .send_to_plugin(PluginInstruction::Unload(env.plugin_id))
1391        .with_context(|| format!("failed to close self"))
1392        .non_fatal();
1393}
1394
1395fn reconfigure(env: &PluginEnv, new_config: String, write_config_to_disk: bool) -> Result<()> {
1396    let err_context = || "Failed to reconfigure";
1397    let client_id = env.client_id;
1398    env.senders
1399        .send_to_server(ServerInstruction::Reconfigure {
1400            client_id,
1401            config: new_config,
1402            write_config_to_disk,
1403        })
1404        .with_context(err_context)?;
1405    Ok(())
1406}
1407
1408fn rebind_keys(
1409    env: &PluginEnv,
1410    keys_to_rebind: Vec<(InputMode, KeyWithModifier, Vec<Action>)>,
1411    keys_to_unbind: Vec<(InputMode, KeyWithModifier)>,
1412    write_config_to_disk: bool,
1413) -> Result<()> {
1414    let err_context = || "Failed to rebind_keys";
1415    let client_id = env.client_id;
1416    env.senders
1417        .send_to_server(ServerInstruction::RebindKeys {
1418            client_id,
1419            keys_to_rebind,
1420            keys_to_unbind,
1421            write_config_to_disk,
1422        })
1423        .with_context(err_context)?;
1424    Ok(())
1425}
1426
1427fn switch_to_mode(env: &PluginEnv, input_mode: InputMode) {
1428    let action = Action::SwitchToMode(input_mode);
1429    let error_msg = || format!("failed to switch to mode in plugin {}", env.name());
1430    apply_action!(action, error_msg, env);
1431}
1432
1433fn new_tabs_with_layout(env: &PluginEnv, raw_layout: &str) -> Result<()> {
1434    // TODO: cwd
1435    let layout = Layout::from_str(
1436        &raw_layout,
1437        format!("Layout from plugin: {}", env.name()),
1438        None,
1439        None,
1440    )
1441    .map_err(|e| anyhow!("Failed to parse layout: {:?}", e))?;
1442    apply_layout(env, layout);
1443    Ok(())
1444}
1445
1446fn new_tabs_with_layout_info(env: &PluginEnv, layout_info: LayoutInfo) -> Result<()> {
1447    // TODO: cwd
1448    let layout = Layout::from_layout_info(&env.layout_dir, layout_info)
1449        .map_err(|e| anyhow!("Failed to parse layout: {:?}", e))?;
1450    apply_layout(env, layout);
1451    Ok(())
1452}
1453
1454fn apply_layout(env: &PluginEnv, layout: Layout) {
1455    let mut tabs_to_open = vec![];
1456    let tabs = layout.tabs();
1457    let cwd = None; // TODO: add this to the plugin API
1458    if tabs.is_empty() {
1459        let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone());
1460        let swap_floating_layouts = Some(layout.swap_floating_layouts.clone());
1461        let action = Action::NewTab(
1462            layout.template.as_ref().map(|t| t.0.clone()),
1463            layout.template.map(|t| t.1).unwrap_or_default(),
1464            swap_tiled_layouts,
1465            swap_floating_layouts,
1466            None,
1467            true,
1468            cwd,
1469        );
1470        tabs_to_open.push(action);
1471    } else {
1472        let focused_tab_index = layout.focused_tab_index().unwrap_or(0);
1473        for (tab_index, (tab_name, tiled_pane_layout, floating_pane_layout)) in
1474            layout.tabs().into_iter().enumerate()
1475        {
1476            let should_focus_tab = tab_index == focused_tab_index;
1477            let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone());
1478            let swap_floating_layouts = Some(layout.swap_floating_layouts.clone());
1479            let action = Action::NewTab(
1480                Some(tiled_pane_layout),
1481                floating_pane_layout,
1482                swap_tiled_layouts,
1483                swap_floating_layouts,
1484                tab_name,
1485                should_focus_tab,
1486                cwd.clone(),
1487            );
1488            tabs_to_open.push(action);
1489        }
1490    }
1491    for action in tabs_to_open {
1492        let error_msg = || format!("Failed to create layout tab");
1493        apply_action!(action, error_msg, env);
1494    }
1495}
1496
1497fn new_tab(env: &PluginEnv, name: Option<String>, cwd: Option<String>) {
1498    let cwd = cwd.map(|c| PathBuf::from(c));
1499    let action = Action::NewTab(None, vec![], None, None, name, true, cwd);
1500    let error_msg = || format!("Failed to open new tab");
1501    apply_action!(action, error_msg, env);
1502}
1503
1504fn go_to_next_tab(env: &PluginEnv) {
1505    let action = Action::GoToNextTab;
1506    let error_msg = || format!("Failed to go to next tab");
1507    apply_action!(action, error_msg, env);
1508}
1509
1510fn go_to_previous_tab(env: &PluginEnv) {
1511    let action = Action::GoToPreviousTab;
1512    let error_msg = || format!("Failed to go to previous tab");
1513    apply_action!(action, error_msg, env);
1514}
1515
1516fn resize(env: &PluginEnv, resize: Resize) {
1517    let error_msg = || format!("failed to resize in plugin {}", env.name());
1518    let action = Action::Resize(resize, None);
1519    apply_action!(action, error_msg, env);
1520}
1521
1522fn resize_with_direction(env: &PluginEnv, resize: ResizeStrategy) {
1523    let error_msg = || format!("failed to resize in plugin {}", env.name());
1524    let action = Action::Resize(resize.resize, resize.direction);
1525    apply_action!(action, error_msg, env);
1526}
1527
1528fn focus_next_pane(env: &PluginEnv) {
1529    let action = Action::FocusNextPane;
1530    let error_msg = || format!("Failed to focus next pane");
1531    apply_action!(action, error_msg, env);
1532}
1533
1534fn focus_previous_pane(env: &PluginEnv) {
1535    let action = Action::FocusPreviousPane;
1536    let error_msg = || format!("Failed to focus previous pane");
1537    apply_action!(action, error_msg, env);
1538}
1539
1540fn move_focus(env: &PluginEnv, direction: Direction) {
1541    let error_msg = || format!("failed to move focus in plugin {}", env.name());
1542    let action = Action::MoveFocus(direction);
1543    apply_action!(action, error_msg, env);
1544}
1545
1546fn move_focus_or_tab(env: &PluginEnv, direction: Direction) {
1547    let error_msg = || format!("failed to move focus in plugin {}", env.name());
1548    let action = Action::MoveFocusOrTab(direction);
1549    apply_action!(action, error_msg, env);
1550}
1551
1552fn detach(env: &PluginEnv) {
1553    let action = Action::Detach;
1554    let error_msg = || format!("Failed to detach");
1555    apply_action!(action, error_msg, env);
1556}
1557
1558fn switch_session(
1559    env: &PluginEnv,
1560    session_name: Option<String>,
1561    tab_position: Option<usize>,
1562    pane_id: Option<(u32, bool)>,
1563    layout: Option<LayoutInfo>,
1564    cwd: Option<PathBuf>,
1565) -> Result<()> {
1566    // pane_id is (id, is_plugin)
1567    let err_context = || format!("Failed to switch session");
1568    if let Some(LayoutInfo::Stringified(stringified_layout)) = layout.as_ref() {
1569        // we verify the stringified layout here to fail early rather than when parsing it at the
1570        // session-switching phase
1571        if let Err(e) = Layout::from_kdl(&stringified_layout, None, None, None) {
1572            return Err(anyhow!("Failed to deserialize layout: {}", e));
1573        }
1574    }
1575    if session_name
1576        .as_ref()
1577        .map(|s| s.contains('/'))
1578        .unwrap_or(false)
1579    {
1580        log::error!("Session names cannot contain \'/\'");
1581    } else {
1582        let client_id = env.client_id;
1583        let tab_position = tab_position.map(|p| p + 1); // ¯\_()_/¯
1584        let connect_to_session = ConnectToSession {
1585            name: session_name,
1586            tab_position,
1587            pane_id,
1588            layout,
1589            cwd,
1590        };
1591        env.senders
1592            .send_to_server(ServerInstruction::SwitchSession(
1593                connect_to_session,
1594                client_id,
1595            ))
1596            .with_context(err_context)?;
1597    }
1598    Ok(())
1599}
1600
1601fn delete_dead_session(session_name: String) -> Result<()> {
1602    std::fs::remove_dir_all(&*ZELLIJ_SESSION_INFO_CACHE_DIR.join(&session_name))
1603        .with_context(|| format!("Failed to delete dead session: {:?}", &session_name))
1604}
1605
1606fn delete_all_dead_sessions() -> Result<()> {
1607    use std::os::unix::fs::FileTypeExt;
1608    let mut live_sessions = vec![];
1609    if let Ok(files) = std::fs::read_dir(&*ZELLIJ_SOCK_DIR) {
1610        files.for_each(|file| {
1611            if let Ok(file) = file {
1612                if let Ok(file_name) = file.file_name().into_string() {
1613                    if file.file_type().unwrap().is_socket() {
1614                        live_sessions.push(file_name);
1615                    }
1616                }
1617            }
1618        });
1619    }
1620    let dead_sessions: Vec<String> = match std::fs::read_dir(&*ZELLIJ_SESSION_INFO_CACHE_DIR) {
1621        Ok(files_in_session_info_folder) => {
1622            let files_that_are_folders = files_in_session_info_folder
1623                .filter_map(|f| f.ok().map(|f| f.path()))
1624                .filter(|f| f.is_dir());
1625            files_that_are_folders
1626                .filter_map(|folder_name| {
1627                    let session_name = folder_name.file_name()?.to_str()?.to_owned();
1628                    if live_sessions.contains(&session_name) {
1629                        // this is not a dead session...
1630                        return None;
1631                    }
1632                    Some(session_name)
1633                })
1634                .collect()
1635        },
1636        Err(e) => {
1637            log::error!("Failed to read session info cache dir: {:?}", e);
1638            vec![]
1639        },
1640    };
1641    for session in dead_sessions {
1642        delete_dead_session(session)?;
1643    }
1644    Ok(())
1645}
1646
1647fn edit_scrollback(env: &PluginEnv) {
1648    let action = Action::EditScrollback;
1649    let error_msg = || format!("Failed to edit scrollback");
1650    apply_action!(action, error_msg, env);
1651}
1652
1653fn write(env: &PluginEnv, bytes: Vec<u8>) {
1654    let error_msg = || format!("failed to write in plugin {}", env.name());
1655    let action = Action::Write(None, bytes, false);
1656    apply_action!(action, error_msg, env);
1657}
1658
1659fn write_chars(env: &PluginEnv, chars_to_write: String) {
1660    let error_msg = || format!("failed to write in plugin {}", env.name());
1661    let action = Action::WriteChars(chars_to_write);
1662    apply_action!(action, error_msg, env);
1663}
1664
1665fn toggle_tab(env: &PluginEnv) {
1666    let error_msg = || format!("Failed to toggle tab");
1667    let action = Action::ToggleTab;
1668    apply_action!(action, error_msg, env);
1669}
1670
1671fn move_pane(env: &PluginEnv) {
1672    let error_msg = || format!("failed to move pane in plugin {}", env.name());
1673    let action = Action::MovePane(None);
1674    apply_action!(action, error_msg, env);
1675}
1676
1677fn move_pane_with_direction(env: &PluginEnv, direction: Direction) {
1678    let error_msg = || format!("failed to move pane in plugin {}", env.name());
1679    let action = Action::MovePane(Some(direction));
1680    apply_action!(action, error_msg, env);
1681}
1682
1683fn clear_screen(env: &PluginEnv) {
1684    let error_msg = || format!("failed to clear screen in plugin {}", env.name());
1685    let action = Action::ClearScreen;
1686    apply_action!(action, error_msg, env);
1687}
1688fn scroll_up(env: &PluginEnv) {
1689    let error_msg = || format!("failed to scroll up in plugin {}", env.name());
1690    let action = Action::ScrollUp;
1691    apply_action!(action, error_msg, env);
1692}
1693
1694fn scroll_down(env: &PluginEnv) {
1695    let error_msg = || format!("failed to scroll down in plugin {}", env.name());
1696    let action = Action::ScrollDown;
1697    apply_action!(action, error_msg, env);
1698}
1699
1700fn scroll_to_top(env: &PluginEnv) {
1701    let error_msg = || format!("failed to scroll in plugin {}", env.name());
1702    let action = Action::ScrollToTop;
1703    apply_action!(action, error_msg, env);
1704}
1705
1706fn scroll_to_bottom(env: &PluginEnv) {
1707    let error_msg = || format!("failed to scroll in plugin {}", env.name());
1708    let action = Action::ScrollToBottom;
1709    apply_action!(action, error_msg, env);
1710}
1711
1712fn page_scroll_up(env: &PluginEnv) {
1713    let error_msg = || format!("failed to scroll in plugin {}", env.name());
1714    let action = Action::PageScrollUp;
1715    apply_action!(action, error_msg, env);
1716}
1717
1718fn page_scroll_down(env: &PluginEnv) {
1719    let error_msg = || format!("failed to scroll in plugin {}", env.name());
1720    let action = Action::PageScrollDown;
1721    apply_action!(action, error_msg, env);
1722}
1723
1724fn toggle_focus_fullscreen(env: &PluginEnv) {
1725    let error_msg = || format!("failed to toggle full screen in plugin {}", env.name());
1726    let action = Action::ToggleFocusFullscreen;
1727    apply_action!(action, error_msg, env);
1728}
1729
1730fn toggle_pane_frames(env: &PluginEnv) {
1731    let error_msg = || format!("failed to toggle full screen in plugin {}", env.name());
1732    let action = Action::TogglePaneFrames;
1733    apply_action!(action, error_msg, env);
1734}
1735
1736fn toggle_pane_embed_or_eject(env: &PluginEnv) {
1737    let error_msg = || {
1738        format!(
1739            "failed to toggle pane embed or eject in plugin {}",
1740            env.name()
1741        )
1742    };
1743    let action = Action::TogglePaneEmbedOrFloating;
1744    apply_action!(action, error_msg, env);
1745}
1746
1747fn undo_rename_pane(env: &PluginEnv) {
1748    let error_msg = || format!("failed to undo rename pane in plugin {}", env.name());
1749    let action = Action::UndoRenamePane;
1750    apply_action!(action, error_msg, env);
1751}
1752
1753fn close_focus(env: &PluginEnv) {
1754    let error_msg = || format!("failed to close focused pane in plugin {}", env.name());
1755    let action = Action::CloseFocus;
1756    apply_action!(action, error_msg, env);
1757}
1758
1759fn toggle_active_tab_sync(env: &PluginEnv) {
1760    let error_msg = || format!("failed to toggle active tab sync in plugin {}", env.name());
1761    let action = Action::ToggleActiveSyncTab;
1762    apply_action!(action, error_msg, env);
1763}
1764
1765fn close_focused_tab(env: &PluginEnv) {
1766    let error_msg = || format!("failed to close active tab in plugin {}", env.name());
1767    let action = Action::CloseTab;
1768    apply_action!(action, error_msg, env);
1769}
1770
1771fn undo_rename_tab(env: &PluginEnv) {
1772    let error_msg = || format!("failed to undo rename tab in plugin {}", env.name());
1773    let action = Action::UndoRenameTab;
1774    apply_action!(action, error_msg, env);
1775}
1776
1777fn quit_zellij(env: &PluginEnv) {
1778    let error_msg = || format!("failed to quit zellij in plugin {}", env.name());
1779    let action = Action::Quit;
1780    apply_action!(action, error_msg, env);
1781}
1782
1783fn previous_swap_layout(env: &PluginEnv) {
1784    let error_msg = || format!("failed to switch swap layout in plugin {}", env.name());
1785    let action = Action::PreviousSwapLayout;
1786    apply_action!(action, error_msg, env);
1787}
1788
1789fn next_swap_layout(env: &PluginEnv) {
1790    let error_msg = || format!("failed to switch swap layout in plugin {}", env.name());
1791    let action = Action::NextSwapLayout;
1792    apply_action!(action, error_msg, env);
1793}
1794
1795fn go_to_tab_name(env: &PluginEnv, tab_name: String) {
1796    let error_msg = || format!("failed to change tab in plugin {}", env.name());
1797    let create = false;
1798    let action = Action::GoToTabName(tab_name, create);
1799    apply_action!(action, error_msg, env);
1800}
1801
1802fn focus_or_create_tab(env: &PluginEnv, tab_name: String) {
1803    let error_msg = || format!("failed to change or create tab in plugin {}", env.name());
1804    let create = true;
1805    let action = Action::GoToTabName(tab_name, create);
1806    apply_action!(action, error_msg, env);
1807}
1808
1809fn go_to_tab(env: &PluginEnv, tab_index: u32) {
1810    let error_msg = || format!("failed to change tab focus in plugin {}", env.name());
1811    let action = Action::GoToTab(tab_index + 1);
1812    apply_action!(action, error_msg, env);
1813}
1814
1815fn start_or_reload_plugin(env: &PluginEnv, url: &str) -> Result<()> {
1816    let error_msg = || format!("failed to start or reload plugin in plugin {}", env.name());
1817    let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
1818    let run_plugin_or_alias = RunPluginOrAlias::from_url(url, &None, None, Some(cwd))
1819        .map_err(|e| anyhow!("Failed to parse plugin location: {}", e))?;
1820    let action = Action::StartOrReloadPlugin(run_plugin_or_alias);
1821    apply_action!(action, error_msg, env);
1822    Ok(())
1823}
1824
1825fn close_terminal_pane(env: &PluginEnv, terminal_pane_id: u32) {
1826    let error_msg = || format!("failed to change tab focus in plugin {}", env.name());
1827    let action = Action::CloseTerminalPane(terminal_pane_id);
1828    apply_action!(action, error_msg, env);
1829    env.senders
1830        .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(
1831            terminal_pane_id,
1832        )))
1833        .non_fatal();
1834}
1835
1836fn close_plugin_pane(env: &PluginEnv, plugin_pane_id: u32) {
1837    let error_msg = || format!("failed to change tab focus in plugin {}", env.name());
1838    let action = Action::ClosePluginPane(plugin_pane_id);
1839    apply_action!(action, error_msg, env);
1840    env.senders
1841        .send_to_plugin(PluginInstruction::Unload(plugin_pane_id))
1842        .non_fatal();
1843}
1844
1845fn focus_terminal_pane(env: &PluginEnv, terminal_pane_id: u32, should_float_if_hidden: bool) {
1846    let action = Action::FocusTerminalPaneWithId(terminal_pane_id, should_float_if_hidden);
1847    let error_msg = || format!("Failed to focus terminal pane");
1848    apply_action!(action, error_msg, env);
1849}
1850
1851fn focus_plugin_pane(env: &PluginEnv, plugin_pane_id: u32, should_float_if_hidden: bool) {
1852    let action = Action::FocusPluginPaneWithId(plugin_pane_id, should_float_if_hidden);
1853    let error_msg = || format!("Failed to focus plugin pane");
1854    apply_action!(action, error_msg, env);
1855}
1856
1857fn rename_terminal_pane(env: &PluginEnv, terminal_pane_id: u32, new_name: &str) {
1858    let error_msg = || format!("Failed to rename terminal pane");
1859    let rename_pane_action =
1860        Action::RenameTerminalPane(terminal_pane_id, new_name.as_bytes().to_vec());
1861    apply_action!(rename_pane_action, error_msg, env);
1862}
1863
1864fn rename_plugin_pane(env: &PluginEnv, plugin_pane_id: u32, new_name: &str) {
1865    let error_msg = || format!("Failed to rename plugin pane");
1866    let rename_pane_action = Action::RenamePluginPane(plugin_pane_id, new_name.as_bytes().to_vec());
1867    apply_action!(rename_pane_action, error_msg, env);
1868}
1869
1870fn rename_tab(env: &PluginEnv, tab_index: u32, new_name: &str) {
1871    let error_msg = || format!("Failed to rename tab");
1872    let rename_tab_action = Action::RenameTab(tab_index, new_name.as_bytes().to_vec());
1873    apply_action!(rename_tab_action, error_msg, env);
1874}
1875
1876fn rename_session(env: &PluginEnv, new_session_name: String) {
1877    let error_msg = || format!("failed to rename session in plugin {}", env.name());
1878    if new_session_name.contains('/') {
1879        log::error!("Session names cannot contain \'/\'");
1880    } else {
1881        let action = Action::RenameSession(new_session_name);
1882        apply_action!(action, error_msg, env);
1883    }
1884}
1885
1886fn disconnect_other_clients(env: &PluginEnv) {
1887    let _ = env
1888        .senders
1889        .send_to_server(ServerInstruction::DisconnectAllClientsExcept(env.client_id))
1890        .context("failed to send disconnect other clients instruction");
1891}
1892
1893fn kill_sessions(session_names: Vec<String>) {
1894    for session_name in session_names {
1895        let path = &*ZELLIJ_SOCK_DIR.join(&session_name);
1896        match LocalSocketStream::connect(path) {
1897            Ok(stream) => {
1898                let _ = IpcSenderWithContext::new(stream).send(ClientToServerMsg::KillSession);
1899            },
1900            Err(e) => {
1901                log::error!("Failed to kill session {}: {:?}", session_name, e);
1902            },
1903        };
1904    }
1905}
1906
1907fn watch_filesystem(env: &PluginEnv) {
1908    let _ = env
1909        .senders
1910        .to_plugin
1911        .as_ref()
1912        .map(|sender| sender.send(PluginInstruction::WatchFilesystem));
1913}
1914
1915fn dump_session_layout(env: &PluginEnv) {
1916    let _ = env
1917        .senders
1918        .to_screen
1919        .as_ref()
1920        .map(|sender| sender.send(ScreenInstruction::DumpLayoutToPlugin(env.plugin_id)));
1921}
1922
1923fn list_clients(env: &PluginEnv) {
1924    let _ = env.senders.to_screen.as_ref().map(|sender| {
1925        sender.send(ScreenInstruction::ListClientsToPlugin(
1926            env.plugin_id,
1927            env.client_id,
1928        ))
1929    });
1930}
1931
1932fn change_host_folder(env: &PluginEnv, new_host_folder: PathBuf) {
1933    let _ = env.senders.to_plugin.as_ref().map(|sender| {
1934        sender.send(PluginInstruction::ChangePluginHostDir(
1935            new_host_folder,
1936            env.plugin_id,
1937            env.client_id,
1938        ))
1939    });
1940}
1941
1942fn set_floating_pane_pinned(env: &PluginEnv, pane_id: PaneId, should_be_pinned: bool) {
1943    let _ = env.senders.to_screen.as_ref().map(|sender| {
1944        sender.send(ScreenInstruction::SetFloatingPanePinned(
1945            pane_id,
1946            should_be_pinned,
1947        ))
1948    });
1949}
1950
1951fn stack_panes(env: &PluginEnv, pane_ids: Vec<PaneId>) {
1952    let _ = env
1953        .senders
1954        .send_to_screen(ScreenInstruction::StackPanes(pane_ids, env.client_id));
1955}
1956
1957fn change_floating_panes_coordinates(
1958    env: &PluginEnv,
1959    pane_ids_and_coordinates: Vec<(PaneId, FloatingPaneCoordinates)>,
1960) {
1961    let _ = env
1962        .senders
1963        .send_to_screen(ScreenInstruction::ChangeFloatingPanesCoordinates(
1964            pane_ids_and_coordinates,
1965        ));
1966}
1967
1968fn scan_host_folder(env: &PluginEnv, folder_to_scan: PathBuf) {
1969    if !folder_to_scan.starts_with("/host") {
1970        log::error!(
1971            "Can only scan files in the /host filesystem, found: {}",
1972            folder_to_scan.display()
1973        );
1974        return;
1975    }
1976    let plugin_host_folder = env.plugin_cwd.clone();
1977    let folder_to_scan = plugin_host_folder.join(folder_to_scan.strip_prefix("/host").unwrap());
1978    match folder_to_scan.canonicalize() {
1979        Ok(folder_to_scan) => {
1980            if !folder_to_scan.starts_with(&plugin_host_folder) {
1981                log::error!(
1982                    "Can only scan files in the plugin filesystem: {}, found: {}",
1983                    plugin_host_folder.display(),
1984                    folder_to_scan.display()
1985                );
1986                return;
1987            }
1988            let reading_folder = std::fs::read_dir(&folder_to_scan);
1989            match reading_folder {
1990                Ok(reading_folder) => {
1991                    let send_plugin_instructions = env.senders.to_plugin.clone();
1992                    let update_target = Some(env.plugin_id);
1993                    let client_id = env.client_id;
1994                    thread::spawn({
1995                        move || {
1996                            let mut paths_in_folder = vec![];
1997                            for entry in reading_folder {
1998                                if let Ok(entry) = entry {
1999                                    let entry_metadata = entry.metadata().ok().map(|m| m.into());
2000                                    paths_in_folder.push((
2001                                        PathBuf::from("/host").join(
2002                                            entry.path().strip_prefix(&plugin_host_folder).unwrap(),
2003                                        ),
2004                                        entry_metadata.into(),
2005                                    ));
2006                                }
2007                            }
2008                            let _ = send_plugin_instructions
2009                                .ok_or(anyhow!("found no sender to send plugin instruction to"))
2010                                .map(|sender| {
2011                                    let _ = sender.send(PluginInstruction::Update(vec![(
2012                                        update_target,
2013                                        Some(client_id),
2014                                        Event::FileSystemUpdate(paths_in_folder),
2015                                    )]));
2016                                })
2017                                .non_fatal();
2018                        }
2019                    });
2020                },
2021                Err(e) => {
2022                    log::error!("Failed to read folder {}: {e}", folder_to_scan.display());
2023                },
2024            }
2025        },
2026        Err(e) => {
2027            log::error!(
2028                "Failed to canonicalize path {folder_to_scan:?} when scanning folder: {:?}",
2029                e
2030            );
2031        },
2032    }
2033}
2034
2035fn resize_pane_with_id(env: &PluginEnv, resize: ResizeStrategy, pane_id: PaneId) {
2036    let _ = env
2037        .senders
2038        .send_to_screen(ScreenInstruction::ResizePaneWithId(resize, pane_id));
2039}
2040
2041fn edit_scrollback_for_pane_with_id(env: &PluginEnv, pane_id: PaneId) {
2042    let _ = env
2043        .senders
2044        .send_to_screen(ScreenInstruction::EditScrollbackForPaneWithId(pane_id));
2045}
2046
2047fn write_to_pane_id(env: &PluginEnv, bytes: Vec<u8>, pane_id: PaneId) {
2048    let _ = env
2049        .senders
2050        .send_to_screen(ScreenInstruction::WriteToPaneId(bytes, pane_id));
2051}
2052
2053fn write_chars_to_pane_id(env: &PluginEnv, chars: String, pane_id: PaneId) {
2054    let bytes = chars.into_bytes();
2055    let _ = env
2056        .senders
2057        .send_to_screen(ScreenInstruction::WriteToPaneId(bytes, pane_id));
2058}
2059
2060fn move_pane_with_pane_id(env: &PluginEnv, pane_id: PaneId) {
2061    let _ = env
2062        .senders
2063        .send_to_screen(ScreenInstruction::MovePaneWithPaneId(pane_id));
2064}
2065
2066fn move_pane_with_pane_id_in_direction(env: &PluginEnv, pane_id: PaneId, direction: Direction) {
2067    let _ = env
2068        .senders
2069        .send_to_screen(ScreenInstruction::MovePaneWithPaneIdInDirection(
2070            pane_id, direction,
2071        ));
2072}
2073
2074fn clear_screen_for_pane_id(env: &PluginEnv, pane_id: PaneId) {
2075    let _ = env
2076        .senders
2077        .send_to_screen(ScreenInstruction::ClearScreenForPaneId(pane_id));
2078}
2079
2080fn scroll_up_in_pane_id(env: &PluginEnv, pane_id: PaneId) {
2081    let _ = env
2082        .senders
2083        .send_to_screen(ScreenInstruction::ScrollUpInPaneId(pane_id));
2084}
2085
2086fn scroll_down_in_pane_id(env: &PluginEnv, pane_id: PaneId) {
2087    let _ = env
2088        .senders
2089        .send_to_screen(ScreenInstruction::ScrollDownInPaneId(pane_id));
2090}
2091
2092fn scroll_to_top_in_pane_id(env: &PluginEnv, pane_id: PaneId) {
2093    let _ = env
2094        .senders
2095        .send_to_screen(ScreenInstruction::ScrollToTopInPaneId(pane_id));
2096}
2097
2098fn scroll_to_bottom_in_pane_id(env: &PluginEnv, pane_id: PaneId) {
2099    let _ = env
2100        .senders
2101        .send_to_screen(ScreenInstruction::ScrollToBottomInPaneId(pane_id));
2102}
2103
2104fn page_scroll_up_in_pane_id(env: &PluginEnv, pane_id: PaneId) {
2105    let _ = env
2106        .senders
2107        .send_to_screen(ScreenInstruction::PageScrollUpInPaneId(pane_id));
2108}
2109
2110fn page_scroll_down_in_pane_id(env: &PluginEnv, pane_id: PaneId) {
2111    let _ = env
2112        .senders
2113        .send_to_screen(ScreenInstruction::PageScrollDownInPaneId(pane_id));
2114}
2115
2116fn toggle_pane_id_fullscreen(env: &PluginEnv, pane_id: PaneId) {
2117    let _ = env
2118        .senders
2119        .send_to_screen(ScreenInstruction::TogglePaneIdFullscreen(pane_id));
2120}
2121
2122fn toggle_pane_embed_or_eject_for_pane_id(env: &PluginEnv, pane_id: PaneId) {
2123    let _ = env
2124        .senders
2125        .send_to_screen(ScreenInstruction::TogglePaneEmbedOrEjectForPaneId(pane_id));
2126}
2127
2128fn close_tab_with_index(env: &PluginEnv, tab_index: usize) {
2129    let _ = env
2130        .senders
2131        .send_to_screen(ScreenInstruction::CloseTabWithIndex(tab_index));
2132}
2133
2134fn break_panes_to_new_tab(
2135    env: &PluginEnv,
2136    pane_ids: Vec<PaneId>,
2137    new_tab_name: Option<String>,
2138    should_change_focus_to_new_tab: bool,
2139) {
2140    let default_shell = env.default_shell.clone().or_else(|| {
2141        Some(TerminalAction::RunCommand(RunCommand {
2142            command: env.path_to_default_shell.clone(),
2143            use_terminal_title: true,
2144            ..Default::default()
2145        }))
2146    });
2147    let _ = env
2148        .senders
2149        .send_to_screen(ScreenInstruction::BreakPanesToNewTab {
2150            pane_ids,
2151            default_shell,
2152            new_tab_name,
2153            should_change_focus_to_new_tab,
2154            client_id: env.client_id,
2155        });
2156}
2157
2158fn break_panes_to_tab_with_index(
2159    env: &PluginEnv,
2160    pane_ids: Vec<PaneId>,
2161    should_change_focus_to_new_tab: bool,
2162    tab_index: usize,
2163) {
2164    let _ = env
2165        .senders
2166        .send_to_screen(ScreenInstruction::BreakPanesToTabWithIndex {
2167            pane_ids,
2168            tab_index,
2169            client_id: env.client_id,
2170            should_change_focus_to_new_tab,
2171        });
2172}
2173
2174fn reload_plugin(env: &PluginEnv, plugin_id: u32) {
2175    let _ = env
2176        .senders
2177        .send_to_plugin(PluginInstruction::ReloadPluginWithId(plugin_id));
2178}
2179
2180fn load_new_plugin(
2181    env: &PluginEnv,
2182    url: String,
2183    config: BTreeMap<String, String>,
2184    load_in_background: bool,
2185    skip_plugin_cache: bool,
2186) {
2187    let url = if &url == "zellij:OWN_URL" {
2188        env.plugin.location.display()
2189    } else {
2190        url
2191    };
2192    if load_in_background {
2193        match RunPluginOrAlias::from_url(&url, &Some(config), None, Some(env.plugin_cwd.clone())) {
2194            Ok(run_plugin_or_alias) => {
2195                let _ = env
2196                    .senders
2197                    .send_to_plugin(PluginInstruction::LoadBackgroundPlugin(
2198                        run_plugin_or_alias,
2199                        env.client_id,
2200                    ));
2201            },
2202            Err(e) => {
2203                log::error!("Failed to load new plugin: {:?}", e);
2204            },
2205        }
2206    } else {
2207        let should_float = Some(true);
2208        let should_be_open_in_place = false;
2209        let pane_title = None;
2210        let tab_index = None;
2211        let pane_id_to_replace = None;
2212        let client_id = env.client_id;
2213        let size = Default::default();
2214        let cwd = Some(env.plugin_cwd.clone());
2215        let skip_cache = skip_plugin_cache;
2216        match RunPluginOrAlias::from_url(&url, &Some(config), None, Some(env.plugin_cwd.clone())) {
2217            Ok(run_plugin_or_alias) => {
2218                let _ = env.senders.send_to_plugin(PluginInstruction::Load(
2219                    should_float,
2220                    should_be_open_in_place,
2221                    pane_title,
2222                    run_plugin_or_alias,
2223                    tab_index,
2224                    pane_id_to_replace,
2225                    client_id,
2226                    size,
2227                    cwd,
2228                    None,
2229                    skip_cache,
2230                    None,
2231                    None,
2232                ));
2233            },
2234            Err(e) => {
2235                log::error!("Failed to load new plugin: {:?}", e);
2236            },
2237        }
2238    }
2239}
2240
2241fn start_web_server(env: &PluginEnv) {
2242    let _ = env
2243        .senders
2244        .send_to_server(ServerInstruction::StartWebServer(env.client_id));
2245}
2246
2247fn stop_web_server(_env: &PluginEnv) {
2248    #[cfg(feature = "web_server_capability")]
2249    let _ = shutdown_all_webserver_instances();
2250    #[cfg(not(feature = "web_server_capability"))]
2251    log::error!("This instance of Zellij was compiled without web server capabilities");
2252}
2253
2254fn query_web_server_status(env: &PluginEnv) {
2255    let _ = env
2256        .senders
2257        .send_to_background_jobs(BackgroundJob::QueryZellijWebServerStatus);
2258}
2259
2260fn share_current_session(env: &PluginEnv) {
2261    let _ = env
2262        .senders
2263        .send_to_server(ServerInstruction::ShareCurrentSession(env.client_id));
2264}
2265
2266fn stop_sharing_current_session(env: &PluginEnv) {
2267    let _ = env
2268        .senders
2269        .send_to_server(ServerInstruction::StopSharingCurrentSession(env.client_id));
2270}
2271
2272fn group_and_ungroup_panes(
2273    env: &PluginEnv,
2274    panes_to_group: Vec<PaneId>,
2275    panes_to_ungroup: Vec<PaneId>,
2276    for_all_clients: bool,
2277) {
2278    let _ = env
2279        .senders
2280        .send_to_screen(ScreenInstruction::GroupAndUngroupPanes(
2281            panes_to_group,
2282            panes_to_ungroup,
2283            for_all_clients,
2284            env.client_id,
2285        ));
2286}
2287
2288fn highlight_and_unhighlight_panes(
2289    env: &PluginEnv,
2290    panes_to_highlight: Vec<PaneId>,
2291    panes_to_unhighlight: Vec<PaneId>,
2292) {
2293    let _ = env
2294        .senders
2295        .send_to_screen(ScreenInstruction::HighlightAndUnhighlightPanes(
2296            panes_to_highlight,
2297            panes_to_unhighlight,
2298            env.client_id,
2299        ));
2300}
2301
2302fn close_multiple_panes(env: &PluginEnv, pane_ids: Vec<PaneId>) {
2303    for pane_id in pane_ids {
2304        match pane_id {
2305            PaneId::Terminal(terminal_pane_id) => {
2306                close_terminal_pane(env, terminal_pane_id);
2307            },
2308            PaneId::Plugin(plugin_pane_id) => {
2309                close_plugin_pane(env, plugin_pane_id);
2310            },
2311        }
2312    }
2313}
2314
2315fn float_multiple_panes(env: &PluginEnv, pane_ids: Vec<PaneId>) {
2316    let _ = env
2317        .senders
2318        .send_to_screen(ScreenInstruction::FloatMultiplePanes(
2319            pane_ids,
2320            env.client_id,
2321        ));
2322}
2323
2324fn embed_multiple_panes(env: &PluginEnv, pane_ids: Vec<PaneId>) {
2325    let _ = env
2326        .senders
2327        .send_to_screen(ScreenInstruction::EmbedMultiplePanes(
2328            pane_ids,
2329            env.client_id,
2330        ));
2331}
2332
2333#[cfg(feature = "web_server_capability")]
2334fn generate_web_login_token(env: &PluginEnv, token_label: Option<String>) {
2335    let serialized = match create_token(token_label) {
2336        Ok((token, token_label)) => CreateTokenResponse {
2337            token: Some(token),
2338            token_label: Some(token_label),
2339            error: None,
2340        },
2341        Err(e) => CreateTokenResponse {
2342            token: None,
2343            token_label: None,
2344            error: Some(e.to_string()),
2345        },
2346    };
2347    let _ = wasi_write_object(env, &serialized.encode_to_vec());
2348}
2349
2350#[cfg(not(feature = "web_server_capability"))]
2351fn generate_web_login_token(env: &PluginEnv, _token_label: Option<String>) {
2352    log::error!("This version of Zellij was compiled without the web server capabilities!");
2353    let empty_vec: Vec<&str> = vec![];
2354    let _ = wasi_write_object(env, &empty_vec);
2355}
2356
2357#[cfg(feature = "web_server_capability")]
2358fn revoke_web_login_token(env: &PluginEnv, token_label: String) {
2359    let serialized = match revoke_token(&token_label) {
2360        Ok(true) => RevokeTokenResponse {
2361            successfully_revoked: true,
2362            error: None,
2363        },
2364        Ok(false) => RevokeTokenResponse {
2365            successfully_revoked: false,
2366            error: Some(format!("Token with label {} not found", token_label)),
2367        },
2368        Err(e) => RevokeTokenResponse {
2369            successfully_revoked: false,
2370            error: Some(e.to_string()),
2371        },
2372    };
2373    let _ = wasi_write_object(env, &serialized.encode_to_vec());
2374}
2375
2376#[cfg(not(feature = "web_server_capability"))]
2377fn revoke_web_login_token(env: &PluginEnv, _token_label: String) {
2378    log::error!("This version of Zellij was compiled without the web server capabilities!");
2379    let empty_vec: Vec<&str> = vec![];
2380    let _ = wasi_write_object(env, &empty_vec);
2381}
2382
2383#[cfg(feature = "web_server_capability")]
2384fn revoke_all_web_login_tokens(env: &PluginEnv) {
2385    let serialized = match revoke_all_tokens() {
2386        Ok(_) => RevokeAllWebTokensResponse {
2387            successfully_revoked: true,
2388            error: None,
2389        },
2390        Err(e) => RevokeAllWebTokensResponse {
2391            successfully_revoked: false,
2392            error: Some(e.to_string()),
2393        },
2394    };
2395    let _ = wasi_write_object(env, &serialized.encode_to_vec());
2396}
2397
2398#[cfg(not(feature = "web_server_capability"))]
2399fn revoke_all_web_login_tokens(env: &PluginEnv) {
2400    log::error!("This version of Zellij was compiled without the web server capabilities!");
2401    let empty_vec: Vec<&str> = vec![];
2402    let _ = wasi_write_object(env, &empty_vec);
2403}
2404
2405#[cfg(feature = "web_server_capability")]
2406fn rename_web_login_token(env: &PluginEnv, old_name: String, new_name: String) {
2407    let serialized = match rename_token(&old_name, &new_name) {
2408        Ok(_) => RenameWebTokenResponse {
2409            successfully_renamed: true,
2410            error: None,
2411        },
2412        Err(e) => RenameWebTokenResponse {
2413            successfully_renamed: false,
2414            error: Some(e.to_string()),
2415        },
2416    };
2417    let _ = wasi_write_object(env, &serialized.encode_to_vec());
2418}
2419
2420#[cfg(not(feature = "web_server_capability"))]
2421fn rename_web_login_token(env: &PluginEnv, _old_name: String, _new_name: String) {
2422    log::error!("This version of Zellij was compiled without the web server capabilities!");
2423    let empty_vec: Vec<&str> = vec![];
2424    let _ = wasi_write_object(env, &empty_vec);
2425}
2426
2427#[cfg(feature = "web_server_capability")]
2428fn list_web_login_tokens(env: &PluginEnv) {
2429    let serialized = match list_tokens() {
2430        Ok(token_list) => ListTokensResponse {
2431            tokens: token_list.iter().map(|t| t.name.clone()).collect(),
2432            creation_times: token_list.iter().map(|t| t.created_at.clone()).collect(),
2433            error: None,
2434        },
2435        Err(e) => ListTokensResponse {
2436            tokens: vec![],
2437            creation_times: vec![],
2438            error: Some(e.to_string()),
2439        },
2440    };
2441    let _ = wasi_write_object(env, &serialized.encode_to_vec());
2442}
2443
2444#[cfg(not(feature = "web_server_capability"))]
2445fn list_web_login_tokens(env: &PluginEnv) {
2446    log::error!("This version of Zellij was compiled without the web server capabilities!");
2447    let empty_vec: Vec<&str> = vec![];
2448    let _ = wasi_write_object(env, &empty_vec);
2449}
2450
2451fn set_self_mouse_selection_support(env: &PluginEnv, selection_support: bool) {
2452    env.senders
2453        .send_to_screen(ScreenInstruction::SetMouseSelectionSupport(
2454            PaneId::Plugin(env.plugin_id),
2455            selection_support,
2456        ))
2457        .with_context(|| {
2458            format!(
2459                "failed to set plugin {} selectable from plugin {}",
2460                selection_support,
2461                env.name()
2462            )
2463        })
2464        .non_fatal();
2465}
2466
2467fn intercept_key_presses(env: &mut PluginEnv) {
2468    env.intercepting_key_presses = true;
2469    let _ = env
2470        .senders
2471        .send_to_screen(ScreenInstruction::InterceptKeyPresses(
2472            env.plugin_id,
2473            env.client_id,
2474        ));
2475}
2476
2477fn clear_key_presses_intercepts(env: &mut PluginEnv) {
2478    env.intercepting_key_presses = false;
2479    let _ = env
2480        .senders
2481        .send_to_screen(ScreenInstruction::ClearKeyPressesIntercepts(env.client_id));
2482}
2483
2484fn replace_pane_with_existing_pane(
2485    env: &mut PluginEnv,
2486    pane_to_replace: PaneId,
2487    existing_pane: PaneId,
2488) {
2489    let _ = env
2490        .senders
2491        .send_to_screen(ScreenInstruction::ReplacePaneWithExistingPane(
2492            pane_to_replace,
2493            existing_pane,
2494        ));
2495}
2496
2497// Custom panic handler for plugins.
2498//
2499// This is called when a panic occurs in a plugin. Since most panics will likely originate in the
2500// code trying to deserialize an `Event` upon a plugin state update, we read some panic message,
2501// formatted as string from the plugin.
2502fn report_panic(env: &PluginEnv, msg: &str) {
2503    log::error!("PANIC IN PLUGIN!\n\r{}", msg);
2504    handle_plugin_crash(env.plugin_id, msg.to_owned(), env.senders.clone());
2505}
2506
2507// Helper Functions ---------------------------------------------------------------------------------------------------
2508
2509pub fn wasi_read_string(plugin_env: &PluginEnv) -> Result<String> {
2510    let err_context = || format!("failed to read string from WASI env");
2511
2512    let mut buf = vec![];
2513    plugin_env
2514        .stdout_pipe
2515        .lock()
2516        .unwrap()
2517        .read_to_end(&mut buf)
2518        .map_err(anyError::new)
2519        .with_context(err_context)?;
2520    let buf = String::from_utf8_lossy(&buf);
2521
2522    // https://stackoverflow.com/questions/66450942/in-rust-is-there-a-way-to-make-literal-newlines-in-r-using-windows-c
2523    Ok(buf.replace("\n", "\n\r"))
2524}
2525
2526pub fn wasi_write_string(plugin_env: &PluginEnv, buf: &str) -> Result<()> {
2527    let mut stdin = plugin_env.stdin_pipe.lock().unwrap();
2528    writeln!(stdin, "{}\r", buf)
2529        .map_err(anyError::new)
2530        .with_context(|| format!("failed to write string to WASI env"))
2531}
2532
2533pub fn wasi_write_object(plugin_env: &PluginEnv, object: &(impl Serialize + ?Sized)) -> Result<()> {
2534    serde_json::to_string(&object)
2535        .map_err(anyError::new)
2536        .and_then(|string| wasi_write_string(plugin_env, &string))
2537        .with_context(|| format!("failed to serialize object for WASI env"))
2538}
2539
2540pub fn wasi_read_bytes(plugin_env: &PluginEnv) -> Result<Vec<u8>> {
2541    wasi_read_string(plugin_env)
2542        .and_then(|string| serde_json::from_str(&string).map_err(anyError::new))
2543        .with_context(|| format!("failed to deserialize object from WASI env"))
2544}
2545
2546// TODO: move to permissions?
2547fn check_command_permission(
2548    plugin_env: &PluginEnv,
2549    command: &PluginCommand,
2550) -> (PermissionStatus, Option<PermissionType>) {
2551    if plugin_env.plugin.is_builtin() {
2552        // built-in plugins can do all the things because they're part of the application and
2553        // there's no use to deny them anything
2554        return (PermissionStatus::Granted, None);
2555    }
2556    let permission = match command {
2557        PluginCommand::OpenFile(..)
2558        | PluginCommand::OpenFileFloating(..)
2559        | PluginCommand::OpenFileNearPlugin(..)
2560        | PluginCommand::OpenFileFloatingNearPlugin(..)
2561        | PluginCommand::OpenFileInPlaceOfPlugin(..)
2562        | PluginCommand::OpenFileInPlace(..) => PermissionType::OpenFiles,
2563        PluginCommand::OpenTerminal(..)
2564        | PluginCommand::OpenTerminalNearPlugin(..)
2565        | PluginCommand::StartOrReloadPlugin(..)
2566        | PluginCommand::OpenTerminalFloating(..)
2567        | PluginCommand::OpenTerminalFloatingNearPlugin(..)
2568        | PluginCommand::OpenTerminalInPlace(..)
2569        | PluginCommand::OpenTerminalInPlaceOfPlugin(..) => PermissionType::OpenTerminalsOrPlugins,
2570        PluginCommand::OpenCommandPane(..)
2571        | PluginCommand::OpenCommandPaneNearPlugin(..)
2572        | PluginCommand::OpenCommandPaneFloating(..)
2573        | PluginCommand::OpenCommandPaneFloatingNearPlugin(..)
2574        | PluginCommand::OpenCommandPaneInPlace(..)
2575        | PluginCommand::OpenCommandPaneInPlaceOfPlugin(..)
2576        | PluginCommand::OpenCommandPaneBackground(..)
2577        | PluginCommand::RunCommand(..)
2578        | PluginCommand::ExecCmd(..) => PermissionType::RunCommands,
2579        PluginCommand::WebRequest(..) => PermissionType::WebAccess,
2580        PluginCommand::Write(..)
2581        | PluginCommand::WriteChars(..)
2582        | PluginCommand::WriteToPaneId(..)
2583        | PluginCommand::WriteCharsToPaneId(..) => PermissionType::WriteToStdin,
2584        PluginCommand::SwitchTabTo(..)
2585        | PluginCommand::SwitchToMode(..)
2586        | PluginCommand::NewTabsWithLayout(..)
2587        | PluginCommand::NewTabsWithLayoutInfo(..)
2588        | PluginCommand::NewTab { .. }
2589        | PluginCommand::GoToNextTab
2590        | PluginCommand::GoToPreviousTab
2591        | PluginCommand::Resize(..)
2592        | PluginCommand::ResizeWithDirection(..)
2593        | PluginCommand::FocusNextPane
2594        | PluginCommand::MoveFocus(..)
2595        | PluginCommand::MoveFocusOrTab(..)
2596        | PluginCommand::Detach
2597        | PluginCommand::EditScrollback
2598        | PluginCommand::EditScrollbackForPaneWithId(..)
2599        | PluginCommand::ToggleTab
2600        | PluginCommand::MovePane
2601        | PluginCommand::MovePaneWithDirection(..)
2602        | PluginCommand::MovePaneWithPaneId(..)
2603        | PluginCommand::MovePaneWithPaneIdInDirection(..)
2604        | PluginCommand::ClearScreen
2605        | PluginCommand::ClearScreenForPaneId(..)
2606        | PluginCommand::ScrollUp
2607        | PluginCommand::ScrollUpInPaneId(..)
2608        | PluginCommand::ScrollDown
2609        | PluginCommand::ScrollDownInPaneId(..)
2610        | PluginCommand::ScrollToTop
2611        | PluginCommand::ScrollToTopInPaneId(..)
2612        | PluginCommand::ScrollToBottom
2613        | PluginCommand::ScrollToBottomInPaneId(..)
2614        | PluginCommand::PageScrollUp
2615        | PluginCommand::PageScrollUpInPaneId(..)
2616        | PluginCommand::PageScrollDown
2617        | PluginCommand::PageScrollDownInPaneId(..)
2618        | PluginCommand::ToggleFocusFullscreen
2619        | PluginCommand::TogglePaneIdFullscreen(..)
2620        | PluginCommand::TogglePaneFrames
2621        | PluginCommand::TogglePaneEmbedOrEject
2622        | PluginCommand::TogglePaneEmbedOrEjectForPaneId(..)
2623        | PluginCommand::UndoRenamePane
2624        | PluginCommand::CloseFocus
2625        | PluginCommand::ToggleActiveTabSync
2626        | PluginCommand::CloseFocusedTab
2627        | PluginCommand::UndoRenameTab
2628        | PluginCommand::QuitZellij
2629        | PluginCommand::PreviousSwapLayout
2630        | PluginCommand::NextSwapLayout
2631        | PluginCommand::GoToTabName(..)
2632        | PluginCommand::FocusOrCreateTab(..)
2633        | PluginCommand::GoToTab(..)
2634        | PluginCommand::CloseTerminalPane(..)
2635        | PluginCommand::ClosePluginPane(..)
2636        | PluginCommand::FocusTerminalPane(..)
2637        | PluginCommand::FocusPluginPane(..)
2638        | PluginCommand::RenameTerminalPane(..)
2639        | PluginCommand::RenamePluginPane(..)
2640        | PluginCommand::SwitchSession(..)
2641        | PluginCommand::DeleteDeadSession(..)
2642        | PluginCommand::DeleteAllDeadSessions
2643        | PluginCommand::RenameSession(..)
2644        | PluginCommand::RenameTab(..)
2645        | PluginCommand::DisconnectOtherClients
2646        | PluginCommand::ShowPaneWithId(..)
2647        | PluginCommand::HidePaneWithId(..)
2648        | PluginCommand::RerunCommandPane(..)
2649        | PluginCommand::ResizePaneIdWithDirection(..)
2650        | PluginCommand::CloseTabWithIndex(..)
2651        | PluginCommand::BreakPanesToNewTab(..)
2652        | PluginCommand::BreakPanesToTabWithIndex(..)
2653        | PluginCommand::ReloadPlugin(..)
2654        | PluginCommand::LoadNewPlugin { .. }
2655        | PluginCommand::SetFloatingPanePinned(..)
2656        | PluginCommand::StackPanes(..)
2657        | PluginCommand::ChangeFloatingPanesCoordinates(..)
2658        | PluginCommand::GroupAndUngroupPanes(..)
2659        | PluginCommand::HighlightAndUnhighlightPanes(..)
2660        | PluginCommand::CloseMultiplePanes(..)
2661        | PluginCommand::FloatMultiplePanes(..)
2662        | PluginCommand::EmbedMultiplePanes(..)
2663        | PluginCommand::ReplacePaneWithExistingPane(..)
2664        | PluginCommand::KillSessions(..) => PermissionType::ChangeApplicationState,
2665        PluginCommand::UnblockCliPipeInput(..)
2666        | PluginCommand::BlockCliPipeInput(..)
2667        | PluginCommand::CliPipeOutput(..) => PermissionType::ReadCliPipes,
2668        PluginCommand::MessageToPlugin(..) => PermissionType::MessageAndLaunchOtherPlugins,
2669        PluginCommand::ListClients | PluginCommand::DumpSessionLayout => {
2670            PermissionType::ReadApplicationState
2671        },
2672        PluginCommand::RebindKeys { .. } | PluginCommand::Reconfigure(..) => {
2673            PermissionType::Reconfigure
2674        },
2675        PluginCommand::ChangeHostFolder(..) => PermissionType::FullHdAccess,
2676        PluginCommand::ShareCurrentSession
2677        | PluginCommand::StopSharingCurrentSession
2678        | PluginCommand::StopWebServer
2679        | PluginCommand::QueryWebServerStatus
2680        | PluginCommand::GenerateWebLoginToken(..)
2681        | PluginCommand::RevokeWebLoginToken(..)
2682        | PluginCommand::RevokeAllWebLoginTokens
2683        | PluginCommand::RenameWebLoginToken(..)
2684        | PluginCommand::ListWebLoginTokens
2685        | PluginCommand::StartWebServer => PermissionType::StartWebServer,
2686        PluginCommand::InterceptKeyPresses | PluginCommand::ClearKeyPressesIntercepts => {
2687            PermissionType::InterceptInput
2688        },
2689        _ => return (PermissionStatus::Granted, None),
2690    };
2691
2692    if let Some(permissions) = plugin_env.permissions.lock().unwrap().as_ref() {
2693        if permissions.contains(&permission) {
2694            return (PermissionStatus::Granted, None);
2695        }
2696    }
2697
2698    (PermissionStatus::Denied, Some(permission))
2699}