aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils/src/plugin_api
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2023-08-24 13:36:24 +0200
committerGitHub <noreply@github.com>2023-08-24 13:36:24 +0200
commitbc628abc1266cdc0dbce4f19a89727527a3e39a8 (patch)
tree6a0fcac6ec35c71a237bca0128a57a5b40afb0e3 /zellij-utils/src/plugin_api
parentdocs(changelog): permission system (diff)
downloadsl0ppij-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.proto14
-rw-r--r--zellij-utils/src/plugin_api/event.rs218
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.proto9
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.rs30
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),
+ })),
+ }),
}
}
}