aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2026-01-14 17:13:41 +0100
committerGitHub <noreply@github.com>2026-01-14 17:13:41 +0100
commit28cdd5bcf6dc9b1cd940e6e8c5c045da3e65de53 (patch)
tree83624ac6388b399c10b50685bf6b442ce29ea146
parentdocs(changelog): add PRs (diff)
downloadsl0ppij-28cdd5bcf6dc9b1cd940e6e8c5c045da3e65de53.tar.gz
sl0ppij-28cdd5bcf6dc9b1cd940e6e8c5c045da3e65de53.tar.xz
sl0ppij-28cdd5bcf6dc9b1cd940e6e8c5c045da3e65de53.zip
fix: center floating panes when width/height but no x/y are provided (#4607)
* fix: center floating panes when width/height but no x/y are provided * rustfmt * add pr
-rw-r--r--CHANGELOG.md1
-rw-r--r--zellij-server/src/panes/floating_panes/mod.rs20
-rw-r--r--zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout-2.snap22
-rw-r--r--zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout-3.snap42
-rw-r--r--zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout.snap22
-rw-r--r--zellij-server/src/tab/unit/tab_integration_tests.rs2
-rw-r--r--zellij-server/src/unit/screen_tests.rs223
-rw-r--r--zellij-utils/src/input/layout.rs26
-rw-r--r--zellij-utils/src/pane_size.rs48
9 files changed, 334 insertions, 72 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f820019..92dc3a15 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
* feat: new `layout-manager` interface and plugin API commands (https://github.com/zellij-org/zellij/pull/4601)
* fix: keep serializing sessions resurrected from the welcome screen (https://github.com/zellij-org/zellij/pull/4604)
* fix: sanitize session names when deleting them from the CLI (https://github.com/zellij-org/zellij/pull/4583)
+* fix: properly center floating panes started with `--floating` when explicit width/height is provided (https://github.com/zellij-org/zellij/pull/4607)
## [0.43.1] - 2025-08-08
* fix: pane rename backspace regression (https://github.com/zellij-org/zellij/pull/4346)
diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs
index a52c78e7..18710c01 100644
--- a/zellij-server/src/panes/floating_panes/mod.rs
+++ b/zellij-server/src/panes/floating_panes/mod.rs
@@ -292,18 +292,14 @@ impl FloatingPanes {
let mut position = floating_pane_grid
.find_room_for_new_pane()
.with_context(err_context)?;
- if let Some(x) = &floating_pane_layout.x {
- position.x = x.to_position(viewport.cols);
- }
- if let Some(y) = &floating_pane_layout.y {
- position.y = y.to_position(viewport.rows);
- }
- if let Some(width) = &floating_pane_layout.width {
- position.cols = Dimension::fixed(width.to_position(viewport.cols));
- }
- if let Some(height) = &floating_pane_layout.height {
- position.rows = Dimension::fixed(height.to_position(viewport.rows));
- }
+ position.apply_floating_pane_position(
+ floating_pane_layout.x.clone(),
+ floating_pane_layout.y.clone(),
+ floating_pane_layout.width.clone(),
+ floating_pane_layout.height.clone(),
+ viewport.cols,
+ viewport.rows,
+ );
if let Some(is_pinned) = &floating_pane_layout.pinned {
position.is_pinned = *is_pinned;
}
diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout-2.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout-2.snap
index 1394348a..0b88ec67 100644
--- a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout-2.snap
+++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout-2.snap
@@ -1,6 +1,5 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
-assertion_line: 8040
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
@@ -8,19 +7,18 @@ expression: snapshot
02 (C): │ │
03 (C): │ │
04 (C): │ │
-05 (C): │ ┌ Pane #2 ─────────────────────────────────── PIN [ ] ┐ ┌ Pane #3 ─────────────────────────────────── PIN [ ] ┐ │
-06 (C): │ │ │ │ │ │
-07 (C): │ │ │ │ │ │
-08 (C): │ │ │ │ │ │
-09 (C): │ │ │ │ │ │
-10 (C): │ │ │ │ │ │
-11 (C): │ │ │ │ │ │
-12 (C): │ │ │ │ │ │
-13 (C): │ │ │ │ │ │
-14 (C): │ └─────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────┘ │
+05 (C): │┌ Pane #2 ────────────────────────────────── PIN [ ] ┐ ┌ Pane #3 ────────────────────────────────── PIN [ ] ┐ │
+06 (C): ││ │ │ │ │
+07 (C): ││ │ │ │ │
+08 (C): ││ │ │ │ │
+09 (C): ││ │ │ │ │
+10 (C): ││ │ │ │ │
+11 (C): ││ │ │ │ │
+12 (C): ││ │ │ │ │
+13 (C): ││ │ │ │ │
+14 (C): │└────────────────────────────────────────────────────┘ └────────────────────────────────────────────────────┘ │
15 (C): │ │
16 (C): │ │
17 (C): │ │
18 (C): │ │
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
-
diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout-3.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout-3.snap
index 13848a2b..07992d63 100644
--- a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout-3.snap
+++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout-3.snap
@@ -1,26 +1,24 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
-assertion_line: 8043
expression: snapshot
---
-00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
-01 (C): │ ┌ Pane #2 ─────────────────────────────────── PIN [ ] ┐ ┌ Pane #3 ─────────────────────────────────── PIN [ ] ┐ │
-02 (C): │ │ │ │ │ │
-03 (C): │ │ │ │ │ │
-04 (C): │ │ │ │ │ │
-05 (C): │ │ │ │ │ │
-06 (C): │ │ │ │ │ │
-07 (C): │ │ │ │ │ │
-08 (C): │ │ │ │ │ │
-09 (C): │ │ │ │ │ │
-10 (C): │ └─────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────┘ │
-11 (C): │ ┌ Pane #4 ─────────────────────────────────── PIN [ ] ┐ │
-12 (C): │ │ │ │
-13 (C): │ │ │ │
-14 (C): │ │ │ │
-15 (C): │ │ │ │
-16 (C): │ │ │ │
-17 (C): │ │ │ │
-18 (C): │ │ │ │
-19 (C): └─────────────────────────────└─────────────────────────────────────────────────────┘───────────────────────────────────┘
-
+00 (C): ┌┌ Pane #2 ────────────────────────────────── PIN [ ] ┐─────┌ Pane #3 ────────────────────────────────── PIN [ ] ┐──────┐
+01 (C): ││ │ │ │ │
+02 (C): ││ │ │ │ │
+03 (C): ││ │ │ │ │
+04 (C): ││ │ │ │ │
+05 (C): ││ │ │ │ │
+06 (C): ││ │ │ │ │
+07 (C): ││ │ │ │ │
+08 (C): ││ │ │ │ │
+09 (C): │└────────────────────────────────────────────────────┘ └────────────────────────────────────────────────────┘ │
+10 (C): │ │
+11 (C): │ ┌ Pane #4 ────────────────────────────────── PIN [ ] ┐ │
+12 (C): │ │ │ │
+13 (C): │ │ │ │
+14 (C): │ │ │ │
+15 (C): │ │ │ │
+16 (C): │ │ │ │
+17 (C): │ │ │ │
+18 (C): │ │ │ │
+19 (C): └────────────────────────────────└────────────────────────────────────────────────────┘─────────────────────────────────┘
diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout.snap
index 675409f8..2f15c3ca 100644
--- a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout.snap
+++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__new_floating_pane_in_auto_layout.snap
@@ -1,6 +1,5 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
-assertion_line: 8040
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
@@ -13,14 +12,13 @@ expression: snapshot
07 (C): │ │
08 (C): │ │
09 (C): │ │
-10 (C): │ ┌ Pane #2 ──────────────────────────────────────── PIN [ ] ┐
-11 (C): │ │ │
-12 (C): │ │ │
-13 (C): │ │ │
-14 (C): │ │ │
-15 (C): │ │ │
-16 (C): │ │ │
-17 (C): │ │ │
-18 (C): │ │ │
-19 (C): └────────────────────────────────────────────────────────────└──────────────────────────────────────────────────────────┘
-
+10 (C): │ ┌ Pane #2 ──────────────────────────────────────── PIN [ ] ┐│
+11 (C): │ │ ││
+12 (C): │ │ ││
+13 (C): │ │ ││
+14 (C): │ │ ││
+15 (C): │ │ ││
+16 (C): │ │ ││
+17 (C): │ │ ││
+18 (C): │ │ ││
+19 (C): └───────────────────────────────────────────────────────────└──────────────────────────────────────────────────────────┘┘
diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs
index 6eb39eae..d80fb8b7 100644
--- a/zellij-server/src/tab/unit/tab_integration_tests.rs
+++ b/zellij-server/src/tab/unit/tab_integration_tests.rs
@@ -9405,7 +9405,7 @@ fn new_floating_pane_in_auto_layout() {
stacked_resize,
);
- let mut expected_cursor_coordinates = vec![(62, 11), (62, 6), (31, 12)];
+ let mut expected_cursor_coordinates = vec![(61, 11), (61, 6), (34, 12)];
for i in 0..3 {
let new_pane_id = i + 2;
tab.new_pane(
diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs
index b305a28f..75b427c3 100644
--- a/zellij-server/src/unit/screen_tests.rs
+++ b/zellij-server/src/unit/screen_tests.rs
@@ -1497,6 +1497,229 @@ fn open_new_floating_pane_with_custom_coordinates_exceeding_viewport() {
}
#[test]
+fn floating_pane_auto_centers_horizontally_with_only_width() {
+ let size = Size {
+ cols: 120,
+ rows: 20,
+ };
+ let mut screen = create_new_screen(size, true);
+
+ new_tab(&mut screen, 1, 0);
+ let active_tab = screen.get_active_tab_mut(1).unwrap();
+ active_tab
+ .new_pane(
+ PaneId::Terminal(2),
+ None,
+ None,
+ false,
+ true,
+ NewPanePlacement::Floating(Some(FloatingPaneCoordinates {
+ x: None,
+ y: Some(SplitSize::Fixed(5)),
+ width: Some(SplitSize::Fixed(60)),
+ height: Some(SplitSize::Fixed(10)),
+ pinned: None,
+ })),
+ Some(1),
+ None,
+ )
+ .unwrap();
+ let active_pane = active_tab.get_active_pane(1).unwrap();
+ assert_eq!(active_pane.x(), 30, "x centered: (120-60)/2 = 30");
+ assert_eq!(active_pane.y(), 5, "y explicitly set");
+ assert_eq!(active_pane.cols(), 60, "width set");
+ assert_eq!(active_pane.rows(), 10, "height set");
+}
+
+#[test]
+fn floating_pane_auto_centers_vertically_with_only_height() {
+ let size = Size {
+ cols: 120,
+ rows: 40,
+ };
+ let mut screen = create_new_screen(size, true);
+
+ new_tab(&mut screen, 1, 0);
+ let active_tab = screen.get_active_tab_mut(1).unwrap();
+ active_tab
+ .new_pane(
+ PaneId::Terminal(2),
+ None,
+ None,
+ false,
+ true,
+ NewPanePlacement::Floating(Some(FloatingPaneCoordinates {
+ x: Some(SplitSize::Fixed(10)),
+ y: None,
+ width: Some(SplitSize::Fixed(50)),
+ height: Some(SplitSize::Fixed(20)),
+ pinned: None,
+ })),
+ Some(1),
+ None,
+ )
+ .unwrap();
+ let active_pane = active_tab.get_active_pane(1).unwrap();
+ assert_eq!(active_pane.x(), 10, "x explicitly set");
+ assert_eq!(active_pane.y(), 10, "y centered: (40-20)/2 = 10");
+ assert_eq!(active_pane.cols(), 50, "width set");
+ assert_eq!(active_pane.rows(), 20, "height set");
+}
+
+#[test]
+fn floating_pane_auto_centers_both_axes_with_only_size() {
+ let size = Size {
+ cols: 120,
+ rows: 40,
+ };
+ let mut screen = create_new_screen(size, true);
+
+ new_tab(&mut screen, 1, 0);
+ let active_tab = screen.get_active_tab_mut(1).unwrap();
+ active_tab
+ .new_pane(
+ PaneId::Terminal(2),
+ None,
+ None,
+ false,
+ true,
+ NewPanePlacement::Floating(Some(FloatingPaneCoordinates {
+ x: None,
+ y: None,
+ width: Some(SplitSize::Fixed(80)),
+ height: Some(SplitSize::Fixed(30)),
+ pinned: None,
+ })),
+ Some(1),
+ None,
+ )
+ .unwrap();
+ let active_pane = active_tab.get_active_pane(1).unwrap();
+ assert_eq!(active_pane.x(), 20, "x centered: (120-80)/2 = 20");
+ assert_eq!(active_pane.y(), 5, "y centered: (40-30)/2 = 5");
+ assert_eq!(active_pane.cols(), 80, "width set");
+ assert_eq!(active_pane.rows(), 30, "height set");
+}
+
+#[test]
+fn floating_pane_respects_explicit_coordinates_with_size() {
+ let size = Size {
+ cols: 120,
+ rows: 40,
+ };
+ let mut screen = create_new_screen(size, true);
+
+ new_tab(&mut screen, 1, 0);
+ let active_tab = screen.get_active_tab_mut(1).unwrap();
+ active_tab
+ .new_pane(
+ PaneId::Terminal(2),
+ None,
+ None,
+ false,
+ true,
+ NewPanePlacement::Floating(Some(FloatingPaneCoordinates {
+ x: Some(SplitSize::Fixed(15)),
+ y: Some(SplitSize::Fixed(8)),
+ width: Some(SplitSize::Fixed(80)),
+ height: Some(SplitSize::Fixed(30)),
+ pinned: None,
+ })),
+ Some(1),
+ None,
+ )
+ .unwrap();
+ let active_pane = active_tab.get_active_pane(1).unwrap();
+ assert_eq!(active_pane.x(), 15, "x explicitly set, not centered");
+ assert_eq!(active_pane.y(), 8, "y explicitly set, not centered");
+ assert_eq!(active_pane.cols(), 80, "width set");
+ assert_eq!(active_pane.rows(), 30, "height set");
+}
+
+#[test]
+fn floating_pane_centers_with_percentage_width() {
+ let size = Size {
+ cols: 120,
+ rows: 40,
+ };
+ let mut screen = create_new_screen(size, true);
+
+ new_tab(&mut screen, 1, 0);
+ let active_tab = screen.get_active_tab_mut(1).unwrap();
+ active_tab
+ .new_pane(
+ PaneId::Terminal(2),
+ None,
+ None,
+ false,
+ true,
+ NewPanePlacement::Floating(Some(FloatingPaneCoordinates {
+ x: None,
+ y: Some(SplitSize::Fixed(5)),
+ width: Some(SplitSize::Percent(50)),
+ height: Some(SplitSize::Fixed(20)),
+ pinned: None,
+ })),
+ Some(1),
+ None,
+ )
+ .unwrap();
+ let active_pane = active_tab.get_active_pane(1).unwrap();
+ let expected_width = ((50.0_f64 / 100.0) * 120.0).floor() as usize;
+ let expected_x = (120 - expected_width) / 2;
+ assert_eq!(active_pane.cols(), expected_width, "width is 50% of 120");
+ assert_eq!(
+ active_pane.x(),
+ expected_x,
+ "x centered based on calculated width"
+ );
+ assert_eq!(active_pane.y(), 5, "y explicitly set");
+}
+
+#[test]
+fn floating_pane_centers_large_pane_safely() {
+ let size = Size {
+ cols: 100,
+ rows: 30,
+ };
+ let mut screen = create_new_screen(size, true);
+
+ new_tab(&mut screen, 1, 0);
+ let active_tab = screen.get_active_tab_mut(1).unwrap();
+ active_tab
+ .new_pane(
+ PaneId::Terminal(2),
+ None,
+ None,
+ false,
+ true,
+ NewPanePlacement::Floating(Some(FloatingPaneCoordinates {
+ x: None,
+ y: None,
+ width: Some(SplitSize::Fixed(150)),
+ height: Some(SplitSize::Fixed(50)),
+ pinned: None,
+ })),
+ Some(1),
+ None,
+ )
+ .unwrap();
+ let active_pane = active_tab.get_active_pane(1).unwrap();
+ assert_eq!(
+ active_pane.x(),
+ 0,
+ "x is 0 when pane larger than viewport (saturating_sub)"
+ );
+ assert_eq!(
+ active_pane.y(),
+ 0,
+ "y is 0 when pane larger than viewport (saturating_sub)"
+ );
+ assert!(active_pane.cols() <= 100, "width clamped to viewport");
+ assert!(active_pane.rows() <= 30, "height clamped to viewport");
+}
+
+#[test]
pub fn mouse_hover_effect() {
let size = Size {
cols: 130,
diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs
index 8ba81159..ae7d85a1 100644
--- a/zellij-utils/src/input/layout.rs
+++ b/zellij-utils/src/input/layout.rs
@@ -73,6 +73,24 @@ pub enum SplitSize {
Fixed(usize), // An absolute number of columns or rows
}
+impl From<PercentOrFixed> for SplitSize {
+ fn from(pof: PercentOrFixed) -> Self {
+ match pof {
+ PercentOrFixed::Percent(p) => SplitSize::Percent(p),
+ PercentOrFixed::Fixed(f) => SplitSize::Fixed(f),
+ }
+ }
+}
+
+impl From<SplitSize> for PercentOrFixed {
+ fn from(ss: SplitSize) -> Self {
+ match ss {
+ SplitSize::Percent(p) => PercentOrFixed::Percent(p),
+ SplitSize::Fixed(f) => PercentOrFixed::Fixed(f),
+ }
+ }
+}
+
impl SplitSize {
pub fn to_fixed(&self, full_size: usize) -> usize {
match self {
@@ -727,6 +745,14 @@ impl PercentOrFixed {
},
}
}
+ pub fn to_fixed(&self, full_size: usize) -> usize {
+ match self {
+ PercentOrFixed::Percent(percent) => {
+ ((*percent as f64 / 100.0) * full_size as f64).floor() as usize
+ },
+ PercentOrFixed::Fixed(fixed) => *fixed,
+ }
+ }
}
impl PercentOrFixed {
diff --git a/zellij-utils/src/pane_size.rs b/zellij-utils/src/pane_size.rs
index 50c4229a..453e57c1 100644
--- a/zellij-utils/src/pane_size.rs
+++ b/zellij-utils/src/pane_size.rs
@@ -5,7 +5,7 @@ use std::{
};
use crate::data::FloatingPaneCoordinates;
-use crate::input::layout::{SplitDirection, SplitSize};
+use crate::input::layout::{PercentOrFixed, SplitDirection, SplitSize};
use crate::position::Position;
/// Contains the position and size of a [`Pane`], or more generally of any terminal, measured
@@ -250,23 +250,45 @@ impl PaneGeom {
SplitDirection::Horizontal => self.rows.is_percent(),
}
}
+ pub fn apply_floating_pane_position(
+ &mut self,
+ x: Option<PercentOrFixed>,
+ y: Option<PercentOrFixed>,
+ width: Option<PercentOrFixed>,
+ height: Option<PercentOrFixed>,
+ viewport_cols: usize,
+ viewport_rows: usize,
+ ) {
+ if let Some(width) = &width {
+ self.cols = Dimension::fixed(width.to_fixed(viewport_cols));
+ }
+ if let Some(height) = &height {
+ self.rows = Dimension::fixed(height.to_fixed(viewport_rows));
+ }
+ self.x = match &x {
+ Some(x) => x.to_fixed(viewport_cols),
+ None if width.is_some() => viewport_cols.saturating_sub(self.cols.as_usize()) / 2,
+ None => self.x,
+ };
+ self.y = match &y {
+ Some(y) => y.to_fixed(viewport_rows),
+ None if height.is_some() => viewport_rows.saturating_sub(self.rows.as_usize()) / 2,
+ None => self.y,
+ };
+ }
pub fn adjust_coordinates(
&mut self,
floating_pane_coordinates: FloatingPaneCoordinates,
viewport: Viewport,
) {
- if let Some(x) = floating_pane_coordinates.x {
- self.x = x.to_fixed(viewport.cols);
- }
- if let Some(y) = floating_pane_coordinates.y {
- self.y = y.to_fixed(viewport.rows);
- }
- if let Some(height) = floating_pane_coordinates.height {
- self.rows = Dimension::from_split_size(height, viewport.rows);
- }
- if let Some(width) = floating_pane_coordinates.width {
- self.cols = Dimension::from_split_size(width, viewport.cols);
- }
+ self.apply_floating_pane_position(
+ floating_pane_coordinates.x.map(Into::into),
+ floating_pane_coordinates.y.map(Into::into),
+ floating_pane_coordinates.width.map(Into::into),
+ floating_pane_coordinates.height.map(Into::into),
+ viewport.cols,
+ viewport.rows,
+ );
if self.x < viewport.x {
self.x = viewport.x;
} else if self.x > viewport.x + viewport.cols {