use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::io::prelude::*; use zellij_tile::prelude::*; // This is a fixture plugin used only for tests in Zellij // it is not (and should not!) be included in the mainline executable // it's included here for convenience so that it will be built by the CI #[derive(Default)] struct State { received_events: Vec, received_payload: Option, configuration: BTreeMap, message_to_plugin_payload: Option, } #[derive(Default, Serialize, Deserialize)] struct TestWorker { number_of_messages_received: usize, } impl<'de> ZellijWorker<'de> for TestWorker { fn on_message(&mut self, message: String, payload: String) { if message == "ping" { self.number_of_messages_received += 1; post_message_to_plugin(PluginMessage { worker_name: None, name: "pong".into(), payload: format!( "{}, received {} messages", payload, self.number_of_messages_received ), }); } } } #[cfg(target_family = "wasm")] register_plugin!(State); #[cfg(target_family = "wasm")] register_worker!(TestWorker, test_worker, TEST_WORKER); #[cfg(target_family = "wasm")] impl ZellijPlugin for State { fn load(&mut self, configuration: BTreeMap) { request_permission(&[ PermissionType::ChangeApplicationState, PermissionType::ReadApplicationState, PermissionType::ReadApplicationState, PermissionType::ChangeApplicationState, PermissionType::OpenFiles, PermissionType::RunCommands, PermissionType::OpenTerminalsOrPlugins, PermissionType::WriteToStdin, PermissionType::WebAccess, PermissionType::ReadCliPipes, PermissionType::MessageAndLaunchOtherPlugins, PermissionType::Reconfigure, ]); self.configuration = configuration; subscribe(&[ EventType::InputReceived, EventType::Key, EventType::SystemClipboardFailure, EventType::CustomMessage, EventType::FileSystemCreate, EventType::FileSystemUpdate, EventType::FileSystemDelete, ]); watch_filesystem(); } fn update(&mut self, event: Event) -> bool { match &event { Event::Key(key) => match key.bare_key { BareKey::Char('a') if key.has_no_modifiers() => { switch_to_input_mode(&InputMode::Tab); }, BareKey::Char('b') if key.has_no_modifiers() => { new_tabs_with_layout( "layout { tab { pane pane } tab split_direction=\"vertical\" { pane pane } }", ); }, BareKey::Char('c') if key.has_no_modifiers() => new_tab(), BareKey::Char('d') if key.has_no_modifiers() => go_to_next_tab(), BareKey::Char('e') if key.has_no_modifiers() => go_to_previous_tab(), BareKey::Char('f') if key.has_no_modifiers() => { let resize = Resize::Increase; resize_focused_pane(resize) }, BareKey::Char('g') if key.has_no_modifiers() => { let resize = Resize::Increase; let direction = Direction::Left; resize_focused_pane_with_direction(resize, direction); }, BareKey::Char('h') if key.has_no_modifiers() => focus_next_pane(), BareKey::Char('i') if key.has_no_modifiers() => focus_previous_pane(), BareKey::Char('j') if key.has_no_modifiers() => { let direction = Direction::Left; move_focus(direction) }, BareKey::Char('k') if key.has_no_modifiers() => { let direction = Direction::Left; move_focus_or_tab(direction) }, BareKey::Char('l') if key.has_no_modifiers() => detach(), BareKey::Char('m') if key.has_no_modifiers() => edit_scrollback(), BareKey::Char('n') if key.has_no_modifiers() => { let bytes = vec![102, 111, 111]; write(bytes) }, BareKey::Char('o') if key.has_no_modifiers() => { let chars = "foo"; write_chars(chars); }, BareKey::Char('p') if key.has_no_modifiers() => toggle_tab(), BareKey::Char('q') if key.has_no_modifiers() => move_pane(), BareKey::Char('r') if key.has_no_modifiers() => { let direction = Direction::Left; move_pane_with_direction(direction) }, BareKey::Char('s') if key.has_no_modifiers() => clear_screen(), BareKey::Char('t') if key.has_no_modifiers() => scroll_up(), BareKey::Char('u') if key.has_no_modifiers() => scroll_down(), BareKey::Char('v') if key.has_no_modifiers() => scroll_to_top(), BareKey::Char('w') if key.has_no_modifiers() => scroll_to_bottom(), BareKey::Char('x') if key.has_no_modifiers() => page_scroll_up(), BareKey::Char('y') if key.has_no_modifiers() => page_scroll_down(), BareKey::Char('z') if key.has_no_modifiers() => toggle_focus_fullscreen(), BareKey::Char('1') if key.has_no_modifiers() => toggle_pane_frames(), BareKey::Char('2') if key.has_no_modifiers() => toggle_pane_embed_or_eject(), BareKey::Char('3') if key.has_no_modifiers() => undo_rename_pane(), BareKey::Char('4') if key.has_no_modifiers() => close_focus(), BareKey::Char('5') if key.has_no_modifiers() => toggle_active_tab_sync(), BareKey::Char('6') if key.has_no_modifiers() => close_focused_tab(), BareKey::Char('7') if key.has_no_modifiers() => undo_rename_tab(), BareKey::Char('8') if key.has_no_modifiers() => quit_zellij(), BareKey::Char('a') if key.has_modifiers(&[KeyModifier::Ctrl]) => { previous_swap_layout() }, BareKey::Char('b') if key.has_modifiers(&[KeyModifier::Ctrl]) => next_swap_layout(), BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let tab_name = "my tab name"; go_to_tab_name(tab_name) }, BareKey::Char('d') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let tab_name = "my tab name"; focus_or_create_tab(tab_name) }, BareKey::Char('e') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let tab_index = 2; go_to_tab(tab_index) }, BareKey::Char('f') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let plugin_url = "file:/path/to/my/plugin.wasm"; start_or_reload_plugin(plugin_url) }, BareKey::Char('g') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_file( FileToOpen { path: std::path::PathBuf::from("/path/to/my/file.rs"), ..Default::default() }, BTreeMap::new(), ); }, BareKey::Char('h') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_file_floating( FileToOpen { path: std::path::PathBuf::from("/path/to/my/file.rs"), ..Default::default() }, None, BTreeMap::new(), ); }, BareKey::Char('i') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_file( FileToOpen { path: std::path::PathBuf::from("/path/to/my/file.rs"), line_number: Some(42), ..Default::default() }, BTreeMap::new(), ); }, BareKey::Char('j') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_file_floating( FileToOpen { path: std::path::PathBuf::from("/path/to/my/file.rs"), line_number: Some(42), ..Default::default() }, None, BTreeMap::new(), ); }, BareKey::Char('k') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_terminal(std::path::PathBuf::from("/path/to/my/file.rs").as_path()); }, BareKey::Char('l') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_terminal_floating( std::path::PathBuf::from("/path/to/my/file.rs").as_path(), None, ); }, BareKey::Char('m') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_command_pane( CommandToRun { path: std::path::PathBuf::from("/path/to/my/file.rs"), args: vec!["arg1".to_owned(), "arg2".to_owned()], ..Default::default() }, BTreeMap::new(), ); }, BareKey::Char('n') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_command_pane_floating( CommandToRun { path: std::path::PathBuf::from("/path/to/my/file.rs"), args: vec!["arg1".to_owned(), "arg2".to_owned()], ..Default::default() }, None, BTreeMap::new(), ); }, BareKey::Char('o') if key.has_modifiers(&[KeyModifier::Ctrl]) => { switch_tab_to(1); }, BareKey::Char('p') if key.has_modifiers(&[KeyModifier::Ctrl]) => { hide_self(); }, BareKey::Char('q') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let should_float_if_hidden = false; show_self(should_float_if_hidden); }, BareKey::Char('r') if key.has_modifiers(&[KeyModifier::Ctrl]) => { close_terminal_pane(1); }, BareKey::Char('s') if key.has_modifiers(&[KeyModifier::Ctrl]) => { close_plugin_pane(1); }, BareKey::Char('t') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let should_float_if_hidden = false; focus_terminal_pane(1, should_float_if_hidden); }, BareKey::Char('u') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let should_float_if_hidden = false; focus_plugin_pane(1, should_float_if_hidden); }, BareKey::Char('v') if key.has_modifiers(&[KeyModifier::Ctrl]) => { rename_terminal_pane(1, "new terminal_pane_name"); }, BareKey::Char('w') if key.has_modifiers(&[KeyModifier::Ctrl]) => { rename_plugin_pane(1, "new plugin_pane_name"); }, BareKey::Char('x') if key.has_modifiers(&[KeyModifier::Ctrl]) => { rename_tab(1, "new tab name"); }, BareKey::Char('z') if key.has_modifiers(&[KeyModifier::Ctrl]) => { go_to_tab_name(&format!("{:?}", self.configuration)); }, BareKey::Char('1') if key.has_modifiers(&[KeyModifier::Ctrl]) => { request_permission(&[PermissionType::ReadApplicationState]); }, BareKey::Char('2') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let mut context = BTreeMap::new(); context.insert("user_key_1".to_owned(), "user_value_1".to_owned()); run_command(&["ls", "-l"], context); }, BareKey::Char('3') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let mut context = BTreeMap::new(); context.insert("user_key_2".to_owned(), "user_value_2".to_owned()); let mut env_vars = BTreeMap::new(); env_vars.insert("VAR1".to_owned(), "some_value".to_owned()); run_command_with_env_variables_and_cwd( &["ls", "-l"], env_vars, std::path::PathBuf::from("/some/custom/folder"), context, ); }, BareKey::Char('4') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let mut headers = BTreeMap::new(); let mut context = BTreeMap::new(); let body = vec![1, 2, 3]; headers.insert("header1".to_owned(), "value1".to_owned()); headers.insert("header2".to_owned(), "value2".to_owned()); context.insert("user_key_1".to_owned(), "user_value1".to_owned()); context.insert("user_key_2".to_owned(), "user_value2".to_owned()); web_request( "https://example.com/foo?arg1=val1&arg2=val2", HttpVerb::Post, headers, body, context, ); }, BareKey::Char('5') if key.has_modifiers(&[KeyModifier::Ctrl]) => { switch_session(Some("my_new_session")); }, BareKey::Char('6') if key.has_modifiers(&[KeyModifier::Ctrl]) => { disconnect_other_clients() }, BareKey::Char('7') if key.has_modifiers(&[KeyModifier::Ctrl]) => { switch_session_with_layout( Some("my_other_new_session"), LayoutInfo::BuiltIn("compact".to_owned()), None, ); }, BareKey::Char('8') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let mut file = std::fs::File::create("/host/hi-from-plugin.txt").unwrap(); file.write_all(b"Hi there!").unwrap(); }, BareKey::Char('9') if key.has_modifiers(&[KeyModifier::Ctrl]) => { switch_session_with_layout( Some("my_other_new_session_with_cwd"), LayoutInfo::BuiltIn("compact".to_owned()), Some(std::path::PathBuf::from("/tmp")), ); }, BareKey::Char('0') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let write_to_disk = true; reconfigure( " keybinds { locked { bind \"a\" { NewTab; } } } " .to_owned(), write_to_disk, ); }, BareKey::Char('a') if key.has_modifiers(&[KeyModifier::Alt]) => { hide_pane_with_id(PaneId::Terminal(1)); }, BareKey::Char('b') if key.has_modifiers(&[KeyModifier::Alt]) => { show_pane_with_id(PaneId::Terminal(1), true); }, BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Alt]) => { open_command_pane_background( CommandToRun { path: std::path::PathBuf::from("/path/to/my/file.rs"), args: vec!["arg1".to_owned(), "arg2".to_owned()], ..Default::default() }, BTreeMap::new(), ); }, BareKey::Char('d') if key.has_modifiers(&[KeyModifier::Alt]) => { rerun_command_pane(1); }, BareKey::Char('e') if key.has_modifiers(&[KeyModifier::Alt]) => { resize_pane_with_id( ResizeStrategy::new(Resize::Increase, Some(Direction::Left)), PaneId::Terminal(2), ); }, BareKey::Char('f') if key.has_modifiers(&[KeyModifier::Alt]) => { edit_scrollback_for_pane_with_id(PaneId::Terminal(2)); }, BareKey::Char('g') if key.has_modifiers(&[KeyModifier::Alt]) => { write_to_pane_id(vec![102, 111, 111], PaneId::Terminal(2)); }, BareKey::Char('h') if key.has_modifiers(&[KeyModifier::Alt]) => { write_chars_to_pane_id("foo\n", PaneId::Terminal(2)); }, BareKey::Char('i') if key.has_modifiers(&[KeyModifier::Alt]) => { move_pane_with_pane_id(PaneId::Terminal(2)); }, BareKey::Char('j') if key.has_modifiers(&[KeyModifier::Alt]) => { move_pane_with_pane_id_in_direction(PaneId::Terminal(2), Direction::Left); }, BareKey::Char('k') if key.has_modifiers(&[KeyModifier::Alt]) => { clear_screen_for_pane_id(PaneId::Terminal(2)); }, BareKey::Char('l') if key.has_modifiers(&[KeyModifier::Alt]) => { scroll_up_in_pane_id(PaneId::Terminal(2)); }, BareKey::Char('m') if key.has_modifiers(&[KeyModifier::Alt]) => { scroll_down_in_pane_id(PaneId::Terminal(2)); }, BareKey::Char('n') if key.has_modifiers(&[KeyModifier::Alt]) => { scroll_to_top_in_pane_id(PaneId::Terminal(2)); }, BareKey::Char('o') if key.has_modifiers(&[KeyModifier::Alt]) => { scroll_to_bottom_in_pane_id(PaneId::Terminal(2)); }, BareKey::Char('p') if key.has_modifiers(&[KeyModifier::Alt]) => { page_scroll_up_in_pane_id(PaneId::Terminal(2)); }, BareKey::Char('q') if key.has_modifiers(&[KeyModifier::Alt]) => { page_scroll_down_in_pane_id(PaneId::Terminal(2)); }, BareKey::Char('r') if key.has_modifiers(&[KeyModifier::Alt]) => { toggle_pane_id_fullscreen(PaneId::Terminal(2)); }, BareKey::Char('s') if key.has_modifiers(&[KeyModifier::Alt]) => { toggle_pane_embed_or_eject_for_pane_id(PaneId::Terminal(2)); }, BareKey::Char('t') if key.has_modifiers(&[KeyModifier::Alt]) => { close_tab_with_index(2); }, BareKey::Char('u') if key.has_modifiers(&[KeyModifier::Alt]) => { let should_change_focus_to_new_tab = true; break_panes_to_new_tab( &[PaneId::Terminal(1), PaneId::Plugin(2)], Some("new_tab_name".to_owned()), should_change_focus_to_new_tab, ); }, BareKey::Char('v') if key.has_modifiers(&[KeyModifier::Alt]) => { let should_change_focus_to_target_tab = true; break_panes_to_tab_with_index( &[PaneId::Terminal(1), PaneId::Plugin(2)], 2, should_change_focus_to_target_tab, ); }, BareKey::Char('w') if key.has_modifiers(&[KeyModifier::Alt]) => { reload_plugin_with_id(0); }, BareKey::Char('x') if key.has_modifiers(&[KeyModifier::Alt]) => { let config = BTreeMap::new(); let load_in_background = true; let skip_plugin_cache = true; load_new_plugin( "zellij:OWN_URL", config, load_in_background, skip_plugin_cache, ) }, _ => {}, }, Event::CustomMessage(message, payload) => { if message == "pong" { self.received_payload = Some(payload.clone()); } }, Event::SystemClipboardFailure => { // this is just to trigger the worker message post_message_to(PluginMessage { worker_name: Some("test".into()), name: "ping".into(), payload: "gimme_back_my_payload".into(), }); }, _ => {}, } let should_render = true; self.received_events.push(event); should_render } fn pipe(&mut self, pipe_message: PipeMessage) -> bool { let input_pipe_id = match pipe_message.source { PipeSource::Cli(id) => id.clone(), PipeSource::Plugin(id) => format!("{}", id), PipeSource::Keybind => format!("keybind"), }; let name = pipe_message.name; let payload = pipe_message.payload; if name == "message_name" && payload == Some("message_payload".to_owned()) { unblock_cli_pipe_input(&input_pipe_id); } else if name == "message_name_block" { block_cli_pipe_input(&input_pipe_id); } else if name == "pipe_output" { cli_pipe_output(&name, "this_is_my_output"); } else if name == "pipe_message_to_plugin" { pipe_message_to_plugin( MessageToPlugin::new("message_to_plugin").with_payload("my_cool_payload"), ); } else if name == "message_to_plugin" { self.message_to_plugin_payload = payload.clone(); } let should_render = true; should_render } fn render(&mut self, rows: usize, cols: usize) { if let Some(payload) = self.received_payload.as_ref() { println!("Payload from worker: {:?}", payload); } else if let Some(payload) = self.message_to_plugin_payload.take() { println!("Payload from self: {:?}", payload); } else { println!( "Rows: {:?}, Cols: {:?}, Received events: {:?}", rows, cols, self.received_events ); } } }