feat(plugins): API to change floating pane coordinates (#3958)

* basic functionality through the cli

* added to plugin api

* add display area and viewport size to TabInfo

* fix tests and add new one

* some cleanups

* refactor: extract pane_id parsing logic

* style(fmt): rustfmt
This commit is contained in:
Aram Drevekenin 2025-01-30 17:04:36 +01:00 committed by GitHub
parent 382a0757e2
commit c4cb9d3d81
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 464 additions and 44 deletions

View file

@ -1,6 +1,6 @@
pub mod floating_pane_grid;
use zellij_utils::{
data::{Direction, PaneInfo, ResizeStrategy},
data::{Direction, FloatingPaneCoordinates, PaneInfo, ResizeStrategy},
position::Position,
};
@ -728,6 +728,27 @@ impl FloatingPanes {
let _ = self.set_pane_frames();
}
}
pub fn change_pane_coordinates(
&mut self,
pane_id: PaneId,
new_coordinates: FloatingPaneCoordinates,
) -> Result<()> {
let err_context = || format!("Failed to change_pane_coordinates");
{
let viewport = { self.viewport.borrow().clone() };
let pane = self.get_pane_mut(pane_id).with_context(err_context)?;
let mut pane_geom = pane.position_and_size();
if let Some(pinned) = new_coordinates.pinned.as_ref() {
pane.set_pinned(*pinned);
}
pane_geom.adjust_coordinates(new_coordinates, viewport);
pane.set_geom(pane_geom);
pane.set_should_render(true);
}
let _ = self.set_pane_frames();
Ok(())
}
pub fn move_clients_out_of_pane(&mut self, pane_id: PaneId) {
let active_panes: Vec<(ClientId, PaneId)> = self
.active_panes

View file

@ -361,6 +361,15 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) {
PluginCommand::StackPanes(pane_ids) => {
stack_panes(env, pane_ids.into_iter().map(|p_id| p_id.into()).collect())
},
PluginCommand::ChangeFloatingPanesCoordinates(pane_ids_and_coordinates) => {
change_floating_panes_coordinates(
env,
pane_ids_and_coordinates
.into_iter()
.map(|(p_id, coordinates)| (p_id.into(), coordinates))
.collect(),
)
},
},
(PermissionStatus::Denied, permission) => {
log::error!(
@ -1530,6 +1539,17 @@ fn stack_panes(env: &PluginEnv, pane_ids: Vec<PaneId>) {
.send_to_screen(ScreenInstruction::StackPanes(pane_ids));
}
fn change_floating_panes_coordinates(
env: &PluginEnv,
pane_ids_and_coordinates: Vec<(PaneId, FloatingPaneCoordinates)>,
) {
let _ = env
.senders
.send_to_screen(ScreenInstruction::ChangeFloatingPanesCoordinates(
pane_ids_and_coordinates,
));
}
fn scan_host_folder(env: &PluginEnv, folder_to_scan: PathBuf) {
if !folder_to_scan.starts_with("/host") {
log::error!(
@ -1950,6 +1970,7 @@ fn check_command_permission(
| PluginCommand::LoadNewPlugin { .. }
| PluginCommand::SetFloatingPanePinned(..)
| PluginCommand::StackPanes(..)
| PluginCommand::ChangeFloatingPanesCoordinates(..)
| PluginCommand::KillSessions(..) => PermissionType::ChangeApplicationState,
PluginCommand::UnblockCliPipeInput(..)
| PluginCommand::BlockCliPipeInput(..)

View file

@ -935,6 +935,14 @@ pub(crate) fn route_action(
))
.with_context(err_context)?;
},
Action::ChangeFloatingPaneCoordinates(pane_id, coordinates) => {
senders
.send_to_screen(ScreenInstruction::ChangeFloatingPanesCoordinates(vec![(
pane_id.into(),
coordinates,
)]))
.with_context(err_context)?;
},
}
Ok(should_break)
}

View file

@ -402,6 +402,7 @@ pub enum ScreenInstruction {
TogglePanePinned(ClientId),
SetFloatingPanePinned(PaneId, bool),
StackPanes(Vec<PaneId>),
ChangeFloatingPanesCoordinates(Vec<(PaneId, FloatingPaneCoordinates)>),
}
impl From<&ScreenInstruction> for ScreenContext {
@ -609,6 +610,9 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::TogglePanePinned(..) => ScreenContext::TogglePanePinned,
ScreenInstruction::SetFloatingPanePinned(..) => ScreenContext::SetFloatingPanePinned,
ScreenInstruction::StackPanes(..) => ScreenContext::StackPanes,
ScreenInstruction::ChangeFloatingPanesCoordinates(..) => {
ScreenContext::ChangeFloatingPanesCoordinates
},
}
}
}
@ -1528,6 +1532,8 @@ impl Screen {
.copied()
.collect();
let (active_swap_layout_name, is_swap_layout_dirty) = tab.swap_layout_info();
let tab_viewport = tab.get_viewport();
let tab_display_area = tab.get_display_area();
let tab_info_for_screen = TabInfo {
position: tab.position,
name: tab.name.clone(),
@ -1539,6 +1545,10 @@ impl Screen {
other_focused_clients: all_focused_clients,
active_swap_layout_name,
is_swap_layout_dirty,
viewport_rows: tab_viewport.rows,
viewport_columns: tab_viewport.cols,
display_area_rows: tab_display_area.rows,
display_area_columns: tab_display_area.cols,
};
tab_infos_for_screen_state.insert(tab.position, tab_info_for_screen);
}
@ -1558,6 +1568,8 @@ impl Screen {
.collect()
};
let (active_swap_layout_name, is_swap_layout_dirty) = tab.swap_layout_info();
let tab_viewport = tab.get_viewport();
let tab_display_area = tab.get_display_area();
let tab_info_for_plugins = TabInfo {
position: tab.position,
name: tab.name.clone(),
@ -1569,6 +1581,10 @@ impl Screen {
other_focused_clients,
active_swap_layout_name,
is_swap_layout_dirty,
viewport_rows: tab_viewport.rows,
viewport_columns: tab_viewport.cols,
display_area_rows: tab_display_area.rows,
display_area_columns: tab_display_area.cols,
};
plugin_tab_updates.push(tab_info_for_plugins);
}
@ -2573,6 +2589,20 @@ impl Screen {
.get_mut(&root_tab_id)
.map(|t| t.stack_panes(root_pane_id, panes_to_stack));
}
pub fn change_floating_panes_coordinates(
&mut self,
pane_ids_and_coordinates: Vec<(PaneId, FloatingPaneCoordinates)>,
) {
for (pane_id, coordinates) in pane_ids_and_coordinates {
for (_tab_id, tab) in self.tabs.iter_mut() {
if tab.has_pane_with_pid(&pane_id) {
tab.change_floating_pane_coordinates(&pane_id, coordinates)
.non_fatal();
break;
}
}
}
}
fn unblock_input(&self) -> Result<()> {
self.bus
.senders
@ -4756,6 +4786,11 @@ pub(crate) fn screen_thread_main(
let _ = screen.unblock_input();
let _ = screen.render(None);
},
ScreenInstruction::ChangeFloatingPanesCoordinates(pane_ids_and_coordinates) => {
screen.change_floating_panes_coordinates(pane_ids_and_coordinates);
let _ = screen.unblock_input();
let _ = screen.render(None);
},
}
}
Ok(())

View file

@ -4495,6 +4495,23 @@ impl Tab {
self.tiled_panes.expand_pane_in_stack(root_pane_id);
}
}
pub fn change_floating_pane_coordinates(
&mut self,
pane_id: &PaneId,
floating_pane_coordinates: FloatingPaneCoordinates,
) -> Result<()> {
self.floating_panes
.change_pane_coordinates(*pane_id, floating_pane_coordinates)?;
self.set_force_render();
self.swap_layouts.set_is_floating_damaged();
Ok(())
}
pub fn get_viewport(&self) -> Viewport {
self.viewport.borrow().clone()
}
pub fn get_display_area(&self) -> Size {
self.display_area.borrow().clone()
}
fn new_scrollback_editor_pane(&self, pid: u32) -> TerminalPane {
let next_terminal_position = self.get_next_terminal_position();
let mut new_pane = TerminalPane::new(

View file

@ -3460,8 +3460,6 @@ fn pane_in_sgr_any_event_tracking_mouse_mode() {
let sgr_mouse_mode_any_button = String::from("\u{1b}[?1003;1006h"); // any event tracking (1003) with SGR encoding (1006)
tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec())
.unwrap();
// TODO: CONTINUE HERE - make sure these pass, then add some button-less motions and see what
// we track them
tab.handle_mouse_event(
&MouseEvent::new_left_press_event(Position::new(5, 71)),
client_id,

View file

@ -3689,3 +3689,46 @@ pub fn send_cli_stack_panes_action() {
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_change_floating_pane_coordinates_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let initial_tiled_layout = TiledPaneLayout::default();
let initial_floating_panes = vec![FloatingPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_tiled_layout), initial_floating_panes);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let change_floating_pane_coordinates_action = CliAction::ChangeFloatingPaneCoordinates {
pane_id: "0".to_owned(),
x: Some("0".to_owned()),
y: Some("0".to_owned()),
width: Some("10".to_owned()),
height: Some("10".to_owned()),
pinned: None,
};
send_cli_action_to_server(
&session_metadata,
change_floating_pane_coordinates_action,
client_id,
);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}

View file

@ -0,0 +1,16 @@
---
source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 3725
expression: "format!(\"{}\", snapshot)"
---
00 (C): ┌ P[..]2 ┐─────────────────────────────────────────────────────────────────────┐
01 (C): │ │ │
02 (C): │ │ │
03 (C): │ │ │
04 (C): │ │ │
05 (C): │ │ │
06 (C): │ │ │
07 (C): │ │ │
08 (C): │ │ │
09 (C): └────────┘─────────────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 3727
expression: "format!(\"{}\", snapshot_count)"
---
2

View file

@ -0,0 +1,16 @@
---
source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 3725
expression: "format!(\"{}\", snapshot)"
---
00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐
01 (C): │ │
02 (C): │ │
03 (C): │ ┌ Pane #2 ──────────────────── PIN [ ] ┐ │
04 (C): │ │ │ │
05 (C): │ │ │ │
06 (C): │ │ │ │
07 (C): │ └──────────────────────────────────────┘ │
08 (C): │ │
09 (C): └──────────────────────────────────────────────────────────────────────────────┘

View file

@ -1,6 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2448
assertion_line: 2926
expression: "format!(\"{:#?}\", plugin_rename_tab_instruction)"
---
Some(
@ -26,6 +26,10 @@ Some(
"BASE",
),
is_swap_layout_dirty: false,
viewport_rows: 10,
viewport_columns: 80,
display_area_rows: 10,
display_area_columns: 80,
},
],
),

View file

@ -1,6 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2486
assertion_line: 2976
expression: "format!(\"{:#?}\", plugin_undo_rename_tab_instruction)"
---
Some(
@ -26,6 +26,10 @@ Some(
"BASE",
),
is_swap_layout_dirty: false,
viewport_rows: 10,
viewport_columns: 80,
display_area_rows: 10,
display_area_columns: 80,
},
],
),

View file

@ -1149,6 +1149,15 @@ pub fn stack_panes(pane_ids: Vec<PaneId>) {
unsafe { host_run_plugin_command() };
}
pub fn change_floating_panes_coordinates(
pane_ids_and_coordinates: Vec<(PaneId, FloatingPaneCoordinates)>,
) {
let plugin_command = PluginCommand::ChangeFloatingPanesCoordinates(pane_ids_and_coordinates);
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
unsafe { host_run_plugin_command() };
}
// Utility Functions
#[allow(unused)]

View file

@ -406,6 +406,14 @@ pub struct TabInfo {
pub active_swap_layout_name: ::core::option::Option<::prost::alloc::string::String>,
#[prost(bool, tag = "10")]
pub is_swap_layout_dirty: bool,
#[prost(uint32, tag = "11")]
pub viewport_rows: u32,
#[prost(uint32, tag = "12")]
pub viewport_columns: u32,
#[prost(uint32, tag = "13")]
pub display_area_rows: u32,
#[prost(uint32, tag = "14")]
pub display_area_columns: u32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]

View file

@ -5,7 +5,7 @@ pub struct PluginCommand {
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"
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"
)]
pub payload: ::core::option::Option<plugin_command::Payload>,
}
@ -180,10 +180,22 @@ pub mod plugin_command {
SetFloatingPanePinnedPayload(super::SetFloatingPanePinnedPayload),
#[prost(message, tag = "91")]
StackPanesPayload(super::StackPanesPayload),
#[prost(message, tag = "92")]
ChangeFloatingPanesCoordinatesPayload(
super::ChangeFloatingPanesCoordinatesPayload,
),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ChangeFloatingPanesCoordinatesPayload {
#[prost(message, repeated, tag = "1")]
pub pane_ids_and_floating_panes_coordinates: ::prost::alloc::vec::Vec<
PaneIdAndFloatingPaneCoordinates,
>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct StackPanesPayload {
#[prost(message, repeated, tag = "1")]
pub pane_ids: ::prost::alloc::vec::Vec<PaneId>,
@ -598,6 +610,14 @@ pub struct MovePayload {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PaneIdAndFloatingPaneCoordinates {
#[prost(message, optional, tag = "1")]
pub pane_id: ::core::option::Option<PaneId>,
#[prost(message, optional, tag = "2")]
pub floating_pane_coordinates: ::core::option::Option<FloatingPaneCoordinates>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct IdAndNewName {
/// pane id or tab index
#[prost(uint32, tag = "1")]
@ -747,6 +767,7 @@ pub enum CommandName {
ChangeHostFolder = 114,
SetFloatingPanePinned = 115,
StackPanes = 116,
ChangeFloatingPanesCoordinates = 117,
}
impl CommandName {
/// String value of the enum field names used in the ProtoBuf definition.
@ -874,6 +895,9 @@ impl CommandName {
CommandName::ChangeHostFolder => "ChangeHostFolder",
CommandName::SetFloatingPanePinned => "SetFloatingPanePinned",
CommandName::StackPanes => "StackPanes",
CommandName::ChangeFloatingPanesCoordinates => {
"ChangeFloatingPanesCoordinates"
}
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@ -998,6 +1022,9 @@ impl CommandName {
"ChangeHostFolder" => Some(Self::ChangeHostFolder),
"SetFloatingPanePinned" => Some(Self::SetFloatingPanePinned),
"StackPanes" => Some(Self::StackPanes),
"ChangeFloatingPanesCoordinates" => {
Some(Self::ChangeFloatingPanesCoordinates)
}
_ => None,
}
}

View file

@ -771,4 +771,25 @@ tail -f /tmp/my-live-logfile | zellij action pipe --name logs --plugin https://e
#[clap(last(true), required(true))]
pane_ids: Vec<String>,
},
ChangeFloatingPaneCoordinates {
/// The pane_id of the floating pane, eg. terminal_1, plugin_2 or 3 (equivalent to
/// terminal_3)
#[clap(short, long, value_parser)]
pane_id: String,
/// The x coordinates if the pane is floating as a bare integer (eg. 1) or percent (eg. 10%)
#[clap(short, long)]
x: Option<String>,
/// The y coordinates if the pane is floating as a bare integer (eg. 1) or percent (eg. 10%)
#[clap(short, long)]
y: Option<String>,
/// The width if the pane is floating as a bare integer (eg. 1) or percent (eg. 10%)
#[clap(long)]
width: Option<String>,
/// The height if the pane is floating as a bare integer (eg. 1) or percent (eg. 10%)
#[clap(long)]
height: Option<String>,
/// Whether to pin a floating pane so that it is always on top
#[clap(long)]
pinned: Option<bool>,
},
}

View file

@ -1294,6 +1294,16 @@ pub struct TabInfo {
pub active_swap_layout_name: Option<String>,
/// Whether the user manually changed the layout, moving out of the swap layout scheme
pub is_swap_layout_dirty: bool,
/// Row count in the viewport (including all non-ui panes, eg. will excluse the status bar)
pub viewport_rows: usize,
/// Column count in the viewport (including all non-ui panes, eg. will excluse the status bar)
pub viewport_columns: usize,
/// Row count in the display area (including all panes, will typically be larger than the
/// viewport)
pub display_area_rows: usize,
/// Column count in the display area (including all panes, will typically be larger than the
/// viewport)
pub display_area_columns: usize,
}
/// The `PaneManifest` contains a dictionary of panes, indexed by the tab position (0 indexed).
@ -1513,6 +1523,25 @@ pub enum PaneId {
Plugin(u32),
}
impl FromStr for PaneId {
type Err = Box<dyn std::error::Error>;
fn from_str(stringified_pane_id: &str) -> Result<Self, Self::Err> {
if let Some(terminal_stringified_pane_id) = stringified_pane_id.strip_prefix("terminal_") {
u32::from_str_radix(terminal_stringified_pane_id, 10)
.map(|id| PaneId::Terminal(id))
.map_err(|e| e.into())
} else if let Some(plugin_pane_id) = stringified_pane_id.strip_prefix("plugin_") {
u32::from_str_radix(plugin_pane_id, 10)
.map(|id| PaneId::Plugin(id))
.map_err(|e| e.into())
} else {
u32::from_str_radix(&stringified_pane_id, 10)
.map(|id| PaneId::Terminal(id))
.map_err(|e| e.into())
}
}
}
impl MessageToPlugin {
pub fn new(message_name: impl Into<String>) -> Self {
MessageToPlugin {
@ -1903,4 +1932,5 @@ pub enum PluginCommand {
ChangeHostFolder(PathBuf),
SetFloatingPanePinned(PaneId, bool), // bool -> should be pinned
StackPanes(Vec<PaneId>),
ChangeFloatingPanesCoordinates(Vec<(PaneId, FloatingPaneCoordinates)>),
}

View file

@ -374,6 +374,7 @@ pub enum ScreenContext {
TogglePanePinned,
SetFloatingPanePinned,
StackPanes,
ChangeFloatingPanesCoordinates,
}
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.

View file

@ -295,6 +295,7 @@ pub enum Action {
ListClients,
TogglePanePinned,
StackPanes(Vec<PaneId>),
ChangeFloatingPaneCoordinates(PaneId, FloatingPaneCoordinates),
}
impl Action {
@ -740,37 +741,15 @@ impl Action {
let mut malformed_ids = vec![];
let pane_ids = pane_ids
.iter()
.filter_map(|stringified_pane_id| {
if let Some(terminal_pane_id) =
stringified_pane_id.strip_prefix("terminal_")
{
u32::from_str_radix(terminal_pane_id, 10)
.ok()
.or_else(|| {
.filter_map(
|stringified_pane_id| match PaneId::from_str(stringified_pane_id) {
Ok(pane_id) => Some(pane_id),
Err(_e) => {
malformed_ids.push(stringified_pane_id.to_owned());
None
})
.map(|id| PaneId::Terminal(id))
} else if let Some(plugin_pane_id) =
stringified_pane_id.strip_prefix("plugin_")
{
u32::from_str_radix(plugin_pane_id, 10)
.ok()
.or_else(|| {
malformed_ids.push(stringified_pane_id.to_owned());
None
})
.map(|id| PaneId::Plugin(id))
} else {
u32::from_str_radix(stringified_pane_id, 10)
.ok()
.or_else(|| {
malformed_ids.push(stringified_pane_id.to_owned());
None
})
.map(|id| PaneId::Terminal(id))
}
})
},
},
)
.collect();
if !malformed_ids.is_empty() {
Err(
@ -783,6 +762,31 @@ impl Action {
Ok(vec![Action::StackPanes(pane_ids)])
}
},
CliAction::ChangeFloatingPaneCoordinates {
pane_id,
x,
y,
width,
height,
pinned,
} => {
let Some(coordinates) = FloatingPaneCoordinates::new(x, y, width, height, pinned)
else {
return Err(format!("Failed to parse floating pane coordinates"));
};
let parsed_pane_id = PaneId::from_str(&pane_id);
match parsed_pane_id {
Ok(parsed_pane_id) => {
Ok(vec![Action::ChangeFloatingPaneCoordinates(parsed_pane_id, coordinates)])
},
Err(_e) => {
Err(format!(
"Malformed pane id: {}, expecting a space separated list of either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
pane_id
))
}
}
},
}
}
pub fn launches_plugin(&self, plugin_url: &str) -> bool {

View file

@ -4259,6 +4259,15 @@ impl TabInfo {
.map(|s| s.to_owned())
}};
}
macro_rules! optional_int_node {
($name:expr, $type:ident) => {{
kdl_document
.get($name)
.and_then(|n| n.entries().iter().next())
.and_then(|e| e.value().as_i64())
.map(|e| e as $type)
}};
}
macro_rules! bool_node {
($name:expr) => {{
kdl_document
@ -4288,6 +4297,10 @@ impl TabInfo {
}
}
let active_swap_layout_name = optional_string_node!("active_swap_layout_name");
let viewport_rows = optional_int_node!("viewport_rows", usize).unwrap_or(0);
let viewport_columns = optional_int_node!("viewport_columns", usize).unwrap_or(0);
let display_area_rows = optional_int_node!("display_area_rows", usize).unwrap_or(0);
let display_area_columns = optional_int_node!("display_area_columns", usize).unwrap_or(0);
let is_swap_layout_dirty = bool_node!("is_swap_layout_dirty");
Ok(TabInfo {
position,
@ -4300,6 +4313,10 @@ impl TabInfo {
other_focused_clients,
active_swap_layout_name,
is_swap_layout_dirty,
viewport_rows,
viewport_columns,
display_area_rows,
display_area_columns,
})
}
pub fn encode_to_kdl(&self) -> KdlDocument {
@ -4347,6 +4364,22 @@ impl TabInfo {
kdl_doucment.nodes_mut().push(active_swap_layout);
}
let mut viewport_rows = KdlNode::new("viewport_rows");
viewport_rows.push(self.viewport_rows as i64);
kdl_doucment.nodes_mut().push(viewport_rows);
let mut viewport_columns = KdlNode::new("viewport_columns");
viewport_columns.push(self.viewport_columns as i64);
kdl_doucment.nodes_mut().push(viewport_columns);
let mut display_area_columns = KdlNode::new("display_area_columns");
display_area_columns.push(self.display_area_columns as i64);
kdl_doucment.nodes_mut().push(display_area_columns);
let mut display_area_rows = KdlNode::new("display_area_rows");
display_area_rows.push(self.display_area_rows as i64);
kdl_doucment.nodes_mut().push(display_area_rows);
let mut is_swap_layout_dirty = KdlNode::new("is_swap_layout_dirty");
is_swap_layout_dirty.push(self.is_swap_layout_dirty);
kdl_doucment.nodes_mut().push(is_swap_layout_dirty);
@ -4691,6 +4724,10 @@ fn serialize_and_deserialize_session_info_with_data() {
other_focused_clients: vec![2, 3],
active_swap_layout_name: Some("BASE".to_owned()),
is_swap_layout_dirty: true,
viewport_rows: 10,
viewport_columns: 10,
display_area_rows: 10,
display_area_columns: 10,
},
TabInfo {
position: 1,
@ -4703,6 +4740,10 @@ fn serialize_and_deserialize_session_info_with_data() {
other_focused_clients: vec![2, 3],
active_swap_layout_name: None,
is_swap_layout_dirty: false,
viewport_rows: 10,
viewport_columns: 10,
display_area_rows: 10,
display_area_columns: 10,
},
],
panes: PaneManifest { panes },

View file

@ -1,6 +1,6 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 2552
assertion_line: 4728
expression: serialized
---
name "my session name"
@ -15,6 +15,10 @@ tabs {
are_floating_panes_visible true
other_focused_clients 2 3
active_swap_layout_name "BASE"
viewport_rows 10
viewport_columns 10
display_area_columns 10
display_area_rows 10
is_swap_layout_dirty true
}
tab {
@ -26,6 +30,10 @@ tabs {
is_sync_panes_active true
are_floating_panes_visible true
other_focused_clients 2 3
viewport_rows 10
viewport_columns 10
display_area_columns 10
display_area_rows 10
is_swap_layout_dirty false
}
}

View file

@ -1246,6 +1246,7 @@ impl TryFrom<Action> for ProtobufAction {
| Action::CliPipe { .. }
| Action::ListClients
| Action::StackPanes(..)
| Action::ChangeFloatingPaneCoordinates(..)
| Action::SkipConfirm(..) => Err("Unsupported action"),
}
}

View file

@ -303,6 +303,10 @@ message TabInfo {
repeated uint32 other_focused_clients = 8;
optional string active_swap_layout_name = 9;
bool is_swap_layout_dirty = 10;
uint32 viewport_rows = 11;
uint32 viewport_columns = 12;
uint32 display_area_rows = 13;
uint32 display_area_columns = 14;
}
message ModeUpdatePayload {

View file

@ -1070,6 +1070,10 @@ impl TryFrom<ProtobufTabInfo> for TabInfo {
.collect(),
active_swap_layout_name: protobuf_tab_info.active_swap_layout_name,
is_swap_layout_dirty: protobuf_tab_info.is_swap_layout_dirty,
viewport_rows: protobuf_tab_info.viewport_rows as usize,
viewport_columns: protobuf_tab_info.viewport_columns as usize,
display_area_rows: protobuf_tab_info.display_area_rows as usize,
display_area_columns: protobuf_tab_info.display_area_columns as usize,
})
}
}
@ -1092,6 +1096,10 @@ impl TryFrom<TabInfo> for ProtobufTabInfo {
.collect(),
active_swap_layout_name: tab_info.active_swap_layout_name,
is_swap_layout_dirty: tab_info.is_swap_layout_dirty,
viewport_rows: tab_info.viewport_rows as u32,
viewport_columns: tab_info.viewport_columns as u32,
display_area_rows: tab_info.display_area_rows as u32,
display_area_columns: tab_info.display_area_columns as u32,
})
}
}
@ -1475,6 +1483,10 @@ fn serialize_tab_update_event_with_non_default_values() {
other_focused_clients: vec![2, 3, 4],
active_swap_layout_name: Some("my cool swap layout".to_owned()),
is_swap_layout_dirty: false,
viewport_rows: 10,
viewport_columns: 10,
display_area_rows: 10,
display_area_columns: 10,
},
TabInfo {
position: 1,
@ -1487,6 +1499,10 @@ fn serialize_tab_update_event_with_non_default_values() {
other_focused_clients: vec![1, 5, 111],
active_swap_layout_name: None,
is_swap_layout_dirty: true,
viewport_rows: 10,
viewport_columns: 10,
display_area_rows: 10,
display_area_columns: 10,
},
TabInfo::default(),
]);
@ -1754,6 +1770,10 @@ fn serialize_session_update_event_with_non_default_values() {
other_focused_clients: vec![2, 3, 4],
active_swap_layout_name: Some("my cool swap layout".to_owned()),
is_swap_layout_dirty: false,
viewport_rows: 10,
viewport_columns: 10,
display_area_rows: 10,
display_area_columns: 10,
},
TabInfo {
position: 1,
@ -1766,6 +1786,10 @@ fn serialize_session_update_event_with_non_default_values() {
other_focused_clients: vec![1, 5, 111],
active_swap_layout_name: None,
is_swap_layout_dirty: true,
viewport_rows: 10,
viewport_columns: 10,
display_area_rows: 10,
display_area_columns: 10,
},
TabInfo::default(),
];

View file

@ -130,6 +130,7 @@ enum CommandName {
ChangeHostFolder = 114;
SetFloatingPanePinned = 115;
StackPanes = 116;
ChangeFloatingPanesCoordinates = 117;
}
message PluginCommand {
@ -216,9 +217,14 @@ message PluginCommand {
ChangeHostFolderPayload change_host_folder_payload = 89;
SetFloatingPanePinnedPayload set_floating_pane_pinned_payload = 90;
StackPanesPayload stack_panes_payload = 91;
ChangeFloatingPanesCoordinatesPayload change_floating_panes_coordinates_payload = 92;
}
}
message ChangeFloatingPanesCoordinatesPayload {
repeated PaneIdAndFloatingPaneCoordinates pane_ids_and_floating_panes_coordinates = 1;
}
message StackPanesPayload {
repeated PaneId pane_ids = 1;
}
@ -488,6 +494,11 @@ message MovePayload {
resize.MoveDirection direction = 1;
}
message PaneIdAndFloatingPaneCoordinates {
PaneId pane_id = 1;
FloatingPaneCoordinates floating_pane_coordinates = 2;
}
message IdAndNewName {
uint32 id = 1; // pane id or tab index
string new_name = 2;

View file

@ -4,9 +4,10 @@ pub use super::generated_api::api::{
input_mode::InputMode as ProtobufInputMode,
plugin_command::{
plugin_command::Payload, BreakPanesToNewTabPayload, BreakPanesToTabWithIndexPayload,
ChangeHostFolderPayload, ClearScreenForPaneIdPayload, CliPipeOutputPayload,
CloseTabWithIndexPayload, CommandName, ContextItem, EditScrollbackForPaneWithIdPayload,
EnvVariable, ExecCmdPayload, FixedOrPercent as ProtobufFixedOrPercent,
ChangeFloatingPanesCoordinatesPayload, ChangeHostFolderPayload,
ClearScreenForPaneIdPayload, CliPipeOutputPayload, CloseTabWithIndexPayload, CommandName,
ContextItem, EditScrollbackForPaneWithIdPayload, EnvVariable, ExecCmdPayload,
FixedOrPercent as ProtobufFixedOrPercent,
FixedOrPercentValue as ProtobufFixedOrPercentValue,
FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, HidePaneWithIdPayload,
HttpVerb as ProtobufHttpVerb, IdAndNewName, KeyToRebind, KeyToUnbind, KillSessionsPayload,
@ -14,8 +15,9 @@ pub use super::generated_api::api::{
MovePaneWithPaneIdPayload, MovePayload, NewPluginArgs as ProtobufNewPluginArgs,
NewTabsWithLayoutInfoPayload, OpenCommandPanePayload, OpenFilePayload,
PageScrollDownInPaneIdPayload, PageScrollUpInPaneIdPayload, PaneId as ProtobufPaneId,
PaneType as ProtobufPaneType, PluginCommand as ProtobufPluginCommand, PluginMessagePayload,
RebindKeysPayload, ReconfigurePayload, ReloadPluginPayload, RequestPluginPermissionPayload,
PaneIdAndFloatingPaneCoordinates, PaneType as ProtobufPaneType,
PluginCommand as ProtobufPluginCommand, PluginMessagePayload, RebindKeysPayload,
ReconfigurePayload, ReloadPluginPayload, RequestPluginPermissionPayload,
RerunCommandPanePayload, ResizePaneIdWithDirectionPayload, ResizePayload,
RunCommandPayload, ScrollDownInPaneIdPayload, ScrollToBottomInPaneIdPayload,
ScrollToTopInPaneIdPayload, ScrollUpInPaneIdPayload, SetFloatingPanePinnedPayload,
@ -1339,7 +1341,26 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
.collect(),
))
},
_ => Err("Mismatched payload for SetFloatingPanePinned"),
_ => Err("Mismatched payload for StackPanes"),
},
Some(CommandName::ChangeFloatingPanesCoordinates) => {
match protobuf_plugin_command.payload {
Some(Payload::ChangeFloatingPanesCoordinatesPayload(
change_floating_panes_coordinates_payload,
)) => Ok(PluginCommand::ChangeFloatingPanesCoordinates(
change_floating_panes_coordinates_payload
.pane_ids_and_floating_panes_coordinates
.into_iter()
.filter_map(|p_id_a_fp| {
let pane_id: PaneId = p_id_a_fp.pane_id?.try_into().ok()?;
let floating_pane_coordinates: FloatingPaneCoordinates =
p_id_a_fp.floating_pane_coordinates?.try_into().ok()?;
Some((pane_id, floating_pane_coordinates))
})
.collect(),
)),
_ => Err("Mismatched payload for ChangeFloatingPanesCoordinates"),
}
},
None => Err("Unrecognized plugin command"),
}
@ -2194,6 +2215,27 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
.collect(),
})),
}),
PluginCommand::ChangeFloatingPanesCoordinates(
pane_ids_and_floating_panes_coordinates,
) => Ok(ProtobufPluginCommand {
name: CommandName::ChangeFloatingPanesCoordinates as i32,
payload: Some(Payload::ChangeFloatingPanesCoordinatesPayload(
ChangeFloatingPanesCoordinatesPayload {
pane_ids_and_floating_panes_coordinates:
pane_ids_and_floating_panes_coordinates
.into_iter()
.filter_map(|(p_id, floating_pane_coordinates)| {
Some(PaneIdAndFloatingPaneCoordinates {
pane_id: Some(p_id.try_into().ok()?),
floating_pane_coordinates: Some(
floating_pane_coordinates.try_into().ok()?,
),
})
})
.collect(),
},
)),
}),
}
}
}