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 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; 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; 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; 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; 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; 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; 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; 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 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 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 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 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 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; 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 let err_context = || format!("Failed to switch session");
1568 if let Some(LayoutInfo::Stringified(stringified_layout)) = layout.as_ref() {
1569 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); 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 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
2497fn 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
2507pub 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 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
2546fn check_command_permission(
2548 plugin_env: &PluginEnv,
2549 command: &PluginCommand,
2550) -> (PermissionStatus, Option<PermissionType>) {
2551 if plugin_env.plugin.is_builtin() {
2552 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}