diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-03-20 17:28:36 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-20 17:28:36 +0100 |
| commit | 4ba41c085ab9207ed31e72a1ba331bff2bebed99 (patch) | |
| tree | af832d3e2b1c4577525f0276bda83fc9a12612b3 | |
| parent | adjust badges location in readme (diff) | |
| download | sl0ppij-4ba41c085ab9207ed31e72a1ba331bff2bebed99.tar.gz sl0ppij-4ba41c085ab9207ed31e72a1ba331bff2bebed99.tar.xz sl0ppij-4ba41c085ab9207ed31e72a1ba331bff2bebed99.zip | |
Release notes and fixes (#4879)
* release notes
* fix some link issues
* rustfmt
* fix click wording
| -rw-r--r-- | default-plugins/about/src/active_component.rs | 17 | ||||
| -rw-r--r-- | default-plugins/about/src/pages.rs | 380 | ||||
| -rw-r--r-- | default-plugins/link/src/main.rs | 15 | ||||
| -rw-r--r-- | zellij-server/src/panes/grid.rs | 29 | ||||
| -rw-r--r-- | zellij-server/src/plugins/zellij_exports.rs | 80 | ||||
| -rw-r--r-- | zellij-tile/src/shim.rs | 39 | ||||
| -rw-r--r-- | zellij-utils/assets/prost/api.plugin_command.rs | 25 | ||||
| -rw-r--r-- | zellij-utils/src/data.rs | 7 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/plugin_command.proto | 13 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/plugin_command.rs | 68 |
10 files changed, 537 insertions, 136 deletions
diff --git a/default-plugins/about/src/active_component.rs b/default-plugins/about/src/active_component.rs index 6e6c716a..7041ca5a 100644 --- a/default-plugins/about/src/active_component.rs +++ b/default-plugins/about/src/active_component.rs @@ -55,6 +55,16 @@ impl ActiveComponent { run_command(&[&executable.borrow(), &link], Default::default()); None }, + Some(ClickAction::LaunchPlugin(plugin_url)) => { + open_plugin_pane_floating( + &plugin_url, + Default::default(), + None, + Default::default(), + ); + self.left_click_action = Some(ClickAction::LaunchPlugin(plugin_url)); + None + }, None => None, } } @@ -125,6 +135,7 @@ impl ComponentCoordinates { pub enum ClickAction { ChangePage(Box<dyn FnOnce() -> Page>), OpenLink(String, Rc<RefCell<String>>), // (destination, executable) + LaunchPlugin(String), // plugin URL (e.g. "zellij:share") } impl std::fmt::Debug for ClickAction { @@ -134,6 +145,9 @@ impl std::fmt::Debug for ClickAction { ClickAction::OpenLink(destination, executable) => { write!(f, "OpenLink: {}, {:?}", destination, executable) }, + ClickAction::LaunchPlugin(url) => { + write!(f, "LaunchPlugin: {}", url) + }, } } } @@ -148,4 +162,7 @@ impl ClickAction { pub fn new_open_link(destination: String, executable: Rc<RefCell<String>>) -> Self { ClickAction::OpenLink(destination, executable) } + pub fn new_launch_plugin(plugin_url: String) -> Self { + ClickAction::LaunchPlugin(plugin_url) + } } diff --git a/default-plugins/about/src/pages.rs b/default-plugins/about/src/pages.rs index a1107da3..849e7c26 100644 --- a/default-plugins/about/src/pages.rs +++ b/default-plugins/about/src/pages.rs @@ -27,51 +27,70 @@ impl Page { .with_title(main_screen_title(zellij_version.clone(), is_release_notes)) .with_bulletin_list(BulletinList::new(whats_new_title()).with_items(vec![ ActiveComponent::new(TextOrCustomRender::Text(main_menu_item( - "Web Client", + "Windows Support", ))) .with_hover(TextOrCustomRender::Text( - main_menu_item("Web Client").selected(), + main_menu_item("Windows Support").selected(), + )) + .with_left_click_action(ClickAction::new_change_page({ + move || Page::new_windows_support() + })), + ActiveComponent::new(TextOrCustomRender::Text(main_menu_item( + "Remote Sessions", + ))) + .with_hover(TextOrCustomRender::Text( + main_menu_item("Remote Sessions").selected(), )) .with_left_click_action(ClickAction::new_change_page({ let link_executable = link_executable.clone(); - move || Page::new_web_client(link_executable.clone()) + move || Page::new_remote_sessions(link_executable.clone()) })), ActiveComponent::new(TextOrCustomRender::Text(main_menu_item( - "Multiple Pane Select", + "Read-Only Session Sharing", ))) .with_hover(TextOrCustomRender::Text( - main_menu_item("Multiple Pane Select").selected(), + main_menu_item("Read-Only Session Sharing").selected(), )) - .with_left_click_action(ClickAction::new_change_page(move || { - Page::new_multiple_select() + .with_left_click_action(ClickAction::new_change_page({ + let link_executable = link_executable.clone(); + move || Page::new_read_only_sharing(link_executable.clone()) })), ActiveComponent::new(TextOrCustomRender::Text(main_menu_item( - "Key Tooltips for the compact-bar", + "CLI Automation", ))) .with_hover(TextOrCustomRender::Text( - main_menu_item("Key Tooltips for the compact-bar").selected(), + main_menu_item("CLI Automation").selected(), )) .with_left_click_action(ClickAction::new_change_page({ let link_executable = link_executable.clone(); - move || Page::new_key_tooltips_for_compact_bar(link_executable.clone()) + move || Page::new_cli_automation(link_executable.clone()) })), ActiveComponent::new(TextOrCustomRender::Text(main_menu_item( - "Stack Keybinding", + "Mouse Resize", ))) .with_hover(TextOrCustomRender::Text( - main_menu_item("Stack Keybinding").selected(), + main_menu_item("Mouse Resize").selected(), )) .with_left_click_action(ClickAction::new_change_page(move || { - Page::new_stack_keybinding() + Page::new_mouse_resize() })), ActiveComponent::new(TextOrCustomRender::Text(main_menu_item( - "Performance Improvements", + "Click-to-Open File Paths", ))) .with_hover(TextOrCustomRender::Text( - main_menu_item("Performance Improvements").selected(), + main_menu_item("Click-to-Open File Paths").selected(), )) .with_left_click_action(ClickAction::new_change_page({ - move || Page::new_performance_improvements() + move || Page::new_click_to_open() + })), + ActiveComponent::new(TextOrCustomRender::Text(main_menu_item( + "Layout Manager", + ))) + .with_hover(TextOrCustomRender::Text( + main_menu_item("Layout Manager").selected(), + )) + .with_left_click_action(ClickAction::new_change_page({ + move || Page::new_layout_manager() })), ])) .with_paragraph(vec![ComponentLine::new(vec![ @@ -113,110 +132,183 @@ impl Page { }) }) } - pub fn new_web_client(link_executable: Rc<RefCell<String>>) -> Page { + fn new_windows_support() -> Page { Page::new() - .with_title(Text::new("Web Client").color_range(0, ..)) + .with_title(Text::new("Windows Support").color_range(0, ..)) .with_paragraph(vec![ - ComponentLine::new(vec![ - // ActiveComponent::new(TextOrCustomRender::Text(Text::new("This version includes a new resizing algorithm that helps better manage panes"))), - ActiveComponent::new(TextOrCustomRender::Text(Text::new("This version includes a web client, allowing you to share sessions in the browser."))), - ]), - ]) - .with_bulletin_list(BulletinList::new(Text::new("The web client:").color_range(2, ..)) - .with_items(vec![ - ActiveComponent::new(TextOrCustomRender::Text( - Text::new("Allows you to bookmark sessions") - .color_substring(3, "bookmark sessions") - )), - ActiveComponent::new(TextOrCustomRender::Text( - Text::new("Includes built-in authentication") - )), - ActiveComponent::new(TextOrCustomRender::Text( - Text::new("Can be used as a daily-driver, making your terminal emulator optional") - )), - ActiveComponent::new(TextOrCustomRender::Text( - Text::new("Is completely opt-in") - )), - ]) - ) - .with_paragraph(vec![ - ComponentLine::new(vec![ - ActiveComponent::new(TextOrCustomRender::Text( - Text::new("For more details, see: ") - .color_range(2, ..) - )), - ActiveComponent::new(TextOrCustomRender::Text(Text::new("https://zellij.dev/tutorials/web-client"))) - .with_hover(TextOrCustomRender::CustomRender(Box::new(web_client_screencast_link_selected), Box::new(web_client_screencast_link_selected_len))) - .with_left_click_action(ClickAction::new_open_link("https://zellij.dev/tutorials/web-client".to_owned(), link_executable.clone())) - ]) + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Zellij now runs natively on Windows."), + ))]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Windows users can now enjoy the same workspace management, plugin ecosystem"), + ))]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("and multiplayer capabilities that have been available on Linux and macOS."), + ))]), ]) - .with_help(Box::new(|hovering_over_link, menu_item_is_selected| esc_go_back_plus_link_hover(hovering_over_link, menu_item_is_selected))) + .with_help(Box::new(|_hovering_over_link, _menu_item_is_selected| { + esc_to_go_back_help() + })) } - fn new_multiple_select() -> Page { + pub fn new_remote_sessions(link_executable: Rc<RefCell<String>>) -> Page { Page::new() - .with_title(Text::new("Multiple Pane Select").color_range(0, ..)) + .with_title(Text::new("Remote Sessions").color_range(0, ..)) .with_paragraph(vec![ ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( - Text::new("This version adds the ability to perform bulk operations on panes"), + Text::new("Attach to remote Zellij sessions over HTTPS, directly from the terminal."), ))]), ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( - Text::new("eg. close, make floating, break to a new tab, etc."), + Text::new("The remote session needs to be running the Zellij web client."), + ))]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Zellij will attach to it exactly as a browser would, through the same interface."), ))]), ]) .with_bulletin_list( - BulletinList::new( - Text::new(format!("To select multiple panes: ")).color_range(2, ..), - ) - .with_items(vec![ - ActiveComponent::new(TextOrCustomRender::Text( - Text::new(format!("Alt <left-click> them")) - .color_substring(3, "Alt <left-click>"), - )), - ActiveComponent::new(TextOrCustomRender::Text( - Text::new(format!("Toggle with Alt p")).color_substring(3, "Alt p"), - )), - ]), + BulletinList::new(Text::new("Try it:").color_range(2, ..)) + .with_items(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Run the Zellij web server on one machine") + .color_substring(3, "Zellij web server"), + )) + .with_hover(TextOrCustomRender::Text( + Text::new("Run the Zellij web server on one machine") + .color_substring(3, "Zellij web server") + .selected(), + )) + .with_left_click_action(ClickAction::new_launch_plugin( + "zellij:share".to_owned(), + )), + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("From another: zellij attach https://<ip>/<session-name>") + .color_substring(3, "zellij attach") + .color_substring(2, "https://<ip>/<session-name>"), + )), + ]), ) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Learn more about the web client: ").color_range(2, ..), + )), + ActiveComponent::new(TextOrCustomRender::Text(Text::new( + "https://zellij.dev/tutorials/web-client/", + ))) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(web_client_link_selected), + Box::new(web_client_link_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://zellij.dev/tutorials/web-client/".to_owned(), + link_executable.clone(), + )), + ])]) + .with_help(Box::new(|hovering_over_link, menu_item_is_selected| { + esc_go_back_plus_link_hover(hovering_over_link, menu_item_is_selected) + })) + } + fn new_read_only_sharing(link_executable: Rc<RefCell<String>>) -> Page { + Page::new() + .with_title(Text::new("Read-Only Session Sharing").color_range(0, ..)) .with_paragraph(vec![ ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( - Text::new("To disable this behavior (and the associated hover effects)"), + Text::new("Sessions can now be shared in read-only mode."), + ))]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new( + "Useful for demonstrations, teaching, monitoring and pair programming", + ) + .color_substring(2, "demonstrations") + .color_substring(2, "teaching") + .color_substring(2, "monitoring") + .color_substring(2, "pair programming"), ))]), ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( - Text::new(format!("add advanced_mouse_actions false to the config.")) - .color_substring(3, "advanced_mouse_actions false"), + Text::new("where one participant should observe without interfering."), ))]), ]) - .with_help(Box::new(|_hovering_over_link, _menu_item_is_selected| { - esc_to_go_back_help() + .with_paragraph(vec![ + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Create a read-only web token with:") + .color_substring(2, "read-only web token"), + ))]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("zellij web --create-read-only-token").color_range(3, ..), + ))]), + ]) + .with_paragraph(vec![ComponentLine::new(vec![ActiveComponent::new( + TextOrCustomRender::Text(Text::new( + "Share the token for view-only access without risk of unintended input.", + )), + )])]) + .with_paragraph(vec![ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Learn more: ").color_range(2, ..), + )), + ActiveComponent::new(TextOrCustomRender::Text(Text::new( + "https://zellij.dev/tutorials/web-client/", + ))) + .with_hover(TextOrCustomRender::CustomRender( + Box::new(web_client_link_selected), + Box::new(web_client_link_selected_len), + )) + .with_left_click_action(ClickAction::new_open_link( + "https://zellij.dev/tutorials/web-client/".to_owned(), + link_executable.clone(), + )), + ])]) + .with_help(Box::new(|hovering_over_link, menu_item_is_selected| { + esc_go_back_plus_link_hover(hovering_over_link, menu_item_is_selected) })) } - fn new_key_tooltips_for_compact_bar(link_executable: Rc<RefCell<String>>) -> Page { + fn new_cli_automation(link_executable: Rc<RefCell<String>>) -> Page { Page::new() - .with_title(Text::new("Key Tooltips for the compact-bar").color_range(0, ..)) + .with_title(Text::new("CLI Automation").color_range(0, ..)) .with_paragraph(vec![ ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( - Text::new( - "Starting this version, it's possible to add toggle-able key tooltips", - ) - .color_range(3, 37..=58), + Text::new("This release significantly expands the CLI's control surface,"), ))]), ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( - Text::new("when using the compact-bar.").color_substring(3, "compact-bar"), + Text::new("enabling the building of powerful workspace automations."), ))]), ]) + .with_bulletin_list( + BulletinList::new(Text::new("New and expanded capabilities:").color_range(2, ..)) + .with_items(vec![ + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("list-panes, list-tabs, dump-screen, dump-layout with --json output") + .color_substring(3, "list-panes") + .color_substring(3, "list-tabs") + .color_substring(3, "dump-screen") + .color_substring(3, "dump-layout") + .color_substring(3, "--json"), + )), + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("zellij run optionally blocks until success/failure") + .color_substring(3, "zellij run"), + )), + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("zellij subscribe can stream pane scrollback in real time") + .color_substring(3, "zellij subscribe"), + )), + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("zellij send-keys/paste can send human readable keys to other panes or sessions") + .color_substring(3, "zellij send-keys/paste"), + )), + ]), + ) .with_paragraph(vec![ComponentLine::new(vec![ ActiveComponent::new(TextOrCustomRender::Text( - Text::new("For more information: ").color_range(2, ..), + Text::new("Learn more: ").color_range(2, ..), )), ActiveComponent::new(TextOrCustomRender::Text(Text::new( - "https://zellij.dev/documentation/faq.html", + "https://zellij.dev/documentation/controlling-zellij-through-cli.html", ))) .with_hover(TextOrCustomRender::CustomRender( - Box::new(compact_bar_link_selected), - Box::new(compact_bar_link_selected_len), + Box::new(cli_automation_link_selected), + Box::new(cli_automation_link_selected_len), )) .with_left_click_action(ClickAction::new_open_link( - "https://zellij.dev/documentation/faq.html".to_owned(), + "https://zellij.dev/documentation/controlling-zellij-through-cli.html".to_owned(), link_executable.clone(), )), ])]) @@ -224,44 +316,107 @@ impl Page { esc_go_back_plus_link_hover(hovering_over_link, menu_item_is_selected) })) } - fn new_stack_keybinding() -> Page { + fn new_mouse_resize() -> Page { Page::new() - .with_title(Text::new("New Stack Keybinding").color_range(0, ..)) + .with_title(Text::new("Mouse Resize").color_range(0, ..)) .with_paragraph(vec![ ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( - Text::new("It's now possible to open a stacked pane directly on top of the current pane").color_substring(2, "stacked pane"), + Text::new("Panes can now be resized by dragging their borders with the mouse."), + ))]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Tiled panes can be resized with or without Ctrl held down.") + .color_substring(3, "Ctrl"), + ))]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Floating panes require Ctrl+drag to resize.") + .color_substring(3, "Ctrl+drag"), ))]), ]) + .with_paragraph(vec![ComponentLine::new(vec![ActiveComponent::new( + TextOrCustomRender::Text( + Text::new("Try it: Ctrl+drag on the borders of this pane.") + .color_substring(2, "Try it:") + .color_substring(3, "Ctrl+drag"), + ), + )])]) + .with_help(Box::new(|_hovering_over_link, _menu_item_is_selected| { + esc_to_go_back_help() + })) + } + fn new_click_to_open() -> Page { + Page::new() + .with_title(Text::new("Click-to-Open File Paths").color_range(0, ..)) .with_paragraph(vec![ - ComponentLine::new(vec![ - ActiveComponent::new(TextOrCustomRender::Text( - Text::new("By default: Ctrl p + s").color_substring(3, "Ctrl p").color_substring(3, " s"), - )), - ]), - ComponentLine::new(vec![ - ActiveComponent::new(TextOrCustomRender::Text( - Text::new("In unlock first: Ctrl g + p + s").color_substring(3, "Ctrl g").color_substring(3, " p").color_substring(3, " s"), - )), - ]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Zellij now detects file paths in the terminal viewport."), + ))]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Alt-Click on a file path to open it.") + .color_substring(3, "Alt-Click"), + ))]), + ]) + .with_paragraph(vec![ + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("Useful for navigating compiler errors, grep results,") + .color_substring(2, "compiler errors") + .color_substring(2, "grep results"), + ))]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("log files, or any output containing file paths.") + .color_substring(2, "log files"), + ))]), ]) .with_paragraph(vec![ ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( - Text::new("To add to an existing config, see the release notes.") + Text::new("Plugins can also highlight arbitrary text in the viewport,"), + ))]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("opening possibilities for custom link handlers") + .color_substring(3, "custom link handlers"), + ))]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new("and interactive overlays.") + .color_substring(3, "interactive overlays"), ))]), ]) .with_help(Box::new(|_hovering_over_link, _menu_item_is_selected| { esc_to_go_back_help() })) } - fn new_performance_improvements() -> Page { + fn new_layout_manager() -> Page { Page::new() - .with_title(Text::new("Performance Improvements").color_range(0, ..)) + .with_title(Text::new("Layout Manager").color_range(0, ..)) + .with_paragraph(vec![ + ComponentLine::new(vec![ + ActiveComponent::new(TextOrCustomRender::Text(Text::new("A new "))), + ActiveComponent::new(TextOrCustomRender::Text( + Text::new("layout-manager interface").color_range(3, ..), + )) + .with_hover(TextOrCustomRender::Text( + Text::new("layout-manager interface") + .color_range(3, ..) + .selected(), + )) + .with_left_click_action(ClickAction::new_launch_plugin( + "zellij:layout-manager".to_owned(), + )), + ActiveComponent::new(TextOrCustomRender::Text(Text::new( + " allows overriding layouts at runtime.", + ))), + ]), + ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( + Text::new( + "Workspaces can be reconfigured dynamically without restarting sessions.", + ), + ))]), + ]) .with_paragraph(vec![ ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( - Text::new("This version adds a debounced asynchronous render mechanism"), + Text::new("Access it through the session menu, or run:") + .color_substring(2, "session menu"), ))]), ComponentLine::new(vec![ActiveComponent::new(TextOrCustomRender::Text( - Text::new("making rendering much smoother across the whole application."), + Text::new("zellij plugin -- zellij:layout-manager").color_range(3, ..), ))]), ]) .with_help(Box::new(|_hovering_over_link, _menu_item_is_selected| { @@ -597,29 +752,30 @@ fn sponsors_link_text_selected_len() -> usize { 34 } -fn web_client_screencast_link_selected(x: usize, y: usize) -> usize { +fn cli_automation_link_selected(x: usize, y: usize) -> usize { print!( - "\u{1b}[{};{}H\u{1b}[m\u{1b}[1;4mhttps://zellij.dev/tutorials/web-client", + "\u{1b}[{};{}H\u{1b}[m\u{1b}[1;4mhttps://zellij.dev/documentation/controlling-zellij-through-cli.html", y + 1, x + 1 ); - 39 + 68 } -fn web_client_screencast_link_selected_len() -> usize { - 39 +fn cli_automation_link_selected_len() -> usize { + 68 } -fn compact_bar_link_selected(x: usize, y: usize) -> usize { +fn web_client_link_selected(x: usize, y: usize) -> usize { print!( - "\u{1b}[{};{}H\u{1b}[m\u{1b}[1;4mhttps://zellij.dev/documentation/faq.html", + "\u{1b}[{};{}H\u{1b}[m\u{1b}[1;4mhttps://zellij.dev/tutorials/web-client/", y + 1, x + 1 ); - 41 + 40 } -fn compact_bar_link_selected_len() -> usize { - 41 + +fn web_client_link_selected_len() -> usize { + 40 } // Text components diff --git a/default-plugins/link/src/main.rs b/default-plugins/link/src/main.rs index f2127d44..f27076b5 100644 --- a/default-plugins/link/src/main.rs +++ b/default-plugins/link/src/main.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::path::{Path, PathBuf}; use zellij_tile::prelude::*; -const FILE_PATH_REGEX: &str = r#"(?:(?:\./|\.\./|/)[A-Za-z0-9_./\-+@%,#=~!\$\{\}\[\]]+|~/[A-Za-z0-9_./\-+@%,#=~!\$\{\}\[\]]+|\$\{?[A-Za-z_][A-Za-z0-9_]*\}?/[A-Za-z0-9_./\-+@%,#=~!\$\{\}\[\]]+|[a-zA-Z0-9_][a-zA-Z0-9_.\-]*/[A-Za-z0-9_./\-+@%,#=~!\$\{\}\[\]]+)(?::\d+(?::\d+)?)?"#; +const FILE_PATH_REGEX: &str = r#"(?:^|\s)((?:(?:\./|\.\./|/)[A-Za-z0-9_./\-+@%,#=~!\$\{\}\[\]]+|~/[A-Za-z0-9_./\-+@%,#=~!\$\{\}\[\]]+|\$\{?[A-Za-z_][A-Za-z0-9_]*\}?/[A-Za-z0-9_./\-+@%,#=~!\$\{\}\[\]]+)(?::\d+(?::\d+)?)?)(?:\s|$)"#; const CWD_CONTEXT_KEY: &str = "cwd"; @@ -187,7 +187,11 @@ impl State { // Directory-entry patterns for the pane's current CWD if let Some(entries) = self.pane_dir_entries.get(&pane_id) { for entry_name in entries { - let pattern = regex_escape(entry_name); + let path_chars = r#"[A-Za-z0-9_./\-+@%,#=~!\$\{\}\[\]]"#; + let pattern = format!( + "(?:^|\\s)({}(?:/{path_chars}+)?(?::\\d+(?::\\d+)?)?)(?:\\s|$)", + regex_escape(entry_name), + ); highlights.push(RegexHighlight { pattern, style: HighlightStyle::None, @@ -222,6 +226,10 @@ impl State { /// Scan a directory for first-level file and folder names. /// Returns an empty vec on any error. +/// Maximum number of directory entries to scan. Directories with more entries +/// than this are skipped entirely to avoid excessive regex pattern count. +const MAX_DIR_ENTRIES: usize = 500; + fn scan_directory(path: &Path) -> Vec<String> { let mut entries = Vec::new(); let read_dir = match std::fs::read_dir(path) { @@ -232,6 +240,9 @@ fn scan_directory(path: &Path) -> Vec<String> { if let Ok(entry) = entry { if let Some(name) = entry.file_name().to_str() { entries.push(name.to_owned()); + if entries.len() > MAX_DIR_ENTRIES { + return Vec::new(); + } } } } diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index 0f0e3fb4..07752542 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -458,6 +458,15 @@ fn match_to_selection( Some((sel, start_row, start_col, end_row, end_col)) } +/// Extract the effective match from a set of captures. +/// If capture group 1 exists, it is used (allowing patterns to include +/// context such as surrounding whitespace in the full match while +/// highlighting only the content in group 1). Otherwise the full match +/// (group 0) is returned. +fn highlight_match<'t>(captures: ®ex::Captures<'t>) -> Option<regex::Match<'t>> { + captures.get(1).or_else(|| captures.get(0)) +} + /// Check whether a (row, col) position falls within a display span. /// The span is inclusive at start and exclusive at end. fn position_in_span( @@ -2163,7 +2172,10 @@ impl Grid { )> = None; for (plugin_id, pattern_map) in &self.plugin_highlights { for (pattern, compiled) in pattern_map { - for mat in compiled.regex.find_iter(&logical_text) { + for captures in compiled.regex.captures_iter(&logical_text) { + let Some(mat) = highlight_match(&captures) else { + continue; + }; if let Some((_sel, start_row, start_col, end_row, end_col)) = match_to_selection(&mat, &boundaries, &self.viewport) { @@ -2250,7 +2262,10 @@ impl Grid { if !compiled.on_hover || compiled.tooltip_text.is_none() { continue; } - for mat in compiled.regex.find_iter(&logical_text) { + for captures in compiled.regex.captures_iter(&logical_text) { + let Some(mat) = highlight_match(&captures) else { + continue; + }; if let Some((_sel, start_row, start_col, end_row, end_col)) = match_to_selection(&mat, &boundaries, &self.viewport) { @@ -2306,7 +2321,10 @@ impl Grid { if !compiled.on_hover || !compiled.has_visual_effect() { continue; } - for mat in compiled.regex.find_iter(&logical_text) { + for captures in compiled.regex.captures_iter(&logical_text) { + let Some(mat) = highlight_match(&captures) else { + continue; + }; if let Some((sel, start_row, start_col, end_row, end_col)) = match_to_selection(&mat, &boundaries, &self.viewport) { @@ -2340,7 +2358,10 @@ impl Grid { if compiled.on_hover || !compiled.has_visual_effect() { continue; } - for mat in compiled.regex.find_iter(&logical_text) { + for captures in compiled.regex.captures_iter(&logical_text) { + let Some(mat) = highlight_match(&captures) else { + continue; + }; if let Some((sel, _, _, _, _)) = match_to_selection(&mat, &boundaries, &self.viewport) { diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index c96f028d..9b6f022f 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -33,8 +33,8 @@ use zellij_utils::data::{ OpenEditPaneInPlaceOfPaneIdResponse, OpenFileFloatingNearPluginResponse, OpenFileFloatingResponse, OpenFileInPlaceOfPluginResponse, OpenFileInPlaceResponse, OpenFileNearPluginResponse, OpenFileResponse, OpenPaneInNewTabResponse, - OpenTerminalFloatingNearPluginResponse, OpenTerminalFloatingResponse, - OpenTerminalInPlaceOfPluginResponse, OpenTerminalInPlaceResponse, + OpenPluginPaneFloatingResponse, OpenTerminalFloatingNearPluginResponse, + OpenTerminalFloatingResponse, OpenTerminalInPlaceOfPluginResponse, OpenTerminalInPlaceResponse, OpenTerminalNearPluginResponse, OpenTerminalPaneInPlaceOfPaneIdResponse, OpenTerminalResponse, OriginatingPlugin, PaneScrollbackResponse, PermissionStatus, PermissionType, PluginPermission, RegexHighlight, RenameLayoutResponse, SaveLayoutResponse, TabMetadata, @@ -94,9 +94,10 @@ use zellij_utils::{ ProtobufOpenFileFloatingNearPluginResponse, ProtobufOpenFileFloatingResponse, ProtobufOpenFileInPlaceOfPluginResponse, ProtobufOpenFileInPlaceResponse, ProtobufOpenFileNearPluginResponse, ProtobufOpenFileResponse, - ProtobufOpenPaneInNewTabResponse, ProtobufOpenTerminalFloatingNearPluginResponse, - ProtobufOpenTerminalFloatingResponse, ProtobufOpenTerminalInPlaceOfPluginResponse, - ProtobufOpenTerminalInPlaceResponse, ProtobufOpenTerminalNearPluginResponse, + ProtobufOpenPaneInNewTabResponse, ProtobufOpenPluginPaneFloatingResponse, + ProtobufOpenTerminalFloatingNearPluginResponse, ProtobufOpenTerminalFloatingResponse, + ProtobufOpenTerminalInPlaceOfPluginResponse, ProtobufOpenTerminalInPlaceResponse, + ProtobufOpenTerminalNearPluginResponse, ProtobufOpenTerminalPaneInPlaceOfPaneIdResponse, ProtobufOpenTerminalResponse, ProtobufParseLayoutResponse, ProtobufPluginCommand, ProtobufRenameLayoutResponse, ProtobufSaveLayoutResponse, ProtobufSaveSessionResponse, @@ -700,6 +701,20 @@ fn host_run_plugin_command(mut caller: Caller<'_, PluginEnv>) { } => { open_plugin_pane_in_new_tab(env, plugin_url, configuration, context); }, + PluginCommand::OpenPluginPaneFloating { + plugin_url, + configuration, + floating_pane_coordinates, + context, + } => { + open_plugin_pane_floating( + env, + plugin_url, + configuration, + floating_pane_coordinates, + context, + ); + }, PluginCommand::OpenEditorPaneInNewTab(file_to_open, context) => { open_editor_pane_in_new_tab(env, file_to_open, context); }, @@ -1113,6 +1128,53 @@ fn open_plugin_pane_in_new_tab( .non_fatal(); } +fn open_plugin_pane_floating( + env: &PluginEnv, + plugin_url: String, + configuration: BTreeMap<String, String>, + floating_pane_coordinates: Option<FloatingPaneCoordinates>, + context: BTreeMap<String, String>, +) { + let run_plugin_or_alias = RunPluginOrAlias::from_url( + &plugin_url, + &Some(configuration), + None, + Some(env.plugin_cwd.clone()), + ); + let run_plugin_or_alias = match run_plugin_or_alias { + Ok(r) => r, + Err(e) => { + log::error!("Failed to parse plugin url '{}': {}", plugin_url, e); + let response = ProtobufOpenPluginPaneFloatingResponse::from( + OpenPluginPaneFloatingResponse::default(), + ); + wasi_write_object(env, &response.encode_to_vec()) + .with_context(|| { + format!("failed to write open_plugin_pane_floating error response") + }) + .non_fatal(); + return; + }, + }; + let _ = context; // context is not currently used for plugin panes + let action = Action::NewFloatingPluginPane { + plugin: run_plugin_or_alias, + pane_name: None, + skip_cache: false, + cwd: Some(env.plugin_cwd.clone()), + coordinates: floating_pane_coordinates, + }; + let error_msg = || format!("Failed to open floating plugin pane"); + let result = apply_action!(action, error_msg, env); + + let pane_id: OpenPluginPaneFloatingResponse = + result.and_then(|r| r.affected_pane_id).map(|p| p.into()); + let response = ProtobufOpenPluginPaneFloatingResponse::from(pane_id); + wasi_write_object(env, &response.encode_to_vec()) + .with_context(|| format!("failed to write open_plugin_pane_floating response")) + .non_fatal(); +} + fn open_editor_pane_in_new_tab( env: &PluginEnv, file_to_open: FileToOpen, @@ -5014,6 +5076,8 @@ fn check_command_permission( | PluginCommand::OpenTerminalFloatingNearPlugin(..) | PluginCommand::OpenTerminalInPlace(..) | PluginCommand::OpenTerminalInPlaceOfPlugin(..) + | PluginCommand::OpenPluginPaneInNewTab { .. } + | PluginCommand::OpenPluginPaneFloating { .. } | PluginCommand::OpenTerminalPaneInPlaceOfPaneId(..) => { PermissionType::OpenTerminalsOrPlugins }, @@ -5173,9 +5237,9 @@ fn check_command_permission( PluginCommand::GetSessionEnvironmentVariables => { PermissionType::ReadSessionEnvironmentVariables }, - PluginCommand::OpenCommandPaneInNewTab(..) - | PluginCommand::OpenPluginPaneInNewTab { .. } - | PluginCommand::OpenEditorPaneInNewTab(..) => PermissionType::ChangeApplicationState, + PluginCommand::OpenCommandPaneInNewTab(..) | PluginCommand::OpenEditorPaneInNewTab(..) => { + PermissionType::ChangeApplicationState + }, _ => return (PermissionStatus::Granted, None), }; diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs index 167df3ec..db07548f 100644 --- a/zellij-tile/src/shim.rs +++ b/zellij-tile/src/shim.rs @@ -31,13 +31,14 @@ use zellij_utils::plugin_api::plugin_command::{ ProtobufOpenEditPaneInPlaceOfPaneIdResponse, ProtobufOpenFileFloatingNearPluginResponse, ProtobufOpenFileFloatingResponse, ProtobufOpenFileInPlaceOfPluginResponse, ProtobufOpenFileInPlaceResponse, ProtobufOpenFileNearPluginResponse, ProtobufOpenFileResponse, - ProtobufOpenPaneInNewTabResponse, ProtobufOpenTerminalFloatingNearPluginResponse, - ProtobufOpenTerminalFloatingResponse, ProtobufOpenTerminalInPlaceOfPluginResponse, - ProtobufOpenTerminalInPlaceResponse, ProtobufOpenTerminalNearPluginResponse, - ProtobufOpenTerminalPaneInPlaceOfPaneIdResponse, ProtobufOpenTerminalResponse, - ProtobufParseLayoutResponse, ProtobufPluginCommand, ProtobufRenameLayoutResponse, - ProtobufSaveLayoutResponse, ProtobufSaveSessionResponse, ProtobufShowFloatingPanesResponse, - RenameWebTokenResponse, RevokeAllWebTokensResponse, RevokeTokenResponse, + ProtobufOpenPaneInNewTabResponse, ProtobufOpenPluginPaneFloatingResponse, + ProtobufOpenTerminalFloatingNearPluginResponse, ProtobufOpenTerminalFloatingResponse, + ProtobufOpenTerminalInPlaceOfPluginResponse, ProtobufOpenTerminalInPlaceResponse, + ProtobufOpenTerminalNearPluginResponse, ProtobufOpenTerminalPaneInPlaceOfPaneIdResponse, + ProtobufOpenTerminalResponse, ProtobufParseLayoutResponse, ProtobufPluginCommand, + ProtobufRenameLayoutResponse, ProtobufSaveLayoutResponse, ProtobufSaveSessionResponse, + ProtobufShowFloatingPanesResponse, RenameWebTokenResponse, RevokeAllWebTokensResponse, + RevokeTokenResponse, }; use zellij_utils::plugin_api::plugin_ids::{ProtobufPluginIds, ProtobufZellijVersion}; @@ -998,6 +999,30 @@ pub fn open_plugin_pane_in_new_tab( (result.tab_id, result.pane_id) } +/// Open a new floating plugin pane with the specified plugin URL and configuration. +/// Returns the pane ID of the newly created plugin pane, if successful. +pub fn open_plugin_pane_floating( + plugin_url: &str, + configuration: BTreeMap<String, String>, + coordinates: Option<FloatingPaneCoordinates>, + context: BTreeMap<String, String>, +) -> Option<PaneId> { + let plugin_command = PluginCommand::OpenPluginPaneFloating { + plugin_url: plugin_url.to_owned(), + configuration, + floating_pane_coordinates: coordinates, + context, + }; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; + + let response = + ProtobufOpenPluginPaneFloatingResponse::decode(bytes_from_stdin().unwrap().as_slice()) + .unwrap(); + OpenPluginPaneFloatingResponse::try_from(response).unwrap() +} + /// Opens a new tab with an editor pane for `file_to_open`. /// Returns `(tab_id, pane_id)` of the created tab and pane. pub fn open_editor_pane_in_new_tab( diff --git a/zellij-utils/assets/prost/api.plugin_command.rs b/zellij-utils/assets/prost/api.plugin_command.rs index ccb77f81..52626ac6 100644 --- a/zellij-utils/assets/prost/api.plugin_command.rs +++ b/zellij-utils/assets/prost/api.plugin_command.rs @@ -3,7 +3,7 @@ pub struct PluginCommand { #[prost(enumeration="CommandName", tag="1")] pub name: i32, - #[prost(oneof="plugin_command::Payload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159")] + #[prost(oneof="plugin_command::Payload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160")] pub payload: ::core::option::Option<plugin_command::Payload>, } /// Nested message and enum types in `PluginCommand`. @@ -301,6 +301,8 @@ pub mod plugin_command { SetPaneRegexHighlightsPayload(super::SetPaneRegexHighlightsPayload), #[prost(message, tag="159")] ClearPaneHighlightsPayload(super::ClearPaneHighlightsPayload), + #[prost(message, tag="160")] + OpenPluginPaneFloatingPayload(super::OpenPluginPaneFloatingPayload), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -1879,6 +1881,24 @@ pub struct ClearPaneHighlightsPayload { #[prost(message, optional, tag="1")] pub pane_id: ::core::option::Option<PaneId>, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OpenPluginPaneFloatingPayload { + #[prost(string, tag="1")] + pub plugin_url: ::prost::alloc::string::String, + #[prost(map="string, string", tag="2")] + pub configuration: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(message, optional, tag="3")] + pub floating_pane_coordinates: ::core::option::Option<FloatingPaneCoordinates>, + #[prost(message, repeated, tag="4")] + pub context: ::prost::alloc::vec::Vec<ContextItem>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OpenPluginPaneFloatingResponse { + #[prost(message, optional, tag="1")] + pub pane_id: ::core::option::Option<PaneId>, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum CommandName { @@ -2070,6 +2090,7 @@ pub enum CommandName { SetPaneColor = 206, SetPaneRegexHighlights = 207, ClearPaneHighlights = 208, + OpenPluginPaneFloating = 209, } impl CommandName { /// String value of the enum field names used in the ProtoBuf definition. @@ -2266,6 +2287,7 @@ impl CommandName { CommandName::SetPaneColor => "SetPaneColor", CommandName::SetPaneRegexHighlights => "SetPaneRegexHighlights", CommandName::ClearPaneHighlights => "ClearPaneHighlights", + CommandName::OpenPluginPaneFloating => "OpenPluginPaneFloating", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2459,6 +2481,7 @@ impl CommandName { "SetPaneColor" => Some(Self::SetPaneColor), "SetPaneRegexHighlights" => Some(Self::SetPaneRegexHighlights), "ClearPaneHighlights" => Some(Self::ClearPaneHighlights), + "OpenPluginPaneFloating" => Some(Self::OpenPluginPaneFloating), _ => None, } } diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 46a0daa5..1fdea34e 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -3555,6 +3555,12 @@ pub enum PluginCommand { SetPaneColor(PaneId, Option<String>, Option<String>), // (pane_id, fg, bg) SetPaneRegexHighlights(PaneId, Vec<RegexHighlight>), ClearPaneHighlights(PaneId), + OpenPluginPaneFloating { + plugin_url: String, + configuration: BTreeMap<String, String>, + floating_pane_coordinates: Option<FloatingPaneCoordinates>, + context: BTreeMap<String, String>, + }, } // Response type for plugin API methods that open a pane in a new tab @@ -3597,3 +3603,4 @@ pub type OpenCommandPaneBackgroundResponse = Option<PaneId>; pub type OpenCommandPaneInPlaceOfPaneIdResponse = Option<PaneId>; pub type OpenTerminalPaneInPlaceOfPaneIdResponse = Option<PaneId>; pub type OpenEditPaneInPlaceOfPaneIdResponse = Option<PaneId>; +pub type OpenPluginPaneFloatingResponse = Option<PaneId>; diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index 0634d63e..f00ffdd1 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -201,6 +201,7 @@ enum CommandName { SetPaneColor = 206; SetPaneRegexHighlights = 207; ClearPaneHighlights = 208; + OpenPluginPaneFloating = 209; } message PluginCommand { @@ -351,6 +352,7 @@ message PluginCommand { SetPaneColorPayload set_pane_color_payload = 157; SetPaneRegexHighlightsPayload set_pane_regex_highlights_payload = 158; ClearPaneHighlightsPayload clear_pane_highlights_payload = 159; + OpenPluginPaneFloatingPayload open_plugin_pane_floating_payload = 160; } } @@ -1281,3 +1283,14 @@ message SetPaneRegexHighlightsPayload { message ClearPaneHighlightsPayload { PaneId pane_id = 1; } + +message OpenPluginPaneFloatingPayload { + string plugin_url = 1; + map<string, string> configuration = 2; + optional FloatingPaneCoordinates floating_pane_coordinates = 3; + repeated ContextItem context = 4; +} + +message OpenPluginPaneFloatingResponse { + optional PaneId pane_id = 1; +} diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index 00acb760..fc55dd91 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -81,6 +81,8 @@ pub use super::generated_api::api::{ OpenFileNearPluginResponse as ProtobufOpenFileNearPluginResponse, OpenFilePayload, OpenFileResponse as ProtobufOpenFileResponse, OpenPaneInNewTabResponse as ProtobufOpenPaneInNewTabResponse, + OpenPluginPaneFloatingPayload, + OpenPluginPaneFloatingResponse as ProtobufOpenPluginPaneFloatingResponse, OpenPluginPaneInNewTabPayload as ProtobufOpenPluginPaneInNewTabPayload, OpenTerminalFloatingNearPluginPayload, OpenTerminalFloatingNearPluginResponse as ProtobufOpenTerminalFloatingNearPluginResponse, @@ -2543,6 +2545,26 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand { }, _ => Err("Mismatched payload for ClearPaneHighlights"), }, + Some(CommandName::OpenPluginPaneFloating) => match protobuf_plugin_command.payload { + Some(Payload::OpenPluginPaneFloatingPayload(payload)) => { + let configuration: BTreeMap<String, String> = + payload.configuration.into_iter().collect(); + let floating_pane_coordinates = + payload.floating_pane_coordinates.map(|f| f.into()); + let context: BTreeMap<String, String> = payload + .context + .into_iter() + .map(|e| (e.name, e.value)) + .collect(); + Ok(PluginCommand::OpenPluginPaneFloating { + plugin_url: payload.plugin_url, + configuration, + floating_pane_coordinates, + context, + }) + }, + _ => Err("Mismatched payload for OpenPluginPaneFloating"), + }, None => Err("Unrecognized plugin command"), } } @@ -4202,6 +4224,30 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand { }, )), }), + PluginCommand::OpenPluginPaneFloating { + plugin_url, + configuration, + floating_pane_coordinates, + context, + } => { + let context: Vec<_> = context + .into_iter() + .map(|(name, value)| ContextItem { name, value }) + .collect(); + let configuration: std::collections::HashMap<String, String> = + configuration.into_iter().collect(); + Ok(ProtobufPluginCommand { + name: CommandName::OpenPluginPaneFloating as i32, + payload: Some(Payload::OpenPluginPaneFloatingPayload( + OpenPluginPaneFloatingPayload { + plugin_url, + configuration, + floating_pane_coordinates: floating_pane_coordinates.map(|f| f.into()), + context, + }, + )), + }) + }, } } } @@ -4216,8 +4262,8 @@ use crate::data::{ OpenEditPaneInPlaceOfPaneIdResponse, OpenFileFloatingNearPluginResponse, OpenFileFloatingResponse, OpenFileInPlaceOfPluginResponse, OpenFileInPlaceResponse, OpenFileNearPluginResponse, OpenFileResponse, OpenPaneInNewTabResponse, - OpenTerminalFloatingNearPluginResponse, OpenTerminalFloatingResponse, - OpenTerminalInPlaceOfPluginResponse, OpenTerminalInPlaceResponse, + OpenPluginPaneFloatingResponse, OpenTerminalFloatingNearPluginResponse, + OpenTerminalFloatingResponse, OpenTerminalInPlaceOfPluginResponse, OpenTerminalInPlaceResponse, OpenTerminalNearPluginResponse, OpenTerminalPaneInPlaceOfPaneIdResponse, OpenTerminalResponse, }; @@ -4811,3 +4857,21 @@ impl From<OpenEditPaneInPlaceOfPaneIdResponse> for ProtobufOpenEditPaneInPlaceOf } } } + +impl TryFrom<ProtobufOpenPluginPaneFloatingResponse> for OpenPluginPaneFloatingResponse { + type Error = &'static str; + fn try_from(protobuf: ProtobufOpenPluginPaneFloatingResponse) -> Result<Self, Self::Error> { + match protobuf.pane_id { + Some(pane_id) => Ok(Some(pane_id.try_into()?)), + None => Ok(None), + } + } +} + +impl From<OpenPluginPaneFloatingResponse> for ProtobufOpenPluginPaneFloatingResponse { + fn from(response: OpenPluginPaneFloatingResponse) -> Self { + ProtobufOpenPluginPaneFloatingResponse { + pane_id: response.map(|p| p.try_into().unwrap()), + } + } +} |
