diff options
| author | Aram Drevekenin <aram@poor.dev> | 2023-08-24 13:36:24 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-08-24 13:36:24 +0200 |
| commit | bc628abc1266cdc0dbce4f19a89727527a3e39a8 (patch) | |
| tree | 6a0fcac6ec35c71a237bca0128a57a5b40afb0e3 /zellij-utils/src/plugin_api | |
| parent | docs(changelog): permission system (diff) | |
| download | sl0ppij-bc628abc1266cdc0dbce4f19a89727527a3e39a8.tar.gz sl0ppij-bc628abc1266cdc0dbce4f19a89727527a3e39a8.tar.xz sl0ppij-bc628abc1266cdc0dbce4f19a89727527a3e39a8.zip | |
feat(sessions): add a session manager to switch between sessions, tabs and panes and create new ones (#2721)
* write/read session metadata to disk for all sessions
* switch session client side
* fix tests
* various adjustments
* fix full screen focus bug in tiled panes
* fix tests
* fix permission sorting issue
* cleanups
* add session manager
* fix tests
* various cleanups
* style(fmt): rustfmt
* clear screen before switching sessions
* I hate you clippy
* truncate controls line to width
* version session cache
* attempt to fix plugin tests
* style(fmt): rustfmt
* another attempt to fix the tests in the ci
Diffstat (limited to 'zellij-utils/src/plugin_api')
| -rw-r--r-- | zellij-utils/src/plugin_api/event.proto | 14 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/event.rs | 218 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/plugin_command.proto | 9 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/plugin_command.rs | 30 |
4 files changed, 267 insertions, 4 deletions
diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto index 3a9838fa..4d5b805d 100644 --- a/zellij-utils/src/plugin_api/event.proto +++ b/zellij-utils/src/plugin_api/event.proto @@ -39,6 +39,7 @@ enum EventType { /// A file was deleted somewhere in the Zellij CWD folder FileSystemDelete = 14; PermissionRequestResult = 15; + SessionUpdate = 16; } message EventNameList { @@ -59,9 +60,14 @@ message Event { CustomMessagePayload custom_message_payload = 10; FileListPayload file_list_payload = 11; PermissionRequestResultPayload permission_request_result_payload = 12; + SessionUpdatePayload session_update_payload = 13; } } +message SessionUpdatePayload { + repeated SessionManifest session_manifests = 1; +} + message PermissionRequestResultPayload { bool granted = 1; } @@ -111,6 +117,14 @@ message PaneManifest { repeated PaneInfo panes = 2; } +message SessionManifest { + string name = 1; + repeated TabInfo tabs = 2; + repeated PaneManifest panes = 3; + uint32 connected_clients = 4; + bool is_current_session = 5; +} + message PaneInfo { uint32 id = 1; bool is_plugin = 2; diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs index 6115fe1e..9b0e9886 100644 --- a/zellij-utils/src/plugin_api/event.rs +++ b/zellij-utils/src/plugin_api/event.rs @@ -6,7 +6,7 @@ pub use super::generated_api::api::{ EventType as ProtobufEventType, InputModeKeybinds as ProtobufInputModeKeybinds, KeyBind as ProtobufKeyBind, ModeUpdatePayload as ProtobufModeUpdatePayload, PaneInfo as ProtobufPaneInfo, PaneManifest as ProtobufPaneManifest, - TabInfo as ProtobufTabInfo, *, + SessionManifest as ProtobufSessionManifest, TabInfo as ProtobufTabInfo, *, }, input_mode::InputMode as ProtobufInputMode, key::Key as ProtobufKey, @@ -14,7 +14,7 @@ pub use super::generated_api::api::{ }; use crate::data::{ CopyDestination, Event, EventType, InputMode, Key, ModeInfo, Mouse, PaneInfo, PaneManifest, - PermissionStatus, PluginCapabilities, Style, TabInfo, + PermissionStatus, PluginCapabilities, SessionInfo, Style, TabInfo, }; use crate::errors::prelude::*; @@ -171,6 +171,18 @@ impl TryFrom<ProtobufEvent> for Event { }, _ => Err("Malformed payload for the file system delete Event"), }, + Some(ProtobufEventType::SessionUpdate) => match protobuf_event.payload { + Some(ProtobufEventPayload::SessionUpdatePayload( + protobuf_session_update_payload, + )) => { + let mut session_infos: Vec<SessionInfo> = vec![]; + for protobuf_session_info in protobuf_session_update_payload.session_manifests { + session_infos.push(SessionInfo::try_from(protobuf_session_info)?); + } + Ok(Event::SessionUpdate(session_infos)) + }, + _ => Err("Malformed payload for the SessionUpdate Event"), + }, None => Err("Unknown Protobuf Event"), } } @@ -313,7 +325,80 @@ impl TryFrom<Event> for ProtobufEvent { )), }) }, + Event::SessionUpdate(session_infos) => { + let mut protobuf_session_manifests = vec![]; + for session_info in session_infos { + protobuf_session_manifests.push(session_info.try_into()?); + } + let session_update_payload = SessionUpdatePayload { + session_manifests: protobuf_session_manifests, + }; + Ok(ProtobufEvent { + name: ProtobufEventType::SessionUpdate as i32, + payload: Some(event::Payload::SessionUpdatePayload(session_update_payload)), + }) + }, + } + } +} + +impl TryFrom<SessionInfo> for ProtobufSessionManifest { + type Error = &'static str; + fn try_from(session_info: SessionInfo) -> Result<Self, &'static str> { + let mut protobuf_pane_manifests = vec![]; + for (tab_index, pane_infos) in session_info.panes.panes { + let mut protobuf_pane_infos = vec![]; + for pane_info in pane_infos { + protobuf_pane_infos.push(pane_info.try_into()?); + } + protobuf_pane_manifests.push(ProtobufPaneManifest { + tab_index: tab_index as u32, + panes: protobuf_pane_infos, + }); + } + Ok(ProtobufSessionManifest { + name: session_info.name, + panes: protobuf_pane_manifests, + tabs: session_info + .tabs + .iter() + .filter_map(|t| t.clone().try_into().ok()) + .collect(), + connected_clients: session_info.connected_clients as u32, + is_current_session: session_info.is_current_session, + }) + } +} + +impl TryFrom<ProtobufSessionManifest> for SessionInfo { + type Error = &'static str; + fn try_from(protobuf_session_manifest: ProtobufSessionManifest) -> Result<Self, &'static str> { + let mut pane_manifest: HashMap<usize, Vec<PaneInfo>> = HashMap::new(); + for protobuf_pane_manifest in protobuf_session_manifest.panes { + let tab_index = protobuf_pane_manifest.tab_index as usize; + let mut panes = vec![]; + for protobuf_pane_info in protobuf_pane_manifest.panes { + panes.push(protobuf_pane_info.try_into()?); + } + if pane_manifest.contains_key(&tab_index) { + return Err("Duplicate tab definition in pane manifest"); + } + pane_manifest.insert(tab_index, panes); } + let panes = PaneManifest { + panes: pane_manifest, + }; + Ok(SessionInfo { + name: protobuf_session_manifest.name, + tabs: protobuf_session_manifest + .tabs + .iter() + .filter_map(|t| t.clone().try_into().ok()) + .collect(), + panes, + connected_clients: protobuf_session_manifest.connected_clients as usize, + is_current_session: protobuf_session_manifest.is_current_session, + }) } } @@ -697,6 +782,7 @@ impl TryFrom<ProtobufEventType> for EventType { ProtobufEventType::FileSystemUpdate => EventType::FileSystemUpdate, ProtobufEventType::FileSystemDelete => EventType::FileSystemDelete, ProtobufEventType::PermissionRequestResult => EventType::PermissionRequestResult, + ProtobufEventType::SessionUpdate => EventType::SessionUpdate, }) } } @@ -721,6 +807,7 @@ impl TryFrom<EventType> for ProtobufEventType { EventType::FileSystemUpdate => ProtobufEventType::FileSystemUpdate, EventType::FileSystemDelete => ProtobufEventType::FileSystemDelete, EventType::PermissionRequestResult => ProtobufEventType::PermissionRequestResult, + EventType::SessionUpdate => ProtobufEventType::SessionUpdate, }) } } @@ -1083,3 +1170,130 @@ fn serialize_file_system_delete_event() { "Event properly serialized/deserialized without change" ); } + +#[test] +fn serialize_session_update_event() { + use prost::Message; + let session_update_event = Event::SessionUpdate(Default::default()); + let protobuf_event: ProtobufEvent = session_update_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + session_update_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_session_update_event_with_non_default_values() { + use prost::Message; + let tab_infos = vec![ + TabInfo { + position: 0, + name: "First tab".to_owned(), + active: true, + panes_to_hide: 2, + is_fullscreen_active: true, + is_sync_panes_active: false, + are_floating_panes_visible: true, + other_focused_clients: vec![2, 3, 4], + active_swap_layout_name: Some("my cool swap layout".to_owned()), + is_swap_layout_dirty: false, + }, + TabInfo { + position: 1, + name: "Secondtab".to_owned(), + active: false, + panes_to_hide: 5, + is_fullscreen_active: false, + is_sync_panes_active: true, + are_floating_panes_visible: true, + other_focused_clients: vec![1, 5, 111], + active_swap_layout_name: None, + is_swap_layout_dirty: true, + }, + TabInfo::default(), + ]; + let mut panes = HashMap::new(); + let panes_list = vec![ + PaneInfo { + id: 1, + is_plugin: false, + is_focused: true, + is_fullscreen: true, + is_floating: false, + is_suppressed: false, + title: "pane 1".to_owned(), + exited: false, + exit_status: None, + is_held: false, + pane_x: 0, + pane_content_x: 1, + pane_y: 0, + pane_content_y: 1, + pane_rows: 5, + pane_content_rows: 4, + pane_columns: 22, + pane_content_columns: 21, + cursor_coordinates_in_pane: Some((0, 0)), + terminal_command: Some("foo".to_owned()), + plugin_url: None, + is_selectable: true, + }, + PaneInfo { + id: 1, + is_plugin: true, + is_focused: true, + is_fullscreen: true, + is_floating: false, + is_suppressed: false, + title: "pane 1".to_owned(), + exited: false, + exit_status: None, + is_held: false, + pane_x: 0, + pane_content_x: 1, + pane_y: 0, + pane_content_y: 1, + pane_rows: 5, + pane_content_rows: 4, + pane_columns: 22, + pane_content_columns: 21, + cursor_coordinates_in_pane: Some((0, 0)), + terminal_command: None, + plugin_url: Some("i_am_a_fake_plugin".to_owned()), + is_selectable: true, + }, + ]; + panes.insert(0, panes_list); + let session_info_1 = SessionInfo { + name: "session 1".to_owned(), + tabs: tab_infos, + panes: PaneManifest { panes }, + connected_clients: 2, + is_current_session: true, + }; + let session_info_2 = SessionInfo { + name: "session 2".to_owned(), + tabs: vec![], + panes: PaneManifest { + panes: HashMap::new(), + }, + connected_clients: 0, + is_current_session: false, + }; + let session_infos = vec![session_info_1, session_info_2]; + + let session_update_event = Event::SessionUpdate(session_infos); + let protobuf_event: ProtobufEvent = session_update_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + session_update_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index 9ba8bb7f..69d4c97b 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -78,6 +78,7 @@ enum CommandName { RenameTab = 64; ReportCrash = 65; RequestPluginPermissions = 66; + SwitchSession = 67; } message PluginCommand { @@ -120,9 +121,17 @@ message PluginCommand { IdAndNewName rename_tab_payload = 36; string report_crash_payload = 37; RequestPluginPermissionPayload request_plugin_permission_payload = 38; + SwitchSessionPayload switch_session_payload = 39; } } +message SwitchSessionPayload { + optional string name = 1; + optional uint32 tab_position = 2; + optional uint32 pane_id = 3; + optional bool pane_id_is_plugin = 4; +} + message RequestPluginPermissionPayload { repeated plugin_permission.PermissionType permissions = 1; } diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index ff50a6a7..71289a14 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -5,13 +5,13 @@ pub use super::generated_api::api::{ OpenCommandPanePayload, OpenFilePayload, PaneIdAndShouldFloat, PluginCommand as ProtobufPluginCommand, PluginMessagePayload, RequestPluginPermissionPayload, ResizePayload, SetTimeoutPayload, SubscribePayload, - SwitchTabToPayload, SwitchToModePayload, UnsubscribePayload, + SwitchSessionPayload, SwitchTabToPayload, SwitchToModePayload, UnsubscribePayload, }, plugin_permission::PermissionType as ProtobufPermissionType, resize::ResizeAction as ProtobufResizeAction, }; -use crate::data::{PermissionType, PluginCommand}; +use crate::data::{ConnectToSession, PermissionType, PluginCommand}; use std::convert::TryFrom; @@ -500,6 +500,23 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand { }, _ => Err("Mismatched payload for RequestPluginPermission"), }, + Some(CommandName::SwitchSession) => match protobuf_plugin_command.payload { + Some(Payload::SwitchSessionPayload(payload)) => { + let pane_id = match (payload.pane_id, payload.pane_id_is_plugin) { + (Some(pane_id), Some(is_plugin)) => Some((pane_id, is_plugin)), + (None, None) => None, + _ => { + return Err("Malformed payload for SwitchSession, 'pane_id' and 'is_plugin' must be included together or not at all") + } + }; + Ok(PluginCommand::SwitchSession(ConnectToSession { + name: payload.name, + tab_position: payload.tab_position.map(|p| p as usize), + pane_id, + })) + }, + _ => Err("Mismatched payload for SwitchSession"), + }, None => Err("Unrecognized plugin command"), } } @@ -846,6 +863,15 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand { }, )), }), + PluginCommand::SwitchSession(switch_to_session) => Ok(ProtobufPluginCommand { + name: CommandName::SwitchSession as i32, + payload: Some(Payload::SwitchSessionPayload(SwitchSessionPayload { + name: switch_to_session.name, + tab_position: switch_to_session.tab_position.map(|t| t as u32), + pane_id: switch_to_session.pane_id.map(|p| p.0), + pane_id_is_plugin: switch_to_session.pane_id.map(|p| p.1), + })), + }), } } } |
