diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-07-27 12:26:02 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-27 12:26:02 +0200 |
| commit | 828a1508c6366049ab3756aaba7631f7a4e5a81c (patch) | |
| tree | a579ef49d57d340d604771bf1b3ce680f9dd6bc5 | |
| parent | fix(windows): prefer PATHEXT variants over bare filename in resolver (#5364) (diff) | |
| download | sl0ppij-828a1508c6366049ab3756aaba7631f7a4e5a81c.tar.gz sl0ppij-828a1508c6366049ab3756aaba7631f7a4e5a81c.tar.xz sl0ppij-828a1508c6366049ab3756aaba7631f7a4e5a81c.zip | |
chore: replace unmaintained cassowary with maintained fork kasuari (#5416)
* chore: replace unmaintained cassowary with maintained fork kasuari
* add pr
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | Cargo.lock | 37 | ||||
| -rw-r--r-- | zellij-server/Cargo.toml | 2 | ||||
| -rw-r--r-- | zellij-server/src/panes/tiled_panes/mod.rs | 8 | ||||
| -rw-r--r-- | zellij-server/src/panes/tiled_panes/pane_resizer.rs | 21 | ||||
| -rw-r--r-- | zellij-server/src/panes/tiled_panes/unit/mock_pane.rs | 368 | ||||
| -rw-r--r-- | zellij-server/src/panes/tiled_panes/unit/pane_resizer_tests.rs | 911 | ||||
| -rw-r--r-- | zellij-server/src/tab/unit/tab_tests.rs | 4 |
8 files changed, 1330 insertions, 22 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 6db75732..cc63e07f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * fix: properly pad lines before CUF (https://github.com/zellij-org/zellij/pull/5377) * fix: clear existing pane name when entering rename input (https://github.com/zellij-org/zellij/pull/5369) * fix: occasional input spam when pressing ESC and holding the mouse over slow SSH connections (https://github.com/zellij-org/zellij/pull/5323) +* chore: switch cassowary deps to maintained fork kasuari (https://github.com/zellij-org/zellij/pull/5416) ## [0.44.3] - 2026-05-13 * fix(windows): bump windows-sys to 0.59 to align manifest with code, fixing source builds via `cargo install`/`cargo binstall` (https://github.com/zellij-org/zellij/pull/5139) @@ -439,12 +439,6 @@ dependencies = [ ] [[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - -[[package]] name = "castaway" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1085,6 +1079,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1355,7 +1355,18 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ - "foldhash", + "foldhash 0.1.4", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", ] [[package]] @@ -1866,6 +1877,16 @@ dependencies = [ ] [[package]] +name = "kasuari" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.12", +] + +[[package]] name = "kdl" version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5044,7 +5065,6 @@ dependencies = [ "base64 0.13.0", "byteorder", "bytes", - "cassowary", "chrono", "close_fds", "crossbeam", @@ -5053,6 +5073,7 @@ dependencies = [ "insta", "interprocess", "isahc", + "kasuari", "kdl", "lazy_static", "libc", diff --git a/zellij-server/Cargo.toml b/zellij-server/Cargo.toml index 0f04acef..859b7433 100644 --- a/zellij-server/Cargo.toml +++ b/zellij-server/Cargo.toml @@ -16,12 +16,12 @@ async-trait = { version = "0.1.50", default-features = false } base64 = { version = "0.13.0", default-features = false, features = ["std"] } byteorder = { version = "1.4.3", default-features = false, features = ["std"] } bytes = { version = "1.6.0", default-features = false, features = ["std"] } -cassowary = { version = "0.3.0", default-features = false } chrono = { version = "0.4.19", default-features = false, features = ["std", "clock"] } crossbeam = { version = "0.8", default-features = false, features = ["crossbeam-channel", "std"] } highway = { version = "0.6.4", default-features = false, features = ["std"] } interprocess = { workspace = true } isahc = { workspace = true } +kasuari = { version = "0.4.12", default-features = false } kdl = { workspace = true } lazy_static = { workspace = true } log = { workspace = true } diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs index 88bec53a..9bc67713 100644 --- a/zellij-server/src/panes/tiled_panes/mod.rs +++ b/zellij-server/src/panes/tiled_panes/mod.rs @@ -2,6 +2,14 @@ mod pane_resizer; mod stacked_panes; mod tiled_pane_grid; +#[cfg(test)] +#[path = "./unit/mock_pane.rs"] +mod pane_resizer_test_mock; + +#[cfg(test)] +#[path = "./unit/pane_resizer_tests.rs"] +mod pane_resizer_tests; + use crate::resize_pty; use tiled_pane_grid::{split, TiledPaneGrid, RESIZE_PERCENT}; diff --git a/zellij-server/src/panes/tiled_panes/pane_resizer.rs b/zellij-server/src/panes/tiled_panes/pane_resizer.rs index 506481d4..8453fa76 100644 --- a/zellij-server/src/panes/tiled_panes/pane_resizer.rs +++ b/zellij-server/src/panes/tiled_panes/pane_resizer.rs @@ -1,10 +1,6 @@ use super::stacked_panes::StackedPanes; use crate::{panes::PaneId, tab::Pane}; -use cassowary::{ - strength::{REQUIRED, STRONG}, - Expression, Solver, Variable, - WeightedRelation::EQ, -}; +use kasuari::{Expression, Solver, Strength, Variable, WeightedRelation::EQ}; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::rc::Rc; @@ -74,7 +70,7 @@ impl<'a> PaneResizer<'a> { .collect(); self.solver - .add_constraints(&constraints) + .add_constraints(constraints) .map_err(|e| format!("{:?}", e))?; Ok(grid) @@ -307,7 +303,7 @@ impl<'a> PaneResizer<'a> { } } -fn constrain_spans(space: usize, spans: &[Span]) -> HashSet<cassowary::Constraint> { +fn constrain_spans(space: usize, spans: &[Span]) -> HashSet<kasuari::Constraint> { let mut constraints = HashSet::new(); // Calculating "flexible" space (space not consumed by fixed-size spans) @@ -323,14 +319,17 @@ fn constrain_spans(space: usize, spans: &[Span]) -> HashSet<cassowary::Constrain let full_size = spans .iter() .fold(Expression::from_constant(0.0), |acc, s| acc + s.size_var); - constraints.insert(full_size.clone() | EQ(REQUIRED) | space as f64); + constraints.insert(full_size.clone() | EQ(Strength::REQUIRED) | space as f64); // Try to maintain ratios and lock non-flexible sizes for span in spans { match span.size.constraint { - Constraint::Fixed(s) => constraints.insert(span.size_var | EQ(REQUIRED) | s as f64), - Constraint::Percent(p) => constraints - .insert((span.size_var / new_flex_space as f64) | EQ(STRONG) | (p / 100.0)), + Constraint::Fixed(s) => { + constraints.insert(span.size_var | EQ(Strength::REQUIRED) | s as f64) + }, + Constraint::Percent(p) => constraints.insert( + (span.size_var / new_flex_space as f64) | EQ(Strength::STRONG) | (p / 100.0), + ), }; } diff --git a/zellij-server/src/panes/tiled_panes/unit/mock_pane.rs b/zellij-server/src/panes/tiled_panes/unit/mock_pane.rs new file mode 100644 index 00000000..c21f5297 --- /dev/null +++ b/zellij-server/src/panes/tiled_panes/unit/mock_pane.rs @@ -0,0 +1,368 @@ +#![allow(dead_code)] + +use super::pane_resizer::PaneResizer; +use crate::panes::PaneId; +use crate::tab::Pane; +use crate::ui::pane_boundaries_frame::FrameParams; +use crate::{ + output::{CharacterChunk, SixelImageChunk}, + pty::VteBytes, + ClientId, +}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; +use std::time::Instant; +use zellij_utils::data::{InputMode, PaletteColor, PaneContents}; +use zellij_utils::errors::prelude::*; +use zellij_utils::input::layout::Run as LayoutRun; +use zellij_utils::input::layout::SplitDirection; +use zellij_utils::pane_size::Offset; +use zellij_utils::pane_size::{Dimension, PaneGeom}; + +#[derive(Clone, Copy, Debug)] +pub struct PaneSpec { + pub id: PaneId, + pub x: usize, + pub y: usize, + pub cols: Dimension, + pub rows: Dimension, + pub stacked: Option<usize>, + pub logical_position: Option<usize>, + pub geom_override: Option<PaneGeom>, +} + +impl PaneSpec { + pub fn geom(&self) -> PaneGeom { + PaneGeom { + x: self.x, + y: self.y, + rows: self.rows, + cols: self.cols, + stacked: self.stacked, + is_pinned: false, + logical_position: self.logical_position, + } + } +} + +pub fn dim_pct(percent: f64, inner: usize) -> Dimension { + let mut dimension = Dimension::percent(percent); + dimension.set_inner(inner); + dimension +} + +pub fn dim_fixed(size: usize) -> Dimension { + Dimension::fixed(size) +} + +pub fn spec(id: u32, x: usize, y: usize, cols: Dimension, rows: Dimension) -> PaneSpec { + PaneSpec { + id: PaneId::Terminal(id), + x, + y, + cols, + rows, + stacked: None, + logical_position: None, + geom_override: None, + } +} + +pub fn spec_stacked( + id: u32, + x: usize, + y: usize, + cols: Dimension, + rows: Dimension, + stack_id: usize, +) -> PaneSpec { + PaneSpec { + id: PaneId::Terminal(id), + x, + y, + cols, + rows, + stacked: Some(stack_id), + logical_position: None, + geom_override: None, + } +} + +pub fn with_override(base: PaneSpec, geom_override: PaneGeom) -> PaneSpec { + PaneSpec { + geom_override: Some(geom_override), + ..base + } +} + +pub struct MockPane { + pid: PaneId, + geom: PaneGeom, + geom_override: Option<PaneGeom>, +} + +impl MockPane { + pub fn from_spec(spec: PaneSpec) -> Self { + MockPane { + pid: spec.id, + geom: spec.geom(), + geom_override: spec.geom_override, + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct PaneSnapshot { + pub id: PaneId, + pub geom: PaneGeom, + pub current: PaneGeom, + pub geom_override: Option<PaneGeom>, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Outcome { + Ok, + PaneSizeUnchanged, + OtherErr(String), +} + +#[derive(Clone, Debug)] +pub struct Run { + pub outcome: Outcome, + pub panes: Vec<PaneSnapshot>, +} + +pub fn run_layout(specs: &[PaneSpec], direction: SplitDirection, space: usize) -> Run { + let mut owned: Vec<Box<dyn Pane>> = specs + .iter() + .map(|s| Box::new(MockPane::from_spec(*s)) as Box<dyn Pane>) + .collect(); + + let mut map: HashMap<PaneId, &mut Box<dyn Pane>> = HashMap::new(); + for (s, b) in specs.iter().zip(owned.iter_mut()) { + map.insert(s.id, b); + } + let map = Rc::new(RefCell::new(map)); + + let result = PaneResizer::new(map.clone()).layout(direction, space); + + let outcome = match result { + Ok(()) => Outcome::Ok, + Err(e) => match e.downcast_ref::<ZellijError>() { + Some(ZellijError::PaneSizeUnchanged) => Outcome::PaneSizeUnchanged, + _ => Outcome::OtherErr(format!("{:#}", e)), + }, + }; + + let mut panes: Vec<PaneSnapshot> = map + .borrow() + .iter() + .map(|(id, p)| PaneSnapshot { + id: *id, + geom: p.position_and_size(), + current: p.current_geom(), + geom_override: p.geom_override(), + }) + .collect(); + panes.sort_by_key(|s| s.id); + + Run { outcome, panes } +} + +impl Pane for MockPane { + fn x(&self) -> usize { + unimplemented!() + } + fn y(&self) -> usize { + unimplemented!() + } + fn rows(&self) -> usize { + unimplemented!() + } + fn cols(&self) -> usize { + unimplemented!() + } + fn get_content_x(&self) -> usize { + unimplemented!() + } + fn get_content_y(&self) -> usize { + unimplemented!() + } + fn get_content_columns(&self) -> usize { + unimplemented!() + } + fn get_content_rows(&self) -> usize { + unimplemented!() + } + fn reset_size_and_position_override(&mut self) { + unimplemented!() + } + fn set_geom(&mut self, position_and_size: PaneGeom) { + self.geom = position_and_size; + } + fn set_geom_override(&mut self, pane_geom: PaneGeom) { + self.geom_override = Some(pane_geom); + } + fn handle_pty_bytes(&mut self, _bytes: VteBytes) { + unimplemented!() + } + fn handle_plugin_bytes(&mut self, _client_id: ClientId, _bytes: VteBytes) { + unimplemented!() + } + fn cursor_coordinates(&self, _client_id: Option<ClientId>) -> Option<(usize, usize, bool)> { + unimplemented!() + } + fn position_and_size(&self) -> PaneGeom { + self.geom + } + fn current_geom(&self) -> PaneGeom { + self.geom_override.unwrap_or(self.geom) + } + fn geom_override(&self) -> Option<PaneGeom> { + self.geom_override + } + fn should_render(&self) -> bool { + unimplemented!() + } + fn set_should_render(&mut self, _should_render: bool) { + unimplemented!() + } + fn set_should_render_boundaries(&mut self, _should_render: bool) { + unimplemented!() + } + fn selectable(&self) -> bool { + unimplemented!() + } + fn set_selectable(&mut self, _selectable: bool) { + unimplemented!() + } + fn render( + &mut self, + _client_id: Option<ClientId>, + ) -> Result<Option<(Vec<CharacterChunk>, Option<String>, Vec<SixelImageChunk>)>> { + unimplemented!() + } + fn render_frame( + &mut self, + _client_id: ClientId, + _frame_params: FrameParams, + _input_mode: InputMode, + ) -> Result<Option<(Vec<CharacterChunk>, Option<String>)>> { + unimplemented!() + } + fn render_fake_cursor( + &mut self, + _cursor_color: PaletteColor, + _text_color: PaletteColor, + ) -> Option<String> { + unimplemented!() + } + fn render_terminal_title(&mut self, _input_mode: InputMode) -> String { + unimplemented!() + } + fn update_name(&mut self, _name: &str) { + unimplemented!() + } + fn pid(&self) -> PaneId { + self.pid + } + fn reduce_height(&mut self, _percent: f64) { + unimplemented!() + } + fn increase_height(&mut self, _percent: f64) { + unimplemented!() + } + fn reduce_width(&mut self, _percent: f64) { + unimplemented!() + } + fn increase_width(&mut self, _percent: f64) { + unimplemented!() + } + fn push_down(&mut self, _count: usize) { + unimplemented!() + } + fn push_right(&mut self, _count: usize) { + unimplemented!() + } + fn pull_left(&mut self, _count: usize) { + unimplemented!() + } + fn pull_up(&mut self, _count: usize) { + unimplemented!() + } + fn clear_screen(&mut self) { + unimplemented!() + } + fn scroll_up(&mut self, _count: usize, _client_id: ClientId) { + unimplemented!() + } + fn scroll_down(&mut self, _count: usize, _client_id: ClientId) { + unimplemented!() + } + fn clear_scroll(&mut self) { + unimplemented!() + } + fn is_scrolled(&self) -> bool { + unimplemented!() + } + fn active_at(&self) -> Instant { + unimplemented!() + } + fn set_active_at(&mut self, _instant: Instant) { + unimplemented!() + } + fn set_frame(&mut self, _frame: bool) { + unimplemented!() + } + fn set_content_offset(&mut self, _offset: Offset) { + unimplemented!() + } + fn store_pane_name(&mut self) { + unimplemented!() + } + fn load_pane_name(&mut self) { + unimplemented!() + } + fn set_borderless(&mut self, _borderless: bool) { + unimplemented!() + } + fn borderless(&self) -> bool { + unimplemented!() + } + fn set_exclude_from_sync(&mut self, _exclude_from_sync: bool) { + unimplemented!() + } + fn exclude_from_sync(&self) -> bool { + unimplemented!() + } + fn add_red_pane_frame_color_override(&mut self, _error_text: Option<String>) { + unimplemented!() + } + fn clear_pane_frame_color_override(&mut self, _client_id: Option<ClientId>) { + unimplemented!() + } + fn frame_color_override(&self) -> Option<PaletteColor> { + unimplemented!() + } + fn invoked_with(&self) -> &Option<LayoutRun> { + unimplemented!() + } + fn set_title(&mut self, _title: String) { + unimplemented!() + } + fn current_title(&self) -> String { + unimplemented!() + } + fn custom_title(&self) -> Option<String> { + unimplemented!() + } + fn pane_contents( + &self, + _client_id: Option<ClientId>, + _get_full_scrollback: bool, + _max_scrollback_lines: Option<usize>, + ) -> PaneContents { + unimplemented!() + } +} diff --git a/zellij-server/src/panes/tiled_panes/unit/pane_resizer_tests.rs b/zellij-server/src/panes/tiled_panes/unit/pane_resizer_tests.rs new file mode 100644 index 00000000..c21a14f5 --- /dev/null +++ b/zellij-server/src/panes/tiled_panes/unit/pane_resizer_tests.rs @@ -0,0 +1,911 @@ +#![allow(dead_code)] + +use super::pane_resizer_test_mock::{ + dim_fixed, dim_pct, run_layout, spec, spec_stacked, with_override, Outcome, PaneSnapshot, + PaneSpec, Run, +}; +use crate::panes::PaneId; +use std::collections::BTreeMap; +use zellij_utils::input::layout::SplitDirection; +use zellij_utils::pane_size::{Constraint, PaneGeom}; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Class { + A, + B, + Artefact, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Axis { + Cols, + Rows, +} + +struct Fixture { + name: &'static str, + specs: Vec<PaneSpec>, + direction: SplitDirection, + space: usize, + class: Class, + bands: Vec<Vec<PaneId>>, +} + +impl Fixture { + fn axis(&self) -> Axis { + match self.direction { + SplitDirection::Horizontal => Axis::Cols, + SplitDirection::Vertical => Axis::Rows, + } + } + fn run(&self) -> Run { + run_layout(&self.specs, self.direction, self.space) + } + fn has_stacked(&self) -> bool { + self.specs.iter().any(|s| s.stacked.is_some()) + } +} + +fn tid(id: u32) -> PaneId { + PaneId::Terminal(id) +} + +fn fixtures() -> Vec<Fixture> { + let h = SplitDirection::Horizontal; + let v = SplitDirection::Vertical; + vec![ + Fixture { + name: "single_pane_grows_to_space", + specs: vec![spec(1, 0, 0, dim_pct(100.0, 40), dim_pct(100.0, 10))], + direction: h, + space: 80, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "empty_pane_map_reports_unchanged", + specs: vec![], + direction: h, + space: 80, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "two_panes_split_available_space", + specs: vec![ + spec(1, 0, 0, dim_pct(50.0, 40), dim_pct(100.0, 10)), + spec(2, 40, 0, dim_pct(50.0, 40), dim_pct(100.0, 10)), + ], + direction: h, + space: 100, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "identical_sizes_report_unchanged", + specs: vec![ + spec(1, 0, 0, dim_pct(50.0, 40), dim_pct(100.0, 10)), + spec(2, 40, 0, dim_pct(50.0, 40), dim_pct(100.0, 10)), + ], + direction: h, + space: 80, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "unchanged_sizes_still_reposition", + specs: vec![ + spec(1, 0, 0, dim_pct(50.0, 40), dim_pct(100.0, 10)), + spec(2, 45, 0, dim_pct(50.0, 40), dim_pct(100.0, 10)), + ], + direction: h, + space: 80, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "vertical_split_resizes_rows", + specs: vec![ + spec(1, 0, 0, dim_pct(100.0, 80), dim_pct(50.0, 10)), + spec(2, 0, 10, dim_pct(100.0, 80), dim_pct(50.0, 10)), + ], + direction: v, + space: 30, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "three_panes_round_up_first", + specs: vec![ + spec(1, 0, 0, dim_pct(33.3, 26), dim_pct(100.0, 10)), + spec(2, 26, 0, dim_pct(33.3, 27), dim_pct(100.0, 10)), + spec(3, 53, 0, dim_pct(33.4, 27), dim_pct(100.0, 10)), + ], + direction: h, + space: 100, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "over_rounded_band_shrinks_last", + specs: vec![ + spec(1, 0, 0, dim_pct(33.4999, 33), dim_pct(100.0, 10)), + spec(2, 33, 0, dim_pct(33.4999, 33), dim_pct(100.0, 10)), + spec(3, 66, 0, dim_pct(33.0002, 34), dim_pct(100.0, 10)), + ], + direction: h, + space: 100, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "fixed_pane_locks_and_percent_absorbs", + specs: vec![ + spec(1, 0, 0, dim_fixed(20), dim_pct(100.0, 10)), + spec(2, 20, 0, dim_pct(100.0, 60), dim_pct(100.0, 10)), + ], + direction: h, + space: 100, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "fixed_pane_excluded_from_rounding", + specs: vec![ + spec(1, 0, 0, dim_fixed(21), dim_pct(100.0, 10)), + spec(2, 21, 0, dim_pct(50.0, 30), dim_pct(100.0, 10)), + spec(3, 51, 0, dim_pct(50.0, 29), dim_pct(100.0, 10)), + ], + direction: h, + space: 100, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "pane_spanning_two_bands_adjusted_once", + specs: vec![ + spec(1, 0, 0, dim_pct(33.3, 26), dim_pct(50.0, 10)), + spec(3, 26, 0, dim_pct(66.7, 54), dim_pct(100.0, 20)), + spec(4, 0, 10, dim_pct(16.65, 13), dim_pct(50.0, 10)), + spec(5, 13, 10, dim_pct(16.65, 13), dim_pct(50.0, 10)), + ], + direction: h, + space: 100, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "stack_main_pane_carries_whole_stack", + specs: vec![ + spec_stacked(1, 0, 0, dim_pct(50.0, 40), dim_fixed(1), 0), + spec_stacked(2, 0, 1, dim_pct(50.0, 40), dim_pct(50.0, 8), 0), + spec_stacked(3, 0, 9, dim_pct(50.0, 40), dim_fixed(1), 0), + spec(4, 40, 0, dim_pct(50.0, 40), dim_pct(100.0, 10)), + ], + direction: v, + space: 12, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "short_stack_aborts_without_error", + specs: vec![ + spec_stacked(1, 0, 0, dim_pct(100.0, 80), dim_fixed(1), 0), + spec_stacked(2, 0, 1, dim_pct(100.0, 80), dim_pct(100.0, 8), 0), + spec_stacked(3, 0, 9, dim_pct(100.0, 80), dim_fixed(1), 0), + ], + direction: v, + space: 2, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "override_pane_updates_override_only", + specs: { + let one = spec(1, 0, 0, dim_pct(50.0, 40), dim_pct(100.0, 10)); + let two = spec(2, 40, 0, dim_pct(50.0, 40), dim_pct(100.0, 10)); + vec![one, with_override(two, two.geom())] + }, + direction: h, + space: 100, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "no_room_for_all_spans", + specs: vec![ + spec(1, 0, 0, dim_pct(50.0, 40), dim_pct(100.0, 10)), + spec(2, 40, 0, dim_pct(50.0, 40), dim_pct(100.0, 10)), + ], + direction: h, + space: 1, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "conflicting_required_constraints_fail", + specs: vec![spec(1, 0, 0, dim_fixed(10), dim_pct(100.0, 10))], + direction: h, + space: 20, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "stack_without_flexible_pane_is_ignored", + specs: vec![ + spec_stacked(1, 0, 0, dim_pct(100.0, 80), dim_fixed(1), 0), + spec_stacked(2, 0, 1, dim_pct(100.0, 80), dim_fixed(1), 0), + ], + direction: v, + space: 20, + class: Class::A, + bands: vec![], + }, + Fixture { + name: "under_specified_percents", + specs: vec![ + spec(1, 0, 0, dim_pct(30.0, 40), dim_pct(100.0, 10)), + spec(2, 40, 0, dim_pct(30.0, 40), dim_pct(100.0, 10)), + ], + direction: h, + space: 100, + class: Class::B, + bands: vec![vec![tid(1), tid(2)]], + }, + Fixture { + name: "over_specified_percents", + specs: vec![ + spec(1, 0, 0, dim_pct(60.0, 40), dim_pct(100.0, 10)), + spec(2, 40, 0, dim_pct(60.0, 40), dim_pct(100.0, 10)), + ], + direction: h, + space: 100, + class: Class::B, + bands: vec![vec![tid(1), tid(2)]], + }, + Fixture { + name: "degenerate_across_two_bands", + specs: vec![ + spec(1, 0, 0, dim_pct(60.0, 26), dim_pct(50.0, 10)), + spec(3, 26, 0, dim_pct(60.0, 54), dim_pct(100.0, 20)), + spec(4, 0, 10, dim_pct(30.0, 13), dim_pct(50.0, 10)), + spec(5, 13, 10, dim_pct(30.0, 13), dim_pct(50.0, 10)), + ], + direction: h, + space: 100, + class: Class::B, + bands: vec![vec![tid(1), tid(3)], vec![tid(4), tid(5), tid(3)]], + }, + Fixture { + name: "fixed_and_degenerate_percents", + specs: vec![ + spec(1, 0, 0, dim_fixed(20), dim_pct(100.0, 10)), + spec(2, 20, 0, dim_pct(60.0, 30), dim_pct(100.0, 10)), + spec(3, 50, 0, dim_pct(60.0, 30), dim_pct(100.0, 10)), + ], + direction: h, + space: 100, + class: Class::B, + bands: vec![vec![tid(1), tid(2), tid(3)]], + }, + Fixture { + name: "zero_flex_space_division", + specs: vec![ + spec(1, 0, 0, dim_fixed(20), dim_pct(100.0, 10)), + spec(2, 20, 0, dim_pct(50.0, 20), dim_pct(100.0, 10)), + ], + direction: h, + space: 20, + class: Class::Artefact, + bands: vec![], + }, + Fixture { + name: "saturated_flex_space_division", + specs: vec![ + spec(1, 0, 0, dim_fixed(30), dim_pct(100.0, 10)), + spec(2, 30, 0, dim_pct(50.0, 20), dim_pct(100.0, 10)), + ], + direction: h, + space: 20, + class: Class::Artefact, + bands: vec![], + }, + Fixture { + name: "stacked_pane_with_override", + specs: { + let two = spec_stacked(2, 0, 1, dim_pct(100.0, 80), dim_pct(100.0, 8), 0); + vec![ + spec_stacked(1, 0, 0, dim_pct(100.0, 80), dim_fixed(1), 0), + with_override(two, two.geom()), + spec_stacked(3, 0, 9, dim_pct(100.0, 80), dim_fixed(1), 0), + ] + }, + direction: SplitDirection::Vertical, + space: 20, + class: Class::Artefact, + bands: vec![], + }, + ] +} + +fn fixture(name: &str) -> Fixture { + fixtures() + .into_iter() + .find(|f| f.name == name) + .unwrap_or_else(|| panic!("unknown fixture `{}`", name)) +} + +fn pane(run: &Run, id: u32) -> PaneSnapshot { + *run.panes + .iter() + .find(|p| p.id == PaneId::Terminal(id)) + .unwrap_or_else(|| panic!("no snapshot for pane {}", id)) +} + +fn input_spec(specs: &[PaneSpec], id: PaneId) -> PaneSpec { + *specs + .iter() + .find(|s| s.id == id) + .unwrap_or_else(|| panic!("no input spec for {:?}", id)) +} + +fn assert_ok(run: &Run, name: &str) { + assert_eq!(run.outcome, Outcome::Ok, "fixture `{}`", name); +} + +fn assert_geom_field_eq(actual: &PaneGeom, expected: &PaneGeom, ctx: &str) { + assert_eq!(actual.x, expected.x, "{}: x", ctx); + assert_eq!(actual.y, expected.y, "{}: y", ctx); + assert_eq!( + actual.cols.constraint, expected.cols.constraint, + "{}: cols.constraint", + ctx + ); + assert_eq!( + actual.cols.as_usize(), + expected.cols.as_usize(), + "{}: cols.inner", + ctx + ); + assert_eq!( + actual.rows.constraint, expected.rows.constraint, + "{}: rows.constraint", + ctx + ); + assert_eq!( + actual.rows.as_usize(), + expected.rows.as_usize(), + "{}: rows.inner", + ctx + ); + assert_eq!(actual.stacked, expected.stacked, "{}: stacked", ctx); + assert_eq!(actual.is_pinned, expected.is_pinned, "{}: is_pinned", ctx); + assert_eq!( + actual.logical_position, expected.logical_position, + "{}: logical_position", + ctx + ); +} + +fn axis_dim(geom: &PaneGeom, axis: Axis) -> (usize, usize, Constraint) { + match axis { + Axis::Cols => (geom.x, geom.cols.as_usize(), geom.cols.constraint), + Axis::Rows => (geom.y, geom.rows.as_usize(), geom.rows.constraint), + } +} + +fn assert_result_is_benign(run: &Run, name: &str) { + match &run.outcome { + Outcome::Ok | Outcome::PaneSizeUnchanged => {}, + Outcome::OtherErr(msg) => panic!("fixture `{}` produced an error: {}", name, msg), + } +} + +fn assert_constraints_preserved(run: &Run, input: &[PaneSpec], axis: Axis, name: &str) { + for snapshot in &run.panes { + let expected = input_spec(input, snapshot.id).geom(); + let (_, _, actual_constraint) = axis_dim(&snapshot.current, axis); + let (_, _, expected_constraint) = axis_dim(&expected, axis); + assert_eq!( + actual_constraint, expected_constraint, + "fixture `{}`: {:?} constraint on {:?}", + name, snapshot.id, axis + ); + } +} + +fn assert_cross_axis_untouched(run: &Run, input: &[PaneSpec], axis: Axis, name: &str) { + for snapshot in &run.panes { + let expected = input_spec(input, snapshot.id).geom(); + match axis { + Axis::Cols => { + assert_eq!( + snapshot.current.y, expected.y, + "fixture `{}`: {:?} y", + name, snapshot.id + ); + assert_eq!( + snapshot.current.rows.constraint, expected.rows.constraint, + "fixture `{}`: {:?} rows.constraint", + name, snapshot.id + ); + assert_eq!( + snapshot.current.rows.as_usize(), + expected.rows.as_usize(), + "fixture `{}`: {:?} rows.inner", + name, + snapshot.id + ); + }, + Axis::Rows => { + assert_eq!( + snapshot.current.x, expected.x, + "fixture `{}`: {:?} x", + name, snapshot.id + ); + assert_eq!( + snapshot.current.cols.constraint, expected.cols.constraint, + "fixture `{}`: {:?} cols.constraint", + name, snapshot.id + ); + assert_eq!( + snapshot.current.cols.as_usize(), + expected.cols.as_usize(), + "fixture `{}`: {:?} cols.inner", + name, + snapshot.id + ); + }, + } + } +} + +fn assert_metadata_preserved(run: &Run, input: &[PaneSpec], name: &str) { + for snapshot in &run.panes { + let expected = input_spec(input, snapshot.id).geom(); + assert_eq!( + snapshot.current.stacked, expected.stacked, + "fixture `{}`: {:?} stacked", + name, snapshot.id + ); + assert_eq!( + snapshot.current.logical_position, expected.logical_position, + "fixture `{}`: {:?} logical_position", + name, snapshot.id + ); + } +} + +fn assert_min_size(run: &Run, axis: Axis, name: &str) { + for snapshot in &run.panes { + let (_, size, _) = axis_dim(&snapshot.current, axis); + assert!( + size >= 1, + "fixture `{}`: {:?} has {:?} size {}", + name, + snapshot.id, + axis, + size + ); + } +} + +fn assert_fixed_panes_locked(run: &Run, input: &[PaneSpec], axis: Axis, name: &str) { + for snapshot in &run.panes { + let expected = input_spec(input, snapshot.id).geom(); + if let (_, _, Constraint::Fixed(n)) = axis_dim(&expected, axis) { + let (_, size, _) = axis_dim(&snapshot.current, axis); + assert_eq!( + size, n, + "fixture `{}`: {:?} fixed pane size drifted", + name, snapshot.id + ); + } + } +} + +fn assert_band_partitions_space(run: &Run, band: &[PaneId], axis: Axis, space: usize, name: &str) { + let mut members: Vec<(usize, usize)> = band + .iter() + .map(|id| { + let snapshot = run + .panes + .iter() + .find(|p| p.id == *id) + .unwrap_or_else(|| panic!("fixture `{}`: no snapshot for {:?}", name, id)); + let (pos, size, _) = axis_dim(&snapshot.current, axis); + (pos, size) + }) + .collect(); + members.sort_by_key(|(pos, _)| *pos); + assert!( + !members.is_empty(), + "fixture `{}`: empty band supplied", + name + ); + assert_eq!( + members[0].0, 0, + "fixture `{}`: band {:?} does not start at 0", + name, band + ); + for window in members.windows(2) { + assert_eq!( + window[1].0, + window[0].0 + window[0].1, + "fixture `{}`: band {:?} has a gap or overlap", + name, + band + ); + } + let last = members[members.len() - 1]; + assert_eq!( + last.0 + last.1, + space, + "fixture `{}`: band {:?} does not fill {}", + name, + band, + space + ); +} + +fn assert_no_mutation(run: &Run, input: &[PaneSpec], name: &str) { + for snapshot in &run.panes { + let spec = input_spec(input, snapshot.id); + assert_geom_field_eq( + &snapshot.geom, + &spec.geom(), + &format!("fixture `{}`: {:?} geom", name, snapshot.id), + ); + let expected_current = spec.geom_override.unwrap_or_else(|| spec.geom()); + assert_geom_field_eq( + &snapshot.current, + &expected_current, + &format!("fixture `{}`: {:?} current", name, snapshot.id), + ); + match (snapshot.geom_override, spec.geom_override) { + (None, None) => {}, + (Some(actual), Some(expected)) => assert_geom_field_eq( + &actual, + &expected, + &format!("fixture `{}`: {:?} geom_override", name, snapshot.id), + ), + (actual, expected) => panic!( + "fixture `{}`: {:?} geom_override {:?} != {:?}", + name, snapshot.id, actual, expected + ), + } + } +} + +fn assert_class_b_invariants(fixture: &Fixture, run: &Run) { + let axis = fixture.axis(); + assert_result_is_benign(run, fixture.name); + assert_constraints_preserved(run, &fixture.specs, axis, fixture.name); + if !fixture.has_stacked() { + assert_cross_axis_untouched(run, &fixture.specs, axis, fixture.name); + } + assert_metadata_preserved(run, &fixture.specs, fixture.name); + assert_min_size(run, axis, fixture.name); + assert_fixed_panes_locked(run, &fixture.specs, axis, fixture.name); + for band in &fixture.bands { + assert_band_partitions_space(run, band, axis, fixture.space, fixture.name); + } +} + +fn canonical(run: &Run) -> String { + let outcome = match &run.outcome { + Outcome::Ok => "ok".to_string(), + Outcome::PaneSizeUnchanged => "unchanged".to_string(), + Outcome::OtherErr(msg) => format!("err({})", msg), + }; + let mut out = outcome; + for snapshot in &run.panes { + out.push_str(&format!( + " | {:?} x={} y={} cols={:?}:{} rows={:?}:{} stacked={:?} logical={:?} override={}", + snapshot.id, + snapshot.geom.x, + snapshot.geom.y, + snapshot.geom.cols.constraint, + snapshot.geom.cols.as_usize(), + snapshot.geom.rows.constraint, + snapshot.geom.rows.as_usize(), + snapshot.geom.stacked, + snapshot.geom.logical_position, + match &snapshot.geom_override { + None => "none".to_string(), + Some(g) => format!( + "x={} y={} cols={:?}:{} rows={:?}:{} stacked={:?} logical={:?}", + g.x, + g.y, + g.cols.constraint, + g.cols.as_usize(), + g.rows.constraint, + g.rows.as_usize(), + g.stacked, + g.logical_position + ), + } + )); + } + out +} + +#[test] +fn single_pane_grows_to_space() { + let f = fixture("single_pane_grows_to_space"); + let run = f.run(); + assert_ok(&run, f.name); + let p = pane(&run, 1); + assert_eq!(p.geom.x, 0); + assert_eq!(p.geom.cols.constraint, Constraint::Percent(100.0)); + assert_eq!(p.geom.cols.as_usize(), 80); + assert_eq!(p.geom.y, 0); + assert_eq!(p.geom.rows.constraint, Constraint::Percent(100.0)); + assert_eq!(p.geom.rows.as_usize(), 10); +} + +#[test] +fn empty_pane_map_reports_unchanged() { + let f = fixture("empty_pane_map_reports_unchanged"); + let run = f.run(); + assert_eq!(run.outcome, Outcome::PaneSizeUnchanged); + assert!(run.panes.is_empty()); +} + +#[test] +fn two_panes_split_available_space() { + let f = fixture("two_panes_split_available_space"); + let run = f.run(); + assert_ok(&run, f.name); + let one = pane(&run, 1); + let two = pane(&run, 2); + assert_eq!(one.geom.x, 0); + assert_eq!(one.geom.cols.as_usize(), 50); + assert_eq!(two.geom.x, 50); + assert_eq!(two.geom.cols.as_usize(), 50); +} + +#[test] +fn identical_sizes_report_unchanged() { + let f = fixture("identical_sizes_report_unchanged"); + let run = f.run(); + assert_eq!(run.outcome, Outcome::PaneSizeUnchanged); + assert_no_mutation(&run, &f.specs, f.name); +} + +#[test] +fn unchanged_sizes_still_reposition() { + let f = fixture("unchanged_sizes_still_reposition"); + let run = f.run(); + assert_eq!(run.outcome, Outcome::PaneSizeUnchanged); + assert_eq!(pane(&run, 2).geom.x, 40); +} + +#[test] +fn vertical_split_resizes_rows() { + let f = fixture("vertical_split_resizes_rows"); + let run = f.run(); + assert_ok(&run, f.name); + let one = pane(&run, 1); + let two = pane(&run, 2); + assert_eq!(one.geom.y, 0); + assert_eq!(one.geom.rows.as_usize(), 15); + assert_eq!(two.geom.y, 15); + assert_eq!(two.geom.rows.as_usize(), 15); + assert_eq!(one.geom.x, 0); + assert_eq!(one.geom.cols.as_usize(), 80); + assert_eq!(two.geom.x, 0); + assert_eq!(two.geom.cols.as_usize(), 80); +} + +#[test] +fn three_panes_round_up_first() { + let f = fixture("three_panes_round_up_first"); + let run = f.run(); + assert_ok(&run, f.name); + let sizes: Vec<usize> = [1, 2, 3] + .iter() + .map(|id| pane(&run, *id).geom.cols.as_usize()) + .collect(); + let positions: Vec<usize> = [1, 2, 3].iter().map(|id| pane(&run, *id).geom.x).collect(); + assert_eq!(sizes, vec![34, 33, 33]); + assert_eq!(positions, vec![0, 34, 67]); +} + +#[test] +fn over_rounded_band_shrinks_last() { + let f = fixture("over_rounded_band_shrinks_last"); + let run = f.run(); + assert_ok(&run, f.name); + let sizes: Vec<usize> = [1, 2, 3] + .iter() + .map(|id| pane(&run, *id).geom.cols.as_usize()) + .collect(); + let positions: Vec<usize> = [1, 2, 3].iter().map(|id| pane(&run, *id).geom.x).collect(); + assert_eq!(sizes, vec![34, 33, 33]); + assert_eq!(positions, vec![0, 34, 67]); + for id in [1u32, 2, 3] { + assert_eq!( + pane(&run, id).geom.cols.constraint, + input_spec(&f.specs, tid(id)).cols.constraint + ); + } +} + +#[test] +fn fixed_pane_locks_and_percent_absorbs() { + let f = fixture("fixed_pane_locks_and_percent_absorbs"); + let run = f.run(); + assert_ok(&run, f.name); + let one = pane(&run, 1); + let two = pane(&run, 2); + assert_eq!(one.geom.x, 0); + assert_eq!(one.geom.cols.constraint, Constraint::Fixed(20)); + assert_eq!(one.geom.cols.as_usize(), 20); + assert_eq!(two.geom.x, 20); + assert_eq!(two.geom.cols.as_usize(), 80); +} + +#[test] +fn fixed_pane_excluded_from_rounding() { + let f = fixture("fixed_pane_excluded_from_rounding"); + let run = f.run(); + assert_ok(&run, f.name); + let sizes: Vec<usize> = [1, 2, 3] + .iter() + .map(|id| pane(&run, *id).geom.cols.as_usize()) + .collect(); + let positions: Vec<usize> = [1, 2, 3].iter().map(|id| pane(&run, *id).geom.x).collect(); + assert_eq!(sizes, vec![21, 40, 39]); + assert_eq!(positions, vec![0, 21, 61]); + assert_eq!(pane(&run, 1).geom.cols.constraint, Constraint::Fixed(21)); +} + +#[test] +fn pane_spanning_two_bands_adjusted_once() { + let f = fixture("pane_spanning_two_bands_adjusted_once"); + let run = f.run(); + assert_ok(&run, f.name); + let expected = [ + (1u32, 0usize, 33usize), + (3, 33, 67), + (4, 0, 17), + (5, 17, 16), + ]; + for (id, x, cols) in expected { + let p = pane(&run, id); + assert_eq!(p.geom.x, x, "pane {} x", id); + assert_eq!(p.geom.cols.as_usize(), cols, "pane {} cols", id); + } +} + +#[test] +fn stack_main_pane_carries_whole_stack() { + let f = fixture("stack_main_pane_carries_whole_stack"); + let run = f.run(); + assert_ok(&run, f.name); + let expected = [(1u32, 0usize, 1usize), (2, 1, 10), (3, 11, 1), (4, 0, 12)]; + for (id, y, rows) in expected { + let p = pane(&run, id); + assert_eq!(p.geom.y, y, "pane {} y", id); + assert_eq!(p.geom.rows.as_usize(), rows, "pane {} rows", id); + } +} + +#[test] +fn short_stack_aborts_without_error() { + let f = fixture("short_stack_aborts_without_error"); + let run = f.run(); + assert_ok(&run, f.name); + assert_no_mutation(&run, &f.specs, f.name); +} + +#[test] +fn override_pane_updates_override_only() { + let f = fixture("override_pane_updates_override_only"); + let run = f.run(); + assert_ok(&run, f.name); + let one = pane(&run, 1); + assert_eq!(one.geom.x, 0); + assert_eq!(one.geom.cols.as_usize(), 50); + assert_eq!(one.geom_override, None); + + let two = pane(&run, 2); + assert_geom_field_eq( + &two.geom, + &input_spec(&f.specs, tid(2)).geom(), + "pane 2 geom", + ); + let override_geom = two.geom_override.expect("pane 2 lost its geom_override"); + assert_eq!(override_geom.x, 50); + assert_eq!(override_geom.cols.as_usize(), 50); + assert_geom_field_eq(&two.current, &override_geom, "pane 2 current"); +} + +#[test] +fn no_room_for_all_spans() { + let f = fixture("no_room_for_all_spans"); + let run = f.run(); + match &run.outcome { + Outcome::OtherErr(msg) => assert!( + msg.contains("Ran out of room for spans"), + "unexpected error: {}", + msg + ), + other => panic!("expected OtherErr, got {:?}", other), + } + assert_no_mutation(&run, &f.specs, f.name); +} + +#[test] +fn conflicting_required_constraints_fail() { + let f = fixture("conflicting_required_constraints_fail"); + let run = f.run(); + assert!( + matches!(run.outcome, Outcome::OtherErr(_)), + "expected OtherErr, got {:?}", + run.outcome + ); + assert_ne!(run.outcome, Outcome::PaneSizeUnchanged); + if let Outcome::OtherErr(msg) = &run.outcome { + assert!( + !msg.contains("Ran out of room"), + "unexpected error: {}", + msg + ); + } + assert_no_mutation(&run, &f.specs, f.name); +} + +#[test] +fn stack_without_flexible_pane_is_ignored() { + let f = fixture("stack_without_flexible_pane_is_ignored"); + let run = f.run(); + assert_eq!(run.outcome, Outcome::PaneSizeUnchanged); + assert_no_mutation(&run, &f.specs, f.name); +} + +#[test] +fn under_specified_percents() { + let f = fixture("under_specified_percents"); + assert_class_b_invariants(&f, &f.run()); +} + +#[test] +fn over_specified_percents() { + let f = fixture("over_specified_percents"); + assert_class_b_invariants(&f, &f.run()); +} + +#[test] +fn degenerate_across_two_bands() { + let f = fixture("degenerate_across_two_bands"); + assert_class_b_invariants(&f, &f.run()); +} + +#[test] +fn fixed_and_degenerate_percents() { + let f = fixture("fixed_and_degenerate_percents"); + assert_class_b_invariants(&f, &f.run()); +} + +#[test] +fn zero_flex_space_division() { + let f = fixture("zero_flex_space_division"); + let run = f.run(); + println!("{} = {}", f.name, canonical(&run)); +} + +#[test] +fn saturated_flex_space_division() { + let f = fixture("saturated_flex_space_division"); + let run = f.run(); + println!("{} = {}", f.name, canonical(&run)); +} + +#[test] +fn stacked_pane_with_override() { + let f = fixture("stacked_pane_with_override"); + let run = f.run(); + println!("{} = {}", f.name, canonical(&run)); +} diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs index 1e6d7539..e9f8a93e 100644 --- a/zellij-server/src/tab/unit/tab_tests.rs +++ b/zellij-server/src/tab/unit/tab_tests.rs @@ -1159,7 +1159,7 @@ pub fn resize_while_fullscreen_updates_hidden_pane_geometry() { // When a host-terminal resize arrives while a pane is fullscreen, every // hidden pane's geometry must be updated to match the new display area. // Otherwise their `inner` cell counts stay sized for the old display and - // toggling fullscreen off hands the cassowary solver coordinates that + // toggling fullscreen off hands the layout solver coordinates that // fall outside the viewport, producing layout-solve failures and a // corrupt render. // @@ -1197,7 +1197,7 @@ pub fn resize_while_fullscreen_updates_hidden_pane_geometry() { // Collect the panes hidden by the fullscreen state and verify each one // already fits the new display area; if any extends beyond it, exiting - // fullscreen would hand the cassowary solver an unsatisfiable layout. + // fullscreen would hand the layout solver an unsatisfiable layout. let hidden_pane_ids: Vec<PaneId> = tab .tiled_panes .panes |
